| // Copyright 2022 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. |
| |
| #ifndef V8_WASM_MODULE_DECODER_IMPL_H_ |
| #define V8_WASM_MODULE_DECODER_IMPL_H_ |
| |
| #if !V8_ENABLE_WEBASSEMBLY |
| #error This header should only be included if WebAssembly is enabled. |
| #endif // !V8_ENABLE_WEBASSEMBLY |
| |
| #include "src/base/platform/wrappers.h" |
| #include "src/strings/unicode.h" |
| #include "src/utils/ostreams.h" |
| #include "src/wasm/canonical-types.h" |
| #include "src/wasm/constant-expression-interface.h" |
| #include "src/wasm/function-body-decoder-impl.h" |
| #include "src/wasm/module-decoder.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-subtyping.h" |
| #include "src/wasm/well-known-imports.h" |
| |
| namespace v8::internal::wasm { |
| |
| #define TRACE(...) \ |
| do { \ |
| if (v8_flags.trace_wasm_decoder) PrintF(__VA_ARGS__); \ |
| } while (false) |
| |
| constexpr char kNameString[] = "name"; |
| constexpr char kSourceMappingURLString[] = "sourceMappingURL"; |
| constexpr char kInstTraceString[] = "metadata.code.trace_inst"; |
| constexpr char kBranchHintsString[] = "metadata.code.branch_hint"; |
| #if V8_CC_GNU |
| // TODO(miladfarca): remove once switched to using Clang. |
| __attribute__((used)) |
| #endif |
| constexpr char kCompilationPriorityString[] = |
| "metadata.code.compilation_priority"; |
| constexpr char kInstructionFrequenciesString[] = "metadata.code.instr_freq"; |
| constexpr char kCallTargetsString[] = "metadata.code.call_targets"; |
| constexpr char kDebugInfoString[] = ".debug_info"; |
| constexpr char kExternalDebugInfoString[] = "external_debug_info"; |
| constexpr char kBuildIdString[] = "build_id"; |
| // TODO(403372470): Rename to "descriptors" when finalized. |
| constexpr char kDescriptorsString[] = "experimental-descriptors"; |
| |
| inline const char* ExternalKindName(ImportExportKindCode kind) { |
| switch (kind) { |
| case kExternalFunction: |
| return "function"; |
| case kExternalTable: |
| return "table"; |
| case kExternalMemory: |
| return "memory"; |
| case kExternalGlobal: |
| return "global"; |
| case kExternalTag: |
| return "tag"; |
| } |
| return "unknown"; |
| } |
| |
| inline bool validate_utf8(Decoder* decoder, WireBytesRef string) { |
| return unibrow::Utf8::ValidateEncoding( |
| decoder->start() + decoder->GetBufferRelativeOffset(string.offset()), |
| string.length()); |
| } |
| |
| // Reads a length-prefixed string, checking that it is within bounds. Returns |
| // the offset of the string, and the length as an out parameter. |
| inline WireBytesRef consume_string(Decoder* decoder, |
| unibrow::Utf8Variant grammar, |
| const char* name, ITracer* tracer) { |
| if (tracer) { |
| tracer->Description(name); |
| tracer->Description(" "); |
| } |
| uint32_t length = decoder->consume_u32v("length", tracer); |
| if (tracer) { |
| tracer->Description(": "); |
| tracer->Description(length); |
| tracer->NextLine(); |
| } |
| uint32_t offset = decoder->pc_offset(); |
| const uint8_t* string_start = decoder->pc(); |
| // Consume bytes before validation to guarantee that the string is not oob. |
| if (length > 0) { |
| if (tracer) { |
| tracer->Bytes(decoder->pc(), length); |
| tracer->Description(name); |
| tracer->Description(": "); |
| tracer->Description(reinterpret_cast<const char*>(decoder->pc()), length); |
| tracer->NextLine(); |
| } |
| decoder->consume_bytes(length, name); |
| if (decoder->ok()) { |
| switch (grammar) { |
| case unibrow::Utf8Variant::kLossyUtf8: |
| break; |
| case unibrow::Utf8Variant::kUtf8: |
| if (!unibrow::Utf8::ValidateEncoding(string_start, length)) { |
| decoder->errorf(string_start, "%s: no valid UTF-8 string", name); |
| } |
| break; |
| case unibrow::Utf8Variant::kWtf8: |
| if (!unibrow::Wtf8::ValidateEncoding(string_start, length)) { |
| decoder->errorf(string_start, "%s: no valid WTF-8 string", name); |
| } |
| break; |
| case unibrow::Utf8Variant::kUtf8NoTrap: |
| UNREACHABLE(); |
| } |
| } |
| } |
| return {offset, decoder->failed() ? 0 : length}; |
| } |
| |
| inline WireBytesRef consume_string(Decoder* decoder, |
| unibrow::Utf8Variant grammar, |
| const char* name) { |
| return consume_string(decoder, grammar, name, ITracer::NoTrace); |
| } |
| |
| inline WireBytesRef consume_utf8_string(Decoder* decoder, const char* name, |
| ITracer* tracer) { |
| return consume_string(decoder, unibrow::Utf8Variant::kUtf8, name, tracer); |
| } |
| |
| inline SectionCode IdentifyUnknownSectionInternal(Decoder* decoder, |
| ITracer* tracer) { |
| WireBytesRef string = consume_utf8_string(decoder, "section name", tracer); |
| if (decoder->failed()) { |
| return kUnknownSectionCode; |
| } |
| const uint8_t* section_name_start = |
| decoder->start() + decoder->GetBufferRelativeOffset(string.offset()); |
| |
| TRACE(" +%d section name : \"%.*s\"\n", |
| static_cast<int>(section_name_start - decoder->start()), |
| string.length() < 20 ? string.length() : 20, section_name_start); |
| |
| using SpecialSectionPair = std::pair<base::Vector<const char>, SectionCode>; |
| static constexpr SpecialSectionPair kSpecialSections[]{ |
| {base::StaticCharVector(kNameString), kNameSectionCode}, |
| {base::StaticCharVector(kSourceMappingURLString), |
| kSourceMappingURLSectionCode}, |
| {base::StaticCharVector(kInstTraceString), kInstTraceSectionCode}, |
| {base::StaticCharVector(kBranchHintsString), kBranchHintsSectionCode}, |
| {base::StaticCharVector(kCompilationPriorityString), |
| kCompilationPrioritySectionCode}, |
| {base::StaticCharVector(kInstructionFrequenciesString), |
| kInstFrequenciesSectionCode}, |
| {base::StaticCharVector(kCallTargetsString), kCallTargetsSectionCode}, |
| {base::StaticCharVector(kDebugInfoString), kDebugInfoSectionCode}, |
| {base::StaticCharVector(kExternalDebugInfoString), |
| kExternalDebugInfoSectionCode}, |
| {base::StaticCharVector(kBuildIdString), kBuildIdSectionCode}, |
| {base::StaticCharVector(kDescriptorsString), kDescriptorsSectionCode}}; |
| |
| auto name_vec = base::Vector<const char>::cast( |
| base::VectorOf(section_name_start, string.length())); |
| for (auto& special_section : kSpecialSections) { |
| if (name_vec == special_section.first) return special_section.second; |
| } |
| |
| return kUnknownSectionCode; |
| } |
| |
| // An iterator over the sections in a wasm binary module. |
| // Automatically skips all unknown sections. |
| class WasmSectionIterator { |
| public: |
| explicit WasmSectionIterator(Decoder* decoder, ITracer* tracer) |
| : decoder_(decoder), |
| tracer_(tracer), |
| section_code_(kUnknownSectionCode), |
| section_start_(decoder->pc()), |
| section_end_(decoder->pc()) { |
| next(); |
| } |
| |
| bool more() const { return decoder_->ok() && decoder_->more(); } |
| |
| SectionCode section_code() const { return section_code_; } |
| |
| const uint8_t* section_start() const { return section_start_; } |
| |
| uint32_t section_length() const { |
| return static_cast<uint32_t>(section_end_ - section_start_); |
| } |
| |
| base::Vector<const uint8_t> payload() const { |
| return {payload_start_, payload_length()}; |
| } |
| |
| const uint8_t* payload_start() const { return payload_start_; } |
| |
| uint32_t payload_length() const { |
| return static_cast<uint32_t>(section_end_ - payload_start_); |
| } |
| |
| const uint8_t* section_end() const { return section_end_; } |
| |
| // Advances to the next section, checking that decoding the current section |
| // stopped at {section_end_}. |
| void advance(bool move_to_section_end = false) { |
| if (move_to_section_end && decoder_->pc() < section_end_) { |
| decoder_->consume_bytes( |
| static_cast<uint32_t>(section_end_ - decoder_->pc())); |
| } |
| 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_)); |
| } |
| next(); |
| } |
| |
| private: |
| Decoder* decoder_; |
| ITracer* tracer_; |
| SectionCode section_code_; |
| const uint8_t* section_start_; |
| const uint8_t* payload_start_; |
| const uint8_t* section_end_; |
| |
| // Reads the section code/name at the current position and sets up |
| // the embedder fields. |
| void next() { |
| if (!decoder_->more()) { |
| section_code_ = kUnknownSectionCode; |
| return; |
| } |
| section_start_ = decoder_->pc(); |
| // Empty line before next section. |
| if (tracer_) tracer_->NextLine(); |
| uint8_t section_code = decoder_->consume_u8("section kind", tracer_); |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description(SectionName(static_cast<SectionCode>(section_code))); |
| tracer_->NextLine(); |
| } |
| // Read and check the section size. |
| uint32_t section_length = decoder_->consume_u32v("section length", tracer_); |
| if (tracer_) { |
| tracer_->Description(section_length); |
| tracer_->NextLine(); |
| } |
| payload_start_ = decoder_->pc(); |
| section_end_ = payload_start_ + section_length; |
| if (section_length > decoder_->available_bytes()) { |
| decoder_->errorf( |
| section_start_, |
| "section (code %u, \"%s\") extends past end of the module " |
| "(length %u, remaining bytes %u)", |
| section_code, SectionName(static_cast<SectionCode>(section_code)), |
| section_length, decoder_->available_bytes()); |
| section_end_ = payload_start_; |
| } |
| |
| if (section_code == kUnknownSectionCode) { |
| // Check for known custom sections. |
| // To identify the unknown section we set the end of the decoder bytes to |
| // the end of the custom section, so that we do not read the section name |
| // beyond the end of the section. |
| const uint8_t* module_end = decoder_->end(); |
| decoder_->set_end(section_end_); |
| section_code = IdentifyUnknownSectionInternal(decoder_, tracer_); |
| if (decoder_->ok()) decoder_->set_end(module_end); |
| // As a side effect, the above function will forward the decoder to after |
| // the identifier string. |
| payload_start_ = decoder_->pc(); |
| } else if (!IsValidSectionCode(section_code)) { |
| decoder_->errorf(decoder_->pc(), "unknown section code #0x%02x", |
| section_code); |
| } |
| section_code_ = decoder_->failed() ? kUnknownSectionCode |
| : static_cast<SectionCode>(section_code); |
| |
| if (section_code_ == kUnknownSectionCode && section_end_ > decoder_->pc()) { |
| // Skip to the end of the unknown section. |
| uint32_t remaining = static_cast<uint32_t>(section_end_ - decoder_->pc()); |
| decoder_->consume_bytes(remaining, "section payload", tracer_); |
| } |
| } |
| }; |
| |
| inline void DumpModule(const base::Vector<const uint8_t> module_bytes, |
| bool ok) { |
| std::string path; |
| if (v8_flags.dump_wasm_module_path) { |
| path = v8_flags.dump_wasm_module_path; |
| if (path.size() && !base::OS::isDirectorySeparator(path[path.size() - 1])) { |
| path += base::OS::DirectorySeparator(); |
| } |
| } |
| // File are named `<hash>.{ok,failed}.wasm`. |
| // Limit the hash to 8 characters (32 bits). |
| uint32_t hash = static_cast<uint32_t>(GetWireBytesHash(module_bytes)); |
| base::EmbeddedVector<char, 32> buf; |
| SNPrintF(buf, "%08x.%s.wasm", hash, ok ? "ok" : "failed"); |
| path += buf.begin(); |
| size_t rv = 0; |
| if (FILE* file = base::OS::FOpen(path.c_str(), "wb")) { |
| rv = fwrite(module_bytes.begin(), module_bytes.length(), 1, file); |
| base::Fclose(file); |
| } |
| if (rv != 1) { |
| OFStream os(stderr); |
| os << "Error while dumping wasm file to " << path << std::endl; |
| } |
| } |
| |
| // The main logic for decoding the bytes of a module. |
| class ModuleDecoderImpl : public Decoder { |
| public: |
| ModuleDecoderImpl(WasmEnabledFeatures enabled_features, |
| base::Vector<const uint8_t> wire_bytes, ModuleOrigin origin, |
| WasmDetectedFeatures* detected_features, |
| ITracer* tracer = ITracer::NoTrace) |
| : Decoder(wire_bytes), |
| enabled_features_(enabled_features), |
| detected_features_(detected_features), |
| module_(std::make_shared<WasmModule>(origin)), |
| module_start_(wire_bytes.begin()), |
| module_end_(wire_bytes.end()), |
| tracer_(tracer) {} |
| |
| void onFirstError() override { |
| pc_ = end_; // On error, terminate section decoding loop. |
| } |
| |
| void DecodeModuleHeader(base::Vector<const uint8_t> bytes) { |
| if (failed()) return; |
| Reset(bytes); |
| |
| const uint8_t* pos = pc_; |
| uint32_t magic_word = consume_u32("wasm magic", tracer_); |
| if (tracer_) tracer_->NextLine(); |
| #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)); |
| } |
| |
| pos = pc_; |
| { |
| uint32_t magic_version = consume_u32("wasm version", tracer_); |
| if (tracer_) tracer_->NextLine(); |
| if (magic_version != kWasmVersion) { |
| errorf(pos, |
| "expected version %02x %02x %02x %02x, " |
| "found %02x %02x %02x %02x", |
| BYTES(kWasmVersion), BYTES(magic_version)); |
| } |
| } |
| #undef BYTES |
| } |
| |
| bool CheckSectionOrder(SectionCode section_code) { |
| // Check the order of ordered sections. |
| if (section_code >= kFirstSectionInModule && |
| section_code < kFirstUnorderedSection) { |
| if (section_code < next_ordered_section_) { |
| errorf(pc(), "unexpected section <%s>", SectionName(section_code)); |
| return false; |
| } |
| next_ordered_section_ = section_code + 1; |
| return true; |
| } |
| |
| // Ignore ordering problems in unknown / custom sections. Even allow them to |
| // appear multiple times. As optional sections we use them on a "best |
| // effort" basis. |
| if (section_code == kUnknownSectionCode) return true; |
| if (section_code > kLastKnownModuleSection) return true; |
| |
| // The rest is standardized unordered sections; they are checked more |
| // thoroughly.. |
| DCHECK_LE(kFirstUnorderedSection, section_code); |
| DCHECK_GE(kLastKnownModuleSection, section_code); |
| |
| // Check that unordered sections don't appear multiple times. |
| if (has_seen_unordered_section(section_code)) { |
| errorf(pc(), "Multiple %s sections not allowed", |
| SectionName(section_code)); |
| return false; |
| } |
| set_seen_unordered_section(section_code); |
| |
| // Define a helper to ensure that sections <= {before} appear before the |
| // current unordered section, and everything >= {after} appears after it. |
| auto check_order = [this, section_code](SectionCode before, |
| SectionCode after) -> bool { |
| DCHECK_LT(before, after); |
| if (next_ordered_section_ > after) { |
| errorf(pc(), "The %s section must appear before the %s section", |
| SectionName(section_code), SectionName(after)); |
| return false; |
| } |
| if (next_ordered_section_ <= before) next_ordered_section_ = before + 1; |
| return true; |
| }; |
| |
| // Now check the ordering constraints of specific unordered sections. |
| switch (section_code) { |
| case kDataCountSectionCode: |
| return check_order(kElementSectionCode, kCodeSectionCode); |
| case kTagSectionCode: |
| return check_order(kMemorySectionCode, kGlobalSectionCode); |
| case kStringRefSectionCode: |
| // TODO(12868): If there's a tag section, assert that we're after the |
| // tag section. |
| return check_order(kMemorySectionCode, kGlobalSectionCode); |
| case kInstTraceSectionCode: |
| // Custom section following code.metadata tool convention containing |
| // offsets specifying where trace marks should be emitted. |
| // Be lenient with placement of instruction trace section. All except |
| // first occurrence after function section and before code section are |
| // ignored. |
| return true; |
| default: |
| return true; |
| } |
| } |
| |
| void DecodeSection(SectionCode section_code, |
| base::Vector<const uint8_t> bytes, uint32_t offset) { |
| if (failed()) return; |
| Reset(bytes, offset); |
| TRACE("Section: %s\n", SectionName(section_code)); |
| TRACE("Decode Section %p - %p\n", bytes.begin(), bytes.end()); |
| |
| if (!CheckSectionOrder(section_code)) return; |
| |
| switch (section_code) { |
| case kUnknownSectionCode: |
| break; |
| case kTypeSectionCode: |
| DecodeTypeSection(); |
| break; |
| case kImportSectionCode: |
| DecodeImportSection(); |
| break; |
| case kFunctionSectionCode: |
| DecodeFunctionSection(); |
| break; |
| case kTableSectionCode: |
| DecodeTableSection(); |
| break; |
| case kMemorySectionCode: |
| DecodeMemorySection(); |
| break; |
| case kGlobalSectionCode: |
| DecodeGlobalSection(); |
| break; |
| case kExportSectionCode: |
| DecodeExportSection(); |
| break; |
| case kStartSectionCode: |
| DecodeStartSection(); |
| break; |
| case kCodeSectionCode: |
| DecodeCodeSection(); |
| break; |
| case kElementSectionCode: |
| DecodeElementSection(); |
| break; |
| case kDataSectionCode: |
| DecodeDataSection(); |
| break; |
| case kNameSectionCode: |
| DecodeNameSection(); |
| break; |
| case kSourceMappingURLSectionCode: |
| DecodeSourceMappingURLSection(); |
| break; |
| case kDebugInfoSectionCode: |
| module_->debug_symbols[WasmDebugSymbols::Type::EmbeddedDWARF] = { |
| WasmDebugSymbols::Type::EmbeddedDWARF, {}}; |
| consume_bytes(static_cast<uint32_t>(end_ - start_), ".debug_info"); |
| break; |
| case kExternalDebugInfoSectionCode: |
| DecodeExternalDebugInfoSection(); |
| break; |
| case kBuildIdSectionCode: |
| DecodeBuildIdSection(); |
| break; |
| case kInstTraceSectionCode: |
| if (enabled_features_.has_instruction_tracing()) { |
| DecodeInstTraceSection(); |
| } else { |
| // Ignore this section when feature is disabled. It is an optional |
| // custom section anyways. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| break; |
| case kBranchHintsSectionCode: |
| if (enabled_features_.has_branch_hinting()) { |
| DecodeBranchHintsSection(); |
| } else { |
| // Ignore this section when feature was disabled. It is an optional |
| // custom section anyways. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| break; |
| case kCompilationPrioritySectionCode: |
| if (enabled_features_.has_compilation_hints()) { |
| DecodeCompilationPrioritySection(); |
| } else { |
| // Ignore this section when feature was disabled. It is an optional |
| // custom section anyways. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| break; |
| case kInstFrequenciesSectionCode: |
| if (enabled_features_.has_compilation_hints()) { |
| DecodeInstructionFrequenciesSection(); |
| } else { |
| // Ignore this section when feature was disabled. It is an optional |
| // custom section anyways. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| break; |
| case kCallTargetsSectionCode: |
| if (enabled_features_.has_compilation_hints()) { |
| DecodeCallTargetsSection(); |
| } else { |
| // Ignore this section when feature was disabled. It is an optional |
| // custom section anyways. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| break; |
| case kDescriptorsSectionCode: |
| DecodeDescriptorsSection(); |
| break; |
| case kDataCountSectionCode: |
| DecodeDataCountSection(); |
| break; |
| case kTagSectionCode: |
| DecodeTagSection(); |
| break; |
| case kStringRefSectionCode: |
| if (enabled_features_.has_stringref()) { |
| DecodeStringRefSection(); |
| } else { |
| errorf(pc(), |
| "unexpected section <%s> (enable with " |
| "--experimental-wasm-stringref)", |
| SectionName(section_code)); |
| } |
| break; |
| default: |
| errorf(pc(), "unexpected section <%s>", 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())); |
| } |
| } |
| |
| static constexpr const char* TypeKindName(uint8_t kind) { |
| switch (kind) { |
| // clang-format off |
| case kWasmFunctionTypeCode: return "func"; |
| case kWasmStructTypeCode: return "struct"; |
| case kWasmArrayTypeCode: return "array"; |
| case kWasmContTypeCode: return "cont"; |
| default: return "unknown"; |
| // clang-format on |
| } |
| } |
| |
| TypeDefinition consume_base_type_definition(bool is_descriptor, |
| bool is_shared) { |
| const bool is_final = true; |
| uint8_t kind = consume_u8(" kind", tracer_); |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description(TypeKindName(kind)); |
| } |
| switch (kind) { |
| case kWasmFunctionTypeCode: { |
| const FunctionSig* sig = consume_sig(&module_->signature_zone); |
| if (sig == nullptr) { |
| CHECK(!ok()); |
| return {}; |
| } |
| |
| return {sig, kNoSuperType, is_final, is_shared}; |
| } |
| case kWasmStructTypeCode: { |
| module_->is_wasm_gc = true; |
| const StructType* type = |
| consume_struct(&module_->signature_zone, is_descriptor, is_shared); |
| if (type == nullptr) { |
| CHECK(!ok()); |
| return {}; |
| } |
| return {type, kNoSuperType, is_final, is_shared}; |
| } |
| case kWasmArrayTypeCode: { |
| module_->is_wasm_gc = true; |
| const ArrayType* type = consume_array(&module_->signature_zone); |
| if (type == nullptr) { |
| CHECK(!ok()); |
| return {}; |
| } |
| return {type, kNoSuperType, is_final, is_shared}; |
| } |
| case kWasmContTypeCode: { |
| if (!enabled_features_.has_wasmfx()) { |
| error(pc() - 1, |
| "core stack switching not enabled (enable with " |
| "--experimental-wasm-wasmfx)"); |
| return {}; |
| } |
| |
| const uint8_t* pos = pc(); |
| HeapType hp = consume_heap_type(); |
| |
| if (!hp.is_index()) { |
| error(pos, "cont type must refer to a signature index"); |
| return {}; |
| } |
| |
| ContType* type = module_->signature_zone.New<ContType>(hp.ref_index()); |
| return {type, kNoSuperType, is_final, is_shared}; |
| } |
| default: |
| if (tracer_) tracer_->NextLine(); |
| errorf(pc() - 1, "unknown type form: %d", kind); |
| return {}; |
| } |
| } |
| |
| TypeDefinition consume_described_type(bool is_descriptor, bool is_shared) { |
| uint8_t kind = read_u8<Decoder::FullValidationTag>(pc(), "type kind"); |
| if (kind == kWasmDescriptorCode) { |
| if (!enabled_features_.has_custom_descriptors()) { |
| error(pc(), |
| "descriptor types need --experimental-wasm-custom-descriptors"); |
| return {}; |
| } |
| detected_features_->add_custom_descriptors(); |
| consume_bytes(1, "", tracer_); |
| const uint8_t* pos = pc(); |
| uint32_t descriptor = consume_u32v(" descriptor", tracer_); |
| if (descriptor >= module_->types.size()) { |
| errorf(pos, "descriptor type index %u is out of bounds", descriptor); |
| return {}; |
| } |
| if (tracer_) tracer_->Description(descriptor); |
| if (tracer_) tracer_->NextLine(); |
| TypeDefinition type = |
| consume_base_type_definition(is_descriptor, is_shared); |
| if (type.kind != TypeDefinition::kStruct) { |
| error(pos - 1, "'descriptor' may only be used with structs"); |
| return {}; |
| } |
| type.descriptor = ModuleTypeIndex{descriptor}; |
| return type; |
| } else { |
| return consume_base_type_definition(is_descriptor, is_shared); |
| } |
| } |
| |
| TypeDefinition consume_describing_type(size_t current_type_index, |
| bool is_shared) { |
| uint8_t kind = read_u8<Decoder::FullValidationTag>(pc(), "type kind"); |
| if (kind == kWasmDescribesCode) { |
| if (!enabled_features_.has_custom_descriptors()) { |
| error(pc(), |
| "descriptor types need --experimental-wasm-custom-descriptors"); |
| return {}; |
| } |
| detected_features_->add_custom_descriptors(); |
| consume_bytes(1, "", tracer_); |
| const uint8_t* pos = pc(); |
| uint32_t describes = consume_u32v(" describes", tracer_); |
| if (describes >= current_type_index) { |
| error(pos, "types can only describe previously-declared types"); |
| return {}; |
| } |
| if (tracer_) tracer_->Description(describes); |
| if (tracer_) tracer_->NextLine(); |
| TypeDefinition type = consume_described_type(true, is_shared); |
| if (type.kind != TypeDefinition::kStruct) { |
| error(pos - 1, "'describes' may only be used with structs"); |
| return {}; |
| } |
| type.describes = ModuleTypeIndex{describes}; |
| return type; |
| } else { |
| return consume_described_type(false, is_shared); |
| } |
| } |
| |
| TypeDefinition consume_shared_type(size_t current_type_index) { |
| uint8_t kind = read_u8<Decoder::FullValidationTag>(pc(), "type kind"); |
| if (kind == kSharedFlagCode) { |
| if (!enabled_features_.has_shared()) { |
| errorf(pc() - 1, |
| "unknown type form: %d, enable with --experimental-wasm-shared", |
| kind); |
| return {}; |
| } |
| module_->has_shared_part = true; |
| consume_bytes(1, " shared", tracer_); |
| TypeDefinition type = consume_describing_type(current_type_index, true); |
| DCHECK(type.is_shared); |
| if (type.kind == TypeDefinition::kFunction || |
| type.kind == TypeDefinition::kCont) { |
| // TODO(42204563): Support shared functions/continuations. |
| error(pc() - 1, "shared functions/continuations are not supported yet"); |
| return {}; |
| } |
| return type; |
| } else { |
| return consume_describing_type(current_type_index, false); |
| } |
| } |
| |
| // {current_type_index} is the index of the type that's being decoded. |
| // Any supertype must have a lower index. |
| TypeDefinition consume_subtype_definition(size_t current_type_index) { |
| uint8_t kind = read_u8<Decoder::FullValidationTag>(pc(), "type kind"); |
| if (kind == kWasmSubtypeCode || kind == kWasmSubtypeFinalCode) { |
| module_->is_wasm_gc = true; |
| bool is_final = kind == kWasmSubtypeFinalCode; |
| consume_bytes(1, is_final ? " subtype final, " : " subtype extensible, ", |
| tracer_); |
| constexpr uint32_t kMaximumSupertypes = 1; |
| uint32_t supertype_count = |
| consume_count("supertype count", kMaximumSupertypes); |
| uint32_t supertype = kNoSuperType.index; |
| if (supertype_count == 1) { |
| supertype = consume_u32v("supertype", tracer_); |
| if (supertype >= current_type_index) { |
| errorf("type %u: invalid supertype %u", current_type_index, |
| supertype); |
| return {}; |
| } |
| if (tracer_) { |
| tracer_->Description(supertype); |
| tracer_->NextLine(); |
| } |
| } |
| TypeDefinition type = consume_shared_type(current_type_index); |
| type.supertype = ModuleTypeIndex{supertype}; |
| type.is_final = is_final; |
| return type; |
| } else { |
| return consume_shared_type(current_type_index); |
| } |
| } |
| |
| void DecodeTypeSection() { |
| TypeCanonicalizer* type_canon = GetTypeCanonicalizer(); |
| uint32_t types_count = consume_count("types count", kV8MaxWasmTypes); |
| |
| for (uint32_t i = 0; ok() && i < types_count; ++i) { |
| TRACE("DecodeType[%d] module+%d\n", i, static_cast<int>(pc_ - start_)); |
| uint8_t kind = read_u8<Decoder::FullValidationTag>(pc(), "type kind"); |
| size_t initial_size = module_->types.size(); |
| uint32_t group_size = 1; |
| if (kind == kWasmRecursiveTypeGroupCode) { |
| module_->is_wasm_gc = true; |
| uint32_t rec_group_offset = pc_offset(); |
| consume_bytes(1, "rec. group definition", tracer_); |
| if (tracer_) tracer_->NextLine(); |
| group_size = consume_count("recursive group size", kV8MaxWasmTypes); |
| if (tracer_) tracer_->RecGroupOffset(rec_group_offset, group_size); |
| } |
| if (initial_size + group_size > kV8MaxWasmTypes) { |
| errorf(pc(), "Type definition count exceeds maximum %zu", |
| kV8MaxWasmTypes); |
| return; |
| } |
| // We need to resize types before decoding the type definitions in this |
| // group, so that the correct type size is visible to type definitions. |
| module_->types.resize(initial_size + group_size); |
| for (uint32_t j = 0; j < group_size; j++) { |
| if (tracer_) tracer_->TypeOffset(pc_offset()); |
| TypeDefinition type = consume_subtype_definition(initial_size + j); |
| if (failed()) return; |
| module_->types[initial_size + j] = type; |
| } |
| FinalizeRecgroup(group_size, type_canon); |
| if (kind == kWasmRecursiveTypeGroupCode && tracer_) { |
| tracer_->Description("end of rec. group"); |
| tracer_->NextLine(); |
| } |
| } |
| } |
| |
| void FinalizeRecgroup(uint32_t group_size, TypeCanonicalizer* type_canon) { |
| // Now that we have decoded the entire recgroup, check its validity, |
| // initialize additional data, and canonicalize it: |
| // - check supertype validity |
| // - propagate subtyping depths |
| // - validate is_shared bits and set up RefTypeKind fields |
| WasmModule* module = module_.get(); |
| uint32_t end_index = static_cast<uint32_t>(module->types.size()); |
| uint32_t start_index = end_index - group_size; |
| for (uint32_t i = start_index; ok() && i < end_index; ++i) { |
| TypeDefinition& type_def = module_->types[i]; |
| bool is_shared = type_def.is_shared; |
| switch (type_def.kind) { |
| case TypeDefinition::kFunction: { |
| base::Vector<const ValueType> all = type_def.function_sig->all(); |
| size_t count = all.size(); |
| ValueType* storage = const_cast<ValueType*>(all.begin()); |
| for (uint32_t j = 0; j < count; j++) { |
| value_type_reader::Populate(&storage[j], module); |
| ValueType type = storage[j]; |
| if (is_shared && !type.is_shared()) { |
| DCHECK(enabled_features_.has_shared()); |
| uint32_t retcount = |
| static_cast<uint32_t>(type_def.function_sig->return_count()); |
| const char* param_or_return = |
| j < retcount ? "return" : "parameter"; |
| uint32_t index = j < retcount ? j : j - retcount; |
| // {pc_} isn't very accurate, it's pointing at the end of the |
| // recgroup. So to ease debugging, we print the type's index. |
| errorf(pc_, |
| "Type %u: shared signature must have shared %s types, " |
| "actual type for %s %d is %s", |
| i, param_or_return, param_or_return, index, |
| type.name().c_str()); |
| return; |
| } |
| } |
| break; |
| } |
| case TypeDefinition::kStruct: { |
| size_t count = type_def.struct_type->field_count(); |
| ValueType* storage = |
| const_cast<ValueType*>(type_def.struct_type->fields().begin()); |
| for (uint32_t j = 0; j < count; j++) { |
| value_type_reader::Populate(&storage[j], module); |
| ValueType type = storage[j]; |
| if (is_shared && !type.is_shared()) { |
| errorf(pc_, |
| "Type %u: shared struct must have shared field types, " |
| "actual type for field %d is %s", |
| i, j, type.name().c_str()); |
| return; |
| } |
| } |
| if (type_def.descriptor.valid()) { |
| const TypeDefinition& descriptor = |
| module->type(type_def.descriptor); |
| if (descriptor.describes.index != i) { |
| uint32_t d = type_def.descriptor.index; |
| errorf(pc_, |
| "Type %u has descriptor %u but %u doesn't describe %u", i, |
| d, d, i); |
| return; |
| } |
| if (descriptor.is_shared != type_def.is_shared) { |
| errorf(pc_, |
| "Type %u and its descriptor %u must have same sharedness", |
| i, type_def.descriptor.index); |
| return; |
| } |
| } |
| if (type_def.describes.valid()) { |
| if (module->type(type_def.describes).descriptor.index != i) { |
| uint32_t d = type_def.describes.index; |
| errorf(pc_, |
| "Type %u describes %u but %u isn't a descriptor for %u", i, |
| d, d, i); |
| return; |
| } |
| } |
| break; |
| } |
| case TypeDefinition::kArray: { |
| value_type_reader::Populate( |
| const_cast<ArrayType*>(type_def.array_type) |
| ->element_type_writable_ptr(), |
| module); |
| ValueType type = type_def.array_type->element_type(); |
| if (is_shared && !type.is_shared()) { |
| errorf(pc_, |
| "Type %u: shared array must have shared element type, " |
| "actual element type is %s", |
| i, type.name().c_str()); |
| return; |
| } |
| break; |
| } |
| case TypeDefinition::kCont: { |
| ModuleTypeIndex contfun_typeid = |
| type_def.cont_type->contfun_typeindex(); |
| const TypeDefinition contfun_type = |
| module_->types[contfun_typeid.index]; |
| if (contfun_type.kind != TypeDefinition::kFunction) { |
| errorf(pc_, |
| "Type %u: cont type must refer to a signature index, " |
| "actual type is %s", |
| i, module_->heap_type(contfun_typeid).name().c_str()); |
| return; |
| } |
| if (is_shared && !contfun_type.is_shared) { |
| errorf(pc_, |
| "Type %u: shared cont type must refer to a shared signature," |
| " actual type is %s", |
| module_->heap_type(contfun_typeid).name().c_str()); |
| return; |
| } |
| break; |
| } |
| } |
| } |
| module->isorecursive_canonical_type_ids.resize(end_index); |
| type_canon->AddRecursiveGroup(module, group_size); |
| for (uint32_t i = start_index; ok() && i < end_index; ++i) { |
| TypeDefinition& type_def = module_->types[i]; |
| ModuleTypeIndex explicit_super = type_def.supertype; |
| if (!explicit_super.valid()) continue; // No supertype. |
| DCHECK_LT(explicit_super.index, i); // Checked during decoding. |
| uint32_t depth = module->type(explicit_super).subtyping_depth + 1; |
| type_def.subtyping_depth = depth; |
| DCHECK_GE(depth, 0); |
| if (depth > kV8MaxRttSubtypingDepth) { |
| errorf("type %u: subtyping depth is greater than allowed", i); |
| return; |
| } |
| // This check is technically redundant; we include for the improved error |
| // message. |
| if (module->type(explicit_super).is_final) { |
| errorf("type %u extends final type %u", i, explicit_super.index); |
| return; |
| } |
| if (!ValidSubtypeDefinition(ModuleTypeIndex{i}, explicit_super, module)) { |
| errorf("type %u has invalid explicit supertype %u", i, |
| explicit_super.index); |
| return; |
| } |
| } |
| } |
| |
| void DecodeImportSection() { |
| uint32_t import_table_count = |
| consume_count("imports count", kV8MaxWasmImports); |
| module_->import_table.reserve(import_table_count); |
| for (uint32_t i = 0; ok() && i < import_table_count; ++i) { |
| TRACE("DecodeImportTable[%d] module+%d\n", i, |
| static_cast<int>(pc_ - start_)); |
| if (tracer_) tracer_->ImportOffset(pc_offset()); |
| |
| const uint8_t* pos = pc_; |
| WireBytesRef module_name = |
| consume_utf8_string(this, "module name", tracer_); |
| WireBytesRef field_name = |
| consume_utf8_string(this, "field name", tracer_); |
| ImportExportKindCode kind = |
| static_cast<ImportExportKindCode>(consume_u8("kind", tracer_)); |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description(ExternalKindName(kind)); |
| } |
| module_->import_table.push_back(WasmImport{ |
| .module_name = module_name, .field_name = field_name, .kind = kind}); |
| WasmImport* import = &module_->import_table.back(); |
| switch (kind) { |
| case kExternalFunction: { |
| // ===== Imported function =========================================== |
| import->index = static_cast<uint32_t>(module_->functions.size()); |
| module_->num_imported_functions++; |
| module_->functions.push_back(WasmFunction{ |
| .func_index = import->index, |
| .imported = true, |
| }); |
| WasmFunction* function = &module_->functions.back(); |
| function->sig_index = |
| consume_sig_index(module_.get(), &function->sig); |
| break; |
| } |
| case kExternalTable: { |
| // ===== Imported table ============================================== |
| import->index = static_cast<uint32_t>(module_->tables.size()); |
| const uint8_t* type_position = pc(); |
| ValueType type = consume_value_type(module_.get()); |
| if (!type.is_object_reference()) { |
| errorf(type_position, "Invalid table type %s", type.name().c_str()); |
| break; |
| } |
| module_->num_imported_tables++; |
| module_->tables.push_back(WasmTable{ |
| .type = type, |
| .imported = true, |
| }); |
| WasmTable* table = &module_->tables.back(); |
| consume_table_flags(table); |
| DCHECK_IMPLIES(table->shared, |
| enabled_features_.has_shared() || !ok()); |
| if (table->shared && enabled_features_.has_shared()) { |
| module_->has_shared_part = true; |
| if (!type.is_shared()) { |
| errorf(type_position, |
| "Shared table %i must have shared element type, actual " |
| "type %s", |
| i, type.name().c_str()); |
| break; |
| } |
| } |
| // Note that we should not throw an error if the declared maximum size |
| // is oob. We will instead fail when growing at runtime. |
| uint64_t kNoMaximum = kMaxUInt64; |
| consume_resizable_limits( |
| "table", "elements", wasm::max_table_size(), &table->initial_size, |
| table->has_maximum_size, kNoMaximum, &table->maximum_size, |
| table->is_table64() ? k64BitLimits : k32BitLimits); |
| break; |
| } |
| case kExternalMemory: { |
| // ===== Imported memory ============================================= |
| static_assert(kV8MaxWasmMemories <= kMaxUInt32); |
| if (module_->memories.size() >= kV8MaxWasmMemories - 1) { |
| errorf("At most %u imported memories are supported", |
| kV8MaxWasmMemories); |
| break; |
| } |
| uint32_t mem_index = static_cast<uint32_t>(module_->memories.size()); |
| import->index = mem_index; |
| module_->memories.emplace_back(); |
| WasmMemory* external_memory = &module_->memories.back(); |
| external_memory->imported = true; |
| external_memory->index = mem_index; |
| |
| consume_memory_flags(external_memory); |
| uint32_t max_pages = external_memory->is_memory64() |
| ? kSpecMaxMemory64Pages |
| : kSpecMaxMemory32Pages; |
| consume_resizable_limits( |
| "memory", "pages", max_pages, &external_memory->initial_pages, |
| external_memory->has_maximum_pages, max_pages, |
| &external_memory->maximum_pages, |
| external_memory->is_memory64() ? k64BitLimits : k32BitLimits); |
| break; |
| } |
| case kExternalGlobal: { |
| // ===== Imported global ============================================= |
| import->index = static_cast<uint32_t>(module_->globals.size()); |
| ValueType type = consume_value_type(module_.get()); |
| auto [mutability, shared] = consume_global_flags(); |
| if (V8_UNLIKELY(failed())) break; |
| if (V8_UNLIKELY(shared && !type.is_shared())) { |
| error("shared imported global must have shared type"); |
| break; |
| } |
| module_->globals.push_back( |
| WasmGlobal{.type = type, |
| .mutability = mutability, |
| .index = 0, // set later in CalculateGlobalOffsets |
| .shared = shared, |
| .imported = true}); |
| module_->num_imported_globals++; |
| DCHECK_EQ(module_->globals.size(), module_->num_imported_globals); |
| if (shared) module_->has_shared_part = true; |
| if (mutability) module_->num_imported_mutable_globals++; |
| if (tracer_) tracer_->NextLine(); |
| break; |
| } |
| case kExternalTag: { |
| // ===== Imported tag ================================================ |
| import->index = static_cast<uint32_t>(module_->tags.size()); |
| module_->num_imported_tags++; |
| const WasmTagSig* tag_sig = nullptr; |
| consume_exception_attribute(); // Attribute ignored for now. |
| ModuleTypeIndex sig_index = |
| consume_tag_sig_index(module_.get(), &tag_sig); |
| module_->tags.emplace_back(tag_sig, sig_index); |
| break; |
| } |
| default: |
| errorf(pos, "unknown import kind 0x%02x", kind); |
| break; |
| } |
| } |
| if (module_->memories.size() > 1) { |
| detected_features_->add_multi_memory(); |
| if (v8_flags.wasm_jitless) { |
| error("Multiple memories not supported in Wasm jitless mode"); |
| } |
| } |
| if (module_->num_imported_mutable_globals > 0) { |
| detected_features_->add_mutable_globals(); |
| } |
| UpdateComputedMemoryInformation(); |
| module_->type_feedback.well_known_imports.Initialize( |
| module_->num_imported_functions); |
| if (tracer_) tracer_->ImportsDone(module_.get()); |
| } |
| |
| void DecodeFunctionSection() { |
| uint32_t functions_count = |
| consume_count("functions count", v8_flags.max_wasm_functions); |
| DCHECK_EQ(module_->functions.size(), module_->num_imported_functions); |
| uint32_t total_function_count = |
| module_->num_imported_functions + functions_count; |
| module_->functions.resize(total_function_count); |
| module_->num_declared_functions = functions_count; |
| // Also initialize the {validated_functions} bitset here, now that we know |
| // the number of declared functions. |
| DCHECK_NULL(module_->validated_functions); |
| module_->validated_functions = |
| std::make_unique<std::atomic<uint8_t>[]>((functions_count + 7) / 8); |
| if (is_asmjs_module(module_.get())) { |
| // Mark all asm.js functions as valid by design (it's faster to do this |
| // here than to check this in {WasmModule::function_was_validated}). |
| std::fill_n(module_->validated_functions.get(), (functions_count + 7) / 8, |
| 0xff); |
| } |
| |
| for (uint32_t func_index = module_->num_imported_functions; |
| func_index < total_function_count; ++func_index) { |
| WasmFunction* function = &module_->functions[func_index]; |
| function->func_index = func_index; |
| if (tracer_) tracer_->FunctionName(func_index); |
| function->sig_index = consume_sig_index(module_.get(), &function->sig); |
| if (!ok()) return; |
| } |
| } |
| |
| void DecodeTableSection() { |
| static_assert(kV8MaxWasmTables <= kMaxUInt32); |
| uint32_t table_count = consume_count("table count", kV8MaxWasmTables); |
| |
| for (uint32_t i = 0; ok() && i < table_count; i++) { |
| if (tracer_) tracer_->TableOffset(pc_offset()); |
| module_->tables.emplace_back(); |
| WasmTable* table = &module_->tables.back(); |
| const uint8_t* type_position = pc(); |
| |
| bool has_initializer = false; |
| if (read_u8<Decoder::FullValidationTag>( |
| pc(), "table-with-initializer byte") == 0x40) { |
| consume_bytes(1, "with-initializer ", tracer_); |
| has_initializer = true; |
| type_position++; |
| uint8_t reserved = consume_u8("reserved-byte", tracer_); |
| if (reserved != 0) { |
| error(type_position, "Reserved byte must be 0x00"); |
| break; |
| } |
| type_position++; |
| } |
| |
| ValueType table_type = consume_value_type(module_.get()); |
| if (!table_type.is_object_reference()) { |
| error(type_position, "Only reference types can be used as table types"); |
| break; |
| } |
| if (!has_initializer && !table_type.is_defaultable()) { |
| errorf(type_position, |
| "Table of non-defaultable table %s needs initial value", |
| table_type.name().c_str()); |
| break; |
| } |
| table->type = table_type; |
| |
| consume_table_flags(table); |
| DCHECK_IMPLIES(table->shared, enabled_features_.has_shared() || !ok()); |
| if (table->shared && enabled_features_.has_shared()) { |
| module_->has_shared_part = true; |
| if (!table_type.is_shared()) { |
| errorf( |
| type_position, |
| "Shared table %i must have shared element type, actual type %s", |
| i + module_->num_imported_tables, table_type.name().c_str()); |
| break; |
| } |
| } |
| // Note that we should not throw an error if the declared maximum size is |
| // oob. We will instead fail when growing at runtime. |
| uint64_t kNoMaximum = kMaxUInt64; |
| consume_resizable_limits( |
| "table", "elements", wasm::max_table_size(), &table->initial_size, |
| table->has_maximum_size, kNoMaximum, &table->maximum_size, |
| table->is_table64() ? k64BitLimits : k32BitLimits); |
| |
| if (has_initializer) { |
| table->initial_value = |
| consume_init_expr(module_.get(), table_type, table->shared); |
| } |
| } |
| } |
| |
| void DecodeMemorySection() { |
| const uint8_t* mem_count_pc = pc(); |
| static_assert(kV8MaxWasmMemories <= kMaxUInt32); |
| // Use {kV8MaxWasmMemories} here, but only allow for >1 memory if |
| // multi-memory is enabled (checked below). This allows for better error |
| // messages. |
| uint32_t memory_count = consume_count("memory count", kV8MaxWasmMemories); |
| size_t imported_memories = module_->memories.size(); |
| DCHECK_GE(kV8MaxWasmMemories, imported_memories); |
| if (memory_count > kV8MaxWasmMemories - imported_memories) { |
| errorf(mem_count_pc, |
| "Exceeding maximum number of memories (%u; declared %u, " |
| "imported %zu)", |
| kV8MaxWasmMemories, memory_count, imported_memories); |
| } |
| module_->memories.resize(imported_memories + memory_count); |
| |
| for (uint32_t i = 0; ok() && i < memory_count; i++) { |
| WasmMemory* memory = module_->memories.data() + imported_memories + i; |
| memory->index = static_cast<uint32_t>(imported_memories + i); |
| if (tracer_) tracer_->MemoryOffset(pc_offset()); |
| consume_memory_flags(memory); |
| uint32_t max_pages = |
| memory->is_memory64() ? kSpecMaxMemory64Pages : kSpecMaxMemory32Pages; |
| consume_resizable_limits( |
| "memory", "pages", max_pages, &memory->initial_pages, |
| memory->has_maximum_pages, max_pages, &memory->maximum_pages, |
| memory->is_memory64() ? k64BitLimits : k32BitLimits); |
| } |
| if (module_->memories.size() > 1) { |
| detected_features_->add_multi_memory(); |
| if (v8_flags.wasm_jitless) { |
| error("Multiple memories not supported in Wasm jitless mode"); |
| } |
| } |
| UpdateComputedMemoryInformation(); |
| } |
| |
| void UpdateComputedMemoryInformation() { |
| for (WasmMemory& memory : module_->memories) { |
| UpdateComputedInformation(&memory, module_->origin); |
| } |
| } |
| |
| void DecodeGlobalSection() { |
| uint32_t globals_count = consume_count("globals count", kV8MaxWasmGlobals); |
| uint32_t imported_globals = static_cast<uint32_t>(module_->globals.size()); |
| // It is important to not resize the globals vector from the beginning, |
| // because we use its current size when decoding the initializer. |
| module_->globals.reserve(imported_globals + globals_count); |
| for (uint32_t i = 0; ok() && i < globals_count; ++i) { |
| TRACE("DecodeGlobal[%d] module+%d\n", i, static_cast<int>(pc_ - start_)); |
| if (tracer_) tracer_->GlobalOffset(pc_offset()); |
| const uint8_t* pos = pc_; |
| ValueType type = consume_value_type(module_.get()); |
| auto [mutability, shared] = consume_global_flags(); |
| if (failed()) return; |
| if (shared && !type.is_shared()) { |
| CHECK(enabled_features_.has_shared()); |
| errorf(pos, "Shared global %i must have shared type, actual type %s", |
| i + imported_globals, type.name().c_str()); |
| return; |
| } |
| // Validation that {type} and {shared} are compatible will happen in |
| // {consume_init_expr}. |
| bool ends_with_struct_new = false; |
| ConstantExpression init = |
| consume_init_expr(module_.get(), type, shared, &ends_with_struct_new); |
| module_->globals.push_back( |
| WasmGlobal{.type = type, |
| .mutability = mutability, |
| .init = init, |
| .index = 0, // set later in CalculateGlobalOffsets |
| .shared = shared, |
| .initializer_ends_with_struct_new = ends_with_struct_new}); |
| if (shared) module_->has_shared_part = true; |
| } |
| } |
| |
| void DecodeExportSection() { |
| uint32_t export_table_count = |
| consume_count("exports count", kV8MaxWasmExports); |
| module_->export_table.reserve(export_table_count); |
| for (uint32_t i = 0; ok() && i < export_table_count; ++i) { |
| TRACE("DecodeExportTable[%d] module+%d\n", i, |
| static_cast<int>(pc_ - start_)); |
| if (tracer_) { |
| tracer_->Description("export #"); |
| tracer_->Description(i); |
| tracer_->NextLine(); |
| } |
| |
| WireBytesRef name = consume_utf8_string(this, "field name", tracer_); |
| |
| const uint8_t* kind_pos = pc(); |
| ImportExportKindCode kind = |
| static_cast<ImportExportKindCode>(consume_u8("kind", tracer_)); |
| |
| module_->export_table.push_back(WasmExport{.name = name, .kind = kind}); |
| WasmExport* exp = &module_->export_table.back(); |
| |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description(ExternalKindName(exp->kind)); |
| tracer_->Description(" "); |
| } |
| switch (kind) { |
| case kExternalFunction: { |
| WasmFunction* func = nullptr; |
| exp->index = consume_func_index(module_.get(), &func); |
| |
| if (failed()) break; |
| DCHECK_NOT_NULL(func); |
| |
| module_->num_exported_functions++; |
| func->exported = true; |
| // Exported functions are considered "declared". |
| func->declared = true; |
| break; |
| } |
| case kExternalTable: { |
| WasmTable* table = nullptr; |
| exp->index = consume_table_index(module_.get(), &table); |
| if (table) table->exported = true; |
| break; |
| } |
| case kExternalMemory: { |
| const uint8_t* index_pos = pc(); |
| exp->index = consume_u32v("memory index", tracer_); |
| size_t num_memories = module_->memories.size(); |
| if (exp->index >= module_->memories.size()) { |
| errorf(index_pos, |
| "invalid exported memory index %u (having %zu memor%s)", |
| exp->index, num_memories, num_memories == 1 ? "y" : "ies"); |
| break; |
| } |
| module_->memories[exp->index].exported = true; |
| break; |
| } |
| case kExternalGlobal: { |
| WasmGlobal* global = nullptr; |
| exp->index = consume_global_index(module_.get(), &global); |
| if (global) { |
| global->exported = true; |
| } |
| break; |
| } |
| case kExternalTag: { |
| WasmTag* tag = nullptr; |
| exp->index = consume_tag_index(module_.get(), &tag); |
| break; |
| } |
| default: |
| errorf(kind_pos, "invalid export kind 0x%02x", exp->kind); |
| break; |
| } |
| if (tracer_) tracer_->NextLine(); |
| } |
| // Check for duplicate exports (except for asm.js). |
| if (ok() && module_->origin == kWasmOrigin && |
| module_->export_table.size() > 1) { |
| std::vector<WasmExport> sorted_exports(module_->export_table); |
| |
| auto cmp_less = [this](const WasmExport& a, const WasmExport& b) { |
| // Return true if a < b. |
| if (a.name.length() != b.name.length()) { |
| return a.name.length() < b.name.length(); |
| } |
| const uint8_t* left = |
| start() + GetBufferRelativeOffset(a.name.offset()); |
| const uint8_t* right = |
| start() + GetBufferRelativeOffset(b.name.offset()); |
| return memcmp(left, right, a.name.length()) < 0; |
| }; |
| std::stable_sort(sorted_exports.begin(), sorted_exports.end(), cmp_less); |
| |
| auto it = sorted_exports.begin(); |
| WasmExport* last = &*it++; |
| for (auto end = sorted_exports.end(); it != end; last = &*it++) { |
| DCHECK(!cmp_less(*it, *last)); // Vector must be sorted. |
| if (!cmp_less(*last, *it)) { |
| const uint8_t* pc = |
| start() + GetBufferRelativeOffset(it->name.offset()); |
| TruncatedUserString<> name(pc, it->name.length()); |
| errorf(pc, "Duplicate export name '%.*s' for %s %d and %s %d", |
| name.length(), name.start(), ExternalKindName(last->kind), |
| last->index, ExternalKindName(it->kind), it->index); |
| break; |
| } |
| } |
| } |
| } |
| |
| void DecodeStartSection() { |
| if (tracer_) tracer_->StartOffset(pc_offset()); |
| WasmFunction* func; |
| const uint8_t* pos = pc_; |
| module_->start_function_index = consume_func_index(module_.get(), &func); |
| if (tracer_) tracer_->NextLine(); |
| if (func && |
| (func->sig->parameter_count() > 0 || func->sig->return_count() > 0)) { |
| error(pos, "invalid start function: non-zero parameter or return count"); |
| } |
| } |
| |
| void DecodeElementSection() { |
| uint32_t segment_count = |
| consume_count("segment count", wasm::max_table_size()); |
| |
| for (uint32_t i = 0; i < segment_count; ++i) { |
| if (tracer_) tracer_->ElementOffset(pc_offset()); |
| WasmElemSegment segment = consume_element_segment_header(); |
| if (tracer_) tracer_->NextLineIfNonEmpty(); |
| if (failed()) return; |
| DCHECK_NE(segment.type, kWasmBottom); |
| |
| for (uint32_t j = 0; j < segment.element_count; j++) { |
| // Just run validation on elements; do not store them anywhere. We will |
| // decode them again from wire bytes as needed. |
| consume_element_segment_entry(module_.get(), segment); |
| if (failed()) return; |
| } |
| module_->elem_segments.push_back(std::move(segment)); |
| } |
| } |
| |
| void DecodeCodeSection() { |
| // Make sure global offset were calculated before they get accessed during |
| // function compilation. |
| CalculateGlobalOffsets(module_.get()); |
| uint32_t code_section_start = pc_offset(); |
| uint32_t functions_count = consume_u32v("functions count", tracer_); |
| if (tracer_) { |
| tracer_->Description(functions_count); |
| tracer_->NextLine(); |
| } |
| CheckFunctionsCount(functions_count, code_section_start); |
| |
| auto inst_traces_it = this->inst_traces_.begin(); |
| std::vector<std::pair<uint32_t, uint32_t>> inst_traces; |
| |
| for (uint32_t i = 0; ok() && i < functions_count; ++i) { |
| int function_index = module_->num_imported_functions + i; |
| if (tracer_) { |
| tracer_->Description("function #"); |
| tracer_->FunctionName(function_index); |
| tracer_->NextLine(); |
| } |
| const uint8_t* pos = pc(); |
| uint32_t size = consume_u32v("body size", tracer_); |
| if (tracer_) { |
| tracer_->Description(size); |
| tracer_->NextLine(); |
| } |
| if (size > kV8MaxWasmFunctionSize) { |
| errorf(pos, "size %u > maximum function size %zu", size, |
| kV8MaxWasmFunctionSize); |
| return; |
| } |
| uint32_t offset = pc_offset(); |
| consume_bytes(size, "function body"); |
| if (failed()) break; |
| DecodeFunctionBody(function_index, size, offset); |
| |
| // Now that the function has been decoded, we can compute module offsets. |
| for (; inst_traces_it != this->inst_traces_.end() && |
| std::get<0>(*inst_traces_it) == i; |
| ++inst_traces_it) { |
| uint32_t trace_offset = offset + std::get<1>(*inst_traces_it); |
| uint32_t mark_id = std::get<2>(*inst_traces_it); |
| std::pair<uint32_t, uint32_t> trace_mark = {trace_offset, mark_id}; |
| inst_traces.push_back(trace_mark); |
| } |
| } |
| // If we have actually decoded traces and they were all decoded without |
| // error, then we can move them to the module. If any errors are found, it |
| // is safe to throw away all traces. |
| if (V8_UNLIKELY(!inst_traces.empty() && |
| inst_traces_it == this->inst_traces_.end())) { |
| // This adds an invalid entry at the end of the traces. An invalid entry |
| // is defined as having an module offset of 0 and a markid of 0. |
| inst_traces.push_back({0, 0}); |
| this->module_->inst_traces = std::move(inst_traces); |
| } |
| DCHECK_GE(pc_offset(), code_section_start); |
| module_->code = {code_section_start, pc_offset() - code_section_start}; |
| } |
| |
| void StartCodeSection(WireBytesRef section_bytes) { |
| CheckSectionOrder(kCodeSectionCode); |
| // Make sure global offset were calculated before they get accessed during |
| // function compilation. |
| CalculateGlobalOffsets(module_.get()); |
| module_->code = section_bytes; |
| } |
| |
| bool CheckFunctionsCount(uint32_t functions_count, uint32_t error_offset) { |
| if (functions_count != module_->num_declared_functions) { |
| errorf(error_offset, "function body count %u mismatch (%u expected)", |
| functions_count, module_->num_declared_functions); |
| return false; |
| } |
| return true; |
| } |
| |
| void DecodeFunctionBody(uint32_t func_index, uint32_t length, |
| uint32_t offset) { |
| WasmFunction* function = &module_->functions[func_index]; |
| function->code = {offset, length}; |
| constexpr uint32_t kSmallFunctionThreshold = 50; |
| if (length < kSmallFunctionThreshold) { |
| ++module_->num_small_functions; |
| } |
| if (tracer_) { |
| tracer_->FunctionBody(function, pc_ - (pc_offset() - offset)); |
| } |
| } |
| |
| bool CheckDataSegmentsCount(uint32_t data_segments_count) { |
| if (has_seen_unordered_section(kDataCountSectionCode) && |
| data_segments_count != module_->num_declared_data_segments) { |
| errorf(pc(), "data segments count %u mismatch (%u expected)", |
| data_segments_count, module_->num_declared_data_segments); |
| return false; |
| } |
| return true; |
| } |
| |
| struct DataSegmentHeader { |
| bool is_active; |
| bool is_shared; |
| uint32_t memory_index; |
| ConstantExpression dest_addr; |
| }; |
| |
| void DecodeDataSection() { |
| uint32_t data_segments_count = |
| consume_count("data segments count", kV8MaxWasmDataSegments); |
| if (!CheckDataSegmentsCount(data_segments_count)) return; |
| |
| module_->data_segments.reserve(data_segments_count); |
| for (uint32_t i = 0; i < data_segments_count; ++i) { |
| TRACE("DecodeDataSegment[%d] module+%d\n", i, |
| static_cast<int>(pc_ - start_)); |
| if (tracer_) tracer_->DataOffset(pc_offset()); |
| |
| DataSegmentHeader header = consume_data_segment_header(); |
| |
| uint32_t source_length = consume_u32v("source size", tracer_); |
| if (tracer_) { |
| tracer_->Description(source_length); |
| tracer_->NextLine(); |
| } |
| uint32_t source_offset = pc_offset(); |
| |
| if (tracer_) { |
| tracer_->Bytes(pc_, source_length); |
| tracer_->Description("segment data"); |
| tracer_->NextLine(); |
| } |
| consume_bytes(source_length, "segment data"); |
| |
| if (failed()) break; |
| module_->data_segments.emplace_back( |
| header.is_active, header.is_shared, header.memory_index, |
| header.dest_addr, WireBytesRef{source_offset, source_length}); |
| } |
| } |
| |
| void DecodeNameSection() { |
| if (tracer_) { |
| tracer_->NameSection( |
| pc_, end_, buffer_offset_ + static_cast<uint32_t>(pc_ - start_)); |
| } |
| // TODO(titzer): find a way to report name errors as warnings. |
| // Ignore all but the first occurrence of name section. |
| if (!has_seen_unordered_section(kNameSectionCode)) { |
| set_seen_unordered_section(kNameSectionCode); |
| module_->name_section = {buffer_offset_, |
| static_cast<uint32_t>(end_ - start_)}; |
| // Use an inner decoder so that errors don't fail the outer decoder. |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| // Decode all name subsections. |
| // 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"); |
| |
| uint32_t name_payload_len = inner.consume_u32v("name payload length"); |
| if (!inner.checkAvailable(name_payload_len)) break; |
| |
| // Decode module name, ignore the rest. |
| // Function and local names will be decoded when needed. |
| if (name_type == NameSectionKindCode::kModuleCode) { |
| WireBytesRef name = |
| consume_string(&inner, unibrow::Utf8Variant::kLossyUtf8, |
| "module name", ITracer::NoTrace); |
| if (inner.ok() && validate_utf8(&inner, name)) { |
| module_->name = name; |
| } |
| } else { |
| inner.consume_bytes(name_payload_len, "name subsection payload"); |
| } |
| } |
| } |
| // Skip the whole names section in the outer decoder. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeDescriptorsSection() { |
| if (enabled_features_.has_custom_descriptors() && |
| !has_seen_unordered_section(kDescriptorsSectionCode)) { |
| set_seen_unordered_section(kDescriptorsSectionCode); |
| module_->descriptors_section = {buffer_offset_, |
| static_cast<uint32_t>(end_ - start_)}; |
| } |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeSourceMappingURLSection() { |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| WireBytesRef url = |
| wasm::consume_utf8_string(&inner, "module name", tracer_); |
| if (inner.ok() && |
| module_->debug_symbols[WasmDebugSymbols::Type::SourceMap].type == |
| WasmDebugSymbols::None) { |
| module_->debug_symbols[WasmDebugSymbols::Type::SourceMap] = { |
| WasmDebugSymbols::Type::SourceMap, url}; |
| } |
| set_seen_unordered_section(kSourceMappingURLSectionCode); |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeExternalDebugInfoSection() { |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| WireBytesRef url = |
| wasm::consume_utf8_string(&inner, "external symbol file", tracer_); |
| if (inner.ok()) { |
| module_->debug_symbols[WasmDebugSymbols::Type::ExternalDWARF] = { |
| WasmDebugSymbols::Type::ExternalDWARF, url}; |
| set_seen_unordered_section(kExternalDebugInfoSectionCode); |
| } |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeBuildIdSection() { |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| WireBytesRef build_id = |
| consume_string(&inner, unibrow::Utf8Variant::kLossyUtf8, "build_id"); |
| if (inner.ok()) { |
| module_->build_id = build_id; |
| set_seen_unordered_section(kBuildIdSectionCode); |
| } |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeInstTraceSection() { |
| TRACE("DecodeInstTrace module+%d\n", static_cast<int>(pc_ - start_)); |
| if (!has_seen_unordered_section(kInstTraceSectionCode)) { |
| set_seen_unordered_section(kInstTraceSectionCode); |
| |
| // Use an inner decoder so that errors don't fail the outer decoder. |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| |
| std::vector<std::tuple<uint32_t, uint32_t, uint32_t>> inst_traces; |
| |
| uint32_t func_count = inner.consume_u32v("number of functions"); |
| // Keep track of the previous function index to validate the ordering. |
| int64_t last_func_idx = -1; |
| for (uint32_t i = 0; i < func_count; i++) { |
| uint32_t func_idx = inner.consume_u32v("function index"); |
| if (int64_t{func_idx} <= last_func_idx) { |
| inner.errorf("Invalid function index: %d", func_idx); |
| break; |
| } |
| last_func_idx = func_idx; |
| |
| uint32_t num_traces = inner.consume_u32v("number of trace marks"); |
| TRACE("DecodeInstTrace[%d] module+%d\n", func_idx, |
| static_cast<int>(inner.pc() - inner.start())); |
| // Keep track of the previous offset to validate the ordering. |
| int64_t last_func_off = -1; |
| for (uint32_t j = 0; j < num_traces; ++j) { |
| uint32_t func_off = inner.consume_u32v("function offset"); |
| |
| uint32_t mark_size = inner.consume_u32v("mark size"); |
| uint32_t trace_mark_id = 0; |
| // Build the mark id from the individual bytes. |
| for (uint32_t k = 0; k < mark_size; k++) { |
| trace_mark_id |= inner.consume_u8("trace mark id") << k * 8; |
| } |
| if (int64_t{func_off} <= last_func_off) { |
| inner.errorf("Invalid branch offset: %d", func_off); |
| break; |
| } |
| last_func_off = func_off; |
| TRACE("DecodeInstTrace[%d][%d] module+%d\n", func_idx, func_off, |
| static_cast<int>(inner.pc() - inner.start())); |
| // Store the function index, function offset, and mark id into a |
| // temporary 3-tuple. This will later be translated to a module |
| // offset and mark id. |
| std::tuple<uint32_t, uint32_t, uint32_t> mark_tuple = { |
| func_idx, func_off, trace_mark_id}; |
| inst_traces.push_back(mark_tuple); |
| } |
| } |
| // Extra unexpected bytes are an error. |
| if (inner.more()) { |
| inner.errorf("Unexpected extra bytes: %d\n", |
| static_cast<int>(inner.pc() - inner.start())); |
| } |
| // If everything went well, accept the traces for the module. |
| if (inner.ok()) { |
| this->inst_traces_ = std::move(inst_traces); |
| } |
| } |
| |
| // Skip the whole instruction trace section in the outer decoder. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeBranchHintsSection() { |
| TRACE("DecodeBranchHints module+%d\n", static_cast<int>(pc_ - start_)); |
| detected_features_->add_branch_hinting(); |
| if (!has_seen_unordered_section(kBranchHintsSectionCode)) { |
| set_seen_unordered_section(kBranchHintsSectionCode); |
| // Use an inner decoder so that errors don't fail the outer decoder. |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| BranchHintInfo branch_hints; |
| |
| uint32_t func_count = inner.consume_u32v("number of functions"); |
| // Keep track of the previous function index to validate the ordering |
| int64_t last_func_idx = -1; |
| for (uint32_t i = 0; i < func_count; i++) { |
| uint32_t func_idx = inner.consume_u32v("function index"); |
| if (int64_t{func_idx} <= last_func_idx) { |
| inner.errorf("Invalid function index: %d", func_idx); |
| break; |
| } |
| last_func_idx = func_idx; |
| uint32_t num_hints = inner.consume_u32v("number of hints"); |
| BranchHintMap func_branch_hints; |
| TRACE("DecodeBranchHints[%d] module+%d\n", func_idx, |
| static_cast<int>(inner.pc() - inner.start())); |
| // Keep track of the previous branch offset to validate the ordering |
| int64_t last_br_off = -1; |
| for (uint32_t j = 0; j < num_hints; ++j) { |
| uint32_t br_off = inner.consume_u32v("branch instruction offset"); |
| if (int64_t{br_off} <= last_br_off) { |
| inner.errorf("Invalid branch offset: %d", br_off); |
| break; |
| } |
| last_br_off = br_off; |
| uint32_t data_size = inner.consume_u32v("data size"); |
| if (data_size != 1) { |
| inner.errorf("Invalid data size: %#x. Expected 1.", data_size); |
| break; |
| } |
| uint32_t br_dir = inner.consume_u8("branch direction"); |
| TRACE("DecodeBranchHints[%d][%d] module+%d\n", func_idx, br_off, |
| static_cast<int>(inner.pc() - inner.start())); |
| BranchHint hint; |
| switch (br_dir) { |
| case 0: |
| hint = BranchHint::kFalse; |
| break; |
| case 1: |
| hint = BranchHint::kTrue; |
| break; |
| default: |
| hint = BranchHint::kNone; |
| inner.errorf(inner.pc(), "Invalid branch hint %#x", br_dir); |
| break; |
| } |
| if (!inner.ok()) { |
| break; |
| } |
| func_branch_hints.insert(br_off, hint); |
| } |
| if (!inner.ok()) { |
| break; |
| } |
| branch_hints.emplace(func_idx, std::move(func_branch_hints)); |
| } |
| // Extra unexpected bytes are an error. |
| if (inner.more()) { |
| inner.errorf("Unexpected extra bytes: %d\n", |
| static_cast<int>(inner.pc() - inner.start())); |
| } |
| // If everything went well, accept the hints for the module. |
| if (inner.ok()) { |
| module_->branch_hints = std::move(branch_hints); |
| } else { |
| TRACE("DecodeBranchHints error: %s", inner.error().message().c_str()); |
| } |
| } |
| // Skip the whole branch hints section in the outer decoder. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeCompilationPrioritySection() { |
| TRACE("DecodeCompilationPriority module+%d\n", |
| static_cast<int>(pc_ - start_)); |
| detected_features_->add_compilation_hints(); |
| if (!has_seen_unordered_section(kCompilationPrioritySectionCode)) { |
| set_seen_unordered_section(kCompilationPrioritySectionCode); |
| // Use an inner decoder so that errors don't fail the outer decoder. |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| CompilationPriorities compilation_priorities; |
| |
| uint32_t func_count = inner.consume_u32v("number of functions"); |
| |
| int64_t last_func_index = -1; |
| |
| for (uint32_t i = 0; i < func_count; i++) { |
| uint32_t func_index = inner.consume_u32v("function index"); |
| if (static_cast<int64_t>(func_index) <= last_func_index) { |
| inner.error("out of order functions"); |
| break; |
| } |
| last_func_index = func_index; |
| uint32_t byte_offset = inner.consume_u32v("byte offset"); |
| if (byte_offset != 0) { |
| inner.error("byte offset has to be 0 (function level only)"); |
| break; |
| } |
| uint32_t hint_length = inner.consume_u32v("hint length"); |
| const uint8_t* pc_after_hint_length = inner.pc(); |
| uint32_t compilation_priority = |
| inner.consume_u32v("compilation priority"); |
| if (static_cast<int64_t>(hint_length) < |
| inner.pc() - pc_after_hint_length) { |
| inner.error("Compilation priority longer than declared hint length"); |
| break; |
| } |
| int optimization_priority = kOptimizationPriorityNotSpecifiedSentinel; |
| |
| // If we exhausted the declared hint length, do not parse optimization |
| // priority. |
| if (static_cast<int64_t>(hint_length) > |
| inner.pc() - pc_after_hint_length) { |
| uint32_t parsed_priority = |
| inner.consume_u32v("optimization priority"); |
| if (static_cast<int>(parsed_priority) > |
| kOptimizationPriorityExecutedOnceSentinel) { |
| inner.error("Optimization priority too large"); |
| break; |
| } |
| optimization_priority = static_cast<int>(parsed_priority); |
| if (static_cast<int64_t>(hint_length) < |
| inner.pc() - pc_after_hint_length) { |
| inner.error("Optimization priority overflows declared hint length"); |
| break; |
| } |
| } |
| |
| // Ignore remaining hint bytes. |
| inner.consume_bytes( |
| hint_length - |
| static_cast<uint32_t>(inner.pc() - pc_after_hint_length)); |
| |
| if (!inner.ok()) break; |
| |
| compilation_priorities.emplace( |
| func_index, |
| CompilationPriority{compilation_priority, optimization_priority}); |
| } |
| |
| // Extra unexpected bytes are an error. |
| if (inner.more()) { |
| inner.errorf("Unexpected extra bytes: %d\n", |
| static_cast<int>(inner.pc() - inner.start())); |
| } |
| // If everything went well, accept the compilation priority hints for the |
| // module. |
| if (inner.ok()) { |
| module_->compilation_priorities = std::move(compilation_priorities); |
| } else { |
| TRACE("DecodeCompilationPriority error: %s", |
| inner.error().message().c_str()); |
| } |
| } |
| // Skip the whole compilation priority section in the outer decoder. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeInstructionFrequenciesSection() { |
| TRACE("DecodeInstructionFrequencies module+%d\n", |
| static_cast<int>(pc_ - start_)); |
| detected_features_->add_compilation_hints(); |
| if (!has_seen_unordered_section(kInstFrequenciesSectionCode)) { |
| set_seen_unordered_section(kInstFrequenciesSectionCode); |
| // Use an inner decoder so that errors don't fail the outer decoder. |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| |
| InstructionFrequencies frequencies; |
| |
| uint32_t func_count = inner.consume_u32v("number of functions"); |
| |
| int64_t last_func_index = -1; |
| |
| for (uint32_t i = 0; i < func_count; i++) { |
| uint32_t func_index = inner.consume_u32v("function index"); |
| if (static_cast<int64_t>(func_index) <= last_func_index) { |
| inner.error("out of order functions"); |
| break; |
| } |
| last_func_index = func_index; |
| |
| int64_t last_byte_offset = -1; |
| uint32_t hints_count = inner.consume_u32v("hints count"); |
| |
| for (uint32_t hint = 0; hint < hints_count; hint++) { |
| uint32_t byte_offset = inner.consume_u32v("byte offset"); |
| if (static_cast<int64_t>(byte_offset) <= last_byte_offset) { |
| inner.error("out of order hints"); |
| break; |
| } |
| last_byte_offset = byte_offset; |
| |
| uint32_t hint_length = inner.consume_u32v("hint length"); |
| if (hint_length == 0) { |
| inner.error("hint length must be larger than 0"); |
| break; |
| } |
| |
| uint8_t frequency = inner.consume_u8("frequency"); |
| |
| // Skip remaining hint bytes. |
| if (hint_length > 1) { |
| inner.consume_bytes(hint_length - 1); |
| } |
| |
| frequencies.emplace(std::pair{func_index, byte_offset}, frequency); |
| } |
| } |
| |
| // Extra unexpected bytes are an error. |
| if (inner.more()) { |
| inner.errorf("Unexpected extra bytes: %d\n", |
| static_cast<int>(inner.pc() - inner.start())); |
| } |
| // If everything went well, accept the instruction-frequency hints for the |
| // module. |
| if (inner.ok()) { |
| module_->instruction_frequencies = std::move(frequencies); |
| } else { |
| TRACE("DecodeInstructionFrequencies error: %s", |
| inner.error().message().c_str()); |
| } |
| } |
| // Skip the whole instruction frequencies section in the outer decoder. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeCallTargetsSection() { |
| TRACE("DecodeCallTargets module+%d\n", static_cast<int>(pc_ - start_)); |
| detected_features_->add_compilation_hints(); |
| if (!has_seen_unordered_section(kCallTargetsSectionCode)) { |
| set_seen_unordered_section(kCallTargetsSectionCode); |
| // Use an inner decoder so that errors don't fail the outer decoder. |
| Decoder inner(start_, pc_, end_, buffer_offset_); |
| |
| CallTargets call_targets; |
| |
| uint32_t func_count = inner.consume_u32v("number of functions"); |
| |
| int64_t last_func_index = -1; |
| |
| for (uint32_t i = 0; i < func_count; i++) { |
| uint32_t func_index = inner.consume_u32v("function index"); |
| if (static_cast<int64_t>(func_index) <= last_func_index) { |
| inner.error("out of order functions"); |
| break; |
| } |
| last_func_index = func_index; |
| |
| int64_t last_byte_offset = -1; |
| uint32_t hints_count = inner.consume_u32v("hints count"); |
| |
| for (uint32_t hint = 0; hint < hints_count; hint++) { |
| uint32_t byte_offset = inner.consume_u32v("byte offset"); |
| if (static_cast<int64_t>(byte_offset) <= last_byte_offset) { |
| inner.error("out of order hints"); |
| break; |
| } |
| last_byte_offset = byte_offset; |
| uint32_t hint_length = inner.consume_u32v("hint length"); |
| if (hint_length == 0) { |
| inner.error("hint length must be greater than 0"); |
| break; |
| } |
| CallTargetVector call_targets_for_offset; |
| while (hint_length > 0) { |
| auto [function_index, function_index_length] = |
| inner.read_u32v<FullValidationTag>(inner.pc(), |
| "function index"); |
| if (inner.failed()) break; |
| if (function_index_length > hint_length) { |
| inner.error("function length overflows declared hint length"); |
| break; |
| } |
| hint_length -= function_index_length; |
| inner.consume_bytes(function_index_length); |
| |
| auto [call_frequency, call_frequency_length] = |
| inner.read_u32v<FullValidationTag>(inner.pc(), |
| "call frequency"); |
| if (inner.failed()) break; |
| if (call_frequency_length > hint_length) { |
| inner.error("call frequency overflows declared hint length"); |
| break; |
| } |
| if (call_frequency > 100U) { |
| inner.error("invalid call frequency percentage"); |
| break; |
| } |
| hint_length -= call_frequency_length; |
| inner.consume_bytes(call_frequency_length); |
| |
| call_targets_for_offset.emplace_back(function_index, |
| call_frequency); |
| } |
| if (inner.failed()) break; |
| |
| DCHECK_EQ(hint_length, 0); |
| |
| uint32_t sum_of_percentages = 0; |
| for (CallTarget& call_target : call_targets_for_offset) { |
| sum_of_percentages += call_target.call_frequency_percent; |
| } |
| |
| if (sum_of_percentages > 100U) { |
| inner.error("percentages must sum to at most 100"); |
| break; |
| } |
| |
| call_targets.emplace(std::pair{func_index, byte_offset}, |
| call_targets_for_offset); |
| } |
| if (inner.failed()) break; |
| } |
| |
| // Extra unexpected bytes are an error. |
| if (inner.more()) { |
| inner.errorf("Unexpected extra bytes: %d\n", |
| static_cast<int>(inner.pc() - inner.start())); |
| } |
| // If everything went well, accept the call-target hints for the module. |
| if (inner.ok()) { |
| module_->call_targets = std::move(call_targets); |
| } else { |
| TRACE("DecodeCallTargets error: %s", inner.error().message().c_str()); |
| } |
| } |
| // Skip the whole call-targets section in the outer decoder. |
| consume_bytes(static_cast<uint32_t>(end_ - start_), nullptr); |
| } |
| |
| void DecodeDataCountSection() { |
| module_->num_declared_data_segments = |
| consume_count("data segments count", kV8MaxWasmDataSegments); |
| if (tracer_) tracer_->NextLineIfNonEmpty(); |
| } |
| |
| void DecodeTagSection() { |
| uint32_t tag_count = consume_count("tag count", kV8MaxWasmTags); |
| for (uint32_t i = 0; ok() && i < tag_count; ++i) { |
| TRACE("DecodeTag[%d] module+%d\n", i, static_cast<int>(pc_ - start_)); |
| if (tracer_) tracer_->TagOffset(pc_offset()); |
| const WasmTagSig* tag_sig = nullptr; |
| consume_exception_attribute(); // Attribute ignored for now. |
| ModuleTypeIndex sig_index = |
| consume_tag_sig_index(module_.get(), &tag_sig); |
| module_->tags.emplace_back(tag_sig, sig_index); |
| } |
| } |
| |
| void DecodeStringRefSection() { |
| uint32_t deferred = consume_count("deferred string literal count", |
| kV8MaxWasmStringLiterals); |
| if (deferred) { |
| errorf(pc(), "Invalid deferred string literal count %u (expected 0)", |
| deferred); |
| } |
| uint32_t immediate = consume_count("string literal count", |
| kV8MaxWasmStringLiterals - deferred); |
| for (uint32_t i = 0; ok() && i < immediate; ++i) { |
| TRACE("DecodeStringLiteral[%d] module+%d\n", i, |
| static_cast<int>(pc_ - start_)); |
| if (tracer_) tracer_->StringOffset(pc_offset()); |
| // TODO(12868): Throw if the string's utf-16 length > String::kMaxLength. |
| WireBytesRef pos = wasm::consume_string(this, unibrow::Utf8Variant::kWtf8, |
| "string literal", tracer_); |
| module_->stringref_literals.emplace_back(pos); |
| } |
| } |
| |
| bool CheckMismatchedCounts() { |
| // The declared vs. defined function count is normally checked when |
| // decoding the code section, but we have to check it here too in case the |
| // code section is absent. |
| if (module_->num_declared_functions != 0) { |
| DCHECK_LT(module_->num_imported_functions, module_->functions.size()); |
| // We know that the code section has been decoded if the first |
| // non-imported function has its code set. |
| if (!module_->functions[module_->num_imported_functions].code.is_set()) { |
| errorf(pc(), "function count is %u, but code section is absent", |
| module_->num_declared_functions); |
| return false; |
| } |
| } |
| // Perform a similar check for the DataCount and Data sections, where data |
| // segments are declared but the Data section is absent. |
| if (!CheckDataSegmentsCount( |
| static_cast<uint32_t>(module_->data_segments.size()))) { |
| return false; |
| } |
| return true; |
| } |
| |
| ModuleResult FinishDecoding() { |
| if (ok() && CheckMismatchedCounts()) { |
| // We calculate the global offsets here, because there may not be a |
| // global section and code section that would have triggered the |
| // calculation before. Even without the globals section the calculation |
| // is needed because globals can also be defined in the import section. |
| CalculateGlobalOffsets(module_.get()); |
| } |
| |
| if (module_->has_shared_part) detected_features_->add_shared(); |
| |
| return toResult(std::move(module_)); |
| } |
| |
| // Decodes an entire module. |
| ModuleResult DecodeModule(bool validate_functions) { |
| // Keep a reference to the wire bytes, in case this decoder gets reset on |
| // error. |
| base::Vector<const uint8_t> wire_bytes(start_, end_ - start_); |
| size_t max_size = max_module_size(); |
| if (wire_bytes.size() > max_size) { |
| return ModuleResult{WasmError{0, "size > maximum module size (%zu): %zu", |
| max_size, wire_bytes.size()}}; |
| } |
| |
| DecodeModuleHeader(wire_bytes); |
| if (failed()) return toResult(nullptr); |
| |
| static constexpr uint32_t kWasmHeaderSize = 8; |
| Decoder section_iterator_decoder(start_ + kWasmHeaderSize, end_, |
| kWasmHeaderSize); |
| WasmSectionIterator section_iter(§ion_iterator_decoder, tracer_); |
| |
| while (ok()) { |
| if (section_iter.section_code() != SectionCode::kUnknownSectionCode) { |
| uint32_t offset = static_cast<uint32_t>(section_iter.payload().begin() - |
| wire_bytes.begin()); |
| DecodeSection(section_iter.section_code(), section_iter.payload(), |
| offset); |
| if (!ok()) break; |
| } |
| if (!section_iter.more()) break; |
| section_iter.advance(true); |
| } |
| |
| // Check for module structure errors before validating function bodies, to |
| // produce consistent error message independent of whether validation |
| // happens here or later. |
| if (section_iterator_decoder.failed()) { |
| return section_iterator_decoder.toResult(nullptr); |
| } |
| |
| ModuleResult result = FinishDecoding(); |
| if (!result.failed() && validate_functions) { |
| std::function<bool(int)> kNoFilter; |
| if (WasmError validation_error = |
| ValidateFunctions(module_.get(), enabled_features_, wire_bytes, |
| kNoFilter, detected_features_)) { |
| result = ModuleResult{validation_error}; |
| } |
| } |
| |
| if (v8_flags.dump_wasm_module) DumpModule(wire_bytes, result.ok()); |
| |
| return result; |
| } |
| |
| // Decodes a single anonymous function starting at {start_}. |
| FunctionResult DecodeSingleFunctionForTesting(Zone* zone, |
| ModuleWireBytes wire_bytes, |
| const WasmModule* module) { |
| DCHECK(ok()); |
| pc_ = start_; |
| expect_u8("type form", kWasmFunctionTypeCode); |
| WasmFunction function; |
| function.sig = consume_sig(zone); |
| function.code = {off(pc_), static_cast<uint32_t>(end_ - pc_)}; |
| |
| if (!ok()) return FunctionResult{std::move(error_)}; |
| |
| constexpr bool kShared = false; |
| FunctionBody body{function.sig, off(pc_), pc_, end_, kShared}; |
| |
| WasmDetectedFeatures unused_detected_features; |
| DecodeResult result = ValidateFunctionBody(zone, enabled_features_, module, |
| &unused_detected_features, body); |
| |
| if (result.failed()) return FunctionResult{std::move(result).error()}; |
| |
| return FunctionResult{std::make_unique<WasmFunction>(function)}; |
| } |
| |
| // Decodes a single function signature at {start}. |
| const FunctionSig* DecodeFunctionSignatureForTesting(Zone* zone, |
| const uint8_t* start) { |
| pc_ = start; |
| if (!expect_u8("type form", kWasmFunctionTypeCode)) return nullptr; |
| const FunctionSig* result = consume_sig(zone); |
| return ok() ? result : nullptr; |
| } |
| |
| ConstantExpression DecodeInitExprForTesting(ValueType expected) { |
| constexpr bool kIsShared = false; // TODO(14616): Extend this. |
| return consume_init_expr(module_.get(), expected, kIsShared); |
| } |
| |
| // Takes a module as parameter so that wasm-disassembler.cc can pass its own |
| // module. |
| ConstantExpression consume_element_segment_entry( |
| WasmModule* module, const WasmElemSegment& segment) { |
| if (segment.element_type == WasmElemSegment::kExpressionElements) { |
| return consume_init_expr(module, segment.type, segment.shared); |
| } else { |
| return ConstantExpression::RefFunc( |
| consume_element_func_index(module, segment.type)); |
| } |
| } |
| |
| const std::shared_ptr<WasmModule>& shared_module() const { return module_; } |
| |
| private: |
| bool has_seen_unordered_section(SectionCode section_code) { |
| return seen_unordered_sections_ & (1 << section_code); |
| } |
| |
| void set_seen_unordered_section(SectionCode section_code) { |
| seen_unordered_sections_ |= 1 << section_code; |
| } |
| |
| uint32_t off(const uint8_t* ptr) { |
| return static_cast<uint32_t>(ptr - start_) + buffer_offset_; |
| } |
| |
| // Calculate individual global offsets and total size of globals table. This |
| // function should be called after all globals have been defined, which is |
| // after the import section and the global section, but before the global |
| // offsets are accessed, e.g. by the function compilers. The moment when this |
| // function should be called is not well-defined, as the global section may |
| // not exist. Therefore this function is called multiple times. |
| void CalculateGlobalOffsets(WasmModule* module) { |
| if (module->globals.empty() || module->untagged_globals_buffer_size != 0 || |
| module->tagged_globals_buffer_size != 0) { |
| // This function has already been executed before, so we don't have to |
| // execute it again. |
| return; |
| } |
| uint32_t untagged_offset = 0; |
| uint32_t tagged_offset = 0; |
| uint32_t num_imported_mutable_globals = 0; |
| for (WasmGlobal& global : module->globals) { |
| if (global.mutability && global.imported) { |
| global.index = num_imported_mutable_globals++; |
| } else if (global.type.is_reference()) { |
| global.offset = tagged_offset; |
| // All entries in the tagged_globals_buffer have size 1. |
| tagged_offset++; |
| } else { |
| int size = global.type.value_kind_size(); |
| untagged_offset = (untagged_offset + size - 1) & ~(size - 1); // align |
| global.offset = untagged_offset; |
| untagged_offset += size; |
| } |
| } |
| module->untagged_globals_buffer_size = untagged_offset; |
| module->tagged_globals_buffer_size = tagged_offset; |
| } |
| |
| ModuleTypeIndex consume_sig_index(WasmModule* module, |
| const FunctionSig** sig) { |
| const uint8_t* pos = pc_; |
| ModuleTypeIndex sig_index{consume_u32v("signature index")}; |
| if (tracer_) tracer_->Bytes(pos, static_cast<uint32_t>(pc_ - pos)); |
| if (!module->has_signature(sig_index)) { |
| errorf(pos, "no signature at index %u (%d types)", sig_index.index, |
| static_cast<int>(module->types.size())); |
| *sig = nullptr; |
| return {}; |
| } |
| *sig = module->signature(sig_index); |
| if (tracer_) { |
| tracer_->Description(*sig); |
| tracer_->NextLine(); |
| } |
| return sig_index; |
| } |
| |
| ModuleTypeIndex consume_tag_sig_index(WasmModule* module, |
| const FunctionSig** sig) { |
| const uint8_t* pos = pc_; |
| ModuleTypeIndex sig_index = consume_sig_index(module, sig); |
| |
| if (!enabled_features_.has_wasmfx() && *sig && |
| (*sig)->return_count() != 0) { |
| errorf(pos, "tag signature %u has non-void return", sig_index); |
| *sig = nullptr; |
| return {}; |
| } |
| return sig_index; |
| } |
| |
| uint32_t consume_count(const char* name, size_t maximum) { |
| const uint8_t* p = pc_; |
| uint32_t count = consume_u32v(name, tracer_); |
| if (tracer_) { |
| tracer_->Description(count); |
| if (count == 1) { |
| tracer_->Description(": "); |
| } else { |
| tracer_->NextLine(); |
| } |
| } |
| if (count > maximum) { |
| errorf(p, "%s of %u exceeds internal limit of %zu", name, count, maximum); |
| return 0; |
| } |
| return count; |
| } |
| |
| uint32_t consume_func_index(WasmModule* module, WasmFunction** func) { |
| return consume_index("function", &module->functions, func); |
| } |
| |
| uint32_t consume_global_index(WasmModule* module, WasmGlobal** global) { |
| return consume_index("global", &module->globals, global); |
| } |
| |
| uint32_t consume_table_index(WasmModule* module, WasmTable** table) { |
| return consume_index("table", &module->tables, table); |
| } |
| |
| uint32_t consume_tag_index(WasmModule* module, WasmTag** tag) { |
| return consume_index("tag", &module->tags, tag); |
| } |
| |
| template <typename T> |
| uint32_t consume_index(const char* name, std::vector<T>* vector, T** ptr) { |
| const uint8_t* pos = pc_; |
| uint32_t index = consume_u32v("index", tracer_); |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description(index); |
| } |
| if (index >= vector->size()) { |
| errorf(pos, "%s index %u out of bounds (%d entr%s)", name, index, |
| static_cast<int>(vector->size()), |
| vector->size() == 1 ? "y" : "ies"); |
| *ptr = nullptr; |
| return 0; |
| } |
| *ptr = &(*vector)[index]; |
| return index; |
| } |
| |
| // The limits byte structure is used for memories and tables. |
| struct LimitsByte { |
| uint8_t flags; |
| |
| // Flags 0..7 are valid (3 bits). |
| bool is_valid() const { return (flags & ~0x7) == 0; } |
| bool has_maximum() const { return flags & 0x1; } |
| bool is_shared() const { return flags & 0x2; } |
| bool is_64bit() const { return flags & 0x4; } |
| AddressType address_type() const { |
| return is_64bit() ? AddressType::kI64 : AddressType::kI32; |
| } |
| }; |
| |
| enum LimitsByteType { kMemory, kTable }; |
| |
| template <LimitsByteType limits_type> |
| LimitsByte consume_limits_byte() { |
| if (tracer_) tracer_->Bytes(pc_, 1); |
| LimitsByte limits{consume_u8( |
| limits_type == kMemory ? "memory limits flags" : "table limits flags")}; |
| if (!limits.is_valid()) { |
| errorf(pc() - 1, "invalid %s limits flags 0x%x", |
| limits_type == kMemory ? "memory" : "table", limits.flags); |
| } |
| |
| if (limits.is_shared()) { |
| if constexpr (limits_type == kMemory) { |
| // V8 does not support shared memory without a maximum. |
| if (!limits.has_maximum()) { |
| error(pc() - 1, "shared memory must have a maximum defined"); |
| } |
| // TODO(42204563): Implement proper handling of shared memories when |
| // Shared Everything is enabled. |
| } else if (!enabled_features_.has_shared()) { // table |
| error(pc() - 1, "invalid table limits flags"); |
| } else { |
| // TODO(42204563): Support shared tables. |
| error(pc() - 1, "shared tables are not supported yet"); |
| } |
| } |
| |
| if (tracer_) { |
| if (limits.is_shared()) tracer_->Description(" shared"); |
| if (limits.is_64bit()) { |
| tracer_->Description(limits_type == kMemory ? " mem64" : " table64"); |
| } |
| tracer_->Description(limits.has_maximum() ? " with maximum" |
| : " no maximum"); |
| tracer_->NextLine(); |
| } |
| |
| return limits; |
| } |
| |
| void consume_table_flags(WasmTable* table) { |
| LimitsByte limits = consume_limits_byte<kTable>(); |
| table->has_maximum_size = limits.has_maximum(); |
| table->shared = limits.is_shared(); |
| table->address_type = limits.address_type(); |
| |
| if (table->is_table64()) detected_features_->add_memory64(); |
| } |
| |
| void consume_memory_flags(WasmMemory* memory) { |
| LimitsByte limits = consume_limits_byte<kMemory>(); |
| memory->has_maximum_pages = limits.has_maximum(); |
| memory->is_shared = limits.is_shared(); |
| memory->address_type = limits.address_type(); |
| |
| if (memory->is_shared) detected_features_->add_shared_memory(); |
| if (memory->is_memory64()) detected_features_->add_memory64(); |
| } |
| |
| std::pair<bool, bool> consume_global_flags() { |
| uint8_t flags = consume_u8("global flags"); |
| if (flags & ~0b11) { |
| errorf(pc() - 1, "invalid global flags 0x%x", flags); |
| return {false, false}; |
| } |
| bool mutability = flags & 0b1; |
| bool shared = flags & 0b10; |
| if (tracer_) { |
| tracer_->Bytes(pc_ - 1, 1); // The flags byte. |
| if (shared) tracer_->Description(" shared"); |
| tracer_->Description(mutability ? " mutable" : " immutable"); |
| } |
| if (shared && !enabled_features_.has_shared()) { |
| errorf(pc() - 1, "invalid global flags 0x%x", flags); |
| return {false, false}; |
| } |
| if (shared) { |
| // TODO(42204563): Support shared globals. |
| error(pc() - 1, "shared globals are not supported yet"); |
| return {false, false}; |
| } |
| return {mutability, shared}; |
| } |
| |
| enum ResizableLimitsType : bool { k32BitLimits, k64BitLimits }; |
| void consume_resizable_limits( |
| const char* name, const char* units, |
| // Not: both memories and tables have a 32-bit limit on the initial size. |
| uint32_t max_initial, uint32_t* initial, bool has_maximum, |
| uint64_t max_maximum, uint64_t* maximum, ResizableLimitsType type) { |
| const uint8_t* pos = pc(); |
| // Note that even if we read the values as 64-bit value, all V8 limits are |
| // still within uint32_t range. |
| uint64_t initial_64 = type == k64BitLimits |
| ? consume_u64v("initial size", tracer_) |
| : consume_u32v("initial size", tracer_); |
| if (initial_64 > max_initial) { |
| errorf(pos, |
| "initial %s size (%" PRIu64 |
| " %s) is larger than implementation limit (%u %s)", |
| name, initial_64, units, max_initial, units); |
| } |
| *initial = static_cast<uint32_t>(initial_64); |
| if (tracer_) { |
| tracer_->Description(*initial); |
| tracer_->NextLine(); |
| } |
| if (has_maximum) { |
| pos = pc(); |
| uint64_t maximum_64 = type == k64BitLimits |
| ? consume_u64v("maximum size", tracer_) |
| : consume_u32v("maximum size", tracer_); |
| if (maximum_64 > max_maximum) { |
| errorf(pos, |
| "maximum %s size (%" PRIu64 |
| " %s) is larger than implementation limit (%" PRIu64 " %s)", |
| name, maximum_64, units, max_maximum, units); |
| } |
| if (maximum_64 < *initial) { |
| errorf(pos, |
| "maximum %s size (%" PRIu64 " %s) is less than initial (%u %s)", |
| name, maximum_64, units, *initial, units); |
| } |
| *maximum = maximum_64; |
| if (tracer_) { |
| tracer_->Description(*maximum); |
| tracer_->NextLine(); |
| } |
| } else { |
| *maximum = max_initial; |
| } |
| } |
| |
| // Consumes a byte, and emits an error if it does not equal {expected}. |
| bool expect_u8(const char* name, uint8_t expected) { |
| const uint8_t* pos = pc(); |
| uint8_t value = consume_u8(name); |
| if (value != expected) { |
| errorf(pos, "expected %s 0x%02x, got 0x%02x", name, expected, value); |
| return false; |
| } |
| return true; |
| } |
| |
| ConstantExpression consume_init_expr(WasmModule* module, ValueType expected, |
| bool is_shared, |
| bool* ends_with_struct_new = nullptr) { |
| // The error message mimics the one generated by the {WasmFullDecoder}. |
| #define TYPE_CHECK(found) \ |
| if (V8_UNLIKELY(!IsSubtypeOf(found, expected, module))) { \ |
| errorf(pc() + 1, \ |
| "type error in constant expression[0] (expected %s, got %s)", \ |
| expected.name().c_str(), found.name().c_str()); \ |
| return {}; \ |
| } |
| |
| if (tracer_) tracer_->NextLineIfNonEmpty(); |
| // To avoid initializing a {WasmFullDecoder} for the most common |
| // expressions, we replicate their decoding and validation here. The |
| // manually handled cases correspond to {ConstantExpression}'s kinds. |
| // We need to make sure to check that the expression ends in {kExprEnd}; |
| // otherwise, it is just the first operand of a composite expression, and we |
| // fall back to the default case. |
| if (!more()) { |
| error("Beyond end of code"); |
| return {}; |
| } |
| switch (static_cast<WasmOpcode>(*pc())) { |
| case kExprI32Const: { |
| auto [value, length] = |
| read_i32v<FullValidationTag>(pc() + 1, "i32.const"); |
| if (V8_UNLIKELY(failed())) return {}; |
| if (V8_LIKELY(lookahead(1 + length, kExprEnd))) { |
| TYPE_CHECK(kWasmI32) |
| if (tracer_) { |
| tracer_->InitializerExpression(pc_, pc_ + length + 2, kWasmI32); |
| } |
| consume_bytes(length + 2); |
| return ConstantExpression::I32Const(value); |
| } |
| break; |
| } |
| case kExprRefFunc: { |
| auto [index, length] = |
| read_u32v<FullValidationTag>(pc() + 1, "ref.func"); |
| if (V8_UNLIKELY(failed())) return {}; |
| if (V8_LIKELY(lookahead(1 + length, kExprEnd))) { |
| if (V8_UNLIKELY(index >= module->functions.size())) { |
| errorf(pc() + 1, "function index %u out of bounds", index); |
| return {}; |
| } |
| ModuleTypeIndex functype{module->functions[index].sig_index}; |
| bool functype_is_shared = module->type(functype).is_shared; |
| ValueType type = ValueType::Ref(functype, functype_is_shared, |
| RefTypeKind::kFunction) |
| .AsExactIfEnabled(enabled_features_); |
| TYPE_CHECK(type) |
| if (V8_UNLIKELY(is_shared && !type.is_shared())) { |
| error(pc(), "ref.func does not have a shared type"); |
| return {}; |
| } |
| module->functions[index].declared = true; |
| if (tracer_) { |
| tracer_->InitializerExpression(pc_, pc_ + length + 2, type); |
| } |
| consume_bytes(length + 2); |
| return ConstantExpression::RefFunc(index); |
| } |
| break; |
| } |
| case kExprRefNull: { |
| auto [type, length] = |
| value_type_reader::read_heap_type<FullValidationTag>( |
| this, pc() + 1, enabled_features_, detected_features_); |
| value_type_reader::ValidateHeapType<FullValidationTag>(this, pc_, |
| module, type); |
| if (V8_UNLIKELY(failed())) return {}; |
| value_type_reader::Populate(&type, module); |
| if (V8_LIKELY(lookahead(1 + length, kExprEnd))) { |
| TYPE_CHECK(ValueType::RefNull(type)) |
| if (V8_UNLIKELY(is_shared && !type.is_shared())) { |
| error(pc(), "ref.null does not have a shared type"); |
| return {}; |
| } |
| if (tracer_) { |
| tracer_->InitializerExpression(pc_, pc_ + length + 2, |
| ValueType::RefNull(type)); |
| } |
| consume_bytes(length + 2); |
| return ConstantExpression::RefNull(type); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| #undef TYPE_CHECK |
| |
| auto sig = FixedSizeSignature<ValueType>::Returns(expected); |
| FunctionBody body(&sig, this->pc_offset(), pc_, end_, is_shared); |
| WasmDetectedFeatures detected; |
| ConstantExpression result; |
| { |
| // We need a scope for the decoder because its destructor resets some Zone |
| // elements, which has to be done before we reset the Zone afterwards. |
| WasmFullDecoder<Decoder::FullValidationTag, ConstantExpressionInterface, |
| kConstantExpression> |
| decoder(&init_expr_zone_, module, enabled_features_, &detected, body, |
| module); |
| |
| uint32_t offset = this->pc_offset(); |
| |
| decoder.DecodeFunctionBody(); |
| |
| if (tracer_) { |
| // In case of error, decoder.end() is set to the position right before |
| // the byte(s) that caused the error. For debugging purposes, we should |
| // print these bytes, but we don't know how many of them there are, so |
| // for now we have to guess. For more accurate behavior, we'd have to |
| // pass {num_invalid_bytes} to every {decoder->DecodeError()} call. |
| static constexpr size_t kInvalidBytesGuess = 4; |
| const uint8_t* end = |
| decoder.ok() ? decoder.end() |
| : std::min(decoder.end() + kInvalidBytesGuess, end_); |
| tracer_->InitializerExpression(pc_, end, expected); |
| } |
| this->pc_ = decoder.end(); |
| |
| if (decoder.failed()) { |
| error(decoder.error().offset(), decoder.error().message().c_str()); |
| return {}; |
| } |
| |
| if (!decoder.interface().end_found()) { |
| error("constant expression is missing 'end'"); |
| return {}; |
| } |
| |
| result = ConstantExpression::WireBytes( |
| offset, static_cast<uint32_t>(decoder.end() - decoder.start())); |
| |
| if (ends_with_struct_new) { |
| *ends_with_struct_new = decoder.interface().ends_with_struct_new(); |
| } |
| } |
| |
| // We reset the zone here; its memory is not used anymore, and we do not |
| // want memory from all constant expressions to add up. |
| init_expr_zone_.Reset(); |
| |
| return result; |
| } |
| |
| // Read a mutability flag |
| bool consume_mutability() { |
| if (tracer_) tracer_->Bytes(pc_, 1); |
| uint8_t val = consume_u8("mutability"); |
| if (tracer_) { |
| tracer_->Description(val == 0 ? " immutable" |
| : val == 1 ? " mutable" |
| : " invalid"); |
| } |
| if (val > 1) error(pc_ - 1, "invalid mutability"); |
| return val != 0; |
| } |
| |
| // Pass a {module} to get a pre-{Populate()}d ValueType. That's safe |
| // when the module's type section has already been fully decoded. |
| ValueType consume_value_type(const WasmModule* module = nullptr) { |
| auto [result, length] = |
| value_type_reader::read_value_type<FullValidationTag>( |
| this, pc_, |
| module_->origin == kWasmOrigin ? enabled_features_ |
| : WasmEnabledFeatures::None(), |
| detected_features_); |
| value_type_reader::ValidateValueType<FullValidationTag>( |
| this, pc_, module_.get(), result); |
| if (ok() && module) value_type_reader::Populate(&result, module); |
| if (tracer_) { |
| tracer_->Bytes(pc_, length); |
| tracer_->Description(result); |
| } |
| consume_bytes(length, "value type"); |
| return result; |
| } |
| |
| HeapType consume_heap_type() { |
| auto [heap_type, length] = |
| value_type_reader::read_heap_type<FullValidationTag>( |
| this, pc_, |
| module_->origin == kWasmOrigin ? enabled_features_ |
| : WasmEnabledFeatures::None(), |
| detected_features_); |
| |
| value_type_reader::ValidateHeapType<FullValidationTag>( |
| this, pc_, module_.get(), heap_type); |
| if (tracer_) { |
| tracer_->Bytes(pc_, length); |
| tracer_->Description(heap_type); |
| } |
| consume_bytes(length, "heap type"); |
| return heap_type; |
| } |
| |
| ValueType consume_storage_type() { |
| uint8_t opcode = read_u8<FullValidationTag>(this->pc()); |
| switch (opcode) { |
| case kI8Code: |
| consume_bytes(1, " i8", tracer_); |
| return kWasmI8; |
| case kI16Code: |
| consume_bytes(1, " i16", tracer_); |
| return kWasmI16; |
| default: |
| // It is not a packed type, so it has to be a value type. |
| return consume_value_type(); |
| } |
| } |
| |
| const FunctionSig* consume_sig(Zone* zone) { |
| if (tracer_) tracer_->NextLine(); |
| // Parse parameter types. |
| uint32_t param_count = |
| consume_count("param count", kV8MaxWasmFunctionParams); |
| // We don't know the return count yet, so decode the parameters into a |
| // temporary SmallVector. This needs to be copied over into the permanent |
| // storage later. |
| base::SmallVector<ValueType, 8> params{param_count}; |
| for (uint32_t i = 0; i < param_count; ++i) { |
| params[i] = consume_value_type(); |
| if (tracer_) tracer_->NextLineIfFull(); |
| } |
| if (tracer_) tracer_->NextLineIfNonEmpty(); |
| |
| // Parse return types. |
| uint32_t return_count = |
| consume_count("return count", kV8MaxWasmFunctionReturns); |
| if (return_count > 1) { |
| detected_features_->add_multi_value(); |
| } |
| // Now that we know the param count and the return count, we can allocate |
| // the permanent storage. |
| ValueType* sig_storage = |
| zone->AllocateArray<ValueType>(param_count + return_count); |
| // Note: Returns come first in the signature storage. |
| std::copy_n(params.begin(), param_count, sig_storage + return_count); |
| for (uint32_t i = 0; i < return_count; ++i) { |
| sig_storage[i] = consume_value_type(); |
| if (tracer_) tracer_->NextLineIfFull(); |
| } |
| if (tracer_) tracer_->NextLineIfNonEmpty(); |
| |
| return zone->New<FunctionSig>(return_count, param_count, sig_storage); |
| } |
| |
| const StructType* consume_struct(Zone* zone, bool is_descriptor, |
| bool is_shared) { |
| uint32_t field_count = |
| consume_count(", field count", kV8MaxWasmStructFields); |
| if (failed()) return nullptr; |
| ValueType* fields = zone->AllocateArray<ValueType>(field_count); |
| bool* mutabilities = zone->AllocateArray<bool>(field_count); |
| for (uint32_t i = 0; ok() && i < field_count; ++i) { |
| fields[i] = consume_storage_type(); |
| mutabilities[i] = consume_mutability(); |
| if (tracer_) tracer_->NextLine(); |
| } |
| if (failed()) return nullptr; |
| uint32_t* offsets = zone->AllocateArray<uint32_t>(field_count); |
| StructType* result = zone->New<StructType>( |
| field_count, offsets, fields, mutabilities, is_descriptor, is_shared); |
| result->InitializeOffsets(); |
| return result; |
| } |
| |
| const ArrayType* consume_array(Zone* zone) { |
| ValueType element_type = consume_storage_type(); |
| bool mutability = consume_mutability(); |
| if (tracer_) tracer_->NextLine(); |
| if (failed()) return nullptr; |
| return zone->New<ArrayType>(element_type, mutability); |
| } |
| |
| // Consume the attribute field of an exception. |
| uint32_t consume_exception_attribute() { |
| const uint8_t* pos = pc_; |
| uint32_t attribute = consume_u32v("exception attribute"); |
| if (tracer_) tracer_->Bytes(pos, static_cast<uint32_t>(pc_ - pos)); |
| if (attribute != kExceptionAttribute) { |
| errorf(pos, "exception attribute %u not supported", attribute); |
| return 0; |
| } |
| return attribute; |
| } |
| |
| WasmElemSegment consume_element_segment_header() { |
| const uint8_t* pos = pc(); |
| |
| // The mask for the bit in the flag which indicates if the segment is |
| // active or not (0 is active). |
| constexpr uint8_t kNonActiveMask = 1 << 0; |
| // The mask for the bit in the flag which indicates: |
| // - for active tables, if the segment has an explicit table index field. |
| // - for non-active tables, whether the table is declarative (vs. passive). |
| constexpr uint8_t kHasTableIndexOrIsDeclarativeMask = 1 << 1; |
| // The mask for the bit in the flag which indicates if the functions of this |
| // segment are defined as function indices (0) or constant expressions (1). |
| constexpr uint8_t kExpressionsAsElementsMask = 1 << 2; |
| // The mask for the bit which denotes whether this segment is shared. |
| constexpr uint8_t kSharedFlag = 1 << 3; |
| constexpr uint8_t kFullMask = kNonActiveMask | |
| kHasTableIndexOrIsDeclarativeMask | |
| kExpressionsAsElementsMask | kSharedFlag; |
| |
| uint32_t flag = consume_u32v("flag", tracer_); |
| if ((flag & kFullMask) != flag) { |
| errorf(pos, "illegal flag value %u", flag); |
| return {}; |
| } |
| |
| bool is_shared = flag & kSharedFlag; |
| if (is_shared && !enabled_features_.has_shared()) { |
| errorf(pos, "illegal flag value %u", flag); |
| return {}; |
| } |
| if (is_shared) { |
| // TODO(42204563): Support shared element segments. |
| error(pos, "shared element segments are not supported yet."); |
| return {}; |
| } |
| |
| if (is_shared) module_->has_shared_part = true; |
| |
| const WasmElemSegment::Status status = |
| (flag & kNonActiveMask) ? (flag & kHasTableIndexOrIsDeclarativeMask) |
| ? WasmElemSegment::kStatusDeclarative |
| : WasmElemSegment::kStatusPassive |
| : WasmElemSegment::kStatusActive; |
| const bool is_active = status == WasmElemSegment::kStatusActive; |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description(status == WasmElemSegment::kStatusActive ? "active" |
| : status == WasmElemSegment::kStatusPassive |
| ? "passive," |
| : "declarative,"); |
| } |
| |
| WasmElemSegment::ElementType element_type = |
| flag & kExpressionsAsElementsMask |
| ? WasmElemSegment::kExpressionElements |
| : WasmElemSegment::kFunctionIndexElements; |
| |
| const bool has_table_index = |
| is_active && (flag & kHasTableIndexOrIsDeclarativeMask); |
| uint32_t table_index = 0; |
| if (has_table_index) { |
| table_index = consume_u32v(", table index", tracer_); |
| if (tracer_) tracer_->Description(table_index); |
| } |
| if (V8_UNLIKELY(is_active && table_index >= module_->tables.size())) { |
| // If `has_table_index`, we have an explicit table index. Otherwise, we |
| // always have the implicit table index 0. |
| errorf(pos, "out of bounds%s table index %u", |
| has_table_index ? "" : " implicit", table_index); |
| return {}; |
| } |
| |
| ValueType table_type = |
| is_active ? module_->tables[table_index].type : kWasmBottom; |
| |
| ConstantExpression offset; |
| if (is_active) { |
| if (tracer_) { |
| tracer_->Description(", offset:"); |
| tracer_->NextLine(); |
| } |
| offset = consume_init_expr( |
| module_.get(), |
| module_->tables[table_index].is_table64() ? kWasmI64 : kWasmI32, |
| is_shared); |
| // Failed to parse offset initializer, return early. |
| if (failed()) return {}; |
| } |
| |
| // Denotes an active segment without table index, type, or element kind. |
| const bool backwards_compatible_mode = |
| is_active && !(flag & kHasTableIndexOrIsDeclarativeMask); |
| ValueType type; |
| if (element_type == WasmElemSegment::kExpressionElements) { |
| if (backwards_compatible_mode) { |
| type = kWasmFuncRef; |
| } else { |
| if (tracer_) tracer_->Description(" element type:"); |
| type = consume_value_type(module_.get()); |
| if (failed()) return {}; |
| } |
| } else { |
| if (!backwards_compatible_mode) { |
| // We have to check that there is an element kind of type Function. All |
| // other element kinds are not valid yet. |
| if (tracer_) tracer_->Description(" "); |
| uint8_t val = consume_u8("element type: function", tracer_); |
| if (V8_UNLIKELY(static_cast<ImportExportKindCode>(val) != |
| kExternalFunction)) { |
| errorf(pos, "illegal element kind 0x%x. Must be 0x%x", val, |
| kExternalFunction); |
| return {}; |
| } |
| } |
| type = kWasmFuncRef.AsNonNull(); |
| } |
| |
| if (V8_UNLIKELY(is_active && |
| !IsSubtypeOf(type, table_type, this->module_.get()))) { |
| errorf(pos, |
| "Element segment of type %s is not a subtype of referenced " |
| "table %u (of type %s)", |
| type.name().c_str(), table_index, table_type.name().c_str()); |
| return {}; |
| } |
| |
| // TODO(14616): Is this too restrictive? |
| if (V8_UNLIKELY(is_active && |
| (is_shared != module_->tables[table_index].shared))) { |
| error(pos, |
| "Shared (resp. non-shared) element segments must refer to shared " |
| "(resp. non-shared) tables"); |
| return {}; |
| } |
| |
| uint32_t num_elem = |
| consume_count(" number of elements", max_table_init_entries()); |
| |
| if (is_active) { |
| return {is_shared, type, table_index, std::move(offset), |
| element_type, num_elem, pc_offset()}; |
| } else { |
| return {status, is_shared, type, element_type, num_elem, pc_offset()}; |
| } |
| } |
| |
| DataSegmentHeader consume_data_segment_header() { |
| const uint8_t* pos = pc(); |
| uint32_t flag = consume_u32v("flag", tracer_); |
| |
| if (flag & ~0b1011) { |
| errorf(pos, "illegal flag value %u", flag); |
| return {}; |
| } |
| |
| uint32_t status_flag = flag & 0b11; |
| |
| if (tracer_) { |
| tracer_->Description(": "); |
| tracer_->Description( |
| status_flag == SegmentFlags::kActiveNoIndex ? "active no index" |
| : status_flag == SegmentFlags::kPassive ? "passive" |
| : status_flag == SegmentFlags::kActiveWithIndex ? "active with index" |
| : "unknown"); |
| } |
| |
| if (status_flag != SegmentFlags::kActiveNoIndex && |
| status_flag != SegmentFlags::kPassive && |
| status_flag != SegmentFlags::kActiveWithIndex) { |
| errorf(pos, "illegal flag value %u", flag); |
| return {}; |
| } |
| |
| bool is_shared = flag & 0b1000; |
| |
| if (V8_UNLIKELY(is_shared && !enabled_features_.has_shared())) { |
| errorf(pos, "illegal flag value %u.", flag); |
| return {}; |
| } |
| if (V8_UNLIKELY(is_shared)) { |
| // TODO(42204563): Support shared data segments. |
| error(pos, "shared data segments are not supported yet."); |
| return {}; |
| } |
| |
| if (is_shared) module_->has_shared_part = true; |
| |
| if (tracer_) { |
| if (is_shared) tracer_->Description(" shared"); |
| tracer_->NextLine(); |
| } |
| |
| bool is_active = status_flag == SegmentFlags::kActiveNoIndex || |
| status_flag == SegmentFlags::kActiveWithIndex; |
| uint32_t mem_index = status_flag == SegmentFlags::kActiveWithIndex |
| ? consume_u32v("memory index", tracer_) |
| : 0; |
| ConstantExpression offset; |
| |
| if (is_active) { |
| size_t num_memories = module_->memories.size(); |
| if (mem_index >= num_memories) { |
| errorf(pos, |
| "invalid memory index %u for data section (having %zu memor%s)", |
| mem_index, num_memories, num_memories == 1 ? "y" : "ies"); |
| return {}; |
| } |
| ValueType expected_type = |
| module_->memories[mem_index].is_memory64() ? kWasmI64 : kWasmI32; |
| offset = consume_init_expr(module_.get(), expected_type, is_shared); |
| } |
| |
| return {is_active, is_shared, mem_index, offset}; |
| } |
| |
| uint32_t consume_element_func_index(WasmModule* module, ValueType expected) { |
| WasmFunction* func = nullptr; |
| const uint8_t* initial_pc = pc(); |
| uint32_t index = consume_func_index(module, &func); |
| if (tracer_) tracer_->NextLine(); |
| if (failed()) return index; |
| DCHECK_NOT_NULL(func); |
| DCHECK_EQ(index, func->func_index); |
| ValueType entry_type = |
| ValueType::Ref(func->sig_index, module->type(func->sig_index).is_shared, |
| RefTypeKind::kFunction); |
| if (V8_LIKELY(expected == kWasmFuncRef && |
| !enabled_features_.has_shared())) { |
| DCHECK(module->type(func->sig_index).kind == TypeDefinition::kFunction); |
| DCHECK(IsSubtypeOf(entry_type, expected, module)); |
| } else if (V8_UNLIKELY(!IsSubtypeOf(entry_type, expected, module))) { |
| errorf(initial_pc, |
| "Invalid type in element entry: expected %s, got %s instead.", |
| expected.name().c_str(), entry_type.name().c_str()); |
| return index; |
| } |
| func->declared = true; |
| return index; |
| } |
| |
| const WasmEnabledFeatures enabled_features_; |
| WasmDetectedFeatures* const detected_features_; |
| const std::shared_ptr<WasmModule> module_; |
| const uint8_t* module_start_ = nullptr; |
| const uint8_t* module_end_ = nullptr; |
| ITracer* tracer_; |
| // The type section is the first section in a module. |
| uint8_t next_ordered_section_ = kFirstSectionInModule; |
| // We store next_ordered_section_ as uint8_t instead of SectionCode so that |
| // we can increment it. This static_assert should make sure that SectionCode |
| // does not get bigger than uint8_t accidentally. |
| static_assert(sizeof(ModuleDecoderImpl::next_ordered_section_) == |
| sizeof(SectionCode), |
| "type mismatch"); |
| uint32_t seen_unordered_sections_ = 0; |
| static_assert(kBitsPerByte * |
| sizeof(ModuleDecoderImpl::seen_unordered_sections_) > |
| kLastKnownModuleSection, |
| "not enough bits"); |
| AccountingAllocator allocator_; |
| // We pass this {Zone} to the temporary {WasmFullDecoder} we allocate during |
| // each call to {consume_init_expr}, and reset it after each such call. This |
| // has been found to improve performance a bit over allocating a new {Zone} |
| // each time. |
| Zone init_expr_zone_{&allocator_, "constant expr. zone"}; |
| |
| // Instruction traces are decoded in DecodeInstTraceSection as a 3-tuple |
| // of the function index, function offset, and mark_id. In DecodeCodeSection, |
| // after the functions have been decoded this is translated to pairs of module |
| // offsets and mark ids. |
| std::vector<std::tuple<uint32_t, uint32_t, uint32_t>> inst_traces_; |
| }; |
| |
| } // namespace v8::internal::wasm |
| |
| #undef TRACE |
| |
| #endif // V8_WASM_MODULE_DECODER_IMPL_H_ |