From ab43472a77a0941067c2a6db622f2e31efa9a919 Mon Sep 17 00:00:00 2001 From: pcbz <73325521+pcbzzz@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:41:13 -0700 Subject: [PATCH] fix: require explicit throwing of LuaState errors --- src/main/java/net/hollowcube/luau/LuaState.java | 7 ++++--- .../java/net/hollowcube/luau/LuaStateImpl.java | 4 ++-- .../net/hollowcube/luau/require/RequireImpl.java | 14 +++++++------- .../java/net/hollowcube/luau/TestLuaError.java | 7 ++++--- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main/java/net/hollowcube/luau/LuaState.java b/src/main/java/net/hollowcube/luau/LuaState.java index 12e39e6a..50e4938c 100644 --- a/src/main/java/net/hollowcube/luau/LuaState.java +++ b/src/main/java/net/hollowcube/luau/LuaState.java @@ -240,11 +240,12 @@ static LuaState wrap(MemorySegment L) { void setMemCat(int category); long totalBytes(int category); - /// Throws, assumes that there is a value on the stack which becomes the thrown object. - @Contract("-> fail") + /// Creates a LuaError for the caller to throw + @CheckReturnValue LuaError error(); + @CheckReturnValue LuaError error(String message); - @Contract("_, _ -> fail") + @CheckReturnValue LuaError error(@PrintFormat String message, @Nullable Object... args); @Contract("_, _ -> fail") void typeError(int narg, String tname); diff --git a/src/main/java/net/hollowcube/luau/LuaStateImpl.java b/src/main/java/net/hollowcube/luau/LuaStateImpl.java index bdf960a5..311ce22f 100644 --- a/src/main/java/net/hollowcube/luau/LuaStateImpl.java +++ b/src/main/java/net/hollowcube/luau/LuaStateImpl.java @@ -844,7 +844,7 @@ public long totalBytes(int category) { @Override public LuaError error() { - throw new LuaError(null); + return new LuaError(null); } @Override @@ -994,7 +994,7 @@ public LuaCallbacks callbacks() { @Override public void checkAny(int argNum) { if (lua_type(L, argNum) == LuaType.NONE.id()) return; - error("missing argument #%d", argNum); + throw error("missing argument #%d", argNum); } @Override diff --git a/src/main/java/net/hollowcube/luau/require/RequireImpl.java b/src/main/java/net/hollowcube/luau/require/RequireImpl.java index 7dc3785c..0685d331 100644 --- a/src/main/java/net/hollowcube/luau/require/RequireImpl.java +++ b/src/main/java/net/hollowcube/luau/require/RequireImpl.java @@ -40,7 +40,7 @@ public static void pushRequireClosure(LuaState state, RequireResolver lrc) { public static void registerModule(LuaState state, String path) { if (path.isEmpty() || path.charAt(0) != '@') - state.error("path must begin with '@'"); + throw state.error("path must begin with '@'"); state.findTable(REGISTRY_INDEX, REGISTERED_CACHE_TABLE_KEY, 1); @@ -76,7 +76,7 @@ private static int requireImpl(LuaState state) { int level = 0; do { if (lua_getinfo(state.L(), level++, DEBUG_WHAT, debug) == 0) - state.error("require is not supported in this context"); + throw state.error("require is not supported in this context"); } while (lua_Debug.what(debug).get(ValueLayout.JAVA_BYTE, 0) != 'L'); final String source = lua_Debug.source(debug).getString(0); @@ -90,7 +90,7 @@ private static int requireContinuationImpl(LuaState state, LuaStatus ignored) { final String cacheKey = state.checkString(2); if (numResults < 1) - state.error("module must return a single value"); + throw state.error("module must return a single value"); // Cache the results if (numResults == 1) { @@ -117,7 +117,7 @@ private static int requireInternal(LuaState state, String requirerChunkName) { state.top(1); // Discard extra arguments, we only use path final RequireResolver lrc = (RequireResolver) state.toUserData(upvalueIndex(1)); - if (lrc == null) state.error("unable to find require configuration"); + if (lrc == null) throw state.error("unable to find require configuration"); final String path = state.checkString(1); if (checkRegisteredModules(state, path)) @@ -128,7 +128,7 @@ private static int requireInternal(LuaState state, String requirerChunkName) { case ResolvedRequire.Cached _ -> { return 1; } - case ResolvedRequire.ErrorReported(String error) -> state.error(error); + case ResolvedRequire.ErrorReported(String error) -> throw state.error(error); case ResolvedRequire.ModuleRead(String chunkName, String loadName, String cacheKey) -> { // (1) path, ..., cacheKey, chunkname, loadname state.pushString(cacheKey); @@ -147,7 +147,7 @@ private static int requireInternal(LuaState state, String requirerChunkName) { int numResults = lrc.load(state, path, chunkName, loadName); if (numResults == -1) { if (state.top() != stackValues) - state.error("stack cannot be modified when require yields"); + throw state.error("stack cannot be modified when require yields"); return state.yield(0); } @@ -165,7 +165,7 @@ record ErrorReported(String error) implements ResolvedRequire {} private static ResolvedRequire resolveRequire(RequireResolver lrc, LuaState state, String requirerChunkName, String path) { if (!lrc.isRequireAllowed(state, requirerChunkName)) - state.error("require is not supported in this context"); + throw state.error("require is not supported in this context"); final Navigator navigator = new Navigator(lrc, state, requirerChunkName); diff --git a/src/test/java/net/hollowcube/luau/TestLuaError.java b/src/test/java/net/hollowcube/luau/TestLuaError.java index 05836422..cf46bcfa 100644 --- a/src/test/java/net/hollowcube/luau/TestLuaError.java +++ b/src/test/java/net/hollowcube/luau/TestLuaError.java @@ -24,7 +24,9 @@ void stripDefaultErrorText() { @Test void errorShouldThrowLuaError(LuaState state) { - assertThrows(LuaError.class, state::error); + assertThrows(LuaError.class, () -> { + throw state.error(); + }); } @Test @@ -98,8 +100,7 @@ void throwInUpcall(LuaState state, Arena arena) { void throwInUpcallNoMessage(LuaState state, Arena arena) { var func = LuaFunc.wrap( L -> { - L.error(); - return 0; + throw L.error(); }, "errfunc", arena