append(inlcalls.Calls[idx].InlVars, dwv)
}
- // Post process the map above to assign child indices to vars. For
- // variables that weren't produced by an inline, sort them
- // according to class and name and assign indices that way. For
- // vars produced by an inline, assign child index by looking up
- // the var name in the origin pre-optimization dcl list for the
- // inlined function.
+ // Post process the map above to assign child indices to vars.
+ //
+ // A given variable is treated differently depending on whether it
+ // is part of the top-level function (ii == 0) or if it was
+ // produced as a result of an inline (ii != 0).
+ //
+ // If a variable was not produced by an inline and its containing
+ // function was not inlined, then we just assign an ordering of
+ // based on variable name.
+ //
+ // If a variable was not produced by an inline and its containing
+ // function was inlined, then we need to assign a child index
+ // based on the order of vars in the abstract function (in
+ // addition, those vars that don't appear in the abstract
+ // function, such as "~r1", are flagged as such).
+ //
+ // If a variable was produced by an inline, then we locate it in
+ // the pre-inlining decls for the target function and assign child
+ // index accordingly.
for ii, sl := range vmap {
+ sort.Sort(byClassThenName(sl))
+ var m map[varPos]int
if ii == 0 {
- sort.Sort(byClassThenName(sl))
- for j := 0; j < len(sl); j++ {
- sl[j].ChildIndex = int32(j)
+ if !fnsym.WasInlined() {
+ for j := 0; j < len(sl); j++ {
+ sl[j].ChildIndex = int32(j)
+ }
+ continue
}
+ m = makePreinlineDclMap(fnsym)
} else {
- // Assign child index based on pre-inlined decls
ifnlsym := Ctxt.InlTree.InlinedFunction(int(ii - 1))
- dcl, _ := preInliningDcls(ifnlsym)
- m := make(map[varPos]int)
- for i := 0; i < len(dcl); i++ {
- n := dcl[i]
- pos := Ctxt.InnermostPos(n.Pos)
- vp := varPos{
- DeclName: n.Sym.Name,
- DeclFile: pos.Base().SymFilename(),
- DeclLine: pos.Line(),
- DeclCol: pos.Col(),
- }
- if _, found := m[vp]; found {
- Fatalf("child dcl collision on symbol %s within %v\n", n.Sym.Name, fnsym.Name)
- }
- m[vp] = i
+ m = makePreinlineDclMap(ifnlsym)
+ }
+
+ // Here we assign child indices to variables based on
+ // pre-inlined decls, and set the "IsInAbstract" flag
+ // appropriately. In addition: parameter and local variable
+ // names are given "middle dot" version numbers as part of the
+ // writing them out to export data (see issue 4326). If DWARF
+ // inlined routine generation is turned on, we want to undo
+ // this versioning, since DWARF variables in question will be
+ // parented by the inlined routine and not the top-level
+ // caller.
+ synthCount := len(m)
+ for j := 0; j < len(sl); j++ {
+ canonName := unversion(sl[j].Name)
+ vp := varPos{
+ DeclName: canonName,
+ DeclFile: sl[j].DeclFile,
+ DeclLine: sl[j].DeclLine,
+ DeclCol: sl[j].DeclCol,
}
- for j := 0; j < len(sl); j++ {
- vp := varPos{
- DeclName: sl[j].Name,
- DeclFile: sl[j].DeclFile,
- DeclLine: sl[j].DeclLine,
- DeclCol: sl[j].DeclCol,
- }
- if idx, found := m[vp]; found {
- sl[j].ChildIndex = int32(idx)
- } else {
+ returnTmp := strings.HasPrefix(sl[j].Name, "~r")
+ if idx, found := m[vp]; found {
+ sl[j].ChildIndex = int32(idx)
+ sl[j].IsInAbstract = !returnTmp
+ sl[j].Name = canonName
+ } else {
+ // Variable can't be found in the pre-inline dcl list.
+ // In the top-level case (ii=0) this can happen
+ // because a composite variable was split into pieces,
+ // and we're looking at a piece. We can also see
+ // return temps (~r%d) that were created during
+ // lowering.
+ if ii != 0 && !returnTmp {
Fatalf("unexpected: can't find var %s in preInliningDcls for %v\n", sl[j].Name, Ctxt.InlTree.InlinedFunction(int(ii-1)))
}
+ sl[j].ChildIndex = int32(synthCount)
+ synthCount += 1
}
}
}
- // Make a second pass through the progs to compute PC ranges
- // for the various inlined calls.
+ // Make a second pass through the progs to compute PC ranges for
+ // the various inlined calls.
curii := -1
var crange *dwarf.Range
var prevp *obj.Prog
Ctxt.DwarfAbstractFunc(ifn, fn, myimportpath)
}
+// Undo any versioning performed when a name was written
+// out as part of export data.
+func unversion(name string) string {
+ if i := strings.Index(name, "ยท"); i > 0 {
+ name = name[:i]
+ }
+ return name
+}
+
+// Given a function that was inlined as part of the compilation, dig
+// up the pre-inlining DCL list for the function and create a map that
+// supports lookup of pre-inline dcl index, based on variable
+// position/name.
+func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int {
+ dcl := preInliningDcls(fnsym)
+ m := make(map[varPos]int)
+ for i := 0; i < len(dcl); i++ {
+ n := dcl[i]
+ pos := Ctxt.InnermostPos(n.Pos)
+ vp := varPos{
+ DeclName: unversion(n.Sym.Name),
+ DeclFile: pos.Base().SymFilename(),
+ DeclLine: pos.Line(),
+ DeclCol: pos.Col(),
+ }
+ if _, found := m[vp]; found {
+ Fatalf("child dcl collision on symbol %s within %v\n", n.Sym.Name, fnsym.Name)
+ }
+ m[vp] = i
+ }
+ return m
+}
+
func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
callIdx, found := imap[inlIdx]
if found {
if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM {
typ = "param"
}
- Ctxt.Logf("V%d: %s CI:%d II:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, typ)
+ ia := 0
+ if dwv.IsInAbstract {
+ ia = 1
+ }
+ Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ)
}
}
"cmd/internal/obj"
"cmd/internal/src"
"fmt"
- "sort"
"strings"
)
if genDwarfInline > 0 {
// Don't update the src.Pos on a return variable if it
- // was manufactured by the inliner (e.g. "~r2"); such vars
+ // was manufactured by the inliner (e.g. "~R2"); such vars
// were not part of the original callee.
- if !strings.HasPrefix(m.Sym.Name, "~r") {
+ if !strings.HasPrefix(m.Sym.Name, "~R") {
m.SetInlFormal(true)
m.Pos = mpos
inlfvars = append(inlfvars, m)
if b := Ctxt.PosTable.Pos(n.Pos).Base(); b != nil {
parent = b.InliningIndex()
}
- sort.Sort(byNodeName(dcl))
newIndex := Ctxt.InlTree.Add(parent, n.Pos, fn.Sym.Linksym())
if genDwarfInline > 0 {
// Synthesize a variable to store the inlined function's results in.
func retvar(t *types.Field, i int) *Node {
- n := newname(lookupN("~r", i))
+ n := newname(lookupN("~R", i))
n.Type = t.Type
n.SetClass(PAUTO)
n.Name.SetUsed(true)
pos.SetBase(newbase)
return Ctxt.PosTable.XPos(pos)
}
-
-func cmpNodeName(a, b *Node) bool {
- // named before artificial
- aart := 0
- if strings.HasPrefix(a.Sym.Name, "~r") {
- aart = 1
- }
- bart := 0
- if strings.HasPrefix(b.Sym.Name, "~r") {
- bart = 1
- }
- if aart != bart {
- return aart < bart
- }
-
- // otherwise sort by name
- return a.Sym.Name < b.Sym.Name
-}
-
-// byNodeName implements sort.Interface for []*Node using cmpNodeName.
-type byNodeName []*Node
-
-func (s byNodeName) Len() int { return len(s) }
-func (s byNodeName) Less(i, j int) bool { return cmpNodeName(s[i], s[j]) }
-func (s byNodeName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
if genDwarfInline > 1 {
if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos) + 1
+ if n.InlFormal() {
+ abbrev = dwarf.DW_ABRV_PARAM
+ }
}
}
declpos := Ctxt.InnermostPos(n.Pos)
}
var dcl []*Node
- var chopVersion bool
if fnsym.WasInlined() {
- dcl, chopVersion = preInliningDcls(fnsym)
+ dcl = preInliningDcls(fnsym)
} else {
dcl = automDecls
}
continue
}
c := n.Sym.Name[0]
- if c == '~' || c == '.' || n.Type.IsUntyped() {
+ if c == '.' || n.Type.IsUntyped() {
continue
}
typename := dwarf.InfoPrefix + typesymname(n.Type)
if genDwarfInline > 1 {
if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos) + 1
+ if n.InlFormal() {
+ abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+ }
}
}
declpos := Ctxt.InnermostPos(n.Pos)
}
- // Parameter and local variable names are given middle dot
- // version numbers as part of the writing them out to export
- // data (see issue 4326). If DWARF inlined routine generation
- // is turned on, undo this versioning, since DWARF variables
- // in question will be parented by the inlined routine and
- // not the top-level caller.
- if genDwarfInline > 1 && chopVersion {
- for _, v := range vars {
- if v.InlIndex != -1 {
- if i := strings.Index(v.Name, "ยท"); i > 0 {
- v.Name = v.Name[:i] // cut off Vargen
- }
- }
- }
- }
-
return decls, vars
}
-// Given a function that was inlined at some point during the compilation,
-// return a list of nodes corresponding to the autos/locals in that
-// function prior to inlining. Untyped and compiler-synthesized vars are
-// stripped out along the way.
-func preInliningDcls(fnsym *obj.LSym) ([]*Node, bool) {
+// Given a function that was inlined at some point during the
+// compilation, return a sorted list of nodes corresponding to the
+// autos/locals in that function prior to inlining. If this is a
+// function that is not local to the package being compiled, then the
+// names of the variables may have been "versioned" to avoid conflicts
+// with local vars; disregard this versioning when sorting.
+func preInliningDcls(fnsym *obj.LSym) []*Node {
fn := Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*Node)
- imported := false
var dcl, rdcl []*Node
if fn.Name.Defn != nil {
dcl = fn.Func.Inldcl.Slice() // local function
} else {
dcl = fn.Func.Dcl // imported function
- imported = true
}
for _, n := range dcl {
c := n.Sym.Name[0]
- if c == '~' || c == '.' || n.Type.IsUntyped() {
+ if c == '.' || n.Type.IsUntyped() {
continue
}
rdcl = append(rdcl, n)
}
- return rdcl, imported
+ sort.Sort(byNodeName(rdcl))
+ return rdcl
+}
+
+func cmpNodeName(a, b *Node) bool {
+ aart := 0
+ if strings.HasPrefix(a.Sym.Name, "~") {
+ aart = 1
+ }
+ bart := 0
+ if strings.HasPrefix(b.Sym.Name, "~") {
+ bart = 1
+ }
+ if aart != bart {
+ return aart < bart
+ }
+
+ aname := unversion(a.Sym.Name)
+ bname := unversion(b.Sym.Name)
+ return aname < bname
}
+// byNodeName implements sort.Interface for []*Node using cmpNodeName.
+type byNodeName []*Node
+
+func (s byNodeName) Len() int { return len(s) }
+func (s byNodeName) Less(i, j int) bool { return cmpNodeName(s[i], s[j]) }
+func (s byNodeName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
// varOffset returns the offset of slot within the user variable it was
// decomposed from. This has nothing to do with its stack offset.
func varOffset(slot *ssa.LocalSlot) int64 {
if genDwarfInline > 1 {
if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos) + 1
+ if n.InlFormal() {
+ abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+ }
}
}
declpos := Ctxt.InnermostPos(n.Pos)
DeclCol uint
InlIndex int32 // subtract 1 to form real index into InlTree
ChildIndex int32 // child DIE index in abstract function
+ IsInAbstract bool // variable exists in abstract function
}
// A Scope represents a lexical scope. All variables declared within a
for _, scope := range s.Scopes {
for i := 0; i < len(scope.Vars); i++ {
_, found := pvars[scope.Vars[i]]
- if !found {
- flattened = append(flattened, scope.Vars[i])
+ if found || !scope.Vars[i].IsInAbstract {
+ continue
}
+ flattened = append(flattened, scope.Vars[i])
}
}
if len(flattened) > 0 {
sort.Sort(byChildIndex(flattened))
+ if logDwarf {
+ ctxt.Logf("putAbstractScope(%v): vars:", s.Info)
+ for i, v := range flattened {
+ ctxt.Logf(" %d:%s", i, v.Name)
+ }
+ ctxt.Logf("\n")
+ }
+
// This slice will hold the offset in bytes for each child
// variable DIE with respect to the start of the parent
// subprogram DIE.
inlIndex := ic.InlIndex
var encbuf [20]byte
for _, v := range vars {
+ if !v.IsInAbstract {
+ continue
+ }
putvar(ctxt, s, v, callee, abbrev, inlIndex, encbuf[:0])
}
return curscope
}
+// Given a default var abbrev code, select corresponding concrete code.
+func concreteVarAbbrev(varAbbrev int) int {
+ switch varAbbrev {
+ case DW_ABRV_AUTO:
+ return DW_ABRV_AUTO_CONCRETE
+ case DW_ABRV_PARAM:
+ return DW_ABRV_PARAM_CONCRETE
+ case DW_ABRV_AUTO_LOCLIST:
+ return DW_ABRV_AUTO_CONCRETE_LOCLIST
+ case DW_ABRV_PARAM_LOCLIST:
+ return DW_ABRV_PARAM_CONCRETE_LOCLIST
+ default:
+ panic("should never happen")
+ }
+}
+
// Pick the correct abbrev code for variable or parameter DIE.
func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
abbrev := v.Abbrev
abbrev = DW_ABRV_PARAM
}
+ // Determine whether to use a concrete variable or regular variable DIE.
concrete := true
switch fnabbrev {
case DW_ABRV_FUNCTION:
concrete = false
break
- case DW_ABRV_FUNCTION_CONCRETE, DW_ABRV_INLINED_SUBROUTINE, DW_ABRV_INLINED_SUBROUTINE_RANGES:
- switch abbrev {
- case DW_ABRV_AUTO:
- if v.IsInlFormal {
- abbrev = DW_ABRV_PARAM_CONCRETE
- } else {
- abbrev = DW_ABRV_AUTO_CONCRETE
- }
- concrete = true
- case DW_ABRV_AUTO_LOCLIST:
- if v.IsInlFormal {
- abbrev = DW_ABRV_PARAM_CONCRETE_LOCLIST
- } else {
- abbrev = DW_ABRV_AUTO_CONCRETE_LOCLIST
- }
- case DW_ABRV_PARAM:
- abbrev = DW_ABRV_PARAM_CONCRETE
- case DW_ABRV_PARAM_LOCLIST:
- abbrev = DW_ABRV_PARAM_CONCRETE_LOCLIST
+ case DW_ABRV_FUNCTION_CONCRETE:
+ // If we're emitting a concrete subprogram DIE and the variable
+ // in question is not part of the corresponding abstract function DIE,
+ // then use the default (non-concrete) abbrev for this param.
+ if !v.IsInAbstract {
+ concrete = false
}
+ case DW_ABRV_INLINED_SUBROUTINE, DW_ABRV_INLINED_SUBROUTINE_RANGES:
default:
panic("should never happen")
}
+ // Select proper abbrev based on concrete/non-concrete
+ if concrete {
+ abbrev = concreteVarAbbrev(abbrev)
+ }
+
return abbrev, missing, concrete
}
"testing"
)
+const (
+ NoOpt = "-gcflags=-l -N"
+ Opt = ""
+ OptInl4 = "-gcflags=all=-l=4"
+ OptInl4DwLoc = "-gcflags=all=-l=4 -dwarflocationlists"
+)
+
func TestRuntimeTypeDIEs(t *testing.T) {
testenv.MustHaveGoBuild(t)
}
defer os.RemoveAll(dir)
- f := gobuild(t, dir, `package main; func main() { }`, false)
+ f := gobuild(t, dir, `package main; func main() { }`, NoOpt)
defer f.Close()
dwarf, err := f.DWARF()
return
}
-func gobuild(t *testing.T, dir string, testfile string, opt bool) *objfilepkg.File {
+func gobuild(t *testing.T, dir string, testfile string, gcflags string) *objfilepkg.File {
src := filepath.Join(dir, "test.go")
dst := filepath.Join(dir, "out")
t.Fatal(err)
}
- gcflags := "-gcflags=-N -l"
- if opt {
- gcflags = "-gcflags=-l=4"
- }
cmd := exec.Command(testenv.GoToolPath(t), "build", gcflags, "-o", dst, src)
if b, err := cmd.CombinedOutput(); err != nil {
t.Logf("build: %s\n", b)
}
defer os.RemoveAll(dir)
- f := gobuild(t, dir, prog, false)
+ f := gobuild(t, dir, prog, NoOpt)
defer f.Close()
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
- f := gobuild(t, dir, prog, false)
+ f := gobuild(t, dir, prog, NoOpt)
defer f.Close()
d, err := f.DWARF()
if err != nil {
}
defer os.RemoveAll(dir)
- f := gobuild(t, dir, prog, false)
+ f := gobuild(t, dir, prog, NoOpt)
defer f.Close()
d, err := f.DWARF()
}
defer os.RemoveAll(dir)
- f := gobuild(t, dir, prog, false)
+ f := gobuild(t, dir, prog, NoOpt)
d, err := f.DWARF()
if err != nil {
dies []*dwarf.Entry
idxByOffset map[dwarf.Offset]int
kids map[int][]int
+ parent map[int]int
byname map[string][]int
}
func (ex *examiner) populate(rdr *dwarf.Reader) error {
ex.idxByOffset = make(map[dwarf.Offset]int)
ex.kids = make(map[int][]int)
+ ex.parent = make(map[int]int)
ex.byname = make(map[string][]int)
var nesting []int
for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
if len(nesting) > 0 {
parent := nesting[len(nesting)-1]
ex.kids[parent] = append(ex.kids[parent], idx)
+ ex.parent[idx] = parent
}
if entry.Children {
nesting = append(nesting, idx)
}
entry := ex.dies[idx]
indent(ilevel)
- fmt.Printf("%d: %v\n", idx, entry.Tag)
+ fmt.Printf("0x%x: %v\n", idx, entry.Tag)
for _, f := range entry.Field {
indent(ilevel)
- fmt.Printf("at=%v val=%v\n", f.Attr, f.Val)
+ fmt.Printf("at=%v val=0x%x\n", f.Attr, f.Val)
}
if dumpKids {
ksl := ex.kids[idx]
// Return the dwarf.Entry pointer for the DIE with id 'idx'
func (ex *examiner) entryFromIdx(idx int) *dwarf.Entry {
- if idx >= len(ex.dies) {
+ if idx >= len(ex.dies) || idx < 0 {
return nil
}
return ex.dies[idx]
return ret
}
+// Returns parent DIE for DIE 'idx', or nil if the DIE is top level
+func (ex *examiner) Parent(idx int) *dwarf.Entry {
+ p, found := ex.parent[idx]
+ if !found {
+ return nil
+ }
+ return ex.entryFromIdx(p)
+}
+
// Return a list of all DIEs with name 'name'. When searching for DIEs
// by name, keep in mind that the returned results will include child
// DIEs such as params/variables. For example, asking for all DIEs named
}
defer os.RemoveAll(dir)
- // Note: this is a regular go build here, without "-l -N". The
- // test is intended to verify DWARF that is only generated when the
- // inliner is active.
- f := gobuild(t, dir, prog, true)
+ // Note: this is a build with "-l=4", as opposed to "-l -N". The
+ // test is intended to verify DWARF that is only generated when
+ // the inliner is active.
+ f := gobuild(t, dir, prog, OptInl4)
d, err := f.DWARF()
if err != nil {
t.Fatalf("not enough inlined subroutines found in main.main")
}
}
+
+func abstractOriginSanity(t *testing.T, flags string) {
+
+ // Nothing special about net/http here, this is just a convenient
+ // way to pull in a lot of code.
+ const prog = `
+package main
+
+import (
+ "net/http"
+ "net/http/httptest"
+)
+
+type statusHandler int
+
+func (h *statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(int(*h))
+}
+
+func main() {
+ status := statusHandler(http.StatusNotFound)
+ s := httptest.NewServer(&status)
+ defer s.Close()
+}
+`
+ dir, err := ioutil.TempDir("", "TestAbstractOriginSanity")
+ if err != nil {
+ t.Fatalf("could not create directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ // Build with inlining, to exercise DWARF inlining support.
+ f := gobuild(t, dir, prog, flags)
+
+ d, err := f.DWARF()
+ if err != nil {
+ t.Fatalf("error reading DWARF: %v", err)
+ }
+ rdr := d.Reader()
+ ex := examiner{}
+ if err := ex.populate(rdr); err != nil {
+ t.Fatalf("error reading DWARF: %v", err)
+ }
+
+ // Make a pass through all DIEs looking for abstract origin
+ // references.
+ abscount := 0
+ for i, die := range ex.dies {
+
+ // Does it have an abstract origin?
+ ooff, originOK := die.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
+ if !originOK {
+ continue
+ }
+
+ // All abstract origin references should be resolvable.
+ abscount += 1
+ originDIE := ex.entryFromOffset(ooff)
+ if originDIE == nil {
+ ex.dumpEntry(i, false, 0)
+ t.Fatalf("unresolved abstract origin ref in DIE at offset 0x%x\n", die.Offset)
+ }
+
+ // Suppose that DIE X has parameter/variable children {K1,
+ // K2, ... KN}. If X has an abstract origin of A, then for
+ // each KJ, the abstract origin of KJ should be a child of A.
+ // Note that this same rule doesn't hold for non-variable DIEs.
+ pidx := ex.idxFromOffset(die.Offset)
+ if pidx < 0 {
+ t.Fatalf("can't locate DIE id")
+ }
+ kids := ex.Children(pidx)
+ for _, kid := range kids {
+ if kid.Tag != dwarf.TagVariable &&
+ kid.Tag != dwarf.TagFormalParameter {
+ continue
+ }
+ kooff, originOK := kid.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
+ if !originOK {
+ continue
+ }
+ childOriginDIE := ex.entryFromOffset(kooff)
+ if childOriginDIE == nil {
+ ex.dumpEntry(i, false, 0)
+ t.Fatalf("unresolved abstract origin ref in DIE at offset %x", kid.Offset)
+ }
+ coidx := ex.idxFromOffset(childOriginDIE.Offset)
+ childOriginParent := ex.Parent(coidx)
+ if childOriginParent != originDIE {
+ ex.dumpEntry(i, false, 0)
+ t.Fatalf("unexpected parent of abstract origin DIE at offset %v", childOriginDIE.Offset)
+ }
+ }
+ }
+ if abscount == 0 {
+ t.Fatalf("no abstract origin refs found, something is wrong")
+ }
+}
+
+func TestAbstractOriginSanity(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping on plan9; no DWARF symbol table in executables")
+ }
+
+ abstractOriginSanity(t, OptInl4)
+}
+
+func TestAbstractOriginSanityWithLocationLists(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping on plan9; no DWARF symbol table in executables")
+ }
+
+ abstractOriginSanity(t, OptInl4DwLoc)
+}