-
Notifications
You must be signed in to change notification settings - Fork 319
Description
Closure looses access to imports when called. See example below.
Tengo ---------------
// Minimal reproduction of Tengo closure import access bug
// This script demonstrates that closures lose access to imported modules
// when called from Go code via args[0].Call()
//
// Environment:
// - Go version: go1.23.3 darwin/amd64
// - Tengo version: github.com/d5/tengo/v2 v2.17.0
// - OS: macOS
//
// EXPECTED OUTPUT:
// =================================
// === Tengo Closure Import Bug Demonstration ===
//
// 1. MAIN SCOPE - fmt.println() works:
// This message proves fmt import works in main scope
//
// 2. DIRECT CLOSURE EXECUTION - fmt.println() works:
// Inside direct closure: fmt.println() works correctly
//
// 3. CLOSURE PASSED TO GO FUNCTION - BUG:
// The closure below should print a message, but it likely won't...
// This demonstrates the import access loss bug.
//
// Go: About to call closure from Go code...
// INSIDE CLOSURE CALLED FROM GO: This message should appear but likely won't
// INSIDE CLOSURE: localVar = test
// Go: Closure call returned: result=, error=
//
// 4. EXPECTED VS ACTUAL BEHAVIOR:
// EXPECTED: You should see 'INSIDE CLOSURE...' messages above
// ACTUAL: You likely only see the Go debug output, not the closure messages
//
// 5. CONCLUSION:
// - Direct closure execution works (step 2)
// - Closure passed to Go function loses import access (step 3)
// - This is the demonstrated bug
//
// === End Demonstration ===
//
// ACTUAL OUTPUT:
// =================================
// === Tengo Closure Import Bug Demonstration ===
//
// 1. MAIN SCOPE - fmt.println() works:
// This message proves fmt import works in main scope
//
// 2. DIRECT CLOSURE EXECUTION - fmt.println() works:
// Inside direct closure: fmt.println() works correctly
//
// 3. CLOSURE PASSED TO GO FUNCTION - BUG:
// The closure below should print a message, but it likely won't...
// This demonstrates the import access loss bug.
//
// Go: About to call closure from Go code...
// Go: Closure call returned: result=, error=
//
// 4. EXPECTED VS ACTUAL BEHAVIOR:
// EXPECTED: You should see 'INSIDE CLOSURE...' messages above
// ACTUAL: You likely only see the Go debug output, not the closure messages
//
// 5. CONCLUSION:
// - Direct closure execution works (step 2)
// - Closure passed to Go function loses import access (step 3)
// - This is the demonstrated bug
//
// === End Demonstration ===
//
// NOTICE: The "INSIDE CLOSURE" messages are missing from the actual output,
// demonstrating that the closure loses access to the fmt import when called from Go.
fmt := import("fmt")
demo := import("demo")
fmt.println("=== Tengo Closure Import Bug Demonstration ===")
fmt.println()
// STEP 1: Show that imports work in main scope
fmt.println("1. MAIN SCOPE - fmt.println() works:")
fmt.println(" This message proves fmt import works in main scope")
fmt.println()
// STEP 2: Show that direct closure execution works
fmt.println("2. DIRECT CLOSURE EXECUTION - fmt.println() works:")
directClosure := func() {
fmt.println(" Inside direct closure: fmt.println() works correctly")
}
directClosure() // Execute directly in main scope
fmt.println()
// STEP 3: Demonstrate the bug - closure passed to Go function
fmt.println("3. CLOSURE PASSED TO GO FUNCTION - BUG:")
fmt.println(" The closure below should print a message, but it likely won't...")
fmt.println(" This demonstrates the import access loss bug.")
fmt.println()
demo.callClosure(func() {
// This fmt.println() call will fail silently due to the bug
fmt.println(" INSIDE CLOSURE CALLED FROM GO: This message should appear but likely won't")
// Basic operations might still work
localVar := "test"
// Even trying to print localVar will fail if fmt import is not accessible
fmt.println(" INSIDE CLOSURE: localVar =", localVar)
})
fmt.println()
fmt.println("4. EXPECTED VS ACTUAL BEHAVIOR:")
fmt.println(" EXPECTED: You should see 'INSIDE CLOSURE...' messages above")
fmt.println(" ACTUAL: You likely only see the Go debug output, not the closure messages")
fmt.println()
fmt.println("5. CONCLUSION:")
fmt.println(" - Direct closure execution works (step 2)")
fmt.println(" - Closure passed to Go function loses import access (step 3)")
fmt.println(" - This is the demonstrated bug")
fmt.println()
fmt.println("=== End Demonstration ===")
Go ------------------
// Minimal reproduction of Tengo closure import access bug
// This demonstrates that closures lose access to imported modules when called from Go code
//
// Environment:
// - Go version: go1.23.3 darwin/amd64
// - Tengo version: github.com/d5/tengo/v2 v2.17.0
// - OS: macOS
//
// Expected behavior: Closure should have access to imported modules
// Actual behavior: Closure loses access to imported modules when called via args[0].Call()
package main
import (
"context"
"fmt"
"os"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
)
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: go run tengo-bug-demo.go <script.tengo>")
os.Exit(1)
}
scriptPath := os.Args[1]
scriptBytes, err := os.ReadFile(scriptPath)
if err != nil {
fmt.Printf("Error reading script: %v\n", err)
os.Exit(1)
}
script := tengo.NewScript(scriptBytes)
// Set up standard library modules
modules := stdlib.GetModuleMap(stdlib.AllModuleNames()...)
// Add a simple function that accepts a closure and calls it
modules.AddBuiltinModule("demo", map[string]tengo.Object{
"callClosure": &tengo.UserFunction{
Name: "callClosure",
Value: func(args ...tengo.Object) (tengo.Object, error) {
if len(args) != 1 || !args[0].CanCall() {
return nil, fmt.Errorf("callClosure requires a callable argument")
}
fmt.Println("Go: About to call closure from Go code...")
result, err := args[0].Call()
fmt.Printf("Go: Closure call returned: result=%v, error=%v\n", result, err)
return tengo.UndefinedValue, nil
},
},
})
script.SetImports(modules)
compiled, err := script.Compile()
if err != nil {
fmt.Printf("Compile error: %v\n", err)
os.Exit(1)
}
err = compiled.RunContext(context.Background())
if err != nil {
fmt.Printf("Runtime error: %v\n", err)
os.Exit(1)
}
}