bug 670901 pt 1 - add support for an error-message callback to OTS. r=jdaggett
☠☠ backed out by 1c508a4f2dd0 ☠ ☠
authorJonathan Kew <jkew@mozilla.com>
Tue, 29 May 2012 23:42:56 +0100
changeset 95201 df4cf37667ac01fcb3ac34e3761e697153b339c6
parent 95200 fcbaab8ad541fa2f5fca5b4e5483d49b945dac21
child 95202 cd033f175e87bf63aa0237e4a21b8b6428b8c407
push id10001
push userjkew@mozilla.com
push dateTue, 29 May 2012 22:46:24 +0000
treeherdermozilla-inbound@664a8855fde5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdaggett
bugs670901
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 670901 pt 1 - add support for an error-message callback to OTS. r=jdaggett
gfx/ots/include/opentype-sanitiser.h
gfx/ots/src/gdef.cc
gfx/ots/src/gpos.cc
gfx/ots/src/gsub.cc
gfx/ots/src/ots.cc
gfx/ots/src/ots.h
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -194,26 +194,36 @@ class OTSStream {
   }
 
  protected:
   uint32_t chksum_;
   uint8_t chksum_buffer_[4];
   unsigned chksum_buffer_offset_;
 };
 
+#ifdef MOZ_OTS_REPORT_ERRORS
+// Signature of the function to be provided by the client in order to report errors.
+// The return type is a boolean so that it can be used within an expression,
+// but the actual value is ignored. (Suggested convention is to always return 'false'.)
+typedef bool (*MessageFunc)(void *user_data, const char *format, ...);
+#endif
+
 // -----------------------------------------------------------------------------
 // Process a given OpenType file and write out a sanitised version
 //   output: a pointer to an object implementing the OTSStream interface. The
 //     sanitisied output will be written to this. In the even of a failure,
 //     partial output may have been written.
 //   input: the OpenType file
 //   length: the size, in bytes, of |input|
 //   preserve_graphite_tables: whether to preserve Graphite Layout tables
 // -----------------------------------------------------------------------------
 bool OTS_API Process(OTSStream *output, const uint8_t *input, size_t length,
+#ifdef MOZ_OTS_REPORT_ERRORS
+                     MessageFunc message_func, void *user_data,
+#endif
                      bool preserve_graphite_tables = false);
 
 // Force to disable debug output even when the library is compiled with
 // -DOTS_DEBUG.
 void DisableDebugOutput();
 
 }  // namespace ots
 
--- a/gfx/ots/src/gdef.cc
+++ b/gfx/ots/src/gdef.cc
@@ -10,16 +10,18 @@
 #include "gpos.h"
 #include "gsub.h"
 #include "layout.h"
 #include "maxp.h"
 
 // GDEF - The Glyph Definition Table
 // http://www.microsoft.com/typography/otspec/gdef.htm
 
+#define TABLE_NAME "GDEF"
+
 namespace {
 
 // The maximum class value in class definition tables.
 const uint16_t kMaxClassDefValue = 0xFFFF;
 // The maximum class value in the glyph class definision table.
 const uint16_t kMaxGlyphClassDefValue = 4;
 // The maximum format number of caret value tables.
 // We don't support format 3 for now. See the comment in
@@ -237,17 +239,21 @@ bool ParseMarkGlyphSetsDefTable(ots::Ope
   }
   file->gdef->num_mark_glyph_sets = mark_set_count;
   return true;
 }
 
 }  // namespace
 
 #define DROP_THIS_TABLE \
