forked from TrueCloudLab/neoneo-go
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 {
|
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) {
|
c.ForEachFile(func(f *ast.File, pkg *types.Package) {
|
||||||
isMain := pkg == c.mainPkg.Pkg
|
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
|
||||||
switch n := node.(type) {
|
|
||||||
case *ast.CallExpr:
|
|
||||||
switch t := n.Fun.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
var pkgPath string
|
var pkgPath string
|
||||||
|
isMain := pkg == c.mainPkg.Pkg
|
||||||
if !isMain {
|
if !isMain {
|
||||||
pkgPath = pkg.Path()
|
pkgPath = pkg.Path()
|
||||||
}
|
}
|
||||||
usage[c.getIdentName(pkgPath, t.Name)] = true
|
|
||||||
|
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:
|
||||||
|
name = c.getIdentName(pkgPath, t.Name)
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
name, _ := c.getFuncNameFromSelector(t)
|
name, _ = c.getFuncNameFromSelector(t)
|
||||||
usage[name] = true
|
default:
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
diff[name] = true
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
|
name := c.getFuncNameFromDecl(pkgPath, n)
|
||||||
|
|
||||||
// exported functions are always assumed to be used
|
// exported functions are always assumed to be used
|
||||||
if isMain && n.Name.IsExported() {
|
if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) {
|
||||||
usage[c.getFuncNameFromDecl("", n)] = true
|
diff[name] = true
|
||||||
}
|
}
|
||||||
|
nodeCache[name] = declPair{n, c.importMap, pkgPath}
|
||||||
|
return false // will be processed in the next stage
|
||||||
}
|
}
|
||||||
return true
|
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
|
return usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,40 @@ func TestEventWarnings(t *testing.T) {
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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) {
|
func TestNotifyInVerify(t *testing.T) {
|
||||||
|
|
|
@ -324,3 +324,30 @@ func TestFunctionUnusedParameters(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
eval(t, src, big.NewInt(101))
|
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