307 lines
9.0 KiB
Go
307 lines
9.0 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package obj
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// A LineHist records the history of the file input stack, which maps the virtual line number,
|
|
// an incrementing count of lines processed in any input file and typically named lineno,
|
|
// to a stack of file:line pairs showing the path of inclusions that led to that position.
|
|
// The first line directive (//line in Go, #line in assembly) is treated as pushing
|
|
// a new entry on the stack, so that errors can report both the actual and translated
|
|
// line number.
|
|
//
|
|
// In typical use, the virtual lineno begins at 1, and file line numbers also begin at 1,
|
|
// but the only requirements placed upon the numbers by this code are:
|
|
// - calls to Push, Update, and Pop must be monotonically increasing in lineno
|
|
// - except as specified by those methods, virtual and file line number increase
|
|
// together, so that given (only) calls Push(10, "x.go", 1) and Pop(15),
|
|
// virtual line 12 corresponds to x.go line 3.
|
|
type LineHist struct {
|
|
Top *LineStack // current top of stack
|
|
Ranges []LineRange // ranges for lookup
|
|
Dir string // directory to qualify relative paths
|
|
TrimPathPrefix string // remove leading TrimPath from recorded file names
|
|
PrintFilenameOnly bool // ignore path when pretty-printing a line; internal use only
|
|
GOROOT string // current GOROOT
|
|
}
|
|
|
|
// A LineStack is an entry in the recorded line history.
|
|
// Although the history at any given line number is a stack,
|
|
// the record for all line processed forms a tree, with common
|
|
// stack prefixes acting as parents.
|
|
type LineStack struct {
|
|
Parent *LineStack // parent in inclusion stack
|
|
Lineno int // virtual line number where this entry takes effect
|
|
File string // file name used to open source file, for error messages
|
|
AbsFile string // absolute file name, for pcln tables
|
|
FileLine int // line number in file at Lineno
|
|
Directive bool
|
|
Sym *LSym // for linkgetline - TODO(rsc): remove
|
|
}
|
|
|
|
func (stk *LineStack) fileLineAt(lineno int) int {
|
|
return stk.FileLine + lineno - stk.Lineno
|
|
}
|
|
|
|
// The span of valid linenos in the recorded line history can be broken
|
|
// into a set of ranges, each with a particular stack.
|
|
// A LineRange records one such range.
|
|
type LineRange struct {
|
|
Start int // starting lineno
|
|
Stack *LineStack // top of stack for this range
|
|
}
|
|
|
|
// startRange starts a new range with the given top of stack.
|
|
func (h *LineHist) startRange(lineno int, top *LineStack) {
|
|
h.Top = top
|
|
h.Ranges = append(h.Ranges, LineRange{top.Lineno, top})
|
|
}
|
|
|
|
// setFile sets stk.File = file and also derives stk.AbsFile.
|
|
func (h *LineHist) setFile(stk *LineStack, file string) {
|
|
// Note: The exclusion of stk.Directive may be wrong but matches what we've done before.
|
|
// The check for < avoids putting a path prefix on "<autogenerated>".
|
|
abs := file
|
|
if h.Dir != "" && !filepath.IsAbs(file) && !strings.HasPrefix(file, "<") && !stk.Directive {
|
|
abs = filepath.Join(h.Dir, file)
|
|
}
|
|
|
|
// Remove leading TrimPathPrefix, or else rewrite $GOROOT to literal $GOROOT.
|
|
if h.TrimPathPrefix != "" && hasPathPrefix(abs, h.TrimPathPrefix) {
|
|
if abs == h.TrimPathPrefix {
|
|
abs = ""
|
|
} else {
|
|
abs = abs[len(h.TrimPathPrefix)+1:]
|
|
}
|
|
} else if hasPathPrefix(abs, h.GOROOT) {
|
|
abs = "$GOROOT" + abs[len(h.GOROOT):]
|
|
}
|
|
if abs == "" {
|
|
abs = "??"
|
|
}
|
|
abs = filepath.Clean(abs)
|
|
stk.AbsFile = abs
|
|
|
|
if file == "" {
|
|
file = "??"
|
|
}
|
|
stk.File = file
|
|
}
|
|
|
|
// Does s have t as a path prefix?
|
|
// That is, does s == t or does s begin with t followed by a slash?
|
|
// For portability, we allow ASCII case folding, so that hasPathPrefix("a/b/c", "A/B") is true.
|
|
// Similarly, we allow slash folding, so that hasPathPrefix("a/b/c", "a\\b") is true.
|
|
// We do not allow full Unicode case folding, for fear of causing more confusion
|
|
// or harm than good. (For an example of the kinds of things that can go wrong,
|
|
// see http://article.gmane.org/gmane.linux.kernel/1853266.)
|
|
func hasPathPrefix(s string, t string) bool {
|
|
if len(t) > len(s) {
|
|
return false
|
|
}
|
|
var i int
|
|
for i = 0; i < len(t); i++ {
|
|
cs := int(s[i])
|
|
ct := int(t[i])
|
|
if 'A' <= cs && cs <= 'Z' {
|
|
cs += 'a' - 'A'
|
|
}
|
|
if 'A' <= ct && ct <= 'Z' {
|
|
ct += 'a' - 'A'
|
|
}
|
|
if cs == '\\' {
|
|
cs = '/'
|
|
}
|
|
if ct == '\\' {
|
|
ct = '/'
|
|
}
|
|
if cs != ct {
|
|
return false
|
|
}
|
|
}
|
|
return i >= len(s) || s[i] == '/' || s[i] == '\\'
|
|
}
|
|
|
|
// Push records that at that lineno a new file with the given name was pushed onto the input stack.
|
|
func (h *LineHist) Push(lineno int, file string) {
|
|
stk := &LineStack{
|
|
Parent: h.Top,
|
|
Lineno: lineno,
|
|
FileLine: 1,
|
|
}
|
|
h.setFile(stk, file)
|
|
h.startRange(lineno, stk)
|
|
}
|
|
|
|
// Pop records that at lineno the current file was popped from the input stack.
|
|
func (h *LineHist) Pop(lineno int) {
|
|
top := h.Top
|
|
if top == nil {
|
|
return
|
|
}
|
|
if top.Directive && top.Parent != nil { // pop #line level too
|
|
top = top.Parent
|
|
}
|
|
next := top.Parent
|
|
if next == nil {
|
|
h.Top = nil
|
|
h.Ranges = append(h.Ranges, LineRange{lineno, nil})
|
|
return
|
|
}
|
|
|
|
// Popping included file. Update parent offset to account for
|
|
// the virtual line number range taken by the included file.
|
|
// Cannot modify the LineStack directly, or else lookups
|
|
// for the earlier line numbers will get the wrong answers,
|
|
// so make a new one.
|
|
stk := new(LineStack)
|
|
*stk = *next
|
|
stk.Lineno = lineno
|
|
stk.FileLine = next.fileLineAt(top.Lineno)
|
|
h.startRange(lineno, stk)
|
|
}
|
|
|
|
// Update records that at lineno the file name and line number were changed using
|
|
// a line directive (//line in Go, #line in assembly).
|
|
func (h *LineHist) Update(lineno int, file string, line int) {
|
|
top := h.Top
|
|
if top == nil {
|
|
return // shouldn't happen
|
|
}
|
|
var stk *LineStack
|
|
if top.Directive {
|
|
// Update existing entry, except make copy to avoid changing earlier history.
|
|
stk = new(LineStack)
|
|
*stk = *top
|
|
} else {
|
|
// Push new entry.
|
|
stk = &LineStack{
|
|
Parent: top,
|
|
Directive: true,
|
|
}
|
|
}
|
|
stk.Lineno = lineno
|
|
if stk.File != file {
|
|
h.setFile(stk, file) // only retain string if needed
|
|
}
|
|
stk.FileLine = line
|
|
h.startRange(lineno, stk)
|
|
}
|
|
|
|
// AddImport adds a package to the list of imported packages.
|
|
func (ctxt *Link) AddImport(pkg string) {
|
|
ctxt.Imports = append(ctxt.Imports, pkg)
|
|
}
|
|
|
|
// At returns the input stack in effect at lineno.
|
|
func (h *LineHist) At(lineno int) *LineStack {
|
|
i := sort.Search(len(h.Ranges), func(i int) bool {
|
|
return h.Ranges[i].Start > lineno
|
|
})
|
|
// Found first entry beyond lineno.
|
|
if i == 0 {
|
|
return nil
|
|
}
|
|
return h.Ranges[i-1].Stack
|
|
}
|
|
|
|
// LineString returns a string giving the file and line number
|
|
// corresponding to lineno, for use in error messages.
|
|
func (h *LineHist) LineString(lineno int) string {
|
|
stk := h.At(lineno)
|
|
if stk == nil {
|
|
return "<unknown line number>"
|
|
}
|
|
|
|
filename := stk.File
|
|
if h.PrintFilenameOnly {
|
|
filename = filepath.Base(filename)
|
|
}
|
|
text := fmt.Sprintf("%s:%d", filename, stk.fileLineAt(lineno))
|
|
if stk.Directive && stk.Parent != nil {
|
|
stk = stk.Parent
|
|
filename = stk.File
|
|
if h.PrintFilenameOnly {
|
|
filename = filepath.Base(filename)
|
|
}
|
|
text += fmt.Sprintf("[%s:%d]", filename, stk.fileLineAt(lineno))
|
|
}
|
|
const showFullStack = false // was used by old C compilers
|
|
if showFullStack {
|
|
for stk.Parent != nil {
|
|
lineno = stk.Lineno - 1
|
|
stk = stk.Parent
|
|
text += fmt.Sprintf(" %s:%d", filename, stk.fileLineAt(lineno))
|
|
if stk.Directive && stk.Parent != nil {
|
|
stk = stk.Parent
|
|
text += fmt.Sprintf("[%s:%d]", filename, stk.fileLineAt(lineno))
|
|
}
|
|
}
|
|
}
|
|
return text
|
|
}
|
|
|
|
// FileLine returns the file name and line number
|
|
// at the top of the stack for the given lineno.
|
|
func (h *LineHist) FileLine(lineno int) (file string, line int) {
|
|
stk := h.At(lineno)
|
|
if stk == nil {
|
|
return "??", 0
|
|
}
|
|
return stk.File, stk.fileLineAt(lineno)
|
|
}
|
|
|
|
// AbsFileLine returns the absolute file name and line number
|
|
// at the top of the stack for the given lineno.
|
|
func (h *LineHist) AbsFileLine(lineno int) (file string, line int) {
|
|
stk := h.At(lineno)
|
|
if stk == nil {
|
|
return "??", 0
|
|
}
|
|
return stk.AbsFile, stk.fileLineAt(lineno)
|
|
}
|
|
|
|
// This is a simplified copy of linklinefmt above.
|
|
// It doesn't allow printing the full stack, and it returns the file name and line number separately.
|
|
// TODO: Unify with linklinefmt somehow.
|
|
func linkgetline(ctxt *Link, lineno int32) (f *LSym, l int32) {
|
|
stk := ctxt.LineHist.At(int(lineno))
|
|
if stk == nil || stk.AbsFile == "" {
|
|
return Linklookup(ctxt, "??", HistVersion), 0
|
|
}
|
|
if stk.Sym == nil {
|
|
stk.Sym = Linklookup(ctxt, stk.AbsFile, HistVersion)
|
|
}
|
|
return stk.Sym, int32(stk.fileLineAt(int(lineno)))
|
|
}
|
|
|
|
func Linkprfile(ctxt *Link, line int) {
|
|
fmt.Printf("%s ", ctxt.LineHist.LineString(line))
|
|
}
|
|
|
|
func fieldtrack(ctxt *Link, cursym *LSym) {
|
|
p := cursym.Text
|
|
if p == nil || p.Link == nil { // handle external functions and ELF section symbols
|
|
return
|
|
}
|
|
ctxt.Cursym = cursym
|
|
|
|
for ; p != nil; p = p.Link {
|
|
if p.As == AUSEFIELD {
|
|
r := Addrel(ctxt.Cursym)
|
|
r.Off = 0
|
|
r.Siz = 0
|
|
r.Sym = p.From.Sym
|
|
r.Type = R_USEFIELD
|
|
}
|
|
}
|
|
}
|