-  do { file->gdef->data = 0; file->gdef->length = 0; } while (0)
+  do { \
+    file->gdef->data = 0; \
+    file->gdef->length = 0; \
+    OTS_FAILURE_MSG("OpenType layout data discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_gdef_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   // Grab the number of glyphs in the file from the maxp table to check
   // GlyphIDs in GDEF table.
   if (!file->maxp) {
     return OTS_FAILURE();
--- a/gfx/ots/src/gpos.cc
+++ b/gfx/ots/src/gpos.cc
@@ -10,16 +10,18 @@
 #include "gdef.h"
 #include "gsub.h"
 #include "layout.h"
 #include "maxp.h"
 
 // GPOS - The Glyph Positioning Table
 // http://www.microsoft.com/typography/otspec/gpos.htm
 
+#define TABLE_NAME "GPOS"
+
 namespace {
 
 enum GPOS_TYPE {
   GPOS_TYPE_SINGLE_ADJUSTMENT = 1,
   GPOS_TYPE_PAIR_ADJUSTMENT = 2,
   GPOS_TYPE_CURSIVE_ATTACHMENT = 3,
   GPOS_TYPE_MARK_TO_BASE_ATTACHMENT = 4,
   GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5,
@@ -664,17 +666,21 @@ bool ParseExtensionPositioning(const ots
                                const uint8_t *data, const size_t length) {
   return ots::ParseExtensionSubtable(file, data, length,
                                      &kGposLookupSubtableParser);
 }
 
 }  // namespace
 
 #define DROP_THIS_TABLE \
-  do { file->gpos->data = 0; file->gpos->length = 0; } while (0)
+  do { \
+    file->gpos->data = 0; \
+    file->gpos->length = 0; \
+    OTS_FAILURE_MSG("OpenType layout data discarded"); \
+  } while (0)
 
 namespace ots {
 
 // As far as I checked, following fonts contain invalid GPOS table and
 // OTS will drop their GPOS table.
 //
 // # invalid delta format in device table
 // samanata.ttf
--- a/gfx/ots/src/gsub.cc
+++ b/gfx/ots/src/gsub.cc
@@ -10,16 +10,18 @@
 #include "gdef.h"
 #include "gpos.h"
 #include "layout.h"
 #include "maxp.h"
 
 // GSUB - The Glyph Substitution Table
 // http://www.microsoft.com/typography/otspec/gsub.htm
 
+#define TABLE_NAME "GSUB"
+
 namespace {
 
 // The GSUB header size
 const size_t kGsubHeaderSize = 8;
 
 enum GSUB_TYPE {
   GSUB_TYPE_SINGLE = 1,
   GSUB_TYPE_MULTIPLE = 2,
@@ -524,17 +526,21 @@ bool ParseReverseChainingContextSingleSu
   }
 
   return true;
 }
 
 }  // namespace
 
 #define DROP_THIS_TABLE \
-  do { file->gsub->data = 0; file->gsub->length = 0; } while (0)
+  do { \
+    file->gsub->data = 0; \
+    file->gsub->length = 0; \
+    OTS_FAILURE_MSG("OpenType layout data discarded"); \
+  } while (0)
 
 namespace ots {
 
 // As far as I checked, following fonts contain invalid values in GSUB table.
 // OTS will drop their GSUB table.
 //
 // # too large substitute (value is 0xFFFF)
 // kaiu.ttf
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -16,16 +16,30 @@
 
 // The OpenType Font File
 // http://www.microsoft.com/typography/otspec/cmap.htm
 
 namespace {
 
 bool g_debug_output = true;
 
+#ifdef MOZ_OTS_REPORT_ERRORS
+
+// Generate a message with or without a table tag, when 'header' is the OpenTypeFile pointer
+#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_FAILURE_MSG_TAG_(header, msg_, tag_)
+#define OTS_FAILURE_MSG_HDR(msg_)      OTS_FAILURE_MSG_(header, msg_)
+
+#else
+
+#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_FAILURE()
+#define OTS_FAILURE_MSG_HDR(msg_)      OTS_FAILURE()
+
+#endif
+
+
 struct OpenTypeTable {
   uint32_t tag;
   uint32_t chksum;
   uint32_t offset;
   uint32_t length;
   uint32_t uncompressed_length;
 };
 
@@ -177,37 +191,37 @@ bool ProcessGeneric(ots::OpenTypeFile *h
                     ots::Buffer& file);
 
 bool ProcessTTF(ots::OpenTypeFile *header,
                 ots::OTSStream *output, const uint8_t *data, size_t length) {
   ots::Buffer file(data, length);
 
   // we disallow all files > 1GB in size for sanity.
   if (length > 1024 * 1024 * 1024) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("file exceeds 1GB");
   }
 
   if (!file.ReadTag(&header->version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading version tag");
   }
   if (!IsValidVersionTag(header->version)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid version tag");
   }
 
   if (!file.ReadU16(&header->num_tables) ||
       !file.ReadU16(&header->search_range) ||
       !file.ReadU16(&header->entry_selector) ||
       !file.ReadU16(&header->range_shift)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading table directory search header");
   }
 
   // search_range is (Maximum power of 2 <= numTables) x 16. Thus, to avoid
   // overflow num_tables is, at most, 2^16 / 16 = 2^12
   if (header->num_tables >= 4096 || header->num_tables < 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("excessive (or zero) number of tables");
   }
 
   unsigned max_pow2 = 0;
   while (1u << (max_pow2 + 1) <= header->num_tables) {
     max_pow2++;
   }
   const uint16_t expected_search_range = (1u << max_pow2) << 4;
 
@@ -215,17 +229,17 @@ bool ProcessTTF(ots::OpenTypeFile *heade
   // http://www.princexml.com/fonts/ have unexpected search_range value.
   if (header->search_range != expected_search_range) {
     OTS_WARNING("bad search range");
     header->search_range = expected_search_range;  // Fix the value.
   }
 
   // entry_selector is Log2(maximum power of 2 <= numTables)
   if (header->entry_selector != max_pow2) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("incorrect entrySelector for table directory");
   }
 
   // range_shift is NumTables x 16-searchRange. We know that 16*num_tables
   // doesn't over flow because we range checked it above. Also, we know that
   // it's > header->search_range by construction of search_range.
   const uint32_t expected_range_shift
       = 16 * header->num_tables - header->search_range;
   if (header->range_shift != expected_range_shift) {
@@ -237,105 +251,105 @@ bool ProcessTTF(ots::OpenTypeFile *heade
   std::vector<OpenTypeTable> tables;
 
   for (unsigned i = 0; i < header->num_tables; ++i) {
     OpenTypeTable table;
     if (!file.ReadTag(&table.tag) ||
         !file.ReadU32(&table.chksum) ||
         !file.ReadU32(&table.offset) ||
         !file.ReadU32(&table.length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error reading table directory");
     }
 
     table.uncompressed_length = table.length;
     tables.push_back(table);
   }
 
   return ProcessGeneric(header, output, data, length, tables, file);
 }
 
 bool ProcessWOFF(ots::OpenTypeFile *header,
                  ots::OTSStream *output, const uint8_t *data, size_t length) {
   ots::Buffer file(data, length);
 
   // we disallow all files > 1GB in size for sanity.
   if (length > 1024 * 1024 * 1024) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("file exceeds 1GB");
   }
 
   uint32_t woff_tag;
   if (!file.ReadTag(&woff_tag)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading WOFF marker");
   }
 
   if (woff_tag != Tag("wOFF")) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("invalid WOFF marker");
   }
 
   if (!file.ReadTag(&header->version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading version tag");
   }
   if (!IsValidVersionTag(header->version)) {
-      return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("invalid version tag");
   }
 
   header->search_range = 0;
   header->entry_selector = 0;
   header->range_shift = 0;
 
   uint32_t reported_length;
   if (!file.ReadU32(&reported_length) || length != reported_length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("incorrect file size in WOFF header");
   }
 
   if (!file.ReadU16(&header->num_tables) || !header->num_tables) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading number of tables");
   }
 
   uint16_t reserved_value;
   if (!file.ReadU16(&reserved_value) || reserved_value) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error in reserved field of WOFF header");
   }
 
   uint32_t reported_total_sfnt_size;
   if (!file.ReadU32(&reported_total_sfnt_size)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading total sfnt size");
   }
 
   // We don't care about these fields of the header:
   //   uint16_t major_version, minor_version
   if (!file.Skip(2 * 2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error skipping WOFF header fields");
   }
 
   // Checks metadata block size.
   uint32_t meta_offset;
   uint32_t meta_length;
   uint32_t meta_length_orig;
   if (!file.ReadU32(&meta_offset) ||
       !file.ReadU32(&meta_length) ||
       !file.ReadU32(&meta_length_orig)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading WOFF header fields");
   }
   if (meta_offset) {
     if (meta_offset >= length || length - meta_offset < meta_length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid metadata block location/size");
     }
   }
 
   // Checks private data block size.
   uint32_t priv_offset;
   uint32_t priv_length;
   if (!file.ReadU32(&priv_offset) ||
       !file.ReadU32(&priv_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading WOFF header fields");
   }
   if (priv_offset) {
     if (priv_offset >= length || length - priv_offset < priv_length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid private block location/size");
     }
   }
 
   // Next up is the list of tables.
   std::vector<OpenTypeTable> tables;
 
   uint32_t first_index = 0;
   uint32_t last_index = 0;
@@ -343,73 +357,73 @@ bool ProcessWOFF(ots::OpenTypeFile *head
   uint64_t total_sfnt_size = 12 + 16 * header->num_tables;
   for (unsigned i = 0; i < header->num_tables; ++i) {
     OpenTypeTable table;
     if (!file.ReadTag(&table.tag) ||
         !file.ReadU32(&table.offset) ||
         !file.ReadU32(&table.length) ||
         !file.ReadU32(&table.uncompressed_length) ||
         !file.ReadU32(&table.chksum)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error reading table directory");
     }
 
     total_sfnt_size += Round4(table.uncompressed_length);
     if (total_sfnt_size > std::numeric_limits<uint32_t>::max()) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("sfnt size overflow");
     }
     tables.push_back(table);
     if (i == 0 || tables[first_index].offset > table.offset)
       first_index = i;
     if (i == 0 || tables[last_index].offset < table.offset)
       last_index = i;
   }
 
   if (reported_total_sfnt_size != total_sfnt_size) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("uncompressed sfnt size mismatch");
   }
 
   // Table data must follow immediately after the header.
   if (tables[first_index].offset != Round4(file.offset())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("junk before tables in WOFF file");
   }
 
   if (tables[last_index].offset >= length ||
       length - tables[last_index].offset < tables[last_index].length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("invalid table location/size");
   }
   // Blocks must follow immediately after the previous block.
   // (Except for padding with a maximum of three null bytes)
   uint64_t block_end = Round4(
       static_cast<uint64_t>(tables[last_index].offset) +
       static_cast<uint64_t>(tables[last_index].length));
   if (block_end > std::numeric_limits<uint32_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("invalid table location/size");
   }
   if (meta_offset) {
     if (block_end != meta_offset) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid metadata block location");
     }
     block_end = Round4(static_cast<uint64_t>(meta_offset) +
                        static_cast<uint64_t>(meta_length));
     if (block_end > std::numeric_limits<uint32_t>::max()) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid metadata block size");
     }
   }
   if (priv_offset) {
     if (block_end != priv_offset) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid private block location");
     }
     block_end = Round4(static_cast<uint64_t>(priv_offset) +
                        static_cast<uint64_t>(priv_length));
     if (block_end > std::numeric_limits<uint32_t>::max()) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid private block size");
     }
   }
   if (block_end != Round4(length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("file length mismatch (trailing junk?)");
   }
 
   return ProcessGeneric(header, output, data, length, tables, file);
 }
 
 bool ProcessGeneric(ots::OpenTypeFile *header, ots::OTSStream *output,
                     const uint8_t *data, size_t length,
                     const std::vector<OpenTypeTable>& tables,
@@ -420,74 +434,74 @@ bool ProcessGeneric(ots::OpenTypeFile *h
 
   for (unsigned i = 0; i < header->num_tables; ++i) {
     // the tables must be sorted by tag (when taken as big-endian numbers).
     // This also remove the possibility of duplicate tables.
     if (i) {
       const uint32_t this_tag = ntohl(tables[i].tag);
       const uint32_t prev_tag = ntohl(tables[i - 1].tag);
       if (this_tag <= prev_tag) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_HDR("table directory not correctly ordered");
       }
     }
 
     // all tag names must be built from printable ASCII characters
     if (!CheckTag(tables[i].tag)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("invalid table tag", tables[i].tag);
     }
 
     // tables must be 4-byte aligned
     if (tables[i].offset & 3) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("misaligned table", tables[i].tag);
     }
 
     // and must be within the file
     if (tables[i].offset < data_offset || tables[i].offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("invalid table offset", tables[i].tag);
     }
     // disallow all tables with a zero length
     if (tables[i].length < 1) {
       // Note: malayalam.ttf has zero length CVT table...
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("zero-length table", tables[i].tag);
     }
     // disallow all tables with a length > 1GB
     if (tables[i].length > 1024 * 1024 * 1024) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("table length exceeds 1GB", tables[i].tag);
     }
     // disallow tables where the uncompressed size is < the compressed size.
     if (tables[i].uncompressed_length < tables[i].length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("invalid compressed table", tables[i].tag);
     }
     if (tables[i].uncompressed_length > tables[i].length) {
       // We'll probably be decompressing this table.
 
       // disallow all tables which uncompress to > 30 MB
       if (tables[i].uncompressed_length > 30 * 1024 * 1024) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_TAG("uncompressed length exceeds 30MB", tables[i].tag);
       }
       if (uncompressed_sum + tables[i].uncompressed_length < uncompressed_sum) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_TAG("overflow of uncompressed sum", tables[i].tag);
       }
 
       uncompressed_sum += tables[i].uncompressed_length;
     }
     // since we required that the file be < 1GB in length, and that the table
     // length is < 1GB, the following addtion doesn't overflow
     const uint32_t end_byte = tables[i].offset + tables[i].length;
     // Some fonts which are automatically generated by a font generator
     // called TTX seems not to add 0-padding to the final table. It might be
     // ok to accept these fonts so we round up the length of the font file.
     if (!end_byte || end_byte > length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("table overruns end of file", tables[i].tag);
     }
   }
 
   // All decompressed tables uncompressed must be <= 30MB.
   if (uncompressed_sum > 30 * 1024 * 1024) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("uncompressed sum exceeds 30MB");
   }
 
   std::map<uint32_t, OpenTypeTable> table_map;
   for (unsigned i = 0; i < header->num_tables; ++i) {
     table_map[tables[i].tag] = tables[i];
   }
 
   // check that the tables are not overlapping.
