Repository: go-delve/delve

Issue:
Support map evaluation by composite literal keys
1. What version of Delve are you using (`dlv version`)?

```
$ dlv version
Delve Debugger
Version: 1.1.0
Build: $Id: 1990ba12450cab9425a2ae62e6ab988725023d5c $
```

2. What version of Go are you using? (`go version`)?

```
$ go version
go version go1.11.2 linux/amd64
```

3. What operating system and processor architecture are you using?

Linux/amd64

4. What did you do?

Compile the following program, debug it with delve, try to print some map values:

```go
package main

import "fmt"

type Pair struct {
        A, B int
}

func main() {
        m := map[Pair]string{}
        m[Pair{A: 1, B: 1}] = "x"
        m[Pair{A: 1, B: 2}] = "y"
        fmt.Println(m)
}
```

```
$ dlv debug test.go
(dlv) b main.main:4
...
(dlv) c
...
(dlv) p m[main.Pair{A: 1, B: 1}]
Command failed: expression *ast.CompositeLit not implemented
(dlv) p m[Pair{A: 1, B: 1}]
Command failed: expression *ast.CompositeLit not implemented
```

5. What did you expect to see?

Print the corresponding map values.

6. What did you see instead?

"Command failed: expression *ast.CompositeLit not implemented"

Looks like CompositeLit evaluation wasn't implemented: https://github.com/go-delve/delve/blob/4c9a72e486f1f0d0c90ecede8415a871dced8117/pkg/proc/eval.go#L258

Discussion:
@apmattil: any progress here ?
I think this is really important feature.. at least with cmd-line debugging.@1Mark: Any update on this please?@Ap3lsin4k: The issue `expression *ast.CompositeLit not implemented` is the same for GoLand. 

I am encouraged to write more unit tests because the debugger is inconvenient to use. Maybe the absence of "composite literal evaluation" is a well-thought feature, not a bug :smiley: @glezjose: Any luck? Having the same issue with GoLand

@debemdeboas: Incredible how such a (seemingly) simple feature has been left behind by the team!@iamzili: I would welcome this functionality as well, but we can still inspect the items by their addresses by following the second possibility described in [this link](https://github.com/go-delve/delve/blob/master/Documentation/cli/expr.md#nesting-limit). for example:

```
 print *(*"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model.AggregateContainerState")(0xc000c8f5e0)
```

Next, we can also print out one of the struct's fields:

```
 print (*(*"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model.AggregateContainerState")(0xc000c8f5e0)).IsUnderVPA
```

Solution patch:
diff --git a/pkg/astutil/astutil.go b/pkg/astutil/astutil.go
index 024ab0bd..56c155c6 100644
--- a/pkg/astutil/astutil.go
+++ b/pkg/astutil/astutil.go
@@ -3,7 +3,9 @@
 package astutil
 
 import (
+	"bytes"
 	"go/ast"
+	"go/printer"
 	"go/token"
 	"strconv"
 )
@@ -43,3 +45,10 @@ func Or(x, y ast.Expr) ast.Expr {
 	}
 	return &ast.BinaryExpr{Op: token.LOR, X: x, Y: y}
 }
+
+// ExprToString uses go/printer to format an ast.Expr into a string.
+func ExprToString(t ast.Expr) string {
+	var buf bytes.Buffer
+	printer.Fprint(&buf, token.NewFileSet(), t)
+	return buf.String()
+}
diff --git a/pkg/dwarf/godwarf/fakes.go b/pkg/dwarf/godwarf/fakes.go
index da19afe4..6365d33b 100644
--- a/pkg/dwarf/godwarf/fakes.go
+++ b/pkg/dwarf/godwarf/fakes.go
@@ -53,3 +53,9 @@ func FakeBasicType(name string, bitSize int) Type {
 		panic("unsupported")
 	}
 }
+
+// FakePointerType synthesizes a pointer type
+func FakePointerType(typ Type, ptrSize int64) *PtrType {
+	typename := "*" + typ.Common().Name
+	return &PtrType{CommonType: CommonType{ByteSize: ptrSize, Name: typename}, Type: typ}
+}
diff --git a/pkg/dwarf/godwarf/type.go b/pkg/dwarf/godwarf/type.go
index f4d0bfaa..6cc1d85b 100644
--- a/pkg/dwarf/godwarf/type.go
+++ b/pkg/dwarf/godwarf/type.go
@@ -1101,3 +1101,17 @@ func zeroArray(t Type) {
 		t = at.Type
 	}
 }
