diff --git a/Cargo.toml b/Cargo.toml index 9676f01..186dda8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "luau0-src" -version = "0.4.0+luau548" +version = "0.4.1+luau553" authors = ["Aleksandr Orlenko "] edition = "2021" repository = "https://github.com/khvzak/luau-src-rs" diff --git a/luau/Ast/include/Luau/ParseResult.h b/luau/Ast/include/Luau/ParseResult.h index 17ce2e3..9c0a952 100644 --- a/luau/Ast/include/Luau/ParseResult.h +++ b/luau/Ast/include/Luau/ParseResult.h @@ -58,6 +58,8 @@ struct Comment struct ParseResult { AstStatBlock* root; + size_t lines = 0; + std::vector hotcomments; std::vector errors; diff --git a/luau/Ast/include/Luau/Parser.h b/luau/Ast/include/Luau/Parser.h index 848d711..8b7eb73 100644 --- a/luau/Ast/include/Luau/Parser.h +++ b/luau/Ast/include/Luau/Parser.h @@ -302,8 +302,8 @@ private: AstStatError* reportStatError(const Location& location, const AstArray& expressions, const AstArray& statements, const char* format, ...) LUAU_PRINTF_ATTR(5, 6); AstExprError* reportExprError(const Location& location, const AstArray& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5); - AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray& types, bool isMissing, const char* format, ...) - LUAU_PRINTF_ATTR(5, 6); + AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray& types, const char* format, ...) + LUAU_PRINTF_ATTR(4, 5); // `parseErrorLocation` is associated with the parser error // `astErrorLocation` is associated with the AstTypeError created // It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely diff --git a/luau/Ast/include/Luau/StringUtils.h b/luau/Ast/include/Luau/StringUtils.h index dab7610..6345fde 100644 --- a/luau/Ast/include/Luau/StringUtils.h +++ b/luau/Ast/include/Luau/StringUtils.h @@ -1,17 +1,13 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/Common.h" + #include #include #include -#if defined(__GNUC__) -#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg))) -#else -#define LUAU_PRINTF_ATTR(fmt, arg) -#endif - namespace Luau { diff --git a/luau/Ast/src/Lexer.cpp b/luau/Ast/src/Lexer.cpp index d93f2cc..66436ac 100644 --- a/luau/Ast/src/Lexer.cpp +++ b/luau/Ast/src/Lexer.cpp @@ -641,8 +641,8 @@ Lexeme Lexer::readInterpolatedStringSection(Position start, Lexeme::Type formatT return brokenDoubleBrace; } - Lexeme lexemeOutput(Location(start, position()), Lexeme::InterpStringBegin, &buffer[startOffset], offset - startOffset); consume(); + Lexeme lexemeOutput(Location(start, position()), Lexeme::InterpStringBegin, &buffer[startOffset], offset - startOffset - 1); return lexemeOutput; } diff --git a/luau/Ast/src/Parser.cpp b/luau/Ast/src/Parser.cpp index c20c084..8338a04 100644 --- a/luau/Ast/src/Parser.cpp +++ b/luau/Ast/src/Parser.cpp @@ -23,9 +23,11 @@ LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false) LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false) -LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false) LUAU_FASTFLAGVARIABLE(LuauCommaParenWarnings, false) +LUAU_FASTFLAGVARIABLE(LuauTableConstructorRecovery, false) + +LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false) bool lua_telemetry_parsed_out_of_range_bin_integer = false; bool lua_telemetry_parsed_out_of_range_hex_integer = false; @@ -164,15 +166,16 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n try { AstStatBlock* root = p.parseChunk(); + size_t lines = p.lexer.current().location.end.line + (bufferSize > 0 && buffer[bufferSize - 1] != '\n'); - return ParseResult{root, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations)}; + return ParseResult{root, lines, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations)}; } catch (ParseError& err) { // when catching a fatal error, append it to the list of non-fatal errors and return p.parseErrors.push_back(err); - return ParseResult{nullptr, {}, p.parseErrors}; + return ParseResult{nullptr, 0, {}, p.parseErrors}; } } @@ -811,9 +814,8 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod() if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) { - return AstDeclaredClassProp{fnName.name, - reportTypeAnnotationError(Location(start, end), {}, /*isMissing*/ false, "'self' must be present as the unannotated first parameter"), - true}; + return AstDeclaredClassProp{ + fnName.name, reportTypeAnnotationError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true}; } // Skip the first index. @@ -824,8 +826,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod() if (args[i].annotation) vars.push_back(args[i].annotation); else - vars.push_back(reportTypeAnnotationError( - Location(start, end), {}, /*isMissing*/ false, "All declaration parameters aside from 'self' must be annotated")); + vars.push_back(reportTypeAnnotationError(Location(start, end), {}, "All declaration parameters aside from 'self' must be annotated")); } if (vararg && !varargAnnotation) @@ -905,6 +906,25 @@ AstStat* Parser::parseDeclaration(const Location& start) { props.push_back(parseDeclaredClassMethod()); } + else if (lexer.current().type == '[') + { + const Lexeme begin = lexer.current(); + nextLexeme(); // [ + + std::optional> chars = parseCharArray(); + + expectMatchAndConsume(']', begin); + expectAndConsume(':', "property type annotation"); + AstType* type = parseTypeAnnotation(); + + // TODO: since AstName conains a char*, it can't contain null + bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size); + + if (chars && !containsNull) + props.push_back(AstDeclaredClassProp{AstName(chars->data), type, false}); + else + report(begin.location, "String literal contains malformed escape sequence"); + } else { Name propName = parseName("property name"); @@ -1518,7 +1538,7 @@ AstType* Parser::parseTypeAnnotation(TempVector& parts, const Location if (isUnion && isIntersection) { - return reportTypeAnnotationError(Location(begin, parts.back()->location), copy(parts), /*isMissing*/ false, + return reportTypeAnnotationError(Location(begin, parts.back()->location), copy(parts), "Mixing union and intersection types is not allowed; consider wrapping in parentheses."); } @@ -1604,18 +1624,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) return {allocator.alloc(start, svalue)}; } else - return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")}; + return {reportTypeAnnotationError(start, {}, "String literal contains malformed escape sequence")}; } else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple) { parseInterpString(); - return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")}; + return {reportTypeAnnotationError(start, {}, "Interpolated string literals cannot be used as types")}; } else if (lexer.current().type == Lexeme::BrokenString) { nextLexeme(); - return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")}; + return {reportTypeAnnotationError(start, {}, "Malformed string")}; } else if (lexer.current().type == Lexeme::Name) { @@ -1674,33 +1694,20 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) { nextLexeme(); - return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, + return {reportTypeAnnotationError(start, {}, "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> " "...any'"), {}}; } else { - if (FFlag::LuauTypeAnnotationLocationChange) - { - // For a missing type annotation, capture 'space' between last token and the next one - Location astErrorlocation(lexer.previousLocation().end, start.begin); - // The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message). - // Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau. - Location parseErrorLocation(lexer.previousLocation().end, start.end); - return { - reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()), - {}}; - } - else - { - Location location = lexer.current().location; - - // For a missing type annotation, capture 'space' between last token and the next one - location = Location(lexer.previousLocation().end, lexer.current().location.begin); - - return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}}; - } + // For a missing type annotation, capture 'space' between last token and the next one + Location astErrorlocation(lexer.previousLocation().end, start.begin); + // The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message). + // Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau. + Location parseErrorLocation(lexer.previousLocation().end, start.end); + return { + reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()), {}}; } } @@ -2306,9 +2313,13 @@ AstExpr* Parser::parseTableConstructor() MatchLexeme matchBrace = lexer.current(); expectAndConsume('{', "table literal"); + unsigned lastElementIndent = 0; while (lexer.current().type != '}') { + if (FFlag::LuauTableConstructorRecovery) + lastElementIndent = lexer.current().location.begin.column; + if (lexer.current().type == '[') { MatchLexeme matchLocationBracket = lexer.current(); @@ -2353,10 +2364,14 @@ AstExpr* Parser::parseTableConstructor() { nextLexeme(); } - else + else if (FFlag::LuauTableConstructorRecovery && (lexer.current().type == '[' || lexer.current().type == Lexeme::Name) && + lexer.current().location.begin.column == lastElementIndent) { - if (lexer.current().type != '}') - break; + report(lexer.current().location, "Expected ',' after table constructor element"); + } + else if (lexer.current().type != '}') + { + break; } } @@ -2490,7 +2505,7 @@ std::pair, AstArray> Parser::parseG namePacks.push_back({name, nameLocation, typePack}); } - else if (lexer.current().type == '(') + else if (!FFlag::LuauParserErrorsOnMissingDefaultTypePackArgument && lexer.current().type == '(') { auto [type, typePack] = parseTypeOrPackAnnotation(); @@ -2499,6 +2514,15 @@ std::pair, AstArray> Parser::parseG namePacks.push_back({name, nameLocation, typePack}); } + else if (FFlag::LuauParserErrorsOnMissingDefaultTypePackArgument) + { + auto [type, typePack] = parseTypeOrPackAnnotation(); + + if (type) + report(type->location, "Expected type pack after '=', got type"); + + namePacks.push_back({name, nameLocation, typePack}); + } } else { @@ -3014,27 +3038,18 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray(location, expressions, unsigned(parseErrors.size() - 1)); } -AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray& types, bool isMissing, const char* format, ...) +AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray& types, const char* format, ...) { - if (FFlag::LuauTypeAnnotationLocationChange) - { - // Missing type annotations should be using `reportMissingTypeAnnotationError` when LuauTypeAnnotationLocationChange is enabled - // Note: `isMissing` can be removed once FFlag::LuauTypeAnnotationLocationChange is removed since it will always be true. - LUAU_ASSERT(!isMissing); - } - va_list args; va_start(args, format); report(location, format, args); va_end(args); - return allocator.alloc(location, types, isMissing, unsigned(parseErrors.size() - 1)); + return allocator.alloc(location, types, false, unsigned(parseErrors.size() - 1)); } AstTypeError* Parser::reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...) { - LUAU_ASSERT(FFlag::LuauTypeAnnotationLocationChange); - va_list args; va_start(args, format); report(parseErrorLocation, format, args); diff --git a/luau/Common/include/Luau/Common.h b/luau/Common/include/Luau/Common.h index f1846ac..e590987 100644 --- a/luau/Common/include/Luau/Common.h +++ b/luau/Common/include/Luau/Common.h @@ -20,6 +20,10 @@ #define LUAU_DEBUGBREAK() __builtin_trap() #endif +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define LUAU_BIG_ENDIAN +#endif + namespace Luau { @@ -122,3 +126,9 @@ FValue* FValue::list = nullptr; { \ Luau::FValue flag(#flag, def, true); \ } + +#if defined(__GNUC__) +#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg))) +#else +#define LUAU_PRINTF_ATTR(fmt, arg) +#endif diff --git a/luau/Common/include/Luau/ExperimentalFlags.h b/luau/Common/include/Luau/ExperimentalFlags.h index 7cff70a..15db9ea 100644 --- a/luau/Common/include/Luau/ExperimentalFlags.h +++ b/luau/Common/include/Luau/ExperimentalFlags.h @@ -11,9 +11,10 @@ inline bool isFlagExperimental(const char* flag) // Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final, // or critical bugs that are found after the code has been submitted. static const char* kList[] = { - "LuauLowerBoundsCalculation", "LuauInterpolatedStringBaseSupport", "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code + "LuauOptionalNextKey", // waiting for a fix to land in lua-apps + "LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps // makes sure we always have at least one entry nullptr, }; diff --git a/luau/Compiler/include/Luau/BytecodeBuilder.h b/luau/Compiler/include/Luau/BytecodeBuilder.h index 6ec10b5..0d4543f 100644 --- a/luau/Compiler/include/Luau/BytecodeBuilder.h +++ b/luau/Compiler/include/Luau/BytecodeBuilder.h @@ -102,6 +102,11 @@ public: void setDumpSource(const std::string& source); + bool needsDebugRemarks() const + { + return (dumpFlags & Dump_Remarks) != 0; + } + const std::string& getBytecode() const { LUAU_ASSERT(!bytecode.empty()); // did you forget to call finalize? @@ -110,6 +115,9 @@ public: std::string dumpFunction(uint32_t id) const; std::string dumpEverything() const; + std::string dumpSourceRemarks() const; + + void annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const; static uint32_t getImportId(int32_t id0); static uint32_t getImportId(int32_t id0, int32_t id1); @@ -173,6 +181,7 @@ private: std::string dump; std::string dumpname; + std::vector dumpinstoffs; }; struct DebugLocal @@ -243,12 +252,15 @@ private: uint32_t dumpFlags = 0; std::vector dumpSource; + std::vector> dumpRemarks; - std::string (BytecodeBuilder::*dumpFunctionPtr)() const = nullptr; + std::string (BytecodeBuilder::*dumpFunctionPtr)(std::vector&) const = nullptr; void validate() const; + void validateInstructions() const; + void validateVariadic() const; - std::string dumpCurrentFunction() const; + std::string dumpCurrentFunction(std::vector& dumpinstoffs) const; void dumpInstruction(const uint32_t* opcode, std::string& output, int targetLabel) const; void writeFunction(std::string& ss, uint32_t id) const; diff --git a/luau/Compiler/src/Builtins.cpp b/luau/Compiler/src/Builtins.cpp index 8d4640d..ae96337 100644 --- a/luau/Compiler/src/Builtins.cpp +++ b/luau/Compiler/src/Builtins.cpp @@ -4,8 +4,6 @@ #include "Luau/Bytecode.h" #include "Luau/Compiler.h" -LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinMT, false) - namespace Luau { namespace Compile @@ -66,13 +64,10 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op if (builtin.isGlobal("select")) return LBF_SELECT_VARARG; - if (FFlag::LuauCompileBuiltinMT) - { - if (builtin.isGlobal("getmetatable")) - return LBF_GETMETATABLE; - if (builtin.isGlobal("setmetatable")) - return LBF_SETMETATABLE; - } + if (builtin.isGlobal("getmetatable")) + return LBF_GETMETATABLE; + if (builtin.isGlobal("setmetatable")) + return LBF_SETMETATABLE; if (builtin.object == "math") { diff --git a/luau/Compiler/src/BytecodeBuilder.cpp b/luau/Compiler/src/BytecodeBuilder.cpp index 2848447..8376424 100644 --- a/luau/Compiler/src/BytecodeBuilder.cpp +++ b/luau/Compiler/src/BytecodeBuilder.cpp @@ -269,7 +269,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues) // this call is indirect to make sure we only gain link time dependency on dumpCurrentFunction when needed if (dumpFunctionPtr) - func.dump = (this->*dumpFunctionPtr)(); + func.dump = (this->*dumpFunctionPtr)(func.dumpinstoffs); insns.clear(); lines.clear(); @@ -572,6 +572,7 @@ void BytecodeBuilder::addDebugRemark(const char* format, ...) debugRemarkBuffer += '\0'; debugRemarks.emplace_back(uint32_t(insns.size()), uint32_t(offset)); + dumpRemarks.emplace_back(debugLine, debugRemarkBuffer.c_str() + offset); } void BytecodeBuilder::finalize() @@ -1077,6 +1078,12 @@ uint8_t BytecodeBuilder::getVersion() #ifdef LUAU_ASSERTENABLED void BytecodeBuilder::validate() const +{ + validateInstructions(); + validateVariadic(); +} + +void BytecodeBuilder::validateInstructions() const { #define VREG(v) LUAU_ASSERT(unsigned(v) < func.maxstacksize) #define VREGRANGE(v, count) LUAU_ASSERT(unsigned(v + (count < 0 ? 0 : count)) <= func.maxstacksize) @@ -1089,26 +1096,27 @@ void BytecodeBuilder::validate() const const Function& func = functions[currentFunction]; - // first pass: tag instruction offsets so that we can validate jumps - std::vector insnvalid(insns.size(), false); + // tag instruction offsets so that we can validate jumps + std::vector insnvalid(insns.size(), 0); for (size_t i = 0; i < insns.size();) { - uint8_t op = LUAU_INSN_OP(insns[i]); + uint32_t insn = insns[i]; + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn)); insnvalid[i] = true; - i += getOpLength(LuauOpcode(op)); + i += getOpLength(op); LUAU_ASSERT(i <= insns.size()); } std::vector openCaptures; - // second pass: validate the rest of the bytecode + // validate individual instructions for (size_t i = 0; i < insns.size();) { uint32_t insn = insns[i]; - uint8_t op = LUAU_INSN_OP(insn); + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn)); switch (op) { @@ -1451,7 +1459,7 @@ void BytecodeBuilder::validate() const LUAU_ASSERT(!"Unsupported opcode"); } - i += getOpLength(LuauOpcode(op)); + i += getOpLength(op); LUAU_ASSERT(i <= insns.size()); } @@ -1468,6 +1476,126 @@ void BytecodeBuilder::validate() const #undef VCONSTANY #undef VJUMP } + +void BytecodeBuilder::validateVariadic() const +{ + // validate MULTRET sequences: instructions that produce a variadic sequence and consume one must come in pairs + // we classify instructions into four groups: producers, consumers, neutral and others + // any producer (an instruction that produces more than one value) must be followed by 0 or more neutral instructions + // and a consumer (that consumes more than one value); these form a variadic sequence. + // except for producer, no instruction in the variadic sequence may be a jump target. + // from the execution perspective, producer adjusts L->top to point to one past the last result, neutral instructions + // leave L->top unmodified, and consumer adjusts L->top back to the stack frame end. + // consumers invalidate all values after L->top after they execute (which we currently don't validate) + bool variadicSeq = false; + + std::vector insntargets(insns.size(), 0); + + for (size_t i = 0; i < insns.size();) + { + uint32_t insn = insns[i]; + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn)); + + int target = getJumpTarget(insn, uint32_t(i)); + + if (target >= 0 && !isFastCall(op)) + { + LUAU_ASSERT(unsigned(target) < insns.size()); + + insntargets[target] = true; + } + + i += getOpLength(op); + LUAU_ASSERT(i <= insns.size()); + } + + for (size_t i = 0; i < insns.size();) + { + uint32_t insn = insns[i]; + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn)); + + if (variadicSeq) + { + // no instruction inside the sequence, including the consumer, may be a jump target + // this guarantees uninterrupted L->top adjustment flow + LUAU_ASSERT(!insntargets[i]); + } + + if (op == LOP_CALL) + { + // note: calls may end one variadic sequence and start a new one + + if (LUAU_INSN_B(insn) == 0) + { + // consumer instruction ens a variadic sequence + LUAU_ASSERT(variadicSeq); + variadicSeq = false; + } + else + { + // CALL is not a neutral instruction so it can't be present in a variadic sequence unless it's a consumer + LUAU_ASSERT(!variadicSeq); + } + + if (LUAU_INSN_C(insn) == 0) + { + // producer instruction starts a variadic sequence + LUAU_ASSERT(!variadicSeq); + variadicSeq = true; + } + } + else if (op == LOP_GETVARARGS && LUAU_INSN_B(insn) == 0) + { + // producer instruction starts a variadic sequence + LUAU_ASSERT(!variadicSeq); + variadicSeq = true; + } + else if ((op == LOP_RETURN && LUAU_INSN_B(insn) == 0) || (op == LOP_SETLIST && LUAU_INSN_C(insn) == 0)) + { + // consumer instruction ends a variadic sequence + LUAU_ASSERT(variadicSeq); + variadicSeq = false; + } + else if (op == LOP_FASTCALL) + { + int callTarget = int(i + LUAU_INSN_C(insn) + 1); + LUAU_ASSERT(unsigned(callTarget) < insns.size() && LUAU_INSN_OP(insns[callTarget]) == LOP_CALL); + + if (LUAU_INSN_B(insns[callTarget]) == 0) + { + // consumer instruction ends a variadic sequence; however, we can't terminate it yet because future analysis of CALL will do it + // during FASTCALL fallback, the instructions between this and CALL consumer are going to be executed before L->top so they must + // be neutral; as such, we will defer termination of variadic sequence until CALL analysis + LUAU_ASSERT(variadicSeq); + } + else + { + // FASTCALL is not a neutral instruction so it can't be present in a variadic sequence unless it's linked to CALL consumer + LUAU_ASSERT(!variadicSeq); + } + + // note: if FASTCALL is linked to a CALL producer, the instructions between FASTCALL and CALL are technically not part of an executed + // variadic sequence since they are never executed if FASTCALL does anything, so it's okay to skip their validation until CALL + // (we can't simply start a variadic sequence here because that would trigger assertions during linked CALL validation) + } + else if (op == LOP_CLOSEUPVALS || op == LOP_NAMECALL || op == LOP_GETIMPORT || op == LOP_MOVE || op == LOP_GETUPVAL || op == LOP_GETGLOBAL || + op == LOP_GETTABLEKS || op == LOP_COVERAGE) + { + // instructions inside a variadic sequence must be neutral (can't change L->top) + // while there are many neutral instructions like this, here we check that the instruction is one of the few + // that we'd expect to exist in FASTCALL fallback sequences or between consecutive CALLs for encoding reasons + } + else + { + LUAU_ASSERT(!variadicSeq); + } + + i += getOpLength(op); + LUAU_ASSERT(i <= insns.size()); + } + + LUAU_ASSERT(!variadicSeq); +} #endif void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, int targetLabel) const @@ -1799,7 +1927,7 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, } } -std::string BytecodeBuilder::dumpCurrentFunction() const +std::string BytecodeBuilder::dumpCurrentFunction(std::vector& dumpinstoffs) const { if ((dumpFlags & Dump_Code) == 0) return std::string(); @@ -1849,11 +1977,15 @@ std::string BytecodeBuilder::dumpCurrentFunction() const if (labels[i] == 0) labels[i] = nextLabel++; + dumpinstoffs.resize(insns.size() + 1, -1); + for (size_t i = 0; i < insns.size();) { const uint32_t* code = &insns[i]; uint8_t op = LUAU_INSN_OP(*code); + dumpinstoffs[i] = int(result.size()); + if (op == LOP_PREPVARARGS) { // Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information @@ -1896,6 +2028,8 @@ std::string BytecodeBuilder::dumpCurrentFunction() const LUAU_ASSERT(i <= insns.size()); } + dumpinstoffs[insns.size()] = int(result.size()); + return result; } @@ -1949,4 +2083,62 @@ std::string BytecodeBuilder::dumpEverything() const return result; } +std::string BytecodeBuilder::dumpSourceRemarks() const +{ + std::string result; + + size_t nextRemark = 0; + + std::vector> remarks = dumpRemarks; + std::sort(remarks.begin(), remarks.end()); + + for (size_t i = 0; i < dumpSource.size(); ++i) + { + const std::string& line = dumpSource[i]; + + size_t indent = 0; + while (indent < line.length() && (line[indent] == ' ' || line[indent] == '\t')) + indent++; + + while (nextRemark < remarks.size() && remarks[nextRemark].first == int(i + 1)) + { + formatAppend(result, "%.*s-- remark: %s\n", int(indent), line.c_str(), remarks[nextRemark].second.c_str()); + nextRemark++; + + // skip duplicate remarks (due to inlining/unrolling) + while (nextRemark < remarks.size() && remarks[nextRemark] == remarks[nextRemark - 1]) + nextRemark++; + } + + result += line; + + if (i + 1 < dumpSource.size()) + result += '\n'; + } + + return result; +} + +void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const +{ + if ((dumpFlags & Dump_Code) == 0) + return; + + LUAU_ASSERT(fid < functions.size()); + + const Function& function = functions[fid]; + const std::string& dump = function.dump; + const std::vector& dumpinstoffs = function.dumpinstoffs; + + uint32_t next = instpos + 1; + + LUAU_ASSERT(next < dumpinstoffs.size()); + + // Skip locations of multi-dword instructions + while (next < dumpinstoffs.size() && dumpinstoffs[next] == -1) + next++; + + formatAppend(result, "%.*s", dumpinstoffs[next] - dumpinstoffs[instpos], dump.data() + dumpinstoffs[instpos]); +} + } // namespace Luau diff --git a/luau/Compiler/src/Compiler.cpp b/luau/Compiler/src/Compiler.cpp index ce2c5a9..7ccd116 100644 --- a/luau/Compiler/src/Compiler.cpp +++ b/luau/Compiler/src/Compiler.cpp @@ -709,6 +709,17 @@ struct Compiler if (const int* id = builtins.find(expr)) bfid = *id; + if (bfid >= 0 && bytecode.needsDebugRemarks()) + { + Builtin builtin = getBuiltin(expr->func, globals, variables); + bool lastMult = expr->args.size > 0 && isExprMultRet(expr->args.data[expr->args.size - 1]); + + if (builtin.object.value) + bytecode.addDebugRemark("builtin %s.%s/%d%s", builtin.object.value, builtin.method.value, int(expr->args.size), lastMult ? "+" : ""); + else if (builtin.method.value) + bytecode.addDebugRemark("builtin %s/%d%s", builtin.method.value, int(expr->args.size), lastMult ? "+" : ""); + } + if (bfid == LBF_SELECT_VARARG) { // Optimization: compile select(_, ...) as FASTCALL1; the builtin will read variadic arguments directly @@ -918,6 +929,9 @@ struct Compiler shared = int16_t(cid); } + if (shared < 0) + bytecode.addDebugRemark("allocation: closure with %d upvalues", int(captures.size())); + if (shared >= 0) bytecode.emitAD(LOP_DUPCLOSURE, target, shared); else @@ -1599,6 +1613,8 @@ struct Compiler { TableShape shape = tableShapes[expr]; + bytecode.addDebugRemark("allocation: table hash %d", shape.hashSize); + bytecode.emitABC(LOP_NEWTABLE, target, encodeHashSize(shape.hashSize), 0); bytecode.emitAux(shape.arraySize); return; @@ -1671,6 +1687,8 @@ struct Compiler if (tid < 0) CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); + bytecode.addDebugRemark("allocation: table template %d", hashSize); + if (tid < 32768) { bytecode.emitAD(LOP_DUPTABLE, reg, int16_t(tid)); @@ -1690,8 +1708,17 @@ struct Compiler bool trailingVarargs = last && last->kind == AstExprTable::Item::List && last->value->is(); LUAU_ASSERT(!trailingVarargs || arraySize > 0); + unsigned int arrayAllocation = arraySize - trailingVarargs + indexSize; + + if (hashSize == 0) + bytecode.addDebugRemark("allocation: table array %d", arrayAllocation); + else if (arrayAllocation == 0) + bytecode.addDebugRemark("allocation: table hash %d", hashSize); + else + bytecode.addDebugRemark("allocation: table hash %d array %d", hashSize, arrayAllocation); + bytecode.emitABC(LOP_NEWTABLE, reg, uint8_t(encodedHashSize), 0); - bytecode.emitAux(arraySize - trailingVarargs + indexSize); + bytecode.emitAux(arrayAllocation); } unsigned int arrayChunkSize = std::min(16u, arraySize); diff --git a/luau/VM/include/lua.h b/luau/VM/include/lua.h index ea658a4..73ebbb0 100644 --- a/luau/VM/include/lua.h +++ b/luau/VM/include/lua.h @@ -38,10 +38,10 @@ enum lua_Status enum lua_CoStatus { LUA_CORUN = 0, // running - LUA_COSUS, // suspended - LUA_CONOR, // 'normal' (it resumed another coroutine) - LUA_COFIN, // finished - LUA_COERR, // finished with error + LUA_COSUS, // suspended + LUA_CONOR, // 'normal' (it resumed another coroutine) + LUA_COFIN, // finished + LUA_COERR, // finished with error }; typedef struct lua_State lua_State; @@ -398,16 +398,16 @@ LUA_API const char* lua_debugtrace(lua_State* L); struct lua_Debug { - const char* name; // (n) - const char* what; // (s) `Lua', `C', `main', `tail' - const char* source; // (s) - const char* short_src; // (s) - int linedefined; // (s) - int currentline; // (l) - unsigned char nupvals; // (u) number of upvalues - unsigned char nparams; // (a) number of parameters - char isvararg; // (a) - void* userdata; // only valid in luau_callhook + const char* name; // (n) + const char* what; // (s) `Lua', `C', `main', `tail' + const char* source; // (s) + const char* short_src; // (s) + int linedefined; // (s) + int currentline; // (l) + unsigned char nupvals; // (u) number of upvalues + unsigned char nparams; // (a) number of parameters + char isvararg; // (a) + void* userdata; // only valid in luau_callhook char ssbuf[LUA_IDSIZE]; }; diff --git a/luau/VM/include/luaconf.h b/luau/VM/include/luaconf.h index 1d8d781..dcf5692 100644 --- a/luau/VM/include/luaconf.h +++ b/luau/VM/include/luaconf.h @@ -133,4 +133,4 @@ #define LUA_VECTOR_SIZE 3 // must be 3 or 4 -#define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2 +#define LUA_EXTRA_SIZE (LUA_VECTOR_SIZE - 2) diff --git a/luau/VM/src/lbuiltins.cpp b/luau/VM/src/lbuiltins.cpp index 87b6ae0..71869b1 100644 --- a/luau/VM/src/lbuiltins.cpp +++ b/luau/VM/src/lbuiltins.cpp @@ -263,11 +263,14 @@ static int luauF_log(lua_State* L, StkId res, TValue* arg0, int nresults, StkId static int luauF_max(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0)) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { - double r = nvalue(arg0); + double a1 = nvalue(arg0); + double a2 = nvalue(args); - for (int i = 2; i <= nparams; ++i) + double r = (a2 > a1) ? a2 : a1; + + for (int i = 3; i <= nparams; ++i) { if (!ttisnumber(args + (i - 2))) return -1; @@ -286,11 +289,14 @@ static int luauF_max(lua_State* L, StkId res, TValue* arg0, int nresults, StkId static int luauF_min(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0)) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { - double r = nvalue(arg0); + double a1 = nvalue(arg0); + double a2 = nvalue(args); - for (int i = 2; i <= nparams; ++i) + double r = (a2 < a1) ? a2 : a1; + + for (int i = 3; i <= nparams; ++i) { if (!ttisnumber(args + (i - 2))) return -1; @@ -439,22 +445,18 @@ static int luauF_arshift(lua_State* L, StkId res, TValue* arg0, int nresults, St static int luauF_band(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= 1 && nresults <= 1) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { - uint32_t r = ~0u; + double a1 = nvalue(arg0); + double a2 = nvalue(args); - if (!ttisnumber(arg0)) - return -1; + unsigned u1, u2; + luai_num2unsigned(u1, a1); + luai_num2unsigned(u2, a2); - { - double a1 = nvalue(arg0); - unsigned u; - luai_num2unsigned(u, a1); + uint32_t r = u1 & u2; - r &= u; - } - - for (int i = 2; i <= nparams; ++i) + for (int i = 3; i <= nparams; ++i) { if (!ttisnumber(args + (i - 2))) return -1; @@ -492,22 +494,18 @@ static int luauF_bnot(lua_State* L, StkId res, TValue* arg0, int nresults, StkId static int luauF_bor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= 1 && nresults <= 1) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { - uint32_t r = 0; + double a1 = nvalue(arg0); + double a2 = nvalue(args); - if (!ttisnumber(arg0)) - return -1; + unsigned u1, u2; + luai_num2unsigned(u1, a1); + luai_num2unsigned(u2, a2); - { - double a1 = nvalue(arg0); - unsigned u; - luai_num2unsigned(u, a1); + uint32_t r = u1 | u2; - r |= u; - } - - for (int i = 2; i <= nparams; ++i) + for (int i = 3; i <= nparams; ++i) { if (!ttisnumber(args + (i - 2))) return -1; @@ -528,22 +526,18 @@ static int luauF_bor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId static int luauF_bxor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= 1 && nresults <= 1) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { - uint32_t r = 0; + double a1 = nvalue(arg0); + double a2 = nvalue(args); - if (!ttisnumber(arg0)) - return -1; + unsigned u1, u2; + luai_num2unsigned(u1, a1); + luai_num2unsigned(u2, a2); - { - double a1 = nvalue(arg0); - unsigned u; - luai_num2unsigned(u, a1); + uint32_t r = u1 ^ u2; - r ^= u; - } - - for (int i = 2; i <= nparams; ++i) + for (int i = 3; i <= nparams; ++i) { if (!ttisnumber(args + (i - 2))) return -1; @@ -564,22 +558,18 @@ static int luauF_bxor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId static int luauF_btest(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= 1 && nresults <= 1) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { - uint32_t r = ~0u; + double a1 = nvalue(arg0); + double a2 = nvalue(args); - if (!ttisnumber(arg0)) - return -1; + unsigned u1, u2; + luai_num2unsigned(u1, a1); + luai_num2unsigned(u2, a2); - { - double a1 = nvalue(arg0); - unsigned u; - luai_num2unsigned(u, a1); + uint32_t r = u1 & u2; - r &= u; - } - - for (int i = 2; i <= nparams; ++i) + for (int i = 3; i <= nparams; ++i) { if (!ttisnumber(args + (i - 2))) return -1; @@ -1249,7 +1239,12 @@ static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresult return -1; } -luau_FastFunction luauF_table[256] = { +static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) +{ + return -1; +} + +const luau_FastFunction luauF_table[256] = { NULL, luauF_assert, @@ -1327,4 +1322,20 @@ luau_FastFunction luauF_table[256] = { luauF_getmetatable, luauF_setmetatable, + +// When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback. +// This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing. +// Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest. +#define MISSING8 luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing + + MISSING8, + MISSING8, + MISSING8, + MISSING8, + MISSING8, + MISSING8, + MISSING8, + MISSING8, + +#undef MISSING8 }; diff --git a/luau/VM/src/lbuiltins.h b/luau/VM/src/lbuiltins.h index a642c93..44702c9 100644 --- a/luau/VM/src/lbuiltins.h +++ b/luau/VM/src/lbuiltins.h @@ -6,4 +6,4 @@ typedef int (*luau_FastFunction)(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams); -extern luau_FastFunction luauF_table[256]; +extern const luau_FastFunction luauF_table[256]; diff --git a/luau/VM/src/ldebug.cpp b/luau/VM/src/ldebug.cpp index 82af5d3..4439042 100644 --- a/luau/VM/src/ldebug.cpp +++ b/luau/VM/src/ldebug.cpp @@ -12,8 +12,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauFasterGetInfo, false) - static const char* getfuncname(Closure* f); static int currentpc(lua_State* L, CallInfo* ci) @@ -105,8 +103,7 @@ static Closure* auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closur ar->source = "=[C]"; ar->what = "C"; ar->linedefined = -1; - if (FFlag::LuauFasterGetInfo) - ar->short_src = "[C]"; + ar->short_src = "[C]"; } else { @@ -114,13 +111,7 @@ static Closure* auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closur ar->source = getstr(source); ar->what = "Lua"; ar->linedefined = f->l.p->linedefined; - if (FFlag::LuauFasterGetInfo) - ar->short_src = luaO_chunkid(ar->ssbuf, sizeof(ar->ssbuf), getstr(source), source->len); - } - if (!FFlag::LuauFasterGetInfo) - { - luaO_chunkid(ar->ssbuf, LUA_IDSIZE, ar->source, 0); - ar->short_src = ar->ssbuf; + ar->short_src = luaO_chunkid(ar->ssbuf, sizeof(ar->ssbuf), getstr(source), source->len); } break; } @@ -195,25 +186,12 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar) } if (f) { - if (FFlag::LuauFasterGetInfo) + // auxgetinfo fills ar and optionally requests to put closure on stack + if (Closure* fcl = auxgetinfo(L, what, ar, f, ci)) { - // auxgetinfo fills ar and optionally requests to put closure on stack - if (Closure* fcl = auxgetinfo(L, what, ar, f, ci)) - { - luaC_threadbarrier(L); - setclvalue(L, L->top, fcl); - incr_top(L); - } - } - else - { - auxgetinfo(L, what, ar, f, ci); - if (strchr(what, 'f')) - { - luaC_threadbarrier(L); - setclvalue(L, L->top, f); - incr_top(L); - } + luaC_threadbarrier(L); + setclvalue(L, L->top, fcl); + incr_top(L); } } return f ? 1 : 0; diff --git a/luau/VM/src/lfunc.cpp b/luau/VM/src/lfunc.cpp index aeb3d71..2230a74 100644 --- a/luau/VM/src/lfunc.cpp +++ b/luau/VM/src/lfunc.cpp @@ -108,7 +108,7 @@ UpVal* luaF_findupval(lua_State* L, StkId level) void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page) { - luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue + luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue } void luaF_close(lua_State* L, StkId level) diff --git a/luau/VM/src/lobject.cpp b/luau/VM/src/lobject.cpp index 8b3e478..88d8d7c 100644 --- a/luau/VM/src/lobject.cpp +++ b/luau/VM/src/lobject.cpp @@ -13,10 +13,6 @@ #include #include - - -LUAU_FASTFLAG(LuauFasterGetInfo) - const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL}; int luaO_log2(unsigned int x) @@ -123,48 +119,20 @@ const char* luaO_chunkid(char* buf, size_t buflen, const char* source, size_t sr { if (*source == '=') { - if (FFlag::LuauFasterGetInfo) - { - if (srclen <= buflen) - return source + 1; - // truncate the part after = - memcpy(buf, source + 1, buflen - 1); - buf[buflen - 1] = '\0'; - } - else - { - source++; // skip the `=' - size_t len = strlen(source); - size_t dstlen = len < buflen ? len : buflen - 1; - memcpy(buf, source, dstlen); - buf[dstlen] = '\0'; - } + if (srclen <= buflen) + return source + 1; + // truncate the part after = + memcpy(buf, source + 1, buflen - 1); + buf[buflen - 1] = '\0'; } else if (*source == '@') { - if (FFlag::LuauFasterGetInfo) - { - if (srclen <= buflen) - return source + 1; - // truncate the part after @ - memcpy(buf, "...", 3); - memcpy(buf + 3, source + srclen - (buflen - 4), buflen - 4); - buf[buflen - 1] = '\0'; - } - else - { - size_t l; - source++; // skip the `@' - buflen -= sizeof("..."); - l = strlen(source); - strcpy(buf, ""); - if (l > buflen) - { - source += (l - buflen); // get last part of file name - strcat(buf, "..."); - } - strcat(buf, source); - } + if (srclen <= buflen) + return source + 1; + // truncate the part after @ + memcpy(buf, "...", 3); + memcpy(buf + 3, source + srclen - (buflen - 4), buflen - 4); + buf[buflen - 1] = '\0'; } else { // buf = [string "string"] diff --git a/luau/VM/src/lobject.h b/luau/VM/src/lobject.h index 41bf338..f0471c2 100644 --- a/luau/VM/src/lobject.h +++ b/luau/VM/src/lobject.h @@ -290,6 +290,7 @@ typedef struct Proto int sizelineinfo; int linegaplog2; int linedefined; + int bytecodeid; uint8_t nups; // number of upvalues diff --git a/luau/VM/src/lstate.cpp b/luau/VM/src/lstate.cpp index cfe2cbf..b320a25 100644 --- a/luau/VM/src/lstate.cpp +++ b/luau/VM/src/lstate.cpp @@ -100,6 +100,12 @@ static void close_state(lua_State* L) LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG)); for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++) LUAU_ASSERT(g->memcatbytes[i] == 0); + +#if LUA_CUSTOM_EXECUTION + if (L->global->ecb.close) + L->global->ecb.close(L); +#endif + (*g->frealloc)(g->ud, L, sizeof(LG), 0); } diff --git a/luau/VM/src/lstate.h b/luau/VM/src/lstate.h index 5b7d088..1d32489 100644 --- a/luau/VM/src/lstate.h +++ b/luau/VM/src/lstate.h @@ -146,18 +146,15 @@ struct GCMetrics }; #endif -#if LUA_CUSTOM_EXECUTION - // Callbacks that can be used to to redirect code execution from Luau bytecode VM to a custom implementation (AoT/JiT/sandboxing/...) -typedef struct lua_ExecutionCallbacks +struct lua_ExecutionCallbacks { void* context; + void (*close)(lua_State* L); // called when global VM state is closed void (*destroy)(lua_State* L, Proto* proto); // called when function is destroyed int (*enter)(lua_State* L, Proto* proto); // called when function is about to start/resume (when execdata is present), return 0 to exit VM void (*setbreakpoint)(lua_State* L, Proto* proto, int line); // called when a breakpoint is set in a function -} lua_ExecutionCallbacks; - -#endif +}; /* ** `global state', shared by all threads of this state diff --git a/luau/VM/src/lvmexecute.cpp b/luau/VM/src/lvmexecute.cpp index 7ee3ee9..6f600e9 100644 --- a/luau/VM/src/lvmexecute.cpp +++ b/luau/VM/src/lvmexecute.cpp @@ -756,6 +756,8 @@ reentry: Proto* pv = cl->l.p->p[LUAU_INSN_D(insn)]; LUAU_ASSERT(unsigned(LUAU_INSN_D(insn)) < unsigned(cl->l.p->sizep)); + VM_PROTECT_PC(); // luaF_newLclosure may fail due to OOM + // note: we save closure to stack early in case the code below wants to capture it by value Closure* ncl = luaF_newLclosure(L, pv->nups, cl->env, pv); setclvalue(L, ra, ncl); @@ -2054,6 +2056,8 @@ reentry: int b = LUAU_INSN_B(insn); uint32_t aux = *pc++; + VM_PROTECT_PC(); // luaH_new may fail due to OOM + sethvalue(L, ra, luaH_new(L, aux, b == 0 ? 0 : (1 << (b - 1)))); VM_PROTECT(luaC_checkGC(L)); VM_NEXT(); @@ -2065,6 +2069,8 @@ reentry: StkId ra = VM_REG(LUAU_INSN_A(insn)); TValue* kv = VM_KV(LUAU_INSN_D(insn)); + VM_PROTECT_PC(); // luaH_clone may fail due to OOM + sethvalue(L, ra, luaH_clone(L, hvalue(kv))); VM_PROTECT(luaC_checkGC(L)); VM_NEXT(); @@ -2086,12 +2092,17 @@ reentry: Table* h = hvalue(ra); + // TODO: we really don't need this anymore if (!ttistable(ra)) return; // temporary workaround to weaken a rather powerful exploitation primitive in case of a MITM attack on bytecode int last = index + c - 1; if (last > h->sizearray) + { + VM_PROTECT_PC(); // luaH_resizearray may fail due to OOM + luaH_resizearray(L, h, last); + } TValue* array = h->array; @@ -2183,7 +2194,8 @@ reentry: // protect against __iter returning nil, since nil is used as a marker for builtin iteration in FORGLOOP if (ttisnil(ra)) { - VM_PROTECT(luaG_typeerror(L, ra, "call")); + VM_PROTECT_PC(); // next call always errors + luaG_typeerror(L, ra, "call"); } } else if (fasttm(L, mt, TM_CALL)) @@ -2200,7 +2212,8 @@ reentry: } else { - VM_PROTECT(luaG_typeerror(L, ra, "iterate over")); + VM_PROTECT_PC(); // next call always errors + luaG_typeerror(L, ra, "iterate over"); } } @@ -2323,7 +2336,8 @@ reentry: } else if (!ttisfunction(ra)) { - VM_PROTECT(luaG_typeerror(L, ra, "iterate over")); + VM_PROTECT_PC(); // next call always errors + luaG_typeerror(L, ra, "iterate over"); } pc += LUAU_INSN_D(insn); @@ -2351,7 +2365,8 @@ reentry: } else if (!ttisfunction(ra)) { - VM_PROTECT(luaG_typeerror(L, ra, "iterate over")); + VM_PROTECT_PC(); // next call always errors + luaG_typeerror(L, ra, "iterate over"); } pc += LUAU_INSN_D(insn); @@ -2402,6 +2417,8 @@ reentry: Closure* kcl = clvalue(kv); + VM_PROTECT_PC(); // luaF_newLclosure may fail due to OOM + // clone closure if the environment is not shared // note: we save closure to stack early in case the code below wants to capture it by value Closure* ncl = (kcl->env == cl->env) ? kcl : luaF_newLclosure(L, kcl->nupvalues, cl->env, kcl->l.p); @@ -2528,15 +2545,18 @@ reentry: nparams = (nparams == LUA_MULTRET) ? int(L->top - ra - 1) : nparams; luau_FastFunction f = luauF_table[bfid]; + LUAU_ASSERT(f); - if (cl->env->safeenv && f) + if (cl->env->safeenv) { - VM_PROTECT_PC(); + VM_PROTECT_PC(); // f may fail due to OOM int n = f(L, ra, ra + 1, nresults, ra + 2, nparams); if (n >= 0) { + // when nresults != MULTRET, L->top might be pointing to the middle of stack frame if nparams is equal to MULTRET + // instead of restoring L->top to L->ci->top if nparams is MULTRET, we do it unconditionally to skip an extra check L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top; pc += skip + 1; // skip instructions that compute function as well as CALL @@ -2604,16 +2624,18 @@ reentry: int nresults = LUAU_INSN_C(call) - 1; luau_FastFunction f = luauF_table[bfid]; + LUAU_ASSERT(f); - if (cl->env->safeenv && f) + if (cl->env->safeenv) { - VM_PROTECT_PC(); + VM_PROTECT_PC(); // f may fail due to OOM int n = f(L, ra, arg, nresults, NULL, nparams); if (n >= 0) { - L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top; + if (nresults == LUA_MULTRET) + L->top = ra + n; pc += skip + 1; // skip instructions that compute function as well as CALL LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); @@ -2652,16 +2674,18 @@ reentry: int nresults = LUAU_INSN_C(call) - 1; luau_FastFunction f = luauF_table[bfid]; + LUAU_ASSERT(f); - if (cl->env->safeenv && f) + if (cl->env->safeenv) { - VM_PROTECT_PC(); + VM_PROTECT_PC(); // f may fail due to OOM int n = f(L, ra, arg1, nresults, arg2, nparams); if (n >= 0) { - L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top; + if (nresults == LUA_MULTRET) + L->top = ra + n; pc += skip + 1; // skip instructions that compute function as well as CALL LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); @@ -2700,16 +2724,18 @@ reentry: int nresults = LUAU_INSN_C(call) - 1; luau_FastFunction f = luauF_table[bfid]; + LUAU_ASSERT(f); - if (cl->env->safeenv && f) + if (cl->env->safeenv) { - VM_PROTECT_PC(); + VM_PROTECT_PC(); // f may fail due to OOM int n = f(L, ra, arg1, nresults, arg2, nparams); if (n >= 0) { - L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top; + if (nresults == LUA_MULTRET) + L->top = ra + n; pc += skip + 1; // skip instructions that compute function as well as CALL LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); diff --git a/luau/VM/src/lvmload.cpp b/luau/VM/src/lvmload.cpp index 3edec68..305e540 100644 --- a/luau/VM/src/lvmload.cpp +++ b/luau/VM/src/lvmload.cpp @@ -192,6 +192,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size { Proto* p = luaF_newproto(L); p->source = source; + p->bytecodeid = int(i); p->maxstacksize = read(data, size, offset); p->numparams = read(data, size, offset);