/* * Copyright 2017 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wabt/wast-parser.h" #include "wabt/binary-reader-ir.h" #include "wabt/binary-reader.h" #include "wabt/cast.h" #include "wabt/expr-visitor.h" #include "wabt/resolve-names.h" #include "wabt/stream.h" #include "wabt/utf8.h" #include "wabt/validator.h" #define WABT_TRACING 0 #include "wabt/tracing.h" #define EXPECT(token_type) CHECK_RESULT(Expect(TokenType::token_type)) namespace wabt { namespace { static const size_t kMaxErrorTokenLength = 80; bool IsPowerOfTwo(uint32_t x) { return x && ((x & (x - 1)) == 0); } template void RemoveEscapes(std::string_view text, OutputIter dest) { // Remove surrounding quotes; if any. This may be empty if the string was // invalid (e.g. if it contained a bad escape sequence). if (text.size() <= 2) { return; } text = text.substr(1, text.size() - 2); const char* src = text.data(); const char* end = text.data() + text.size(); while (src < end) { if (*src == '\\') { src++; switch (*src) { case 'n': *dest++ = '\n'; break; case 'r': *dest++ = '\r'; break; case 't': *dest++ = '\t'; break; case '\\': *dest++ = '\\'; break; case '\'': *dest++ = '\''; break; case '\"': *dest++ = '\"'; break; case 'u': { // The string should be validated already, // so this must be a valid unicode escape sequence. uint32_t digit; uint32_t scalar_value = 0; // Skip u and { characters. src += 2; do { if (Succeeded(ParseHexdigit(src[0], &digit))) { scalar_value = (scalar_value << 4) | digit; } else { assert(0); } src++; } while (src[0] != '}'); // Maximum value of a unicode scalar value assert(scalar_value < 0x110000); // Encode the unicode scalar value as UTF8 sequence if (scalar_value < 0x80) { *dest++ = static_cast(scalar_value); } else { if (scalar_value < 0x800) { *dest++ = static_cast(0xc0 | (scalar_value >> 6)); } else { if (scalar_value < 0x10000) { *dest++ = static_cast(0xe0 | (scalar_value >> 12)); } else { *dest++ = static_cast(0xf0 | (scalar_value >> 18)); *dest++ = static_cast(0x80 | ((scalar_value >> 12) & 0x3f)); } *dest++ = static_cast(0x80 | ((scalar_value >> 6) & 0x3f)); } *dest++ = static_cast(0x80 | (scalar_value & 0x3f)); } break; } default: { // The string should be validated already, so we know this is a hex // sequence. uint32_t hi; uint32_t lo; if (Succeeded(ParseHexdigit(src[0], &hi)) && Succeeded(ParseHexdigit(src[1], &lo))) { *dest++ = (hi << 4) | lo; } else { assert(0); } src++; break; } } src++; } else { *dest++ = *src++; } } } using TextVector = std::vector; template void RemoveEscapes(const TextVector& texts, OutputIter out) { for (std::string_view text : texts) RemoveEscapes(text, out); } bool IsPlainInstr(TokenType token_type) { switch (token_type) { case TokenType::Unreachable: case TokenType::Nop: case TokenType::Drop: case TokenType::Select: case TokenType::Br: case TokenType::BrIf: case TokenType::BrTable: case TokenType::Return: case TokenType::ReturnCall: case TokenType::ReturnCallIndirect: case TokenType::Call: case TokenType::CallIndirect: case TokenType::CallRef: case TokenType::LocalGet: case TokenType::LocalSet: case TokenType::LocalTee: case TokenType::GlobalGet: case TokenType::GlobalSet: case TokenType::Load: case TokenType::Store: case TokenType::Const: case TokenType::Unary: case TokenType::Binary: case TokenType::Compare: case TokenType::Convert: case TokenType::MemoryCopy: case TokenType::DataDrop: case TokenType::MemoryFill: case TokenType::MemoryGrow: case TokenType::MemoryInit: case TokenType::MemorySize: case TokenType::TableCopy: case TokenType::ElemDrop: case TokenType::TableInit: case TokenType::TableGet: case TokenType::TableSet: case TokenType::TableGrow: case TokenType::TableSize: case TokenType::TableFill: case TokenType::Throw: case TokenType::Rethrow: case TokenType::RefFunc: case TokenType::RefNull: case TokenType::RefIsNull: case TokenType::AtomicLoad: case TokenType::AtomicStore: case TokenType::AtomicRmw: case TokenType::AtomicRmwCmpxchg: case TokenType::AtomicNotify: case TokenType::AtomicFence: case TokenType::AtomicWait: case TokenType::Ternary: case TokenType::SimdLaneOp: case TokenType::SimdLoadLane: case TokenType::SimdStoreLane: case TokenType::SimdShuffleOp: return true; default: return false; } } bool IsBlockInstr(TokenType token_type) { switch (token_type) { case TokenType::Block: case TokenType::Loop: case TokenType::If: case TokenType::Try: return true; default: return false; } } bool IsPlainOrBlockInstr(TokenType token_type) { return IsPlainInstr(token_type) || IsBlockInstr(token_type); } bool IsExpr(TokenTypePair pair) { return pair[0] == TokenType::Lpar && IsPlainOrBlockInstr(pair[1]); } bool IsInstr(TokenTypePair pair) { return IsPlainOrBlockInstr(pair[0]) || IsExpr(pair); } bool IsLparAnn(TokenTypePair pair) { return pair[0] == TokenType::LparAnn; } bool IsCatch(TokenType token_type) { return token_type == TokenType::Catch || token_type == TokenType::CatchAll; } bool IsModuleField(TokenTypePair pair) { if (pair[0] != TokenType::Lpar) { return false; } switch (pair[1]) { case TokenType::Data: case TokenType::Elem: case TokenType::Tag: case TokenType::Export: case TokenType::Func: case TokenType::Type: case TokenType::Global: case TokenType::Import: case TokenType::Memory: case TokenType::Start: case TokenType::Table: return true; default: return false; } } bool IsCommand(TokenTypePair pair) { if (pair[0] != TokenType::Lpar) { return false; } switch (pair[1]) { case TokenType::AssertException: case TokenType::AssertExhaustion: case TokenType::AssertInvalid: case TokenType::AssertMalformed: case TokenType::AssertReturn: case TokenType::AssertTrap: case TokenType::AssertUnlinkable: case TokenType::Get: case TokenType::Invoke: case TokenType::Input: case TokenType::Module: case TokenType::Output: case TokenType::Register: return true; default: return false; } } bool IsEmptySignature(const FuncSignature& sig) { return sig.result_types.empty() && sig.param_types.empty(); } bool ResolveFuncTypeWithEmptySignature(const Module& module, FuncDeclaration* decl) { // Resolve func type variables where the signature was not specified // explicitly, e.g.: (func (type 1) ...) if (decl->has_func_type && IsEmptySignature(decl->sig)) { const FuncType* func_type = module.GetFuncType(decl->type_var); if (func_type) { decl->sig = func_type->sig; return true; } } return false; } void ResolveTypeName( const Module& module, Type& type, Index index, const std::unordered_map& bindings) { if (type != Type::Reference || type.GetReferenceIndex() != kInvalidIndex) { return; } const auto name_iterator = bindings.find(index); assert(name_iterator != bindings.cend()); const auto type_index = module.type_bindings.FindIndex(name_iterator->second); assert(type_index != kInvalidIndex); type = Type(Type::Reference, type_index); } void ResolveTypeNames(const Module& module, FuncDeclaration* decl) { assert(decl); auto& signature = decl->sig; for (uint32_t param_index = 0; param_index < signature.GetNumParams(); ++param_index) { ResolveTypeName(module, signature.param_types[param_index], param_index, signature.param_type_names); } for (uint32_t result_index = 0; result_index < signature.GetNumResults(); ++result_index) { ResolveTypeName(module, signature.result_types[result_index], result_index, signature.result_type_names); } } void ResolveImplicitlyDefinedFunctionType(const Location& loc, Module* module, const FuncDeclaration& decl) { // Resolve implicitly defined function types, e.g.: (func (param i32) ...) if (!decl.has_func_type) { Index func_type_index = module->GetFuncTypeIndex(decl.sig); if (func_type_index == kInvalidIndex) { auto func_type_field = std::make_unique(loc); auto func_type = std::make_unique(); func_type->sig = decl.sig; func_type_field->type = std::move(func_type); module->AppendField(std::move(func_type_field)); } } } Result CheckTypeIndex(const Location& loc, Type actual, Type expected, const char* desc, Index index, const char* index_kind, Errors* errors) { // Types must match exactly; no subtyping should be allowed. if (actual != expected) { errors->emplace_back( ErrorLevel::Error, loc, StringPrintf("type mismatch for %s %" PRIindex " of %s. got %s, expected %s", index_kind, index, desc, actual.GetName().c_str(), expected.GetName().c_str())); return Result::Error; } return Result::Ok; } Result CheckTypes(const Location& loc, const TypeVector& actual, const TypeVector& expected, const char* desc, const char* index_kind, Errors* errors) { Result result = Result::Ok; if (actual.size() == expected.size()) { for (size_t i = 0; i < actual.size(); ++i) { result |= CheckTypeIndex(loc, actual[i], expected[i], desc, i, index_kind, errors); } } else { errors->emplace_back( ErrorLevel::Error, loc, StringPrintf("expected %" PRIzd " %ss, got %" PRIzd, expected.size(), index_kind, actual.size())); result = Result::Error; } return result; } Result CheckFuncTypeVarMatchesExplicit(const Location& loc, const Module& module, const FuncDeclaration& decl, Errors* errors) { Result result = Result::Ok; if (decl.has_func_type) { const FuncType* func_type = module.GetFuncType(decl.type_var); if (func_type) { result |= CheckTypes(loc, decl.sig.result_types, func_type->sig.result_types, "function", "result", errors); result |= CheckTypes(loc, decl.sig.param_types, func_type->sig.param_types, "function", "argument", errors); } else if (!(decl.sig.param_types.empty() && decl.sig.result_types.empty())) { // We want to check whether the function type at the explicit index // matches the given param and result types. If they were omitted then // they'll be resolved automatically (see // ResolveFuncTypeWithEmptySignature), but if they are provided then we // have to check. If we get here then the type var is invalid, so we // can't check whether they match. if (decl.type_var.is_index()) { errors->emplace_back(ErrorLevel::Error, loc, StringPrintf("invalid func type index %" PRIindex, decl.type_var.index())); } else { errors->emplace_back(ErrorLevel::Error, loc, StringPrintf("expected func type identifier %s", decl.type_var.name().c_str())); } result = Result::Error; } } return result; } bool IsInlinableFuncSignature(const FuncSignature& sig) { return sig.GetNumParams() == 0 && sig.GetNumResults() <= 1; } class ResolveFuncTypesExprVisitorDelegate : public ExprVisitor::DelegateNop { public: explicit ResolveFuncTypesExprVisitorDelegate(Module* module, Errors* errors) : module_(module), errors_(errors) {} void ResolveBlockDeclaration(const Location& loc, BlockDeclaration* decl) { ResolveTypeNames(*module_, decl); ResolveFuncTypeWithEmptySignature(*module_, decl); if (!IsInlinableFuncSignature(decl->sig)) { ResolveImplicitlyDefinedFunctionType(loc, module_, *decl); } } Result BeginBlockExpr(BlockExpr* expr) override { ResolveBlockDeclaration(expr->loc, &expr->block.decl); return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->block.decl, errors_); } Result BeginIfExpr(IfExpr* expr) override { ResolveBlockDeclaration(expr->loc, &expr->true_.decl); return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->true_.decl, errors_); } Result BeginLoopExpr(LoopExpr* expr) override { ResolveBlockDeclaration(expr->loc, &expr->block.decl); return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->block.decl, errors_); } Result BeginTryExpr(TryExpr* expr) override { ResolveBlockDeclaration(expr->loc, &expr->block.decl); return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->block.decl, errors_); } Result OnCallIndirectExpr(CallIndirectExpr* expr) override { ResolveFuncTypeWithEmptySignature(*module_, &expr->decl); ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl); return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl, errors_); } Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr* expr) override { ResolveFuncTypeWithEmptySignature(*module_, &expr->decl); ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl); return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl, errors_); } private: Module* module_; Errors* errors_; }; Result ResolveFuncTypes(Module* module, Errors* errors) { Result result = Result::Ok; for (ModuleField& field : module->fields) { Func* func = nullptr; FuncDeclaration* decl = nullptr; if (auto* func_field = dyn_cast(&field)) { func = &func_field->func; decl = &func->decl; } else if (auto* tag_field = dyn_cast(&field)) { decl = &tag_field->tag.decl; } else if (auto* import_field = dyn_cast(&field)) { if (auto* func_import = dyn_cast(import_field->import.get())) { // Only check the declaration, not the function itself, since it is an // import. decl = &func_import->func.decl; } else if (auto* tag_import = dyn_cast(import_field->import.get())) { decl = &tag_import->tag.decl; } else { continue; } } else { continue; } bool has_func_type_and_empty_signature = false; if (decl) { ResolveTypeNames(*module, decl); has_func_type_and_empty_signature = ResolveFuncTypeWithEmptySignature(*module, decl); ResolveImplicitlyDefinedFunctionType(field.loc, module, *decl); result |= CheckFuncTypeVarMatchesExplicit(field.loc, *module, *decl, errors); } if (func) { if (has_func_type_and_empty_signature) { // The call to ResolveFuncTypeWithEmptySignature may have updated the // function signature so there are parameters. Since parameters and // local variables share the same index space, we need to increment the // local indexes bound to a given name by the number of parameters in // the function. for (auto& [name, binding] : func->bindings) { binding.index += func->GetNumParams(); } } ResolveFuncTypesExprVisitorDelegate delegate(module, errors); ExprVisitor visitor(&delegate); result |= visitor.VisitFunc(func); } } return result; } void AppendInlineExportFields(Module* module, ModuleFieldList* fields, Index index) { Location last_field_loc = module->fields.back().loc; for (ModuleField& field : *fields) { auto* export_field = cast(&field); export_field->export_.var = Var(index, last_field_loc); } module->AppendFields(fields); } } // End of anonymous namespace WastParser::WastParser(WastLexer* lexer, Errors* errors, WastParseOptions* options) : lexer_(lexer), errors_(errors), options_(options) {} void WastParser::Error(Location loc, const char* format, ...) { WABT_SNPRINTF_ALLOCA(buffer, length, format); errors_->emplace_back(ErrorLevel::Error, loc, buffer); } Token WastParser::GetToken() { if (tokens_.empty()) { tokens_.push_back(lexer_->GetToken()); } return tokens_.front(); } Location WastParser::GetLocation() { return GetToken().loc; } TokenType WastParser::Peek(size_t n) { while (tokens_.size() <= n) { Token cur = lexer_->GetToken(); if (cur.token_type() != TokenType::LparAnn) { tokens_.push_back(cur); } else { // Custom annotation. For now, discard until matching Rpar, unless it is // a code metadata annotation. In that case, we know how to parse it. if (!options_->features.annotations_enabled()) { Error(cur.loc, "annotations not enabled: %s", cur.to_string().c_str()); tokens_.push_back(Token(cur.loc, TokenType::Invalid)); continue; } if (options_->features.code_metadata_enabled() && cur.text().find("metadata.code.") == 0) { tokens_.push_back(cur); continue; } int indent = 1; while (indent > 0) { cur = lexer_->GetToken(); switch (cur.token_type()) { case TokenType::Lpar: case TokenType::LparAnn: indent++; break; case TokenType::Rpar: indent--; break; case TokenType::Eof: indent = 0; Error(cur.loc, "unterminated annotation"); break; default: break; } } } } return tokens_.at(n).token_type(); } TokenTypePair WastParser::PeekPair() { return TokenTypePair{{Peek(), Peek(1)}}; } bool WastParser::PeekMatch(TokenType type, size_t n) { return Peek(n) == type; } bool WastParser::PeekMatchLpar(TokenType type) { return Peek() == TokenType::Lpar && Peek(1) == type; } bool WastParser::PeekMatchExpr() { return IsExpr(PeekPair()); } bool WastParser::PeekMatchRefType() { return options_->features.function_references_enabled() && PeekMatchLpar(TokenType::Ref); } bool WastParser::Match(TokenType type) { if (PeekMatch(type)) { Consume(); return true; } return false; } bool WastParser::MatchLpar(TokenType type) { if (PeekMatchLpar(type)) { Consume(); Consume(); return true; } return false; } Result WastParser::Expect(TokenType type) { if (!Match(type)) { Token token = Consume(); Error(token.loc, "unexpected token %s, expected %s.", token.to_string_clamp(kMaxErrorTokenLength).c_str(), GetTokenTypeName(type)); return Result::Error; } return Result::Ok; } Token WastParser::Consume() { assert(!tokens_.empty()); Token token = tokens_.front(); tokens_.pop_front(); return token; } Result WastParser::Synchronize(SynchronizeFunc func) { static const int kMaxConsumed = 10; for (int i = 0; i < kMaxConsumed; ++i) { if (func(PeekPair())) { return Result::Ok; } Token token = Consume(); if (token.token_type() == TokenType::Reserved) { Error(token.loc, "unexpected token %s.", token.to_string_clamp(kMaxErrorTokenLength).c_str()); } } return Result::Error; } void WastParser::ErrorUnlessOpcodeEnabled(const Token& token) { Opcode opcode = token.opcode(); if (!opcode.IsEnabled(options_->features)) { Error(token.loc, "opcode not allowed: %s", opcode.GetName()); } } Result WastParser::ErrorExpected(const std::vector& expected, const char* example) { Token token = Consume(); std::string expected_str; if (!expected.empty()) { expected_str = ", expected "; for (size_t i = 0; i < expected.size(); ++i) { if (i != 0) { if (i == expected.size() - 1) { expected_str += " or "; } else { expected_str += ", "; } } expected_str += expected[i]; } if (example) { expected_str += " (e.g. "; expected_str += example; expected_str += ")"; } } Error(token.loc, "unexpected token \"%s\"%s.", token.to_string_clamp(kMaxErrorTokenLength).c_str(), expected_str.c_str()); return Result::Error; } Result WastParser::ErrorIfLpar(const std::vector& expected, const char* example) { if (Match(TokenType::Lpar)) { GetToken(); return ErrorExpected(expected, example); } return Result::Ok; } bool WastParser::ParseBindVarOpt(std::string* name) { WABT_TRACE(ParseBindVarOpt); if (!PeekMatch(TokenType::Var)) { return false; } Token token = Consume(); *name = std::string(token.text()); return true; } Result WastParser::ParseVar(Var* out_var) { WABT_TRACE(ParseVar); if (PeekMatch(TokenType::Nat)) { Token token = Consume(); std::string_view sv = token.literal().text; uint64_t index = kInvalidIndex; if (Failed(ParseUint64(sv, &index))) { // Print an error, but don't fail parsing. Error(token.loc, "invalid int \"" PRIstringview "\"", WABT_PRINTF_STRING_VIEW_ARG(sv)); } *out_var = Var(index, token.loc); return Result::Ok; } else if (PeekMatch(TokenType::Var)) { Token token = Consume(); *out_var = Var(token.text(), token.loc); return Result::Ok; } else { return ErrorExpected({"a numeric index", "a name"}, "12 or $foo"); } } bool WastParser::ParseVarOpt(Var* out_var, Var default_var) { WABT_TRACE(ParseVarOpt); if (PeekMatch(TokenType::Nat) || PeekMatch(TokenType::Var)) { Result result = ParseVar(out_var); // Should always succeed, the only way it could fail is if the token // doesn't match. assert(Succeeded(result)); WABT_USE(result); return true; } else { *out_var = default_var; return false; } } Result WastParser::ParseOffsetExpr(ExprList* out_expr_list) { WABT_TRACE(ParseOffsetExpr); if (!ParseOffsetExprOpt(out_expr_list)) { return ErrorExpected({"an offset expr"}, "(i32.const 123)"); } return Result::Ok; } bool WastParser::ParseOffsetExprOpt(ExprList* out_expr_list) { WABT_TRACE(ParseOffsetExprOpt); if (MatchLpar(TokenType::Offset)) { CHECK_RESULT(ParseTerminatingInstrList(out_expr_list)); EXPECT(Rpar); return true; } else if (PeekMatchExpr()) { CHECK_RESULT(ParseExpr(out_expr_list)); return true; } else { return false; } } Result WastParser::ParseTextList(std::vector* out_data) { WABT_TRACE(ParseTextList); if (!ParseTextListOpt(out_data)) { // TODO(binji): Add error message here. return Result::Error; } return Result::Ok; } bool WastParser::ParseTextListOpt(std::vector* out_data) { WABT_TRACE(ParseTextListOpt); TextVector texts; while (PeekMatch(TokenType::Text)) texts.push_back(Consume().text()); RemoveEscapes(texts, std::back_inserter(*out_data)); return !texts.empty(); } Result WastParser::ParseVarList(VarVector* out_var_list) { WABT_TRACE(ParseVarList); Var var; while (ParseVarOpt(&var)) { out_var_list->emplace_back(var); } if (out_var_list->empty()) { return ErrorExpected({"a var"}, "12 or $foo"); } else { return Result::Ok; } } bool WastParser::ParseElemExprOpt(ExprList* out_elem_expr) { WABT_TRACE(ParseElemExprOpt); bool item = MatchLpar(TokenType::Item); ExprList exprs; if (item) { if (ParseTerminatingInstrList(&exprs) != Result::Ok) { return false; } EXPECT(Rpar); } else { if (ParseExpr(&exprs) != Result::Ok) { return false; } } if (!exprs.size()) { return false; } *out_elem_expr = std::move(exprs); return true; } bool WastParser::ParseElemExprListOpt(ExprListVector* out_list) { ExprList elem_expr; while (ParseElemExprOpt(&elem_expr)) { out_list->push_back(std::move(elem_expr)); } return !out_list->empty(); } bool WastParser::ParseElemExprVarListOpt(ExprListVector* out_list) { WABT_TRACE(ParseElemExprVarListOpt); Var var; ExprList init_expr; while (ParseVarOpt(&var)) { init_expr.push_back(std::make_unique(var)); out_list->push_back(std::move(init_expr)); } return !out_list->empty(); } Result WastParser::ParseValueType(Var* out_type) { WABT_TRACE(ParseValueType); const bool is_ref_type = PeekMatchRefType(); const bool is_value_type = PeekMatch(TokenType::ValueType); if (!is_value_type && !is_ref_type) { return ErrorExpected( {"i32", "i64", "f32", "f64", "v128", "externref", "funcref"}); } if (is_ref_type) { EXPECT(Lpar); EXPECT(Ref); CHECK_RESULT(ParseVar(out_type)); EXPECT(Rpar); return Result::Ok; } Token token = Consume(); Type type = token.type(); bool is_enabled; switch (type) { case Type::V128: is_enabled = options_->features.simd_enabled(); break; case Type::FuncRef: case Type::ExternRef: is_enabled = options_->features.reference_types_enabled(); break; default: is_enabled = true; break; } if (!is_enabled) { Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); return Result::Error; } *out_type = Var(type, GetLocation()); return Result::Ok; } Result WastParser::ParseValueTypeList( TypeVector* out_type_list, std::unordered_map* type_names) { WABT_TRACE(ParseValueTypeList); while (true) { if (!PeekMatchRefType() && !PeekMatch(TokenType::ValueType)) { break; } Var type; CHECK_RESULT(ParseValueType(&type)); if (type.is_index()) { out_type_list->push_back(Type(type.index())); } else { assert(type.is_name()); assert(options_->features.function_references_enabled()); type_names->emplace(out_type_list->size(), type.name()); out_type_list->push_back(Type(Type::Reference, kInvalidIndex)); } } return Result::Ok; } Result WastParser::ParseRefKind(Type* out_type) { WABT_TRACE(ParseRefKind); if (!IsTokenTypeRefKind(Peek())) { return ErrorExpected({"func", "extern", "exn"}); } Token token = Consume(); Type type = token.type(); if ((type == Type::ExternRef && !options_->features.reference_types_enabled()) || ((type == Type::Struct || type == Type::Array) && !options_->features.gc_enabled())) { Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); return Result::Error; } *out_type = type; return Result::Ok; } Result WastParser::ParseRefType(Type* out_type) { WABT_TRACE(ParseRefType); if (!PeekMatch(TokenType::ValueType)) { return ErrorExpected({"funcref", "externref"}); } Token token = Consume(); Type type = token.type(); if (type == Type::ExternRef && !options_->features.reference_types_enabled()) { Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); return Result::Error; } *out_type = type; return Result::Ok; } bool WastParser::ParseRefTypeOpt(Type* out_type) { WABT_TRACE(ParseRefTypeOpt); if (!PeekMatch(TokenType::ValueType)) { return false; } Token token = Consume(); Type type = token.type(); if (type == Type::ExternRef && !options_->features.reference_types_enabled()) { return false; } *out_type = type; return true; } Result WastParser::ParseQuotedText(std::string* text, bool check_utf8) { WABT_TRACE(ParseQuotedText); if (!PeekMatch(TokenType::Text)) { return ErrorExpected({"a quoted string"}, "\"foo\""); } Token token = Consume(); RemoveEscapes(token.text(), std::back_inserter(*text)); if (check_utf8 && !IsValidUtf8(text->data(), text->length())) { Error(token.loc, "quoted string has an invalid utf-8 encoding"); } return Result::Ok; } bool WastParser::ParseOffsetOpt(Address* out_offset) { WABT_TRACE(ParseOffsetOpt); if (PeekMatch(TokenType::OffsetEqNat)) { Token token = Consume(); uint64_t offset64; std::string_view sv = token.text(); if (Failed(ParseInt64(sv, &offset64, ParseIntType::SignedAndUnsigned))) { Error(token.loc, "invalid offset \"" PRIstringview "\"", WABT_PRINTF_STRING_VIEW_ARG(sv)); } // FIXME: make this depend on the current memory. if (offset64 > UINT32_MAX) { Error(token.loc, "offset must be less than or equal to 0xffffffff"); } *out_offset = offset64; return true; } else { *out_offset = 0; return false; } } bool WastParser::ParseAlignOpt(Address* out_align) { WABT_TRACE(ParseAlignOpt); if (PeekMatch(TokenType::AlignEqNat)) { Token token = Consume(); std::string_view sv = token.text(); if (Failed(ParseInt64(sv, out_align, ParseIntType::UnsignedOnly))) { Error(token.loc, "invalid alignment \"" PRIstringview "\"", WABT_PRINTF_STRING_VIEW_ARG(sv)); } if (!IsPowerOfTwo(*out_align)) { Error(token.loc, "alignment must be power-of-two"); } return true; } else { *out_align = WABT_USE_NATURAL_ALIGNMENT; return false; } } Result WastParser::ParseMemidx(Location loc, Var* out_memidx) { WABT_TRACE(ParseMemidx); if (PeekMatchLpar(TokenType::Memory)) { if (!options_->features.multi_memory_enabled()) { Error(loc, "Specifying memory variable is not allowed"); return Result::Error; } EXPECT(Lpar); EXPECT(Memory); CHECK_RESULT(ParseVar(out_memidx)); EXPECT(Rpar); } else { if (ParseVarOpt(out_memidx, Var(0, loc)) && !options_->features.multi_memory_enabled()) { Error(loc, "Specifying memory variable is not allowed"); return Result::Error; } } return Result::Ok; } Result WastParser::ParseLimitsIndex(Limits* out_limits) { WABT_TRACE(ParseLimitsIndex); if (PeekMatch(TokenType::ValueType)) { if (GetToken().type() == Type::I64) { Consume(); out_limits->is_64 = true; } else if (GetToken().type() == Type::I32) { Consume(); out_limits->is_64 = false; } } return Result::Ok; } Result WastParser::ParseLimits(Limits* out_limits) { WABT_TRACE(ParseLimits); CHECK_RESULT(ParseNat(&out_limits->initial, out_limits->is_64)); if (PeekMatch(TokenType::Nat)) { CHECK_RESULT(ParseNat(&out_limits->max, out_limits->is_64)); out_limits->has_max = true; } else { out_limits->has_max = false; } if (Match(TokenType::Shared)) { out_limits->is_shared = true; } return Result::Ok; } Result WastParser::ParseNat(uint64_t* out_nat, bool is_64) { WABT_TRACE(ParseNat); if (!PeekMatch(TokenType::Nat)) { return ErrorExpected({"a natural number"}, "123"); } Token token = Consume(); std::string_view sv = token.literal().text; if (Failed(ParseUint64(sv, out_nat)) || (!is_64 && *out_nat > 0xffffffffu)) { Error(token.loc, "invalid int \"" PRIstringview "\"", WABT_PRINTF_STRING_VIEW_ARG(sv)); } return Result::Ok; } Result WastParser::ParseModule(std::unique_ptr* out_module) { WABT_TRACE(ParseModule); auto module = std::make_unique(); if (PeekMatchLpar(TokenType::Module)) { // Starts with "(module". Allow text and binary modules, but no quoted // modules. CommandPtr command; CHECK_RESULT(ParseModuleCommand(nullptr, &command)); if (isa(command.get())) { auto module_command = cast(std::move(command)); *module = std::move(module_command->module); } else { assert(isa(command.get())); auto module_command = cast(std::move(command)); *module = std::move(module_command->module); } } else if (IsModuleField(PeekPair())) { // Parse an inline module (i.e. one with no surrounding (module)). CHECK_RESULT(ParseModuleFieldList(module.get())); } else { ConsumeIfLpar(); ErrorExpected({"a module field", "a module"}); } EXPECT(Eof); if (errors_->size() == 0) { *out_module = std::move(module); return Result::Ok; } else { return Result::Error; } } Result WastParser::ParseScript(std::unique_ptr