+
+// ResolveTypedef returns the underlying type of a typedef or qualified type.
+func ResolveTypedef(typ Type) Type {
+	for {
+		switch tt := typ.(type) {
+		case *TypedefType:
+			typ = tt.Type
+		case *QualType:
+			typ = tt.Type
+		default:
+			return typ
+		}
+	}
+}
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index a4494ed3..da709006 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -24,6 +24,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/go-delve/delve/pkg/astutil"
 	pdwarf "github.com/go-delve/delve/pkg/dwarf"
 	"github.com/go-delve/delve/pkg/dwarf/frame"
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
@@ -192,7 +193,14 @@ func FindFileLocation(p Process, filename string, lineno int) ([]uint64, error)
 	}
 
 	if len(pcs) == 0 {
-		return nil, &ErrCouldNotFindLine{fileFound, filename, lineno}
+		stripped := false
+		for _, image := range bi.Images {
+			if image.Stripped() && image.symTable != nil && image.symTable.Files[filename] != nil {
+				stripped = true
+				break
+			}
+		}
+		return nil, &ErrCouldNotFindLine{fileFound, stripped, filename, lineno}
 	}
 
 	// 2. assign all occurrences of filename:lineno to their containing function
@@ -921,15 +929,21 @@ func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *Function) {
 }
 
 type ErrCouldNotFindLine struct {
-	fileFound bool
-	filename  string
-	lineno    int
+	fileFound, stripped bool
+	filename            string
+	lineno              int
 }
 
 func (err *ErrCouldNotFindLine) Error() string {
 	if err.fileFound {
+		if err.stripped {
+			return fmt.Sprintf("could not find statement at %s:%d, binary is stripped", err.filename, err.lineno)
+		}
 		return fmt.Sprintf("could not find statement at %s:%d, please use a line with a statement", err.filename, err.lineno)
 	}
+	if err.stripped {
+		return fmt.Sprintf("could not find file %s, binary is stripped", err.filename)
+	}
 	return fmt.Sprintf("could not find file %s", err.filename)
 }
 
@@ -2374,10 +2388,10 @@ func (bi *BinaryInfo) findTypeExpr(expr ast.Expr) (godwarf.Type, error) {
 		alen, litlen := anode.Len.(*ast.BasicLit)
 		if litlen && alen.Kind == token.INT {
 			n, _ := strconv.Atoi(alen.Value)
-			return bi.findArrayType(n, exprToString(anode.Elt))
+			return bi.findArrayType(n, astutil.ExprToString(anode.Elt))
 		}
 	}
