From 439d9ff94db84bd1297dbc934131e91d27c336be Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Aug 2020 11:14:43 +0300 Subject: [PATCH] compiler: initialize packages according to go spec `init()` functions should be called during package initialization, after global variables were processed. --- pkg/compiler/analysis.go | 17 +++++++++++++++-- pkg/compiler/compiler.go | 14 +++++++++++--- pkg/compiler/init_test.go | 7 +++++++ pkg/compiler/testdata/pkg3/pkg3.go | 4 ++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index febb3a5e3..b15e81513 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "golang.org/x/tools/go/loader" ) var ( @@ -47,8 +48,20 @@ func (c *codegen) traverseGlobals() int { return 0 } emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)}) - c.ForEachFile(c.convertGlobals) - c.ForEachFile(c.convertInitFuncs) + c.ForEachPackage(func(pkg *loader.PackageInfo) { + for _, f := range pkg.Files { + c.fillImportMap(f, pkg.Pkg) + c.convertGlobals(f, pkg.Pkg) + } + for _, f := range pkg.Files { + c.fillImportMap(f, pkg.Pkg) + c.convertInitFuncs(f, pkg.Pkg) + } + // because we reuse `convertFuncDecl` for init funcs, + // we need to cleare scope, so that global variables + // encountered after will be recognized as globals. + c.scope = nil + }) } return n } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 95b952914..aaefbc039 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -47,17 +47,25 @@ type buildInfo struct { program *loader.Program } -// ForEachFile executes fn on each file used in current program. -func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { +// ForEachPackage executes fn on each package used in the current program +// in the order they should be initialized. +func (c *codegen) ForEachPackage(fn func(*loader.PackageInfo)) { for i := range c.packages { pkg := c.buildInfo.program.Package(c.packages[i]) c.typeInfo = &pkg.Info c.currPkg = pkg.Pkg + fn(pkg) + } +} + +// ForEachFile executes fn on each file used in current program. +func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { + c.ForEachPackage(func(pkg *loader.PackageInfo) { for _, f := range pkg.Files { c.fillImportMap(f, pkg.Pkg) fn(f, pkg.Pkg) } - } + }) } // fillImportMap fills import map for f. diff --git a/pkg/compiler/init_test.go b/pkg/compiler/init_test.go index 31c8c98b8..4327855e2 100644 --- a/pkg/compiler/init_test.go +++ b/pkg/compiler/init_test.go @@ -80,4 +80,11 @@ func TestImportOrder(t *testing.T) { func Main() int { return pkg3.A }` eval(t, src, big.NewInt(1)) }) + t.Run("InitializeOnce", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/pkg3" + var A = pkg3.A + func Main() int { return A }` + eval(t, src, big.NewInt(3)) + }) } diff --git a/pkg/compiler/testdata/pkg3/pkg3.go b/pkg/compiler/testdata/pkg3/pkg3.go index 077019e0c..bf7c49a9e 100644 --- a/pkg/compiler/testdata/pkg3/pkg3.go +++ b/pkg/compiler/testdata/pkg3/pkg3.go @@ -1,3 +1,7 @@ package pkg3 var A int + +func init() { + A = 3 +}