Skip to content

Commit 1ba26a3

Browse files
committed
cmd/compile: fix DWARF inline debug issue with dead local vars
Fix a problem in DWARF inline debug generation relating to handling of statically unreachable local variables. For a function such as: const always = true func HasDeadLocal() int { if always { return 9 } x := new(Something) ... return x.y } the variable "x" is placed onto the Dcl list for the function during parsing, but the actual declaration node is deleted later on when gc.Main invokes "deadcode". Later in the compile the DWARF code emits an abstract function with "x" (since "x" was on the Dcl list at the point of the inline), but the export data emitted does not contain "x". This then creates clashing/inconsistant DWARF abstract function DIEs later on if HasDeadLocal is inlined in somewhere else. As a fix, the inliner now pruned away variables such as "x" when creating a copy of the Dcl list as part of the inlining; this means that both the export data generator and the DWARF emitter wind up seeing a consistent picture. Fixes #25459 Change-Id: I753dc4e9f9ec694340adba5f43c907ba8cc9badc Reviewed-on: https://go-review.googlesource.com/114090 Run-TryBot: Than McIntosh <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent 8875693 commit 1ba26a3

File tree

5 files changed

+144
-31
lines changed

5 files changed

+144
-31
lines changed

src/cmd/compile/internal/gc/inl.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,20 @@ func caninl(fn *Node) {
169169
cc = 1 // this appears to yield better performance than 0.
170170
}
171171

