Merge pull request #2205 from nspcc-dev/skip-unemitted-events
compiler: do not emit code for unused imported functions
This commit is contained in:
commit
af9e39ced2
6 changed files with 174 additions and 10 deletions
|
@ -241,34 +241,86 @@ func (c *codegen) fillDocumentInfo() {
|
|||
})
|
||||
}
|
||||
|
||||
// analyzeFuncUsage traverses all code and returns map with functions
|
||||
// which should be present in the emitted code.
|
||||
// This is done using BFS starting from exported functions or
|
||||
// function used in variable declarations (graph edge corresponds to
|
||||
// function being called in declaration).
|
||||
func (c *codegen) analyzeFuncUsage() funcUsage {
|
||||
usage := funcUsage{}
|
||||
type declPair struct {
|
||||
decl *ast.FuncDecl
|
||||
importMap map[string]string
|
||||
path string
|
||||
}
|
||||
|
||||
// nodeCache contains top-level function declarations .
|
||||
nodeCache := make(map[string]declPair)
|
||||
diff := funcUsage{}
|
||||
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||
var pkgPath string
|
||||
isMain := pkg == c.mainPkg.Pkg
|
||||
if !isMain {
|
||||
pkgPath = pkg.Path()
|
||||
}
|
||||
|
||||
ast.Inspect(f, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.CallExpr:
|
||||
// functions invoked in variable declarations in imported packages
|
||||
// are marked as used.
|
||||
var name string
|
||||
switch t := n.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
var pkgPath string
|
||||
if !isMain {
|
||||
pkgPath = pkg.Path()
|
||||
}
|
||||
usage[c.getIdentName(pkgPath, t.Name)] = true
|
||||
name = c.getIdentName(pkgPath, t.Name)
|
||||
case *ast.SelectorExpr:
|
||||
name, _ := c.getFuncNameFromSelector(t)
|
||||
usage[name] = true
|
||||
name, _ = c.getFuncNameFromSelector(t)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
diff[name] = true
|
||||
case *ast.FuncDecl:
|
||||
name := c.getFuncNameFromDecl(pkgPath, n)
|
||||
|
||||
// exported functions are always assumed to be used
|
||||
if isMain && n.Name.IsExported() {
|
||||
usage[c.getFuncNameFromDecl("", n)] = true
|
||||
if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) {
|
||||
diff[name] = true
|
||||
}
|
||||
nodeCache[name] = declPair{n, c.importMap, pkgPath}
|
||||
return false // will be processed in the next stage
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
usage := funcUsage{}
|
||||
for len(diff) != 0 {
|
||||
nextDiff := funcUsage{}
|
||||
for name := range diff {
|
||||
fd, ok := nodeCache[name]
|
||||
if !ok || usage[name] {
|
||||
continue
|
||||
}
|
||||
usage[name] = true
|
||||
|
||||
old := c.importMap
|
||||
c.importMap = fd.importMap
|
||||
ast.Inspect(fd.decl, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.CallExpr:
|
||||
switch t := n.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
nextDiff[c.getIdentName(fd.path, t.Name)] = true
|
||||
case *ast.SelectorExpr:
|
||||
name, _ := c.getFuncNameFromSelector(t)
|
||||
nextDiff[name] = true
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
c.importMap = old
|
||||
}
|
||||
diff = nextDiff
|
||||
}
|
||||
return usage
|
||||
}
|
||||
|
||||
|
|
|
@ -180,6 +180,40 @@ func TestEventWarnings(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("event in imported package", func(t *testing.T) {
|
||||
t.Run("unused", func(t *testing.T) {
|
||||
src := `package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/notify"
|
||||
func Main() int {
|
||||
return notify.Value
|
||||
}`
|
||||
|
||||
_, di, err := compiler.CompileWithDebugInfo("eventTest", strings.NewReader(src))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = compiler.CreateManifest(di, &compiler.Options{NoEventsCheck: true})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("used", func(t *testing.T) {
|
||||
src := `package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/notify"
|
||||
func Main() int {
|
||||
notify.EmitEvent()
|
||||
return 42
|
||||
}`
|
||||
|
||||
_, di, err := compiler.CompileWithDebugInfo("eventTest", strings.NewReader(src))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = compiler.CreateManifest(di, &compiler.Options{})
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = compiler.CreateManifest(di, &compiler.Options{
|
||||
ContractEvents: []manifest.Event{{Name: "Event"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotifyInVerify(t *testing.T) {
|
||||
|
|
|
@ -324,3 +324,30 @@ func TestFunctionUnusedParameters(t *testing.T) {
|
|||
}`
|
||||
eval(t, src, big.NewInt(101))
|
||||
}
|
||||
|
||||
func TestUnusedFunctions(t *testing.T) {
|
||||
t.Run("only variable", func(t *testing.T) {
|
||||
src := `package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/nestedcall"
|
||||
func Main() int {
|
||||
return nestedcall.X
|
||||
}`
|
||||
|
||||
b, err := compiler.Compile("foo", strings.NewReader(src))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(b)) // PUSHINT8 (42) + RET
|
||||
eval(t, src, big.NewInt(42))
|
||||
})
|
||||
t.Run("imported function", func(t *testing.T) {
|
||||
// Check that import map is set correctly during package traversal.
|
||||
src := `package foo
|
||||
import inner "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/nestedcall"
|
||||
func Main() int {
|
||||
return inner.N()
|
||||
}`
|
||||
|
||||
_, err := compiler.Compile("foo", strings.NewReader(src))
|
||||
require.NoError(t, err)
|
||||
eval(t, src, big.NewInt(65))
|
||||
})
|
||||
}
|
||||
|
|
30
pkg/compiler/testdata/nestedcall/call.go
vendored
Normal file
30
pkg/compiler/testdata/nestedcall/call.go
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
package nestedcall
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/nestedcall/inner"
|
||||
|
||||
// X is what we use.
|
||||
const X = 42
|
||||
|
||||
// N returns inner.A().
|
||||
func N() int {
|
||||
return inner.Return65()
|
||||
}
|
||||
|
||||
// F calls G.
|
||||
func F() int {
|
||||
a := 1
|
||||
return G() + a
|
||||
}
|
||||
|
||||
// G calls x and returns y().
|
||||
func G() int {
|
||||
x()
|
||||
z := 3
|
||||
return y() + z
|
||||
}
|
||||
|
||||
func x() {}
|
||||
func y() int {
|
||||
tmp := 10
|
||||
return tmp
|
||||
}
|
6
pkg/compiler/testdata/nestedcall/inner/call.go
vendored
Normal file
6
pkg/compiler/testdata/nestedcall/inner/call.go
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
package inner
|
||||
|
||||
// Return65 returns 65.
|
||||
func Return65() int {
|
||||
return 65
|
||||
}
|
15
pkg/compiler/testdata/notify/event.go
vendored
Normal file
15
pkg/compiler/testdata/notify/event.go
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
package notify
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
|
||||
// Value is the constant we use.
|
||||
const Value = 42
|
||||
|
||||
// EmitEvent emits some event.
|
||||
func EmitEvent() {
|
||||
emitPrivate()
|
||||
}
|
||||
|
||||
func emitPrivate() {
|
||||
runtime.Notify("Event")
|
||||
}
|
Loading…
Reference in a new issue