diff --git a/proc/eval.go b/proc/eval.go index 1a23caf4..762103a7 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -345,7 +345,7 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) { } return xev.mapAccess(idxev) default: - return nil, fmt.Errorf("invalid expression \"%s\" (type %s does not support indexing)", exprToString(node.X), xev.DwarfType.String()) + return nil, fmt.Errorf("expression \"%s\" (%s) does not support indexing", exprToString(node.X), xev.TypeString()) } } @@ -404,7 +404,7 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) { } return xev, nil default: - return nil, fmt.Errorf("can not slice \"%s\" (type %s)", exprToString(node.X), xev.DwarfType.String()) + return nil, fmt.Errorf("can not slice \"%s\" (type %s)", exprToString(node.X), xev.TypeString()) } } @@ -415,12 +415,12 @@ func (scope *EvalScope) evalPointerDeref(node *ast.StarExpr) (*Variable, error) return nil, err } - if xev.DwarfType == nil { - return nil, fmt.Errorf("expression \"%s\" can not be dereferenced", exprToString(node.X)) + if xev.Kind != reflect.Ptr { + return nil, fmt.Errorf("expression \"%s\" (%s) can not be dereferenced", exprToString(node.X), xev.TypeString()) } - if xev.Kind != reflect.Ptr { - return nil, fmt.Errorf("expression \"%s\" (%s) can not be dereferenced", exprToString(node.X), xev.DwarfType.String()) + if xev == nilVariable { + return nil, fmt.Errorf("nil can not be dereferenced") } if len(xev.Children) == 1 { @@ -441,7 +441,7 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) { if err != nil { return nil, err } - if xev.Addr == 0 { + if xev.Addr == 0 || xev.DwarfType == nil { return nil, fmt.Errorf("can not take address of \"%s\"", exprToString(node.X)) } @@ -519,6 +519,14 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) { } func negotiateType(op token.Token, xv, yv *Variable) (dwarf.Type, error) { + if xv == nilVariable { + return nil, negotiateTypeNil(op, yv) + } + + if yv == nilVariable { + return nil, negotiateTypeNil(op, xv) + } + if op == token.SHR || op == token.SHL { if xv.Value == nil || xv.Value.Kind() != constant.Int { return nil, fmt.Errorf("shift of type %s", xv.Kind) @@ -562,6 +570,18 @@ func negotiateType(op token.Token, xv, yv *Variable) (dwarf.Type, error) { panic("unreachable") } +func negotiateTypeNil(op token.Token, v *Variable) error { + if op != token.EQL && op != token.NEQ { + return fmt.Errorf("operator %s can not be applied to \"nil\"", op.String()) + } + switch v.Kind { + case reflect.Ptr, reflect.UnsafePointer, reflect.Chan, reflect.Map, reflect.Interface, reflect.Slice, reflect.Func: + return nil + default: + return fmt.Errorf("can not compare %s to nil", v.Kind.String()) + } +} + func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { switch node.Op { case token.INC, token.DEC, token.ARROW: @@ -662,6 +682,24 @@ func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) { var eql bool var err error + if xv == nilVariable { + switch op { + case token.EQL: + return yv.isNil(), nil + case token.NEQ: + return !yv.isNil(), nil + } + } + + if yv == nilVariable { + switch op { + case token.EQL: + return xv.isNil(), nil + case token.NEQ: + return !xv.isNil(), nil + } + } + switch xv.Kind { case reflect.Ptr: eql = xv.Children[0].Addr == yv.Children[0].Addr @@ -679,11 +717,13 @@ func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) { } eql, err = equalChildren(xv, yv, false) case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: - if xv != nilVariable && yv != nilVariable { - return false, fmt.Errorf("can not compare %s variables", xv.Kind.String()) + return false, fmt.Errorf("can not compare %s variables", xv.Kind.String()) + case reflect.Interface: + if xv.Children[0].RealType.String() != yv.Children[0].RealType.String() { + eql = false + } else { + eql, err = compareOp(token.EQL, &xv.Children[0], &yv.Children[0]) } - - eql = xv.base == yv.base default: return false, fmt.Errorf("unimplemented comparison of %s variables", xv.Kind.String()) } @@ -694,6 +734,18 @@ func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) { return eql, err } +func (v *Variable) isNil() bool { + switch v.Kind { + case reflect.Ptr: + return v.Children[0].Addr == 0 + case reflect.Interface: + return false + case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: + return v.base == 0 + } + return false +} + func equalChildren(xv, yv *Variable, shortcircuit bool) (bool, error) { r := true for i := range xv.Children { diff --git a/proc/variables.go b/proc/variables.go index 2d867087..50925f54 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -234,6 +234,16 @@ func (v *Variable) clone() *Variable { return &r } +func (v *Variable) TypeString() string { + if v == nilVariable { + return "nil" + } + if v.DwarfType != nil { + return v.DwarfType.String() + } + return v.Kind.String() +} + func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) { if v.Unreadable != nil { return v.clone(), nil @@ -643,7 +653,11 @@ func (v *Variable) structMember(memberName string) (*Variable, error) { } return nil, fmt.Errorf("%s has no member %s", v.Name, memberName) default: - return nil, fmt.Errorf("%s (type %s) is not a struct", v.Name, structVar.DwarfType) + if v.Name == "" { + return nil, fmt.Errorf("type %s is not a struct", structVar.TypeString()) + } else { + return nil, fmt.Errorf("%s (type %s) is not a struct", v.Name, structVar.TypeString()) + } } } diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index dacf889b..ed945f81 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -61,7 +61,11 @@ func (v *Variable) writeTo(buf *bytes.Buffer, top, newlines, includeType bool, i if newlines { v.writeStructTo(buf, newlines, includeType, indent) } else { - fmt.Fprintf(buf, "%s %s/%s", v.Type, v.Children[0].Value, v.Children[1].Value) + if len(v.Children) == 0 { + fmt.Fprintf(buf, "%s nil", v.Type) + } else { + fmt.Fprintf(buf, "%s %s/%s", v.Type, v.Children[0].Value, v.Children[1].Value) + } } case reflect.Struct: v.writeStructTo(buf, newlines, includeType, indent) diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 196f91b2..d977a2a6 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -387,6 +387,7 @@ func TestEvalExpression(t *testing.T) { // channels {"ch1", true, "chan int 0/2", "", "chan int", nil}, + {"chnil", true, "chan int nil", "", "chan int", nil}, {"ch1+1", false, "", "", "", fmt.Errorf("can not convert 1 constant to chan int")}, // maps @@ -463,6 +464,7 @@ func TestEvalExpression(t *testing.T) { {"c1.sa[0] == nil", false, "false", "", "", nil}, {"c1.sa[0] != nil", false, "true", "", "", nil}, {"nilslice == nil", false, "true", "", "", nil}, + {"nil == nilslice", false, "true", "", "", nil}, {"nilslice != nil", false, "false", "", "", nil}, {"nilptr == nil", false, "true", "", "", nil}, {"nilptr != nil", false, "false", "", "", nil}, @@ -474,10 +476,12 @@ func TestEvalExpression(t *testing.T) { {"m1 == nil", false, "false", "", "", nil}, {"mnil == m1", false, "", "", "", fmt.Errorf("can not compare map variables")}, {"mnil == nil", false, "true", "", "", nil}, + {"nil == 2", false, "", "", "", fmt.Errorf("can not compare int to nil")}, + {"2 == nil", false, "", "", "", fmt.Errorf("can not compare int to nil")}, // errors {"&3", false, "", "", "", fmt.Errorf("can not take address of \"3\"")}, - {"*3", false, "", "", "", fmt.Errorf("expression \"3\" can not be dereferenced")}, + {"*3", false, "", "", "", fmt.Errorf("expression \"3\" (int) can not be dereferenced")}, {"&(i2 + i3)", false, "", "", "", fmt.Errorf("can not take address of \"(i2 + i3)\"")}, {"i2 + p1", false, "", "", "", fmt.Errorf("mismatched types \"int\" and \"*int\"")}, {"i2 + f1", false, "", "", "", fmt.Errorf("mismatched types \"int\" and \"float64\"")}, @@ -487,6 +491,12 @@ func TestEvalExpression(t *testing.T) { {"*(i2 + i3)", false, "", "", "", fmt.Errorf("expression \"(i2 + i3)\" (int) can not be dereferenced")}, {"i2.member", false, "", "", "", fmt.Errorf("i2 (type int) is not a struct")}, {"fmt.Println(\"hello\")", false, "", "", "", fmt.Errorf("no type entry found")}, + {"*nil", false, "", "", "", fmt.Errorf("nil can not be dereferenced")}, + {"!nil", false, "", "", "", fmt.Errorf("operator ! can not be applied to \"nil\"")}, + {"&nil", false, "", "", "", fmt.Errorf("can not take address of \"nil\"")}, + {"nil[0]", false, "", "", "", fmt.Errorf("expression \"nil\" (nil) does not support indexing")}, + {"nil[2:10]", false, "", "", "", fmt.Errorf("can not slice \"nil\" (type nil)")}, + {"nil.member", false, "", "", "", fmt.Errorf("type nil is not a struct")}, // typecasts {"uint(i2)", false, "2", "", "uint", nil}, @@ -508,7 +518,7 @@ func TestEvalExpression(t *testing.T) { assertVariable(t, variable, tc) } else { if err == nil { - t.Fatalf("Expected error %s, got non (%s)", tc.err.Error(), tc.name) + t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name) } if tc.err.Error() != err.Error() { t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())