1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "vm/Compression.h"
8
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/MemoryChecking.h"
11 #include "mozilla/PodOperations.h"
12 #include "mozilla/ScopeExit.h"
13
14 #include "js/Utility.h"
15 #include "util/Memory.h"
16
17 using namespace js;
18
zlib_alloc(void * cx,uInt items,uInt size)19 static void* zlib_alloc(void* cx, uInt items, uInt size) {
20 return js_calloc(items, size);
21 }
22
zlib_free(void * cx,void * addr)23 static void zlib_free(void* cx, void* addr) { js_free(addr); }
24
Compressor(const unsigned char * inp,size_t inplen)25 Compressor::Compressor(const unsigned char* inp, size_t inplen)
26 : inp(inp),
27 inplen(inplen),
28 initialized(false),
29 finished(false),
30 currentChunkSize(0),
31 chunkOffsets() {
32 MOZ_ASSERT(inplen > 0, "data to compress can't be empty");
33
34 zs.opaque = nullptr;
35 zs.next_in = (Bytef*)inp;
36 zs.avail_in = 0;
37 zs.next_out = nullptr;
38 zs.avail_out = 0;
39 zs.zalloc = zlib_alloc;
40 zs.zfree = zlib_free;
41 zs.total_in = 0;
42 zs.total_out = 0;
43 zs.msg = nullptr;
44 zs.state = nullptr;
45 zs.data_type = 0;
46 zs.adler = 0;
47 zs.reserved = 0;
48
49 // Reserve space for the CompressedDataHeader.
50 outbytes = sizeof(CompressedDataHeader);
51 }
52
~Compressor()53 Compressor::~Compressor() {
54 if (initialized) {
55 int ret = deflateEnd(&zs);
56 if (ret != Z_OK) {
57 // If we finished early, we can get a Z_DATA_ERROR.
58 MOZ_ASSERT(ret == Z_DATA_ERROR);
59 MOZ_ASSERT(!finished);
60 }
61 }
62 }
63
64 // According to the zlib docs, the default value for windowBits is 15. Passing
65 // -15 is treated the same, but it also forces 'raw deflate' (no zlib header or
66 // trailer). Raw deflate is necessary for chunked decompression.
67 static const int WindowBits = -15;
68
init()69 bool Compressor::init() {
70 if (inplen >= UINT32_MAX) {
71 return false;
72 }
73 // zlib is slow and we'd rather be done compression sooner
74 // even if it means decompression is slower which penalizes
75 // Function.toString()
76 int ret = deflateInit2(&zs, Z_BEST_SPEED, Z_DEFLATED, WindowBits, 8,
77 Z_DEFAULT_STRATEGY);
78 if (ret != Z_OK) {
79 MOZ_ASSERT(ret == Z_MEM_ERROR);
80 return false;
81 }
82 initialized = true;
83 return true;
84 }
85
setOutput(unsigned char * out,size_t outlen)86 void Compressor::setOutput(unsigned char* out, size_t outlen) {
87 MOZ_ASSERT(outlen > outbytes);
88 zs.next_out = out + outbytes;
89 zs.avail_out = outlen - outbytes;
90 }
91
compressMore()92 Compressor::Status Compressor::compressMore() {
93 MOZ_ASSERT(zs.next_out);
94 uInt left = inplen - (zs.next_in - inp);
95 if (left <= MAX_INPUT_SIZE) {
96 zs.avail_in = left;
97 } else if (zs.avail_in == 0) {
98 zs.avail_in = MAX_INPUT_SIZE;
99 }
100
101 // Finish the current chunk if needed.
102 bool flush = false;
103 MOZ_ASSERT(currentChunkSize <= CHUNK_SIZE);
104 if (currentChunkSize + zs.avail_in >= CHUNK_SIZE) {
105 // Adjust avail_in, so we don't get chunks that are larger than
106 // CHUNK_SIZE.
107 zs.avail_in = CHUNK_SIZE - currentChunkSize;
108 MOZ_ASSERT(currentChunkSize + zs.avail_in == CHUNK_SIZE);
109 flush = true;
110 }
111
112 MOZ_ASSERT(zs.avail_in <= left);
113 bool done = zs.avail_in == left;
114
115 Bytef* oldin = zs.next_in;
116 Bytef* oldout = zs.next_out;
117 int ret = deflate(&zs, done ? Z_FINISH : (flush ? Z_FULL_FLUSH : Z_NO_FLUSH));
118 outbytes += zs.next_out - oldout;
119 currentChunkSize += zs.next_in - oldin;
120 MOZ_ASSERT(currentChunkSize <= CHUNK_SIZE);
121
122 if (ret == Z_MEM_ERROR) {
123 zs.avail_out = 0;
124 return OOM;
125 }
126 if (ret == Z_BUF_ERROR || (ret == Z_OK && zs.avail_out == 0)) {
127 // We have to resize the output buffer. Note that we're not done yet
128 // because ret != Z_STREAM_END.
129 MOZ_ASSERT(zs.avail_out == 0);
130 return MOREOUTPUT;
131 }
132
133 if (done || currentChunkSize == CHUNK_SIZE) {
134 MOZ_ASSERT_IF(!done, flush);
135 MOZ_ASSERT(chunkSize(inplen, chunkOffsets.length()) == currentChunkSize);
136 if (!chunkOffsets.append(outbytes)) {
137 return OOM;
138 }
139 currentChunkSize = 0;
140 MOZ_ASSERT_IF(done, chunkOffsets.length() == (inplen - 1) / CHUNK_SIZE + 1);
141 }
142
143 MOZ_ASSERT_IF(!done, ret == Z_OK);
144 MOZ_ASSERT_IF(done, ret == Z_STREAM_END);
145 return done ? DONE : CONTINUE;
146 }
147
totalBytesNeeded() const148 size_t Compressor::totalBytesNeeded() const {
149 return AlignBytes(outbytes, sizeof(uint32_t)) + sizeOfChunkOffsets();
150 }
151
finish(char * dest,size_t destBytes)152 void Compressor::finish(char* dest, size_t destBytes) {
153 MOZ_ASSERT(!chunkOffsets.empty());
154
155 CompressedDataHeader* compressedHeader =
156 reinterpret_cast<CompressedDataHeader*>(dest);
157 compressedHeader->compressedBytes = outbytes;
158
159 size_t outbytesAligned = AlignBytes(outbytes, sizeof(uint32_t));
160
161 // Zero the padding bytes, the ImmutableStringsCache will hash them.
162 mozilla::PodZero(dest + outbytes, outbytesAligned - outbytes);
163
164 uint32_t* destArr = reinterpret_cast<uint32_t*>(dest + outbytesAligned);
165
166 MOZ_ASSERT(uintptr_t(dest + destBytes) ==
167 uintptr_t(destArr + chunkOffsets.length()));
168 mozilla::PodCopy(destArr, chunkOffsets.begin(), chunkOffsets.length());
169
170 finished = true;
171 }
172
DecompressString(const unsigned char * inp,size_t inplen,unsigned char * out,size_t outlen)173 bool js::DecompressString(const unsigned char* inp, size_t inplen,
174 unsigned char* out, size_t outlen) {
175 MOZ_ASSERT(inplen <= UINT32_MAX);
176
177 // Mark the memory we pass to zlib as initialized for MSan.
178 MOZ_MAKE_MEM_DEFINED(out, outlen);
179
180 z_stream zs;
181 zs.zalloc = zlib_alloc;
182 zs.zfree = zlib_free;
183 zs.opaque = nullptr;
184 zs.next_in = (Bytef*)inp;
185 zs.avail_in = inplen;
186 zs.next_out = out;
187 MOZ_ASSERT(outlen);
188 zs.avail_out = outlen;
189 int ret = inflateInit(&zs);
190 if (ret != Z_OK) {
191 MOZ_ASSERT(ret == Z_MEM_ERROR);
192 return false;
193 }
194 ret = inflate(&zs, Z_FINISH);
195 MOZ_ASSERT(ret == Z_STREAM_END);
196 ret = inflateEnd(&zs);
197 MOZ_ASSERT(ret == Z_OK);
198 return true;
199 }
200
DecompressStringChunk(const unsigned char * inp,size_t chunk,unsigned char * out,size_t outlen)201 bool js::DecompressStringChunk(const unsigned char* inp, size_t chunk,
202 unsigned char* out, size_t outlen) {
203 MOZ_ASSERT(outlen <= Compressor::CHUNK_SIZE);
204
205 const CompressedDataHeader* header =
206 reinterpret_cast<const CompressedDataHeader*>(inp);
207
208 size_t compressedBytes = header->compressedBytes;
209 size_t compressedBytesAligned = AlignBytes(compressedBytes, sizeof(uint32_t));
210
211 const unsigned char* offsetBytes = inp + compressedBytesAligned;
212 const uint32_t* offsets = reinterpret_cast<const uint32_t*>(offsetBytes);
213
214 uint32_t compressedStart =
215 chunk > 0 ? offsets[chunk - 1] : sizeof(CompressedDataHeader);
216 uint32_t compressedEnd = offsets[chunk];
217
218 MOZ_ASSERT(compressedStart < compressedEnd);
219 MOZ_ASSERT(compressedEnd <= compressedBytes);
220
221 bool lastChunk = compressedEnd == compressedBytes;
222
223 // Mark the memory we pass to zlib as initialized for MSan.
224 MOZ_MAKE_MEM_DEFINED(out, outlen);
225
226 z_stream zs;
227 zs.zalloc = zlib_alloc;
228 zs.zfree = zlib_free;
229 zs.opaque = nullptr;
230 zs.next_in = (Bytef*)(inp + compressedStart);
231 zs.avail_in = compressedEnd - compressedStart;
232 zs.next_out = out;
233 MOZ_ASSERT(outlen);
234 zs.avail_out = outlen;
235
236 int ret = inflateInit2(&zs, WindowBits);
237 if (ret != Z_OK) {
238 MOZ_ASSERT(ret == Z_MEM_ERROR);
239 return false;
240 }
241
242 auto autoCleanup = mozilla::MakeScopeExit([&] {
243 mozilla::DebugOnly<int> ret = inflateEnd(&zs);
244 MOZ_ASSERT(ret == Z_OK);
245 });
246
247 if (lastChunk) {
248 ret = inflate(&zs, Z_FINISH);
249 MOZ_RELEASE_ASSERT(ret == Z_STREAM_END);
250 } else {
251 ret = inflate(&zs, Z_NO_FLUSH);
252 if (ret == Z_MEM_ERROR) {
253 return false;
254 }
255 MOZ_RELEASE_ASSERT(ret == Z_OK);
256 }
257 MOZ_ASSERT(zs.avail_in == 0);
258 MOZ_ASSERT(zs.avail_out == 0);
259 return true;
260 }
261