From 57fc11ce47fff5e6f665a6d6b5f1466ae00860bf Mon Sep 17 00:00:00 2001 From: kunitoki Date: Mon, 30 Mar 2026 16:48:17 +0200 Subject: [PATCH 01/19] Fix coverage --- .github/workflows/coverage.yml | 10 + Tests/Source/ArrayTests.cpp | 33 ++++ Tests/Source/DumpTests.cpp | 30 +++ Tests/Source/IteratorTests.cpp | 14 ++ Tests/Source/ListTests.cpp | 24 +++ Tests/Source/MapTests.cpp | 21 +++ Tests/Source/SetTests.cpp | 9 + Tests/Source/StackTests.cpp | 283 ++++++++++++++++++++++++++++- Tests/Source/UnorderedMapTests.cpp | 53 ++++++ Tests/Source/VectorTests.cpp | 44 +++++ amalgamate.py | 2 + cobertura.py | 88 +++++++++ 12 files changed, 609 insertions(+), 2 deletions(-) create mode 100644 cobertura.py diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e7c7e3e6..5ea9bb59 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -491,12 +491,22 @@ jobs: working-directory: ${{runner.workspace}}/build run: lcov2xml coverage/merged.info -o coverage/cobertura.xml + - name: Convert to Coverage TXT + working-directory: ${{runner.workspace}}/build + run: python3 ${{runner.workspace}}/cobertura.py coverage/cobertura.info coverage/coverage.txt + - name: Upload Cobertura XML uses: actions/upload-artifact@v4 with: name: cobertura-coverage path: ${{runner.workspace}}/build/coverage/cobertura.xml + - name: Upload Coverage TXT + uses: actions/upload-artifact@v4 + with: + name: text-coverage + path: ${{runner.workspace}}/build/coverage/coverage.txt + - name: Coveralls uses: coverallsapp/github-action@master with: diff --git a/Tests/Source/ArrayTests.cpp b/Tests/Source/ArrayTests.cpp index 44645fb3..4d39375a 100644 --- a/Tests/Source/ArrayTests.cpp +++ b/Tests/Source/ArrayTests.cpp @@ -147,6 +147,39 @@ TEST_F(ArrayTests, FailOnWrongSize) EXPECT_FALSE((luabridge::isInstance>(L, -1))); } +TEST_F(ArrayTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(ArrayTests, GetWithInvalidItem) +{ + lua_createtable(L, 3, 0); + for (int i = 1; i <= 3; ++i) + { + lua_pushinteger(L, i); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + } + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(ArrayTests, StackOverflow) +{ + exhaustStackSpace(); + + std::array value{ 1, 2, 3 }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + #if !LUABRIDGE_HAS_EXCEPTIONS TEST_F(ArrayTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) { diff --git a/Tests/Source/DumpTests.cpp b/Tests/Source/DumpTests.cpp index 59b42bb5..d4de1dcc 100644 --- a/Tests/Source/DumpTests.cpp +++ b/Tests/Source/DumpTests.cpp @@ -273,6 +273,36 @@ TEST_F(DumpTests, DumpTable) } } +TEST_F(DumpTests, DumpTableDirectWithNewLine) +{ + { + std::stringstream ss; + + lua_newtable(L); + lua_pushliteral(L, "key"); + lua_pushliteral(L, "value"); + lua_settable(L, -3); + + // Call dumpTable directly with newLine=true - covers trailing newline (line 134) + luabridge::dumpTable(L, -1, 1, 0, true, ss); + EXPECT_TRUE(ss.str().find("table@") == 0); + EXPECT_TRUE(ss.str().find("\"key\": \"value\",") != std::string::npos); + EXPECT_TRUE(ss.str().find("\n") != std::string::npos); + EXPECT_EQ('\n', ss.str().back()); + } + + { + std::stringstream ss; + + lua_newtable(L); + + // Call dumpTable with level > maxDepth and newLine=true - covers early-return newline (line 98) + luabridge::dumpTable(L, -1, 0, 1, true, ss); + EXPECT_TRUE(ss.str().find("table@") == 0); + EXPECT_EQ('\n', ss.str().back()); + } +} + TEST_F(DumpTests, DumpState) { std::stringstream ss; diff --git a/Tests/Source/IteratorTests.cpp b/Tests/Source/IteratorTests.cpp index d1a897d9..772532d1 100644 --- a/Tests/Source/IteratorTests.cpp +++ b/Tests/Source/IteratorTests.cpp @@ -53,6 +53,20 @@ TEST_F(IteratorTests, DictionaryIteration) ASSERT_EQ(expected, actual); } +TEST_F(IteratorTests, IncrementPastEnd) +{ + runLua("result = {1, 2, 3}"); + + luabridge::Iterator it(result()); + while (!it.isNil()) + ++it; + + // Now at end (isNil() == true) - incrementing should be a no-op + EXPECT_TRUE(it.isNil()); + ++it; + EXPECT_TRUE(it.isNil()); +} + TEST_F(IteratorTests, SequenceIteration) { runLua("result = {" diff --git a/Tests/Source/ListTests.cpp b/Tests/Source/ListTests.cpp index dd3d3cd5..7ae0998b 100644 --- a/Tests/Source/ListTests.cpp +++ b/Tests/Source/ListTests.cpp @@ -70,6 +70,30 @@ struct ListTests : TestBase { }; +TEST_F(ListTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(ListTests, GetWithInvalidItem) +{ + lua_createtable(L, 2, 0); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + lua_pushinteger(L, 2); + lua_pushstring(L, "also_not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + TEST_F(ListTests, PassToFunction) { runLua("function foo (list) " diff --git a/Tests/Source/MapTests.cpp b/Tests/Source/MapTests.cpp index 30b3da63..869ae17f 100644 --- a/Tests/Source/MapTests.cpp +++ b/Tests/Source/MapTests.cpp @@ -71,6 +71,27 @@ struct MapTests : TestBase { }; +TEST_F(MapTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(MapTests, GetWithInvalidValue) +{ + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + TEST_F(MapTests, LuaRef) { { diff --git a/Tests/Source/SetTests.cpp b/Tests/Source/SetTests.cpp index 1df87773..79385367 100644 --- a/Tests/Source/SetTests.cpp +++ b/Tests/Source/SetTests.cpp @@ -71,6 +71,15 @@ struct SetTests : TestBase { }; +TEST_F(SetTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + TEST_F(SetTests, LuaRef) { { diff --git a/Tests/Source/StackTests.cpp b/Tests/Source/StackTests.cpp index 585096b8..78a45a45 100644 --- a/Tests/Source/StackTests.cpp +++ b/Tests/Source/StackTests.cpp @@ -5,6 +5,13 @@ #include "TestBase.h" +#include "LuaBridge/Array.h" +#include "LuaBridge/List.h" +#include "LuaBridge/Map.h" +#include "LuaBridge/Set.h" +#include "LuaBridge/UnorderedMap.h" +#include "LuaBridge/Vector.h" + namespace { struct Unregistered {}; } // namespace @@ -2076,11 +2083,11 @@ TEST_F(StackTests, IntArrayType) EXPECT_FALSE(luabridge::isInstance(L, -1)); EXPECT_FALSE(luabridge::isInstance(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); - EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); + EXPECT_TRUE(luabridge::isInstance>(L, -1)); EXPECT_TRUE(luabridge::isInstance(L, -1)); } @@ -2130,11 +2137,11 @@ TEST_F(StackTests, ConstIntArrayType) EXPECT_FALSE(luabridge::isInstance(L, -1)); EXPECT_FALSE(luabridge::isInstance(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); - EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); EXPECT_FALSE(luabridge::isInstance>(L, -1)); + EXPECT_TRUE(luabridge::isInstance>(L, -1)); EXPECT_TRUE(luabridge::isInstance(L, -1)); } @@ -2599,6 +2606,278 @@ TEST_F(StackTests, ConstVoidPointerStackOverflow) ASSERT_FALSE(luabridge::push(L, ptr)); } +TEST_F(StackTests, VoidPointerGetNil) +{ + lua_pushnil(L); + + { + auto result = luabridge::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(nullptr, *result); + } + + { + auto result = luabridge::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(nullptr, *result); + } +} + +TEST_F(StackTests, LongLongType) +{ + long long value = 42LL; + + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(value, *result); + + EXPECT_TRUE(luabridge::isInstance(L, -1)); +} + +TEST_F(StackTests, LongLongStackOverflow) +{ + exhaustStackSpace(); + + ASSERT_FALSE(luabridge::push(L, 42LL)); +} + +TEST_F(StackTests, LongLongInvalidType) +{ + (void)luabridge::Stack::push(L, "not_a_number"); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, LongLongNotFittingGet) +{ + // Push a non-integer float - tointeger returns isValid=0, can't represent as long long + lua_pushnumber(L, 1.5); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, result.error()); +} + +TEST_F(StackTests, LongLongNotFittingPush) +{ + if constexpr (sizeof(long long) > sizeof(lua_Integer)) + { + long long value = static_cast(std::numeric_limits::max()) + 1LL; + auto result = luabridge::push(L, value); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, result.error()); + } +} + +TEST_F(StackTests, UlongLongNotFittingPush) +{ + if constexpr (sizeof(unsigned long long) == sizeof(lua_Integer) && !std::is_unsigned_v) + { + // Value > LLONG_MAX can't fit in signed lua_Integer + auto result = luabridge::push(L, 9223372036854775808ull); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, result.error()); + } +} + +TEST_F(StackTests, UlongLongType) +{ + unsigned long long value = 42ULL; + + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(value, *result); + + EXPECT_TRUE(luabridge::isInstance(L, -1)); +} + +TEST_F(StackTests, UlongLongStackOverflow) +{ + exhaustStackSpace(); + + ASSERT_FALSE(luabridge::push(L, 42ULL)); +} + +TEST_F(StackTests, UlongLongInvalidType) +{ + (void)luabridge::Stack::push(L, "not_a_number"); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, UnsignedIntNotFittingGet) +{ + // Push a negative number - can't fit in unsigned int + lua_pushinteger(L, -1); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, result.error()); +} + +TEST_F(StackTests, LongNotFittingGet) +{ + if constexpr (sizeof(long) < sizeof(lua_Integer)) + { + // Push a value larger than LONG_MAX + lua_pushinteger(L, static_cast(std::numeric_limits::max()) + 1); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, result.error()); + } +} + +TEST_F(StackTests, UnsignedLongInvalidType) +{ + (void)luabridge::Stack::push(L, "not_a_number"); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +#if !LUABRIDGE_STRICT_STACK_CONVERSIONS +TEST_F(StackTests, StringGetFromNumberStackOverflow) +{ + // Push a number (will trigger string coercion path in non-strict mode) + lua_pushnumber(L, 42.0); + + exhaustStackSpace(); + + // In non-strict mode, get tries lua_pushvalue which fails on overflow + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::LuaStackOverflow, result.error()); +} +#endif + +TEST_F(StackTests, FloatNotFittingGet) +{ + // Push lua_Number::max which doesn't fit in float + (void)luabridge::push(L, std::numeric_limits::max()); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::FloatingPointDoesntFitIntoLuaNumber, result.error()); +} + +TEST_F(StackTests, OptionalNulloptStackOverflow) +{ + exhaustStackSpace(); + + std::optional value = std::nullopt; + ASSERT_FALSE(luabridge::push(L, value)); +} + +TEST_F(StackTests, OptionalGetInnerError) +{ + // Push a string (not nil, but can't be converted to int) + (void)luabridge::Stack::push(L, "not_a_number"); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, PairGetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, PairGetFirstElementError) +{ + // Push pair but try to get as pair + auto value = std::make_pair(std::string("not_int"), std::string("second")); + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, TupleGetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, TupleGetFirstElementError) +{ + // Push tuple but try to get as tuple + auto value = std::make_tuple(std::string("not_int"), std::string("second")); + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(StackTests, ErrorCodeMessages) +{ + EXPECT_STREQ("The class is not registered in LuaBridge", + luabridge::makeErrorCode(luabridge::ErrorCode::ClassNotRegistered).message().c_str()); + + EXPECT_STREQ("The lua stack has overflow", + luabridge::makeErrorCode(luabridge::ErrorCode::LuaStackOverflow).message().c_str()); + + EXPECT_STREQ("The lua function invocation raised an error", + luabridge::makeErrorCode(luabridge::ErrorCode::LuaFunctionCallFailed).message().c_str()); + + EXPECT_STREQ("The native integer can't fit inside a lua integer", + luabridge::makeErrorCode(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger).message().c_str()); + + EXPECT_STREQ("The native floating point can't fit inside a lua number", + luabridge::makeErrorCode(luabridge::ErrorCode::FloatingPointDoesntFitIntoLuaNumber).message().c_str()); + + EXPECT_STREQ("The lua object can't be cast to desired type", + luabridge::makeErrorCode(luabridge::ErrorCode::InvalidTypeCast).message().c_str()); + + EXPECT_STREQ("The lua table has different size than expected", + luabridge::makeErrorCode(luabridge::ErrorCode::InvalidTableSizeInCast).message().c_str()); + + // Default case: an unknown error code value + EXPECT_STREQ("Unknown error", + luabridge::detail::ErrorCategory::getInstance().message(9999).c_str()); +} + +TEST_F(StackTests, GetGlobalTyped) +{ + // Test the template getGlobal from Globals.h (not the LuaRef version from LuaRef.h) + lua_pushinteger(L, 42); + lua_setglobal(L, "test_int_global"); + + auto result = luabridge::getGlobal(L, "test_int_global"); + ASSERT_TRUE(result); + EXPECT_EQ(42, *result); +} + +TEST_F(StackTests, SetGlobalFailure) +{ + struct Unregistered2 {}; + + // setGlobal should return false when push fails (non-exception mode only) +#if !LUABRIDGE_HAS_EXCEPTIONS + bool ok = luabridge::setGlobal(L, Unregistered2{}, "test_var"); + EXPECT_FALSE(ok); +#endif +} + TEST_F(StackTests, ResultCheck) { #if LUABRIDGE_HAS_EXCEPTIONS diff --git a/Tests/Source/UnorderedMapTests.cpp b/Tests/Source/UnorderedMapTests.cpp index 512e91ba..2059ee74 100644 --- a/Tests/Source/UnorderedMapTests.cpp +++ b/Tests/Source/UnorderedMapTests.cpp @@ -78,6 +78,59 @@ std::unordered_map processPointers(const std::unordered_map>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedMapTests, GetWithInvalidValue) +{ + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedMapTests, GetWithInvalidKey) +{ + lua_createtable(L, 0, 1); + lua_pushstring(L, "string_key"); + lua_pushinteger(L, 42); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedMapTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::unordered_map{ { "x", 1 }, { "y", 2 } }))); + EXPECT_TRUE((luabridge::isInstance>(L, -1))); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE((luabridge::isInstance>(L, -1))); +} + +TEST_F(UnorderedMapTests, StackOverflow) +{ + exhaustStackSpace(); + + std::unordered_map value{ { "x", 1 }, { "y", 2 } }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + TEST_F(UnorderedMapTests, LuaRef) { { diff --git a/Tests/Source/VectorTests.cpp b/Tests/Source/VectorTests.cpp index 5997278f..435c01dc 100644 --- a/Tests/Source/VectorTests.cpp +++ b/Tests/Source/VectorTests.cpp @@ -119,6 +119,50 @@ TEST_F(VectorTests, PassFromLua) ASSERT_EQ(std::vector({-3, 4}), result>()); } +TEST_F(VectorTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(VectorTests, GetWithInvalidItem) +{ + lua_createtable(L, 2, 0); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + lua_pushinteger(L, 2); + lua_pushstring(L, "also_not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(VectorTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::vector{ 1, 2, 3 }))); + EXPECT_TRUE(luabridge::isInstance>(L, -1)); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE(luabridge::isInstance>(L, -1)); +} + +TEST_F(VectorTests, StackOverflow) +{ + exhaustStackSpace(); + + std::vector value{ 1, 2, 3 }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + #if !LUABRIDGE_HAS_EXCEPTIONS TEST_F(VectorTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) { diff --git a/amalgamate.py b/amalgamate.py index d55f8412..e12f99b5 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import os import re import argparse diff --git a/cobertura.py b/cobertura.py new file mode 100644 index 00000000..9c9ba16e --- /dev/null +++ b/cobertura.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +import sys +import xml.etree.ElementTree as ET +from collections import defaultdict + + +def extract_uncovered(root): + """ + Returns: + dict[str, list[int]] -> filename -> uncovered line numbers + """ + result = defaultdict(list) + + for cls in root.findall(".//class"): + filename = cls.attrib.get("filename", "unknown") + lines = cls.find("lines") + if lines is None: + continue + + for line in lines.findall("line"): + if line.attrib.get("hits") == "0": + result[filename].append(int(line.attrib["number"])) + + return result + + +def compress_ranges(numbers): + """ + Converts sorted list like [1,2,3,5,6,10] -> ["1-3","5-6","10"] + """ + if not numbers: + return [] + + numbers = sorted(set(numbers)) + ranges = [] + + start = prev = numbers[0] + + for n in numbers[1:]: + if n == prev + 1: + prev = n + else: + ranges.append((start, prev)) + start = prev = n + + ranges.append((start, prev)) + + # Format + out = [] + for s, e in ranges: + if s == e: + out.append(f"{s}") + else: + out.append(f"{s}-{e}") + + return out + + +def write_ultra_compact(data, output_path): + """ + Writes: + file:range,range,... + """ + with open(output_path, "w", encoding="utf-8") as f: + for filename in sorted(data.keys()): + ranges = compress_ranges(data[filename]) + if ranges: + f.write(f"{filename}:{','.join(ranges)}\n") + + +def main(): + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} input.xml output.txt") + sys.exit(1) + + input_path = sys.argv[1] + output_path = sys.argv[2] + + tree = ET.parse(input_path) + root = tree.getroot() + + uncovered = extract_uncovered(root) + write_ultra_compact(uncovered, output_path) + + +if __name__ == "__main__": + main() \ No newline at end of file From b4c60e3e4d1610ac3d53e52c694c4489ae0a56d3 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Mon, 30 Mar 2026 17:13:56 +0200 Subject: [PATCH 02/19] Comment coverage to txt for now --- .github/workflows/coverage.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5ea9bb59..1da7cf93 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -491,9 +491,9 @@ jobs: working-directory: ${{runner.workspace}}/build run: lcov2xml coverage/merged.info -o coverage/cobertura.xml - - name: Convert to Coverage TXT - working-directory: ${{runner.workspace}}/build - run: python3 ${{runner.workspace}}/cobertura.py coverage/cobertura.info coverage/coverage.txt + #- name: Convert to Coverage TXT + # working-directory: ${{runner.workspace}}/build + # run: python3 ${{runner.workspace}}/cobertura.py coverage/cobertura.info coverage/coverage.txt - name: Upload Cobertura XML uses: actions/upload-artifact@v4 @@ -501,11 +501,11 @@ jobs: name: cobertura-coverage path: ${{runner.workspace}}/build/coverage/cobertura.xml - - name: Upload Coverage TXT - uses: actions/upload-artifact@v4 - with: - name: text-coverage - path: ${{runner.workspace}}/build/coverage/coverage.txt + #- name: Upload Coverage TXT + # uses: actions/upload-artifact@v4 + # with: + # name: text-coverage + # path: ${{runner.workspace}}/build/coverage/coverage.txt - name: Coveralls uses: coverallsapp/github-action@master From 0809e539599e43ab9535473ca4250390e5d67ce7 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 09:44:12 +0200 Subject: [PATCH 03/19] Bump testing and coverage --- .github/workflows/build_asan.yml | 68 ++++++++ .github/workflows/build_linux.yml | 170 +++---------------- .github/workflows/build_macos.yml | 160 +++--------------- .github/workflows/build_tsan.yml | 68 ++++++++ .github/workflows/build_ubsan.yml | 68 ++++++++ .github/workflows/build_windows.yml | 170 +++---------------- .github/workflows/coverage.yml | 242 +++------------------------- CMakeLists.txt | 1 + README.md | 18 +-- Source/LuaBridge/Dump.h | 4 +- Tests/CMakeLists.txt | 32 ++++ Tests/Source/DumpTests.cpp | 15 ++ Tests/Source/EnumTests.cpp | 12 ++ Tests/Source/LuaRefTests.cpp | 171 ++++++++++++++++++++ Tests/Source/NamespaceTests.cpp | 16 ++ Tests/Source/StackTests.cpp | 23 +++ justfile | 7 +- 17 files changed, 576 insertions(+), 669 deletions(-) create mode 100644 .github/workflows/build_asan.yml create mode 100644 .github/workflows/build_tsan.yml create mode 100644 .github/workflows/build_ubsan.yml diff --git a/.github/workflows/build_asan.yml b/.github/workflows/build_asan.yml new file mode 100644 index 00000000..80899994 --- /dev/null +++ b/.github/workflows/build_asan.yml @@ -0,0 +1,68 @@ +name: Build ASAN + +on: + push: + paths: + - "**/workflows/build_asan.yml" + - "**/Source/**" + - "**/Tests/**" + - "**/ThirdParty/**" + - "**/CMakeLists.txt" + - "**/.gitmodules" + - "!**/*.md" + - "!**/*.txt" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + lua: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } + + steps: + + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Dependencies + run: sudo apt-get -y install ninja-build + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_SANITIZE=address -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} + working-directory: ${{runner.workspace}}/build + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} + working-directory: ${{runner.workspace}}/build/Tests + env: + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + run: | + ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index f916a7c3..f692a423 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -20,148 +20,18 @@ env: BUILD_TYPE: Release jobs: - lua51: + lua: runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install Dependencies - run: sudo apt-get -y install ninja-build - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.1 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests51 \ - LuaBridgeTests51LuaC \ - LuaBridgeTests51Noexcept \ - LuaBridgeTests51LuaCNoexcept - - - name: Test Lua 5.1 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests51 - ./LuaBridgeTests51LuaC - ./LuaBridgeTests51Noexcept - ./LuaBridgeTests51LuaCNoexcept - - lua52: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install Dependencies - run: sudo apt-get -y install ninja-build - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.2 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests52 \ - LuaBridgeTests52LuaC \ - LuaBridgeTests52Noexcept \ - LuaBridgeTests52LuaCNoexcept - - - name: Test Lua 5.2 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests52 - ./LuaBridgeTests52LuaC - ./LuaBridgeTests52Noexcept - ./LuaBridgeTests52LuaCNoexcept + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } - lua53: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install Dependencies - run: sudo apt-get -y install ninja-build - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.3 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests53 \ - LuaBridgeTests53LuaC \ - LuaBridgeTests53Noexcept \ - LuaBridgeTests53LuaCNoexcept - - - name: Test Lua 5.3 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests53 - ./LuaBridgeTests53LuaC - ./LuaBridgeTests53Noexcept - ./LuaBridgeTests53LuaCNoexcept - - lua54: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install Dependencies - run: sudo apt-get -y install ninja-build - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.4 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests54 \ - LuaBridgeTests54LuaC \ - LuaBridgeTests54Noexcept \ - LuaBridgeTests54LuaCNoexcept - - - name: Test Lua 5.4 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests54 - ./LuaBridgeTests54LuaC - ./LuaBridgeTests54Noexcept - ./LuaBridgeTests54LuaCNoexcept - - lua55: - runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -178,22 +48,22 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - name: Build Lua 5.5 + - name: Build Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests55 \ - LuaBridgeTests55LuaC \ - LuaBridgeTests55Noexcept \ - LuaBridgeTests55LuaCNoexcept + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua 5.5 + - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests run: | - ./LuaBridgeTests55 - ./LuaBridgeTests55LuaC - ./LuaBridgeTests55Noexcept - ./LuaBridgeTests55LuaCNoexcept + ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept luajit: runs-on: ubuntu-latest diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 172a1854..d5a01e70 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -20,136 +20,18 @@ env: BUILD_TYPE: Release jobs: - lua51: + lua: runs-on: macos-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.1 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests51 \ - LuaBridgeTests51LuaC \ - LuaBridgeTests51Noexcept \ - LuaBridgeTests51LuaCNoexcept - - - name: Test Lua 5.1 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests51 - ./LuaBridgeTests51LuaC - ./LuaBridgeTests51Noexcept - ./LuaBridgeTests51LuaCNoexcept - - lua52: - runs-on: macos-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.2 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests52 \ - LuaBridgeTests52LuaC \ - LuaBridgeTests52Noexcept \ - LuaBridgeTests52LuaCNoexcept - - - name: Test Lua 5.2 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests52 - ./LuaBridgeTests52LuaC - ./LuaBridgeTests52Noexcept - ./LuaBridgeTests52LuaCNoexcept - - lua53: - runs-on: macos-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.3 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests53 \ - LuaBridgeTests53LuaC \ - LuaBridgeTests53Noexcept \ - LuaBridgeTests53LuaCNoexcept - - - name: Test Lua 5.3 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests53 - ./LuaBridgeTests53LuaC - ./LuaBridgeTests53Noexcept - ./LuaBridgeTests53LuaCNoexcept - - lua54: - runs-on: macos-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - - name: Build Lua 5.4 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests54 \ - LuaBridgeTests54LuaC \ - LuaBridgeTests54Noexcept \ - LuaBridgeTests54LuaCNoexcept - - - name: Test Lua 5.4 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests54 - ./LuaBridgeTests54LuaC - ./LuaBridgeTests54Noexcept - ./LuaBridgeTests54LuaCNoexcept - - lua55: - runs-on: macos-latest steps: - uses: actions/checkout@v6 @@ -163,22 +45,22 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - name: Build Lua 5.5 + - name: Build Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests55 \ - LuaBridgeTests55LuaC \ - LuaBridgeTests55Noexcept \ - LuaBridgeTests55LuaCNoexcept + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua 5.5 + - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests run: | - ./LuaBridgeTests55 - ./LuaBridgeTests55LuaC - ./LuaBridgeTests55Noexcept - ./LuaBridgeTests55LuaCNoexcept + ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept luajit: runs-on: macos-latest @@ -246,7 +128,7 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja - - name: Build LuaJIT + - name: Build Ravi working-directory: ${{runner.workspace}}/build run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsRavi diff --git a/.github/workflows/build_tsan.yml b/.github/workflows/build_tsan.yml new file mode 100644 index 00000000..acbe697f --- /dev/null +++ b/.github/workflows/build_tsan.yml @@ -0,0 +1,68 @@ +name: Build TSAN + +on: + push: + paths: + - "**/workflows/build_tsan.yml" + - "**/Source/**" + - "**/Tests/**" + - "**/ThirdParty/**" + - "**/CMakeLists.txt" + - "**/.gitmodules" + - "!**/*.md" + - "!**/*.txt" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + lua: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } + + steps: + + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Dependencies + run: sudo apt-get -y install ninja-build + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_SANITIZE=thread -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} + working-directory: ${{runner.workspace}}/build + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} + working-directory: ${{runner.workspace}}/build/Tests + env: + TSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + run: | + ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept diff --git a/.github/workflows/build_ubsan.yml b/.github/workflows/build_ubsan.yml new file mode 100644 index 00000000..8c9ff510 --- /dev/null +++ b/.github/workflows/build_ubsan.yml @@ -0,0 +1,68 @@ +name: Build UBSAN + +on: + push: + paths: + - "**/workflows/build_ubsan.yml" + - "**/Source/**" + - "**/Tests/**" + - "**/ThirdParty/**" + - "**/CMakeLists.txt" + - "**/.gitmodules" + - "!**/*.md" + - "!**/*.txt" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + lua: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } + + steps: + + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Dependencies + run: sudo apt-get -y install ninja-build + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_SANITIZE=undefined -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} + working-directory: ${{runner.workspace}}/build + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} + working-directory: ${{runner.workspace}}/build/Tests + env: + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + run: | + ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index edfb867a..01d53534 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -20,148 +20,18 @@ env: BUILD_TYPE: Release jobs: - lua51: + lua: runs-on: windows-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - - - name: Build Lua 5.1 - working-directory: ${{runner.workspace}}/build - shell: bash - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests51 \ - LuaBridgeTests51LuaC \ - LuaBridgeTests51Noexcept \ - LuaBridgeTests51LuaCNoexcept - - - name: Test Lua 5.1 - working-directory: ${{runner.workspace}}/build/Tests/Release - shell: bash - run: | - ./LuaBridgeTests51.exe - ./LuaBridgeTests51LuaC.exe - ./LuaBridgeTests51Noexcept.exe - ./LuaBridgeTests51LuaCNoexcept.exe - - lua52: - runs-on: windows-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - - - name: Build Lua 5.2 - working-directory: ${{runner.workspace}}/build - shell: bash - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests52 \ - LuaBridgeTests52LuaC \ - LuaBridgeTests52Noexcept \ - LuaBridgeTests52LuaCNoexcept + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } - - name: Test Lua 5.2 - working-directory: ${{runner.workspace}}/build/Tests/Release - shell: bash - run: | - ./LuaBridgeTests52.exe - ./LuaBridgeTests52LuaC.exe - ./LuaBridgeTests52Noexcept.exe - ./LuaBridgeTests52LuaCNoexcept.exe - - lua53: - runs-on: windows-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - - - name: Build Lua 5.3 - working-directory: ${{runner.workspace}}/build - shell: bash - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests53 \ - LuaBridgeTests53LuaC \ - LuaBridgeTests53Noexcept \ - LuaBridgeTests53LuaCNoexcept - - - name: Test Lua 5.3 - working-directory: ${{runner.workspace}}/build/Tests/Release - shell: bash - run: | - ./LuaBridgeTests53.exe - ./LuaBridgeTests53LuaC.exe - ./LuaBridgeTests53Noexcept.exe - ./LuaBridgeTests53LuaCNoexcept.exe - - lua54: - runs-on: windows-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - - - name: Build Lua 5.4 - working-directory: ${{runner.workspace}}/build - shell: bash - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests54 \ - LuaBridgeTests54LuaC \ - LuaBridgeTests54Noexcept \ - LuaBridgeTests54LuaCNoexcept - - - name: Test Lua 5.4 - working-directory: ${{runner.workspace}}/build/Tests/Release - shell: bash - run: | - ./LuaBridgeTests54.exe - ./LuaBridgeTests54LuaC.exe - ./LuaBridgeTests54Noexcept.exe - ./LuaBridgeTests54LuaCNoexcept.exe - - lua55: - runs-on: windows-latest steps: - uses: actions/checkout@v6 @@ -176,24 +46,24 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - - name: Build Lua 5.5 + - name: Build Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build shell: bash run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests55 \ - LuaBridgeTests55LuaC \ - LuaBridgeTests55Noexcept \ - LuaBridgeTests55LuaCNoexcept + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua 5.5 + - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests/Release shell: bash run: | - ./LuaBridgeTests55.exe - ./LuaBridgeTests55LuaC.exe - ./LuaBridgeTests55Noexcept.exe - ./LuaBridgeTests55LuaCNoexcept.exe + ./LuaBridgeTests${{ matrix.lua.suffix }}.exe + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC.exe + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept.exe + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept.exe luajit: runs-on: windows-latest diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1da7cf93..fd35e509 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -20,212 +20,18 @@ env: BUILD_TYPE: Debug jobs: - lua51: + lua: runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install lcov - run: sudo apt-get install -y lcov ninja-build - - - name: Create Build Environment - run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja - - - name: Build Lua 5.1 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests51 \ - LuaBridgeTests51LuaC \ - LuaBridgeTests51Noexcept \ - LuaBridgeTests51LuaCNoexcept - - - name: Test Lua 5.1 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests51 - ./LuaBridgeTests51LuaC - ./LuaBridgeTests51Noexcept - ./LuaBridgeTests51LuaCNoexcept - - - name: Coverage Lua 5.1 - working-directory: ${{runner.workspace}}/build - run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ - --ignore-errors mismatch,unused \ - --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/lua51.info" - - - name: Cache Lcov Files - uses: actions/cache@v5 - with: - path: "${{runner.workspace}}/build/coverage/*.info" - key: lcov-lua51-${{runner.os}}-${{github.sha}} - - lua52: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true + strategy: + fail-fast: false + matrix: + lua: + - { version: "5.1", suffix: "51" } + - { version: "5.2", suffix: "52" } + - { version: "5.3", suffix: "53" } + - { version: "5.4", suffix: "54" } + - { version: "5.5", suffix: "55" } - - name: Install lcov - run: sudo apt-get install -y lcov ninja-build - - - name: Create Build Environment - run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja - - - name: Build Lua 5.2 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests52 \ - LuaBridgeTests52LuaC \ - LuaBridgeTests52Noexcept \ - LuaBridgeTests52LuaCNoexcept - - - name: Test Lua 5.2 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests52 - ./LuaBridgeTests52LuaC - ./LuaBridgeTests52Noexcept - ./LuaBridgeTests52LuaCNoexcept - - - name: Coverage Lua 5.2 - working-directory: ${{runner.workspace}}/build - run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ - --ignore-errors mismatch,unused \ - --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/lua52.info" - - - name: Cache Lcov Files - uses: actions/cache@v5 - with: - path: "${{runner.workspace}}/build/coverage/*.info" - key: lcov-lua52-${{runner.os}}-${{github.sha}} - - lua53: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install lcov - run: sudo apt-get install -y lcov ninja-build - - - name: Create Build Environment - run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja - - - name: Build Lua 5.3 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests53 \ - LuaBridgeTests53LuaC \ - LuaBridgeTests53Noexcept \ - LuaBridgeTests53LuaCNoexcept - - - name: Test Lua 5.3 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests53 - ./LuaBridgeTests53LuaC - ./LuaBridgeTests53Noexcept - ./LuaBridgeTests53LuaCNoexcept - - - name: Coverage Lua 5.3 - working-directory: ${{runner.workspace}}/build - run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ - --ignore-errors mismatch,unused \ - --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/lua53.info" - - - name: Cache Lcov Files - uses: actions/cache@v5 - with: - path: "${{runner.workspace}}/build/coverage/*.info" - key: lcov-lua53-${{runner.os}}-${{github.sha}} - - lua54: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install lcov - run: sudo apt-get install -y lcov ninja-build - - - name: Create Build Environment - run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage - - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja - - - name: Build Lua 5.4 - working-directory: ${{runner.workspace}}/build - run: | - cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests54 \ - LuaBridgeTests54LuaC \ - LuaBridgeTests54Noexcept \ - LuaBridgeTests54LuaCNoexcept - - - name: Test Lua 5.4 - working-directory: ${{runner.workspace}}/build/Tests - run: | - ./LuaBridgeTests54 - ./LuaBridgeTests54LuaC - ./LuaBridgeTests54Noexcept - ./LuaBridgeTests54LuaCNoexcept - - - name: Coverage Lua 5.4 - working-directory: ${{runner.workspace}}/build - run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ - --ignore-errors mismatch,unused \ - --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/lua54.info" - - - name: Cache Lcov Files - uses: actions/cache@v5 - with: - path: "${{runner.workspace}}/build/coverage/*.info" - key: lcov-lua54-${{runner.os}}-${{github.sha}} - - lua55: - runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -244,36 +50,36 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja - - name: Build Lua 5.5 + - name: Build Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTests55 \ - LuaBridgeTests55LuaC \ - LuaBridgeTests55Noexcept \ - LuaBridgeTests55LuaCNoexcept + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua 5.5 + - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests run: | - ./LuaBridgeTests55 - ./LuaBridgeTests55LuaC - ./LuaBridgeTests55Noexcept - ./LuaBridgeTests55LuaCNoexcept + ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC + ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Coverage Lua 5.5 + - name: Coverage Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build run: | lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch,unused \ --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/lua55.info" + -o "coverage/lua${{ matrix.lua.suffix }}.info" - name: Cache Lcov Files uses: actions/cache@v5 with: path: "${{runner.workspace}}/build/coverage/*.info" - key: lcov-lua55-${{runner.os}}-${{github.sha}} + key: lcov-lua${{ matrix.lua.suffix }}-${{runner.os}}-${{github.sha}} luajit: runs-on: ubuntu-latest @@ -408,7 +214,7 @@ jobs: coveralls: runs-on: ubuntu-latest - needs: [lua51, lua52, lua53, lua54, lua55, luajit, luau, ravi] + needs: [lua, luajit, luau, ravi] steps: - uses: actions/checkout@v6 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index ccee8949..b0d83295 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ find_program (GENHTML_EXECUTABLE genhtml) cmake_dependent_option (LUABRIDGE_TESTING "Build tests" ON "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) cmake_dependent_option (LUABRIDGE_COVERAGE "Enable coverage" OFF "LUABRIDGE_TESTING;FIND_EXECUTABLE;LCOV_EXECUTABLE;GENHTML_EXECUTABLE" OFF) cmake_dependent_option (LUABRIDGE_BENCHMARKS "Build benchmark executable" OFF "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) +set (LUABRIDGE_SANITIZE "" CACHE STRING "Sanitizer to enable (address, undefined, thread)") add_subdirectory (Source) diff --git a/README.md b/README.md index dc83e6f5..51ae98a8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,15 @@
+## Status + +![Build MacOS](https://github.com/kunitoki/LuaBridge3/workflows/Build%20MacOS/badge.svg?branch=master) +![Build Windows](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Windows/badge.svg?branch=master) +![Build Linux](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Linux/badge.svg?branch=master) + +## Code Coverage +[![Coverage Status](https://coveralls.io/repos/github/kunitoki/LuaBridge3/badge.svg?branch=master&kill_cache=1)](https://coveralls.io/github/kunitoki/LuaBridge3?branch=master) + # LuaBridge 3.0 [LuaBridge3](https://github.com/kunitoki/LuaBridge3) is a lightweight and dependency-free library for mapping data, @@ -77,15 +86,6 @@ LuaBridge3 offers a set of improvements compared to vanilla LuaBridge: * Simplified registration of enum types via the `luabridge::Enum` stack wrapper. * Opt-out handling of safe stack space checks (automatically avoids exhausting lua stack space when pushing values!). -## Status - -![Build MacOS](https://github.com/kunitoki/LuaBridge3/workflows/Build%20MacOS/badge.svg?branch=master) -![Build Windows](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Windows/badge.svg?branch=master) -![Build Linux](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Linux/badge.svg?branch=master) - -## Code Coverage -[![Coverage Status](https://coveralls.io/repos/github/kunitoki/LuaBridge3/badge.svg?branch=master&kill_cache=1)](https://coveralls.io/github/kunitoki/LuaBridge3?branch=master) - ## Documentation Please read the [LuaBridge3 Reference Manual](https://kunitoki.github.io/LuaBridge3/Manual) for more details on the API. diff --git a/Source/LuaBridge/Dump.h b/Source/LuaBridge/Dump.h index 851952af..a9899f7c 100644 --- a/Source/LuaBridge/Dump.h +++ b/Source/LuaBridge/Dump.h @@ -33,7 +33,9 @@ inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned l */ inline void dumpValue(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr) { - const int type = lua_type(L, index); + const int stackTop = lua_gettop(L); + const int absIndex = (index > 0) ? index : (index < 0 ? stackTop + index + 1 : 0); + const int type = (absIndex < 1 || absIndex > stackTop) ? LUA_TNONE : lua_type(L, index); switch (type) { case LUA_TNIL: diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 6884365b..e7bca939 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -296,6 +296,33 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F setup_target_for_coverage (${LUABRIDGE_TEST_NAME} ${SOURCE_LOCATION} LuaBridge) endif () + if (LUABRIDGE_SANITIZE AND (APPLE OR UNIX)) + if ("${CMAKE_GENERATOR}" STREQUAL "Xcode") + set_target_properties (${LUABRIDGE_TEST_NAME} PROPERTIES XCODE_GENERATE_SCHEME ON) + if (LUABRIDGE_SANITIZE STREQUAL "address") + set_target_properties (${LUABRIDGE_TEST_NAME} PROPERTIES + XCODE_SCHEME_ADDRESS_SANITIZER ON + XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN ON + XCODE_SCHEME_ENVIRONMENT "ASAN_OPTIONS=detect_leaks=0:detect_odr_violation=0") + elseif (LUABRIDGE_SANITIZE STREQUAL "undefined") + set_target_properties (${LUABRIDGE_TEST_NAME} PROPERTIES + XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER ON) + elseif (LUABRIDGE_SANITIZE STREQUAL "thread") + set_target_properties (${LUABRIDGE_TEST_NAME} PROPERTIES + XCODE_SCHEME_THREAD_SANITIZER ON) + endif () + else () + set (LUABRIDGE_SANITIZE_FLAGS -fsanitize=${LUABRIDGE_SANITIZE}) + if (LUABRIDGE_SANITIZE STREQUAL "address") + list (APPEND LUABRIDGE_SANITIZE_FLAGS -fno-omit-frame-pointer) + endif () + target_compile_options (${LUABRIDGE_TEST_NAME} PRIVATE ${LUABRIDGE_SANITIZE_FLAGS}) + target_link_options (${LUABRIDGE_TEST_NAME} PRIVATE -fsanitize=${LUABRIDGE_SANITIZE}) + target_compile_options (${LUABRIDGE_TESTLIB_NAME} PRIVATE ${LUABRIDGE_SANITIZE_FLAGS}) + target_link_options (${LUABRIDGE_TESTLIB_NAME} PRIVATE -fsanitize=${LUABRIDGE_SANITIZE}) + endif () + endif () + target_include_directories (${LUABRIDGE_TEST_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${SOURCE_LOCATION} @@ -364,6 +391,11 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F add_test(NAME ${LUABRIDGE_TEST_NAME} COMMAND ${LUABRIDGE_TEST_NAME}) + if (UNIX AND LUABRIDGE_SANITIZE STREQUAL "address") + set_tests_properties (${LUABRIDGE_TEST_NAME} PROPERTIES + ENVIRONMENT "ASAN_OPTIONS=detect_leaks=0:detect_odr_violation=0") + endif () + endmacro (add_test_app) # ====================================================== Real Unit Tests diff --git a/Tests/Source/DumpTests.cpp b/Tests/Source/DumpTests.cpp index d4de1dcc..6f2570ca 100644 --- a/Tests/Source/DumpTests.cpp +++ b/Tests/Source/DumpTests.cpp @@ -303,6 +303,21 @@ TEST_F(DumpTests, DumpTableDirectWithNewLine) } } +TEST_F(DumpTests, DumpNone) +{ + std::stringstream ss; + + // Index 0 is invalid in Lua; dumpValue treats out-of-range indices as LUA_TNONE, + // falling through to the default case in its switch statement. + luabridge::dumpValue(L, 0, 0, 0, false, ss); + EXPECT_FALSE(ss.str().empty()); + + ss.clear(); + ss.str(""); + luabridge::dumpValue(L, 0, 0, 0, true, ss); + EXPECT_TRUE(ss.str().find("\n") != std::string::npos); +} + TEST_F(DumpTests, DumpState) { std::stringstream ss; diff --git a/Tests/Source/EnumTests.cpp b/Tests/Source/EnumTests.cpp index 9b3b773f..1aa777ea 100644 --- a/Tests/Source/EnumTests.cpp +++ b/Tests/Source/EnumTests.cpp @@ -127,6 +127,18 @@ TEST_F(EnumTests, RegisteredStackInvalidValue) EXPECT_TRUE(luabridge::get(L, 1)); } +TEST_F(EnumTests, RegisteredStackGetNonNumber) +{ + // When the Lua value is not a number, the underlying Stack::get fails, + // causing Enum::get to return result.error() at Enum.h:42. + lua_pushstring(L, "not_a_number"); + + EXPECT_FALSE(luabridge::get(L, 1)); // constrained enum: underlying get fails + EXPECT_FALSE(luabridge::get(L, 1)); + EXPECT_FALSE(luabridge::get(L, 1)); // unconstrained enum: same underlying failure + EXPECT_FALSE(luabridge::get(L, 1)); +} + TEST_F(EnumTests, MethodTakingEnum) { luabridge::getGlobalNamespace(L) diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index bccdbe00..586aff6a 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -1160,3 +1160,174 @@ TEST_F(LuaRefTests, TableItemOperatorIndexAdoptPathAndRawRoundTrip) EXPECT_EQ(1234, nested); EXPECT_EQ(stackTopBefore, lua_gettop(L)); } + +TEST_F(LuaRefTests, GetClassNameNoMetatable) +{ + // A raw userdata with no metatable causes lua_getmetatable to return 0, + // hitting the early-return nullopt at LuaRef.h:311. + lua_newuserdata(L, 100); + auto ref = luabridge::LuaRef::fromStack(L); // fromStack pops the userdata + + EXPECT_FALSE(ref.getClassName()); +} + +TEST_F(LuaRefTests, CallReturningTupleSuccess) +{ + // Exercises the success path of decodeTupleResult (Invoke.h:63). + runLua("result = function() return 42, 'hello' end"); + auto r = result().call>(); + ASSERT_TRUE(r); + EXPECT_EQ(42, std::get<0>(*r)); + EXPECT_EQ("hello", std::get<1>(*r)); +} + +TEST_F(LuaRefTests, CallReturningTupleWrongType) +{ + // Exercises the error path of decodeTupleResult (Invoke.h:58-59): + // the first returned value cannot be converted to int. + runLua("result = function() return 'not_an_int', 'hello' end"); + auto r = result().call>(); + EXPECT_FALSE(r); +} + +TEST_F(LuaRefTests, ToStringStackOverflow) +{ + // Exercises the early return in LuaRefBase::tostring (LuaRef.h:117) + // when the Lua stack is exhausted. + runLua("result = 42"); + auto ref = result(); // capture ref before exhausting the stack + exhaustStackSpace(); + std::string s = ref.tostring(); + EXPECT_TRUE(s.empty()); + lua_settop(L, 0); // restore stack so ref's destructor can call luaL_unref safely +} + +TEST_F(LuaRefTests, GetMetatableOnNil) +{ + // Covers LuaRef.h:391 - early return when the ref is nil + // Use LuaRef(L) which creates an invalid (nil-like) ref without touching the Lua stack + luabridge::LuaRef nilRef(L); + + EXPECT_TRUE(nilRef.isNil()); + auto mt = nilRef.getMetatable(); + EXPECT_TRUE(mt.isNil()); +} + +TEST_F(LuaRefTests, NewTableStackOverflow) +{ + // Covers LuaRef.h:1184-1185 - early return when stack is exhausted + exhaustStackSpace(); + auto t = luabridge::LuaRef::newTable(L); + EXPECT_FALSE(t.isValid()); +} + +TEST_F(LuaRefTests, NewFunctionStackOverflow) +{ + // Covers LuaRef.h:1208-1209 - early return when stack is exhausted + exhaustStackSpace(); + auto f = luabridge::LuaRef::newFunction(L, [](lua_State*) -> int { return 0; }); + EXPECT_FALSE(f.isValid()); +} + +TEST_F(LuaRefTests, GetGlobalStackOverflow) +{ + // Covers LuaRef.h:1230-1231 - early return when stack is exhausted + exhaustStackSpace(); + auto g = luabridge::LuaRef::getGlobal(L, "print"); + EXPECT_FALSE(g.isValid()); +} + +TEST_F(LuaRefTests, PushStackOverflow) +{ + // Covers LuaRef.h:1338-1339 - early return in push() when stack is exhausted + runLua("result = 42"); + auto ref = result(); + exhaustStackSpace(); + // push() should silently return without pushing + const int topBefore = lua_gettop(L); + ref.push(L); + EXPECT_EQ(topBefore, lua_gettop(L)); + lua_settop(L, 0); // restore stack so ref's destructor can call luaL_unref safely +} + +TEST_F(LuaRefTests, SetFieldPushFailure) +{ + // Covers LuaRef.h:1466-1467 - setField returns false when value push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = {}"); + auto t = result(); + long double huge = std::numeric_limits::max(); + EXPECT_FALSE(t.setField("key", huge)); + } +} + +TEST_F(LuaRefTests, RawSetFieldPushFailure) +{ + // Covers LuaRef.h:1510-1511 - rawsetField returns false when value push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = {}"); + auto t = result(); + long double huge = std::numeric_limits::max(); + EXPECT_FALSE(t.rawsetField("key", huge)); + } +} + +TEST_F(LuaRefTests, RawGetPushFailure) +{ + // Covers LuaRef.h:1419-1420 - rawget returns nil LuaRef when key push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = {}"); + auto t = result(); + long double huge = std::numeric_limits::max(); + auto r = t.rawget(huge); + EXPECT_TRUE(r.isNil()); + } +} + +TEST_F(LuaRefTests, OperatorIndexPushFailure) +{ + // Covers LuaRef.h:1390-1391 - operator[] returns default TableItem when key push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = {}"); + auto t = result(); + long double huge = std::numeric_limits::max(); + // The subscript with a failing push returns a default TableItem + auto item = t[huge]; + (void)item; // just ensure we don't crash + } +} + +TEST_F(LuaRefTests, AppendPushFailure) +{ + // Covers LuaRef.h:611-612 - append returns false when element push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = {}"); + auto t = result(); + long double huge = std::numeric_limits::max(); + EXPECT_FALSE(t.append(huge)); + } +} + +TEST_F(LuaRefTests, ComparisonOperatorPushFailure) +{ + // Covers LuaRef.h:421 (operator==), 458 (operator<), 485 (operator<=), + // 511 (operator>), 539 (operator>=), 566 (rawequal) - early return false when rhs push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = 1.0"); + auto ref = result(); + long double huge = std::numeric_limits::max(); + + EXPECT_FALSE(ref == huge); + EXPECT_FALSE(ref < huge); + EXPECT_FALSE(ref <= huge); + EXPECT_FALSE(ref > huge); + EXPECT_FALSE(ref >= huge); + EXPECT_FALSE(ref.rawequal(huge)); + } +} diff --git a/Tests/Source/NamespaceTests.cpp b/Tests/Source/NamespaceTests.cpp index 6c4d60bc..2cfd9b6c 100644 --- a/Tests/Source/NamespaceTests.cpp +++ b/Tests/Source/NamespaceTests.cpp @@ -731,3 +731,19 @@ TEST_F(NamespaceTests, FastCallFunctions) } #endif // _M_IX86 + +#if LUABRIDGE_HAS_EXCEPTIONS +TEST_F(NamespaceTests, EndGlobalNamespaceThrows) +{ + // endNamespace() on the global namespace is a logic error (Namespace.h:1663-1665). + EXPECT_THROW(luabridge::getGlobalNamespace(L).endNamespace(), std::logic_error); +} + +TEST_F(NamespaceTests, AddPropertyGetterSetterOnGlobalThrows) +{ + // addProperty(getter, setter) on the global namespace is a logic error (Namespace.h:1754-1758). + EXPECT_THROW( + luabridge::getGlobalNamespace(L).addProperty("p", [] { return 1; }, [](int) {}), + std::logic_error); +} +#endif diff --git a/Tests/Source/StackTests.cpp b/Tests/Source/StackTests.cpp index 78a45a45..de8b6d64 100644 --- a/Tests/Source/StackTests.cpp +++ b/Tests/Source/StackTests.cpp @@ -2913,7 +2913,30 @@ TEST_F(StackTests, TypeResultValueOr) EXPECT_EQ(42, result2.valueOr(1337)); } +TEST_F(StackTests, ArrayPushElementFailure) +{ + // When pushing a fixed-size array, if an element push fails the error is + // propagated immediately (Stack.h:1330). + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + long double arr[2] = { std::numeric_limits::max(), 1.0L }; + auto result = luabridge::push(L, arr); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::FloatingPointDoesntFitIntoLuaNumber, result.error()); + } +} + #if LUABRIDGE_HAS_EXCEPTIONS +TEST_F(StackTests, PcallFailureThrowsException) +{ + luabridge::lua_pushcfunction_x(L, [](lua_State* L) -> int { + luaL_error(L, "deliberate error"); + return 0; + }, ""); + + EXPECT_THROW(luabridge::pcall(L, 0, 0), luabridge::LuaException); +} + TEST_F(StackTests, ResultThrowOnError) { exhaustStackSpace(); diff --git a/justfile b/justfile index a038c84c..eaeb348e 100644 --- a/justfile +++ b/justfile @@ -4,13 +4,16 @@ default: generate: cmake -G Xcode -B Build . -clean: - rm -rf Build +sanitize: + cmake -G Xcode -B Build -DLUABRIDGE_SANITIZE=address . benchmark: cmake -G Xcode -B Build -DLUABRIDGE_BENCHMARKS=ON . cmake --build Build --config Release --target LuaBridgeBenchmarks -j8 ./Build/Benchmarks/Release/LuaBridgeBenchmarks 1000000 +clean: + rm -rf Build + amalgamate: python3 amalgamate.py From fd8fe4675880d3f6377189c7ff2b9e9cf6006684 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 09:48:26 +0200 Subject: [PATCH 04/19] Updated readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51ae98a8..944de2e0 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ ## Status -![Build MacOS](https://github.com/kunitoki/LuaBridge3/workflows/Build%20MacOS/badge.svg?branch=master) -![Build Windows](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Windows/badge.svg?branch=master) -![Build Linux](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Linux/badge.svg?branch=master) +[![MacOS](https://github.com/kunitoki/LuaBridge3/workflows/Build%20MacOS/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_macos.yml) +[![Windows](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Windows/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_windows.yml) +[![Linux](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Linux/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_linux.yml) +[![UBSAN](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_ubsan.yml/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_ubsan.yml) +[![ASAN](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_asan.yml/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_asan.yml) +[![TSAN](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_tsan.yml/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_tsan.yml) ## Code Coverage [![Coverage Status](https://coveralls.io/repos/github/kunitoki/LuaBridge3/badge.svg?branch=master&kill_cache=1)](https://coveralls.io/github/kunitoki/LuaBridge3?branch=master) From 458cfd6e2f0029b5d61933aa0083c860479f07dd Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 09:49:36 +0200 Subject: [PATCH 05/19] Fix warning --- Source/LuaBridge/detail/CFunctions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index a0eddd99..f43626e3 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1852,7 +1852,7 @@ bool overload_check_one_arg(lua_State* L, int& idx) template bool overload_check_args_impl(lua_State* L, int start, std::index_sequence) { - int idx = start; + [[maybe_unused]] int idx = start; return (overload_check_one_arg>(L, idx) && ...); } From 74e71db17dbb0aa59f068466c5ba768188aa84e3 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 09:52:17 +0200 Subject: [PATCH 06/19] Fix warnings --- Source/LuaBridge/detail/Userdata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/LuaBridge/detail/Userdata.h b/Source/LuaBridge/detail/Userdata.h index 3c8c44fa..c8ebde7c 100644 --- a/Source/LuaBridge/detail/Userdata.h +++ b/Source/LuaBridge/detail/Userdata.h @@ -97,7 +97,7 @@ class Userdata lua_rawgetp_x(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: registry metatable (rt) | nil const bool classIsRegistered = lua_istable(L, -1); - const char* expected = "unregistered class"; + [[maybe_unused]] const char* expected = "unregistered class"; if (classIsRegistered) { lua_rawgetp_x(L, -1, getTypeKey()); // Stack: rt, registry type From 489908fbdc405e0f75ded8a2013cbf6b1d50a40c Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 09:57:47 +0200 Subject: [PATCH 07/19] Fix issue with dll tests --- Tests/Source/SharedCode.cpp | 2 -- Tests/Source/SharedCode.h | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/Source/SharedCode.cpp b/Tests/Source/SharedCode.cpp index 13a970c6..9e8a4120 100644 --- a/Tests/Source/SharedCode.cpp +++ b/Tests/Source/SharedCode.cpp @@ -6,8 +6,6 @@ #include "LuaBridge/LuaBridge.h" namespace xyz { -ISharedClass::ISharedClass() = default; -ISharedClass::~ISharedClass() = default; class SharedClass : public ISharedClass { diff --git a/Tests/Source/SharedCode.h b/Tests/Source/SharedCode.h index d3507301..8bb68ea7 100644 --- a/Tests/Source/SharedCode.h +++ b/Tests/Source/SharedCode.h @@ -23,8 +23,8 @@ namespace xyz { class LUABRIDGE_TEST_SHARED_API ISharedClass { public: - ISharedClass(); - virtual ~ISharedClass(); + ISharedClass() = default; + virtual ~ISharedClass() = default; virtual int publicMethod(const std::string& s) const = 0; }; From 809109f7dc063150d78ea8edbc6f18b931ecc513 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 10:29:43 +0200 Subject: [PATCH 08/19] More work on docs --- CHANGES.md | 23 ++++- Manual.md | 291 ++++++++++++++++++++++++++++++++++++++++++++++------- README.md | 19 +++- 3 files changed, 291 insertions(+), 42 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a47cd16f..a992da8b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,23 +16,36 @@ * Breaking Change: Removed `Class::addStaticCFunction`, it was just an alias for `Class::addStaticFunction`. * Allow specifying a non virtual base class method when declaring class members (functions or variables) not exposed in the inherited class. * Allow using capturing lambdas in `Namespace::addFunction`, `Namespace::addProperty`, `Class::addFunction`, `Class::addStaticFunction`, `Class::addProperty` and `Class::addStaticProperty`. -* Added support for specifying factory functor in `Class::addConstructor` to do placement new of the object instance. +* Added multiple inheritance support: `deriveClass` now accepts more than one registered base class (e.g. `deriveClass`). * Added `Namespace::addVariable` to allow adding a modifiable value by copy into the namespace without incurring in function calls or metatables generation. -* Added `getNamespaceFromStack` function to construct a namespace object from a table on the stack. -* Added `registerMainThread` function especially useful when using lua 5.1 to register the main lua thread. +* Added `luabridge::callWithHandler` free function and `LuaRef::callWithHandler` member to provide a custom Lua message handler during `lua_pcall`. +* Added `luabridge::newFunction` free function and `LuaRef::newFunction` static method to wrap any C++ callable into a Lua function exposed as a `LuaRef`. +* Added `luabridge::getNamespaceFromStack` function to construct a namespace object from a table on the stack. +* Added `luabridge::registerMainThread` function especially useful when using lua 5.1 to register the main lua thread. * Added `std::shared_ptr` support for types intrusively deriving from `std::enable_shared_from_this`. +* Added `Class::addConstructor` support for specifying factory functor to do placement new of the object instance. +* Added `Class::addDestructor` to register a custom `__destruct` metamethod hook invoked just before the C++ destructor runs. * Added `Class::addFunction` overload taking a `lua_CFunction` as if it were a member. * Added `Class::addIndexMetaMethod` to allow register `__index` metamethod fallback on a registered class. * Added `Class::addNewIndexMetaMethod` to allow register `__newindex` metamethod fallback on a registered class. +* Added `Class::addStaticIndexMetaMethod` for fallback `__index` handling on the static class table. +* Added `Class::addStaticNewIndexMetaMethod` for fallback `__newindex` handling on the static class table. * Added `LuaRef::isValid` to check when the reference is a LUA_NOREF. * Added `LuaRef::isCallable` to check when the reference is a function or has a `__call` metamethod. * Added `LuaException::state` to return the `lua_State` associated with the exception. +* Added `void*` and `const void*` stack specializations mapped transparently to Lua lightuserdata. * Added support for `std::byte` as stack value type. * Added support for `std::string_view` as stack value type. * Added support for `std::tuple` as stack value type. * Added support for `std::optional` as stack value type. * Added support for `std::set` as stack value type by using `LuaBridge/Set.h`. * Added support to `LuaRef` for being hashed with `std::hash` (`LuaRef` properly usable in `std::unordered_map`). +* Added `LuaRef::append` and variadic `LuaRef::append(vs...)` to append values to a Lua sequence table using `lua_rawseti`. +* Added `LuaRef::call()` now returns a strongly-typed `TypeResult` instead of a generic `LuaResult`; `LuaRef::operator()` returns `TypeResult`. +* Added `LUABRIDGE_STRICT_STACK_CONVERSIONS` compile-time flag to enforce strict type-safe stack conversions (`bool` requires `LUA_TBOOLEAN`, `std::string` requires `LUA_TSTRING`). +* Added `LuaFunction` strongly-typed wrapper class for invoking Lua functions with compile-time argument and return-type checking. +* Added `TypeResult::valueOr(default)` to extract the contained value or return a fallback when a cast fails. +* Added `allowOverridingMethods` class option to permit Lua scripts to override C++ methods registered in an extensible class. * Added single header amalgamated distribution file, to simplify including in projects. * Added more asserts for functions and property names. * Renamed `luabridge::Nil` to `luabridge::LuaNil` to allow including LuaBridge in Obj-C sources. @@ -42,6 +55,8 @@ * Removed `TypeList` from loki, using parameter packs and `std::tuple` with `std::apply`. * Removed juce traces from unit tests, simplified unit tests runs. * Changed all generic functions in `LuaRef` and `TableItem` to accept arguments by const reference instead of by copy. +* Fixed `Stack::get` to properly require `LUA_TBOOLEAN` when `LUABRIDGE_STRICT_STACK_CONVERSIONS` is enabled; without the flag, legacy `lua_toboolean` semantics are preserved. +* Fixed floating-point `Stack::push`, `get`, and `isInstance` to correctly allow NaN and Inf values to pass through without error. * Fixed issue when `LuaRef::cast<>` fails with exceptions enabled, popping from the now empty stack could trigger the panic handler twice. * Fixed unaligned access in user allocated member pointers in 64bit machines reported by ASAN. * Fixed access of `LuaRef` in garbage collected `lua_thread`. @@ -49,7 +64,7 @@ * Included testing against Ravi VM * Bumped lua 5.2.x in unit tests from lua 5.2.0 to 5.2.4. * Bumped lua 5.4.x in unit tests from lua 5.4.1 to 5.4.8. -* Run against lua 5.3.6 and 5.5.0 in unit tests. +* Added Lua 5.3.6 and 5.5.0 in unit tests. * Run against PUC-Lua, Luau, LuaJIT and Ravi in CI. * Converted the manual from html to markdown. * Small improvements to code and doxygen comments readability. diff --git a/Manual.md b/Manual.md index 6a6da8de..40f72a7b 100644 --- a/Manual.md +++ b/Manual.md @@ -29,12 +29,14 @@ Contents * [2.1 - Namespaces](#21---namespaces) * [2.2 - Properties and Functions](#22---properties-and-functions) * [2.3 - Class Objects](#23---class-objects) + * [2.3.1 - Multiple Inheritance](#231---multiple-inheritance) * [2.4 - Property Member Proxies](#24---property-member-proxies) * [2.5 - Function Member Proxies](#25---function-member-proxies) * [2.5.1 - Function Overloading](#251---function-overloading) * [2.6 - Constructors](#26---constructors) * [2.6.1 - Constructor Proxies](#261---constructor-proxies) * [2.6.2 - Constructor Factories](#262---constructor-factories) + * [2.6.3 - Destructors](#263---destructors) * [2.7 - Extending Classes](#27---extending-classes) * [2.7.1 - Extensible Classes](#271---extensible-classes) * [2.7.2 - Index and New Index Metamethods Fallback](#272---index-and-new-index-metamethods-fallback) @@ -64,6 +66,8 @@ Contents * [4.3 - Calling Lua](#43---calling-lua) * [4.3.1 - Exceptions](#431---exceptions) * [4.3.2 - Class LuaException](#432---class-luaexception) + * [4.3.3 - Calling with Error Handlers](#433---calling-with-error-handlers) + * [4.4 - Wrapping C++ Callables](#44---wrapping-c-callables) * [5 - Security](#5---security) @@ -341,8 +345,6 @@ test.bar ("Employee") -- calls bar with a string test.bar (test) -- error: bar expects a string not a table ``` -LuaBridge does not support overloaded functions nor is it likely to in the future. Since Lua is dynamically typed, any system that tries to resolve a set of parameters passed from a script will face considerable ambiguity when trying to choose an appropriately matching C++ function signature. - 2.3 - Class Objects ------------------- @@ -413,7 +415,7 @@ luabridge::getGlobalNamespace (L) Method registration works just like function registration. Virtual methods work normally; no special syntax is needed. const methods are detected and const-correctness is enforced, so if a function returns a const object (or a container holding to a const object) to Lua, that reference to the object will be considered const and only const methods can be called on it. It is possible to register Lua metamethods (except `__gc`). Destructors are registered automatically for each class. -As with regular variables and properties, class properties can be marked read-only by passing false in the second parameter, or omitting the set function. The `deriveClass` takes two template arguments: the class to be registered, and its base class. Inherited methods do not have to be re-declared and will function normally in Lua. If a class has a base class that is **not** registered with Lua, there is no need to declare it as a subclass. +As with regular variables and properties, class properties can be marked read-only by passing false in the second parameter, or omitting the set function. The `deriveClass` takes a derived class and one or more registered base classes as template arguments. Inherited methods do not have to be re-declared and will function normally in Lua. If a class has a base class that is **not** registered with Lua, there is no need to declare it as a subclass. Remember that in Lua, the colon operator '`:`' is used for method call syntax: @@ -425,6 +427,51 @@ a.func1 (a) -- okay, verbose, this how OOP works in Lua a:func1 () -- okay, less verbose, equivalent to the previous ``` +### 2.3.1 - Multiple Inheritance + +`deriveClass` supports multiple registered base classes by specifying them as additional template parameters. LuaBridge will traverse all base class hierarchies when resolving member lookups from Lua: + +```cpp +struct A +{ + void funcA () { } +}; + +struct B +{ + void funcB () { } +}; + +struct C : public A, public B +{ + void funcC () { } +}; + +luabridge::getGlobalNamespace (L) + .beginNamespace ("test") + .beginClass ("A") + .addFunction ("funcA", &A::funcA) + .endClass () + .beginClass ("B") + .addFunction ("funcB", &B::funcB) + .endClass () + .deriveClass ("C") + .addFunction ("funcC", &C::funcC) + .endClass () + .endNamespace (); +``` + +From Lua, all inherited methods are accessible on instances of `C`: + +```lua +local c = test.C () +c:funcA () -- calls A::funcA via multiple-inheritance lookup +c:funcB () -- calls B::funcB via multiple-inheritance lookup +c:funcC () -- calls C::funcC +``` + +Only base classes that are themselves registered with LuaBridge need to be listed as template parameters. If a base class is not registered, it can be omitted from `deriveClass`. + 2.4 - Property Member Proxies ----------------------------- @@ -733,6 +780,31 @@ a = nil -- Remove any reference count collectgarbage ("collect") -- The object is garbage collected using objectFactoryDeallocator ``` +### 2.6.3 - Destructors + +In addition to the automatic `__gc` metamethod that LuaBridge registers for every class (which calls the C++ destructor when Lua garbage-collects the userdata), you can register an extra hook that is called **just before** the destructor runs. This is useful for performing clean-up work in a context where the object is still fully valid. + +Use `addDestructor` to register the hook. The callable must accept a `T*` (and optionally a trailing `lua_State*`): + +```cpp +struct Resource +{ + ~Resource () { /* cleanup */ } +}; + +luabridge::getGlobalNamespace (L) + .beginNamespace ("test") + .beginClass ("Resource") + .addDestructor ([] (Resource* r) { + // called before ~Resource() + std::cout << "Resource about to be destroyed\n"; + }) + .endClass () + .endNamespace (); +``` + +The `__destruct` metamethod is invoked by Lua's garbage collector before `__gc` calls the C++ destructor. This metamethod is only available on non-Luau builds; Luau already calls `__gc` directly. + 2.7 - Extending Classes ----------------------- @@ -984,7 +1056,8 @@ The Stack template class specializations are used automatically for variables, p * `bool` * `char`, converted to a string of length one. * `const char*`, `std::string_view` and `std::string` all converted to strings. -* `std::byte`, integers, `float`, `double`, `long double` all converted to `Lua_number`. +* `std::byte`, integers, `float`, `double`, `long double` all converted to `lua_Number`. Floating-point values including NaN and Inf pass through without error. +* `void*` and `const void*` mapped to Lua lightuserdata. User-defined types which are convertible to one of the basic types are possible, simply provide a `luabridge::Stack <>` specialization in the `luabridge` namespace for your user-defined type, modeled after the existing types. For example, here is a specialization for a `juce::String`: @@ -1622,7 +1695,7 @@ luabridge::LuaRef v = luabridge::getGlobal (L, "t"); By default `LuaBridge3` is able to work without exceptions, and it's perfectly compatible with the `-fno-exceptions` or `/EHsc-` flags, which is typically used in games. Even if compiling with exceptions enabled, they are not used internally when calling into lua to convert lua errors, but exceptions are only used in registration code to signal potential issues when registering namespaces, classes and methods. You can use the free function `luabridge::enableExceptions` to enable exceptions once before starting to use any luabridge call, and of course that will work only if the application is compiled with exceptions enabled. -When using the `luabridge::call` or `LuaRef::operator()` no exception should be raised, only if exceptions are disabled in the application or enabled in the application but disabled in luabridge. To control if the lua function invoked has raised a lua error, it is possible to do so by checking the `LuaResult` object that is returned from those functions. +When using `luabridge::call` or `LuaRef::operator()`, no exception is raised regardless of whether exceptions are enabled in the application. To detect whether the invoked Lua function raised an error, check the `TypeResult` returned by those calls: ```lua function fail () @@ -1631,14 +1704,30 @@ end ``` ```cpp -luabridge::LuaRef f (L) = luabridge::getGlobal (L, "fail"); +luabridge::LuaRef f = luabridge::getGlobal (L, "fail"); -luabridge::LuaResult result = f (); +auto result = f (); if (! result) - std::cerr << result.errorMessage (); + std::cerr << result.message (); +``` + +To call a Lua function and decode its first return value to a specific C++ type, use the typed `call()` overload: + +```lua +function add (a, b) + return a + b +end ``` -It is also possible that pushing an unregistered class instance into those function will generate an error, that can be trapped using the same mechanism in a `luabridge::LuaResult`: +```cpp +luabridge::LuaRef f = luabridge::getGlobal (L, "add"); + +auto result = f.call (1, 2); +if (result) + std::cout << *result; // prints 3 +``` + +It is also possible that pushing an unregistered class instance into those function will generate an error, that can be trapped using the same mechanism: ```lua function fail (unregistred) @@ -1649,16 +1738,16 @@ end ```cpp struct UnregisteredClass {}; -luabridge::LuaRef f (L) = luabridge::getGlobal (L, "fail"); +luabridge::LuaRef f = luabridge::getGlobal (L, "fail"); auto argument = UnregisteredClass(); -luabridge::LuaResult result = f (argument); +auto result = f (argument); if (! result) - std::cerr << result.errorMessage (); + std::cerr << result.message (); ``` -Calling `luabridge::pcall` will not return a `luabridge::LuaResult` but only the status code. It will anyway throw an exception if the return code of `lua_pcall`is not equal `LUA_OK`, and return the error code in case exceptions are disabled. +Calling `luabridge::pcall` will not return a `TypeResult` but only the raw status code. It will throw an exception if the return code is not `LUA_OK` (when exceptions are enabled), or return the error code otherwise. When compiling `LuaBridge3` with exceptions disabled, all references to try catch blocks and throws will be removed. @@ -1673,7 +1762,7 @@ end ``` ```cpp -luabridge::LuaRef f (L) = luabridge::getGlobal (L, "fail"); +luabridge::LuaRef f = luabridge::getGlobal (L, "fail"); try { @@ -1685,6 +1774,71 @@ catch (const luabridge::LuaException& e) } ``` +### 4.3.3 - Calling with Error Handlers + +By default, when a Lua error occurs the raw Lua error message is stored in the `TypeResult`. For more detailed diagnostics you can supply a custom message handler (equivalent to the `msgh` parameter of `lua_pcall`). The handler is a C++ callable that receives a `lua_State*`, may inspect the stack, and must return an `int`: + +```lua +function riskyOp () + error ("something went wrong") +end +``` + +```cpp +luabridge::LuaRef f = luabridge::getGlobal (L, "riskyOp"); + +auto handler = [] (lua_State* L) -> int { + // Augment the error message with a traceback + luaL_traceback (L, L, lua_tostring (L, 1), 1); + return 1; +}; + +auto result = f.callWithHandler (handler); +if (! result) + std::cerr << result.message (); // includes traceback +``` + +The same function is available as a free function: + +```cpp +auto result = luabridge::callWithHandler (f, handler, /* args... */); +``` + +4.4 - Wrapping C++ Callables +----------------------------- + +`luabridge::newFunction` (and its equivalent `LuaRef::newFunction`) wraps any C++ callable — a lambda, a function pointer, or a `std::function` — into a Lua function and returns it as a `LuaRef`. This is useful when you need to pass a C++ callback to Lua without going through the namespace/class registration API: + +```cpp +// Create a Lua function that squares its argument +luabridge::LuaRef square = luabridge::newFunction (L, [] (int x) { return x * x; }); + +// Store it in a Lua global +luabridge::setGlobal (L, square, "square"); +``` + +From Lua: +```lua +print (square (5)) -- 25 +``` + +You can also store such a function in a table or pass it as a callback argument. + +### 4.4.1 - LuaFunction\ + +When you have a `LuaRef` that you know will always be called with fixed argument and return types, `LuaFunction` provides a strongly-typed wrapper that avoids repeating the template arguments at every call site: + +```cpp +// Retrieve a Lua function and wrap it with a known signature +auto add = luabridge::getGlobal (L, "add").callable(); + +auto result = add (3, 4); // TypeResult +if (result) + std::cout << *result; // 7 +``` + +`LuaFunction` supports the same `call`, `callWithHandler`, and `isValid` interface as a `LuaRef`. The wrapped `LuaRef` is accessible via `ref()`. + 5 - Security ============ @@ -1859,11 +2013,19 @@ Namespace getGlobalNamespace (lua_State* L); Namespace getNamespaceFromStack (lua_State* L); /// Invokes a LuaRef if it references a lua callable. -template -LuaResult call (const LuaRef& object, Args&&... args) +template +TypeResult call (const LuaRef& object, Args&&... args); + +/// Invokes a LuaRef with a custom Lua error message handler. +template +TypeResult callWithHandler (const LuaRef& object, F&& errorHandler, Args&&... args); + +/// Wraps a C++ callable into a LuaRef representing a Lua function. +template +LuaRef newFunction (lua_State* L, F&& func); /// Wrapper for lua_pcall, converting lua errors into C++ exceptions if they are enabled. -int pcall (lua_State* L, int nargs = 0, int nresults = 0, int msgh = 0) +int pcall (lua_State* L, int nargs = 0, int nresults = 0, int msgh = 0); /// Return a range iterable view over a lua table. Range pairs (const LuaRef& table); @@ -1902,8 +2064,8 @@ Class Registration - Class template Class beginClass (const char* name); -/// Begins derived class registration, returns this class object. -template +/// Begins derived class registration with one or more base classes, returns this class object. +template Class deriveClass (const char* name); /// Ends class registration, returns the parent namespace object. @@ -1933,6 +2095,10 @@ Class addConstructorFrom (Functions... functions); /// Registers allocator and deallocators for type T. template Class addFactory (Alloc alloc, Dealloc dealloc); + +/// Registers a destructor hook called just before the C++ destructor (__destruct metamethod). +template +Class addDestructor (Function function); ``` ### Member Function Registration @@ -1975,6 +2141,26 @@ template Class addStaticProperty (const char* name, Getter getter, Setter setter); ``` +### Metamethod Registration + +```cpp +/// Registers a fallback __index handler for instances (called when a key is not found). +template +Class addIndexMetaMethod (Function function); + +/// Registers a fallback __newindex handler for instances (called when a key is not found). +template +Class addNewIndexMetaMethod (Function function); + +/// Registers a fallback __index handler for the static class table. +template +Class addStaticIndexMetaMethod (Function function); + +/// Registers a fallback __newindex handler for the static class table. +template +Class addStaticNewIndexMetaMethod (Function function); +``` + Lua Variable Reference - LuaRef ------------------------------- @@ -2085,9 +2271,25 @@ bool append (const Ts&... vs) const; /// Return the length of a referred array. This is identical to applying the Lua # operator. int length () const; -/// Invoke the lua ref if it references a lua function. +/// Invoke the lua ref with no expected return value. template -LuaResult call (Args&&... args) const; +TypeResult operator() (Args&&... args) const; + +/// Invoke the lua ref and decode the return value to R. +template +TypeResult call (Args&&... args) const; + +/// Invoke the lua ref with an error handler and decode the return value to R. +template +TypeResult callWithHandler (F&& errorHandler, Args&&... args) const; + +/// Build a strongly-typed callable wrapper from this Lua object. +template +LuaFunction callable () const; + +/// Wrap a C++ callable into a new Lua function returned as a LuaRef. +template +static LuaRef newFunction (lua_State* L, F&& func); ``` Lua Nil Special Value - LuaNil @@ -2097,30 +2299,51 @@ Lua Nil Special Value - LuaNil /// LuaNil can be used to construct LuaRef. ``` -Lua Result Of Function Invocation - LuaResult ---------------------------------------------- +TypeResult — Result of a Call or Cast +----------------------------------------- ```cpp explicit operator bool() const; -/// Return if the invocation was ok and didn't raise a lua error. -bool wasOk() const; +/// Return the contained value (undefined behavior if result holds an error). +const T& value() const; + +/// Dereference operator — equivalent to value(). +T& operator*(); -/// Return if the invocation did raise a lua error. -bool hasFailed() const; +/// Return the contained value or a default when the result holds an error. +template +T valueOr(U&& defaultValue) const; /// Return the error code, if any. -std::error_code errorCode() const; +std::error_code error() const; -/// Return the error message, if any. -std::string errorMessage() const; +/// Return the error message string, if any. +std::string message() const; +``` + +Typed Lua Function Wrapper - LuaFunction\ +------------------------------------------------------- + +```cpp +/// Construct from a LuaRef. +explicit LuaFunction (const LuaRef& function); -/// Return the number of return values. -std::size_t size() const; +/// Call the function with the given arguments. +TypeResult operator() (Args... args) const; -/// Get a return value at a specific index. -LuaRef operator[](std::size_t index) const; +/// Call the function — equivalent to operator(). +TypeResult call (Args... args) const; + +/// Call the function with a custom error handler. +template +TypeResult callWithHandler (F&& errorHandler, Args... args) const; + +/// Return true if the underlying LuaRef is callable. +bool isValid () const; +/// Return the underlying LuaRef. +const LuaRef& ref () const; ``` Stack Traits - Stack diff --git a/README.md b/README.md index 944de2e0..5861d0ed 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ LuaBridge3 is usable from a compliant C++17 compiler and offers the following fe * Headers-only: No Makefile, no .cpp files, just one `#include` and one header file (optional) ! * Works with ANY lua version out there (PUC-Lua, LuaJIT, Luau, Ravi, you name it). * Simple, light, and nothing else needed. +* Competitive performance with the fastest C++/Lua binding libraries available. * Fast to compile (even in release mode), scaling linearly with the size of your binded code. * No macros, settings, or configuration scripts needed. * Supports different object lifetime management models. @@ -44,7 +45,6 @@ LuaBridge3 is usable from a compliant C++17 compiler and offers the following fe * Expose C++ classes allowing them to use the flexibility of lua property lookup. * Interoperable with most common c++ standard library container types. * Written in a clear and easy to debug style. -* Competitive performance with the fastest C++/Lua binding libraries available. ## Performance @@ -74,20 +74,31 @@ Bold entries indicate cases where LuaBridge3 outperforms sol2. Lower is better ( LuaBridge3 offers a set of improvements compared to vanilla LuaBridge: * The only binder library that works with PUC-Lua as well as LuaJIT, Luau and Ravi, wonderful for game development ! +* Faster runtime execution for most common use cases, playing in the same league as the fastest binders in town. * Can work with both c++ exceptions and without (Works with `-fno-exceptions` and `/EHsc-`). -* Can safely register and use classes exposed across shared library boundaries. * Full support for capturing lambdas in all namespace and class methods. -* Overloaded function support in Namespace functions, Class constructors, functions and static functions. +* Overloaded functions support in Namespace functions, Class constructors, functions and static functions. +* Multiple inheritance: `deriveClass` supports any number of registered base classes. * Supports placement allocation or custom allocations/deallocations of C++ classes exposed to lua. * Lightweight object creation: allow adding lua tables on the stack and register methods and metamethods in them. -* Allows for fallback `__index` and `__newindex` metamethods in exposed C++ classes, to support flexible and dynamic C++ classes ! +* Instance metamethods fallbacks via `__index` and `__newindex` in exposed C++ classes. +* Static metamethod fallbacks via `__index` and `__newindex` in exposed C++ classes on the class static table. +* Custom destructor hook via `addDestructor` (`__destruct` metamethod) called just before the C++ destructor. * Added `std::shared_ptr` to support shared C++/Lua lifetime for types deriving from `std::enable_shared_from_this`. * Supports conversion to and from `std::nullptr_t`, `std::byte`, `std::pair`, `std::tuple` and `std::reference_wrapper`. * Supports conversion to and from C style arrays of any supported type. +* `void*` and `const void*` are transparently mapped to Lua lightuserdata. * Transparent support of all signed and unsigned integer types up to `int64_t`. * Consistent numeric handling and conversions (signed, unsigned and floats) across all lua versions. +* NaN and Inf values pass through floating-point stack conversions without error. * Simplified registration of enum types via the `luabridge::Enum` stack wrapper. * Opt-out handling of safe stack space checks (automatically avoids exhausting lua stack space when pushing values!). +* Optional strict stack conversions via `LUABRIDGE_STRICT_STACK_CONVERSIONS` (e.g. `bool` requires an actual boolean, not any truthy value). +* Error handler support in Lua calls via `LuaRef::callWithHandler` and `luabridge::callWithHandler`. +* `newFunction` free function wraps any C++ callable into a Lua function exposed as a `LuaRef`. +* `LuaFunction` provides a strongly-typed callable wrapper around a Lua function. +* `TypeResult::valueOr(default)` allows safe value extraction with an explicit fallback. +* Can safely register and use classes exposed across shared library boundaries. ## Documentation From 01db643ffddeb469f285c5a9f122b53bd80e5b5f Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 10:41:47 +0200 Subject: [PATCH 09/19] More tests --- Tests/Source/ClassTests.cpp | 64 ++++++++++++++++ Tests/Source/IteratorTests.cpp | 16 ++++ Tests/Source/LuaRefTests.cpp | 129 +++++++++++++++++++++++++++++++++ Tests/Source/StackTests.cpp | 24 ++++++ 4 files changed, 233 insertions(+) diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 09e4e554..131ea54a 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -3456,3 +3456,67 @@ TEST_F(ClassTests, BugWithSharedPtrPropertyGetterFromClassNotDerivingFromSharedF EXPECT_TRUE(runLua("result = bar.getAsFunction()")); EXPECT_TRUE(runLua("result = bar.getAsProperty")); } + +namespace { +struct CoverageBase +{ + int value = 42; + int getValue() const { return value; } +}; + +struct CoverageDerived : CoverageBase +{ +}; +} // namespace + +TEST_F(ClassTests, DerivedClassAccessesParentProperty) +{ + // Covers CFunctions.h:476-477, 511 - parent propget table lookup in index_metamethod + // Derived is registered without any properties; accessing "value" traverses parent list + // and finds it in Base's propget table (lines 491-504) or continues iteration (476-477, 511) + luabridge::getGlobalNamespace(L) + .beginClass("CoverageBase") + .addProperty("value", &CoverageBase::value) + .endClass() + .deriveClass("CoverageDerived") + .addConstructor() + .endClass(); + + EXPECT_TRUE(runLua("result = CoverageDerived().value")); + EXPECT_EQ(42, result()); +} + +TEST_F(ClassTests, DerivedClassAccessesMetamethodNameReturnsNil) +{ + // Covers CFunctions.h:381-382 - metamethod name protection in index_metamethod + // Derived class uses complex index_metamethod (not simple); accessing "__index" + // triggers the is_metamethod guard and returns nil + luabridge::getGlobalNamespace(L) + .beginClass("CoverageBase") + .endClass() + .deriveClass("CoverageDerived") + .addConstructor() + .endClass(); + + EXPECT_TRUE(runLua("result = CoverageDerived().__index")); + EXPECT_TRUE(result().isNil()); +} + +TEST_F(ClassTests, StaticClassAccessReturnsMethodFromConstTable) +{ + // Covers CFunctions.h:613 - in index_metamethod_simple (static path), + // upvalue[2] (const/methods table) lookup returns a non-nil value + struct SimpleClass + { + int getValue() const { return 7; } + }; + + luabridge::getGlobalNamespace(L) + .beginClass("SimpleClass") + .addFunction("getValue", &SimpleClass::getValue) + .endClass(); + + // Accessing SimpleClass.getValue (through class table, static path) hits upvalue[2] + EXPECT_TRUE(runLua("result = type(SimpleClass.getValue)")); + EXPECT_EQ("function", result()); +} diff --git a/Tests/Source/IteratorTests.cpp b/Tests/Source/IteratorTests.cpp index 772532d1..e0405ab1 100644 --- a/Tests/Source/IteratorTests.cpp +++ b/Tests/Source/IteratorTests.cpp @@ -109,3 +109,19 @@ TEST_F(IteratorTests, SequenceIteration) ASSERT_EQ(expected, actual); } + +TEST_F(IteratorTests, StackOverflowDuringIteration) +{ + // Covers Iterator.h:138-140 - stack overflow path in Iterator::next() + runLua("result = {1, 2, 3}"); + + luabridge::Iterator it(result()); + ASSERT_FALSE(it.isNil()); // iterator starts valid + + exhaustStackSpace(); + + ++it; // next() will fail lua_checkstack and set key/value to nil + EXPECT_TRUE(it.isNil()); + + lua_settop(L, 0); +} diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index 586aff6a..88d9467f 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -1331,3 +1331,132 @@ TEST_F(LuaRefTests, ComparisonOperatorPushFailure) EXPECT_FALSE(ref.rawequal(huge)); } } + +TEST_F(LuaRefTests, TableItemCopyWithKeyRef) +{ + // Covers LuaRef.h:757-758 - copy constructor path when other.m_keyRef != LUA_NOREF + runLua("result = { [\"outer\"] = { value = 99 } }"); + + std::string key = "outer"; + auto item = result()[key]; // creates TableItem with m_keyRef (not m_keyLiteral) + auto itemCopy = item; // exercises lines 757-758 + + auto val = itemCopy["value"]; + EXPECT_EQ(99, luabridge::cast(val).valueOr(0)); +} + +TEST_F(LuaRefTests, TableItemCopyStackOverflow) +{ + // Covers LuaRef.h:748 - early return in TableItem copy constructor when stack is exhausted + runLua("result = { [\"k\"] = 1 }"); + std::string key = "k"; + auto item = result()[key]; + exhaustStackSpace(); + auto itemCopy = item; // should not crash; m_tableRef stays LUA_NOREF + lua_settop(L, 0); +} + +TEST_F(LuaRefTests, TableItemAssignStackOverflow) +{ + // Covers LuaRef.h:794 - early return in TableItem::operator= when stack is exhausted + runLua("result = { key = 0 }"); + auto item = result()["key"]; + exhaustStackSpace(); + item = 42; // should silently return *this without modifying the table + lua_settop(L, 0); +} + +TEST_F(LuaRefTests, TableItemRawsetStackOverflow) +{ + // Covers LuaRef.h:837 - early return in TableItem::rawset when stack is exhausted + runLua("result = { key = 0 }"); + auto item = result()["key"]; + exhaustStackSpace(); + item.rawset(42); // should silently return *this without modifying the table + lua_settop(L, 0); +} + +TEST_F(LuaRefTests, TableItemPushStackOverflow) +{ + // Covers LuaRef.h:880 - early return in TableItem::push when stack is exhausted + runLua("result = { key = 1 }"); + auto item = result()["key"]; + exhaustStackSpace(); + const int topBefore = lua_gettop(L); + item.push(L); // should silently return without pushing + EXPECT_EQ(topBefore, lua_gettop(L)); + lua_settop(L, 0); +} + +TEST_F(LuaRefTests, LuaRefFromStackOverflow) +{ + // Covers LuaRef.h:1048 - early return in LuaRef(L, index, FromStack) when stack is exhausted + lua_pushinteger(L, 7); + exhaustStackSpace(); + auto ref = luabridge::LuaRef::fromStack(L, -1); + EXPECT_FALSE(ref.isValid()); + lua_settop(L, 0); +} + +TEST_F(LuaRefTests, TableItemAssignKeyRefPushFailure) +{ + // Covers LuaRef.h:812 - return *this in operator= key-ref path when Stack push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = { [\"k\"] = 0 }"); + std::string key = "k"; + auto item = result()[key]; // TableItem with m_keyRef + long double huge = std::numeric_limits::max(); + item = huge; // push fails - early return at line 812 + } +} + +TEST_F(LuaRefTests, TableItemRawsetLiteralPushFailure) +{ + // Covers LuaRef.h:848 - return *this in rawset literal path when Stack push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = { key = 0 }"); + auto item = result()["key"]; // TableItem with m_keyLiteral + long double huge = std::numeric_limits::max(); + item.rawset(huge); // push fails - early return at line 848 + } +} + +TEST_F(LuaRefTests, TableItemRawsetKeyRefPushFailure) +{ + // Covers LuaRef.h:857 - return *this in rawset key-ref path when Stack push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = { [\"k\"] = 0 }"); + std::string key = "k"; + auto item = result()[key]; // TableItem with m_keyRef + long double huge = std::numeric_limits::max(); + item.rawset(huge); // push fails - early return at line 857 + } +} + +TEST_F(LuaRefTests, TableItemOperatorIndexKeyPushFailure) +{ + // Covers LuaRef.h:918 - lua_pushnil fallback in TableItem::operator[] when key push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + runLua("result = { [\"k\"] = {} }"); + std::string key = "k"; + auto item = result()[key]; // TableItem + long double huge = std::numeric_limits::max(); + auto child = item[huge]; // key push fails -> lua_pushnil used as key + (void)child; + } +} + +TEST_F(LuaRefTests, LuaRefConstructorPushFailure) +{ + // Covers LuaRef.h:1085 - early return in LuaRef(L, v) template constructor when push fails + if constexpr (sizeof(long double) > sizeof(lua_Number)) + { + long double huge = std::numeric_limits::max(); + luabridge::LuaRef ref(L, huge); // push fails - early return at line 1085 + EXPECT_FALSE(ref.isValid()); + } +} diff --git a/Tests/Source/StackTests.cpp b/Tests/Source/StackTests.cpp index de8b6d64..6441b247 100644 --- a/Tests/Source/StackTests.cpp +++ b/Tests/Source/StackTests.cpp @@ -2745,6 +2745,22 @@ TEST_F(StackTests, UnsignedLongInvalidType) EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); } +TEST_F(StackTests, UnsignedLongNotFittingGet) +{ + lua_pushnumber(L, 18446744073709551616.0); + auto r = luabridge::Stack::get(L, -1); + ASSERT_FALSE(r); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, r.error()); +} + +TEST_F(StackTests, UlongLongNotFittingGet) +{ + lua_pushnumber(L, 18446744073709551616.0); + auto r = luabridge::Stack::get(L, -1); + ASSERT_FALSE(r); + EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, r.error()); +} + #if !LUABRIDGE_STRICT_STACK_CONVERSIONS TEST_F(StackTests, StringGetFromNumberStackOverflow) { @@ -2770,6 +2786,14 @@ TEST_F(StackTests, FloatNotFittingGet) EXPECT_EQ(luabridge::ErrorCode::FloatingPointDoesntFitIntoLuaNumber, result.error()); } +TEST_F(StackTests, ConstCharPtrGetNil) +{ + lua_pushnil(L); + auto r = luabridge::Stack::get(L, -1); + ASSERT_TRUE(r); + EXPECT_EQ(nullptr, *r); +} + TEST_F(StackTests, OptionalNulloptStackOverflow) { exhaustStackSpace(); From 64c530b0b412b3ae32b197939d0fd6ee34ad358a Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 13:01:25 +0200 Subject: [PATCH 10/19] Fix UB --- Tests/Source/EnumTests.cpp | 4 ++-- justfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Source/EnumTests.cpp b/Tests/Source/EnumTests.cpp index 1aa777ea..255e669c 100644 --- a/Tests/Source/EnumTests.cpp +++ b/Tests/Source/EnumTests.cpp @@ -41,7 +41,7 @@ enum class D D_THREE }; -enum E +enum E : int { E_ZERO, E_ONE, @@ -49,7 +49,7 @@ enum E E_THREE }; -enum class F +enum class F : int { F_ZERO, F_ONE, diff --git a/justfile b/justfile index eaeb348e..b359ac8c 100644 --- a/justfile +++ b/justfile @@ -4,8 +4,8 @@ default: generate: cmake -G Xcode -B Build . -sanitize: - cmake -G Xcode -B Build -DLUABRIDGE_SANITIZE=address . +sanitizer TYPE='address': + cmake -G Xcode -B Build -DLUABRIDGE_SANITIZE={{TYPE}} . benchmark: cmake -G Xcode -B Build -DLUABRIDGE_BENCHMARKS=ON . From 9595f5b1c63f1a930ddccfcb6111f9ec91f918c2 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 14:44:21 +0200 Subject: [PATCH 11/19] Fix UB and warnings --- Source/LuaBridge/detail/ClassInfo.h | 12 +++ Source/LuaBridge/detail/Namespace.h | 104 +++++++++++++++++++--- Source/LuaBridge/detail/Userdata.h | 23 ++++- Tests/Source/DynamicLibraryTests.cpp | 2 +- Tests/Source/MultipleInheritanceTests.cpp | 11 +-- Tests/Source/StackTests.cpp | 12 +-- 6 files changed, 136 insertions(+), 28 deletions(-) diff --git a/Source/LuaBridge/detail/ClassInfo.h b/Source/LuaBridge/detail/ClassInfo.h index 9de1308c..ad90f244 100644 --- a/Source/LuaBridge/detail/ClassInfo.h +++ b/Source/LuaBridge/detail/ClassInfo.h @@ -146,6 +146,18 @@ template ().find_first_of('.')> return reinterpret_cast(0xdad); } +//================================================================================================= +/** + * @brief The key of a cast offset table in a derived class metatable. + * + * Maps base class registry keys to byte offsets for pointer adjustment when converting + * a derived class pointer to a base class pointer in multiple inheritance scenarios. + */ +[[nodiscard]] inline const void* getCastTableKey() noexcept +{ + return reinterpret_cast(0xca57); +} + //================================================================================================= /** * The key of the index fall back in another metatable. diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index dc070de2..610b234b 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -37,6 +37,26 @@ namespace luabridge { */ namespace detail { +struct BaseClassInfo +{ + const void* staticKey; + const void* classKey; + lua_Integer castOffset; +}; + +template +lua_Integer computeCastOffset() noexcept +{ + static_assert(std::is_base_of_v); + + alignas(Derived) std::byte buf[sizeof(Derived)] = {}; + + auto* derived = reinterpret_cast(buf); + auto* base = static_cast(derived); // implicit upcast, purely pointer arithmetic + + return static_cast(reinterpret_cast(base) - reinterpret_cast(derived)); +} + class Registrar { protected: @@ -489,7 +509,7 @@ class Namespace : public detail::Registrar * @param parent A parent namespace object. * @param staticKey Key where the class is stored. */ - Class(const char* name, Namespace parent, std::initializer_list staticKeys, Options options) + Class(const char* name, Namespace parent, std::initializer_list bases, Options options) : ClassBase(name, std::move(parent)) { LUABRIDGE_ASSERT(name != nullptr); @@ -533,36 +553,81 @@ class Namespace : public detail::Registrar lua_newtable(L); // Stack: ns, co, cl, st, cl parents const int clParentsIndex = lua_absindex(L, -1); - lua_newtable(L); // Stack: ns, co, cl, st, cl parents, visited + lua_newtable(L); // Stack: ns, co, cl, st, cl parents, cast table + const int castTableIndex = lua_absindex(L, -1); + lua_newtable(L); // Stack: ns, co, cl, st, cl parents, cast table, visited const int visitedIndex = lua_absindex(L, -1); - for (const auto* staticKey : staticKeys) + for (const detail::BaseClassInfo& base : bases) { - lua_rawgetp_x(L, LUA_REGISTRYINDEX, staticKey); // Stack: ..., visited, base st | nil + lua_rawgetp_x(L, LUA_REGISTRYINDEX, base.staticKey); // Stack: ..., visited, base st | nil if (! lua_istable(L, -1)) { - lua_pop(L, 2); // Stack: ns, co, cl, st, cl parents - lua_pop(L, 1); // Stack: ns, co, cl, st + lua_pop(L, 4); // pop (nil), visited, cast table, cl parents. Stack: ns, co, cl, st throw_or_assert("Base class is not registered"); return; } - lua_rawgetp_x(L, -1, detail::getClassKey()); // Stack: ..., visited, base st, base cl + lua_rawgetp_x(L, -1, detail::getClassKey()); // Stack: ..., visited, base st, base cl | nil if (! lua_istable(L, -1)) { - lua_pop(L, 3); // Stack: ns, co, cl, st, cl parents - lua_pop(L, 1); // Stack: ns, co, cl, st + lua_pop(L, 5); // pop (nil), base st, visited, cast table, cl parents. Stack: ns, co, cl, st throw_or_assert("Base class is not registered"); return; } appendParentList(L, clParentsIndex, visitedIndex, -1); - lua_pop(L, 2); // Stack: ns, co, cl, st, cl parents, visited + + // Store the direct pointer adjustment offset for this base class + lua_pushlightuserdata(L, const_cast(base.classKey)); + lua_pushinteger(L, base.castOffset); + lua_rawset(L, castTableIndex); + + // Compose and propagate ancestor offsets from the base's own cast table. + // offset(T → ancestor) = offset(T → base) + offset(base → ancestor) + lua_rawgetp_x(L, -1, detail::getCastTableKey()); // Stack: ..., base st, base cl, base cast table | nil + if (lua_istable(L, -1)) + { + lua_pushnil(L); + while (lua_next(L, -2) != 0) + { + // Stack: ..., base cast table, ancestor key, ancestor offset + const lua_Integer ancestorOffset = lua_tointeger(L, -1); + + lua_pushvalue(L, -2); // duplicate ancestor key to check presence + lua_rawget(L, castTableIndex); + const bool alreadyPresent = ! lua_isnil(L, -1); + lua_pop(L, 1); + + if (! alreadyPresent) + { + lua_pushvalue(L, -2); // push ancestor key + lua_pushinteger(L, base.castOffset + ancestorOffset); + lua_rawset(L, castTableIndex); + } + + lua_pop(L, 1); // pop ancestor offset; ancestor key stays for next() + } + lua_pop(L, 1); // pop base cast table + } + else + { + lua_pop(L, 1); // pop nil + } + + lua_pop(L, 2); // pop base cl and base st. Stack: ..., cast table, visited } - lua_pop(L, 1); // Stack: ns, co, cl, st, cl parents + lua_pop(L, 1); // pop visited. Stack: ns, co, cl, st, cl parents, cast table + + // Store the cast table in both class and const metatables + lua_pushvalue(L, castTableIndex); + lua_rawsetp_x(L, clIndex, detail::getCastTableKey()); // cl[castTableKey] = cast table + lua_pushvalue(L, castTableIndex); + lua_rawsetp_x(L, coIndex, detail::getCastTableKey()); // co[castTableKey] = cast table + lua_pop(L, 1); // pop cast table. Stack: ns, co, cl, st, cl parents lua_createtable(L, get_length(L, clParentsIndex), 0); // Stack: ns, co, cl, st, cl parents, co parents const int coParentsIndex = lua_absindex(L, -1); @@ -1879,9 +1944,22 @@ class Namespace : public detail::Registrar template Class deriveClass(const char* name, Options options = defaultOptions) { + static_assert(std::is_base_of_v, "Derived must inherit from Base1"); + static_assert((std::is_base_of_v && ...), "Derived must inherit from all specified base classes"); + assertIsActive(); - return Class(name, std::move(*this), - {detail::getStaticRegistryKey(), detail::getStaticRegistryKey()...}, options); + return Class(name, std::move(*this), { + detail::BaseClassInfo{ + detail::getStaticRegistryKey(), + detail::getClassRegistryKey(), + detail::computeCastOffset() + }, + detail::BaseClassInfo{ + detail::getStaticRegistryKey(), + detail::getClassRegistryKey(), + detail::computeCastOffset() + }... + }, options); } private: diff --git a/Source/LuaBridge/detail/Userdata.h b/Source/LuaBridge/detail/Userdata.h index c8ebde7c..a09d4e06 100644 --- a/Source/LuaBridge/detail/Userdata.h +++ b/Source/LuaBridge/detail/Userdata.h @@ -318,7 +318,28 @@ class Userdata if (! clazz) return clazz.error(); - return static_cast((*clazz)->getPointer()); + void* rawPtr = (*clazz)->getPointer(); + + // For multiple inheritance, apply the stored byte offset so that the raw derived + // pointer is correctly adjusted to point to the T subobject within it. + if (lua_getmetatable(L, absIndex) && lua_istable(L, -1)) + { + lua_rawgetp_x(L, -1, detail::getCastTableKey()); // Stack: ..., mt, cast table | nil + if (lua_istable(L, -1)) + { + lua_rawgetp_x(L, -1, classId); // Stack: ..., mt, cast table, offset | nil + if (! lua_isnil(L, -1)) + { + const lua_Integer offset = lua_tointeger(L, -1); + lua_pop(L, 3); + return reinterpret_cast(static_cast(rawPtr) + static_cast(offset)); + } + lua_pop(L, 1); // pop nil + } + lua_pop(L, 2); // pop cast table (or nil) and mt + } + + return static_cast(rawPtr); } template diff --git a/Tests/Source/DynamicLibraryTests.cpp b/Tests/Source/DynamicLibraryTests.cpp index 59975604..9e323fc2 100644 --- a/Tests/Source/DynamicLibraryTests.cpp +++ b/Tests/Source/DynamicLibraryTests.cpp @@ -42,7 +42,7 @@ std::optional getExecutablePath() { #if _WIN32 TCHAR path[MAX_PATH]; - auto pathLength = GetModuleFileName(NULL, path, MAX_PATH); + auto pathLength = GetModuleFileName(nullptr, path, MAX_PATH); if (pathLength > 0) return std::filesystem::canonical(std::string_view(path, static_cast(pathLength))); diff --git a/Tests/Source/MultipleInheritanceTests.cpp b/Tests/Source/MultipleInheritanceTests.cpp index 64b9448a..b3a3ecef 100644 --- a/Tests/Source/MultipleInheritanceTests.cpp +++ b/Tests/Source/MultipleInheritanceTests.cpp @@ -584,8 +584,8 @@ TEST_F(MultipleInheritanceTests, MultipleBasesWithCopySemantics) TEST_F(MultipleInheritanceTests, MultipleInheritanceChainLookup) { - // Test method resolution through a multi-level inheritance chain - // C derives from A and B, E derives from C and A (testing chain lookup through levels) + // Test method resolution through a multi-level inheritance chain. + // D derives from A and B, E derives from D (transitively getting A's and B's methods). luabridge::getGlobalNamespace(L) .beginClass("A") .addConstructor() @@ -595,18 +595,15 @@ TEST_F(MultipleInheritanceTests, MultipleInheritanceChainLookup) .addConstructor() .addFunction("methodB", &B::methodB) .endClass() - .deriveClass("C") + .deriveClass("D") .addConstructor() .endClass() - .deriveClass("E") // E derives from C (which derives from A,B) and A + .deriveClass("E") .addConstructor() .endClass(); runLua(R"( local e = E() - -- Methods should resolve through the inheritance chain: - -- E -> C -> A,B and E -> A - -- methodA and methodB should both be accessible result = e:methodA() + e:methodB() )"); diff --git a/Tests/Source/StackTests.cpp b/Tests/Source/StackTests.cpp index 6441b247..4419c17a 100644 --- a/Tests/Source/StackTests.cpp +++ b/Tests/Source/StackTests.cpp @@ -2625,7 +2625,7 @@ TEST_F(StackTests, VoidPointerGetNil) TEST_F(StackTests, LongLongType) { - long long value = 42LL; + long long value = 42ll; ASSERT_TRUE(luabridge::push(L, value)); @@ -2640,7 +2640,7 @@ TEST_F(StackTests, LongLongStackOverflow) { exhaustStackSpace(); - ASSERT_FALSE(luabridge::push(L, 42LL)); + ASSERT_FALSE(luabridge::push(L, 42ll)); } TEST_F(StackTests, LongLongInvalidType) @@ -2666,7 +2666,7 @@ TEST_F(StackTests, LongLongNotFittingPush) { if constexpr (sizeof(long long) > sizeof(lua_Integer)) { - long long value = static_cast(std::numeric_limits::max()) + 1LL; + long long value = static_cast(std::numeric_limits::max()) + 1ull; auto result = luabridge::push(L, value); ASSERT_FALSE(result); EXPECT_EQ(luabridge::ErrorCode::IntegerDoesntFitIntoLuaInteger, result.error()); @@ -2686,7 +2686,7 @@ TEST_F(StackTests, UlongLongNotFittingPush) TEST_F(StackTests, UlongLongType) { - unsigned long long value = 42ULL; + unsigned long long value = 42ull; ASSERT_TRUE(luabridge::push(L, value)); @@ -2701,7 +2701,7 @@ TEST_F(StackTests, UlongLongStackOverflow) { exhaustStackSpace(); - ASSERT_FALSE(luabridge::push(L, 42ULL)); + ASSERT_FALSE(luabridge::push(L, 42ull)); } TEST_F(StackTests, UlongLongInvalidType) @@ -2728,7 +2728,7 @@ TEST_F(StackTests, LongNotFittingGet) if constexpr (sizeof(long) < sizeof(lua_Integer)) { // Push a value larger than LONG_MAX - lua_pushinteger(L, static_cast(std::numeric_limits::max()) + 1); + lua_pushinteger(L, static_cast(static_cast(std::numeric_limits::max()) + 1u)); auto result = luabridge::Stack::get(L, -1); ASSERT_FALSE(result); From 06ba0e0317405d1995d60c8a03f1295df8bd99c2 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 14:55:24 +0200 Subject: [PATCH 12/19] Cook the amalgama --- Distribution/LuaBridge/LuaBridge.h | 126 ++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 13 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 51d6618a..7ec9de0c 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -2788,6 +2788,11 @@ template ().find_first_of('.')> return reinterpret_cast(0xdad); } +[[nodiscard]] inline const void* getCastTableKey() noexcept +{ + return reinterpret_cast(0xca57); +} + [[nodiscard]] inline const void* getIndexFallbackKey() { return reinterpret_cast(0x81ca); @@ -3118,7 +3123,7 @@ class Userdata lua_rawgetp_x(L, LUA_REGISTRYINDEX, registryClassKey); const bool classIsRegistered = lua_istable(L, -1); - const char* expected = "unregistered class"; + [[maybe_unused]] const char* expected = "unregistered class"; if (classIsRegistered) { lua_rawgetp_x(L, -1, getTypeKey()); @@ -3302,7 +3307,26 @@ class Userdata if (! clazz) return clazz.error(); - return static_cast((*clazz)->getPointer()); + void* rawPtr = (*clazz)->getPointer(); + + if (lua_getmetatable(L, absIndex) && lua_istable(L, -1)) + { + lua_rawgetp_x(L, -1, detail::getCastTableKey()); + if (lua_istable(L, -1)) + { + lua_rawgetp_x(L, -1, classId); + if (! lua_isnil(L, -1)) + { + const lua_Integer offset = lua_tointeger(L, -1); + lua_pop(L, 3); + return reinterpret_cast(static_cast(rawPtr) + static_cast(offset)); + } + lua_pop(L, 1); + } + lua_pop(L, 2); + } + + return static_cast(rawPtr); } template @@ -5428,7 +5452,9 @@ inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned l inline void dumpValue(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr) { - const int type = lua_type(L, index); + const int stackTop = lua_gettop(L); + const int absIndex = (index > 0) ? index : (index < 0 ? stackTop + index + 1 : 0); + const int type = (absIndex < 1 || absIndex > stackTop) ? LUA_TNONE : lua_type(L, index); switch (type) { case LUA_TNIL: @@ -7824,7 +7850,7 @@ bool overload_check_one_arg(lua_State* L, int& idx) template bool overload_check_args_impl(lua_State* L, int start, std::index_sequence) { - int idx = start; + [[maybe_unused]] int idx = start; return (overload_check_one_arg>(L, idx) && ...); } @@ -10282,6 +10308,26 @@ namespace luabridge { namespace detail { +struct BaseClassInfo +{ + const void* staticKey; + const void* classKey; + lua_Integer castOffset; +}; + +template +lua_Integer computeCastOffset() noexcept +{ + static_assert(std::is_base_of_v); + + alignas(Derived) std::byte buf[sizeof(Derived)] = {}; + + auto* derived = reinterpret_cast(buf); + auto* base = static_cast(derived); + + return static_cast(reinterpret_cast(base) - reinterpret_cast(derived)); +} + class Registrar { protected: @@ -10664,7 +10710,7 @@ class Namespace : public detail::Registrar } } - Class(const char* name, Namespace parent, std::initializer_list staticKeys, Options options) + Class(const char* name, Namespace parent, std::initializer_list bases, Options options) : ClassBase(name, std::move(parent)) { LUABRIDGE_ASSERT(name != nullptr); @@ -10709,15 +10755,16 @@ class Namespace : public detail::Registrar lua_newtable(L); const int clParentsIndex = lua_absindex(L, -1); lua_newtable(L); + const int castTableIndex = lua_absindex(L, -1); + lua_newtable(L); const int visitedIndex = lua_absindex(L, -1); - for (const auto* staticKey : staticKeys) + for (const detail::BaseClassInfo& base : bases) { - lua_rawgetp_x(L, LUA_REGISTRYINDEX, staticKey); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, base.staticKey); if (! lua_istable(L, -1)) { - lua_pop(L, 2); - lua_pop(L, 1); + lua_pop(L, 4); throw_or_assert("Base class is not registered"); return; @@ -10726,19 +10773,59 @@ class Namespace : public detail::Registrar lua_rawgetp_x(L, -1, detail::getClassKey()); if (! lua_istable(L, -1)) { - lua_pop(L, 3); - lua_pop(L, 1); + lua_pop(L, 5); throw_or_assert("Base class is not registered"); return; } appendParentList(L, clParentsIndex, visitedIndex, -1); + + lua_pushlightuserdata(L, const_cast(base.classKey)); + lua_pushinteger(L, base.castOffset); + lua_rawset(L, castTableIndex); + + lua_rawgetp_x(L, -1, detail::getCastTableKey()); + if (lua_istable(L, -1)) + { + lua_pushnil(L); + while (lua_next(L, -2) != 0) + { + + const lua_Integer ancestorOffset = lua_tointeger(L, -1); + + lua_pushvalue(L, -2); + lua_rawget(L, castTableIndex); + const bool alreadyPresent = ! lua_isnil(L, -1); + lua_pop(L, 1); + + if (! alreadyPresent) + { + lua_pushvalue(L, -2); + lua_pushinteger(L, base.castOffset + ancestorOffset); + lua_rawset(L, castTableIndex); + } + + lua_pop(L, 1); + } + lua_pop(L, 1); + } + else + { + lua_pop(L, 1); + } + lua_pop(L, 2); } lua_pop(L, 1); + lua_pushvalue(L, castTableIndex); + lua_rawsetp_x(L, clIndex, detail::getCastTableKey()); + lua_pushvalue(L, castTableIndex); + lua_rawsetp_x(L, coIndex, detail::getCastTableKey()); + lua_pop(L, 1); + lua_createtable(L, get_length(L, clParentsIndex), 0); const int coParentsIndex = lua_absindex(L, -1); lua_createtable(L, get_length(L, clParentsIndex), 0); @@ -11813,9 +11900,22 @@ class Namespace : public detail::Registrar template Class deriveClass(const char* name, Options options = defaultOptions) { + static_assert(std::is_base_of_v, "Derived must inherit from Base1"); + static_assert((std::is_base_of_v && ...), "Derived must inherit from all specified base classes"); + assertIsActive(); - return Class(name, std::move(*this), - {detail::getStaticRegistryKey(), detail::getStaticRegistryKey()...}, options); + return Class(name, std::move(*this), { + detail::BaseClassInfo{ + detail::getStaticRegistryKey(), + detail::getClassRegistryKey(), + detail::computeCastOffset() + }, + detail::BaseClassInfo{ + detail::getStaticRegistryKey(), + detail::getClassRegistryKey(), + detail::computeCastOffset() + }... + }, options); } private: From be66e251097b9f543d0571203ec0c3511d9330f0 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:02:20 +0200 Subject: [PATCH 13/19] More fixes --- Distribution/LuaBridge/LuaBridge.h | 1441 ++++++++++++------------ Source/LuaBridge/detail/CFunctions.h | 4 +- Source/LuaBridge/detail/LuaException.h | 2 +- Source/LuaBridge/detail/LuaHelpers.h | 19 +- Tests/Lua/LuaLibrary5.5.0.c | 1 + Tests/Source/LuaRefTests.cpp | 8 + 6 files changed, 743 insertions(+), 732 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 7ec9de0c..eefaea10 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -119,215 +119,627 @@ // End File: Source/LuaBridge/detail/Config.h -// Begin File: Source/LuaBridge/detail/LuaHelpers.h +// Begin File: Source/LuaBridge/detail/FuncTraits.h namespace luabridge { +namespace detail { -template -constexpr void unused(Args&&...) +[[noreturn]] inline void unreachable() { +#if defined(__GNUC__) + __builtin_unreachable(); +#elif defined(_MSC_VER) + __assume(false); +#endif } -#if LUA_VERSION_NUM < 502 -using lua_Unsigned = std::make_unsigned_t; - -#if ! LUABRIDGE_ON_LUAU -inline int lua_absindex(lua_State* L, int idx) +template< class T > +struct remove_cvref { - if (idx > LUA_REGISTRYINDEX && idx < 0) - return lua_gettop(L) + idx + 1; - else - return idx; -} -#endif + typedef std::remove_cv_t> type; +}; -#define LUA_OPEQ 1 -#define LUA_OPLT 2 -#define LUA_OPLE 3 +template +using remove_cvref_t = typename remove_cvref::type; -inline int lua_compare(lua_State* L, int idx1, int idx2, int op) +template +struct function_traits_base { - switch (op) - { - case LUA_OPEQ: - return lua_equal(L, idx1, idx2); - - case LUA_OPLT: - return lua_lessthan(L, idx1, idx2); + using result_type = R; - case LUA_OPLE: - return lua_equal(L, idx1, idx2) || lua_lessthan(L, idx1, idx2); + using argument_types = std::tuple; - default: - return 0; - } -} + static constexpr auto arity = sizeof...(Args); -#if ! LUABRIDGE_ON_LUAJIT -inline void* luaL_testudata(lua_State* L, int ud, const char* tname) -{ - void* p = lua_touserdata(L, ud); - if (p == nullptr) - return nullptr; + static constexpr auto is_member = IsMember; - if (! lua_getmetatable(L, ud)) - return nullptr; + static constexpr auto is_const = IsConst; +}; - luaL_getmetatable(L, tname); - if (! lua_rawequal(L, -1, -2)) - p = nullptr; +template +struct function_traits_impl; - lua_pop(L, 2); - return p; -} -#endif +template +struct function_traits_impl : function_traits_base +{ +}; -inline int get_length(lua_State* L, int idx) +template +struct function_traits_impl : function_traits_base { - return static_cast(lua_objlen(L, idx)); -} -#else -inline int get_length(lua_State* L, int idx) +}; + +template +struct function_traits_impl : function_traits_base { - return static_cast(lua_rawlen(L, idx)); -} -#endif +}; -#if LUABRIDGE_ON_LUAU -inline int luaL_ref(lua_State* L, int idx) +template +struct function_traits_impl : function_traits_base { - LUABRIDGE_ASSERT(idx == LUA_REGISTRYINDEX); +}; - const int ref = lua_ref(L, -1); +template +struct function_traits_impl : function_traits_base +{ +}; - lua_pop(L, 1); +template +struct function_traits_impl : function_traits_base +{ +}; - return ref; -} +template +struct function_traits_impl : function_traits_base +{ +}; -inline void luaL_unref(lua_State* L, int idx, int ref) +template +struct function_traits_impl : function_traits_base { - unused(idx); +}; - lua_unref(L, ref); -} +#if defined(_MSC_VER) && defined(_M_IX86) +inline static constexpr bool is_stdcall_default_calling_convention = std::is_same_v; +inline static constexpr bool is_fastcall_default_calling_convention = std::is_same_v; -template -inline void* lua_newuserdata_x(lua_State* L, size_t sz) +template +struct function_traits_impl : function_traits_base { - return lua_newuserdatadtor(L, sz, [](void* x) - { - T* object = static_cast(x); - object->~T(); - }); -} +}; -inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn, const char* debugname) +template +struct function_traits_impl : function_traits_base { - lua_pushcfunction(L, fn, debugname); -} +}; -inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debugname, int n) +template +struct function_traits_impl : function_traits_base { - lua_pushcclosure(L, fn, debugname, n); -} +}; -inline int lua_error_x(lua_State* L) +template +struct function_traits_impl : function_traits_base { - lua_error(L); - return 0; -} +}; -inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) +template +struct function_traits_impl : function_traits_base { - return lua_getinfo(L, level, "nlS", ar); -} +}; -inline int lua_getstack_info_x(lua_State* L, int level, const char* what, lua_Debug* ar) +template +struct function_traits_impl : function_traits_base { - return lua_getinfo(L, level, what, ar); -} +}; -inline int lua_rawgetp_x(lua_State* L, int idx, void* p) +template +struct function_traits_impl : function_traits_base { - return lua_rawgetp(L, idx, p); -} +}; -inline void lua_rawsetp_x(lua_State* L, int idx, void* p) +template +struct function_traits_impl : function_traits_base { - lua_rawsetp(L, idx, p); -} - -#else -using ::luaL_ref; -using ::luaL_unref; +}; -template -inline void* lua_newuserdata_x(lua_State* L, size_t sz) +template +struct function_traits_impl : function_traits_base { - return lua_newuserdata(L, sz); -} +}; -inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn, const char* debugname) +template +struct function_traits_impl : function_traits_base { - unused(debugname); - - lua_pushcfunction(L, fn); -} +}; -inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debugname, int n) +template +struct function_traits_impl : function_traits_base { - unused(debugname); - - lua_pushcclosure(L, fn, n); -} +}; -inline int lua_error_x(lua_State* L) +template +struct function_traits_impl : function_traits_base { - return lua_error(L); -} +}; -inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) +template +struct function_traits_impl : function_traits_base { - return lua_getstack(L, level, ar); -} +}; -inline int lua_getstack_info_x(lua_State* L, int level, const char* what, lua_Debug* ar) +template +struct function_traits_impl : function_traits_base { - lua_getstack(L, level, ar); - return lua_getinfo(L, what, ar); -} +}; -inline int lua_rawgetp_x(lua_State* L, int idx, void* p) +template +struct function_traits_impl : function_traits_base { -#if LUA_VERSION_NUM < 503 - idx = lua_absindex(L, idx); - luaL_checkstack(L, 1, "not enough stack slots"); - lua_pushlightuserdata(L, p); - lua_rawget(L, idx); - return lua_type(L, -1); -#else - return lua_rawgetp(L, idx, p); -#endif -} +}; -inline void lua_rawsetp_x(lua_State* L, int idx, void* p) +template +struct function_traits_impl : function_traits_base { -#if LUA_VERSION_NUM < 503 - idx = lua_absindex(L, idx); - luaL_checkstack(L, 1, "not enough stack slots"); - lua_pushlightuserdata(L, p); - lua_insert(L, -2); - lua_rawset(L, idx); -#else - lua_rawsetp(L, idx, p); +}; #endif -} -#endif +template +struct functor_traits_impl : function_traits_impl +{ +}; -#if LUA_VERSION_NUM < 505 -inline lua_State* lua_newstate_x(lua_Alloc f, void* ud, [[maybe_unused]] unsigned seed) +template +struct function_traits : std::conditional_t, + detail::functor_traits_impl, + detail::function_traits_impl> +{ +}; + +template +struct function_argument_or_void +{ + using type = void; +}; + +template +struct function_argument_or_void::argument_types>>> +{ + using type = std::tuple_element_t::argument_types>; +}; + +template +using function_argument_or_void_t = typename function_argument_or_void::type; + +template +using function_result_t = typename function_traits::result_type; + +template +using function_argument_t = std::tuple_element_t::argument_types>; + +template +using function_arguments_t = typename function_traits::argument_types; + +template +static constexpr std::size_t function_arity_v = function_traits::arity; + +template +static constexpr bool function_is_member_v = function_traits::is_member; + +template +static constexpr bool function_is_const_v = function_traits::is_const; + +template +struct is_callable +{ + static constexpr bool value = false; +}; + +template +struct is_callable> +{ + static constexpr bool value = true; +}; + +template +struct is_callable && std::is_function_v>>> +{ + static constexpr bool value = true; +}; + +template +struct is_callable>> +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_callable_v = is_callable::value; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = true; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_const_member_function_pointer_v = is_const_member_function_pointer::value; + +template +struct is_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template <> +struct is_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_cfunction_pointer_v = is_cfunction_pointer::value; + +template +struct is_member_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_member_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +struct is_member_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_member_cfunction_pointer_v = is_member_cfunction_pointer::value; + +template +struct is_const_member_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_const_member_cfunction_pointer_v = is_const_member_cfunction_pointer::value; + +template +inline static constexpr bool is_any_cfunction_pointer_v = is_cfunction_pointer_v || is_member_cfunction_pointer_v; + +template +inline static constexpr bool is_proxy_member_function_v = + !std::is_member_function_pointer_v && + std::is_same_v>>>; + +template +inline static constexpr bool is_const_proxy_function_v = + is_proxy_member_function_v && + std::is_const_v>>>; + +template +struct function_arity_excluding +{ +}; + +template < class... Ts, class ExclusionType> +struct function_arity_excluding, ExclusionType> + : std::integral_constant, ExclusionType> ? 0 : 1))> +{ +}; + +template +inline static constexpr std::size_t function_arity_excluding_v = function_arity_excluding, ExclusionType>::value; + +template +struct member_function_arity_excluding +{ +}; + +template +struct member_function_arity_excluding, ExclusionType, std::enable_if_t>> + : std::integral_constant, ExclusionType> ? 0 : 1))> +{ +}; + +template +struct member_function_arity_excluding, ExclusionType, std::enable_if_t>> + : std::integral_constant, ExclusionType> ? 0 : 1)) - 1> +{ +}; + +template +inline static constexpr std::size_t member_function_arity_excluding_v = member_function_arity_excluding, ExclusionType>::value; + +template +static constexpr bool is_const_function = + detail::is_const_member_function_pointer_v || + (detail::function_arity_v > 0 && detail::is_const_proxy_function_v); + +template +inline static constexpr std::size_t const_functions_count = (0 + ... + (is_const_function ? 1 : 0)); + +template +inline static constexpr std::size_t non_const_functions_count = (0 + ... + (is_const_function ? 0 : 1)); + +template +constexpr auto tupleize(Types&&... types) +{ + return std::tuple(std::forward(types)...); +} + +template +struct remove_first_type +{ +}; + +template +struct remove_first_type> +{ + using type = std::tuple; +}; + +template +using remove_first_type_t = typename remove_first_type::type; + +} +} + + +// End File: Source/LuaBridge/detail/FuncTraits.h + +// Begin File: Source/LuaBridge/detail/LuaHelpers.h + +namespace luabridge { + +template +constexpr void unused(Args&&...) +{ +} + +#if LUA_VERSION_NUM < 502 +using lua_Unsigned = std::make_unsigned_t; + +#if ! LUABRIDGE_ON_LUAU +inline int lua_absindex(lua_State* L, int idx) +{ + if (idx > LUA_REGISTRYINDEX && idx < 0) + return lua_gettop(L) + idx + 1; + else + return idx; +} +#endif + +#define LUA_OPEQ 1 +#define LUA_OPLT 2 +#define LUA_OPLE 3 + +inline int lua_compare(lua_State* L, int idx1, int idx2, int op) +{ + switch (op) + { + case LUA_OPEQ: + return lua_equal(L, idx1, idx2); + + case LUA_OPLT: + return lua_lessthan(L, idx1, idx2); + + case LUA_OPLE: + return lua_equal(L, idx1, idx2) || lua_lessthan(L, idx1, idx2); + + default: + return 0; + } +} + +#if ! LUABRIDGE_ON_LUAJIT +inline void* luaL_testudata(lua_State* L, int ud, const char* tname) +{ + void* p = lua_touserdata(L, ud); + if (p == nullptr) + return nullptr; + + if (! lua_getmetatable(L, ud)) + return nullptr; + + luaL_getmetatable(L, tname); + if (! lua_rawequal(L, -1, -2)) + p = nullptr; + + lua_pop(L, 2); + return p; +} +#endif + +inline int get_length(lua_State* L, int idx) +{ + return static_cast(lua_objlen(L, idx)); +} +#else +inline int get_length(lua_State* L, int idx) +{ + return static_cast(lua_rawlen(L, idx)); +} +#endif + +#if LUABRIDGE_ON_LUAU +inline int luaL_ref(lua_State* L, int idx) +{ + LUABRIDGE_ASSERT(idx == LUA_REGISTRYINDEX); + + const int ref = lua_ref(L, -1); + + lua_pop(L, 1); + + return ref; +} + +inline void luaL_unref(lua_State* L, int idx, int ref) +{ + unused(idx); + + lua_unref(L, ref); +} + +template +inline void* lua_newuserdata_x(lua_State* L, size_t sz) +{ + return lua_newuserdatadtor(L, sz, [](void* x) + { + T* object = static_cast(x); + object->~T(); + }); +} + +inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn, const char* debugname) +{ + lua_pushcfunction(L, fn, debugname); +} + +inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debugname, int n) +{ + lua_pushcclosure(L, fn, debugname, n); +} + +[[noreturn]] inline void lua_error_x(lua_State* L) +{ + lua_error(L); +} + +inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) +{ + return lua_getinfo(L, level, "nlS", ar); +} + +inline int lua_getstack_info_x(lua_State* L, int level, const char* what, lua_Debug* ar) +{ + return lua_getinfo(L, level, what, ar); +} + +inline int lua_rawgetp_x(lua_State* L, int idx, void* p) +{ + return lua_rawgetp(L, idx, p); +} + +inline void lua_rawsetp_x(lua_State* L, int idx, void* p) +{ + lua_rawsetp(L, idx, p); +} + +#else +using ::luaL_ref; +using ::luaL_unref; + +template +inline void* lua_newuserdata_x(lua_State* L, size_t sz) +{ + return lua_newuserdata(L, sz); +} + +inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn, const char* debugname) +{ + unused(debugname); + + lua_pushcfunction(L, fn); +} + +inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debugname, int n) +{ + unused(debugname); + + lua_pushcclosure(L, fn, n); +} + +[[noreturn]] inline void lua_error_x(lua_State* L) +{ + lua_error(L); + + detail::unreachable(); +} + +inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) +{ + return lua_getstack(L, level, ar); +} + +inline int lua_getstack_info_x(lua_State* L, int level, const char* what, lua_Debug* ar) +{ + lua_getstack(L, level, ar); + return lua_getinfo(L, what, ar); +} + +inline int lua_rawgetp_x(lua_State* L, int idx, void* p) +{ +#if LUA_VERSION_NUM < 503 + idx = lua_absindex(L, idx); + luaL_checkstack(L, 1, "not enough stack slots"); + lua_pushlightuserdata(L, p); + lua_rawget(L, idx); + return lua_type(L, -1); +#else + return lua_rawgetp(L, idx, p); +#endif +} + +inline void lua_rawsetp_x(lua_State* L, int idx, void* p) +{ +#if LUA_VERSION_NUM < 503 + idx = lua_absindex(L, idx); + luaL_checkstack(L, 1, "not enough stack slots"); + lua_pushlightuserdata(L, p); + lua_insert(L, -2); + lua_rawset(L, idx); +#else + lua_rawsetp(L, idx, p); +#endif +} + +#endif + +#if LUA_VERSION_NUM < 505 +inline lua_State* lua_newstate_x(lua_Alloc f, void* ud, [[maybe_unused]] unsigned seed) { return lua_newstate(f, ud); } @@ -357,7 +769,7 @@ inline lua_Integer to_integerx(lua_State* L, int idx, int* isnum) if (ok) { if (n < static_cast(std::numeric_limits::min()) || - n > static_cast(std::numeric_limits::max())) + n >= -static_cast(std::numeric_limits::min())) { if (isnum) *isnum = 0; @@ -603,7 +1015,7 @@ void* lua_newuserdata_aligned(lua_State* L, Args&&... args) return pointer; } -inline int raise_lua_error(lua_State* L, const char* fmt, ...) +[[noreturn]] inline void raise_lua_error(lua_State* L, const char* fmt, ...) { va_list argp; va_start(argp, fmt); @@ -614,7 +1026,7 @@ inline int raise_lua_error(lua_State* L, const char* fmt, ...) if (message != nullptr) { if (auto str = std::string_view(message); !str.empty() && str[0] == '[') - return lua_error_x(L); + lua_error_x(L); } bool pushed_error = false; @@ -646,7 +1058,7 @@ inline int raise_lua_error(lua_State* L, const char* fmt, ...) lua_remove(L, -3); lua_concat(L, 2); - return lua_error_x(L); + lua_error_x(L); } template @@ -2969,7 +3381,7 @@ class LuaException : public std::exception catch (const std::exception& e) { lua_pushstring(L, e.what()); - return lua_error_x(L); + lua_error_x(L); } } #endif @@ -5384,223 +5796,15 @@ struct Stack> for (std::size_t i = 0; i < Size; ++i) { - lua_pushinteger(L, static_cast(i + 1)); - - auto result = Stack::push(L, array[i]); - if (! result) - return result; - - lua_settable(L, -3); - } - - stackRestore.reset(); - return {}; - } - - [[nodiscard]] static TypeResult get(lua_State* L, int index) - { - if (!lua_istable(L, index)) - return makeErrorCode(ErrorCode::InvalidTypeCast); - - if (get_length(L, index) != Size) - return makeErrorCode(ErrorCode::InvalidTableSizeInCast); - - const StackRestore stackRestore(L); - - Type array; - - int absIndex = lua_absindex(L, index); - lua_pushnil(L); - - int arrayIndex = 0; - while (lua_next(L, absIndex) != 0) - { - auto item = Stack::get(L, -1); - if (!item) - return makeErrorCode(ErrorCode::InvalidTypeCast); - - array[arrayIndex++] = *item; - lua_pop(L, 1); - } - - return array; - } - - [[nodiscard]] static bool isInstance(lua_State* L, int index) - { - return lua_istable(L, index) && get_length(L, index) == Size; - } -}; - -} - - -// End File: Source/LuaBridge/Array.h - -// Begin File: Source/LuaBridge/Dump.h - -namespace luabridge { -namespace detail { -inline void putIndent(std::ostream& stream, unsigned level) -{ - for (unsigned i = 0; i < level; ++i) - stream << " "; -} -} - -inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr); - -inline void dumpValue(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr) -{ - const int stackTop = lua_gettop(L); - const int absIndex = (index > 0) ? index : (index < 0 ? stackTop + index + 1 : 0); - const int type = (absIndex < 1 || absIndex > stackTop) ? LUA_TNONE : lua_type(L, index); - switch (type) - { - case LUA_TNIL: - stream << "nil"; - break; - - case LUA_TBOOLEAN: - stream << (lua_toboolean(L, index) ? "true" : "false"); - break; - - case LUA_TNUMBER: - stream << lua_tonumber(L, index); - break; - - case LUA_TSTRING: - stream << '"' << lua_tostring(L, index) << '"'; - break; - - case LUA_TFUNCTION: - if (lua_iscfunction(L, index)) - stream << "cfunction@" << lua_topointer(L, index); - else - stream << "function@" << lua_topointer(L, index); - break; - - case LUA_TTHREAD: - stream << "thread@" << lua_tothread(L, index); - break; - - case LUA_TLIGHTUSERDATA: - stream << "lightuserdata@" << lua_touserdata(L, index); - break; - - case LUA_TTABLE: - dumpTable(L, index, maxDepth, level, false, stream); - break; - - case LUA_TUSERDATA: - stream << "userdata@" << lua_touserdata(L, index); - break; - - default: - stream << lua_typename(L, type); - break; - } - - if (newLine) - stream << '\n'; -} - -inline void dumpTable(lua_State* L, int index, unsigned maxDepth, unsigned level, bool newLine, std::ostream& stream) -{ - stream << "table@" << lua_topointer(L, index); - - if (level > maxDepth) - { - if (newLine) - stream << '\n'; - - return; - } - - index = lua_absindex(L, index); - - stream << " {"; - - int valuesCount = 0; - - lua_pushnil(L); - while (lua_next(L, index)) - { - stream << '\n'; - detail::putIndent(stream, level + 1); - - dumpValue(L, -2, maxDepth, level + 1, false, stream); - stream << ": "; - dumpValue(L, -1, maxDepth, level + 1, false, stream); - stream << ","; - - lua_pop(L, 1); - - ++valuesCount; - } - - if (valuesCount > 0) - { - stream << '\n'; - detail::putIndent(stream, level); - } - - stream << "}"; - - if (newLine) - stream << '\n'; -} - -inline void dumpState(lua_State* L, unsigned maxDepth = 1, std::ostream& stream = std::cerr) -{ - stream << "----------------------------------------------" << '\n'; - - int top = lua_gettop(L); - for (int i = 1; i <= top; ++i) - { - stream << "stack #" << i << " (" << -(top - i + 1) << "): "; - - dumpValue(L, i, maxDepth, 0, true, stream); - } -} - -} - - -// End File: Source/LuaBridge/Dump.h - -// Begin File: Source/LuaBridge/List.h - -namespace luabridge { - -template -struct Stack> -{ - using Type = std::list; - - [[nodiscard]] static Result push(lua_State* L, const Type& list) - { -#if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(L, 3)) - return makeErrorCode(ErrorCode::LuaStackOverflow); -#endif - - StackRestore stackRestore(L); - - lua_createtable(L, static_cast(list.size()), 0); - - auto it = list.cbegin(); - for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it) - { - lua_pushinteger(L, tableIndex); + lua_pushinteger(L, static_cast(i + 1)); - auto result = Stack::push(L, *it); + auto result = Stack::push(L, array[i]); if (! result) return result; lua_settable(L, -3); } - + stackRestore.reset(); return {}; } @@ -5610,447 +5814,244 @@ struct Stack> if (!lua_istable(L, index)) return makeErrorCode(ErrorCode::InvalidTypeCast); + if (get_length(L, index) != Size) + return makeErrorCode(ErrorCode::InvalidTableSizeInCast); + const StackRestore stackRestore(L); - Type list; + Type array; int absIndex = lua_absindex(L, index); lua_pushnil(L); + int arrayIndex = 0; while (lua_next(L, absIndex) != 0) { auto item = Stack::get(L, -1); - if (! item) + if (!item) return makeErrorCode(ErrorCode::InvalidTypeCast); - list.emplace_back(*item); + array[arrayIndex++] = *item; lua_pop(L, 1); } - return list; + return array; } [[nodiscard]] static bool isInstance(lua_State* L, int index) { - return lua_istable(L, index); + return lua_istable(L, index) && get_length(L, index) == Size; } }; } -// End File: Source/LuaBridge/List.h +// End File: Source/LuaBridge/Array.h -// Begin File: Source/LuaBridge/detail/FuncTraits.h +// Begin File: Source/LuaBridge/Dump.h namespace luabridge { namespace detail { - -[[noreturn]] inline void unreachable() +inline void putIndent(std::ostream& stream, unsigned level) { -#if defined(__GNUC__) - __builtin_unreachable(); -#elif defined(_MSC_VER) - __assume(false); -#endif + for (unsigned i = 0; i < level; ++i) + stream << " "; } +} -template< class T > -struct remove_cvref -{ - typedef std::remove_cv_t> type; -}; - -template -using remove_cvref_t = typename remove_cvref::type; - -template -struct function_traits_base -{ - using result_type = R; - - using argument_types = std::tuple; - - static constexpr auto arity = sizeof...(Args); - - static constexpr auto is_member = IsMember; - - static constexpr auto is_const = IsConst; -}; - -template -struct function_traits_impl; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -#if defined(_MSC_VER) && defined(_M_IX86) -inline static constexpr bool is_stdcall_default_calling_convention = std::is_same_v; -inline static constexpr bool is_fastcall_default_calling_convention = std::is_same_v; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; - -template -struct function_traits_impl : function_traits_base -{ -}; -#endif - -template -struct functor_traits_impl : function_traits_impl -{ -}; - -template -struct function_traits : std::conditional_t, - detail::functor_traits_impl, - detail::function_traits_impl> -{ -}; +inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr); -template -struct function_argument_or_void +inline void dumpValue(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr) { - using type = void; -}; + const int stackTop = lua_gettop(L); + const int absIndex = (index > 0) ? index : (index < 0 ? stackTop + index + 1 : 0); + const int type = (absIndex < 1 || absIndex > stackTop) ? LUA_TNONE : lua_type(L, index); + switch (type) + { + case LUA_TNIL: + stream << "nil"; + break; -template -struct function_argument_or_void::argument_types>>> -{ - using type = std::tuple_element_t::argument_types>; -}; + case LUA_TBOOLEAN: + stream << (lua_toboolean(L, index) ? "true" : "false"); + break; -template -using function_argument_or_void_t = typename function_argument_or_void::type; + case LUA_TNUMBER: + stream << lua_tonumber(L, index); + break; -template -using function_result_t = typename function_traits::result_type; + case LUA_TSTRING: + stream << '"' << lua_tostring(L, index) << '"'; + break; -template -using function_argument_t = std::tuple_element_t::argument_types>; + case LUA_TFUNCTION: + if (lua_iscfunction(L, index)) + stream << "cfunction@" << lua_topointer(L, index); + else + stream << "function@" << lua_topointer(L, index); + break; -template -using function_arguments_t = typename function_traits::argument_types; + case LUA_TTHREAD: + stream << "thread@" << lua_tothread(L, index); + break; -template -static constexpr std::size_t function_arity_v = function_traits::arity; + case LUA_TLIGHTUSERDATA: + stream << "lightuserdata@" << lua_touserdata(L, index); + break; -template -static constexpr bool function_is_member_v = function_traits::is_member; + case LUA_TTABLE: + dumpTable(L, index, maxDepth, level, false, stream); + break; -template -static constexpr bool function_is_const_v = function_traits::is_const; + case LUA_TUSERDATA: + stream << "userdata@" << lua_touserdata(L, index); + break; -template -struct is_callable -{ - static constexpr bool value = false; -}; + default: + stream << lua_typename(L, type); + break; + } -template -struct is_callable> -{ - static constexpr bool value = true; -}; + if (newLine) + stream << '\n'; +} -template -struct is_callable && std::is_function_v>>> +inline void dumpTable(lua_State* L, int index, unsigned maxDepth, unsigned level, bool newLine, std::ostream& stream) { - static constexpr bool value = true; -}; + stream << "table@" << lua_topointer(L, index); -template -struct is_callable>> -{ - static constexpr bool value = true; -}; + if (level > maxDepth) + { + if (newLine) + stream << '\n'; -template -inline static constexpr bool is_callable_v = is_callable::value; + return; + } -template -struct is_const_member_function_pointer -{ - static constexpr bool value = false; -}; + index = lua_absindex(L, index); -template -struct is_const_member_function_pointer -{ - static constexpr bool value = false; -}; + stream << " {"; -template -struct is_const_member_function_pointer -{ - static constexpr bool value = true; -}; + int valuesCount = 0; -template -struct is_const_member_function_pointer -{ - static constexpr bool value = false; -}; + lua_pushnil(L); + while (lua_next(L, index)) + { + stream << '\n'; + detail::putIndent(stream, level + 1); -template -struct is_const_member_function_pointer -{ - static constexpr bool value = true; -}; + dumpValue(L, -2, maxDepth, level + 1, false, stream); + stream << ": "; + dumpValue(L, -1, maxDepth, level + 1, false, stream); + stream << ","; -template -inline static constexpr bool is_const_member_function_pointer_v = is_const_member_function_pointer::value; + lua_pop(L, 1); -template -struct is_cfunction_pointer -{ - static constexpr bool value = false; -}; + ++valuesCount; + } -template <> -struct is_cfunction_pointer -{ - static constexpr bool value = true; -}; + if (valuesCount > 0) + { + stream << '\n'; + detail::putIndent(stream, level); + } -template -inline static constexpr bool is_cfunction_pointer_v = is_cfunction_pointer::value; + stream << "}"; -template -struct is_member_cfunction_pointer -{ - static constexpr bool value = false; -}; + if (newLine) + stream << '\n'; +} -template -struct is_member_cfunction_pointer +inline void dumpState(lua_State* L, unsigned maxDepth = 1, std::ostream& stream = std::cerr) { - static constexpr bool value = true; -}; + stream << "----------------------------------------------" << '\n'; -template -struct is_member_cfunction_pointer -{ - static constexpr bool value = true; -}; + int top = lua_gettop(L); + for (int i = 1; i <= top; ++i) + { + stream << "stack #" << i << " (" << -(top - i + 1) << "): "; -template -inline static constexpr bool is_member_cfunction_pointer_v = is_member_cfunction_pointer::value; + dumpValue(L, i, maxDepth, 0, true, stream); + } +} -template -struct is_const_member_cfunction_pointer -{ - static constexpr bool value = false; -}; +} -template -struct is_const_member_cfunction_pointer -{ - static constexpr bool value = false; -}; -template -struct is_const_member_cfunction_pointer -{ - static constexpr bool value = true; -}; +// End File: Source/LuaBridge/Dump.h -template -inline static constexpr bool is_const_member_cfunction_pointer_v = is_const_member_cfunction_pointer::value; +// Begin File: Source/LuaBridge/List.h -template -inline static constexpr bool is_any_cfunction_pointer_v = is_cfunction_pointer_v || is_member_cfunction_pointer_v; +namespace luabridge { -template -inline static constexpr bool is_proxy_member_function_v = - !std::is_member_function_pointer_v && - std::is_same_v>>>; +template +struct Stack> +{ + using Type = std::list; + + [[nodiscard]] static Result push(lua_State* L, const Type& list) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif -template -inline static constexpr bool is_const_proxy_function_v = - is_proxy_member_function_v && - std::is_const_v>>>; + StackRestore stackRestore(L); -template -struct function_arity_excluding -{ -}; + lua_createtable(L, static_cast(list.size()), 0); -template < class... Ts, class ExclusionType> -struct function_arity_excluding, ExclusionType> - : std::integral_constant, ExclusionType> ? 0 : 1))> -{ -}; + auto it = list.cbegin(); + for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, tableIndex); -template -inline static constexpr std::size_t function_arity_excluding_v = function_arity_excluding, ExclusionType>::value; + auto result = Stack::push(L, *it); + if (! result) + return result; -template -struct member_function_arity_excluding -{ -}; + lua_settable(L, -3); + } -template -struct member_function_arity_excluding, ExclusionType, std::enable_if_t>> - : std::integral_constant, ExclusionType> ? 0 : 1))> -{ -}; + stackRestore.reset(); + return {}; + } -template -struct member_function_arity_excluding, ExclusionType, std::enable_if_t>> - : std::integral_constant, ExclusionType> ? 0 : 1)) - 1> -{ -}; + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); -template -inline static constexpr std::size_t member_function_arity_excluding_v = member_function_arity_excluding, ExclusionType>::value; + const StackRestore stackRestore(L); -template -static constexpr bool is_const_function = - detail::is_const_member_function_pointer_v || - (detail::function_arity_v > 0 && detail::is_const_proxy_function_v); + Type list; -template -inline static constexpr std::size_t const_functions_count = (0 + ... + (is_const_function ? 1 : 0)); + int absIndex = lua_absindex(L, index); + lua_pushnil(L); -template -inline static constexpr std::size_t non_const_functions_count = (0 + ... + (is_const_function ? 0 : 1)); + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); -template -constexpr auto tupleize(Types&&... types) -{ - return std::tuple(std::forward(types)...); -} + list.emplace_back(*item); + lua_pop(L, 1); + } -template -struct remove_first_type -{ -}; + return list; + } -template -struct remove_first_type> -{ - using type = std::tuple; + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } }; -template -using remove_first_type_t = typename remove_first_type::type; - -} } -// End File: Source/LuaBridge/detail/FuncTraits.h +// End File: Source/LuaBridge/List.h // Begin File: Source/LuaBridge/detail/FlagSet.h @@ -7922,7 +7923,7 @@ inline int try_overload_functions(lua_State* L) } else { - return lua_error_x(L); + lua_error_x(L); } } @@ -7937,7 +7938,7 @@ inline int try_overload_functions(lua_State* L) } lua_concat(L, nerrors * 2 + 1); - return lua_error_x(L); + lua_error_x(L); } inline void push_function(lua_State* L, lua_CFunction fp, const char* debugname) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index f43626e3..bdc357db 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1949,7 +1949,7 @@ inline int try_overload_functions(lua_State* L) } else { - return lua_error_x(L); // critical error: rethrow + lua_error_x(L); // critical error: rethrow } } @@ -1965,7 +1965,7 @@ inline int try_overload_functions(lua_State* L) } lua_concat(L, nerrors * 2 + 1); - return lua_error_x(L); // throw error message just built + lua_error_x(L); // throw error message just built } //================================================================================================= diff --git a/Source/LuaBridge/detail/LuaException.h b/Source/LuaBridge/detail/LuaException.h index 2362b9a8..e65cc41f 100644 --- a/Source/LuaBridge/detail/LuaException.h +++ b/Source/LuaBridge/detail/LuaException.h @@ -159,7 +159,7 @@ class LuaException : public std::exception catch (const std::exception& e) { lua_pushstring(L, e.what()); - return lua_error_x(L); + lua_error_x(L); } } #endif diff --git a/Source/LuaBridge/detail/LuaHelpers.h b/Source/LuaBridge/detail/LuaHelpers.h index c93f1c25..5bdbaf7e 100644 --- a/Source/LuaBridge/detail/LuaHelpers.h +++ b/Source/LuaBridge/detail/LuaHelpers.h @@ -6,7 +6,7 @@ #pragma once -#include "Config.h" +#include "FuncTraits.h" #include #include @@ -132,10 +132,9 @@ inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debug lua_pushcclosure(L, fn, debugname, n); } -inline int lua_error_x(lua_State* L) +[[noreturn]] inline void lua_error_x(lua_State* L) { lua_error(L); - return 0; } inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) @@ -182,9 +181,11 @@ inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debug lua_pushcclosure(L, fn, n); } -inline int lua_error_x(lua_State* L) +[[noreturn]] inline void lua_error_x(lua_State* L) { - return lua_error(L); + lua_error(L); + + detail::unreachable(); } inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) @@ -259,7 +260,7 @@ inline lua_Integer to_integerx(lua_State* L, int idx, int* isnum) if (ok) { if (n < static_cast(std::numeric_limits::min()) || - n > static_cast(std::numeric_limits::max())) + n >= -static_cast(std::numeric_limits::min())) { if (isnum) *isnum = 0; @@ -566,7 +567,7 @@ void* lua_newuserdata_aligned(lua_State* L, Args&&... args) /** * @brief Safe error able to walk backwards for error reporting correctly. */ -inline int raise_lua_error(lua_State* L, const char* fmt, ...) +[[noreturn]] inline void raise_lua_error(lua_State* L, const char* fmt, ...) { va_list argp; va_start(argp, fmt); @@ -577,7 +578,7 @@ inline int raise_lua_error(lua_State* L, const char* fmt, ...) if (message != nullptr) { if (auto str = std::string_view(message); !str.empty() && str[0] == '[') - return lua_error_x(L); + lua_error_x(L); } bool pushed_error = false; @@ -609,7 +610,7 @@ inline int raise_lua_error(lua_State* L, const char* fmt, ...) lua_remove(L, -3); lua_concat(L, 2); - return lua_error_x(L); + lua_error_x(L); } /** diff --git a/Tests/Lua/LuaLibrary5.5.0.c b/Tests/Lua/LuaLibrary5.5.0.c index 37ad0086..657be20a 100644 --- a/Tests/Lua/LuaLibrary5.5.0.c +++ b/Tests/Lua/LuaLibrary5.5.0.c @@ -70,6 +70,7 @@ extern "C" #elif __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" #pragma GCC diagnostic ignored "-Wmisleading-indentation" #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wempty-body" diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index 88d9467f..96a532df 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -58,6 +58,7 @@ TEST_F(LuaRefTests, TypeCheck) EXPECT_NE(0u, result().hash()); } +#if LUA_VERSION_NUM != 502 // Lua 5.2 hashnum has signed integer overflow UB with float literals { runLua("result = 3.1"); EXPECT_EQ(LUA_TNUMBER, result().type()); @@ -68,6 +69,7 @@ TEST_F(LuaRefTests, TypeCheck) EXPECT_EQ("3.1", ss.str()); EXPECT_NE(0u, result().hash()); } +#endif { runLua("result = 'abcd'"); @@ -161,10 +163,12 @@ TEST_F(LuaRefTests, ValueAccess) ASSERT_EQ(7, result()); ASSERT_EQ(7u, result()); +#if LUA_VERSION_NUM != 502 // Lua 5.2 hashnum has signed integer overflow UB with float literals runLua("result = 3.14"); EXPECT_TRUE(result().isNumber()); ASSERT_FLOAT_EQ(3.14f, result()); ASSERT_DOUBLE_EQ(3.14, result()); +#endif runLua("result = 'D'"); EXPECT_TRUE(result().isString()); @@ -792,6 +796,7 @@ TEST_F(LuaRefTests, IsInstance) EXPECT_FALSE(result().isNil()); EXPECT_FALSE(result().getClassName()); +#if LUA_VERSION_NUM != 502 // Lua 5.2 hashnum has signed integer overflow UB with float literals runLua("result = 3.14"); EXPECT_FALSE(result().isInstance()); EXPECT_FALSE(result().isInstance()); @@ -801,6 +806,7 @@ TEST_F(LuaRefTests, IsInstance) EXPECT_TRUE(result().isNumber()); EXPECT_FALSE(result().isNil()); EXPECT_FALSE(result().getClassName()); +#endif } TEST_F(LuaRefTests, Print) @@ -1317,6 +1323,7 @@ TEST_F(LuaRefTests, ComparisonOperatorPushFailure) { // Covers LuaRef.h:421 (operator==), 458 (operator<), 485 (operator<=), // 511 (operator>), 539 (operator>=), 566 (rawequal) - early return false when rhs push fails +#if LUA_VERSION_NUM != 502 // Lua 5.2 hashnum has signed integer overflow UB with float literals if constexpr (sizeof(long double) > sizeof(lua_Number)) { runLua("result = 1.0"); @@ -1330,6 +1337,7 @@ TEST_F(LuaRefTests, ComparisonOperatorPushFailure) EXPECT_FALSE(ref >= huge); EXPECT_FALSE(ref.rawequal(huge)); } +#endif } TEST_F(LuaRefTests, TableItemCopyWithKeyRef) From 61345a45da282578ea3c8ce45687e61503cf4e1b Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:06:11 +0200 Subject: [PATCH 14/19] Fix sanitizer disable --- Source/LuaBridge/detail/Config.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/LuaBridge/detail/Config.h b/Source/LuaBridge/detail/Config.h index 7ca5bf43..786a12aa 100644 --- a/Source/LuaBridge/detail/Config.h +++ b/Source/LuaBridge/detail/Config.h @@ -42,6 +42,12 @@ #define LUABRIDGE_IF_NO_EXCEPTIONS(...) __VA_ARGS__ #endif +#if defined(__clang__) || defined(__GNUC__) +#define LUABRIDGE_NO_SANITIZE(x) __attribute__((no_sanitize(x))) +#else +#define LUABRIDGE_NO_SANITIZE(x) +#endif + #if defined(LUAU_FASTMATH_BEGIN) #define LUABRIDGE_ON_LUAU 1 #elif defined(LUAJIT_VERSION) From 89ddd90498dedde99c4e7e4546f909918a626e3d Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:06:32 +0200 Subject: [PATCH 15/19] Fix running test on lua <= 5.2 --- Tests/Source/OverloadTests.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/Source/OverloadTests.cpp b/Tests/Source/OverloadTests.cpp index da9fe7e7..0f0461c0 100644 --- a/Tests/Source/OverloadTests.cpp +++ b/Tests/Source/OverloadTests.cpp @@ -160,12 +160,14 @@ TEST_F(OverloadTests, IntegerTypeFallbackOverloads) ASSERT_TRUE(result().isNumber()); EXPECT_EQ(3, result()); +#if LUA_VERSION_NUM > 502 if constexpr (sizeof(lua_Integer) >= sizeof(int64_t)) { runLua("result = test (2147483648)"); ASSERT_TRUE(result().isNumber()); EXPECT_EQ(4, result()); } +#endif } TEST_F(OverloadTests, UnregisteredClass) From 68bd684dd8b742f5cd3cfd4221f2c9420cb0b2b7 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:07:50 +0200 Subject: [PATCH 16/19] Disable tsan as lua is not threadsafe --- .github/workflows/{build_tsan.yml => build_tsan.yml_} | 0 README.md | 1 - 2 files changed, 1 deletion(-) rename .github/workflows/{build_tsan.yml => build_tsan.yml_} (100%) diff --git a/.github/workflows/build_tsan.yml b/.github/workflows/build_tsan.yml_ similarity index 100% rename from .github/workflows/build_tsan.yml rename to .github/workflows/build_tsan.yml_ diff --git a/README.md b/README.md index 5861d0ed..a3a62b46 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ [![Linux](https://github.com/kunitoki/LuaBridge3/workflows/Build%20Linux/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_linux.yml) [![UBSAN](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_ubsan.yml/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_ubsan.yml) [![ASAN](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_asan.yml/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_asan.yml) -[![TSAN](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_tsan.yml/badge.svg?branch=master)](https://github.com/kunitoki/LuaBridge3/actions/workflows/build_tsan.yml) ## Code Coverage [![Coverage Status](https://coveralls.io/repos/github/kunitoki/LuaBridge3/badge.svg?branch=master&kill_cache=1)](https://coveralls.io/github/kunitoki/LuaBridge3?branch=master) From a968199aaaa2125095c085d325901739cb86624b Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:08:40 +0200 Subject: [PATCH 17/19] More fixes --- Distribution/LuaBridge/LuaBridge.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index eefaea10..9fd5f26f 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -72,6 +72,12 @@ #define LUABRIDGE_IF_NO_EXCEPTIONS(...) __VA_ARGS__ #endif +#if defined(__clang__) || defined(__GNUC__) +#define LUABRIDGE_NO_SANITIZE(x) __attribute__((no_sanitize(x))) +#else +#define LUABRIDGE_NO_SANITIZE(x) +#endif + #if defined(LUAU_FASTMATH_BEGIN) #define LUABRIDGE_ON_LUAU 1 #elif defined(LUAJIT_VERSION) From b49f7daf4127dffc1bdd1c94dda5e9cb235302e6 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:10:13 +0200 Subject: [PATCH 18/19] Rename workflows --- .github/workflows/build_asan.yml | 2 +- .github/workflows/build_linux.yml | 2 +- .github/workflows/build_macos.yml | 2 +- .github/workflows/build_tsan.yml_ | 2 +- .github/workflows/build_ubsan.yml | 2 +- .github/workflows/build_windows.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/sonar.yml_ | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build_asan.yml b/.github/workflows/build_asan.yml index 80899994..93111e66 100644 --- a/.github/workflows/build_asan.yml +++ b/.github/workflows/build_asan.yml @@ -1,4 +1,4 @@ -name: Build ASAN +name: ASAN on: push: diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index f692a423..1b508eb0 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -1,4 +1,4 @@ -name: Build Linux +name: Linux on: push: diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index d5a01e70..1e2e47f2 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -1,4 +1,4 @@ -name: Build MacOS +name: macOS on: push: diff --git a/.github/workflows/build_tsan.yml_ b/.github/workflows/build_tsan.yml_ index acbe697f..6ef669c0 100644 --- a/.github/workflows/build_tsan.yml_ +++ b/.github/workflows/build_tsan.yml_ @@ -1,4 +1,4 @@ -name: Build TSAN +name: TSAN on: push: diff --git a/.github/workflows/build_ubsan.yml b/.github/workflows/build_ubsan.yml index 8c9ff510..86e34148 100644 --- a/.github/workflows/build_ubsan.yml +++ b/.github/workflows/build_ubsan.yml @@ -1,4 +1,4 @@ -name: Build UBSAN +name: UBSAN on: push: diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index 01d53534..a67bb15b 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -1,4 +1,4 @@ -name: Build Windows +name: Windows on: push: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ee811877..bdd91b01 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,4 +1,4 @@ -name: "CodeQL" +name: CodeQL on: push: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index fd35e509..21064d56 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,4 +1,4 @@ -name: Code Coverage +name: Coverage on: push: diff --git a/.github/workflows/sonar.yml_ b/.github/workflows/sonar.yml_ index 430cf2f0..cc4e7d6b 100644 --- a/.github/workflows/sonar.yml_ +++ b/.github/workflows/sonar.yml_ @@ -1,4 +1,4 @@ -name: SonarCloud +name: Sonar on: push: From bcdac62c6ff0cb8d48baa7312a29658f21533dff Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 31 Mar 2026 17:46:00 +0200 Subject: [PATCH 19/19] Fixes --- Tests/Lua/Lua.5.2.4/src/llimits.h | 2 +- Tests/Source/MapTests.cpp | 2 ++ Tests/Source/OverloadTests.cpp | 2 +- Tests/Source/UnorderedMapTests.cpp | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/Lua/Lua.5.2.4/src/llimits.h b/Tests/Lua/Lua.5.2.4/src/llimits.h index 152dd055..059e62a8 100644 --- a/Tests/Lua/Lua.5.2.4/src/llimits.h +++ b/Tests/Lua/Lua.5.2.4/src/llimits.h @@ -283,7 +283,7 @@ union luai_Cast { double l_d; LUA_INT32 l_p[2]; }; #define luai_hashnum(i,n) { int e; \ n = l_mathop(frexp)(n, &e) * (lua_Number)(INT_MAX - DBL_MAX_EXP); \ - lua_number2int(i, n); i += e; } + lua_number2int(i, n); i = (int)((unsigned int)(i) + (unsigned int)(e)); } #endif diff --git a/Tests/Source/MapTests.cpp b/Tests/Source/MapTests.cpp index 869ae17f..7d140a29 100644 --- a/Tests/Source/MapTests.cpp +++ b/Tests/Source/MapTests.cpp @@ -118,6 +118,7 @@ TEST_F(MapTests, LuaRef) EXPECT_EQ(expected, result()); } +#if !defined(LUABRIDGE_TEST_LUA_VERSION) || LUABRIDGE_TEST_LUA_VERSION > 502 { using Map = std::map; @@ -138,6 +139,7 @@ TEST_F(MapTests, LuaRef) EXPECT_EQ(expected, result()); } +#endif } TEST_F(MapTests, CastToMap) diff --git a/Tests/Source/OverloadTests.cpp b/Tests/Source/OverloadTests.cpp index 0f0461c0..d7e49e05 100644 --- a/Tests/Source/OverloadTests.cpp +++ b/Tests/Source/OverloadTests.cpp @@ -160,7 +160,7 @@ TEST_F(OverloadTests, IntegerTypeFallbackOverloads) ASSERT_TRUE(result().isNumber()); EXPECT_EQ(3, result()); -#if LUA_VERSION_NUM > 502 +#if ! LUABRIDGE_ON_LUAU && LUA_VERSION_NUM > 502 if constexpr (sizeof(lua_Integer) >= sizeof(int64_t)) { runLua("result = test (2147483648)"); diff --git a/Tests/Source/UnorderedMapTests.cpp b/Tests/Source/UnorderedMapTests.cpp index 2059ee74..114dcf31 100644 --- a/Tests/Source/UnorderedMapTests.cpp +++ b/Tests/Source/UnorderedMapTests.cpp @@ -133,6 +133,7 @@ TEST_F(UnorderedMapTests, StackOverflow) TEST_F(UnorderedMapTests, LuaRef) { +#if !defined(LUABRIDGE_TEST_LUA_VERSION) || LUABRIDGE_TEST_LUA_VERSION > 502 { runLua("result = {[false] = true, a = 'abc', [1] = 5, [3.14] = -1.1}"); @@ -150,6 +151,7 @@ TEST_F(UnorderedMapTests, LuaRef) ASSERT_EQ(expected, actual); ASSERT_EQ(expected, result()); } +#endif { runLua("result = {'a', 'b', 'c'}");