@@ -499,73 +513,75 @@ bool ProcessGeneric(ots::OpenTypeFile *h
         std::make_pair(tables[i].offset + tables[i].length,
                        static_cast<uint8_t>(0) /* end */));
   }
   std::sort(overlap_checker.begin(), overlap_checker.end());
   int overlap_count = 0;
   for (unsigned i = 0; i < overlap_checker.size(); ++i) {
     overlap_count += (overlap_checker[i].second ? 1 : -1);
     if (overlap_count > 1) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("overlapping tables");
     }
   }
 
   Arena arena;
 
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) break;
 
     const std::map<uint32_t, OpenTypeTable>::const_iterator it
         = table_map.find(Tag(table_parsers[i].tag));
 
     if (it == table_map.end()) {
       if (table_parsers[i].required) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_TAG("missing required table", table_parsers[i].tag);
       }
       continue;
     }
 
     const uint8_t* table_data;
     size_t table_length;
 
     if (it->second.uncompressed_length != it->second.length) {
       // compressed table. Need to uncompress into memory first.
       table_length = it->second.uncompressed_length;
       table_data = arena.Allocate(table_length);
       uLongf dest_len = table_length;
       int r = uncompress((Bytef*) table_data, &dest_len,
                          data + it->second.offset, it->second.length);
       if (r != Z_OK || dest_len != table_length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_TAG("uncompress failed", table_parsers[i].tag);
       }
     } else {
       // uncompressed table. We can process directly from memory.
       table_data = data + it->second.offset;
       table_length = it->second.length;
     }
 
     if (!table_parsers[i].parse(header, table_data, table_length)) {
-      return OTS_FAILURE();
+      // TODO: parsers should generate specific messages detailing the failure;
+      // once those are all added, we won't need a generic failure message here
+      return OTS_FAILURE_MSG_TAG("failed to parse table", table_parsers[i].tag);
     }
   }
 
   if (header->cff) {
     // font with PostScript glyph
     if (header->version != Tag("OTTO")) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("wrong font version for PostScript glyph data");
     }
     if (header->glyf || header->loca) {
       // mixing outline formats is not recommended
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("font contains both PS and TT glyphs");
     }
   } else {
     if (!header->glyf || !header->loca) {
       // No TrueType glyph found.
       // Note: bitmap-only fonts are not supported.
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("neither PS nor TT glyphs present");
     }
   }
 
   unsigned num_output_tables = 0;
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) {
       break;
     }