-	return bi.findType(exprToString(expr))
+	return bi.findType(astutil.ExprToString(expr))
 }
 
 func (bi *BinaryInfo) findArrayType(n int, etyp string) (godwarf.Type, error) {
diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go
index 596eefc4..786a06a2 100644
--- a/pkg/proc/breakpoints.go
+++ b/pkg/proc/breakpoints.go
@@ -12,6 +12,7 @@ import (
 	"go/token"
 	"reflect"
 
+	"github.com/go-delve/delve/pkg/astutil"
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
 	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/dwarf/reader"
@@ -213,17 +214,17 @@ func (bp *Breakpoint) VerboseDescr() []string {
 	for _, breaklet := range bp.Breaklets {
 		switch breaklet.Kind {
 		case UserBreakpoint:
-			r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", exprToString(breaklet.Cond), lbp.hitCond))
+			r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", astutil.ExprToString(breaklet.Cond), lbp.HitCond()))
 		case NextBreakpoint:
-			r = append(r, fmt.Sprintf("Next Cond=%q", exprToString(breaklet.Cond)))
+			r = append(r, fmt.Sprintf("Next Cond=%q", astutil.ExprToString(breaklet.Cond)))
 		case NextDeferBreakpoint:
-			r = append(r, fmt.Sprintf("NextDefer Cond=%q DeferReturns=%#x", exprToString(breaklet.Cond), breaklet.DeferReturns))
+			r = append(r, fmt.Sprintf("NextDefer Cond=%q DeferReturns=%#x", astutil.ExprToString(breaklet.Cond), breaklet.DeferReturns))
 		case StepBreakpoint:
-			r = append(r, fmt.Sprintf("Step Cond=%q", exprToString(breaklet.Cond)))
+			r = append(r, fmt.Sprintf("Step Cond=%q", astutil.ExprToString(breaklet.Cond)))
 		case WatchOutOfScopeBreakpoint:
-			r = append(r, fmt.Sprintf("WatchOutOfScope Cond=%q checkPanicCall=%v", exprToString(breaklet.Cond), breaklet.checkPanicCall))
+			r = append(r, fmt.Sprintf("WatchOutOfScope Cond=%q checkPanicCall=%v", astutil.ExprToString(breaklet.Cond), breaklet.checkPanicCall))
 		case StackResizeBreakpoint:
-			r = append(r, fmt.Sprintf("StackResizeBreakpoint Cond=%q", exprToString(breaklet.Cond)))
+			r = append(r, fmt.Sprintf("StackResizeBreakpoint Cond=%q", astutil.ExprToString(breaklet.Cond)))
 		case PluginOpenBreakpoint:
 			r = append(r, "PluginOpenBreakpoint")
 		case StepIntoNewProcBreakpoint:
@@ -231,7 +232,7 @@ func (bp *Breakpoint) VerboseDescr() []string {
 		case NextInactivatedBreakpoint:
 			r = append(r, "NextInactivatedBreakpoint")
 		case StepIntoRangeOverFuncBodyBreakpoint:
-			r = append(r, "StepIntoRangeOverFuncBodyBreakpoint Cond=%q", exprToString(breaklet.Cond))
+			r = append(r, "StepIntoRangeOverFuncBodyBreakpoint Cond=%q", astutil.ExprToString(breaklet.Cond))
 		default:
 			r = append(r, fmt.Sprintf("Unknown %d", breaklet.Kind))
 		}
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index e6e21584..e5b6d511 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -1,20 +1,19 @@
 package proc
 
 import (
-	"bytes"
 	"debug/dwarf"
 	"errors"
 	"fmt"
 	"go/ast"
 	"go/constant"
 	"go/parser"
-	"go/printer"
 	"go/token"
 	"reflect"
 	"runtime/debug"
 	"sort"
 	"strings"
 
+	"github.com/go-delve/delve/pkg/astutil"
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
 	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/dwarf/reader"
@@ -1195,7 +1194,7 @@ func (stack *evalStack) executeOp() {
 		fncall := stack.fncallPeek()
 		actualArg := stack.pop()
 		if actualArg.Name == "" {
-			actualArg.Name = exprToString(op.ArgExpr)
+			actualArg.Name = astutil.ExprToString(op.ArgExpr)
 		}
 		stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread)
 
@@ -1232,7 +1231,7 @@ func (stack *evalStack) executeOp() {
 	case *evalop.SetValue:
 		lhv := stack.pop()
 		rhv := stack.pop()
-		stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe))
+		stack.err = scope.setValue(lhv, rhv, astutil.ExprToString(op.Rhe))
 
 	case *evalop.PushPinAddress:
 		debugPinCount++
@@ -1363,12 +1362,6 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
 	return stack.result(nil)
 }
 
-func exprToString(t ast.Expr) string {
-	var buf bytes.Buffer
-	printer.Fprint(&buf, token.NewFileSet(), t)
-	return buf.String()
-}
-
 func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
 	var x *Variable
 
@@ -1409,7 +1402,7 @@ func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
 
 	if x.Kind != reflect.Bool {
 		if op.Node != nil {
-			stack.err = fmt.Errorf("expression %q should be boolean not %s", exprToString(op.Node), x.Kind)
+			stack.err = fmt.Errorf("expression %q should be boolean not %s", astutil.ExprToString(op.Node), x.Kind)
 		} else {
 			stack.err = errors.New("internal debugger error: expected boolean")
 		}
@@ -1429,9 +1422,9 @@ func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
 func (scope *EvalScope) evalTypeCast(op *evalop.TypeCast, stack *evalStack) {
 	argv := stack.pop()
 
-	typ := resolveTypedef(op.DwarfType)
+	typ := godwarf.ResolveTypedef(op.DwarfType)
 
-	converr := fmt.Errorf("can not convert %q to %s", exprToString(op.Node.Args[0]), typ.String())
+	converr := fmt.Errorf("can not convert %q to %s", astutil.ExprToString(op.Node.Args[0]), typ.String())
 
 	// compatible underlying types
 	if typeCastCompatibleTypes(argv.RealType, typ) {
@@ -1722,7 +1715,7 @@ func typeCastCompatibleTypes(typ1, typ2 godwarf.Type) bool {
 				return true
 			}
 			// pointer types are compatible if their element types are compatible
-			return typeCastCompatibleTypes(resolveTypedef(ttyp1.Type), resolveTypedef(ttyp2.Type))
+			return typeCastCompatibleTypes(godwarf.ResolveTypedef(ttyp1.Type), godwarf.ResolveTypedef(ttyp2.Type))
 		}
 	case *godwarf.StringType:
 		if _, ok := typ2.(*godwarf.StringType); ok {
@@ -1798,7 +1791,7 @@ func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
 	}
 
 	arg := args[0]
-	invalidArgErr := fmt.Errorf("invalid argument %s (type %s) for cap", exprToString(nodeargs[0]), arg.TypeString())
+	invalidArgErr := fmt.Errorf("invalid argument %s (type %s) for cap", astutil.ExprToString(nodeargs[0]), arg.TypeString())
 
 	switch arg.Kind {
 	case reflect.Ptr:
@@ -1830,7 +1823,7 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
 		return nil, fmt.Errorf("wrong number of arguments to len: %d", len(args))
 	}
 	arg := args[0]
-	invalidArgErr := fmt.Errorf("invalid argument %s (type %s) for len", exprToString(nodeargs[0]), arg.TypeString())
+	invalidArgErr := fmt.Errorf("invalid argument %s (type %s) for len", astutil.ExprToString(nodeargs[0]), arg.TypeString())
 
 	switch arg.Kind {
 	case reflect.Ptr:
@@ -1887,11 +1880,11 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
 	}
 
 	if realev.Value == nil || ((realev.Value.Kind() != constant.Int) && (realev.Value.Kind() != constant.Float)) {
-		return nil, fmt.Errorf("invalid argument 1 %s (type %s) to complex", exprToString(nodeargs[0]), realev.TypeString())
+		return nil, fmt.Errorf("invalid argument 1 %s (type %s) to complex", astutil.ExprToString(nodeargs[0]), realev.TypeString())
 	}
 
 	if imagev.Value == nil || ((imagev.Value.Kind() != constant.Int) && (imagev.Value.Kind() != constant.Float)) {
-		return nil, fmt.Errorf("invalid argument 2 %s (type %s) to complex", exprToString(nodeargs[1]), imagev.TypeString())
+		return nil, fmt.Errorf("invalid argument 2 %s (type %s) to complex", astutil.ExprToString(nodeargs[1]), imagev.TypeString())
 	}
 
 	sz := int64(0)
@@ -1929,7 +1922,7 @@ func imagBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
 	}
 
 	if arg.Kind != reflect.Complex64 && arg.Kind != reflect.Complex128 {
-		return nil, fmt.Errorf("invalid argument %s (type %s) to imag", exprToString(nodeargs[0]), arg.TypeString())
+		return nil, fmt.Errorf("invalid argument %s (type %s) to imag", astutil.ExprToString(nodeargs[0]), arg.TypeString())
 	}
 
 	return newConstant(constant.Imag(arg.Value), arg.mem), nil
@@ -1948,7 +1941,7 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
 	}
 
 	if arg.Value == nil || ((arg.Value.Kind() != constant.Int) && (arg.Value.Kind() != constant.Float) && (arg.Value.Kind() != constant.Complex)) {
-		return nil, fmt.Errorf("invalid argument %s (type %s) to real", exprToString(nodeargs[0]), arg.TypeString())
+		return nil, fmt.Errorf("invalid argument %s (type %s) to real", astutil.ExprToString(nodeargs[0]), arg.TypeString())
 	}
 
 	return newConstant(constant.Real(arg.Value), arg.mem), nil
@@ -1973,7 +1966,7 @@ func minmaxBuiltin(name string, op token.Token, args []*Variable, nodeargs []ast
 		}
 
 		if args[i].Unreadable != nil {
-			return nil, fmt.Errorf("could not load %q: %v", exprToString(nodeargs[i]), args[i].Unreadable)
+			return nil, fmt.Errorf("could not load %q: %v", astutil.ExprToString(nodeargs[i]), args[i].Unreadable)
 		}
 		if args[i].FloatSpecial != 0 {
 			return nil, errOperationOnSpecialFloat
@@ -2040,7 +2033,7 @@ func (scope *EvalScope) evalStructSelector(op *evalop.Select, stack *evalStack)
 func (scope *EvalScope) evalTypeAssert(op *evalop.TypeAssert, stack *evalStack) {
 	xv := stack.pop()
 	if xv.Kind != reflect.Interface {
-		stack.err = fmt.Errorf("expression %q not an interface", exprToString(op.Node.X))
+		stack.err = fmt.Errorf("expression %q not an interface", astutil.ExprToString(op.Node.X))
 		return
 	}
 	xv.loadInterface(0, false, loadFullValue)
@@ -2053,7 +2046,7 @@ func (scope *EvalScope) evalTypeAssert(op *evalop.TypeAssert, stack *evalStack)
 		return
 	}
 	if xv.Children[0].Addr == 0 {
-		stack.err = fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(op.Node.Type))
+		stack.err = fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), astutil.ExprToString(op.Node.Type))
 		return
 	}
 	typ := op.DwarfType
@@ -2082,7 +2075,7 @@ func (scope *EvalScope) evalIndex(op *evalop.Index, stack *evalStack) {
 		xev = xev.maybeDereference()
 	}
 
-	cantindex := fmt.Errorf("expression %q (%s) does not support indexing", exprToString(op.Node.X), xev.TypeString())
+	cantindex := fmt.Errorf("expression %q (%s) does not support indexing", astutil.ExprToString(op.Node.X), xev.TypeString())
 
 	switch xev.Kind {
 	case reflect.Ptr:
@@ -2102,7 +2095,7 @@ func (scope *EvalScope) evalIndex(op *evalop.Index, stack *evalStack) {
 
 	case reflect.Slice, reflect.Array, reflect.String:
 		if xev.Base == 0 {
-			stack.err = fmt.Errorf("can not index %q", exprToString(op.Node.X))
+			stack.err = fmt.Errorf("can not index %q", astutil.ExprToString(op.Node.X))
 			return
 		}
 		n, err := idxev.asInt()
@@ -2155,7 +2148,7 @@ func (scope *EvalScope) evalReslice(op *evalop.Reslice, stack *evalStack) {
 	switch xev.Kind {
 	case reflect.Slice, reflect.Array, reflect.String:
 		if xev.Base == 0 {
-			stack.err = fmt.Errorf("can not slice %q", exprToString(op.Node.X))
+			stack.err = fmt.Errorf("can not slice %q", astutil.ExprToString(op.Node.X))
 			return
 		}
 		stack.pushErr(xev.reslice(low, high, op.TrustLen))
@@ -2180,7 +2173,7 @@ func (scope *EvalScope) evalReslice(op *evalop.Reslice, stack *evalStack) {
 		}
 		fallthrough
 	default:
-		stack.err = fmt.Errorf("can not slice %q (type %s)", exprToString(op.Node.X), xev.TypeString())
+		stack.err = fmt.Errorf("can not slice %q (type %s)", astutil.ExprToString(op.Node.X), xev.TypeString())
 		return
 	}
 }
@@ -2190,7 +2183,7 @@ func (scope *EvalScope) evalPointerDeref(op *evalop.PointerDeref, stack *evalSta
 	xev := stack.pop()
 
 	if xev.Kind != reflect.Ptr {
-		stack.err = fmt.Errorf("expression %q (%s) can not be dereferenced", exprToString(op.Node.X), xev.TypeString())
+		stack.err = fmt.Errorf("expression %q (%s) can not be dereferenced", astutil.ExprToString(op.Node.X), xev.TypeString())
 		return
 	}
 
@@ -2225,7 +2218,7 @@ func (scope *EvalScope) evalPointerDeref(op *evalop.PointerDeref, stack *evalSta
 func (scope *EvalScope) evalAddrOf(op *evalop.AddrOf, stack *evalStack) {
 	xev := stack.pop()
 	if xev.Addr == 0 || xev.DwarfType == nil {
-		stack.err = fmt.Errorf("can not take address of %q", exprToString(op.Node.X))
+		stack.err = fmt.Errorf("can not take address of %q", astutil.ExprToString(op.Node.X))
 		return
 	}
 
@@ -2235,8 +2228,7 @@ func (scope *EvalScope) evalAddrOf(op *evalop.AddrOf, stack *evalStack) {
 func (v *Variable) pointerToVariable() *Variable {
 	v.OnlyAddr = true
 
-	typename := "*" + v.DwarfType.Common().Name
-	rv := v.newVariable("", 0, &godwarf.PtrType{CommonType: godwarf.CommonType{ByteSize: int64(v.bi.Arch.PtrSize()), Name: typename}, Type: v.DwarfType}, v.mem)
+	rv := v.newVariable("", 0, godwarf.FakePointerType(v.DwarfType, int64(v.bi.Arch.PtrSize())), v.mem)
 	rv.Children = []Variable{*v}
 	rv.loaded = true
 
@@ -2293,7 +2285,7 @@ func (scope *EvalScope) evalUnary(op *evalop.Unary, stack *evalStack) {
 		return
 	}
 	if xv.Value == nil {
-		stack.err = fmt.Errorf("operator %s can not be applied to %q", op.Node.Op.String(), exprToString(op.Node.X))
+		stack.err = fmt.Errorf("operator %s can not be applied to %q", op.Node.Op.String(), astutil.ExprToString(op.Node.X))
 		return
 	}
 	rc, err := constantUnaryOp(op.Node.Op, xv.Value)
@@ -2448,12 +2440,12 @@ func (scope *EvalScope) evalBinary(binop *evalop.Binary, stack *evalStack) {
 			yv.loadValue(loadFullValueLongerStrings)
 		}
 		if xv.Value == nil {
-			stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.X))
+			stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), astutil.ExprToString(node.X))
 			return
 		}
 
 		if yv.Value == nil {
-			stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.Y))
+			stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), astutil.ExprToString(node.Y))
 			return
 		}
 
@@ -2714,8 +2706,8 @@ func sameType(t1, t2 godwarf.Type) bool {
 	// consistent, however we also synthesize some types ourselves
 	// (specifically pointers and slices) and we always use a reference through
 	// a typedef.
-	t1 = resolveTypedef(t1)
-	t2 = resolveTypedef(t2)
+	t1 = godwarf.ResolveTypedef(t1)
+	t2 = godwarf.ResolveTypedef(t2)
 
 	if tt1, isptr1 := t1.(*godwarf.PtrType); isptr1 {
 		tt2, isptr2 := t2.(*godwarf.PtrType)
diff --git a/pkg/proc/evalop/evalcompile.go b/pkg/proc/evalop/evalcompile.go
index af3f9299..7fdded01 100644
--- a/pkg/proc/evalop/evalcompile.go
+++ b/pkg/proc/evalop/evalcompile.go
@@ -1,18 +1,17 @@
 package evalop
 
 import (
-	"bytes"
 	"errors"
 	"fmt"
 	"go/ast"
 	"go/constant"
 	"go/parser"
-	"go/printer"
 	"go/scanner"
 	"go/token"
 	"strconv"
 	"strings"
 
+	"github.com/go-delve/delve/pkg/astutil"
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
 	"github.com/go-delve/delve/pkg/dwarf/reader"
 )
@@ -447,7 +446,7 @@ func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) e
 	// remove all enclosing parenthesis from the type name
 	fnnode = removeParen(fnnode)
 
-	targetTypeStr := exprToString(removeParen(node.Fun))
+	targetTypeStr := astutil.ExprToString(removeParen(node.Fun))
 	styp, err := ctx.FindTypeExpr(fnnode)
 	if err != nil {
 		switch targetTypeStr {
@@ -457,7 +456,7 @@ func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) e
 			styp = godwarf.FakeSliceType(godwarf.FakeBasicType("int", 32))
 		default:
 			if ambiguousErr != nil && err == reader.ErrTypeNotFound {
-				return fmt.Errorf("could not evaluate function or type %s: %v", exprToString(node.Fun), ambiguousErr)
+				return fmt.Errorf("could not evaluate function or type %s: %v", astutil.ExprToString(node.Fun), ambiguousErr)
 			}
 			return err
 		}
@@ -622,7 +621,7 @@ func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int)
 	for i, arg := range node.Args {
 		err := ctx.compileAST(arg)
 		if err != nil {
-			return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err)
+			return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", astutil.ExprToString(arg), i+1, astutil.ExprToString(node.Fun), err)
 		}
 		if isStringLiteral(arg) {
 			ctx.compileAllocLiteralString()
@@ -651,7 +650,7 @@ func (ctx *compileCtx) compileFunctionCallWithPinning(node *ast.CallExpr, id int
 			ctx.compileAllocLiteralString()
 		}
 		if err != nil {
-			return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err)
+			return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", astutil.ExprToString(arg), i+1, astutil.ExprToString(node.Fun), err)
 		}
 	}
 
