Skip to content

Commit c122e65

Browse files
authored
starlark: fix a crash in UnpackArgs (#89)
When computing the error message, the parameter's Type method is called. Mostly this works, but it may panic for a user-defined type whose Type method doesn't work on the zero value. Recover from the panic and show the Go type instead. + Test
1 parent 746fad3 commit c122e65

2 files changed

Lines changed: 63 additions & 9 deletions

File tree

starlark/eval_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,3 +552,34 @@ g(z=7)
552552
t.Errorf("got <<%s>>, want <<%s>>", got, want)
553553
}
554554
}
555+
556+
type badType string
557+
558+
func (b *badType) String() string { return "badType" }
559+
func (b *badType) Type() string { return "badType:" + string(*b) } // panics if b==nil
560+
func (b *badType) Truth() starlark.Bool { return true }
561+
func (b *badType) Hash() (uint32, error) { return 0, nil }
562+
func (b *badType) Freeze() {}
563+
564+
var _ starlark.Value = new(badType)
565+
566+
// TestUnpackErrorBadType verifies that the Unpack functions fail
567+
// gracefully when a parameter's default value's Type method panics.
568+
func TestUnpackErrorBadType(t *testing.T) {
569+
for _, test := range []struct {
570+
x *badType
571+
want string
572+
}{
573+
{new(badType), "got NoneType, want badType"}, // Starlark type name
574+
{nil, "got NoneType, want *starlark_test.badType"}, // Go type name
575+
} {
576+
err := starlark.UnpackArgs("f", starlark.Tuple{starlark.None}, nil, "x", &test.x)
577+
if err == nil {
578+
t.Errorf("UnpackArgs succeeded unexpectedly")
579+
continue
580+
}
581+
if !strings.Contains(err.Error(), test.want) {
582+
t.Errorf("UnpackArgs error %q does not contain %q", err, test.want)
583+
}
584+
}
585+
}

starlark/library.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -307,21 +307,44 @@ func unpackOneArg(v Value, ptr interface{}) error {
307307
return fmt.Errorf("got %s, want iterable", v.Type())
308308
}
309309
default:
310+
// v must have type *V, where V is some subtype of starlark.Value.
310311
ptrv := reflect.ValueOf(ptr)
311312
if ptrv.Kind() != reflect.Ptr {
312-
log.Fatalf("internal error: not a pointer: %T", ptr)
313+
log.Panicf("internal error: not a pointer: %T", ptr)
313314
}
314-
param := ptrv.Elem()
315-
if !reflect.TypeOf(v).AssignableTo(param.Type()) {
316-
// Detect mistakes by caller.
317-
if !param.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) {
318-
log.Fatalf("internal error: invalid pointer type: %T", ptr)
315+
paramVar := ptrv.Elem()
316+
if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) {
317+
// The value is not assignable to the variable.
318+
319+
// Detect a possible bug in the Go program that called Unpack:
320+
// If the variable *ptr is not a subtype of Value,
321+
// no value of v can possibly work.
322+
if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) {
323+
log.Panicf("pointer element type does not implement Value: %T", ptr)
319324
}
320-
// Assume it's safe to call Type() on a zero instance.
321-
paramType := param.Interface().(Value).Type()
325+
326+
// Report Starlark dynamic type error.
327+
//
328+
// We prefer the Starlark Value.Type name over
329+
// its Go reflect.Type name, but calling the
330+
// Value.Type method on the variable is not safe
331+
// in general. If the variable is an interface,
332+
// the call will fail. Even if the variable has
333+
// a concrete type, it might not be safe to call
334+
// Type() on a zero instance. Thus we must use
335+
// recover.
336+
337+
// Default to Go reflect.Type name
338+
paramType := paramVar.Type().String()
339+
340+
// Attempt to call Value.Type method.
341+
func() {
342+
defer func() { recover() }()
343+
paramType = paramVar.MethodByName("Type").Call(nil)[0].String()
344+
}()
322345
return fmt.Errorf("got %s, want %s", v.Type(), paramType)
323346
}
324-
param.Set(reflect.ValueOf(v))
347+
paramVar.Set(reflect.ValueOf(v))
325348
}
326349
return nil
327350
}

0 commit comments

Comments
 (0)