From 6df019913dfdb5d24debdb339c77f1159392e86d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jul 2020 19:35:41 +0300 Subject: [PATCH] compiler: allow to use exported variables Signed-off-by: Evgenii Stratonikov --- pkg/compiler/analysis.go | 11 ++++- pkg/compiler/codegen.go | 63 ++++++++++++++++--------- pkg/compiler/compiler.go | 18 +++++++ pkg/compiler/global_test.go | 51 ++++++++++++++++++++ pkg/compiler/testdata/strange/normal.go | 4 ++ 5 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 pkg/compiler/testdata/strange/normal.go diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index f8fabb3a9..4a0821267 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -21,10 +21,19 @@ var ( ) // newGlobal creates new global variable. -func (c *codegen) newGlobal(name string) { +func (c *codegen) newGlobal(pkg string, name string) { + name = c.getIdentName(pkg, name) c.globals[name] = len(c.globals) } +// getIdentName returns fully-qualified name for a variable. +func (c *codegen) getIdentName(pkg string, name string) string { + if fullName, ok := c.importMap[pkg]; ok { + pkg = fullName + } + return pkg + "." + name +} + // traverseGlobals visits and initializes global variables. // and returns number of variables initialized. func (c *codegen) traverseGlobals() int { diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 2486a67c9..15163c05f 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -62,6 +62,9 @@ type codegen struct { // initEndOffset specifies the end of the initialization method. initEndOffset int + // importMap contains mapping from package aliases to full package names for the current file. + importMap map[string]string + // mainPkg is a main package metadata. mainPkg *loader.PackageInfo @@ -167,14 +170,16 @@ func (c *codegen) emitStoreStructField(i int) { // getVarIndex returns variable type and position in corresponding slot, // according to current scope. -func (c *codegen) getVarIndex(name string) (varType, int) { - if c.scope != nil { - vt, val := c.scope.vars.getVarIndex(name) - if val >= 0 { - return vt, val +func (c *codegen) getVarIndex(pkg string, name string) (varType, int) { + if pkg == "" { + if c.scope != nil { + vt, val := c.scope.vars.getVarIndex(name) + if val >= 0 { + return vt, val + } } } - if i, ok := c.globals[name]; ok { + if i, ok := c.globals[c.getIdentName(pkg, name)]; ok { return varGlobal, i } @@ -195,8 +200,8 @@ func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) { } // emitLoadVar loads specified variable to the evaluation stack. -func (c *codegen) emitLoadVar(name string) { - t, i := c.getVarIndex(name) +func (c *codegen) emitLoadVar(pkg string, name string) { + t, i := c.getVarIndex(pkg, name) base, _ := getBaseOpcode(t) if i < 7 { emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i)) @@ -206,12 +211,12 @@ func (c *codegen) emitLoadVar(name string) { } // emitStoreVar stores top value from the evaluation stack in the specified variable. -func (c *codegen) emitStoreVar(name string) { +func (c *codegen) emitStoreVar(pkg string, name string) { if name == "_" { emit.Opcode(c.prog.BinWriter, opcode.DROP) return } - t, i := c.getVarIndex(name) + t, i := c.getVarIndex(pkg, name) _, base := getBaseOpcode(t) if i < 7 { emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i)) @@ -368,7 +373,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for _, id := range t.Names { if c.scope == nil { // it is a global declaration - c.newGlobal(id.Name) + c.newGlobal("", id.Name) } else { c.scope.newLocal(id.Name) } @@ -380,7 +385,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else { c.emitDefault(c.typeOf(t.Type)) } - c.emitStoreVar(t.Names[i].Name) + c.emitStoreVar("", t.Names[i].Name) } } } @@ -411,13 +416,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if !isAssignOp && (i == 0 || !multiRet) { ast.Walk(c, n.Rhs[i]) } - c.emitStoreVar(t.Name) + c.emitStoreVar("", t.Name) case *ast.SelectorExpr: if !isAssignOp { ast.Walk(c, n.Rhs[i]) } - strct, ok := c.typeOf(t.X).Underlying().(*types.Struct) + typ := c.typeOf(t.X) + if typ == nil { + // Store to other package global variable. + c.emitStoreVar(t.X.(*ast.Ident).Name, t.Sel.Name) + return nil + } + strct, ok := typ.Underlying().(*types.Struct) if !ok { c.prog.Err = fmt.Errorf("nested selector assigns not supported yet") return nil @@ -442,7 +453,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.SliceExpr: name := n.X.(*ast.Ident).Name - c.emitLoadVar(name) + c.emitLoadVar("", name) if n.Low != nil { ast.Walk(c, n.Low) @@ -480,7 +491,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for i := len(results.List) - 1; i >= 0; i-- { names := results.List[i].Names for j := len(names) - 1; j >= 0; j-- { - c.emitLoadVar(names[j].Name) + c.emitLoadVar("", names[j].Name) } } } @@ -595,7 +606,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else if n.Name == "nil" { emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL) } else { - c.emitLoadVar(n.Name) + c.emitLoadVar("", n.Name) } return nil @@ -789,7 +800,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if isString(c.typeOf(n.Fun)) { c.emitConvert(stackitem.ByteArrayT) } else if isFunc { - c.emitLoadVar(name) + c.emitLoadVar("", name) emit.Opcode(c.prog.BinWriter, opcode.CALLA) } case isSyscall(f): @@ -801,7 +812,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil case *ast.SelectorExpr: - strct, ok := c.typeOf(n.X).Underlying().(*types.Struct) + typ := c.typeOf(n.X) + if typ == nil { + // This is a global variable from a package. + pkgAlias := n.X.(*ast.Ident).Name + pkgPath := c.importMap[pkgAlias] + c.emitLoadVar(pkgPath, n.Sel.Name) + return nil + } + strct, ok := typ.Underlying().(*types.Struct) if !ok { c.prog.Err = fmt.Errorf("selectors are supported only on structs") return nil @@ -840,7 +859,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // for i := 0; i < 10; i++ {} // Where the post stmt is ( i++ ) if ident, ok := n.X.(*ast.Ident); ok { - c.emitStoreVar(ident.Name) + c.emitStoreVar("", ident.Name) } return nil @@ -992,7 +1011,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } else { emit.Opcode(c.prog.BinWriter, opcode.DUP) } - c.emitStoreVar(n.Key.(*ast.Ident).Name) + c.emitStoreVar("", n.Key.(*ast.Ident).Name) } if needValue { if !isMap || !keyLoaded { @@ -1005,7 +1024,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) } - c.emitStoreVar(n.Value.(*ast.Ident).Name) + c.emitStoreVar("", n.Value.(*ast.Ident).Name) } ast.Walk(c, n.Body) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 5703f8901..0a19a0f5a 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -49,11 +49,29 @@ func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) { for _, pkg := range c.buildInfo.program.AllPackages { c.typeInfo = &pkg.Info for _, f := range pkg.Files { + c.fillImportMap(f, pkg.Pkg) fn(f, pkg.Pkg) } } } +// fillImportMap fills import map for f. +func (c *codegen) fillImportMap(f *ast.File, pkg *types.Package) { + c.importMap = map[string]string{"": pkg.Path()} + for _, imp := range f.Imports { + // We need to load find package metadata because + // name specified in `package ...` decl, can be in + // conflict with package path. + pkgPath := strings.Trim(imp.Path.Value, `"`) + realPkg := c.buildInfo.program.Package(pkgPath) + name := realPkg.Pkg.Name() + if imp.Name != nil { + name = imp.Name.Name + } + c.importMap[name] = realPkg.Pkg.Path() + } +} + func getBuildInfo(src interface{}) (*buildInfo, error) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("", src) diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index e49d516d2..076555188 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -136,3 +136,54 @@ func TestMultipleFiles(t *testing.T) { }` eval(t, src, big.NewInt(42)) } + +func TestExportedVariable(t *testing.T) { + t.Run("Use", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi" + func Main() int { + return multi.SomeVar12 + }` + eval(t, src, big.NewInt(12)) + }) + t.Run("ChangeAndUse", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi" + func Main() int { + multi.SomeVar12 = 10 + return multi.Sum() + }` + eval(t, src, big.NewInt(40)) + }) + t.Run("PackageAlias", func(t *testing.T) { + src := `package foo + import kek "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi" + func Main() int { + kek.SomeVar12 = 10 + return kek.Sum() + }` + eval(t, src, big.NewInt(40)) + }) + t.Run("DifferentName", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/strange" + func Main() int { + normal.NormalVar = 42 + return normal.NormalVar + }` + eval(t, src, big.NewInt(42)) + }) + t.Run("MultipleEqualNames", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi" + var SomeVar12 = 1 + func Main() int { + SomeVar30 := 3 + sum := SomeVar12 + multi.SomeVar30 + sum += SomeVar30 + sum += multi.SomeVar12 + return sum + }` + eval(t, src, big.NewInt(46)) + }) +} diff --git a/pkg/compiler/testdata/strange/normal.go b/pkg/compiler/testdata/strange/normal.go new file mode 100644 index 000000000..98a4593dc --- /dev/null +++ b/pkg/compiler/testdata/strange/normal.go @@ -0,0 +1,4 @@ +package normal + +// NormalVar is pretty normal, nothing special. +var NormalVar = 7