@@ -725,9 +724,3 @@ func removeParen(n ast.Expr) ast.Expr {
 	}
 	return n
 }
-
-func exprToString(t ast.Expr) string {
-	var buf bytes.Buffer
-	printer.Fprint(&buf, token.NewFileSet(), t)
-	return buf.String()
-}
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index 5e925bb6..2fd0cace 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -13,6 +13,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/go-delve/delve/pkg/astutil"
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
 	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/dwarf/reader"
@@ -505,7 +506,7 @@ func funcCallEvalFuncExpr(scope *EvalScope, stack *evalStack, fncall *functionCa
 
 	fnvar := stack.peek()
 	if fnvar.Kind != reflect.Func {
-		return fmt.Errorf("expression %q is not a function", exprToString(fncall.expr.Fun))
+		return fmt.Errorf("expression %q is not a function", astutil.ExprToString(fncall.expr.Fun))
 	}
 	fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0, 0})
 	if fnvar.Unreadable != nil {
@@ -516,7 +517,7 @@ func funcCallEvalFuncExpr(scope *EvalScope, stack *evalStack, fncall *functionCa
 	}
 	fncall.fn = bi.PCToFunc(fnvar.Base)
 	if fncall.fn == nil {
-		return fmt.Errorf("could not find DIE for function %q", exprToString(fncall.expr.Fun))
+		return fmt.Errorf("could not find DIE for function %q", astutil.ExprToString(fncall.expr.Fun))
 	}
 	if !fncall.fn.cu.isgo {
 		return errNotAGoFunction
@@ -539,7 +540,7 @@ func funcCallEvalFuncExpr(scope *EvalScope, stack *evalStack, fncall *functionCa
 	if len(fnvar.Children) > 0 && argnum == (len(fncall.formalArgs)-1) {
 		argnum++
 		fncall.receiver = &fnvar.Children[0]
-		fncall.receiver.Name = exprToString(fncall.expr.Fun)
+		fncall.receiver.Name = astutil.ExprToString(fncall.expr.Fun)
 	}
 
 	if argnum > len(fncall.formalArgs) {
@@ -631,7 +632,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
 		if err != nil {
 			return 0, nil, err
 		}
-		typ = resolveTypedef(typ)
+		typ = godwarf.ResolveTypedef(typ)
 
 		var formalArg *funcCallArg
 		if bi.regabi {
@@ -742,12 +743,12 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
 		return f(v.Base, name)
 	case reflect.Map:
 		sv := v.clone()
-		sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
+		sv.RealType = godwarf.ResolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
 		sv = sv.maybeDereference()
 		return f(sv.Addr, name)
 	case reflect.Interface:
 		sv := v.clone()
-		sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.InterfaceType).TypedefType))
+		sv.RealType = godwarf.ResolveTypedef(&(v.RealType.(*godwarf.InterfaceType).TypedefType))
 		sv = sv.maybeDereference()
 		sv.Kind = reflect.Struct
 		return allPointers(sv, name, f)
diff --git a/pkg/proc/mapiter.go b/pkg/proc/mapiter.go
index 7d4141ee..19a855dc 100644
--- a/pkg/proc/mapiter.go
+++ b/pkg/proc/mapiter.go
@@ -18,7 +18,7 @@ type mapIterator interface {
 func (v *Variable) mapIterator(maxNumBuckets uint64) mapIterator {
 	mt := v.RealType.(*godwarf.MapType)
 	sv := v.clone()
-	sv.RealType = resolveTypedef(&(sv.RealType.(*godwarf.MapType).TypedefType))
+	sv.RealType = godwarf.ResolveTypedef(&(sv.RealType.(*godwarf.MapType).TypedefType))
 	sv = sv.maybeDereference()
 	v.Base = sv.Addr
 
@@ -383,7 +383,7 @@ func (it *mapIteratorSwiss) loadTypes() {
 						it.groupsFieldData = field
 						typ, ok := field.Type.(*godwarf.PtrType)
 						if ok {
-							it.groupType, _ = resolveTypedef(typ.Type).(*godwarf.StructType)
+							it.groupType, _ = godwarf.ResolveTypedef(typ.Type).(*godwarf.StructType)
 						}
 					case "lengthMask":
 						it.groupsFieldLengthMask = field
@@ -409,7 +409,7 @@ func (it *mapIteratorSwiss) loadTypes() {
 		return
 	}
 
-	slotsType, ok := resolveTypedef(it.groupFieldSlots.Type).(*godwarf.ArrayType)
+	slotsType, ok := godwarf.ResolveTypedef(it.groupFieldSlots.Type).(*godwarf.ArrayType)
 	if !ok {
 		it.v.Unreadable = errSwissMapBadGroupTypeErr
 		return
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index 71c113ea..dc716457 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -1590,7 +1590,7 @@ type onNextGoroutineWalker struct {
 
 func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor {
 	if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL {
-		x := exprToString(binx.X)
+		x := astutil.ExprToString(binx.X)
 		if x == "runtime.curg.goid" || x == "runtime.threadid" {
 			w.ret, w.err = evalBreakpointCondition(w.tgt, w.thread, n.(ast.Expr))
 			return nil
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index dcda48dd..89813fcf 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -664,7 +664,7 @@ func newVariable(name string, addr uint64, dwarfType godwarf.Type, bi *BinaryInf
 		bi:        bi,
 	}
 
-	v.RealType = resolveTypedef(v.DwarfType)
+	v.RealType = godwarf.ResolveTypedef(v.DwarfType)
 
 	switch t := v.RealType.(type) {
 	case *godwarf.PtrType:
@@ -758,19 +758,6 @@ func newVariable(name string, addr uint64, dwarfType godwarf.Type, bi *BinaryInf
 	return v
 }
 
-func resolveTypedef(typ godwarf.Type) godwarf.Type {
-	for {
-		switch tt := typ.(type) {
-		case *godwarf.TypedefType:
-			typ = tt.Type
-		case *godwarf.QualType:
-			typ = tt.Type
-		default:
-			return typ
-		}
-	}
-}
-
 var constantMaxInt64 = constant.MakeInt64(1<<63 - 1)
 
 func newConstant(val constant.Value, mem MemoryReadWriter) *Variable {
@@ -1123,7 +1110,7 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
 	switch v.Kind {
 	case reflect.Chan:
 		v = v.clone()
-		v.RealType = resolveTypedef(&(v.RealType.(*godwarf.ChanType).TypedefType))
+		v.RealType = godwarf.ResolveTypedef(&(v.RealType.(*godwarf.ChanType).TypedefType))
 	case reflect.Interface:
 		v.loadInterface(0, false, LoadConfig{})
 		if len(v.Children) > 0 {
@@ -1354,7 +1341,7 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
 			if v.Children[0].Kind == reflect.Interface {
 				nextLvl++
 			} else if ptyp, isptr := v.RealType.(*godwarf.PtrType); isptr {
-				_, elemTypIsPtr := resolveTypedef(ptyp.Type).(*godwarf.PtrType)
+				_, elemTypIsPtr := godwarf.ResolveTypedef(ptyp.Type).(*godwarf.PtrType)
 				if elemTypIsPtr {
 					nextLvl++
 					checkLvl = true
@@ -1371,7 +1358,7 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
 
 	case reflect.Chan:
 		sv := v.clone()
-		sv.RealType = resolveTypedef(&(sv.RealType.(*godwarf.ChanType).TypedefType))
+		sv.RealType = godwarf.ResolveTypedef(&(sv.RealType.(*godwarf.ChanType).TypedefType))
 		sv = sv.maybeDereference()
 		sv.loadValueInternal(0, loadFullValue)
 		v.Children = sv.Children
@@ -1676,7 +1663,7 @@ func (v *Variable) loadChanInfo() {
 		return
 	}
 	sv := v.clone()
-	sv.RealType = resolveTypedef(&(chanType.TypedefType))
+	sv.RealType = godwarf.ResolveTypedef(&(chanType.TypedefType))
 	sv = sv.maybeDereference()
 	if sv.Unreadable != nil || sv.Addr == 0 {
 		return
@@ -2067,7 +2054,7 @@ func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {
 
 	v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
 
-	ityp := resolveTypedef(&v.RealType.(*godwarf.InterfaceType).TypedefType).(*godwarf.StructType)
+	ityp := godwarf.ResolveTypedef(&v.RealType.(*godwarf.InterfaceType).TypedefType).(*godwarf.StructType)
 
 	// +rtype -field iface.tab *itab|*internal/abi.ITab
 	// +rtype -field iface.data unsafe.Pointer
@@ -2133,7 +2120,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig
 
 	deref := false
 	if kind&kindDirectIface == 0 {
-		realtyp := resolveTypedef(typ)
+		realtyp := godwarf.ResolveTypedef(typ)
 		if _, isptr := realtyp.(*godwarf.PtrType); !isptr {
 			typ = pointerTo(typ, v.bi.Arch)
 			deref = true



Test patch:
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 7e560dd8..3b97bf55 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -2776,6 +2776,22 @@ func TestDebugStripped(t *testing.T) {
 	}
 	withTestProcessArgs("testnextprog", t, "", []string{}, protest.LinkStrip, func(p *proc.Target, grp *proc.TargetGroup, f protest.Fixture) {
 		setFunctionBreakpoint(p, t, "main.main")
+
+		loc, err := proc.FindFunctionLocation(p, "main.main", 0)
+		assertNoError(err, t, "main.main")
+		filename, lineno, _ := p.BinInfo().PCToLine(loc[0])
+		_, err = proc.FindFileLocation(p, filename, lineno)
+		t.Logf("stripped breakpoint by line %s:%d: %v", filename, lineno, err)
+		if !strings.Contains(err.Error(), "binary is stripped") {
+			t.Errorf("wrong error, expected reference to binary being stripped")
+		}
+
+		_, err = proc.FindFunctionLocation(p, "main.main", 1)
+		t.Logf("stripped breakpoint by function and line main.main:1: %v", err)
+		if !strings.Contains(err.Error(), "binary is stripped") {
+			t.Errorf("wrong error, expected reference to binary being stripped")
+		}
+
 		assertNoError(grp.Continue(), t, "Continue")
 		assertCurrentLocationFunction(p, t, "main.main")
 		assertLineNumber(p, t, 37, "first continue")