tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }