Commit 072d0e3e authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm] Allow for arbitrarily long error messages

We currently have a fixed limit of 256 characters for error messages
generated in the decoder. However, we sometimes embed names in it,
which makes it easy to generate a crash by using long names (e.g. for
exports) in invalid wasm modules.
This CL fixes this by switching to a stream based interface, allowing
to pass arbitrary objects to be printed. With this interface, we can
easily limit the length of output later.

R=titzer@chromium.org

Bug: chromium:740023
Change-Id: I2848c31c63a015157e2a3a9458b54e523060cd69
Reviewed-on: https://chromium-review.googlesource.com/565282Reviewed-by: 's avatarBen Titzer <titzer@chromium.org>
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46860}
parent 620b544d
......@@ -37,6 +37,34 @@ namespace wasm {
// a buffer of bytes.
class Decoder {
public:
class ErrorFormatter {
public:
// Allow move, but nothing else.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ErrorFormatter);
~ErrorFormatter() {
if (!decoder_) return;
DCHECK(decoder_->ok());
decoder_->error_offset_ = decoder_->pc_offset(pc_);
decoder_->error_msg_ = message_builder_.str();
decoder_->onFirstError();
}
template <typename T>
ErrorFormatter& operator<<(T&& arg) {
if (decoder_) message_builder_ << std::forward<T>(arg);
return *this;
}
private:
friend class Decoder;
std::ostringstream message_builder_;
const byte* pc_;
Decoder* decoder_;
ErrorFormatter(const byte* pc, Decoder* decoder)
: pc_(pc), decoder_(decoder) {}
};
Decoder(const byte* start, const byte* end, uint32_t buffer_offset = 0)
: start_(start), pc_(start), end_(end), buffer_offset_(buffer_offset) {}
Decoder(const byte* start, const byte* pc, const byte* end,
......@@ -48,7 +76,7 @@ class Decoder {
inline bool check(const byte* pc, uint32_t length, const char* msg) {
DCHECK_LE(start_, pc);
if (V8_UNLIKELY(pc + length > end_)) {
error(pc, msg);
error(pc) << msg;
return false;
}
return true;
......@@ -125,13 +153,13 @@ class Decoder {
}
// Reads a LEB128 variable-length unsigned 32-bit integer and advances {pc_}.
uint32_t consume_u32v(const char* name = nullptr) {
uint32_t consume_u32v(const char* name = "uint32_t") {
uint32_t length = 0;
return read_leb<uint32_t, true, true, true>(pc_, &length, name);
}
// Reads a LEB128 variable-length signed 32-bit integer and advances {pc_}.
int32_t consume_i32v(const char* name = nullptr) {
int32_t consume_i32v(const char* name = "int32_t") {
uint32_t length = 0;
return read_leb<int32_t, true, true, true>(pc_, &length, name);
}
......@@ -151,40 +179,27 @@ class Decoder {
bool checkAvailable(int size) {
intptr_t pc_overflow_value = std::numeric_limits<intptr_t>::max() - size;
if (size < 0 || (intptr_t)pc_ > pc_overflow_value) {
errorf(pc_, "reading %d bytes would underflow/overflow", size);
error(pc_) << "reading " << size << " bytes would underflow/overflow";
return false;
} else if (pc_ < start_ || end_ < (pc_ + size)) {
errorf(pc_, "expected %d bytes, fell off end", size);
error(pc_) << "expected " << size << " bytes, fell off end";
return false;
} else {
return true;
}
}
void error(const char* msg) { errorf(pc_, "%s", msg); }
void error(const byte* pc, const char* msg) { errorf(pc, "%s", msg); }
// Sets internal error state.
void PRINTF_FORMAT(3, 4) errorf(const byte* pc, const char* format, ...) {
// ErrorFormatter sets internal error state in its destructor.
ErrorFormatter error(const byte* pc = nullptr) {
// Only report the first error.
if (!ok()) return;
if (!ok()) return {nullptr, nullptr};
#if DEBUG
if (FLAG_wasm_break_on_decoder_error) {
base::OS::DebugBreak();
}
#endif
constexpr int kMaxErrorMsg = 256;
EmbeddedVector<char, kMaxErrorMsg> buffer;
va_list arguments;
va_start(arguments, format);
int len = VSNPrintF(buffer, format, arguments);
CHECK_LT(0, len);
va_end(arguments);
error_msg_.assign(buffer.start(), len);
DCHECK_GE(pc, start_);
error_offset_ = static_cast<uint32_t>(pc - start_) + buffer_offset_;
onFirstError();
return {pc ? pc : pc_, this};
}
// Behavior triggered on first error, overridden in subclasses.
......@@ -233,9 +248,11 @@ class Decoder {
const byte* start() const { return start_; }
const byte* pc() const { return pc_; }
uint32_t pc_offset() const {
return static_cast<uint32_t>(pc_ - start_) + buffer_offset_;
uint32_t pc_offset(const byte* pc) const {
DCHECK_GE(pc, start_);
return static_cast<uint32_t>(pc - start_) + buffer_offset_;
}
uint32_t pc_offset() const { return pc_offset(pc_); }
uint32_t buffer_offset() const { return buffer_offset_; }
// Takes an offset relative to the module start and returns an offset relative
// to the current buffer of the decoder.
......@@ -318,7 +335,7 @@ class Decoder {
*length = byte_index + (at_end ? 0 : 1);
if (checked && (at_end || (b & 0x80))) {
TRACE_IF(trace, at_end ? "<end> " : "<length overflow> ");
errorf(pc, "expected %s", name);
error(pc) << "expected " << name;
result = 0;
}
if (is_last_byte) {
......@@ -338,7 +355,7 @@ class Decoder {
if (!checked) {
DCHECK(valid_extra_bits);
} else if (!valid_extra_bits) {
error(pc, "extra bits in varint");
error(pc) << "extra bits in varint";
result = 0;
}
}
......
......@@ -101,11 +101,11 @@ struct BlockTypeOperand {
} else {
// Handle multi-value blocks.
if (!CHECKED_COND(FLAG_experimental_wasm_mv)) {
decoder->error(pc + 1, "invalid block arity > 1");
decoder->error(pc + 1) << "invalid block arity > 1";
return;
}
if (!CHECKED_COND(val == kMultivalBlock)) {
decoder->error(pc + 1, "invalid block type");
decoder->error(pc + 1) << "invalid block type";
return;
}
// Decode and check the types vector of the block.
......@@ -123,7 +123,7 @@ struct BlockTypeOperand {
val = decoder->read_u8<checked>(pc + offset, "block type");
decode_local_type(val, &type);
if (!CHECKED_COND(type != kWasmStmt)) {
decoder->error(pc + offset, "invalid block type");
decoder->error(pc + offset) << "invalid block type";
return;
}
}
......@@ -187,8 +187,8 @@ struct CallIndirectOperand {
index = decoder->read_u32v<checked>(pc + 1, &len, "signature index");
table_index = decoder->read_u8<checked>(pc + 1 + len, "table index");
if (!CHECKED_COND(table_index == 0)) {
decoder->errorf(pc + 1 + len, "expected table index 0, found %u",
table_index);
decoder->error(pc + 1 + len)
<< "expected table index 0, found " << table_index;
}
length = 1 + len;
}
......@@ -211,7 +211,7 @@ struct MemoryIndexOperand {
inline MemoryIndexOperand(Decoder* decoder, const byte* pc) {
index = decoder->read_u8<checked>(pc + 1, "memory index");
if (!CHECKED_COND(index == 0)) {
decoder->errorf(pc + 1, "expected memory index 0, found %u", index);
decoder->error(pc + 1) << "expected memory index 0, found " << index;
}
}
};
......@@ -279,10 +279,9 @@ struct MemoryAccessOperand {
alignment =
decoder->read_u32v<checked>(pc + 1, &alignment_length, "alignment");
if (!CHECKED_COND(alignment <= max_alignment)) {
decoder->errorf(pc + 1,
"invalid alignment; expected maximum alignment is %u, "
"actual alignment is %u",
max_alignment, alignment);
decoder->error(pc + 1)
<< "invalid alignment; expected maximum alignment is "
<< max_alignment << ", actual alignment is " << alignment;
}
unsigned offset_length;
offset = decoder->read_u32v<checked>(pc + 1 + alignment_length,
......
......@@ -35,18 +35,18 @@ namespace wasm {
#define TRACE(...)
#endif
#define CHECK_PROTOTYPE_OPCODE(flag) \
if (module_ != nullptr && module_->is_asm_js()) { \
error("Opcode not supported for asmjs modules"); \
} \
if (!FLAG_experimental_wasm_##flag) { \
error("Invalid opcode (enable with --experimental-wasm-" #flag ")"); \
break; \
#define CHECK_PROTOTYPE_OPCODE(flag) \
if (module_ != nullptr && module_->is_asm_js()) { \
error() << "Opcode not supported for asmjs modules"; \
} \
if (!FLAG_experimental_wasm_##flag) { \
error() << "Invalid opcode (enable with --experimental-wasm-" #flag ")"; \
break; \
}
#define PROTOTYPE_NOT_FUNCTIONAL(opcode) \
errorf(pc_, "Prototype still not functional: %s", \
WasmOpcodes::OpcodeName(opcode));
#define PROTOTYPE_NOT_FUNCTIONAL(opcode) \
error() << "Prototype still not functional: " \
<< WasmOpcodes::OpcodeName(opcode);
// An SsaEnv environment carries the current local variable renaming
// as well as the current effect and control dependency in the TF graph.
......@@ -194,7 +194,7 @@ class WasmDecoder : public Decoder {
if (decoder->failed()) return false;
if ((count + type_list->size()) > kV8MaxWasmFunctionLocals) {
decoder->error(decoder->pc() - 1, "local count too large");
decoder->error(decoder->pc() - 1) << "local count too large";
return false;
}
byte code = decoder->consume_u8("local type");
......@@ -218,7 +218,7 @@ class WasmDecoder : public Decoder {
type = kWasmS128;
break;
default:
decoder->error(decoder->pc() - 1, "invalid local type");
decoder->error(decoder->pc() - 1) << "invalid local type";
return false;
}
type_list->insert(type_list->end(), count, type);
......@@ -279,7 +279,7 @@ class WasmDecoder : public Decoder {
}
return true;
}
errorf(pc + 1, "invalid local index: %u", operand.index);
error(pc + 1) << "invalid local index: " << operand.index;
return false;
}
......@@ -289,7 +289,7 @@ class WasmDecoder : public Decoder {
operand.type = operand.global->type;
return true;
}
errorf(pc + 1, "invalid global index: %u", operand.index);
error(pc + 1) << "invalid global index: " << operand.index;
return false;
}
......@@ -305,7 +305,7 @@ class WasmDecoder : public Decoder {
if (Complete(pc, operand)) {
return true;
}
errorf(pc + 1, "invalid function index: %u", operand.index);
error(pc + 1) << "invalid function index: " << operand.index;
return false;
}
......@@ -319,13 +319,13 @@ class WasmDecoder : public Decoder {
inline bool Validate(const byte* pc, CallIndirectOperand<true>& operand) {
if (module_ == nullptr || module_->function_tables.empty()) {
error("function table has to exist to execute call_indirect");
error() << "function table has to exist to execute call_indirect";
return false;
}
if (Complete(pc, operand)) {
return true;
}
errorf(pc + 1, "invalid signature index: #%u", operand.index);
error(pc + 1) << "invalid signature index: #" << operand.index;
return false;
}
......@@ -335,15 +335,15 @@ class WasmDecoder : public Decoder {
operand.target = &control[control.size() - operand.depth - 1];
return true;
}
errorf(pc + 1, "invalid break depth: %u", operand.depth);
error(pc + 1) << "invalid break depth: " << operand.depth;
return false;
}
bool Validate(const byte* pc, BranchTableOperand<true>& operand,
size_t block_depth) {
if (operand.table_count >= kV8MaxWasmFunctionSize) {
errorf(pc + 1, "invalid table count (> max function size): %u",
operand.table_count);
error(pc + 1) << "invalid table count (> max function size): "
<< operand.table_count;
return false;
}
return checkAvailable(operand.table_count);
......@@ -372,7 +372,7 @@ class WasmDecoder : public Decoder {
break;
}
if (operand.lane < 0 || operand.lane >= num_lanes) {
error(pc_ + 2, "invalid lane index");
error(pc_ + 2) << "invalid lane index";
return false;
} else {
return true;
......@@ -403,7 +403,7 @@ class WasmDecoder : public Decoder {
break;
}
if (operand.shift < 0 || operand.shift >= max_shift) {
error(pc_ + 2, "invalid shift amount");
error(pc_ + 2) << "invalid shift amount";
return false;
} else {
return true;
......@@ -416,7 +416,7 @@ class WasmDecoder : public Decoder {
max_lane = std::max(max_lane, operand.shuffle[i]);
// Shuffle indices must be in [0..31] for a 16 lane shuffle.
if (max_lane > 2 * kSimd128Size) {
error(pc_ + 2, "invalid shuffle mask");
error(pc_ + 2) << "invalid shuffle mask";
return false;
} else {
return true;
......@@ -515,7 +515,7 @@ class WasmDecoder : public Decoder {
case kExprS8x16Shuffle:
return 2 + kSimd128Size;
default:
decoder->error(pc, "invalid SIMD opcode");
decoder->error(pc) << "invalid SIMD opcode";
return 2;
}
}
......@@ -621,7 +621,7 @@ class WasmFullDecoder : public WasmDecoder {
control_.clear();
if (end_ < pc_) {
error("function body end < start");
error() << "function body end < start";
return false;
}
......@@ -637,15 +637,15 @@ class WasmFullDecoder : public WasmDecoder {
// Generate a better error message whether the unterminated control
// structure is the function body block or an innner structure.
if (control_.size() > 1) {
error(control_.back().pc, "unterminated control structure");
error(control_.back().pc) << "unterminated control structure";
} else {
error("function body must end with \"end\" opcode");
error() << "function body must end with \"end\" opcode";
}
return TraceFailed();
}
if (!last_end_found_) {
error("function body must end with \"end\" opcode");
error() << "function body must end with \"end\" opcode";
return false;
}
......@@ -747,7 +747,7 @@ class WasmFullDecoder : public WasmDecoder {
bool CheckHasMemory() {
if (!module_->has_memory) {
error(pc_ - 1, "memory instruction with no memory");
error(pc_ - 1) << "memory instruction with no memory";
}
return module_->has_memory;
}
......@@ -843,18 +843,18 @@ class WasmFullDecoder : public WasmDecoder {
len = 1 + operand.length;
if (control_.empty()) {
error("catch does not match any try");
error() << "catch does not match any try";
break;
}
Control* c = &control_.back();
if (!c->is_try()) {
error("catch does not match any try");
error() << "catch does not match any try";
break;
}
if (c->try_info->catch_env == nullptr) {
error(pc_, "catch already present for try with catch");
error() << "catch already present for try with catch";
break;
}
......@@ -915,16 +915,16 @@ class WasmFullDecoder : public WasmDecoder {
}
case kExprElse: {
if (control_.empty()) {
error("else does not match any if");
error() << "else does not match any if";
break;
}
Control* c = &control_.back();
if (!c->is_if()) {
error(pc_, "else does not match an if");
error() << "else does not match an if";
break;
}
if (c->false_env == nullptr) {
error(pc_, "else already present for if");
error() << "else already present for if";
break;
}
FallThruTo(c);
......@@ -936,7 +936,7 @@ class WasmFullDecoder : public WasmDecoder {
}
case kExprEnd: {
if (control_.empty()) {
error("end does not match any if, try, or block");
error() << "end does not match any if, try, or block";
return;
}
const char* name = "block:end";
......@@ -954,11 +954,11 @@ class WasmFullDecoder : public WasmDecoder {
// End the true branch of a one-armed if.
Goto(c->false_env, c->end_env);
if (!c->unreachable && stack_.size() != c->stack_depth) {
error("end of if expected empty stack");
error() << "end of if expected empty stack";
stack_.resize(c->stack_depth);
}
if (c->merge.arity > 0) {
error("non-void one-armed if");
error() << "non-void one-armed if";
}
name = "if:merge";
} else {
......@@ -970,7 +970,7 @@ class WasmFullDecoder : public WasmDecoder {
// validate that catch was seen.
if (c->try_info->catch_env != nullptr) {
error(pc_, "missing catch in try");
error(pc_) << "missing catch in try";
break;
}
}
......@@ -981,7 +981,7 @@ class WasmFullDecoder : public WasmDecoder {
if (control_.size() == 1) {
// If at the last (implicit) control, check we are at end.
if (pc_ + 1 != end_) {
error(pc_ + 1, "trailing code after function end");
error(pc_ + 1) << "trailing code after function end";
break;
}
last_end_found_ = true;
......@@ -1058,7 +1058,7 @@ class WasmFullDecoder : public WasmDecoder {
const byte* pos = iterator.pc();
uint32_t target = iterator.next();
if (target >= control_.size()) {
error(pos, "improper branch in br_table");
error(pos) << "improper branch in br_table";
break;
}
ssa_env_ = Split(copy);
......@@ -1074,18 +1074,18 @@ class WasmFullDecoder : public WasmDecoder {
if (i == 0) {
merge = current;
} else if (merge->arity != current->arity) {
errorf(pos,
"inconsistent arity in br_table target %d"
" (previous was %u, this one %u)",
i, merge->arity, current->arity);
error(pos) << "inconsistent arity in br_table target " << i
<< " (previous was " << merge->arity
<< ", this one " << current->arity << ")";
} else if (control_.back().unreachable) {
for (uint32_t j = 0; ok() && j < merge->arity; ++j) {
if ((*merge)[j].type != (*current)[j].type) {
errorf(pos,
"type error in br_table target %d operand %d"
" (previous expected %s, this one %s)",
i, j, WasmOpcodes::TypeName((*merge)[j].type),
WasmOpcodes::TypeName((*current)[j].type));
error(pos)
<< "type error in br_table target " << i
<< " operand " << j << " (previous expected "
<< WasmOpcodes::TypeName((*merge)[j].type)
<< ", this one "
<< WasmOpcodes::TypeName((*current)[j].type) << ")";
}
}
}
......@@ -1096,7 +1096,7 @@ class WasmFullDecoder : public WasmDecoder {
const byte* pos = iterator.pc();
uint32_t target = iterator.next();
if (target >= control_.size()) {
error(pos, "improper branch in br_table");
error(pos) << "improper branch in br_table";
break;
}
BreakTo(target);
......@@ -1191,8 +1191,8 @@ class WasmFullDecoder : public WasmDecoder {
Value val = Pop(0, operand.type);
BUILD(SetGlobal, operand.index, val.node);
} else {
errorf(pc_, "immutable global #%u cannot be assigned",
operand.index);
error() << "immutable global #" << operand.index
<< " cannot be assigned";
}
}
len = 1 + operand.length;
......@@ -1275,7 +1275,7 @@ class WasmFullDecoder : public WasmDecoder {
Value val = Pop(0, kWasmI32);
Push(kWasmI32, BUILD(GrowMemory, val.node));
} else {
error("grow_memory is not supported for asmjs modules");
error() << "grow_memory is not supported for asmjs modules";
}
len = 1 + operand.length;
break;
......@@ -1323,7 +1323,7 @@ class WasmFullDecoder : public WasmDecoder {
}
case kAtomicPrefix: {
if (module_ == nullptr || !module_->is_asm_js()) {
error("Atomics are allowed only in AsmJs modules");
error() << "Atomics are allowed only in AsmJs modules";
break;
}
CHECK_PROTOTYPE_OPCODE(threads);
......@@ -1344,7 +1344,7 @@ class WasmFullDecoder : public WasmDecoder {
BuildSimpleOperator(opcode, sig);
}
} else {
error("Invalid opcode");
error() << "Invalid opcode";
return;
}
}
......@@ -1418,7 +1418,7 @@ class WasmFullDecoder : public WasmDecoder {
#endif
pc_ += len;
} // end decode loop
if (pc_ > end_ && ok()) error("Beyond end of code");
if (pc_ > end_ && ok()) error() << "Beyond end of code";
}
void FinishFunction() {
......@@ -1638,7 +1638,7 @@ class WasmFullDecoder : public WasmDecoder {
TFNode* node = BUILD(SimdOp, opcode, inputs.data());
Push(GetReturnType(sig), node);
} else {
error("invalid simd opcode");
error() << "invalid simd opcode";
}
}
}
......@@ -1696,9 +1696,10 @@ class WasmFullDecoder : public WasmDecoder {
Value Pop(int index, ValueType expected) {
Value val = Pop();
if (val.type != expected && val.type != kWasmVar && expected != kWasmVar) {
errorf(val.pc, "%s[%d] expected type %s, found %s of type %s",
SafeOpcodeNameAt(pc_), index, WasmOpcodes::TypeName(expected),
SafeOpcodeNameAt(val.pc), WasmOpcodes::TypeName(val.type));
error(val.pc) << SafeOpcodeNameAt(pc_) << "[" << index
<< "] expected type " << WasmOpcodes::TypeName(expected)
<< ", found " << SafeOpcodeNameAt(val.pc) << " of type "
<< WasmOpcodes::TypeName(val.type);
}
return val;
}
......@@ -1709,7 +1710,7 @@ class WasmFullDecoder : public WasmDecoder {
// Popping past the current control start in reachable code.
Value val = {pc_, nullptr, kWasmVar};
if (!control_.back().unreachable) {
errorf(pc_, "%s found empty stack", SafeOpcodeNameAt(pc_));
error(pc_) << SafeOpcodeNameAt(pc_) << " found empty stack";
}
return val;
}
......@@ -1729,11 +1730,9 @@ class WasmFullDecoder : public WasmDecoder {
// Merge the value(s) into the end of the block.
size_t expected = control_.back().stack_depth + c->merge.arity;
if (stack_.size() < expected && !control_.back().unreachable) {
errorf(
pc_,
"expected at least %u values on the stack for br to @%d, found %d",
c->merge.arity, startrel(c->pc),
static_cast<int>(stack_.size() - c->stack_depth));
error() << "expected at least " << c->merge.arity
<< " values on the stack for br to @" << startrel(c->pc)
<< ", found " << stack_.size() - c->stack_depth;
return;
}
MergeValuesInto(c);
......@@ -1750,8 +1749,9 @@ class WasmFullDecoder : public WasmDecoder {
c->unreachable = false;
return;
}
errorf(pc_, "expected %u elements on the stack for fallthru to @%d",
c->merge.arity, startrel(c->pc));
error() << "expected " << c->merge.arity
<< " elements on the stack for fallthru to @",
startrel(c->pc);
}
inline Value& GetMergeValueFromStack(Control* c, size_t i) {
......@@ -1764,8 +1764,8 @@ class WasmFullDecoder : public WasmDecoder {
int arity = static_cast<int>(c->merge.arity);
if (c->stack_depth + arity < stack_.size() ||
(c->stack_depth + arity != stack_.size() && !c->unreachable)) {
errorf(pc_, "expected %d elements on the stack for fallthru to @%d",
arity, startrel(c->pc));
error() << "expected " << arity
<< " elements on the stack for fallthru to @" << startrel(c->pc);
return;
}
// Typecheck the values left on the stack.
......@@ -1775,9 +1775,9 @@ class WasmFullDecoder : public WasmDecoder {
Value& val = GetMergeValueFromStack(c, i);
Value& old = c->merge[i];
if (val.type != old.type) {
errorf(pc_, "type error in merge[%zu] (expected %s, got %s)", i,
WasmOpcodes::TypeName(old.type),
WasmOpcodes::TypeName(val.type));
error() << "type error in merge[" << i << "] (expected "
<< WasmOpcodes::TypeName(old.type) << ", got "
<< WasmOpcodes::TypeName(val.type) << ")";
return;
}
}
......@@ -1795,9 +1795,9 @@ class WasmFullDecoder : public WasmDecoder {
Value& val = GetMergeValueFromStack(c, i);
Value& old = c->merge[i];
if (val.type != old.type && val.type != kWasmVar) {
errorf(pc_, "type error in merge[%zu] (expected %s, got %s)", i,
WasmOpcodes::TypeName(old.type),
WasmOpcodes::TypeName(val.type));
error() << "type error in merge[" << i << "] (expected "
<< WasmOpcodes::TypeName(old.type) << ", got "
<< WasmOpcodes::TypeName(val.type) << ")";
return;
}
if (builder_ && reachable) {
......
......@@ -112,7 +112,7 @@ WireBytesRef consume_string(Decoder& decoder, bool validate_utf8,
decoder.consume_bytes(length, name);
if (decoder.ok() && validate_utf8 &&
!unibrow::Utf8::ValidateEncoding(string_start, length)) {
decoder.errorf(string_start, "%s: no valid UTF-8 string", name);
decoder.error(string_start) << name << ": no valid UTF-8 string";
}
}
return {offset, decoder.failed() ? 0 : length};
......@@ -161,11 +161,10 @@ class WasmSectionIterator {
}
if (decoder_.pc() != section_end_) {
const char* msg = decoder_.pc() < section_end_ ? "shorter" : "longer";
decoder_.errorf(decoder_.pc(),
"section was %s than expected size "
"(%u bytes expected, %zu decoded)",
msg, section_length(),
static_cast<size_t>(decoder_.pc() - section_start_));
decoder_.error(decoder_.pc())
<< "section was " << msg << " than expected size ("
<< section_length() << " bytes expected, "
<< decoder_.pc() - section_start_ << " decoded)";
}
next();
}
......@@ -225,8 +224,8 @@ class WasmSectionIterator {
section_code = kExceptionSectionCode;
}
} else if (!IsValidSectionCode(section_code)) {
decoder_.errorf(decoder_.pc(), "unknown section code #0x%02x",
section_code);
decoder_.error() << "unknown section code "
<< AsHex(section_code, 2, true);
section_code = kUnknownSectionCode;
}
section_code_ = decoder_.failed() ? kUnknownSectionCode
......@@ -249,7 +248,7 @@ class ModuleDecoder : public Decoder {
: Decoder(module_start, module_end),
origin_(FLAG_assume_asmjs_origin ? kAsmJsOrigin : origin) {
if (end_ < start_) {
error(start_, "end is less than start");
error(start_) << "end is less than start";
end_ = start_;
}
}
......@@ -301,22 +300,17 @@ class ModuleDecoder : public Decoder {
const byte* pos = pc_;
uint32_t magic_word = consume_u32("wasm magic");
#define BYTES(x) (x & 0xff), (x >> 8) & 0xff, (x >> 16) & 0xff, (x >> 24) & 0xff
if (magic_word != kWasmMagic) {
errorf(pos,
"expected magic word %02x %02x %02x %02x, "
"found %02x %02x %02x %02x",
BYTES(kWasmMagic), BYTES(magic_word));
error(pos) << "expected magic word " << AsHexBytes(kWasmMagic, 4)
<< ", found " << AsHexBytes(magic_word, 4);
}
pos = pc_;
{
uint32_t magic_version = consume_u32("wasm version");
if (magic_version != kWasmVersion) {
errorf(pos,
"expected version %02x %02x %02x %02x, "
"found %02x %02x %02x %02x",
BYTES(kWasmVersion), BYTES(magic_version));
error(pos) << "expected version " << AsHexBytes(kWasmVersion, 4)
<< ", found " << AsHexBytes(magic_version, 4);
}
}
}
......@@ -328,7 +322,7 @@ class ModuleDecoder : public Decoder {
// Check if the section is out-of-order.
if (section_code < next_section_) {
errorf(pc(), "unexpected section: %s", SectionName(section_code));
error() << "unexpected section: " << SectionName(section_code);
return;
}
if (section_code != kUnknownSectionCode) {
......@@ -379,16 +373,15 @@ class ModuleDecoder : public Decoder {
DecodeExceptionSection();
break;
default:
errorf(pc(), "unexpected section: %s", SectionName(section_code));
error() << "unexpected section: " << SectionName(section_code);
return;
}
if (pc() != bytes.end()) {
const char* msg = pc() < bytes.end() ? "shorter" : "longer";
errorf(pc(),
"section was %s than expected size "
"(%zu bytes expected, %zu decoded)",
msg, bytes.size(), static_cast<size_t>(pc() - bytes.begin()));
error() << "section was " << msg << " than expected size ("
<< bytes.size() << " bytes expected, " << pc() - bytes.begin()
<< " decoded)";
}
}
......@@ -472,12 +465,12 @@ class ModuleDecoder : public Decoder {
global->type = consume_value_type();
global->mutability = consume_mutability();
if (global->mutability) {
error("mutable globals cannot be imported");
error() << "mutable globals cannot be imported";
}
break;
}
default:
errorf(pos, "unknown import kind 0x%02x", import->kind);
error(pos) << "unknown import kind " << AsHex(import->kind, 2, true);
break;
}
}
......@@ -581,7 +574,7 @@ class ModuleDecoder : public Decoder {
// TODO(titzer): This should become more regular
// once we support multiple memories.
if (!module_->has_memory || index != 0) {
error("invalid memory index != 0");
error() << "invalid memory index != 0";
}
module_->mem_export = true;
break;
......@@ -591,14 +584,14 @@ class ModuleDecoder : public Decoder {
exp->index = consume_global_index(module_.get(), &global);
if (global) {
if (global->mutability) {
error("mutable globals cannot be exported");
error() << "mutable globals cannot be exported";
}
global->exported = true;
}
break;
}
default:
errorf(pos, "invalid export kind 0x%02x", exp->kind);
error(pos) << "invalid export kind " << AsHex(exp->kind, 2, true);
break;
}
}
......@@ -623,9 +616,12 @@ class ModuleDecoder : public Decoder {
DCHECK(!cmp_less(*it, *last)); // Vector must be sorted.
if (!cmp_less(*last, *it)) {
const byte* pc = start() + GetBufferRelativeOffset(it->name.offset());
errorf(pc, "Duplicate export name '%.*s' for %s %d and %s %d",
it->name.length(), pc, ExternalKindName(last->kind),
last->index, ExternalKindName(it->kind), it->index);
std::string name(reinterpret_cast<const char*>(pc),
it->name.length());
error(pc) << "Duplicate export name '" << name << "' for "
<< ExternalKindName(last->kind) << " " << last->index
<< " and " << ExternalKindName(it->kind) << " "
<< it->index;
break;
}
}
......@@ -638,7 +634,8 @@ class ModuleDecoder : public Decoder {
module_->start_function_index = consume_func_index(module_.get(), &func);
if (func &&
(func->sig->parameter_count() > 0 || func->sig->return_count() > 0)) {
error(pos, "invalid start function: non-zero parameter or return count");
error(pos)
<< "invalid start function: non-zero parameter or return count";
}
}
......@@ -649,11 +646,11 @@ class ModuleDecoder : public Decoder {
const byte* pos = pc();
uint32_t table_index = consume_u32v("table index");
if (table_index != 0) {
errorf(pos, "illegal table index %u != 0", table_index);
error(pos) << "illegal table index " << table_index << " != 0";
}
WasmIndirectFunctionTable* table = nullptr;
if (table_index >= module_->function_tables.size()) {
errorf(pos, "out of bounds table index %u", table_index);
error(pos) << "out of bounds table index " << table_index;
break;
}
table = &module_->function_tables[table_index];
......@@ -679,8 +676,8 @@ class ModuleDecoder : public Decoder {
const byte* pos = pc_;
uint32_t functions_count = consume_u32v("functions count");
if (functions_count != module_->num_declared_functions) {
errorf(pos, "function body count %u mismatch (%u expected)",
functions_count, module_->num_declared_functions);
error(pos) << "function body count " << functions_count << " mismatch ("
<< module_->num_declared_functions << " expected)";
}
for (uint32_t i = 0; i < functions_count; ++i) {
uint32_t size = consume_u32v("body size");
......@@ -706,7 +703,7 @@ class ModuleDecoder : public Decoder {
module_->data_segments.reserve(data_segments_count);
for (uint32_t i = 0; ok() && i < data_segments_count; ++i) {
if (!module_->has_memory) {
error("cannot load data without memory");
error() << "cannot load data without memory";
break;
}
TRACE("DecodeDataSegment[%d] module+%d\n", i,
......@@ -728,7 +725,7 @@ class ModuleDecoder : public Decoder {
// Be lenient with their order.
while (inner.ok() && inner.more()) {
uint8_t name_type = inner.consume_u8("name type");
if (name_type & 0x80) inner.error("name type if not varuint7");
if (name_type & 0x80) inner.error() << "name type if not varuint7";
uint32_t name_payload_len = inner.consume_u32v("name payload length");
if (!inner.checkAvailable(name_payload_len)) break;
......@@ -872,7 +869,7 @@ class ModuleDecoder : public Decoder {
bool AddTable(WasmModule* module) {
if (module->function_tables.size() > 0) {
error("At most one table is supported");
error() << "At most one table is supported";
return false;
} else {
return true;
......@@ -881,7 +878,7 @@ class ModuleDecoder : public Decoder {
bool AddMemory(WasmModule* module) {
if (module->has_memory) {
error("At most one memory is supported");
error() << "At most one memory is supported";
return false;
} else {
module->has_memory = true;
......@@ -900,25 +897,22 @@ class ModuleDecoder : public Decoder {
case WasmInitExpr::kGlobalIndex: {
uint32_t other_index = global->init.val.global_index;
if (other_index >= index) {
errorf(pos,
"invalid global index in init expression, "
"index %u, other_index %u",
index, other_index);
error(pos) << "invalid global index in init expression, index "
<< index << ", other_index " << other_index;
} else if (module->globals[other_index].type != global->type) {
errorf(pos,
"type mismatch in global initialization "
"(from global #%u), expected %s, got %s",
other_index, WasmOpcodes::TypeName(global->type),
WasmOpcodes::TypeName(module->globals[other_index].type));
error(pos) << "type mismatch in global initialization (from global #"
<< other_index << "), expected "
<< WasmOpcodes::TypeName(global->type) << ", got "
<< WasmOpcodes::TypeName(
module->globals[other_index].type);
}
break;
}
default:
if (global->type != TypeOf(module, global->init)) {
errorf(pos,
"type error in global initialization, expected %s, got %s",
WasmOpcodes::TypeName(global->type),
WasmOpcodes::TypeName(TypeOf(module, global->init)));
error(pos) << "type error in global initialization, expected "
<< WasmOpcodes::TypeName(global->type) << ", got "
<< WasmOpcodes::TypeName(TypeOf(module, global->init));
}
}
}
......@@ -942,7 +936,7 @@ class ModuleDecoder : public Decoder {
uint32_t limit = static_cast<uint32_t>(end_ - start_);
if (!IsWithinLimit(limit, GetBufferRelativeOffset(segment->source.offset()),
segment->source.length())) {
error(start, "segment out of bounds of the section");
error(start) << "segment out of bounds of the section";
}
consume_bytes(segment->source.length(), "segment data");
......@@ -1007,8 +1001,8 @@ class ModuleDecoder : public Decoder {
const byte* pos = pc_;
uint32_t sig_index = consume_u32v("signature index");
if (sig_index >= module->signatures.size()) {
errorf(pos, "signature index %u out of bounds (%d signatures)", sig_index,
static_cast<int>(module->signatures.size()));
error(pos) << "signature index " << sig_index << " out of bounds ("
<< module->signatures.size() << " signatures)";
*sig = nullptr;
return 0;
}
......@@ -1020,7 +1014,8 @@ class ModuleDecoder : public Decoder {
const byte* p = pc_;
uint32_t count = consume_u32v(name);
if (count > maximum) {
errorf(p, "%s of %u exceeds internal limit of %zu", name, count, maximum);
error(p) << name << " of " << count << " exceeds internal limit of "
<< maximum;
return static_cast<uint32_t>(maximum);
}
return count;
......@@ -1044,8 +1039,8 @@ class ModuleDecoder : public Decoder {
const byte* pos = pc_;
uint32_t index = consume_u32v(name);
if (index >= vector.size()) {
errorf(pos, "%s %u out of bounds (%d entr%s)", name, index,
static_cast<int>(vector.size()), vector.size() == 1 ? "y" : "ies");
error(pos) << name << " " << index << " out of bounds (" << vector.size()
<< " entr" << (vector.size() == 1 ? "y" : "ies") << ")";
*ptr = nullptr;
return 0;
}
......@@ -1062,23 +1057,23 @@ class ModuleDecoder : public Decoder {
*initial = consume_u32v("initial size");
*has_max = false;
if (*initial > max_initial) {
errorf(pos,
"initial %s size (%u %s) is larger than implementation limit (%u)",
name, *initial, units, max_initial);
error(pos) << "initial " << name << " size (" << *initial << " " << units
<< ") is larger than implementation limit (" << max_initial
<< ")";
}
if (flags & 1) {
*has_max = true;
pos = pc();
*maximum = consume_u32v("maximum size");
if (*maximum > max_maximum) {
errorf(
pos,
"maximum %s size (%u %s) is larger than implementation limit (%u)",
name, *maximum, units, max_maximum);
error(pos) << "maximum " << name << " size (" << *maximum << units
<< ") is larger than implementation limit (" << max_maximum
<< ")";
}
if (*maximum < *initial) {
errorf(pos, "maximum %s size (%u %s) is less than initial (%u %s)",
name, *maximum, units, *initial, units);
error(pos) << "maximum " << name << " size (" << *maximum << " "
<< units << ") is less than initial (" << *initial << " "
<< units << ")";
}
} else {
*has_max = false;
......@@ -1090,7 +1085,8 @@ class ModuleDecoder : public Decoder {
const byte* pos = pc();
uint8_t value = consume_u8(name);
if (value != expected) {
errorf(pos, "expected %s 0x%02x, got 0x%02x", name, expected, value);
error(pos) << "expected " << name << " " << AsHex(expected, 2, true)
<< "<< got " << AsHex(value, 2, true);
return false;
}
return true;
......@@ -1105,16 +1101,15 @@ class ModuleDecoder : public Decoder {
case kExprGetGlobal: {
GlobalIndexOperand<true> operand(this, pc() - 1);
if (module->globals.size() <= operand.index) {
error("global index is out of bounds");
error() << "global index is out of bounds";
expr.kind = WasmInitExpr::kNone;
expr.val.i32_const = 0;
break;
}
WasmGlobal* global = &module->globals[operand.index];
if (global->mutability || !global->imported) {
error(
"only immutable imported globals can be used in initializer "
"expressions");
error() << "only immutable imported globals can be used in "
"initializer expressions";
expr.kind = WasmInitExpr::kNone;
expr.val.i32_const = 0;
break;
......@@ -1153,7 +1148,7 @@ class ModuleDecoder : public Decoder {
break;
}
default: {
error("invalid opcode in initialization expression");
error() << "invalid opcode in initialization expression";
expr.kind = WasmInitExpr::kNone;
expr.val.i32_const = 0;
}
......@@ -1163,9 +1158,9 @@ class ModuleDecoder : public Decoder {
expr.kind = WasmInitExpr::kNone;
}
if (expected != kWasmStmt && TypeOf(module, expr) != kWasmI32) {
errorf(pos, "type error in init expression, expected %s, got %s",
WasmOpcodes::TypeName(expected),
WasmOpcodes::TypeName(TypeOf(module, expr)));
error(pos) << "type error in init expression, expected "
<< WasmOpcodes::TypeName(expected) << ", got "
<< WasmOpcodes::TypeName(TypeOf(module, expr));
}
return expr;
}
......@@ -1173,7 +1168,7 @@ class ModuleDecoder : public Decoder {
// Read a mutability flag
bool consume_mutability() {
byte val = consume_u8("mutability");
if (val > 1) error(pc_ - 1, "invalid mutability");
if (val > 1) error(pc_ - 1) << "invalid mutability";
return val != 0;
}
......@@ -1199,7 +1194,7 @@ class ModuleDecoder : public Decoder {
break;
}
}
error(pc_ - 1, "invalid local type");
error(pc_ - 1) << "invalid local type";
return kWasmStmt;
}
}
......@@ -1378,7 +1373,7 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(const byte* tables_start,
continue;
}
if (!decoder.checkAvailable(size)) {
decoder.error("illegal asm function offset table size");
decoder.error() << "illegal asm function offset table size";
}
const byte* table_end = decoder.pc() + size;
uint32_t locals_size = decoder.consume_u32v("locals size");
......@@ -1401,11 +1396,11 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(const byte* tables_start,
{last_byte_offset, call_position, to_number_position});
}
if (decoder.pc() != table_end) {
decoder.error("broken asm offset table");
decoder.error() << "broken asm offset table";
}
table.push_back(std::move(func_asm_offsets));
}
if (decoder.more()) decoder.error("unexpected additional bytes");
if (decoder.more()) decoder.error() << "unexpected additional bytes";
return decoder.toResult(std::move(table));
}
......
......@@ -237,35 +237,31 @@ StreamingDecoder::DecodeVarInt32::Next(StreamingDecoder* streaming) {
return nullptr;
}
if (value() > max_value_) {
streaming->decoder()->errorf(buffer(), "size > maximum function size: %zu",
value());
streaming->decoder()->error(buffer())
<< "size > maximum function size: " << value();
return nullptr;
}
return NextWithValue(streaming);
}
#define BYTES(x) (x & 0xff), (x >> 8) & 0xff, (x >> 16) & 0xff, (x >> 24) & 0xff
// Decode the module header. The error state of the decoder stores the result.
void StreamingDecoder::DecodeModuleHeader::CheckHeader(Decoder* decoder) {
// TODO(ahaas): Share code with the module-decoder.
decoder->Reset(buffer(), buffer() + size());
uint32_t magic_word = decoder->consume_u32("wasm magic");
if (magic_word != kWasmMagic) {
decoder->errorf(buffer(),
"expected magic word %02x %02x %02x %02x, "
"found %02x %02x %02x %02x",
BYTES(kWasmMagic), BYTES(magic_word));
decoder->error(buffer())
<< "expected magic word " << AsHexBytes(kWasmMagic, 4) << ", found "
<< AsHexBytes(magic_word, 4);
}
uint32_t magic_version = decoder->consume_u32("wasm version");
if (magic_version != kWasmVersion) {
decoder->errorf(buffer(),
"expected version %02x %02x %02x %02x, "
"found %02x %02x %02x %02x",
BYTES(kWasmVersion), BYTES(magic_version));
decoder->error(buffer())
<< "expected version " << AsHexBytes(kWasmVersion, 4) << ", found "
<< AsHexBytes(magic_version, 4);
}
}
#undef BYTES
std::unique_ptr<StreamingDecoder::DecodingState>
StreamingDecoder::DecodeModuleHeader::Next(StreamingDecoder* streaming) {
......@@ -309,7 +305,7 @@ StreamingDecoder::DecodeNumberOfFunctions::NextWithValue(
memcpy(section_buffer_->bytes() + section_buffer_->payload_offset(),
buffer(), bytes_needed());
} else {
streaming->decoder()->error("Invalid code section length");
streaming->decoder()->error() << "Invalid code section length";
return base::make_unique<DecodeSectionID>();
}
......@@ -330,17 +326,17 @@ StreamingDecoder::DecodeFunctionLength::NextWithValue(
if (section_buffer_->length() >= buffer_offset_ + bytes_needed()) {
memcpy(section_buffer_->bytes() + buffer_offset_, buffer(), bytes_needed());
} else {
streaming->decoder()->error("Invalid code section length");
streaming->decoder()->error() << "Invalid code section length";
return base::make_unique<DecodeSectionID>();
}
// {value} is the length of the function.
if (value() == 0) {
streaming->decoder()->errorf(buffer(), "Invalid function length (0)");
streaming->decoder()->error(buffer()) << "Invalid function length (0)";
return nullptr;
} else if (buffer_offset() + bytes_needed() + value() >
section_buffer()->length()) {
streaming->decoder()->errorf(buffer(), "not enough code section bytes");
streaming->decoder()->error(buffer()) << "not enough code section bytes";
return nullptr;
}
......@@ -360,9 +356,9 @@ StreamingDecoder::DecodeFunctionBody::Next(StreamingDecoder* streaming) {
streaming->decoder()->Reset(
section_buffer()->bytes(),
section_buffer()->bytes() + section_buffer()->length());
streaming->decoder()->errorf(
section_buffer()->bytes() + buffer_offset() + size(),
"not all code section bytes were used");
streaming->decoder()->error(section_buffer()->bytes() + buffer_offset() +
size())
<< "not all code section bytes were used";
return nullptr;
}
return base::make_unique<DecodeSectionID>();
......
......@@ -56,3 +56,20 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
() => builder.instantiate(), WebAssembly.CompileError,
/Duplicate export name 'foo' for global 0 and function 0/);
})();
(function veryLongExportName() {
// Regression test for crbug.com/740023.
var export_name = 'abc';
while (export_name.length < 8192) {
export_name = export_name.concat(export_name);
}
var builder = new WasmModuleBuilder();
var global = builder.addGlobal(kWasmI64, false);
global.exportAs(export_name);
global.exportAs(export_name);
var error_msg =
'Duplicate export name \'' + export_name + '\' for global 0 and global 0';
assertThrows(
() => builder.instantiate(), WebAssembly.CompileError,
new RegExp(error_msg));
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment