| Index: src/wasm/module-decoder.cc
|
| diff --git a/src/wasm/module-decoder.cc b/src/wasm/module-decoder.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..82588bda07e9afacbe33ac8610a230a5b372ab13
|
| --- /dev/null
|
| +++ b/src/wasm/module-decoder.cc
|
| @@ -0,0 +1,532 @@
|
| +// Copyright 2015 the V8 project authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "src/macro-assembler.h"
|
| +#include "src/objects.h"
|
| +#include "src/v8.h"
|
| +
|
| +#include "src/wasm/decoder.h"
|
| +#include "src/wasm/module-decoder.h"
|
| +
|
| +namespace v8 {
|
| +namespace internal {
|
| +namespace wasm {
|
| +
|
| +#if DEBUG
|
| +#define TRACE(...) \
|
| + do { \
|
| + if (FLAG_trace_wasm_decoder) PrintF(__VA_ARGS__); \
|
| + } while (false)
|
| +#else
|
| +#define TRACE(...)
|
| +#endif
|
| +
|
| +
|
| +// The main logic for decoding the bytes of a module.
|
| +class ModuleDecoder : public Decoder {
|
| + public:
|
| + ModuleDecoder(Zone* zone, const byte* module_start, const byte* module_end,
|
| + bool asm_js)
|
| + : Decoder(module_start, module_end), module_zone(zone), asm_js_(asm_js) {
|
| + result_.start = start_;
|
| + if (limit_ < start_) {
|
| + error(start_, "end is less than start");
|
| + limit_ = start_;
|
| + }
|
| + }
|
| +
|
| + virtual void onFirstError() {
|
| + pc_ = limit_; // On error, terminate section decoding loop.
|
| + }
|
| +
|
| + // Decodes an entire module.
|
| + ModuleResult DecodeModule(WasmModule* module, bool verify_functions = true) {
|
| + pc_ = start_;
|
| + module->module_start = start_;
|
| + module->module_end = limit_;
|
| + module->min_mem_size_log2 = 0;
|
| + module->max_mem_size_log2 = 0;
|
| + module->mem_export = false;
|
| + module->mem_external = false;
|
| + module->globals = new std::vector<WasmGlobal>();
|
| + module->signatures = new std::vector<FunctionSig*>();
|
| + module->functions = new std::vector<WasmFunction>();
|
| + module->data_segments = new std::vector<WasmDataSegment>();
|
| + module->function_table = new std::vector<uint16_t>();
|
| +
|
| + bool sections[kMaxModuleSectionCode];
|
| + memset(sections, 0, sizeof(sections));
|
| +
|
| + // Decode the module sections.
|
| + while (pc_ < limit_) {
|
| + TRACE("DecodeSection\n");
|
| + WasmSectionDeclCode section =
|
| + static_cast<WasmSectionDeclCode>(u8("section"));
|
| + // Each section should appear at most once.
|
| + if (section < kMaxModuleSectionCode) {
|
| + CheckForPreviousSection(sections, section, false);
|
| + sections[section] = true;
|
| + }
|
| +
|
| + switch (section) {
|
| + case kDeclEnd:
|
| + // Terminate section decoding.
|
| + limit_ = pc_;
|
| + break;
|
| + case kDeclMemory:
|
| + module->min_mem_size_log2 = u8("min memory");
|
| + module->max_mem_size_log2 = u8("max memory");
|
| + module->mem_export = u8("export memory") != 0;
|
| + break;
|
| + case kDeclSignatures: {
|
| + int length;
|
| + uint32_t signatures_count = u32v(&length, "signatures count");
|
| + module->signatures->reserve(SafeReserve(signatures_count));
|
| + // Decode signatures.
|
| + for (uint32_t i = 0; i < signatures_count; i++) {
|
| + if (failed()) break;
|
| + TRACE("DecodeSignature[%d] module+%d\n", i,
|
| + static_cast<int>(pc_ - start_));
|
| + FunctionSig* s = sig(); // read function sig.
|
| + module->signatures->push_back(s);
|
| + }
|
| + break;
|
| + }
|
| + case kDeclFunctions: {
|
| + // Functions require a signature table first.
|
| + CheckForPreviousSection(sections, kDeclSignatures, true);
|
| + int length;
|
| + uint32_t functions_count = u32v(&length, "functions count");
|
| + module->functions->reserve(SafeReserve(functions_count));
|
| + // Set up module environment for verification.
|
| + ModuleEnv menv;
|
| + menv.module = module;
|
| + menv.globals_area = 0;
|
| + menv.mem_start = 0;
|
| + menv.mem_end = 0;
|
| + menv.function_code = nullptr;
|
| + menv.asm_js = asm_js_;
|
| + // Decode functions.
|
| + for (uint32_t i = 0; i < functions_count; i++) {
|
| + if (failed()) break;
|
| + TRACE("DecodeFunction[%d] module+%d\n", i,
|
| + static_cast<int>(pc_ - start_));
|
| +
|
| + module->functions->push_back(
|
| + {nullptr, 0, 0, 0, 0, 0, 0, false, false});
|
| + WasmFunction* function = &module->functions->back();
|
| + DecodeFunctionInModule(module, function, false);
|
| + }
|
| + if (ok() && verify_functions) {
|
| + for (uint32_t i = 0; i < functions_count; i++) {
|
| + if (failed()) break;
|
| + WasmFunction* function = &module->functions->at(i);
|
| + if (!function->external) {
|
| + VerifyFunctionBody(i, &menv, function);
|
| + if (result_.failed())
|
| + error(result_.error_pc, result_.error_msg.get());
|
| + }
|
| + }
|
| + }
|
| + break;
|
| + }
|
| + case kDeclGlobals: {
|
| + int length;
|
| + uint32_t globals_count = u32v(&length, "globals count");
|
| + module->globals->reserve(SafeReserve(globals_count));
|
| + // Decode globals.
|
| + for (uint32_t i = 0; i < globals_count; i++) {
|
| + if (failed()) break;
|
| + TRACE("DecodeGlobal[%d] module+%d\n", i,
|
| + static_cast<int>(pc_ - start_));
|
| + module->globals->push_back({0, MachineType::Int32(), 0, false});
|
| + WasmGlobal* global = &module->globals->back();
|
| + DecodeGlobalInModule(global);
|
| + }
|
| + break;
|
| + }
|
| + case kDeclDataSegments: {
|
| + int length;
|
| + uint32_t data_segments_count = u32v(&length, "data segments count");
|
| + module->data_segments->reserve(SafeReserve(data_segments_count));
|
| + // Decode data segments.
|
| + for (uint32_t i = 0; i < data_segments_count; i++) {
|
| + if (failed()) break;
|
| + TRACE("DecodeDataSegment[%d] module+%d\n", i,
|
| + static_cast<int>(pc_ - start_));
|
| + module->data_segments->push_back({0, 0, 0});
|
| + WasmDataSegment* segment = &module->data_segments->back();
|
| + DecodeDataSegmentInModule(segment);
|
| + }
|
| + break;
|
| + }
|
| + case kDeclFunctionTable: {
|
| + // An indirect function table requires functions first.
|
| + CheckForPreviousSection(sections, kDeclFunctions, true);
|
| + int length;
|
| + uint32_t function_table_count = u32v(&length, "function table count");
|
| + module->function_table->reserve(SafeReserve(function_table_count));
|
| + // Decode function table.
|
| + for (uint32_t i = 0; i < function_table_count; i++) {
|
| + if (failed()) break;
|
| + TRACE("DecodeFunctionTable[%d] module+%d\n", i,
|
| + static_cast<int>(pc_ - start_));
|
| + uint16_t index = u16();
|
| + if (index >= module->functions->size()) {
|
| + error(pc_ - 2, "invalid function index");
|
| + break;
|
| + }
|
| + module->function_table->push_back(index);
|
| + }
|
| + break;
|
| + }
|
| + default:
|
| + error(pc_ - 1, nullptr, "unrecognized section 0x%02x", section);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + return toResult(module);
|
| + }
|
| +
|
| + uint32_t SafeReserve(uint32_t count) {
|
| + // Avoid OOM by only reserving up to a certain size.
|
| + const uint32_t kMaxReserve = 20000;
|
| + return count < kMaxReserve ? count : kMaxReserve;
|
| + }
|
| +
|
| + void CheckForPreviousSection(bool* sections, WasmSectionDeclCode section,
|
| + bool present) {
|
| + if (section >= kMaxModuleSectionCode) return;
|
| + if (sections[section] == present) return;
|
| + const char* name = "";
|
| + switch (section) {
|
| + case kDeclMemory:
|
| + name = "memory";
|
| + break;
|
| + case kDeclSignatures:
|
| + name = "signatures";
|
| + break;
|
| + case kDeclFunctions:
|
| + name = "function declaration";
|
| + break;
|
| + case kDeclGlobals:
|
| + name = "global variable";
|
| + break;
|
| + case kDeclDataSegments:
|
| + name = "data segment";
|
| + break;
|
| + case kDeclFunctionTable:
|
| + name = "function table";
|
| + break;
|
| + default:
|
| + name = "";
|
| + break;
|
| + }
|
| + if (present) {
|
| + error(pc_ - 1, nullptr, "required %s section missing", name);
|
| + } else {
|
| + error(pc_ - 1, nullptr, "%s section already present", name);
|
| + }
|
| + }
|
| +
|
| + // Decodes a single anonymous function starting at {start_}.
|
| + FunctionResult DecodeSingleFunction(ModuleEnv* module_env,
|
| + WasmFunction* function) {
|
| + pc_ = start_;
|
| + function->sig = sig(); // read signature
|
| + function->name_offset = 0; // ---- name
|
| + function->code_start_offset = off(pc_ + 8); // ---- code start
|
| + function->code_end_offset = off(limit_); // ---- code end
|
| + function->local_int32_count = u16(); // read u16
|
| + function->local_int64_count = u16(); // read u16
|
| + function->local_float32_count = u16(); // read u16
|
| + function->local_float64_count = u16(); // read u16
|
| + function->exported = false; // ---- exported
|
| + function->external = false; // ---- external
|
| +
|
| + if (ok()) VerifyFunctionBody(0, module_env, function);
|
| +
|
| + FunctionResult result;
|
| + result.CopyFrom(result_); // Copy error code and location.
|
| + result.val = function;
|
| + return result;
|
| + }
|
| +
|
| + // Decodes a single function signature at {start}.
|
| + FunctionSig* DecodeFunctionSignature(const byte* start) {
|
| + pc_ = start;
|
| + FunctionSig* result = sig();
|
| + return ok() ? result : nullptr;
|
| + }
|
| +
|
| + private:
|
| + Zone* module_zone;
|
| + ModuleResult result_;
|
| + bool asm_js_;
|
| +
|
| + uint32_t off(const byte* ptr) { return static_cast<uint32_t>(ptr - start_); }
|
| +
|
| + // Decodes a single global entry inside a module starting at {pc_}.
|
| + void DecodeGlobalInModule(WasmGlobal* global) {
|
| + global->name_offset = string("global name");
|
| + global->type = mem_type();
|
| + global->offset = 0;
|
| + global->exported = u8("exported") != 0;
|
| + }
|
| +
|
| + // Decodes a single function entry inside a module starting at {pc_}.
|
| + void DecodeFunctionInModule(WasmModule* module, WasmFunction* function,
|
| + bool verify_body = true) {
|
| + byte decl_bits = u8("function decl");
|
| +
|
| + const byte* sigpos = pc_;
|
| + function->sig_index = u16("signature index");
|
| +
|
| + if (function->sig_index >= module->signatures->size()) {
|
| + return error(sigpos, "invalid signature index");
|
| + } else {
|
| + function->sig = module->signatures->at(function->sig_index);
|
| + }
|
| +
|
| + TRACE(" +%d <function attributes:%s%s%s%s%s>\n",
|
| + static_cast<int>(pc_ - start_),
|
| + decl_bits & kDeclFunctionName ? " name" : "",
|
| + decl_bits & kDeclFunctionImport ? " imported" : "",
|
| + decl_bits & kDeclFunctionLocals ? " locals" : "",
|
| + decl_bits & kDeclFunctionExport ? " exported" : "",
|
| + (decl_bits & kDeclFunctionImport) == 0 ? " body" : "");
|
| +
|
| + if (decl_bits & kDeclFunctionName) {
|
| + function->name_offset = string("function name");
|
| + }
|
| +
|
| + function->exported = decl_bits & kDeclFunctionExport;
|
| +
|
| + // Imported functions have no locals or body.
|
| + if (decl_bits & kDeclFunctionImport) {
|
| + function->external = true;
|
| + return;
|
| + }
|
| +
|
| + if (decl_bits & kDeclFunctionLocals) {
|
| + function->local_int32_count = u16("int32 count");
|
| + function->local_int64_count = u16("int64 count");
|
| + function->local_float32_count = u16("float32 count");
|
| + function->local_float64_count = u16("float64 count");
|
| + }
|
| +
|
| + uint16_t size = u16("body size");
|
| + if (ok()) {
|
| + if ((pc_ + size) > limit_) {
|
| + return error(pc_, limit_,
|
| + "expected %d bytes for function body, fell off end", size);
|
| + }
|
| + function->code_start_offset = static_cast<uint32_t>(pc_ - start_);
|
| + function->code_end_offset = function->code_start_offset + size;
|
| + TRACE(" +%d %-20s: (%d bytes)\n", static_cast<int>(pc_ - start_),
|
| + "function body", size);
|
| + pc_ += size;
|
| + }
|
| + }
|
| +
|
| + // Decodes a single data segment entry inside a module starting at {pc_}.
|
| + void DecodeDataSegmentInModule(WasmDataSegment* segment) {
|
| + segment->dest_addr =
|
| + u32("destination"); // TODO(titzer): check it's within the memory size.
|
| + segment->source_offset = offset("source offset");
|
| + segment->source_size =
|
| + u32("source size"); // TODO(titzer): check the size is reasonable.
|
| + segment->init = u8("init");
|
| + }
|
| +
|
| + // Verifies the body (code) of a given function.
|
| + void VerifyFunctionBody(uint32_t func_num, ModuleEnv* menv,
|
| + WasmFunction* function) {
|
| + if (FLAG_trace_wasm_decode_time) {
|
| + // TODO(titzer): clean me up a bit.
|
| + OFStream os(stdout);
|
| + os << "Verifying WASM function:";
|
| + if (function->name_offset > 0) {
|
| + os << menv->module->GetName(function->name_offset);
|
| + }
|
| + os << std::endl;
|
| + }
|
| + FunctionEnv fenv;
|
| + fenv.module = menv;
|
| + fenv.sig = function->sig;
|
| + fenv.local_int32_count = function->local_int32_count;
|
| + fenv.local_int64_count = function->local_int64_count;
|
| + fenv.local_float32_count = function->local_float32_count;
|
| + fenv.local_float64_count = function->local_float64_count;
|
| + fenv.SumLocals();
|
| +
|
| + TreeResult result =
|
| + VerifyWasmCode(&fenv, start_, start_ + function->code_start_offset,
|
| + start_ + function->code_end_offset);
|
| + if (result.failed()) {
|
| + // Wrap the error message from the function decoder.
|
| + std::ostringstream str;
|
| + str << "in function #" << func_num << ": ";
|
| + // TODO(titzer): add function name for the user?
|
| + str << result;
|
| + const char* raw = str.str().c_str();
|
| + size_t len = strlen(raw);
|
| + char* buffer = new char[len];
|
| + strncpy(buffer, raw, len);
|
| + buffer[len - 1] = 0;
|
| +
|
| + // Copy error code and location.
|
| + result_.CopyFrom(result);
|
| + result_.error_msg.Reset(buffer);
|
| + }
|
| + }
|
| +
|
| + // Reads a single 32-bit unsigned integer interpreted as an offset, checking
|
| + // the offset is within bounds and advances.
|
| + uint32_t offset(const char* name = nullptr) {
|
| + uint32_t offset = u32(name ? name : "offset");
|
| + if (offset > (limit_ - start_)) {
|
| + error(pc_ - sizeof(uint32_t), "offset out of bounds of module");
|
| + }
|
| + return offset;
|
| + }
|
| +
|
| + // Reads a single 32-bit unsigned integer interpreted as an offset into the
|
| + // data and validating the string there and advances.
|
| + uint32_t string(const char* name = nullptr) {
|
| + return offset(name ? name : "string"); // TODO(titzer): validate string
|
| + }
|
| +
|
| + // Reads a single 8-bit integer, interpreting it as a local type.
|
| + LocalType local_type() {
|
| + byte val = u8("local type");
|
| + LocalTypeCode t = static_cast<LocalTypeCode>(val);
|
| + switch (t) {
|
| + case kLocalVoid:
|
| + return kAstStmt;
|
| + case kLocalI32:
|
| + return kAstI32;
|
| + case kLocalI64:
|
| + return kAstI64;
|
| + case kLocalF32:
|
| + return kAstF32;
|
| + case kLocalF64:
|
| + return kAstF64;
|
| + default:
|
| + error(pc_ - 1, "invalid local type");
|
| + return kAstStmt;
|
| + }
|
| + }
|
| +
|
| + // Reads a single 8-bit integer, interpreting it as a memory type.
|
| + MachineType mem_type() {
|
| + byte val = u8("memory type");
|
| + MemTypeCode t = static_cast<MemTypeCode>(val);
|
| + switch (t) {
|
| + case kMemI8:
|
| + return MachineType::Int8();
|
| + case kMemU8:
|
| + return MachineType::Uint8();
|
| + case kMemI16:
|
| + return MachineType::Int16();
|
| + case kMemU16:
|
| + return MachineType::Uint16();
|
| + case kMemI32:
|
| + return MachineType::Int32();
|
| + case kMemU32:
|
| + return MachineType::Uint32();
|
| + case kMemI64:
|
| + return MachineType::Int64();
|
| + case kMemU64:
|
| + return MachineType::Uint64();
|
| + case kMemF32:
|
| + return MachineType::Float32();
|
| + case kMemF64:
|
| + return MachineType::Float64();
|
| + default:
|
| + error(pc_ - 1, "invalid memory type");
|
| + return MachineType::None();
|
| + }
|
| + }
|
| +
|
| + // Parses an inline function signature.
|
| + FunctionSig* sig() {
|
| + byte count = u8("param count");
|
| + LocalType ret = local_type();
|
| + FunctionSig::Builder builder(module_zone, ret == kAstStmt ? 0 : 1, count);
|
| + if (ret != kAstStmt) builder.AddReturn(ret);
|
| +
|
| + for (int i = 0; i < count; i++) {
|
| + LocalType param = local_type();
|
| + if (param == kAstStmt) error(pc_ - 1, "invalid void parameter type");
|
| + builder.AddParam(param);
|
| + }
|
| + return builder.Build();
|
| + }
|
| +};
|
| +
|
| +
|
| +// Helpers for nice error messages.
|
| +class ModuleError : public ModuleResult {
|
| + public:
|
| + explicit ModuleError(const char* msg) {
|
| + error_code = kError;
|
| + size_t len = strlen(msg) + 1;
|
| + char* result = new char[len];
|
| + strncpy(result, msg, len);
|
| + result[len - 1] = 0;
|
| + error_msg.Reset(result);
|
| + }
|
| +};
|
| +
|
| +
|
| +// Helpers for nice error messages.
|
| +class FunctionError : public FunctionResult {
|
| + public:
|
| + explicit FunctionError(const char* msg) {
|
| + error_code = kError;
|
| + size_t len = strlen(msg) + 1;
|
| + char* result = new char[len];
|
| + strncpy(result, msg, len);
|
| + result[len - 1] = 0;
|
| + error_msg.Reset(result);
|
| + }
|
| +};
|
| +
|
| +
|
| +ModuleResult DecodeWasmModule(Isolate* isolate, Zone* zone,
|
| + const byte* module_start, const byte* module_end,
|
| + bool verify_functions, bool asm_js) {
|
| + size_t size = module_end - module_start;
|
| + if (module_start > module_end) return ModuleError("start > end");
|
| + if (size >= kMaxModuleSize) return ModuleError("size > maximum module size");
|
| + WasmModule* module = new WasmModule();
|
| + ModuleDecoder decoder(zone, module_start, module_end, asm_js);
|
| + return decoder.DecodeModule(module, verify_functions);
|
| +}
|
| +
|
| +
|
| +FunctionSig* DecodeWasmSignatureForTesting(Zone* zone, const byte* start,
|
| + const byte* end) {
|
| + ModuleDecoder decoder(zone, start, end, false);
|
| + return decoder.DecodeFunctionSignature(start);
|
| +}
|
| +
|
| +
|
| +FunctionResult DecodeWasmFunction(Isolate* isolate, Zone* zone,
|
| + ModuleEnv* module_env,
|
| + const byte* function_start,
|
| + const byte* function_end) {
|
| + size_t size = function_end - function_start;
|
| + if (function_start > function_end) return FunctionError("start > end");
|
| + if (size > kMaxFunctionSize)
|
| + return FunctionError("size > maximum function size");
|
| + WasmFunction* function = new WasmFunction();
|
| + ModuleDecoder decoder(zone, function_start, function_end, false);
|
| + return decoder.DecodeSingleFunction(module_env, function);
|
| +}
|
| +} // namespace wasm
|
| +} // namespace internal
|
| +} // namespace v8
|
|
|