x/debug: change source file disambiguation heuristic.

First prefer files that match a longer suffix of the requested file.

Then break ties by choosing files that are under the directory from
which the program was compiled.

Then break ties by choosing a file with the shortest name.

Change-Id: I50b87fe4bf1fa70075e6d48032c6633c5c71f8da
Reviewed-on: https://go-review.googlesource.com/16260
Reviewed-by: Dave Day <djd@golang.org>
diff --git a/dwarf/line.go b/dwarf/line.go
index ca30b79..f035ef9 100644
--- a/dwarf/line.go
+++ b/dwarf/line.go
@@ -96,20 +96,35 @@
 		return nil, err
 	}
 
+	compDir := d.compilationDirectory()
+
 	// Find the closest match in the executable for the specified file.
 	// We choose the file with the largest number of path components matching
-	// at the end of the name, and break ties by choosing the shortest filename.
+	// at the end of the name. If there is a tie, we prefer files that are
+	// under the compilation directory.  If there is still a tie, we choose
+	// the file with the shortest name.
 	var bestFile struct {
 		fileNum    uint64 // Index of the file in the DWARF data.
 		components int    // Number of matching path components.
 		length     int    // Length of the filename.
+		underComp  bool   // File is under the compilation directory.
 	}
 	for num, f := range m.header.file {
 		c := matchingPathComponentSuffixSize(f.name, file)
-		if c > bestFile.components || (c == bestFile.components && len(f.name) < bestFile.length) {
+		underComp := strings.HasPrefix(f.name, compDir)
+		better := false
+		if c != bestFile.components {
+			better = c > bestFile.components
+		} else if underComp != bestFile.underComp {
+			better = underComp
+		} else {
+			better = len(f.name) < bestFile.length
+		}
+		if better {
 			bestFile.fileNum = uint64(num)
 			bestFile.components = c
 			bestFile.length = len(f.name)
+			bestFile.underComp = underComp
 		}
 	}
 	if bestFile.components == 0 {
@@ -132,6 +147,23 @@
 	return pcs, nil
 }
 
+// compilationDirectory finds the first compilation unit entry in d and returns
+// the compilation directory contained in it.
+// If it fails, it returns the empty string.
+func (d *Data) compilationDirectory() string {
+	r := d.Reader()
+	for {
+		entry, err := r.Next()
+		if entry == nil || err != nil {
+			return ""
+		}
+		if entry.Tag == TagCompileUnit {
+			name, _ := entry.Val(AttrCompDir).(string)
+			return name
+		}
+	}
+}
+
 // matchingPathComponentSuffixSize returns the largest n such that the last n
 // components of the paths p1 and p2 are equal.
 // e.g. matchingPathComponentSuffixSize("a/b/x/y.go", "b/a/x/y.go") returns 2.