diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 9a91afad0..fb8070e15 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -3107,6 +3107,14 @@ class LintIntegerParsing : AstVisitor "Hexadecimal number literal exceeded available precision and was truncated to 2^64" ); break; + case ConstantNumberParseResult::OctOverflow: + emitWarning( + *context, + LintWarning::Code_IntegerParsing, + node->location, + "Octal number literal exceeded available precision and was truncated to 2^64" + ); + break; } return true; diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 0b6f12ded..6e4ccf35c 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -307,6 +307,7 @@ enum class ConstantNumberParseResult Malformed, BinOverflow, HexOverflow, + OctOverflow, }; class AstExprConstantNumber : public AstExpr diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 6b3db0519..9c733c807 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -3174,7 +3174,7 @@ AstExpr* Parser::parseAssertionExpr() static ConstantNumberParseResult parseInteger(double& result, const char* data, int base) { - LUAU_ASSERT(base == 2 || base == 16); + LUAU_ASSERT(base == 2 || base == 16 || base == 8); char* end = nullptr; unsigned long long value = strtoull(data, &end, base); @@ -3192,7 +3192,14 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data, value = strtoull(data, &end, base); if (errno == ERANGE) - return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow; + { + if (base == 2) + return ConstantNumberParseResult::BinOverflow; + else if (base == 8) + return ConstantNumberParseResult::OctOverflow; + else + return ConstantNumberParseResult::HexOverflow; + } } if (value >= (1ull << 53) && static_cast(result) != value) @@ -3207,6 +3214,10 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data) if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2]) return parseInteger(result, data + 2, 2); + // octal literal + if (data[0] == '0' && (data[1] == 'o' || data[1] == 'O') && data[2]) + return parseInteger(result, data + 2, 8); + // hexadecimal literal if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2]) return parseInteger(result, data, 16); // pass in '0x' prefix, it's handled by 'strtoull' diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 656e7d7f1..fd9fa84b5 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -2235,11 +2235,13 @@ TEST_CASE_FIXTURE(Fixture, "IntegerParsing") LintResult result = lint(R"( local _ = 0b10000000000000000000000000000000000000000000000000000000000000000 local _ = 0x10000000000000000 +local _ = 0o2000000000000000000000 )"); - REQUIRE(2 == result.warnings.size()); + REQUIRE(3 == result.warnings.size()); CHECK_EQ(result.warnings[0].text, "Binary number literal exceeded available precision and was truncated to 2^64"); CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and was truncated to 2^64"); + CHECK_EQ(result.warnings[2].text, "Octal number literal exceeded available precision and was truncated to 2^64"); } TEST_CASE_FIXTURE(Fixture, "IntegerParsingDecimalImprecise") diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index ee2de4425..a1c7e59b2 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -701,6 +701,19 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_binary") CHECK_EQ(str->list.data[3]->as()->value, double(ULLONG_MAX)); } +TEST_CASE_FIXTURE(Fixture, "parse_numbers_octal") +{ + AstStat* stat = parse("return 0o1, 0o0, 0o52, 0o1777777777777777777777"); + REQUIRE(stat != nullptr); + + AstStatReturn* str = stat->as()->body.data[0]->as(); + CHECK(str->list.size == 4); + CHECK_EQ(str->list.data[0]->as()->value, 1); + CHECK_EQ(str->list.data[1]->as()->value, 0); + CHECK_EQ(str->list.data[2]->as()->value, 42); + CHECK_EQ(str->list.data[3]->as()->value, double(ULLONG_MAX)); +} + TEST_CASE_FIXTURE(Fixture, "parse_numbers_error") { matchParseError("return 0b123", "Malformed number"); @@ -709,6 +722,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_error") matchParseError("return 0x0x123", "Malformed number"); matchParseError("return 0xffffffffffffffffffffllllllg", "Malformed number"); matchParseError("return 0x0xffffffffffffffffffffffffffff", "Malformed number"); + matchParseError("return 0o898", "Malformed number"); } TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error") @@ -1694,14 +1708,15 @@ return .5, 12_34_56, 0x1234, - 0b010101 +0b010101, +0o777 )"); REQUIRE(stat != nullptr); AstStatReturn* ret = stat->body.data[0]->as(); REQUIRE(ret != nullptr); - CHECK(ret->list.size == 6); + CHECK(ret->list.size == 7); AstExprConstantNumber* num; @@ -1728,6 +1743,11 @@ return num = ret->list.data[5]->as(); REQUIRE(num != nullptr); CHECK_EQ(num->value, 0x15); + + num = ret->list.data[6]->as(); + REQUIRE(num != nullptr); + CHECK_EQ(num->value, 511); + } TEST_CASE_FIXTURE(Fixture, "end_extent_of_functions_unions_and_intersections") diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index cd47e8ffb..3d5bb7d71 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -582,6 +582,12 @@ TEST_CASE("binary_numbers") CHECK_EQ(code, transpile(code).code); } +TEST_CASE("octal_numbers") +{ + const std::string code = R"( local a = 0o777 )"; + CHECK_EQ(code, transpile(code).code); +} + TEST_CASE("single_quoted_strings") { const std::string code = R"( local a = 'hello world' )";