Merge pull request #2205 from nspcc-dev/skip-unemitted-events

compiler: do not emit code for unused imported functions
This commit is contained in:
Roman Khimov 2021-10-14 10:03:28 +03:00 committed by GitHub
commit af9e39ced2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 174 additions and 10 deletions

View file

@ -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
}

View file

@ -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) {

View file

@ -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))
})
}

View 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
}

View 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
View 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")
}