Skip to content

Commit 9416299

Browse files
committed
internal/refactor/inline: fix pkgname shadowing bug
In cross-package inlining, when replacing free names with qualified identifiers, we failed to check for intervening names defined in the callee that might cast a shadow over the reference. This change re-uses the logic for shadow detection when replacing parameters with arguments. Plus tests. Fixes golang/go#62667 Change-Id: Ib6a620ed2cde313bf51d5fb8a0cd9363f9eadf6e Reviewed-on: https://go-review.googlesource.com/c/tools/+/531455 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 4cd12d6 commit 9416299

File tree

4 files changed

+138
-38
lines changed

4 files changed

+138
-38
lines changed

internal/refactor/inline/callee.go

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ type freeRef struct {
5858

5959
// An object abstracts a free types.Object referenced by the callee. Gob-serializable.
6060
type object struct {
61-
Name string // Object.Name()
62-
Kind string // one of {var,func,const,type,pkgname,nil,builtin}
63-
PkgPath string // pkgpath of object (or of imported package if kind="pkgname")
64-
ValidPos bool // Object.Pos().IsValid()
61+
Name string // Object.Name()
62+
Kind string // one of {var,func,const,type,pkgname,nil,builtin}
63+
PkgPath string // pkgpath of object (or of imported package if kind="pkgname")
64+
ValidPos bool // Object.Pos().IsValid()
65+
Shadow map[string]bool // names shadowed at one of the object's refs
6566
}
6667

6768
// AnalyzeCallee analyzes a function that is a candidate for inlining
@@ -119,7 +120,14 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
119120
)
120121
var f func(n ast.Node) bool
121122
visit := func(n ast.Node) { ast.Inspect(n, f) }
123+
var stack []ast.Node
124+
stack = append(stack, decl.Type) // for scope of function itself
122125
f = func(n ast.Node) bool {
126+
if n != nil {
127+
stack = append(stack, n) // push
128+
} else {
129+
stack = stack[:len(stack)-1] // pop
130+
}
123131
switch n := n.(type) {
124132
case *ast.SelectorExpr:
125133
// Check selections of free fields/methods.
@@ -198,6 +206,8 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
198206
freeObjIndex[obj] = objidx
199207
}
200208

209+
freeObjs[objidx].Shadow = addShadows(freeObjs[objidx].Shadow, info, obj.Name(), stack)
210+
201211
freeRefs = append(freeRefs, freeRef{
202212
Offset: int(n.Pos() - decl.Pos()),
203213
Object: objidx,
@@ -417,6 +427,9 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
417427

418428
// Record locations of all references to parameters.
419429
// And record the set of intervening definitions for each parameter.
430+
//
431+
// TODO(adonovan): combine this traversal with the one that computes
432+
// FreeRefs. The tricky part is that calleefx needs this one first.
420433
var stack []ast.Node
421434
stack = append(stack, decl.Type) // for scope of function itself
422435
ast.Inspect(decl.Body, func(n ast.Node) bool {
@@ -429,26 +442,11 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
429442
if id, ok := n.(*ast.Ident); ok {
430443
if v, ok := info.Uses[id].(*types.Var); ok {
431444
if pinfo, ok := paramInfos[v]; ok {
432-
// Record location of ref to parameter/result.
445+
// Record location of ref to parameter/result
446+
// and any intervening (shadowing) names.
433447
offset := int(n.Pos() - decl.Pos())
434448
pinfo.Refs = append(pinfo.Refs, offset)
435-
436-
// Find set of names shadowed within body
437-
// (excluding the parameter itself).
438-
// If these names are free in the arg expression,
439-
// we can't substitute the parameter.
440-
for _, n := range stack {
441-
if scope, ok := info.Scopes[n]; ok {
442-
for _, name := range scope.Names() {
443-
if name != pinfo.Name {
444-
if pinfo.Shadow == nil {
445-
pinfo.Shadow = make(map[string]bool)
446-
}
447-
pinfo.Shadow[name] = true
448-
}
449-
}
450-
}
451-
}
449+
pinfo.Shadow = addShadows(pinfo.Shadow, info, pinfo.Name, stack)
452450
}
453451
}
454452
}
@@ -465,6 +463,29 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
465463

466464
// -- callee helpers --
467465

466+
// addShadows returns the shadows set augmented by the set of names
467+
// locally shadowed at the location of the reference in the callee
468+
// (identified by the stack). The name of the reference itself is
469+
// excluded.
470+
//
471+
// These shadowed names may not be used in a replacement expression
472+
// for the reference.
473+
func addShadows(shadows map[string]bool, info *types.Info, exclude string, stack []ast.Node) map[string]bool {
474+
for _, n := range stack {
475+
if scope, ok := info.Scopes[n]; ok {
476+
for _, name := range scope.Names() {
477+
if name != exclude {
478+
if shadows == nil {
479+
shadows = make(map[string]bool)
480+
}
481+
shadows[name] = true
482+
}
483+
}
484+
}
485+
}
486+
return shadows
487+
}
488+
468489
// deref removes a pointer type constructor from the core type of t.
469490
func deref(t types.Type) types.Type {
470491
if ptr, ok := typeparams.CoreType(t).(*types.Pointer); ok {

internal/refactor/inline/inline.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,14 +291,17 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
291291

292292
// localImportName returns the local name for a given imported package path.
293293
var newImports []*ast.ImportSpec
294-
localImportName := func(path string) string {
294+
localImportName := func(path string, shadows map[string]bool) string {
295295
// Does an import exist?
296296
for _, name := range importMap[path] {
297297
// Check that either the import preexisted,
298-
// or that it was newly added (no PkgName) but is not shadowed.
299-
found := caller.lookup(name)
300-
if is[*types.PkgName](found) || found == nil {
301-
return name
298+
// or that it was newly added (no PkgName) but is not shadowed,
299+
// either in the callee (shadows) or caller (caller.lookup).
300+
if !shadows[name] {
301+
found := caller.lookup(name)
302+
if is[*types.PkgName](found) || found == nil {
303+
return name
304+
}
302305
}
303306
}
304307

@@ -313,7 +316,7 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
313316
// use the package's declared name.
314317
base := pathpkg.Base(path)
315318
name := base
316-
for n := 0; caller.lookup(name) != nil; n++ {
319+
for n := 0; shadows[name] || caller.lookup(name) != nil; n++ {
317320
name = fmt.Sprintf("%s%d", base, n)
318321
}
319322

@@ -344,7 +347,7 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
344347
// => check not shadowed in caller.
345348
// - package-level var/func/const/types
346349
// => same package: check not shadowed in caller.
347-
// => otherwise: import other package form a qualified identifier.
350+
// => otherwise: import other package, form a qualified identifier.
348351
// (Unexported cross-package references were rejected already.)
349352
// - type parameter
350353
// => not yet supported
@@ -353,10 +356,14 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
353356
//
354357
// There can be no free references to labels, fields, or methods.
355358

359+
// Note that we must consider potential shadowing both
360+
// at the caller side (caller.lookup) and, when
361+
// choosing new PkgNames, within the callee (obj.shadow).
362+
356363
var newName ast.Expr
357364
if obj.Kind == "pkgname" {
358365
// Use locally appropriate import, creating as needed.
359-
newName = makeIdent(localImportName(obj.PkgPath)) // imported package
366+
newName = makeIdent(localImportName(obj.PkgPath, obj.Shadow)) // imported package
360367

361368
} else if !obj.ValidPos {
362369
// Built-in function, type, or value (e.g. nil, zero):
@@ -396,7 +403,7 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
396403

397404
// Form a qualified identifier, pkg.Name.
398405
if qualify {
399-
pkgName := localImportName(obj.PkgPath)
406+
pkgName := localImportName(obj.PkgPath, obj.Shadow)
400407
newName = &ast.SelectorExpr{
401408
X: makeIdent(pkgName),
402409
Sel: makeIdent(obj.Name),

internal/refactor/inline/testdata/import-shadow.txtar

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ Just because a package (e.g. log) is imported by the caller,
22
and the name log is in scope, doesn't mean the name in scope
33
refers to the package: it could be locally shadowed.
44

5-
Two scenarios below:
6-
7-
1. a second (renaming) import is added because the first import is
8-
locally shadowed.
9-
10-
2. a new import is added with a fresh name because the default
11-
name is locally shadowed.
5+
In all three scenarios below, renaming import with a fresh name is
6+
added because the usual name is locally shadowed: in cases 1, 2 an
7+
existing import is shadowed by (respectively) a local constant,
8+
parameter; in case 3 there is no existing import.
129

1310
-- go.mod --
1411
module testdata
@@ -95,3 +92,34 @@ func A(b int) {
9592
b0.Two()
9693
} //@ inline(re"F", fresult)
9794
}
95+
96+
-- d/d.go --
97+
package d
98+
99+
import "testdata/e"
100+
101+
func D() {
102+
const log = "shadow"
103+
e.E() //@ inline(re"E", eresult)
104+
}
105+
106+
-- e/e.go --
107+
package e
108+
109+
import "log"
110+
111+
func E() {
112+
log.Printf("")
113+
}
114+
115+
-- eresult --
116+
package d
117+
118+
import (
119+
log0 "log"
120+
)
121+
122+
func D() {
123+
const log = "shadow"
124+
log0.Printf("") //@ inline(re"E", eresult)
125+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
Regression test for #62667: the callee's reference to Split
2+
was blindly qualified to path.Split even though the imported
3+
PkgName path is shadowed by the parameter of the same name.
4+
5+
The defer is to defeat reduction of the call and
6+
substitution of the path parameter by g().
7+
8+
-- go.mod --
9+
module testdata
10+
go 1.12
11+
12+
-- a/a.go --
13+
package a
14+
15+
import "testdata/path"
16+
17+
func A() {
18+
path.Dir(g()) //@ inline(re"Dir", result)
19+
}
20+
21+
func g() string
22+
23+
-- path/path.go --
24+
package path
25+
26+
func Dir(path string) {
27+
defer func(){}()
28+
Split(path)
29+
}
30+
31+
func Split(string) {}
32+
33+
-- result --
34+
package a
35+
36+
import (
37+
path0 "testdata/path"
38+
)
39+
40+
func A() {
41+
func(path string) { defer func() {}(); path0.Split(path) }(g()) //@ inline(re"Dir", result)
42+
}
43+
44+
func g() string

0 commit comments

Comments
 (0)