Bug 1396026 - Update OTS to accept Awami Nastaliq. r?jfkthame,froydnj draft 11-1396026
authorKevin Hsieh <kevin.hsieh@ucla.edu>
Thu, 07 Sep 2017 10:40:59 -0700
branch11-1396026
changeset 663459 835f412d6c56aaf59dadde7ac30d18ee0e91adf8
parent 658333 0afabd3e5c27b0036517b96eecb1f8553d027179
child 731218 37b7255f034ebb00f5324f6d4987e322f4fa6a28
push id79452
push userbmo:kevin.hsieh@ucla.edu
push dateWed, 13 Sep 2017 03:49:33 +0000
reviewersjfkthame, froydnj
bugs1396026
milestone57.0a1
Bug 1396026 - Update OTS to accept Awami Nastaliq. r?jfkthame,froydnj MozReview-Commit-ID: EvF3YDhuwNn
gfx/ots/README.mozilla
gfx/ots/ots-lz4.patch
gfx/ots/src/glat.cc
gfx/ots/src/layout.cc
gfx/ots/src/silf.cc
gfx/ots/src/sill.cc
mfbt/Compression.cpp
mfbt/Compression.h
--- a/gfx/ots/README.mozilla
+++ b/gfx/ots/README.mozilla
@@ -1,12 +1,12 @@
 This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
 
 Our reference repository is https://github.com/khaledhosny/ots/.
 