172-
visitor := hairyVisitor{budget: inlineMaxBudget, extraCallCost: cc}
172+
// At this point in the game the function we're looking at may
173+
// have "stale" autos, vars that still appear in the Dcl list, but
174+
// which no longer have any uses in the function body (due to
175+
// elimination by deadcode). We'd like to exclude these dead vars
176+
// when creating the "Inline.Dcl" field below; to accomplish this,
177+
// the hairyVisitor below builds up a map of used/referenced
178+
// locals, and we use this map to produce a pruned Inline.Dcl
179+
// list. See issue 25249 for more context.
180+
181+
visitor := hairyVisitor{
182+
budget: inlineMaxBudget,
183+
extraCallCost: cc,
184+
usedLocals: make(map[*Node]bool),
185+
}
173186
if visitor.visitList(fn.Nbody) {
174187
reason = visitor.reason
175188
return
@@ -181,7 +194,7 @@ func caninl(fn *Node) {
181194

182195
n.Func.Inl = &Inline{
183196
Cost: inlineMaxBudget - visitor.budget,
184-
Dcl: inlcopylist(n.Name.Defn.Func.Dcl),
197+
Dcl: inlcopylist(pruneUnusedAutos(n.Name.Defn.Func.Dcl, &visitor)),
185198
Body: inlcopylist(fn.Nbody.Slice()),
186199
}
187200

@@ -245,6 +258,7 @@ type hairyVisitor struct {
245258
budget int32
246259
reason string
247260
extraCallCost int32
261+
usedLocals map[*Node]bool
248262
}
249263

250264
// Look for anything we want to punt on.
@@ -374,6 +388,12 @@ func (v *hairyVisitor) visit(n *Node) bool {
374388
return v.visitList(n.Ninit) || v.visitList(n.Nbody) ||
375389
v.visitList(n.Rlist)
376390
}
391+
392+
case ONAME:
393+
if n.Class() == PAUTO {
394+
v.usedLocals[n] = true
395+
}
396+
377397
}
378398

379399
v.budget--
@@ -1250,3 +1270,16 @@ func (subst *inlsubst) updatedPos(xpos src.XPos) src.XPos {
12501270
pos.SetBase(newbase)
12511271
return Ctxt.PosTable.XPos(pos)
12521272
}
1273+
1274+
func pruneUnusedAutos(ll []*Node, vis *hairyVisitor) []*Node {
1275+
s := make([]*Node, 0, len(ll))
1276+
for _, n := range ll {
1277+
if n.Class() == PAUTO {
1278+
if _, found := vis.usedLocals[n]; !found {
1279+
continue
1280+
}
1281+
}
1282+
s = append(s, n)
1283+
}
1284+
return s
1285+
}

src/cmd/link/internal/ld/dwarf_test.go

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import (
2222
)
2323

2424
const (
25-
NoOpt = "-gcflags=-l -N"
26-
OptInl4 = "-gcflags=all=-l=4"
27-
OptInl4DwLoc = "-gcflags=all=-l=4 -dwarflocationlists"
25+
DefaultOpt = "-gcflags="
26+
NoOpt = "-gcflags=-l -N"
27+
OptInl4 = "-gcflags=all=-l=4"
2828
)
2929

3030
func TestRuntimeTypesPresent(t *testing.T) {
@@ -111,6 +111,38 @@ func gobuild(t *testing.T, dir string, testfile string, gcflags string) *builtFi
111111
return &builtFile{f, dst}
112112
}
113113

114+
func envWithGoPathSet(gp string) []string {
115+
env := os.Environ()
116+
for i := 0; i < len(env); i++ {
117+
if strings.HasPrefix(env[i], "GOPATH=") {
118+
env[i] = "GOPATH=" + gp
119+
return env
120+
}
121+
}
122+
env = append(env, "GOPATH="+gp)
123+
return env
124+
}
125+
126+
// Similar to gobuild() above, but runs off a separate GOPATH environment
127+
128+
func gobuildTestdata(t *testing.T, tdir string, gopathdir string, packtobuild string, gcflags string) *builtFile {
129+
dst := filepath.Join(tdir, "out.exe")
130+
131+
// Run a build with an updated GOPATH
132+
cmd := exec.Command(testenv.GoToolPath(t), "build", gcflags, "-o", dst, packtobuild)
133+
cmd.Env = envWithGoPathSet(gopathdir)
134+
if b, err := cmd.CombinedOutput(); err != nil {
135+
t.Logf("build: %s\n", b)
136+
t.Fatalf("build error: %v", err)
137+
}
138+
139+
f, err := objfilepkg.Open(dst)
140+
if err != nil {
141+
t.Fatal(err)
142+
}
143+
return &builtFile{f, dst}
144+
}
145+
114146
func TestEmbeddedStructMarker(t *testing.T) {
115147
testenv.MustHaveGoBuild(t)
116148

@@ -684,37 +716,16 @@ func main() {
684716
}
685717
}
686718

687-
func abstractOriginSanity(t *testing.T, flags string) {
688-
// Nothing special about net/http here, this is just a convenient
689-
// way to pull in a lot of code.
690-
const prog = `
691-
package main
692-
693-
import (
694-
"net/http"
695-
"net/http/httptest"
696-
)
697-
698-
type statusHandler int
719+
func abstractOriginSanity(t *testing.T, gopathdir string, flags string) {
699720

700-
func (h *statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
701-
w.WriteHeader(int(*h))
702-
}
703-
704-
func main() {
705-
status := statusHandler(http.StatusNotFound)
706-
s := httptest.NewServer(&status)
707-
defer s.Close()
708-
}
709-
`
710721
dir, err := ioutil.TempDir("", "TestAbstractOriginSanity")
711722
if err != nil {
712723
t.Fatalf("could not create directory: %v", err)
713724
}
714725
defer os.RemoveAll(dir)
715726

716727
// Build with inlining, to exercise DWARF inlining support.
717-
f := gobuild(t, dir, prog, flags)
728+
f := gobuildTestdata(t, dir, gopathdir, "main", flags)
718729

719730
d, err := f.DWARF()
720731
if err != nil {
@@ -790,10 +801,15 @@ func TestAbstractOriginSanity(t *testing.T) {
790801
t.Skip("skipping on solaris and darwin, pending resolution of issue #23168")
791802
}
792803

793-
abstractOriginSanity(t, OptInl4)
804+
if wd, err := os.Getwd(); err == nil {
805+
gopathdir := filepath.Join(wd, "testdata", "httptest")
806+
abstractOriginSanity(t, gopathdir, OptInl4)
807+
} else {
808+
t.Fatalf("os.Getwd() failed %v", err)
809+
}
794810
}
795811

796-
func TestAbstractOriginSanityWithLocationLists(t *testing.T) {
812+
func TestAbstractOriginSanityIssue25459(t *testing.T) {
797813
testenv.MustHaveGoBuild(t)
798814

799815
if runtime.GOOS == "plan9" {
@@ -806,7 +822,12 @@ func TestAbstractOriginSanityWithLocationLists(t *testing.T) {
806822
t.Skip("skipping on not-amd64 not-x86; location lists not supported")
807823
}
808824

809-
abstractOriginSanity(t, OptInl4DwLoc)
825+
if wd, err := os.Getwd(); err == nil {
826+
gopathdir := filepath.Join(wd, "testdata", "issue25459")
827+
abstractOriginSanity(t, gopathdir, DefaultOpt)
828+
} else {
829+
t.Fatalf("os.Getwd() failed %v", err)
830+
}
810831
}
811832

812833
func TestRuntimeTypeAttr(t *testing.T) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// A small test program that uses the net/http package. There is
2+
// nothing special about net/http here, this is just a convenient way
3+
// to pull in a lot of code.
4+
5+
package main
6+
7+
import (
8+
"net/http"
9+
"net/http/httptest"
10+
)
11+
12+
type statusHandler int
13+
14+
func (h *statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
15+
w.WriteHeader(int(*h))
16+
}
17+
18+
func main() {
19+
status := statusHandler(http.StatusNotFound)
20+
s := httptest.NewServer(&status)
21+
defer s.Close()
22+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package a
2+
3+
const Always = true
4+
5+
var Count int
6+
7+
type FuncReturningInt func() int
8+
9+
var PointerToConstIf FuncReturningInt
10+
11+
func ConstIf() int {
12+
if Always {
13+
return 1
14+
}
15+
var imdead [4]int
16+
imdead[Count] = 1
17+
return imdead[0]
18+
}
19+
20+
func CallConstIf() int {
21+
Count += 3
22+
return ConstIf()
23+
}
24+
25+
func Another() {
26+
defer func() { PointerToConstIf = ConstIf; Count += 1 }()
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import "a"
4+
5+
var Glob int
6+
7+
func main() {
8+
a.Another()
9+
Glob += a.ConstIf() + a.CallConstIf()
10+
}

0 commit comments

Comments
 (0)