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