-Current revision: e2d4b5daba24e746a48d240e90d92fe09a20b681 (5.2.0)
+Current revision: f87b4556191e4132ef5c47365762eb88ace97fc3 (6.0.0)
 
 Upstream files included: LICENSE, src/, include/, tests/*.cc
 
 Additional files: README.mozilla, src/moz.build
 
 Additional patch: ots-visibility.patch (bug 711079).
 Additional patch: ots-lz4.patch
--- a/gfx/ots/ots-lz4.patch
+++ b/gfx/ots/ots-lz4.patch
@@ -5,66 +5,62 @@ diff --git a/gfx/ots/src/glat.cc b/gfx/o
  #include "glat.h"
  
  #include "gloc.h"
 -#include "lz4.h"
 +#include "mozilla/Compression.h"
  #include <list>
  
  namespace ots {
-@@ -201,13 +201,15 @@ bool OpenTypeGLAT_v3::Parse(const uint8_t* data, size_t length,
+@@ -201,14 +201,15 @@ bool OpenTypeGLAT_v3::Parse(const uint8_t* data, size_t length,
          return DropGraphite("Illegal nested compression");
        }
-       std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE, 0);
--      int ret = LZ4_decompress_safe(
--          reinterpret_cast<const char*>(data + table.offset()),
+       std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
+-      int ret = LZ4_decompress_safe_partial(
++      size_t outputSize = 0;
++      bool ret = mozilla::Compression::LZ4::decompressPartial(
+           reinterpret_cast<const char*>(data + table.offset()),
 -          reinterpret_cast<char*>(decompressed.data()),
--          table.remaining(),
--          decompressed.size());
--      if (ret < 0) {
+           table.remaining(),  // input buffer size (input size + padding)
++          reinterpret_cast<char*>(decompressed.data()),
+           decompressed.size(),  // target output size
+-          decompressed.size());  // output buffer size
+-      if (ret != decompressed.size()) {
 -        return DropGraphite("Decompression failed with error code %d", ret);
-+      size_t outputSize = 0;
-+      if (!mozilla::Compression::LZ4::decompress(
-+            reinterpret_cast<const char*>(data + table.offset()),
-+            table.remaining(),
-+            reinterpret_cast<char*>(decompressed.data()),
-+            decompressed.size(),
-+            &outputSize) ||
-+          outputSize != (this->compHead & FULL_SIZE)) {
++          &outputSize);  // return output size
++      if (!ret || outputSize != decompressed.size()) {
 +        return DropGraphite("Decompression failed");
        }
        return this->Parse(decompressed.data(), decompressed.size(), true);
      }
 diff --git a/gfx/ots/src/silf.cc b/gfx/ots/src/silf.cc
 --- a/gfx/ots/src/silf.cc
 +++ b/gfx/ots/src/silf.cc
 @@ -5,7 +5,7 @@
  #include "silf.h"
  
  #include "name.h"
 -#include "lz4.h"
 +#include "mozilla/Compression.h"
  #include <cmath>
  
  namespace ots {
-@@ -39,13 +39,15 @@ bool OpenTypeSILF::Parse(const uint8_t* data, size_t length,
+@@ -39,14 +39,15 @@ bool OpenTypeSILF::Parse(const uint8_t* data, size_t length,
            return DropGraphite("Illegal nested compression");
          }
-         std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE, 0);
--        int ret = LZ4_decompress_safe(
--            reinterpret_cast<const char*>(data + table.offset()),
+         std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
+-        int ret = LZ4_decompress_safe_partial(
++        size_t outputSize = 0;
++        bool ret = mozilla::Compression::LZ4::decompressPartial(
+             reinterpret_cast<const char*>(data + table.offset()),
 -            reinterpret_cast<char*>(decompressed.data()),
--            table.remaining(),
--            decompressed.size());
--        if (ret < 0) {
+             table.remaining(),  // input buffer size (input size + padding)
++            reinterpret_cast<char*>(decompressed.data()),
+             decompressed.size(),  // target output size
+-            decompressed.size());  // output buffer size
+-        if (ret != decompressed.size()) {
 -          return DropGraphite("Decompression failed with error code %d", ret);
-+        size_t outputSize = 0;
-+        if (!mozilla::Compression::LZ4::decompress(
-+              reinterpret_cast<const char*>(data + table.offset()),
-+              table.remaining(),
-+              reinterpret_cast<char*>(decompressed.data()),
-+              decompressed.size(),
-+              &outputSize) ||
-+            outputSize != (this->compHead & FULL_SIZE)) {
++            &outputSize);   // return output size
++        if (!ret || outputSize != decompressed.size()) {
 +          return DropGraphite("Decompression failed");
          }
          return this->Parse(decompressed.data(), decompressed.size(), true);
        }
--- a/gfx/ots/src/glat.cc
+++ b/gfx/ots/src/glat.cc
@@ -195,25 +195,25 @@ bool OpenTypeGLAT_v3::Parse(const uint8_
   }
   switch ((this->compHead & SCHEME) >> 27) {
     case 0:  // uncompressed
       break;
     case 1: {  // lz4
       if (prevent_decompression) {
         return DropGraphite("Illegal nested compression");
       }
-      std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE, 0);
+      std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
       size_t outputSize = 0;
-      if (!mozilla::Compression::LZ4::decompress(
-            reinterpret_cast<const char*>(data + table.offset()),
-            table.remaining(),
-            reinterpret_cast<char*>(decompressed.data()),
-            decompressed.size(),
-            &outputSize) ||
-          outputSize != (this->compHead & FULL_SIZE)) {
+      bool ret = mozilla::Compression::LZ4::decompressPartial(
+          reinterpret_cast<const char*>(data + table.offset()),
+          table.remaining(),  // input buffer size (input size + padding)
+          reinterpret_cast<char*>(decompressed.data()),
+          decompressed.size(),  // target output size
+          &outputSize);  // return output size
+      if (!ret || outputSize != decompressed.size()) {
         return DropGraphite("Decompression failed");
       }
       return this->Parse(decompressed.data(), decompressed.size(), true);
     }
     default:
       return DropGraphite("Unknown compression scheme");
   }
   if (this->compHead & RESERVED) {
@@ -383,17 +383,17 @@ GlatEntry::ParsePart(Buffer& table) {
   if (!table.ReadS16(&this->attNum)) {
     return parent->Error("GlatEntry: Failed to read attNum");
   }
   if (!table.ReadS16(&this->num) || this->num < 0) {
     return parent->Error("GlatEntry: Failed to read valid num");
   }
 
   //this->attributes.resize(this->num);
-  for (unsigned i = 0; i < this->num; ++i) {
+  for (int i = 0; i < this->num; ++i) {
     this->attributes.emplace_back();
     if (!table.ReadS16(&this->attributes[i])) {
       return parent->Error("GlatEntry: Failed to read attribute %u", i);
     }
   }
   return true;
 }
 
--- a/gfx/ots/src/layout.cc
+++ b/gfx/ots/src/layout.cc
@@ -306,42 +306,45 @@ bool ParseClassDefFormat1(const ots::Fon
 bool ParseClassDefFormat2(const ots::Font *font,
                           const uint8_t *data, size_t length,
                           const uint16_t num_glyphs,
                           const uint16_t num_classes) {
   ots::Buffer subtable(data, length);
 
   // Skip format field.
   if (!subtable.Skip(2)) {
-    return OTS_FAILURE_MSG("Failed to skip format of class defintion header");
+    return OTS_FAILURE_MSG("Failed to read class definition format");
   }
 
   uint16_t range_count = 0;
   if (!subtable.ReadU16(&range_count)) {
-    return OTS_FAILURE_MSG("Failed to read range count in class definition");
+    return OTS_FAILURE_MSG("Failed to read classRangeCount");
   }
   if (range_count > num_glyphs) {
-    return OTS_FAILURE_MSG("bad range count: %u", range_count);
+    return OTS_FAILURE_MSG("classRangeCount > glyph count: %u > %u", range_count, num_glyphs);
   }
 
   uint16_t last_end = 0;
   for (unsigned i = 0; i < range_count; ++i) {
     uint16_t start = 0;
     uint16_t end = 0;
     uint16_t class_value = 0;
     if (!subtable.ReadU16(&start) ||
         !subtable.ReadU16(&end) ||
         !subtable.ReadU16(&class_value)) {
-      return OTS_FAILURE_MSG("Failed to read class definition reange %d", i);
+      return OTS_FAILURE_MSG("Failed to read ClassRangeRecord %d", i);
     }
-    if (start > end || (last_end && start <= last_end)) {
-      return OTS_FAILURE_MSG("glyph range is overlapping.in range %d", i);
+    if (start > end) {
+      return OTS_FAILURE_MSG("ClassRangeRecord %d, start > end: %u > %u", i, start, end);
+    }
+    if (last_end && start <= last_end) {
+      return OTS_FAILURE_MSG("ClassRangeRecord %d start overlaps with end of the previous one: %u <= %u", i, start, last_end);
     }
     if (class_value > num_classes) {
-      return OTS_FAILURE_MSG("bad class value: %u", class_value);
+      return OTS_FAILURE_MSG("ClassRangeRecord %d class > number of classes: %u > %u", i, class_value, num_classes);
     }
     last_end = end;
   }
 
   return true;
 }
 
 bool ParseCoverageFormat1(const ots::Font *font,
--- a/gfx/ots/src/silf.cc
+++ b/gfx/ots/src/silf.cc
@@ -33,25 +33,25 @@ bool OpenTypeSILF::Parse(const uint8_t* 
   if (this->version >> 16 >= 5) {
     switch ((this->compHead & SCHEME) >> 27) {
       case 0:  // uncompressed
         break;
       case 1: {  // lz4
         if (prevent_decompression) {
           return DropGraphite("Illegal nested compression");
         }
-        std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE, 0);
+        std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
         size_t outputSize = 0;
-        if (!mozilla::Compression::LZ4::decompress(
-              reinterpret_cast<const char*>(data + table.offset()),
-              table.remaining(),
-              reinterpret_cast<char*>(decompressed.data()),
-              decompressed.size(),
-              &outputSize) ||
-            outputSize != (this->compHead & FULL_SIZE)) {
+        bool ret = mozilla::Compression::LZ4::decompressPartial(
+            reinterpret_cast<const char*>(data + table.offset()),
+            table.remaining(),  // input buffer size (input size + padding)
+            reinterpret_cast<char*>(decompressed.data()),
+            decompressed.size(),  // target output size
+            &outputSize);   // return output size
+        if (!ret || outputSize != decompressed.size()) {
           return DropGraphite("Decompression failed");
         }
         return this->Parse(decompressed.data(), decompressed.size(), true);
       }
       default:
         return DropGraphite("Unknown compression scheme");
     }
   }
@@ -250,18 +250,23 @@ bool OpenTypeSILF::SILSub::ParsePart(Buf
   //this->scriptTag.resize(this->numScriptTag);
   for (unsigned i = 0; i < this->numScriptTag; ++i) {
     this->scriptTag.emplace_back();
     if (!table.ReadU32(&this->scriptTag[i])) {
       return parent->Error("SILSub: Failed to read scriptTag[%u]", i);
     }
   }
 
-  if (!table.ReadU16(&this->lbGID) || this->lbGID > this->maxGlyphID) {
-    return parent->Error("SILSub: Failed to read valid lbGID");
+  if (!table.ReadU16(&this->lbGID)) {
+    return parent->Error("SILSub: Failed to read lbGID");
+  }
+  if (this->lbGID > this->maxGlyphID) {
+    parent->Warning("SILSub: lbGID %u outside range 0..%u, replaced with 0",
+                    this->lbGID, this->maxGlyphID);
+    this->lbGID = 0;
   }
 
   if (parent->version >> 16 >= 3 &&
       table.offset() != init_offset + this->passOffset) {
     return parent->Error("SILSub: passOffset check failed");
   }
   unsigned long last_oPass = 0;
   //this->oPasses.resize(static_cast<unsigned>(this->numPasses) + 1);
@@ -275,29 +280,37 @@ bool OpenTypeSILF::SILSub::ParsePart(Buf
 
   if (parent->version >> 16 >= 3 &&
       table.offset() != init_offset + this->pseudosOffset) {
     return parent->Error("SILSub: pseudosOffset check failed");
   }
   if (!table.ReadU16(&this->numPseudo)) {
     return parent->Error("SILSub: Failed to read numPseudo");
   }
-  if (!table.ReadU16(&this->searchPseudo) || this->searchPseudo !=
-      (this->numPseudo == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::pow(2, std::floor(std::log2(this->numPseudo))))) {
-    return parent->Error("SILSub: Failed to read valid searchPseudo");
+
+  // The following three fields are deprecated and ignored. We fix them up here
+  // just for internal consistency, but the Graphite engine doesn't care.
+  if (!table.ReadU16(&this->searchPseudo) ||
+      !table.ReadU16(&this->pseudoSelector) ||
+      !table.ReadU16(&this->pseudoShift)) {
+    return parent->Error("SILSub: Failed to read searchPseudo..pseudoShift");
   }
-  if (!table.ReadU16(&this->pseudoSelector) || this->pseudoSelector !=
-      (this->numPseudo == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::floor(std::log2(this->numPseudo)))) {
-    return parent->Error("SILSub: Failed to read valid pseudoSelector");
-  }
-  if (!table.ReadU16(&this->pseudoShift) ||
-      this->pseudoShift != this->numPseudo - this->searchPseudo) {
-    return parent->Error("SILSub: Failed to read valid pseudoShift");
+  if (this->numPseudo == 0) {
+    if (this->searchPseudo != 0 || this->pseudoSelector != 0 || this->pseudoShift != 0) {
+      this->searchPseudo = this->pseudoSelector = this->pseudoShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numPseudo));
+    if (this->searchPseudo != 6 * (unsigned)std::pow(2, floorLog2) ||
+        this->pseudoSelector != floorLog2 ||
+        this->pseudoShift != 6 * this->numPseudo - this->searchPseudo) {
+      this->searchPseudo = 6 * (unsigned)std::pow(2, floorLog2);
+      this->pseudoSelector = floorLog2;
+      this->pseudoShift = 6 * this->numPseudo - this->searchPseudo;
+    }
   }
 
   //this->pMaps.resize(this->numPseudo, parent);
   for (unsigned i = 0; i < numPseudo; i++) {
     this->pMaps.emplace_back(parent);
     if (!this->pMaps[i].ParsePart(table)) {
       return parent->Error("SILSub: Failed to read pMaps[%u]", i);
     }
@@ -536,29 +549,36 @@ ClassMap::SerializePart(OTSStream* out) 
   return true;
 }
 
 bool OpenTypeSILF::SILSub::ClassMap::
 LookupClass::ParsePart(Buffer& table) {
   if (!table.ReadU16(&this->numIDs)) {
     return parent->Error("LookupClass: Failed to read numIDs");
   }
-  if (!table.ReadU16(&this->searchRange) || this->searchRange !=
-      (this->numIDs == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::pow(2, std::floor(std::log2(this->numIDs))))) {
-    return parent->Error("LookupClass: Failed to read valid searchRange");
+  if (!table.ReadU16(&this->searchRange) ||
+      !table.ReadU16(&this->entrySelector) ||
+      !table.ReadU16(&this->rangeShift)) {
+    return parent->Error("LookupClass: Failed to read searchRange..rangeShift");
   }
-  if (!table.ReadU16(&this->entrySelector) || this->entrySelector !=
-      (this->numIDs == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::floor(std::log2(this->numIDs)))) {
-    return parent->Error("LookupClass: Failed to read valid entrySelector");
-  }
-  if (!table.ReadU16(&this->rangeShift) ||
-      this->rangeShift != this->numIDs - this->searchRange) {
-    return parent->Error("LookupClass: Failed to read valid rangeShift");
+  if (this->numIDs == 0) {
+    if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) {
+      parent->Warning("LookupClass: Correcting binary-search header for zero-length LookupPair list");
+      this->searchRange = this->entrySelector = this->rangeShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numIDs));
+    if (this->searchRange != (unsigned)std::pow(2, floorLog2) ||
+        this->entrySelector != floorLog2 ||
+        this->rangeShift != this->numIDs - this->searchRange) {
+      parent->Warning("LookupClass: Correcting binary-search header for LookupPair list");
+      this->searchRange = (unsigned)std::pow(2, floorLog2);
+      this->entrySelector = floorLog2;
+      this->rangeShift = this->numIDs - this->searchRange;
+    }
   }
 
   //this->lookups.resize(this->numIDs, parent);
   for (unsigned i = 0; i < numIDs; ++i) {
     this->lookups.emplace_back(parent);
     if (!this->lookups[i].ParsePart(table)) {
       return parent->Error("LookupClass: Failed to read lookups[%u]", i);
     }
@@ -656,29 +676,37 @@ SILPass::ParsePart(Buffer& table, const 
     return parent->Error("SILPass: Failed to read numSuccess");
   }
   if (!table.ReadU16(&this->numColumns)) {
     return parent->Error("SILPass: Failed to read numColumns");
   }
   if (!table.ReadU16(&this->numRange)) {
     return parent->Error("SILPass: Failed to read numRange");
   }
-  if (!table.ReadU16(&this->searchRange) || this->searchRange !=
-      (this->numRange == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::pow(2, std::floor(std::log2(this->numRange))))) {
-    return parent->Error("SILPass: Failed to read valid searchRange");
+
+  // The following three fields are deprecated and ignored. We fix them up here
+  // just for internal consistency, but the Graphite engine doesn't care.
+  if (!table.ReadU16(&this->searchRange) ||
+      !table.ReadU16(&this->entrySelector) ||
+      !table.ReadU16(&this->rangeShift)) {
+    return parent->Error("SILPass: Failed to read searchRange..rangeShift");
   }
-  if (!table.ReadU16(&this->entrySelector) || this->entrySelector !=
-      (this->numRange == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::floor(std::log2(this->numRange)))) {
-    return parent->Error("SILPass: Failed to read valid entrySelector");
-  }
-  if (!table.ReadU16(&this->rangeShift) ||
-      this->rangeShift != this->numRange - this->searchRange) {
-    return parent->Error("SILPass: Failed to read valid rangeShift");
+  if (this->numRange == 0) {
+    if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) {
+      this->searchRange = this->entrySelector = this->rangeShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numRange));
+    if (this->searchRange != 6 * (unsigned)std::pow(2, floorLog2) ||
+        this->entrySelector != floorLog2 ||
+        this->rangeShift != 6 * this->numRange - this->searchRange) {
+      this->searchRange = 6 * (unsigned)std::pow(2, floorLog2);
+      this->entrySelector = floorLog2;
+      this->rangeShift = 6 * this->numRange - this->searchRange;
+    }
   }
 
   //this->ranges.resize(this->numRange, parent);
   for (unsigned i = 0 ; i < this->numRange; ++i) {
     this->ranges.emplace_back(parent);
     if (!this->ranges[i].ParsePart(table)) {
       return parent->Error("SILPass: Failed to read ranges[%u]", i);
     }
--- a/gfx/ots/src/sill.cc
+++ b/gfx/ots/src/sill.cc
@@ -17,29 +17,37 @@ bool OpenTypeSILL::Parse(const uint8_t* 
   Buffer table(data, length);
 
   if (!table.ReadU32(&this->version) || this->version >> 16 != 1) {
     return Drop("Failed to read valid version");
   }
   if (!table.ReadU16(&this->numLangs)) {
     return Drop("Failed to read numLangs");
   }
-  if (!table.ReadU16(&this->searchRange) || this->searchRange !=
-      (this->numLangs == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::pow(2, std::floor(std::log2(this->numLangs))))) {
-    return Drop("Failed to read valid searchRange");
+
+  // The following three fields are deprecated and ignored. We fix them up here
+  // just for internal consistency, but the Graphite engine doesn't care.
+  if (!table.ReadU16(&this->searchRange) ||
+      !table.ReadU16(&this->entrySelector) ||
+      !table.ReadU16(&this->rangeShift)) {
+    return Drop("Failed to read searchRange..rangeShift");
   }
-  if (!table.ReadU16(&this->entrySelector) || this->entrySelector !=
-      (this->numLangs == 0 ? 0 :  // protect against log2(0)
-       (unsigned)std::floor(std::log2(this->numLangs)))) {
-    return Drop("Failed to read valid entrySelector");
-  }
-  if (!table.ReadU16(&this->rangeShift) ||
-      this->rangeShift != this->numLangs - this->searchRange) {
-    return Drop("Failed to read valid rangeShift");
+  if (this->numLangs == 0) {
+    if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) {
+      this->searchRange = this->entrySelector = this->rangeShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numLangs));
+    if (this->searchRange != (unsigned)std::pow(2, floorLog2) ||
+        this->entrySelector != floorLog2 ||
+        this->rangeShift != this->numLangs - this->searchRange) {
+      this->searchRange = (unsigned)std::pow(2, floorLog2);
+      this->entrySelector = floorLog2;
+      this->rangeShift = this->numLangs - this->searchRange;
+    }
   }
 
   std::unordered_set<size_t> unverified;
   //this->entries.resize(static_cast<unsigned long>(this->numLangs) + 1, this);
   for (unsigned long i = 0; i <= this->numLangs; ++i) {
     this->entries.emplace_back(this);
     LanguageEntry& entry = this->entries[i];
     if (!entry.ParsePart(table)) {
--- a/mfbt/Compression.cpp
+++ b/mfbt/Compression.cpp
@@ -75,8 +75,29 @@ LZ4::decompress(const char* aSource, siz
     *aOutputSize = ret;
     return true;
   }
 
   *aOutputSize = 0;
   return false;
 }
 
+bool
+LZ4::decompressPartial(const char* aSource, size_t aInputSize, char* aDest,
+                       size_t aMaxOutputSize, size_t* aOutputSize)
+{
+  CheckedInt<int> maxOutputSizeChecked = aMaxOutputSize;
+  MOZ_ASSERT(maxOutputSizeChecked.isValid());
+  CheckedInt<int> inputSizeChecked = aInputSize;
+  MOZ_ASSERT(inputSizeChecked.isValid());
+
+  int ret = LZ4_decompress_safe_partial(aSource, aDest,
+                                        inputSizeChecked.value(),
+                                        maxOutputSizeChecked.value(),
+                                        maxOutputSizeChecked.value());
+  if (ret >= 0) {
+    *aOutputSize = ret;
+    return true;
+  }
+
+  *aOutputSize = 0;
+  return false;
+}
--- a/mfbt/Compression.h
+++ b/mfbt/Compression.h
@@ -91,16 +91,39 @@ public:
    * @param aOutputSize the actual number of bytes decoded in the destination
    *   buffer (necessarily <= aMaxOutputSize)
    * @return true on success, false on failure
    */
   static MFBT_API MOZ_MUST_USE bool
   decompress(const char* aSource, size_t aInputSize, char* aDest,
              size_t aMaxOutputSize, size_t* aOutputSize);
 
