Get true stack trace of an error in lua pcall - lua

So for my pcall statements, I've been doing something like this
local status, err = pcall(fn)
if not status then
print(err)
print(debug.stacktrace())
end
This works fine for some basic stuff but the issue is that debug.stacktrace() returns the CURRENT relative stack trace, not the stack trace of the error. If the error within fn happened 10 levels down in the stack, then I wouldn't know where exactly it occurred, just that this pcall block is failing. I was wondering if there was a way to get the stack trace of the pcall and not the current stack trace. I tried debug.stacktrace(err) but it didn't make a difference.

You need to use xpcall to provide a custom function that will add the stacktrace to the error message. From PiL:
Frequently, when an error happens, we want more debug information than
only the location where the error occurred. At least, we want a
traceback, showing the complete stack of calls leading to the error.
When pcall returns its error message, it destroys part of the stack
(the part that went from it to the error point). Consequently, if we
want a traceback, we must build it before pcall returns. To do that,
Lua provides the xpcall function. Besides the function to be called,
it receives a second argument, an error handler function. In case of
errors, Lua calls that error handler before the stack unwinds, so that
it can use the debug library to gather any extra information it wants
about the error.
You may want to check this patch that extends pcall to include stacktrace.
As suggested in the comments, you can use local ok, res = xpcall(f, debug.traceback, args...) with Lua 5.2+ or LuaJIT (with Lua 5.2 compatibility turned on) and used a patch mentioned above for Lua 5.1.

The basic problem is (roughly) that pcall must unwind the stack so that your error handling code is reached. This gives two obvious ways to tackle the problem: Create the stack trace before unwinding, or move the (potentially) error-throwing code out of the way, so the stack frames don't have to be removed.
The first is handled by xpcall. This sets an error handler that can create a message while the stack is still intact. (Note that there are some situations where xpcall will not call the handler,1 so it's not suitable for cleanup code! But for stack traces, it's generally good enough.)
The second option (this works always2) is to preserve the stack by moving the code to a different coroutine. Instead of
local ok, r1, r2, etc = pcall( f, ... )
do
local co = coroutine.create( f )
local ok, r1, r2, etc = coroutine.resume( f, ... )
and now the stack (in co) is still preserved and can be queried by debug.traceback( co ) or other debug functions.
If you want the full stack trace, you'll then have to collect both the stack trace inside the coroutine and the stack trace outside of it (where you currently are) and then combine both while dropping the first line of the latter:
local full_tb = debug.traceback( co )
.. debug.traceback( ):sub( 17 ) -- drop 'stack traceback:' line
1 One situation in which the handler isn't called is for OOMs:
g = ("a"):rep( 1024*1024*1024 ) -- a gigabyte of 'a's
-- fail() tries to create a 32GB string – make it larger if that doesn't OOM
fail = load( "return "..("g"):rep( 32, ".." ), "(replicator)" )
-- plain call errors without traceback
fail()
--> not enough memory
-- xpcall does not call the handler either:
xpcall( fail, function(...) print( "handler:", ... ) return ... end, "foo" )
--> false not enough memory
-- (for comparison: here, the handler is called)
xpcall( error, function(...) print( "handler:", ... ) return ... end, "foo" )
--> handler: foo
-- false foo
-- coroutine preserves the stack anyway:
do
local co = coroutine.create( fail )
print( "result:", coroutine.resume( fail ) )
print( debug.traceback( co ) .. debug.traceback( ):sub( 17 ) )
end
--> result: false not enough memory
--> stack traceback:
-- [string "(replicator)"]:1: in function 'fail'
-- stdin:4: in main chunk
-- [C]: in ?
2 Well, at least as long as Lua itself doesn't crash.

Related

NeoVim - Check if a Vim function exists from Lua

I plan on using vim-plug with NeoVim. So, my init.lua file will have function calls such as
vim.fn['plug#begin'](vim.fn.stdpath('data') .. '/plugged')
vim.fn['plug#']('hoob3rt/lualine.nvim')
However, I don't want to assume vim-plug is definitely installed. I want my init.lua file to degrade gracefully if vim-plug is not installed, rather than throwing an error
E5113: Error while calling lua chunk: Vim:E117: Unknown function: plug#begin
stack traceback:
[C]: in function 'plug#begin'
/Users/andy/.config/nvim/init.lua:8: in main chunk
How can I check if the vim-plug functions exist before attempting to call them?
I tried print(vim.fn['plug#begin']) but that for some reason prints a non-null value: function: 0x0104ba36f0, even though the function doesn't exist.
I tried print(vim.fn['plug#begin']) but that for some reason prints a non-null value: function: 0x0104ba36f0, even though the function doesn't exist.
Presumably it's returning a function that throws the error you are getting. I would thus recommend using pcall:
local success, error = pcall(vim.fn['plug#begin'], vim.fn.stdpath('data') .. '/plugged')
if not success then --[[fail gracefully]] end
caveat: this will catch any error, so you'll probably want to perform some check like if error:find"Unknown function" then ... end to only catch this specific error.

Loadstring Error: attempted to call a nil value