@@ -576,29 +592,31 @@ bool ProcessGeneric(ots::OpenTypeFile *h
   }
 
   unsigned max_pow2 = 0;
   while (1u << (max_pow2 + 1) <= num_output_tables) {
     max_pow2++;
   }
   const uint16_t output_search_range = (1u << max_pow2) << 4;
 
+  // most of the errors here are highly unlikely - they'd only occur if the
+  // output stream returns a failure, e.g. lack of space to write
   output->ResetChecksum();
   if (!output->WriteTag(header->version) ||
       !output->WriteU16(num_output_tables) ||
       !output->WriteU16(output_search_range) ||
       !output->WriteU16(max_pow2) ||
       !output->WriteU16((num_output_tables << 4) - output_search_range)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
   const uint32_t offset_table_chksum = output->chksum();
 
   const size_t table_record_offset = output->Tell();
   if (!output->Pad(16 * num_output_tables)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   std::vector<OutputTable> out_tables;
 
   size_t head_table_offset = 0;
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) {
       break;
@@ -613,92 +631,101 @@ bool ProcessGeneric(ots::OpenTypeFile *h
     out.tag = tag;
     out.offset = output->Tell();
 
     output->ResetChecksum();
     if (tag == Tag("head")) {
       head_table_offset = out.offset;
     }
     if (!table_parsers[i].serialise(output, header)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("failed to serialize table", table_parsers[i].tag);
     }
 
     const size_t end_offset = output->Tell();
     if (end_offset <= out.offset) {
       // paranoid check. |end_offset| is supposed to be greater than the offset,
       // as long as the Tell() interface is implemented correctly.
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error writing output");
     }
     out.length = end_offset - out.offset;
 
     // align tables to four bytes
     if (!output->Pad((4 - (end_offset & 3)) % 4)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error writing output");
     }
     out.chksum = output->chksum();
     out_tables.push_back(out);
   }
 
   const size_t end_of_file = output->Tell();
 
   // Need to sort the output tables for inclusion in the file
   std::sort(out_tables.begin(), out_tables.end(), OutputTable::SortByTag);
   if (!output->Seek(table_record_offset)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   output->ResetChecksum();
   uint32_t tables_chksum = 0;
   for (unsigned i = 0; i < out_tables.size(); ++i) {
     if (!output->WriteTag(out_tables[i].tag) ||
         !output->WriteU32(out_tables[i].chksum) ||
         !output->WriteU32(out_tables[i].offset) ||
         !output->WriteU32(out_tables[i].length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error writing output");
     }
     tables_chksum += out_tables[i].chksum;
   }
   const uint32_t table_record_chksum = output->chksum();
 
   // http://www.microsoft.com/typography/otspec/otff.htm
   const uint32_t file_chksum
       = offset_table_chksum + tables_chksum + table_record_chksum;
   const uint32_t chksum_magic = static_cast<uint32_t>(0xb1b0afba) - file_chksum;
 
   // seek into the 'head' table and write in the checksum magic value
   if (!head_table_offset) {
-    return OTS_FAILURE();  // not reached.
+    return OTS_FAILURE_MSG_HDR("internal error!");
   }
   if (!output->Seek(head_table_offset + 8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
   if (!output->WriteU32(chksum_magic)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   if (!output->Seek(end_of_file)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   return true;
 }
 
 }  // namespace
 
 namespace ots {
 
 void DisableDebugOutput() {
   g_debug_output = false;
 }
 
-bool Process(OTSStream *output, const uint8_t *data, size_t length,
-             bool preserveGraphite) {
+bool OTS_API Process(OTSStream *output, const uint8_t *data, size_t length,
+#ifdef MOZ_OTS_REPORT_ERRORS
+                     MessageFunc message_func, void *user_data,
+#endif
+                     bool preserveGraphite) {
   OpenTypeFile header;
+
+#ifdef MOZ_OTS_REPORT_ERRORS
+  header.message_func = message_func;
+  header.user_data = user_data;
+#endif
+
   if (length < 4) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_(&header, "file less than 4 bytes");
   }
 
   header.preserve_graphite = preserveGraphite;
 
   bool result;
   if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') {
     result = ProcessWOFF(&header, output, data, length);
   } else {
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -33,16 +33,50 @@ bool Failure(const char *f, int l, const
     ots::Warning(__FILE__, __LINE__, format, ##args)
 void Warning(const char *f, int l, const char *format, ...)
      __attribute__((format(printf, 3, 4)));
 #else
 #define OTS_WARNING(format, args...)
 #endif
 #endif
 
+#ifdef MOZ_OTS_REPORT_ERRORS
+
+// All OTS_FAILURE_* macros ultimately evaluate to 'false', just like the original
+// message-less OTS_FAILURE(), so that the current parser will return 'false' as
+// its result (indicating a failure).
+// If the message-callback feature is enabled, and a message_func pointer has been
+// provided, this will be called before returning the 'false' status.
+
+// Generate a simple message
+#define OTS_FAILURE_MSG_(otf_,msg_) \
+  ((otf_)->message_func && \
+    (*(otf_)->message_func)((otf_)->user_data, "%s", msg_) && \
+    false)
+
+// Generate a message with an associated table tag
+#define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) \
+  ((otf_)->message_func && \
+    (*(otf_)->message_func)((otf_)->user_data, "table '%s': %s", tag_, msg_) && \
+    false)
+
+// Convenience macro for use in files that only handle a single table tag,
+// defined as TABLE_NAME at the top of the file; the 'file' variable is
+// expected to be the current OpenTypeFile pointer.
+#define OTS_FAILURE_MSG(msg_) OTS_FAILURE_MSG_TAG_(file, msg_, TABLE_NAME)
+
+#else
+
+// If the message-callback feature is not enabled, error messages are just dropped.
+#define OTS_FAILURE_MSG_(otf_,msg_)          OTS_FAILURE()
+#define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) OTS_FAILURE()
+#define OTS_FAILURE_MSG(msg_)                OTS_FAILURE()
+
+#endif
+
 // Define OTS_NO_TRANSCODE_HINTS (i.e., g++ -DOTS_NO_TRANSCODE_HINTS) if you
 // want to omit TrueType hinting instructions and variables in glyf, fpgm, prep,
 // and cvt tables.
 #if defined(OTS_NO_TRANSCODE_HINTS)
 const bool g_transcode_hints = false;
 #else
 const bool g_transcode_hints = true;
 #endif
@@ -202,16 +236,21 @@ struct OpenTypeFile {
   }
 
   uint32_t version;
   uint16_t num_tables;
   uint16_t search_range;
   uint16_t entry_selector;
   uint16_t range_shift;
 
+#ifdef MOZ_OTS_REPORT_ERRORS
+  MessageFunc message_func;
+  void        *user_data;
+#endif
+
   // This is used to tell the relevant parsers whether to preserve the
   // Graphite layout tables (currently _without_ any checking)
   bool preserve_graphite;
 
 #define F(name, capname) OpenType##capname *name;
 FOR_EACH_TABLE_TYPE
 #undef F
 };