github.com/neilgarb/delve@v1.9.2-nobreaks/pkg/locspec/locations.go (about)

     1  package locspec
     2  
     3  import (
     4  	"fmt"
     5  	"go/constant"
     6  	"path"
     7  	"path/filepath"
     8  	"reflect"
     9  	"regexp"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/go-delve/delve/pkg/proc"
    15  	"github.com/go-delve/delve/service/api"
    16  )
    17  
    18  const maxFindLocationCandidates = 5
    19  
    20  // LocationSpec is an interface that represents a parsed location spec string.
    21  type LocationSpec interface {
    22  	// Find returns all locations that match the location spec.
    23  	Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error)
    24  }
    25  
    26  // NormalLocationSpec represents a basic location spec.
    27  // This can be a file:line or func:line.
    28  type NormalLocationSpec struct {
    29  	Base       string
    30  	FuncBase   *FuncLocationSpec
    31  	LineOffset int
    32  }
    33  
    34  // RegexLocationSpec represents a regular expression
    35  // location expression such as /^myfunc$/.
    36  type RegexLocationSpec struct {
    37  	FuncRegex string
    38  }
    39  
    40  // AddrLocationSpec represents an address when used
    41  // as a location spec.
    42  type AddrLocationSpec struct {
    43  	AddrExpr string
    44  }
    45  
    46  // OffsetLocationSpec represents a location spec that
    47  // is an offset of the current location (file:line).
    48  type OffsetLocationSpec struct {
    49  	Offset int
    50  }
    51  
    52  // LineLocationSpec represents a line number in the current file.
    53  type LineLocationSpec struct {
    54  	Line int
    55  }
    56  
    57  // FuncLocationSpec represents a function in the target program.
    58  type FuncLocationSpec struct {
    59  	PackageName           string
    60  	AbsolutePackage       bool
    61  	ReceiverName          string
    62  	PackageOrReceiverName string
    63  	BaseName              string
    64  }
    65  
    66  // Parse will turn locStr into a parsed LocationSpec.
    67  func Parse(locStr string) (LocationSpec, error) {
    68  	rest := locStr
    69  
    70  	malformed := func(reason string) error {
    71  		//lint:ignore ST1005 backwards compatibility
    72  		return fmt.Errorf("Malformed breakpoint location \"%s\" at %d: %s", locStr, len(locStr)-len(rest), reason)
    73  	}
    74  
    75  	if len(rest) <= 0 {
    76  		return nil, malformed("empty string")
    77  	}
    78  
    79  	switch rest[0] {
    80  	case '+', '-':
    81  		offset, err := strconv.Atoi(rest)
    82  		if err != nil {
    83  			return nil, malformed(err.Error())
    84  		}
    85  		return &OffsetLocationSpec{offset}, nil
    86  
    87  	case '/':
    88  		if rest[len(rest)-1] == '/' {
    89  			rx, rest := readRegex(rest[1:])
    90  			if len(rest) == 0 {
    91  				return nil, malformed("non-terminated regular expression")
    92  			}
    93  			if len(rest) > 1 {
    94  				return nil, malformed("no line offset can be specified for regular expression locations")
    95  			}
    96  			return &RegexLocationSpec{rx}, nil
    97  		} else {
    98  			return parseLocationSpecDefault(locStr, rest)
    99  		}
   100  
   101  	case '*':
   102  		return &AddrLocationSpec{AddrExpr: rest[1:]}, nil
   103  
   104  	default:
   105  		return parseLocationSpecDefault(locStr, rest)
   106  	}
   107  }
   108  
   109  func parseLocationSpecDefault(locStr, rest string) (LocationSpec, error) {
   110  	malformed := func(reason string) error {
   111  		//lint:ignore ST1005 backwards compatibility
   112  		return fmt.Errorf("Malformed breakpoint location \"%s\" at %d: %s", locStr, len(locStr)-len(rest), reason)
   113  	}
   114  
   115  	v := strings.Split(rest, ":")
   116  	if len(v) > 2 {
   117  		// On Windows, path may contain ":", so split only on last ":"
   118  		v = []string{strings.Join(v[0:len(v)-1], ":"), v[len(v)-1]}
   119  	}
   120  
   121  	if len(v) == 1 {
   122  		n, err := strconv.ParseInt(v[0], 0, 64)
   123  		if err == nil {
   124  			return &LineLocationSpec{int(n)}, nil
   125  		}
   126  	}
   127  
   128  	spec := &NormalLocationSpec{}
   129  
   130  	spec.Base = v[0]
   131  	spec.FuncBase = parseFuncLocationSpec(spec.Base)
   132  
   133  	if len(v) < 2 {
   134  		spec.LineOffset = -1
   135  		return spec, nil
   136  	}
   137  
   138  	rest = v[1]
   139  
   140  	var err error
   141  	spec.LineOffset, err = strconv.Atoi(rest)
   142  	if err != nil || spec.LineOffset < 0 {
   143  		return nil, malformed("line offset negative or not a number")
   144  	}
   145  
   146  	return spec, nil
   147  }
   148  
   149  func readRegex(in string) (rx string, rest string) {
   150  	out := make([]rune, 0, len(in))
   151  	escaped := false
   152  	for i, ch := range in {
   153  		if escaped {
   154  			if ch == '/' {
   155  				out = append(out, '/')
   156  			} else {
   157  				out = append(out, '\\', ch)
   158  			}
   159  			escaped = false
   160  		} else {
   161  			switch ch {
   162  			case '\\':
   163  				escaped = true
   164  			case '/':
   165  				return string(out), in[i:]
   166  			default:
   167  				out = append(out, ch)
   168  			}
   169  		}
   170  	}
   171  	return string(out), ""
   172  }
   173  
   174  func parseFuncLocationSpec(in string) *FuncLocationSpec {
   175  	var v []string
   176  	pathend := strings.LastIndex(in, "/")
   177  	if pathend < 0 {
   178  		v = strings.Split(in, ".")
   179  	} else {
   180  		v = strings.Split(in[pathend:], ".")
   181  		if len(v) > 0 {
   182  			v[0] = in[:pathend] + v[0]
   183  		}
   184  	}
   185  
   186  	var spec FuncLocationSpec
   187  	switch len(v) {
   188  	case 1:
   189  		spec.BaseName = v[0]
   190  
   191  	case 2:
   192  		spec.BaseName = v[1]
   193  		r := stripReceiverDecoration(v[0])
   194  		if r != v[0] {
   195  			spec.ReceiverName = r
   196  		} else if strings.Contains(r, "/") {
   197  			spec.PackageName = r
   198  		} else {
   199  			spec.PackageOrReceiverName = r
   200  		}
   201  
   202  	case 3:
   203  		spec.BaseName = v[2]
   204  		spec.ReceiverName = stripReceiverDecoration(v[1])
   205  		spec.PackageName = v[0]
   206  
   207  	default:
   208  		return nil
   209  	}
   210  
   211  	if strings.HasPrefix(spec.PackageName, "/") {
   212  		spec.PackageName = spec.PackageName[1:]
   213  		spec.AbsolutePackage = true
   214  	}
   215  
   216  	if strings.Contains(spec.BaseName, "/") || strings.Contains(spec.ReceiverName, "/") {
   217  		return nil
   218  	}
   219  
   220  	return &spec
   221  }
   222  
   223  func stripReceiverDecoration(in string) string {
   224  	if len(in) < 3 {
   225  		return in
   226  	}
   227  	if (in[0] != '(') || (in[1] != '*') || (in[len(in)-1] != ')') {
   228  		return in
   229  	}
   230  
   231  	return in[2 : len(in)-1]
   232  }
   233  
   234  // Match will return whether the provided function matches the location spec.
   235  func (spec *FuncLocationSpec) Match(sym *proc.Function, packageMap map[string][]string) bool {
   236  	if spec.BaseName != sym.BaseName() {
   237  		return false
   238  	}
   239  
   240  	recv := stripReceiverDecoration(sym.ReceiverName())
   241  	if spec.ReceiverName != "" && spec.ReceiverName != recv {
   242  		return false
   243  	}
   244  	if spec.PackageName != "" {
   245  		if spec.AbsolutePackage {
   246  			if spec.PackageName != sym.PackageName() {
   247  				return false
   248  			}
   249  		} else {
   250  			if !packageMatch(spec.PackageName, sym.PackageName(), packageMap) {
   251  				return false
   252  			}
   253  		}
   254  	}
   255  	if spec.PackageOrReceiverName != "" && !packageMatch(spec.PackageOrReceiverName, sym.PackageName(), packageMap) && spec.PackageOrReceiverName != recv {
   256  		return false
   257  	}
   258  	return true
   259  }
   260  
   261  func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool {
   262  	for _, pkg := range packageMap[specPkg] {
   263  		if partialPackageMatch(pkg, symPkg) {
   264  			return true
   265  		}
   266  	}
   267  	return partialPackageMatch(specPkg, symPkg)
   268  }
   269  
   270  // Find will search all functions in the target program and filter them via the
   271  // regex location spec. Only functions matching the regex will be returned.
   272  func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) {
   273  	funcs := scope.BinInfo.Functions
   274  	matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  	r := make([]api.Location, 0, len(matches))
   279  	for i := range matches {
   280  		addrs, _ := proc.FindFunctionLocation(t, matches[i], 0)
   281  		if len(addrs) > 0 {
   282  			r = append(r, addressesToLocation(addrs))
   283  		}
   284  	}
   285  	return r, nil
   286  }
   287  
   288  // Find returns the locations specified via the address location spec.
   289  func (loc *AddrLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) {
   290  	if scope == nil {
   291  		addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("could not determine current location (scope is nil)")
   294  		}
   295  		return []api.Location{{PC: uint64(addr)}}, nil
   296  	}
   297  
   298  	v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true})
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	if v.Unreadable != nil {
   303  		return nil, v.Unreadable
   304  	}
   305  	switch v.Kind {
   306  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   307  		addr, _ := constant.Uint64Val(v.Value)
   308  		return []api.Location{{PC: addr}}, nil
   309  	case reflect.Func:
   310  		fn := scope.BinInfo.PCToFunc(uint64(v.Base))
   311  		pc, err := proc.FirstPCAfterPrologue(t, fn, false)
   312  		if err != nil {
   313  			return nil, err
   314  		}
   315  		return []api.Location{{PC: pc}}, nil
   316  	default:
   317  		return nil, fmt.Errorf("wrong expression kind: %v", v.Kind)
   318  	}
   319  }
   320  
   321  // FileMatch is true if the path matches the location spec.
   322  func (loc *NormalLocationSpec) FileMatch(path string) bool {
   323  	return partialPathMatch(loc.Base, path)
   324  }
   325  
   326  func tryMatchRelativePathByProc(expr, debugname, file string) bool {
   327  	return len(expr) > 0 && expr[0] == '.' && file == path.Join(path.Dir(debugname), expr)
   328  }
   329  
   330  func partialPathMatch(expr, path string) bool {
   331  	if runtime.GOOS == "windows" {
   332  		// Accept `expr` which is case-insensitive and slash-insensitive match to `path`
   333  		expr = strings.ToLower(filepath.ToSlash(expr))
   334  		path = strings.ToLower(filepath.ToSlash(path))
   335  	}
   336  	return partialPackageMatch(expr, path)
   337  }
   338  
   339  func partialPackageMatch(expr, path string) bool {
   340  	if len(expr) < len(path)-1 {
   341  		return strings.HasSuffix(path, expr) && (path[len(path)-len(expr)-1] == '/')
   342  	}
   343  	return expr == path
   344  }
   345  
   346  // AmbiguousLocationError is returned when the location spec
   347  // should only return one location but returns multiple instead.
   348  type AmbiguousLocationError struct {
   349  	Location           string
   350  	CandidatesString   []string
   351  	CandidatesLocation []api.Location
   352  }
   353  
   354  func (ale AmbiguousLocationError) Error() string {
   355  	var candidates []string
   356  	if ale.CandidatesLocation != nil {
   357  		for i := range ale.CandidatesLocation {
   358  			candidates = append(candidates, ale.CandidatesLocation[i].Function.Name())
   359  		}
   360  
   361  	} else {
   362  		candidates = ale.CandidatesString
   363  	}
   364  	return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
   365  }
   366  
   367  // Find will return a list of locations that match the given location spec.
   368  // This matches each other location spec that does not already have its own spec
   369  // implemented (such as regex, or addr).
   370  func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
   371  	limit := maxFindLocationCandidates
   372  	var candidateFiles []string
   373  	for _, sourceFile := range scope.BinInfo.Sources {
   374  		substFile := sourceFile
   375  		if len(substitutePathRules) > 0 {
   376  			substFile = SubstitutePath(sourceFile, substitutePathRules)
   377  		}
   378  		if loc.FileMatch(substFile) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], substFile)) {
   379  			candidateFiles = append(candidateFiles, sourceFile)
   380  			if len(candidateFiles) >= limit {
   381  				break
   382  			}
   383  		}
   384  	}
   385  
   386  	limit -= len(candidateFiles)
   387  
   388  	var candidateFuncs []string
   389  	if loc.FuncBase != nil && limit > 0 {
   390  		candidateFuncs = loc.findFuncCandidates(scope, limit)
   391  	}
   392  
   393  	if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
   394  		// if no result was found this locations string could be an
   395  		// expression that the user forgot to prefix with '*', try treating it as
   396  		// such.
   397  		addrSpec := &AddrLocationSpec{AddrExpr: locStr}
   398  		locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines, nil)
   399  		if err != nil {
   400  			return nil, fmt.Errorf("location \"%s\" not found", locStr)
   401  		}
   402  		return locs, nil
   403  	} else if matching > 1 {
   404  		return nil, AmbiguousLocationError{Location: locStr, CandidatesString: append(candidateFiles, candidateFuncs...)}
   405  	}
   406  
   407  	// len(candidateFiles) + len(candidateFuncs) == 1
   408  	var addrs []uint64
   409  	var err error
   410  	if len(candidateFiles) == 1 {
   411  		if loc.LineOffset < 0 {
   412  			//lint:ignore ST1005 backwards compatibility
   413  			return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
   414  		}
   415  		addrs, err = proc.FindFileLocation(t, candidateFiles[0], loc.LineOffset)
   416  		if includeNonExecutableLines {
   417  			if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
   418  				return []api.Location{{File: candidateFiles[0], Line: loc.LineOffset}}, nil
   419  			}
   420  		}
   421  	} else { // len(candidateFuncs) == 1
   422  		addrs, err = proc.FindFunctionLocation(t, candidateFuncs[0], loc.LineOffset)
   423  	}
   424  
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  	return []api.Location{addressesToLocation(addrs)}, nil
   429  }
   430  
   431  func (loc *NormalLocationSpec) findFuncCandidates(scope *proc.EvalScope, limit int) []string {
   432  	candidateFuncs := map[string]struct{}{}
   433  	// See if it matches generic functions first
   434  	for fname := range scope.BinInfo.LookupGenericFunc() {
   435  		if len(candidateFuncs) >= limit {
   436  			break
   437  		}
   438  		if !loc.FuncBase.Match(&proc.Function{Name: fname}, scope.BinInfo.PackageMap) {
   439  			continue
   440  		}
   441  		if loc.Base == fname {
   442  			return []string{fname}
   443  		}
   444  		candidateFuncs[fname] = struct{}{}
   445  	}
   446  	for _, f := range scope.BinInfo.LookupFunc {
   447  		if len(candidateFuncs) >= limit {
   448  			break
   449  		}
   450  		if !loc.FuncBase.Match(f, scope.BinInfo.PackageMap) {
   451  			continue
   452  		}
   453  		if loc.Base == f.Name {
   454  			// if an exact match for the function name is found use it
   455  			return []string{f.Name}
   456  		}
   457  		// If f is an instantiation of a generic function see if we should add its generic version instead.
   458  		if gn := f.NameWithoutTypeParams(); gn != "" {
   459  			if _, alreadyAdded := candidateFuncs[gn]; !alreadyAdded {
   460  				candidateFuncs[f.Name] = struct{}{}
   461  			}
   462  		} else {
   463  			candidateFuncs[f.Name] = struct{}{}
   464  		}
   465  	}
   466  	// convert candidateFuncs map into an array of its keys
   467  	r := make([]string, 0, len(candidateFuncs))
   468  	for s := range candidateFuncs {
   469  		r = append(r, s)
   470  	}
   471  	return r
   472  }
   473  
   474  func crossPlatformPath(path string) string {
   475  	if runtime.GOOS == "windows" {
   476  		return strings.ToLower(path)
   477  	}
   478  	return path
   479  }
   480  
   481  // SubstitutePath applies the specified path substitution rules to path.
   482  func SubstitutePath(path string, rules [][2]string) string {
   483  	path = crossPlatformPath(path)
   484  	// On windows paths returned from headless server are as c:/dir/dir
   485  	// though os.PathSeparator is '\\'
   486  
   487  	separator := "/"                  //make it default
   488  	if strings.Contains(path, "\\") { //dependent on the path
   489  		separator = "\\"
   490  	}
   491  	for _, r := range rules {
   492  		from := crossPlatformPath(r[0])
   493  		to := r[1]
   494  
   495  		if !strings.HasSuffix(from, separator) {
   496  			from = from + separator
   497  		}
   498  		if !strings.HasSuffix(to, separator) {
   499  			to = to + separator
   500  		}
   501  		if strings.HasPrefix(path, from) {
   502  			return strings.Replace(path, from, to, 1)
   503  		}
   504  	}
   505  	return path
   506  }
   507  
   508  func addressesToLocation(addrs []uint64) api.Location {
   509  	if len(addrs) <= 0 {
   510  		return api.Location{}
   511  	}
   512  	return api.Location{PC: addrs[0], PCs: addrs}
   513  }
   514  
   515  // Find returns the location after adding the offset amount to the current line number.
   516  func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) {
   517  	if scope == nil {
   518  		return nil, fmt.Errorf("could not determine current location (scope is nil)")
   519  	}
   520  	if loc.Offset == 0 {
   521  		return []api.Location{{PC: scope.PC}}, nil
   522  	}
   523  	file, line, fn := scope.BinInfo.PCToLine(scope.PC)
   524  	if fn == nil {
   525  		return nil, fmt.Errorf("could not determine current location")
   526  	}
   527  	addrs, err := proc.FindFileLocation(t, file, line+loc.Offset)
   528  	if includeNonExecutableLines {
   529  		if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
   530  			return []api.Location{{File: file, Line: line + loc.Offset}}, nil
   531  		}
   532  	}
   533  	return []api.Location{addressesToLocation(addrs)}, err
   534  }
   535  
   536  // Find will return the location at the given line in the current file.
   537  func (loc *LineLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) {
   538  	if scope == nil {
   539  		return nil, fmt.Errorf("could not determine current location (scope is nil)")
   540  	}
   541  	file, _, fn := scope.BinInfo.PCToLine(scope.PC)
   542  	if fn == nil {
   543  		return nil, fmt.Errorf("could not determine current location")
   544  	}
   545  	addrs, err := proc.FindFileLocation(t, file, loc.Line)
   546  	if includeNonExecutableLines {
   547  		if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
   548  			return []api.Location{{File: file, Line: loc.Line}}, nil
   549  		}
   550  	}
   551  	return []api.Location{addressesToLocation(addrs)}, err
   552  }
   553  
   554  func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {
   555  	regex, err := regexp.Compile(filter)
   556  	if err != nil {
   557  		return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
   558  	}
   559  
   560  	funcs := []string{}
   561  	for _, f := range allFuncs {
   562  		if regex.MatchString(f.Name) {
   563  			funcs = append(funcs, f.Name)
   564  		}
   565  	}
   566  	return funcs, nil
   567  }