starbind: fix use of ptr variables in starlark (#3386)
This commit is contained in:
parent
407ace96c8
commit
7603e46f75
17
_fixtures/testvariables_pointers_not_loaded.go
Normal file
17
_fixtures/testvariables_pointers_not_loaded.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type StructWithInterface struct {
|
||||||
|
Val any
|
||||||
|
}
|
||||||
|
var i int
|
||||||
|
var ba = StructWithInterface{}
|
||||||
|
ba.Val = &i
|
||||||
|
|
||||||
|
var ptrPtr **int
|
||||||
|
_ = ptrPtr
|
||||||
|
|
||||||
|
runtime.Breakpoint()
|
||||||
|
}
|
@ -453,10 +453,15 @@ func (v ptrVariableAsStarlarkValue) Attr(name string) (starlark.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v ptrVariableAsStarlarkValue) AttrNames() []string {
|
func (v ptrVariableAsStarlarkValue) AttrNames() []string {
|
||||||
|
if len(v.v.Children) == 0 {
|
||||||
|
// The pointer variable was not loaded; we don't know the field names.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if v.v.Children[0].Kind != reflect.Struct {
|
if v.v.Children[0].Kind != reflect.Struct {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// autodereference
|
// autodereference: present the field names of the pointed-to struct as the
|
||||||
|
// fields of this pointer variable.
|
||||||
x := structVariableAsStarlarkValue{&v.v.Children[0], v.env}
|
x := structVariableAsStarlarkValue{&v.v.Children[0], v.env}
|
||||||
return x.AttrNames()
|
return x.AttrNames()
|
||||||
}
|
}
|
||||||
|
@ -141,12 +141,13 @@ func (env *Env) printFunc() func(_ *starlark.Thread, msg string) {
|
|||||||
// Source can be either a []byte, a string or a io.Reader. If source is nil
|
// Source can be either a []byte, a string or a io.Reader. If source is nil
|
||||||
// Execute will execute the file specified by 'path'.
|
// Execute will execute the file specified by 'path'.
|
||||||
// After the file is executed if a function named mainFnName exists it will be called, passing args to it.
|
// After the file is executed if a function named mainFnName exists it will be called, passing args to it.
|
||||||
func (env *Env) Execute(path string, source interface{}, mainFnName string, args []interface{}) (starlark.Value, error) {
|
func (env *Env) Execute(path string, source interface{}, mainFnName string, args []interface{}) (_ starlark.Value, _err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
err := recover()
|
err := recover()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_err = fmt.Errorf("panic executing starlark script: %v", err)
|
||||||
fmt.Fprintf(env.out, "panic executing starlark script: %v\n", err)
|
fmt.Fprintf(env.out, "panic executing starlark script: %v\n", err)
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
pc, file, line, ok := runtime.Caller(i)
|
pc, file, line, ok := runtime.Caller(i)
|
||||||
|
@ -158,3 +158,70 @@ func TestStarlarkVariable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that pointer variables that were not loaded don't lead to crashes when
|
||||||
|
// used in Starlark scripts.
|
||||||
|
func TestStarlarkVariablePointerNotLoaded(t *testing.T) {
|
||||||
|
withTestTerminal("testvariables_pointers_not_loaded", t, func(term *FakeTerminal) {
|
||||||
|
term.MustExec("continue")
|
||||||
|
|
||||||
|
// We're going to partially load some variables through the eval()
|
||||||
|
// builtin. Then we're going to attempt to evaluate expressions which
|
||||||
|
// try to access a field from a pointer variable that wasn't loaded
|
||||||
|
// (i.e. a ptrVariableAsStarlarkValue with no children). The tests will
|
||||||
|
// verify that we get an error. This is a regression test; we used to
|
||||||
|
// panic.
|
||||||
|
for _, tc := range []struct{ name, script, expErr string }{
|
||||||
|
{
|
||||||
|
// In this test, we'll load v. v will have a child (i.e.
|
||||||
|
// v.Children[0]), but because v is nil, v.Children[0] will not
|
||||||
|
// be loaded. We'll turn v.Children[0] into a
|
||||||
|
// ptrVariableAsStarlarkValue, and attempt to access a field on
|
||||||
|
// it.
|
||||||
|
//
|
||||||
|
// v -> structAsStarlarkValue<api.Variable<**int>>
|
||||||
|
// v.Children[0] -> structAsStarlarkValue<api.Variable<*int>>
|
||||||
|
// v.Children[0].Value -> ptrVariableAsStarlarkValue<*int> ; this pointer variable has not been loaded
|
||||||
|
name: "partial load because of nil ptr",
|
||||||
|
script: `
|
||||||
|
v = eval(
|
||||||
|
None, "ptrPtr", None
|
||||||
|
).Variable
|
||||||
|
v.Children[0].Value.XXX
|
||||||
|
`,
|
||||||
|
expErr: "*int has no .XXX field or method",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// In this test, MaxStructFields = 10 and MaxVariableRecurse = 0
|
||||||
|
// will cause us to load v, and v.Children[0] (in the sense that
|
||||||
|
// v.Children[0] has a child), but _not_ load
|
||||||
|
// v.Children[0].Children[0] (because the recursion limit is
|
||||||
|
// exhausted by the descent into the top-level struct, so we
|
||||||
|
// don't load what hides under the interface).
|
||||||
|
//
|
||||||
|
// v -> structAsStarlarkValue<api.Variable<StructWithInterface>>
|
||||||
|
// v.Children[0] -> structAsStarlarkValue<api.Variable<interface{}>>
|
||||||
|
// v.Children[0].Children[0] -> structAsStarlarkValue<api.Variable<*int>>
|
||||||
|
// v.Children[0].Children[0].Value -> ptrVariableAsStarlarkValue<*int> ; this pointer variable has not been loaded
|
||||||
|
name: "partial load because of recursion limit",
|
||||||
|
script: `
|
||||||
|
v = eval(
|
||||||
|
None, "ba", {"MaxVariableRecurse": 0, "MaxStructFields": 10}
|
||||||
|
).Variable;
|
||||||
|
v.Children[0].Children[0].Value.XXX
|
||||||
|
`,
|
||||||
|
expErr: "*int has no .XXX field or method",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := term.ExecStarlark(tc.script)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error %q, got success", tc.expErr)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tc.expErr) {
|
||||||
|
t.Fatalf("expected error %q, got \"%s\"", tc.expErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user