Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 18 additions & 21 deletions lib/lua/vm/dispatcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, :discard, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -973,7 +973,7 @@ defmodule Lua.VM.Dispatcher do
# `return_one/7` skips Map.fetch! lookups and lets the BEAM
# bind everything in a single `move` per slot.
frame = {code, pc + 1, regs, upvalues, proto, cont, base, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1035,7 +1035,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, base, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1088,7 +1088,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, base, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1142,7 +1142,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, base, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1194,7 +1194,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, :discard, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1246,7 +1246,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, :discard, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1299,7 +1299,7 @@ defmodule Lua.VM.Dispatcher do
else: callee_proto

frame = {code, pc + 1, regs, upvalues, proto, cont, :discard, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1354,12 +1354,9 @@ defmodule Lua.VM.Dispatcher do
# with the same depth, and the callee starts with an empty
# open-upvalue map while the caller's rides in the frame.

# `line` is carried for shape parity with the other call opcodes and
# for tooling that reads the encoded stream; the handler never needs
# it, because a self-call can never reach the native bridge that
# attributes errors to a source line, and the frame's own line slot
# is `0` for every dispatcher-side call.
{@op_call_self, base, arg_count, result_count, name_hint, _line} ->
# `line` goes in the frame's call info, as for every call opcode, so
# `error(msg, level)` can name the line a function was called from.
{@op_call_self, base, arg_count, result_count, name_hint, line} ->
callee_regs = init_callee_regs(proto, regs, base + 1, arg_count)

callee_proto =
Expand All @@ -1375,7 +1372,7 @@ defmodule Lua.VM.Dispatcher do
end

frame = {code, pc + 1, regs, upvalues, proto, cont, dest, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -1866,7 +1863,7 @@ defmodule Lua.VM.Dispatcher do
end

frame = {code, pc + 1, regs, upvalues, proto, cont, dest, ou}
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand All @@ -1887,7 +1884,7 @@ defmodule Lua.VM.Dispatcher do

{:lua_closure, _, _} = closure ->
args = collect_args(regs, base + 1, total_args)
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -2452,7 +2449,7 @@ defmodule Lua.VM.Dispatcher do
{:lua_closure, _, _} = closure,
args,
name_hint,
_line,
line,
code,
pc,
regs,
Expand All @@ -2466,7 +2463,7 @@ defmodule Lua.VM.Dispatcher do
cd,
ou
) do
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down Expand Up @@ -2513,7 +2510,7 @@ defmodule Lua.VM.Dispatcher do
args,
base,
name_hint,
_line,
line,
code,
pc,
regs,
Expand All @@ -2527,7 +2524,7 @@ defmodule Lua.VM.Dispatcher do
cd,
ou
) do
call_info = {proto.source, 0, name_hint}
call_info = {proto.source, line, name_hint}
instruction_count = tick(state, instruction_count, cs, cd)
ckdepth(state, cs, cd)

Expand Down
21 changes: 16 additions & 5 deletions lib/lua/vm/stdlib.ex
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,13 @@ defmodule Lua.VM.Stdlib do
# Per Lua 5.3 §6.1 the raised value passes through pcall/xpcall verbatim,
# and string messages gain a `source:line:` prefix unless `level == 0`.
# The prefixed view lives in `:lua_value` (Lua-facing only); `:value`
# stays raw so host rendering is untouched. Levels >= 2 (attribute to the
# caller's caller) need per-frame lines the call stack does not carry and
# are treated as level 1 for now.
# stays raw so host rendering is untouched. Level 2 names the line the
# erroring function was called from, level 3 its caller's, and so on: each
# call-stack frame records its call site. A level past the stack omits the
# prefix, as PUC Lua does.
defp lua_error([message | rest], state) do
{line, source} = Executor.current_position()
level = error_level(rest)
{line, source} = error_position(level, state)

raise RuntimeError,
value: message,
Expand All @@ -209,9 +210,19 @@ defmodule Lua.VM.Stdlib do
raise RuntimeError, value: nil, line: line, source: source, state: state
end

defp error_level([level | _]) when is_number(level), do: level
defp error_level([level | _]) when is_integer(level), do: level
defp error_level([level | _]) when is_float(level), do: trunc(level)
defp error_level(_), do: 1

defp error_position(level, state) when level >= 2 do
case Enum.at(state.call_stack, level - 2) do
{source, line, _name} when is_integer(line) and line > 0 -> {line, source}
_ -> {nil, nil}
end
end

defp error_position(_level, _state), do: Executor.current_position()

# §6.1 position prefix: only string messages, only when `level ~= 0`, and
# only when the executor recorded a usable position. A `{nil, _}` position
# (native call inside a compiled closure — the dispatcher does not plumb
Expand Down
18 changes: 9 additions & 9 deletions test/lua/error_gallery_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,16 @@ defmodule Lua.ErrorGalleryTest do
Lua runtime error: runtime error: stack overflow

Stack trace:
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
... 20 more frames ...
gallery.lua:0: in function 'f'
gallery.lua:0: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:1: in function 'f'
gallery.lua:2: in function 'f'\
"""}
]
Expand Down
39 changes: 39 additions & 0 deletions test/lua/vm/pcall_error_value_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,45 @@ defmodule Lua.VM.PcallErrorValueTest do

assert results == [false, "hi"]
end

test "level 2 blames the caller's line" do
{results, _state} =
run(
"""
local function check(x)
if not x then error("bad input", 2) end
end
local ok, err = pcall(function()
check(false)
end)
return err
""",
@engine
)

assert results == ["test.lua:5: bad input"]
end

test "level 3 blames the caller's caller" do
{results, _state} =
run(
"""
local function check(x)
if not x then error("deep", 3) end
end
local function validate(x)
check(x)
end
local ok, err = pcall(function()
validate(false)
end)
return err
""",
@engine
)

assert results == ["test.lua:8: deep"]
end
end

describe "xpcall hands the raw value to the handler (#{engine} engine)" do
Expand Down