From efc91ed908899dd0f8dd0ac2842f70f9412be8b3 Mon Sep 17 00:00:00 2001 From: Ville Vesilehto Date: Sun, 30 Nov 2025 22:22:56 +0200 Subject: [PATCH] fix(vm): show func for safe calls in disassembly Previously, safe builtin functions (OpCallSafe) were pushed to the stack as constants (OpPush), causing the disassembler/debugger to display their memory address (e.g., 0x140000...) instead of their name (e.g., concat). This change updates the compiler to store the function name in debugInfo when emitting OpCallSafe. The VM's disassembler now checks debugInfo for OpPush arguments and displays the stored name if available. Signed-off-by: Ville Vesilehto --- compiler/compiler.go | 4 +++- test/issues/567/issue_test.go | 25 +++++++++++++++++++++++++ vm/program.go | 3 +++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 test/issues/567/issue_test.go diff --git a/compiler/compiler.go b/compiler/compiler.go index 83b19a885..fe1c00c4d 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1103,7 +1103,9 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) { if f.Fast != nil { c.emit(OpCallBuiltin1, id) } else if f.Safe != nil { - c.emit(OpPush, c.addConstant(f.Safe)) + id := c.addConstant(f.Safe) + c.emit(OpPush, id) + c.debugInfo[fmt.Sprintf("const_%d", id)] = node.Name c.emit(OpCallSafe, len(node.Arguments)) } else if f.Func != nil { c.emitFunction(f, len(node.Arguments)) diff --git a/test/issues/567/issue_test.go b/test/issues/567/issue_test.go new file mode 100644 index 000000000..b5c76d83e --- /dev/null +++ b/test/issues/567/issue_test.go @@ -0,0 +1,25 @@ +package expr_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/internal/testify/require" +) + +func TestIssue567(t *testing.T) { + program, err := expr.Compile("concat(1..2, 3..4)") + require.NoError(t, err) + + var buf bytes.Buffer + program.DisassembleWriter(&buf) + output := buf.String() + + // Check if "concat" is mentioned in the output + require.True(t, strings.Contains(output, "concat"), "expected 'concat' in disassembly output") + + // It should appear as a pushed constant + require.True(t, strings.Contains(output, "OpPush\t<4>\tconcat"), "expected 'OpPush <4> concat' in disassembly output") +} diff --git a/vm/program.go b/vm/program.go index 15ce26f5b..d146cc139 100644 --- a/vm/program.go +++ b/vm/program.go @@ -112,6 +112,9 @@ func (program *Program) DisassembleWriter(w io.Writer) { } else { c = "out of range" } + if name, ok := program.debugInfo[fmt.Sprintf("const_%d", arg)]; ok { + c = name + } if r, ok := c.(*regexp.Regexp); ok { c = r.String() }