loadstring("
\45\45\32\80\117\116\32\115\99\114\105\112\116\32\104\101\114\101\10\112\114\105\110\116\40\34\104\105\34\41\10")()
I keep getting an error stating this:
lua: /tmp/044957038/main.lua:12: attempt to call a nil value (global 'loadstring')
stack traceback:
/tmp/044957038/main.lua:12: in main chunk
[C]: in ?
Can anyone help me? (I’m using glot.io to run my script.)
Based on the comments and some testing in glot, this should work (the print() is just for reference):
print("\45\45\32\80\117\116\32\115\99\114\105\112\116\32\104\101\114\101\10\112\114\105\110\116\40\34\104\105\34\41\10")
load("\45\45\32\80\117\116\32\115\99\114\105\112\116\32\104\101\114\101\10\112\114\105\110\116\40\34\104\105\34\41\10")()
Output
-- Put script here
print("hi")
hi

Attempt to index global 'Grid' (a nil value)

I have been trying to run the following codes from Euler Equations Solver. It is a Lua script that I have been toying with using ZeroBrane Studio. But the project gave the following error every time I try to execute it.
attempt to index global 'Grid' (a nil value)
stack traceback:
...mming Codes\Project 1\Lua Script\Problem 1\Problem 1.lua:7: in main chunk
[C]: at 0x00401b00
I am completely new to Lua and I have been looking for the solutions everywhere. Appreciate any help that I can get.

lua lfs on flashair w04 seems not to work

Hi for the last week i have been trying to get my Flashair to upload its files over ftp.
I can make a ftp connection without any problems, but when i try to iterate over the files in its folder its gives me a error on the lua lfs, the only thing its returning is nil.
its goes about this code sample:
for file in lfs.dir(localDir) do
attr = lfs.attributes(localDir .. file)
print( "Found "..attr.mode..": " .. file )
if attr.mode == "file" then
response = fa.ftp("put", ftpString..file, localDir .. file)
if response ~= nil then
print("Success!")
else
print("Fail!")
end
end
end
the error i get is:
lua: ftp.lua:17: attempt to index a nil value (global 'lfs')
stack traceback:
ftp.lua:17: in main chunk
[C]: in ?
where line 17 is the first line in the provided code sample.
Am I missing something, seems that i cannot find a lot information about this problem in combination with the Flashair.
Any help would be appreciated.
I am new to Lua so don't flame me if my response is not beneficial to you. I have been developing an app for W-03 and when testing the Lua app on my MACBOOK linux virtual env I was seeing the same error, and discovered I had to use require "lfs" statement in my test code. require "lfs" is not necessary when running on the actual W-03 though.

Lua: How to call error without stack trace

I'm using Lua to parse scripts written in some language (let's call it L) and create Lua-code that can be run by e.g. LuaJIT. But to simplify debugging for the users, I want to map the run time errors given by Lua/LuaJIT to the correct line in the L-files. I do this by xpcalling the created Lua-code, translating the error message and stacktrace and then calling error with this message. Unfortunately this gives me two stack traces, one created by me and one tracing back to the function that called error. Is it possible to get rid of this stack trace, or is there some better way of doing this?
local status, err = xpcall(loadedCode, debug.traceback)
if not status then
error(createANewErrorMessageWithPrettyTraceback(err),0)
end
Output:
luajit: ./my/file.name:5: Some error message
stack traceback:
my pretty traceback
stack traceback:
[C]: in function 'error'
./my/file/calling/error.lua:44: in function <./my/file/calling/error.lua:26>
./my-main:16: in main chunk
[C]: at 0x00404180
I know that e.g. Moonscript does something similar to this, but as far as I can see they just write the new error message to stderr and then continues as normal, instead of stopping the program which is what I want to do.
There is a possibility of doing this and then calling error with no arguments, which will make the program fail (actually I think it's error that fails), but this feels like quite an ugly solution, so I'll rather keep the stupid second trace than doing that.
PS: I assume what the title asks actually doesn't work (as error only takes two arguments), so what I'm actually asking is more how something like this can be achieved. (Are there other functions that do similar things perhaps, or where I should look to figure out how to write that function myself.)
Edit: Is it perhaps possible to edit the function that error's using to get its traceback, as it is with debug.traceback?
I wanted to do something similar (only from Lua directly) and I ended up overwriting debug.traceback function itself to change the stack trace to suit my needs. My code is below; see if this method works for you as well:
local dtraceback = debug.traceback
debug.traceback = function (...)
if select('#', ...) >= 1 then
local err, lvl = ...
if err and type(err) ~= 'thread' then
local trace = dtraceback(err, (lvl or 2)+1)
if genv.print == iobase.print then -- no remote redirect
return trace
else
genv.print(trace) -- report the error remotely
return -- don't report locally to avoid double reporting
end
end
end
-- direct call to debug.traceback: return the original.
-- debug.traceback(nil, level) doesn't work in Lua 5.1
-- (http://lua-users.org/lists/lua-l/2011-06/msg00574.html), so
-- simply remove first frame from the stack trace
return (dtraceback(...):gsub("(stack traceback:\n)[^\n]*\n", "%1"))
end
You could simply display the modified traceback that you want and exit.
local function errh(err)
print(createANewErrorMessageWithPrettyTraceback(debug.traceback(err, 2)))
os.exit(-1) -- error code
end
local status, result = xpcall(loadedCode, errh)
-- The script will never reach this point if there is an error.
print(result)

Resources