Merge pull request #1995 from nspcc-dev/compiler-verify-notify

compiler: disallow `runtime.Notify` in `Verify` function
This commit is contained in:
Roman Khimov 2021-06-06 11:34:42 +03:00 committed by GitHub
commit efb814a97e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 2 deletions

View file

@ -353,6 +353,12 @@ func isInitFunc(decl *ast.FuncDecl) bool {
decl.Type.Results.NumFields() == 0 decl.Type.Results.NumFields() == 0
} }
func (c *codegen) isVerifyFunc(decl *ast.FuncDecl) bool {
return decl.Name.Name == "Verify" && decl.Recv == nil &&
decl.Type.Results.NumFields() == 1 &&
isBool(c.typeOf(decl.Type.Results.List[0].Type))
}
func (c *codegen) clearSlots(n int) { func (c *codegen) clearSlots(n int) {
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL) emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)

View file

@ -59,6 +59,7 @@ type Options struct {
type buildInfo struct { type buildInfo struct {
initialPackage string initialPackage string
program *loader.Program program *loader.Program
options *Options
} }
// ForEachPackage executes fn on each package used in the current program // ForEachPackage executes fn on each package used in the current program
@ -153,10 +154,18 @@ func Compile(name string, r io.Reader) ([]byte, error) {
// CompileWithDebugInfo compiles a Go program into bytecode and emits debug info. // CompileWithDebugInfo compiles a Go program into bytecode and emits debug info.
func CompileWithDebugInfo(name string, r io.Reader) ([]byte, *DebugInfo, error) { func CompileWithDebugInfo(name string, r io.Reader) ([]byte, *DebugInfo, error) {
return CompileWithOptions(name, r, &Options{
NoEventsCheck: true,
})
}
// CompileWithOptions compiles a Go program into bytecode with provided compiler options.
func CompileWithOptions(name string, r io.Reader, o *Options) ([]byte, *DebugInfo, error) {
ctx, err := getBuildInfo(name, r) ctx, err := getBuildInfo(name, r)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
ctx.options = o
return CodeGen(ctx) return CodeGen(ctx)
} }
@ -173,7 +182,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
if len(o.Ext) == 0 { if len(o.Ext) == 0 {
o.Ext = fileExt o.Ext = fileExt
} }
b, di, err := CompileWithDebugInfo(src, nil) b, di, err := CompileWithOptions(src, nil, o)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while trying to compile smart contract file: %w", err) return nil, fmt.Errorf("error while trying to compile smart contract file: %w", err)
} }

View file

@ -1,6 +1,7 @@
package compiler_test package compiler_test
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
@ -175,3 +176,24 @@ func TestEventWarnings(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
} }
func TestNotifyInVerify(t *testing.T) {
srcTmpl := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Verify() bool { runtime.%s("Event"); return true }`
for _, name := range []string{"Notify", "Log"} {
t.Run(name, func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, name)
_, _, err := compiler.CompileWithOptions("eventTest", strings.NewReader(src),
&compiler.Options{ContractEvents: []manifest.Event{{Name: "Event"}}})
require.Error(t, err)
t.Run("suppress", func(t *testing.T) {
_, _, err := compiler.CompileWithOptions("eventTest", strings.NewReader(src),
&compiler.Options{NoEventsCheck: true})
require.NoError(t, err)
})
})
}
}

View file

@ -110,7 +110,21 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
} }
func (c *codegen) processNotify(f *funcScope, args []ast.Expr) { func (c *codegen) processNotify(f *funcScope, args []ast.Expr) {
if f != nil && f.pkg.Path() == interopPrefix+"/runtime" && f.name == "Notify" { if f != nil && f.pkg.Path() == interopPrefix+"/runtime" {
if f.name != "Notify" && f.name != "Log" {
return
}
if c.scope != nil && c.isVerifyFunc(c.scope.decl) &&
c.scope.pkg == c.mainPkg.Pkg && !c.buildInfo.options.NoEventsCheck {
c.prog.Err = fmt.Errorf("runtime.%s is not allowed in `Verify`", f.name)
return
}
if f.name == "Log" {
return
}
// Sometimes event name is stored in a var. // Sometimes event name is stored in a var.
// Skip in this case. // Skip in this case.
tv := c.typeAndValueOf(args[0]) tv := c.typeAndValueOf(args[0])