+  /**
+   * If the source stream is malformed, the function will stop decoding
+   * and return false.
+   *
+   * This function never writes beyond aDest + aMaxOutputSize, and is
+   * therefore protected against malicious data packets. It also ignores
+   * unconsumed input upon reaching aMaxOutputSize and can therefore be used
+   * for partial decompression.
+   *
+   * Note: Destination buffer must be already allocated.  This version is
+   *       slightly slower than the decompress without the aMaxOutputSize.
+   *
+   * @param aInputSize is the length of the input compressed data
+   * @param aMaxOutputSize is the size of the destination buffer (which must be
+   *   already allocated)
+   * @param aOutputSize the actual number of bytes decoded in the destination
+   *   buffer (necessarily <= aMaxOutputSize)
+   * @return true on success, false on failure
+   */
+  static MFBT_API MOZ_MUST_USE bool
+  decompressPartial(const char* aSource, size_t aInputSize, char* aDest,
+                    size_t aMaxOutputSize, size_t* aOutputSize);
+
   /*
    * Provides the maximum size that LZ4 may output in a "worst case"
    * scenario (input data not compressible) primarily useful for memory
    * allocation of output buffer.
    * note : this function is limited by "int" range (2^31-1)
    *
    * @param aInputSize is the input size. Max supported value is ~1.9GB
    * @return maximum output size in a "worst case" scenario