LCOV - code coverage report
Current view: top level - js/src/vm - Compression.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 22 145 15.2 %
Date: 2017-07-14 16:53:18 Functions: 3 12 25.0 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.13