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