Bug 1341412 - Baldr: fix offset reported by validation error (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Thu, 23 Feb 2017 17:59:22 -0600
changeset 344654 ea11b190dac4446444d68f0fdf0ae44a38aaa0bc
parent 344653 fd85a5aafa9fdc2fbe012408e996f573308fb697
child 344655 3fee518242f68ba6a1b2b107bd475645ecf24622
push id31414
push usercbook@mozilla.com
push dateFri, 24 Feb 2017 10:47:41 +0000
treeherdermozilla-central@be661bae6cb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1341412
milestone54.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 1341412 - Baldr: fix offset reported by validation error (r=bbouvier) MozReview-Commit-ID: CllhPo4uGde
js/src/jit-test/lib/wasm-binary.js
js/src/jit-test/tests/wasm/errors.js
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBinaryIterator.h
js/src/wasm/WasmBinaryToAST.cpp
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmValidate.cpp
js/src/wasm/WasmValidate.h
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -66,16 +66,17 @@ const I32Store16       = 0x3b;
 const I64Store8        = 0x3c;
 const I64Store16       = 0x3d;
 const I64Store32       = 0x3e;
 const GrowMemoryCode   = 0x40;
 const I32ConstCode     = 0x41;
 const I64ConstCode     = 0x42;
 const F32ConstCode     = 0x43;
 const F64ConstCode     = 0x44;
+const I32AddCode       = 0x6a;
 const I32DivSCode      = 0x6d;
 const I32DivUCode      = 0x6e;
 const I32RemSCode      = 0x6f;
 const I32RemUCode      = 0x70;
 const I32TruncSF32Code = 0xa8;
 const I32TruncUF32Code = 0xa9;
 const I32TruncSF64Code = 0xaa;
 const I32TruncUF64Code = 0xab;
--- a/js/src/jit-test/tests/wasm/errors.js
+++ b/js/src/jit-test/tests/wasm/errors.js
@@ -1,13 +1,14 @@
 load(libdir + "wasm.js");
 load(libdir + "wasm-binary.js");
 
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
+const CompileError = WebAssembly.CompileError;
 const RuntimeError = WebAssembly.RuntimeError;
 
 function isWasmFunction(name) {
     return /^wasm-function\[\d*\]$/.test(name)
 }
 
 function parseStack(stack) {
     var frames = stack.split('\n');
@@ -148,8 +149,26 @@ assertEq(stack.length > N, true);
 var lastLine = stack[0].line;
 for (var i = 1; i < N; i++) {
     assertEq(isWasmFunction(stack[i].name), true);
     assertEq(stack[i].line > lastLine, true);
     lastLine = stack[i].line;
     assertEq(binary[stack[i].line], i == 3 ? CallIndirectCode : CallCode);
     assertEq(stack[i].column, 1);
 }
+
+function testCompileError(opcode, text) {
+    var binary = new Uint8Array(wasmTextToBinary(text));
+    var exn;
+    try {
+        new Instance(new Module(binary));
+    } catch (e) {
+        exn = e;
+    }
+
+    assertEq(exn instanceof CompileError, true);
+    var offset = Number(exn.message.match(/at offset (\d*)/)[1]);
+    assertEq(binary[offset], opcode);
+}
+
+testCompileError(CallCode, '(module (func $f (param i32)) (func $g call $f))');
+testCompileError(I32AddCode, '(module (func (i32.add (i32.const 1) (f32.const 1))))');
+testCompileError(EndCode, '(module (func (block i32)))');
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -484,17 +484,16 @@ class BaseCompiler
     BCESet                      bceSafe_;        // Locals that have been bounds checked and not updated since
     ValTypeVector               SigI64I64_;
     ValTypeVector               SigD_;
     ValTypeVector               SigF_;
     ValTypeVector               SigI_;
     ValTypeVector               Sig_;
     Label                       returnLabel_;
     Label                       stackOverflowLabel_;
-    TrapOffset                  prologueTrapOffset_;
     CodeOffset                  stackAddOffset_;
 
     LatentOp                    latentOp_;       // Latent operation for branch (seen next)
     ValType                     latentType_;     // Operand type, if latentOp_ is true
     Assembler::Condition        latentIntCmp_;   // Comparison operator, if latentOp_ == Compare, int types
     Assembler::DoubleCondition  latentDoubleCmp_;// Comparison operator, if latentOp_ == Compare, float types
 
     FuncOffsets                 offsets_;
@@ -2101,18 +2100,17 @@ class BaseCompiler
         return iter_.controlOutermost();
     }
 
     ////////////////////////////////////////////////////////////
     //
     // Labels
 
     void insertBreakablePoint(CallSiteDesc::Kind kind) {
-        const uint32_t offset = trapOffset().bytecodeOffset;
-        masm.nopPatchableToCall(CallSiteDesc(offset, kind));
+        masm.nopPatchableToCall(CallSiteDesc(iter_.errorOffset(), kind));
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Function prologue and epilogue.
 
     void beginFunction() {
         JitSpew(JitSpew_Codegen, "# Emitting wasm baseline code");
@@ -2266,17 +2264,18 @@ class BaseCompiler
         // set, we pop all locals space except allocated for DebugFrame to
         // maintain the invariant that, when debugEnabled_, all wasm::Frames
         // are valid wasm::DebugFrames which is observable by WasmHandleThrow.
         masm.bind(&stackOverflowLabel_);
         int32_t debugFrameReserved = debugEnabled_ ? DebugFrame::offsetOfFrame() : 0;
         MOZ_ASSERT(localSize_ >= debugFrameReserved);
         if (localSize_ > debugFrameReserved)
             masm.addToStackPtr(Imm32(localSize_ - debugFrameReserved));
-        masm.jump(TrapDesc(prologueTrapOffset_, Trap::StackOverflow, debugFrameReserved));
+        TrapOffset prologueTrapOffset(func_.lineOrBytecode());
+        masm.jump(TrapDesc(prologueTrapOffset, Trap::StackOverflow, debugFrameReserved));
 
         masm.bind(&returnLabel_);
 
         if (debugEnabled_) {
             // Store and reload the return value from DebugFrame::return so that
             // it can be clobbered, and/or modified by the debug trap.
             saveResult();
             insertBreakablePoint(CallSiteDesc::Breakpoint);
@@ -3508,17 +3507,17 @@ class BaseCompiler
 
     ////////////////////////////////////////////////////////////
     //
     // Sundry helpers.
 
     uint32_t readCallSiteLineOrBytecode() {
         if (!func_.callSiteLineNums().empty())
             return func_.callSiteLineNums()[lastReadCallSite_++];
-        return trapOffset().bytecodeOffset;
+        return iter_.errorOffset();
     }
 
     bool done() const {
         return iter_.done();
     }
 
     TrapOffset trapOffset() const {
         return iter_.trapOffset();
@@ -7329,29 +7328,28 @@ BaseCompiler::emitFunction()
 BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
                            Decoder& decoder,
                            const FuncBytes& func,
                            const ValTypeVector& locals,
                            bool debugEnabled,
                            TempAllocator* alloc,
                            MacroAssembler* masm)
     : env_(env),
-      iter_(env, decoder, func.lineOrBytecode()),
+      iter_(env, decoder),
       func_(func),
       lastReadCallSite_(0),
       alloc_(*alloc),
       locals_(locals),
       localSize_(0),
       varLow_(0),
       varHigh_(0),
       maxFramePushed_(0),
       deadCode_(false),
       debugEnabled_(debugEnabled),
       bceSafe_(0),
-      prologueTrapOffset_(trapOffset()),
       stackAddOffset_(0),
       latentOp_(LatentOp::None),
       latentType_(ValType::I32),
       latentIntCmp_(Assembler::Equal),
       latentDoubleCmp_(Assembler::DoubleEqual),
       masm(*masm),
       availGPR_(GeneralRegisterSet::All()),
       availFPU_(FloatRegisterSet::All()),
@@ -7542,17 +7540,17 @@ bool
 js::wasm::BaselineCompileFunction(CompileTask* task, FuncCompileUnit* unit, UniqueChars *error)
 {
     MOZ_ASSERT(task->mode() == CompileMode::Baseline);
     MOZ_ASSERT(task->env().kind == ModuleKind::Wasm);
 
     const FuncBytes& func = unit->func();
     uint32_t bodySize = func.bytes().length();
 
-    Decoder d(func.bytes(), error);
+    Decoder d(func.bytes().begin(), func.bytes().end(), func.lineOrBytecode(), error);
 
     if (!ValidateFunctionBody(task->env(), func.index(), bodySize, d))
         return false;
 
     d.rollbackPosition(d.begin());
 
     // Build the local types vector.
 
--- a/js/src/wasm/WasmBinaryIterator.h
+++ b/js/src/wasm/WasmBinaryIterator.h
@@ -321,23 +321,22 @@ class MOZ_STACK_CLASS OpIter : private P
 {
     static const bool Validate = Policy::Validate;
     static const bool Output = Policy::Output;
     typedef typename Policy::Value Value;
     typedef typename Policy::ControlItem ControlItem;
 
     Decoder& d_;
     const ModuleEnvironment& env_;
-    const size_t offsetInModule_;
 
     Vector<TypeAndValue<Value>, 8, SystemAllocPolicy> valueStack_;
     Vector<ControlStackEntry<ControlItem>, 8, SystemAllocPolicy> controlStack_;
 
     DebugOnly<Op> op_;
-    size_t offsetOfExpr_;
+    size_t offsetOfLastReadOp_;
 
     MOZ_MUST_USE bool readFixedU8(uint8_t* out) {
         if (Validate)
             return d_.readFixedU8(out);
         *out = d_.uncheckedReadFixedU8();
         return true;
     }
     MOZ_MUST_USE bool readFixedU32(uint32_t* out) {
@@ -480,28 +479,33 @@ class MOZ_STACK_CLASS OpIter : private P
     void afterUnconditionalBranch() {
         valueStack_.shrinkTo(controlStack_.back().valueStackStart());
         controlStack_.back().setPolymorphicBase();
     }
 
   public:
     typedef Vector<Value, 8, SystemAllocPolicy> ValueVector;
 
-    explicit OpIter(const ModuleEnvironment& env, Decoder& decoder, uint32_t offsetInModule = 0)
-      : d_(decoder), env_(env), offsetInModule_(offsetInModule), op_(Op::Limit), offsetOfExpr_(0)
+    explicit OpIter(const ModuleEnvironment& env, Decoder& decoder)
+      : d_(decoder), env_(env), op_(Op::Limit), offsetOfLastReadOp_(0)
     {}
 
     // Return the decoding byte offset.
     uint32_t currentOffset() const {
         return d_.currentOffset();
     }
 
-    // Returning the offset within the entire module of the last-read Op.
+    // Return the offset within the entire module of the last-read op.
+    size_t errorOffset() const {
+        return offsetOfLastReadOp_ ? offsetOfLastReadOp_ : d_.currentOffset();
+    }
+
+    // Return a TrapOffset describing where the current op should be reported to trap.
     TrapOffset trapOffset() const {
-        return TrapOffset(offsetInModule_ + offsetOfExpr_);
+        return TrapOffset(errorOffset());
     }
 
     // Test whether the iterator has reached the end of the buffer.
     bool done() const {
         return d_.done();
     }
 
     // Report a general failure.
@@ -656,17 +660,17 @@ OpIter<Policy>::unrecognizedOpcode(uint3
 
     return fail(error.get());
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::fail(const char* msg)
 {
-    return d_.fail("%s", msg);
+    return d_.fail(errorOffset(), msg);
 }
 
 // This function pops exactly one value from the stack, yielding Any types in
 // various cases and therefore making it the caller's responsibility to do the
 // right thing for StackType::Any. Prefer (pop|top)WithType.
 template <typename Policy>
 inline bool
 OpIter<Policy>::popAnyType(StackType* type, Value* value)
@@ -899,17 +903,17 @@ OpIter<Policy>::readBlockType(ExprType* 
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readOp(uint16_t* op)
 {
     MOZ_ASSERT(!controlStack_.empty());
 
-    offsetOfExpr_ = d_.currentOffset();
+    offsetOfLastReadOp_ = d_.currentOffset();
 
     if (Validate) {
         if (MOZ_UNLIKELY(!d_.readOp(op)))
             return fail("unable to read opcode");
     } else {
         *op = uint16_t(d_.uncheckedReadOp());
     }
 
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -1850,17 +1850,17 @@ bool
 wasm::BinaryToAst(JSContext* cx, const uint8_t* bytes, uint32_t length, LifoAlloc& lifo,
                   AstModule** module)
 {
     AstModule* result = new(lifo) AstModule(lifo);
     if (!result || !result->init())
         return false;
 
     UniqueChars error;
-    Decoder d(bytes, bytes + length, &error, /* resilient */ true);
+    Decoder d(bytes, bytes + length, 0, &error, /* resilient */ true);
     AstDecodeContext c(cx, lifo, d, *result, true);
 
     if (!AstDecodeEnvironment(c) ||
         !AstDecodeCodeSection(c) ||
         !AstDecodeModuleTail(c))
     {
         if (error) {
             JS_ReportErrorNumberASCII(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -105,17 +105,17 @@ CompileArgs::initFromContext(JSContext* 
     return assumptions.initBuildIdFromContext(cx);
 }
 
 SharedModule
 wasm::Compile(const ShareableBytes& bytecode, const CompileArgs& args, UniqueChars* error)
 {
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
-    Decoder d(bytecode.begin(), bytecode.end(), error);
+    Decoder d(bytecode.bytes, error);
 
     auto env = js::MakeUnique<ModuleEnvironment>();
     if (!env)
         return nullptr;
 
     if (!DecodeModuleEnvironment(d, env.get()))
         return nullptr;
 
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -171,17 +171,17 @@ class FunctionCompiler
 
   public:
     FunctionCompiler(const ModuleEnvironment& env,
                      Decoder& decoder,
                      const FuncBytes& func,
                      const ValTypeVector& locals,
                      MIRGenerator& mirGen)
       : env_(env),
-        iter_(env, decoder, func.lineOrBytecode()),
+        iter_(env, decoder),
         func_(func),
         locals_(locals),
         lastReadCallSite_(0),
         alloc_(mirGen.alloc()),
         graph_(mirGen.graph()),
         info_(mirGen.info()),
         mirGen_(mirGen),
         curBlock_(nullptr),
@@ -1504,17 +1504,17 @@ class FunctionCompiler
         return true;
     }
 
     /************************************************************ DECODING ***/
 
     uint32_t readCallSiteLineOrBytecode() {
         if (!func_.callSiteLineNums().empty())
             return func_.callSiteLineNums()[lastReadCallSite_++];
-        return iter_.trapOffset().bytecodeOffset;
+        return iter_.errorOffset();
     }
 
     bool done() const { return iter_.done(); }
 
     /*************************************************************************/
   private:
     bool newBlock(MBasicBlock* pred, MBasicBlock** block)
     {
@@ -3613,17 +3613,17 @@ bool
 wasm::IonCompileFunction(CompileTask* task, FuncCompileUnit* unit, UniqueChars* error)
 {
     MOZ_ASSERT(task->mode() == CompileMode::Ion);
 
     const FuncBytes& func = unit->func();
     const ModuleEnvironment& env = task->env();
     uint32_t bodySize = func.bytes().length();
 
-    Decoder d(func.bytes(), error);
+    Decoder d(func.bytes().begin(), func.bytes().end(), func.lineOrBytecode(), error);
 
     if (!env.isAsmJS()) {
         if (!ValidateFunctionBody(task->env(), func.index(), bodySize, d))
             return false;
 
         d.rollbackPosition(d.begin());
     }
 
@@ -3640,28 +3640,22 @@ wasm::IonCompileFunction(CompileTask* ta
     JitContext jitContext(&task->alloc());
     const JitCompileOptions options;
     MIRGraph graph(&task->alloc());
     CompileInfo compileInfo(locals.length());
     MIRGenerator mir(nullptr, options, &task->alloc(), &graph, &compileInfo,
                      IonOptimizations.get(OptimizationLevel::Wasm));
     mir.initMinWasmHeapLength(env.minMemoryLength);
 
-    // Capture the prologue's trap site before decoding the function.
-
-    TrapOffset prologueTrapOffset;
-
     // Build MIR graph
     {
         FunctionCompiler f(env, d, func, locals, mir);
         if (!f.init())
             return false;
 
-        prologueTrapOffset = f.iter().trapOffset();
-
         if (!f.startBlock())
             return false;
 
         if (!f.iter().readFunctionStart(f.sig().ret()))
             return false;
 
         while (!f.done()) {
             if (!EmitExpr(f))
@@ -3690,16 +3684,17 @@ wasm::IonCompileFunction(CompileTask* ta
         LIRGraph* lir = GenerateLIR(&mir);
         if (!lir)
             return false;
 
         SigIdDesc sigId = env.funcSigs[func.index()]->id;
 
         CodeGenerator codegen(&mir, lir, &task->masm());
 
+        TrapOffset prologueTrapOffset(func.lineOrBytecode());
         FuncOffsets offsets;
         if (!codegen.generateWasm(sigId, prologueTrapOffset, &offsets))
             return false;
 
         unit->finish(offsets);
     }
 
     return true;
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -29,33 +29,33 @@ using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 
 // Decoder implementation.
 
 bool
-Decoder::fail(const char* msg, ...)
+Decoder::failf(const char* msg, ...)
 {
     va_list ap;
     va_start(ap, msg);
     UniqueChars str(JS_vsmprintf(msg, ap));
     va_end(ap);
     if (!str)
         return false;
 
-    return fail(Move(str));
+    return fail(str.get());
 }
 
 bool
-Decoder::fail(UniqueChars msg)
+Decoder::fail(size_t errorOffset, const char* msg)
 {
     MOZ_ASSERT(error_);
-    UniqueChars strWithOffset(JS_smprintf("at offset %" PRIuSIZE ": %s", currentOffset(), msg.get()));
+    UniqueChars strWithOffset(JS_smprintf("at offset %" PRIuSIZE ": %s", errorOffset, msg));
     if (!strWithOffset)
         return false;
 
     *error_ = Move(strWithOffset);
     return false;
 }
 
 bool
@@ -105,26 +105,26 @@ Decoder::startSection(SectionId id, Modu
 
   rewind:
     cur_ = initialCur;
     env->customSections.shrinkTo(initialCustomSectionsLength);
     *sectionStart = NotStarted;
     return true;
 
   fail:
-    return fail("failed to start %s section", sectionName);
+    return failf("failed to start %s section", sectionName);
 }
 
 bool
 Decoder::finishSection(uint32_t sectionStart, uint32_t sectionSize, const char* sectionName)
 {
     if (resilientMode_)
         return true;
     if (sectionSize != (cur_ - beg_) - sectionStart)
-        return fail("byte size mismatch in %s section", sectionName);
+        return failf("byte size mismatch in %s section", sectionName);
     return true;
 }
 
 bool
 Decoder::startCustomSection(const char* expected, size_t expectedLength, ModuleEnvironment* env,
                             uint32_t* sectionStart, uint32_t* sectionSize)
 {
     // Record state at beginning of section to allow rewinding to this point
@@ -730,30 +730,30 @@ DecodeSignatureIndex(Decoder& d, const S
 static bool
 DecodeLimits(Decoder& d, Limits* limits)
 {
     uint32_t flags;
     if (!d.readVarU32(&flags))
         return d.fail("expected flags");
 
     if (flags & ~uint32_t(0x1))
-        return d.fail("unexpected bits set in flags: %" PRIu32, (flags & ~uint32_t(0x1)));
+        return d.failf("unexpected bits set in flags: %" PRIu32, (flags & ~uint32_t(0x1)));
 
     if (!d.readVarU32(&limits->initial))
         return d.fail("expected initial length");
 
     if (flags & 0x1) {
         uint32_t maximum;
         if (!d.readVarU32(&maximum))
             return d.fail("expected maximum length");
 
         if (limits->initial > maximum) {
-            return d.fail("memory size minimum must not be greater than maximum; "
-                          "maximum length %" PRIu32 " is less than initial length %" PRIu32,
-                          maximum, limits->initial);
+            return d.failf("memory size minimum must not be greater than maximum; "
+                           "maximum length %" PRIu32 " is less than initial length %" PRIu32,
+                           maximum, limits->initial);
         }
 
         limits->maximum.emplace(maximum);
     }
 
     return true;
 }
 
@@ -1575,17 +1575,17 @@ wasm::DecodeModuleTail(Decoder& d, Modul
     return true;
 }
 
 // Validate algorithm.
 
 bool
 wasm::Validate(const ShareableBytes& bytecode, UniqueChars* error)
 {
-    Decoder d(bytecode.begin(), bytecode.end(), error);
+    Decoder d(bytecode.bytes, error);
 
     ModuleEnvironment env;
     if (!DecodeModuleEnvironment(d, &env))
         return false;
 
     if (!DecodeCodeSection(d, &env))
         return false;
 
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -297,16 +297,17 @@ class Encoder
 // construction. The client is responsible for keeping the byte range alive as
 // long as the Decoder is used.
 
 class Decoder
 {
     const uint8_t* const beg_;
     const uint8_t* const end_;
     const uint8_t* cur_;
+    const size_t offsetInModule_;
     UniqueChars* error_;
     bool resilientMode_;
 
     template <class T>
     MOZ_MUST_USE bool read(T* out) {
         if (bytesRemain() < sizeof(T))
             return false;
         memcpy((void*)out, cur_, sizeof(T));
@@ -379,36 +380,43 @@ class Decoder
         uint8_t mask = 0x7f & (uint8_t(-1) << remainderBits);
         if ((byte & mask) != ((byte & (1 << (remainderBits - 1))) ? mask : 0))
             return false;
         *out = s | SInt(byte) << shift;
         return true;
     }
 
   public:
-    Decoder(const uint8_t* begin, const uint8_t* end, UniqueChars* error,
+    Decoder(const uint8_t* begin, const uint8_t* end, size_t offsetInModule, UniqueChars* error,
             bool resilientMode = false)
       : beg_(begin),
         end_(end),
         cur_(begin),
+        offsetInModule_(offsetInModule),
         error_(error),
         resilientMode_(resilientMode)
     {
         MOZ_ASSERT(begin <= end);
     }
     explicit Decoder(const Bytes& bytes, UniqueChars* error = nullptr)
       : beg_(bytes.begin()),
         end_(bytes.end()),
         cur_(bytes.begin()),
+        offsetInModule_(0),
         error_(error),
         resilientMode_(false)
     {}
 
-    bool fail(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3);
-    bool fail(UniqueChars msg);
+    // These convenience functions use currentOffset() as the errorOffset.
+    bool fail(const char* msg) { return fail(currentOffset(), msg); }
+    bool failf(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3);
+
+    // Report an error at the given offset (relative to the whole module).
+    bool fail(size_t errorOffset, const char* msg);
+
     void clearError() {
         if (error_)
             error_->reset();
     }
 
     bool done() const {
         MOZ_ASSERT(cur_ <= end_);
         return cur_ == end_;
@@ -424,17 +432,17 @@ class Decoder
     // pos must be a value previously returned from currentPosition.
     void rollbackPosition(const uint8_t* pos) {
         cur_ = pos;
     }
     const uint8_t* currentPosition() const {
         return cur_;
     }
     size_t currentOffset() const {
-        return cur_ - beg_;
+        return offsetInModule_ + (cur_ - beg_);
     }
     const uint8_t* begin() const {
         return beg_;
     }
 
     // Fixed-size encoding operations simply copy the literal bytes (without
     // attempting to align).