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