diff --git a/spec/traceback/anon_lua/anon_lua.pln b/spec/traceback/anon_lua/anon_lua.pln new file mode 100644 index 00000000..f47f2742 --- /dev/null +++ b/spec/traceback/anon_lua/anon_lua.pln @@ -0,0 +1,12 @@ +-- Copyright (c) 2024, The Pallene Developers +-- Pallene is licensed under the MIT license. +-- Please refer to the LICENSE and AUTHORS files for details +-- SPDX-License-Identifier: MIT + +local mod: module = {} + +function mod.call_anon_lua_fn(callback: () -> ()) + callback() +end + +return mod diff --git a/spec/traceback/anon_lua/main.lua b/spec/traceback/anon_lua/main.lua new file mode 100644 index 00000000..2dbc9f40 --- /dev/null +++ b/spec/traceback/anon_lua/main.lua @@ -0,0 +1,15 @@ +-- Copyright (c) 2024, The Pallene Developers +-- Pallene is licensed under the MIT license. +-- Please refer to the LICENSE and AUTHORS files for details +-- SPDX-License-Identifier: MIT + +local anon = require "spec.traceback.anon_lua.anon_lua" + +local function wrapper() + anon.call_anon_lua_fn(function() + error "Error from an anonymous Lua fn!" + end) +end + +-- luacheck: globals pallene_tracer_debug_traceback +xpcall(wrapper, pallene_tracer_debug_traceback) diff --git a/spec/traceback/depth_recursion/main.lua b/spec/traceback/depth_recursion/main.lua index 1688ee67..a081fb5a 100644 --- a/spec/traceback/depth_recursion/main.lua +++ b/spec/traceback/depth_recursion/main.lua @@ -3,7 +3,7 @@ -- Please refer to the LICENSE and AUTHORS files for details -- SPDX-License-Identifier: MIT -local pallene = require 'spec.traceback.depth_recursion.depth_recursion' +local pallene = require "spec.traceback.depth_recursion.depth_recursion" -- luacheck: globals lua_fn function lua_fn(depth) diff --git a/spec/traceback/module_lua/main.lua b/spec/traceback/module_lua/main.lua index ef764119..c1c0ea47 100644 --- a/spec/traceback/module_lua/main.lua +++ b/spec/traceback/module_lua/main.lua @@ -3,8 +3,8 @@ -- Please refer to the LICENSE and AUTHORS files for details -- SPDX-License-Identifier: MIT -local another_module = require 'spec.traceback.module_lua.another_module' -local pallene = require 'spec.traceback.module_lua.module_lua' +local another_module = require "spec.traceback.module_lua.another_module" +local pallene = require "spec.traceback.module_lua.module_lua" -- luacheck: globals lua_1 function lua_1() diff --git a/spec/traceback/module_pallene/main.lua b/spec/traceback/module_pallene/main.lua index 62f7902c..fc111b2c 100644 --- a/spec/traceback/module_pallene/main.lua +++ b/spec/traceback/module_pallene/main.lua @@ -3,12 +3,12 @@ -- Please refer to the LICENSE and AUTHORS files for details -- SPDX-License-Identifier: MIT -local pallene = require 'spec.traceback.module_pallene.module_pallene' -local pallene_alt = require 'spec.traceback.module_pallene.module_pallene_alt' +local pallene = require "spec.traceback.module_pallene.module_pallene" +local pallene_alt = require "spec.traceback.module_pallene.module_pallene_alt" -- luacheck: globals lua_2 function lua_2() - error "There's an error in everyday life. Shame!" + error "There's an error in everyday life. Alas!" end -- luacheck: globals lua_1 diff --git a/spec/traceback/rect/main.lua b/spec/traceback/rect/main.lua index 3a4da419..dde05702 100644 --- a/spec/traceback/rect/main.lua +++ b/spec/traceback/rect/main.lua @@ -3,7 +3,7 @@ -- Please refer to the LICENSE and AUTHORS files for details -- SPDX-License-Identifier: MIT -local rect = require 'spec.traceback.rect.rect' +local rect = require "spec.traceback.rect.rect" -- Should be local. -- Making it global so that it is visible in the traceback. diff --git a/spec/traceback/stack_overflow/main.lua b/spec/traceback/stack_overflow/main.lua new file mode 100644 index 00000000..bde220fd --- /dev/null +++ b/spec/traceback/stack_overflow/main.lua @@ -0,0 +1,19 @@ +-- Copyright (c) 2024, The Pallene Developers +-- Pallene is licensed under the MIT license. +-- Please refer to the LICENSE and AUTHORS files for details +-- SPDX-License-Identifier: MIT + +local so = require "spec.traceback.stack_overflow.stack_overflow" + +-- luacheck: globals please_dont_overflow +function please_dont_overflow() + so.no_overflow(please_dont_overflow) +end + +-- luacheck: globals wrapper +function wrapper() + please_dont_overflow() +end + +-- luacheck: globals pallene_tracer_debug_traceback +xpcall(wrapper, pallene_tracer_debug_traceback) diff --git a/spec/traceback/stack_overflow/stack_overflow.pln b/spec/traceback/stack_overflow/stack_overflow.pln new file mode 100644 index 00000000..d02f8015 --- /dev/null +++ b/spec/traceback/stack_overflow/stack_overflow.pln @@ -0,0 +1,12 @@ +-- Copyright (c) 2024, The Pallene Developers +-- Pallene is licensed under the MIT license. +-- Please refer to the LICENSE and AUTHORS files for details +-- SPDX-License-Identifier: MIT + +local mod: module = {} + +function mod.no_overflow(callback: () -> ()) + callback() +end + +return mod diff --git a/spec/traceback_spec.lua b/spec/traceback_spec.lua index af7666fe..726cffeb 100644 --- a/spec/traceback_spec.lua +++ b/spec/traceback_spec.lua @@ -57,7 +57,7 @@ end) it("Multi-module Pallene", function() assert_test("module_pallene", [[ -Runtime error: spec/traceback/module_pallene/main.lua:11: There's an error in everyday life. Shame! +Runtime error: spec/traceback/module_pallene/main.lua:11: There's an error in everyday life. Alas! Stack traceback: C: in function 'error' spec/traceback/module_pallene/main.lua:11: in function 'lua_2' @@ -94,3 +94,46 @@ Stack traceback: ]]) end) +it("Stack overflow", function() + assert_test("stack_overflow", [[ +Runtime error: C stack overflow +Stack traceback: + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + + ... (Skipped 379 frames) ... + + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/stack_overflow.pln:8: in function 'no_overflow' + spec/traceback/stack_overflow/main.lua:10: in function 'please_dont_overflow' + spec/traceback/stack_overflow/main.lua:15: in function 'wrapper' + C: in function 'xpcall' + spec/traceback/stack_overflow/main.lua:19: in
+ C: in function '' +]]) +end) + +it("Anonymous lua functions", function() + assert_test("anon_lua", [[ +Runtime error: spec/traceback/anon_lua/main.lua:10: Error from an anonymous Lua fn! +Stack traceback: + C: in function 'error' + spec/traceback/anon_lua/main.lua:10: in function '' + spec/traceback/anon_lua/anon_lua.pln:8: in function 'call_anon_lua_fn' + spec/traceback/anon_lua/main.lua:9: in function '' + C: in function 'xpcall' + spec/traceback/anon_lua/main.lua:15: in
+ C: in function '' +]]) +end) diff --git a/src/pallene/pallenelib.lua b/src/pallene/pallenelib.lua index ac5981c9..6a0b82d1 100644 --- a/src/pallene/pallenelib.lua +++ b/src/pallene/pallenelib.lua @@ -76,6 +76,13 @@ return [==[ /* Pallene stack reference entry for the registry. */ #define PALLENE_TRACER_STACK_ENTRY "__PALLENE_TRACER_STACK" +/* Traceback elipsis threshold. */ +#define PALLENE_TRACEBACK_TOP_THRESHOLD 10 +/* This should always be 2 fewer than top threshold, for symmetry. + Becuase we will always have 2 tail frames lingering around at + at the end which is not captured by '_countlevels'. */ +#define PALLENE_TRACEBACK_BOTTOM_THRESHOLD 8 + /* PALLENE TRACER RELATED DATA-STRUCTURES. */ /* Whether the frame is a Pallene->Pallene or Lua->Pallene call. */ @@ -237,6 +244,58 @@ static bool _pgf_name(lua_State *L) { return false; } +/* Returns the maximum number of levels in Lua stack. */ +static int _countlevels (lua_State *L) { + lua_Debug ar; + int li = 1, le = 1; + + /* Find an upper bound */ + while (lua_getstack(L, le, &ar)) { + li = le, le *= 2; + } + + /* Do a binary search */ + while (li < le) { + int m = (li + le)/2; + + if (lua_getstack(L, m, &ar)) li = m + 1; + else le = m; + } + + return le - 1; +} + +/* Counts the number of white and black frames in the Pallene call stack. */ +static void _countframes(pt_frame_t *frame, int *mwhite, int *mblack) { + *mwhite = *mblack = 0; + + while(frame != NULL) { + *mwhite += (frame->type == PALLENE_TRACER_FRAME_TYPE_C); + *mblack += (frame->type == PALLENE_TRACER_FRAME_TYPE_LUA); + frame = frame->prev; + } +} + +/* Responsible for printing and controlling some of the traceback fn parameters. */ +static void _dbg_print(const char *buf, bool *elipsis, int *pframes, int nframes) { + /* We have printed the frame, even tho it might not be visible ;). */ + (*pframes)++; + + /* Should we print? Are we in the point in top or bottom printing threshold? */ + bool should_print = (*pframes <= PALLENE_TRACEBACK_TOP_THRESHOLD) + || ((nframes - *pframes) <= PALLENE_TRACEBACK_BOTTOM_THRESHOLD); + + if(should_print) + fprintf(stderr, buf); + else if(*elipsis) { + fprintf(stderr, "\n ... (Skipped %d frames) ...\n\n", + nframes - (PALLENE_TRACEBACK_TOP_THRESHOLD + + PALLENE_TRACEBACK_BOTTOM_THRESHOLD)); + + *elipsis = false; + } +} + /* Private routines end. */ static void pallene_tracer_frameenter(pt_cont_t *cont, pt_frame_t *restrict frame) { @@ -266,14 +325,35 @@ static void pallene_tracer_frameexit(pt_cont_t *cont) { cont->stack = cont->stack->prev; } +/* Helper macro specific to this function only :). */ +#define DBG_PRINT() _dbg_print(buf, &elipsis, &pframes, nframes) static int pallene_tracer_debug_traceback(lua_State *L) { - const char *message = lua_tostring(L, 1); - fprintf(stderr, "Runtime error: %s\nStack traceback:\n", message); - lua_getfield(L, LUA_REGISTRYINDEX, PALLENE_TRACER_STACK_ENTRY); pt_frame_t *stack = ((pt_cont_t *) lua_touserdata(L, -1))->stack; lua_pop(L, 1); + /* Max number of white and black frames. */ + int mwhite, mblack; + _countframes(stack, &mwhite, &mblack); + /* Max levels of Lua stack. */ + int mlevel = _countlevels(L); + + /* Total frames we are going to print. */ + /* Black frames are used for switching and we will start from + Lua stack level 1. */ + int nframes = mlevel + mwhite - mblack - 1; + /* Amount of frames printed. */ + int pframes = 0; + /* Should we print elipsis? */ + bool elipsis = nframes > (PALLENE_TRACEBACK_TOP_THRESHOLD + + PALLENE_TRACEBACK_BOTTOM_THRESHOLD); + + /* Buffer to store a single frame line to be printed. */ + char buf[1024]; + + const char *message = lua_tostring(L, 1); + fprintf(stderr, "Runtime error: %s\nStack traceback:\n", message); + lua_Debug ar; int top = lua_gettop(L); int level = 1; @@ -294,9 +374,10 @@ static int pallene_tracer_debug_traceback(lua_State *L) { if(lua_tocfunction(L, -1) == check->shared.frame_sig) { /* Now print all the frames in Pallene stack. */ while(stack != check) { - fprintf(stderr, " %s:%d: in function '%s'\n", + sprintf(buf, " %s:%d: in function '%s'\n", stack->shared.details->mod_name, stack->line, stack->shared.details->fn_name); + DBG_PRINT(); stack = stack->prev; } @@ -317,7 +398,8 @@ static int pallene_tracer_debug_traceback(lua_State *L) { lua_pushfstring(L, "%s", lua_tostring(L, -1)); else lua_pushliteral(L, ""); - fprintf(stderr, " C: in function '%s'\n", lua_tostring(L, -1)); + sprintf(buf, " C: in function '%s'\n", lua_tostring(L, -1)); + DBG_PRINT(); } else { /* It's a Lua frame. */ @@ -332,8 +414,9 @@ static int pallene_tracer_debug_traceback(lua_State *L) { lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); else lua_pushliteral(L, "function ''"); - fprintf(stderr, " %s:%d: in %s\n", ar.short_src, + sprintf(buf, " %s:%d: in %s\n", ar.short_src, ar.currentline, lua_tostring(L, -1)); + DBG_PRINT(); } lua_settop(L, top); @@ -341,6 +424,7 @@ static int pallene_tracer_debug_traceback(lua_State *L) { return 0; } +#undef DBG_PRINT static pt_cont_t *pallene_tracer_init(lua_State *L) { pt_cont_t *cont = NULL;