compiler: disallow runtime.Notify in Verify function

Only direct invocations are considered. The check can be disabled
with '--no-events' compiler option.
This commit is contained in:
Evgeniy Stratonikov 2021-06-04 11:47:52 +03:00
parent ebff8be20a
commit 1578904da2
4 changed files with 53 additions and 2 deletions

View file

@ -337,6 +337,12 @@ func isInitFunc(decl *ast.FuncDecl) bool {
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) {
for i := 0; i < n; i++ {
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)

View file

@ -59,6 +59,7 @@ type Options struct {
type buildInfo struct {
initialPackage string
program *loader.Program
options *Options
}
// 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.
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)
if err != nil {
return nil, nil, err
}
ctx.options = o
return CodeGen(ctx)
}
@ -173,7 +182,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
if len(o.Ext) == 0 {
o.Ext = fileExt
}
b, di, err := CompileWithDebugInfo(src, nil)
b, di, err := CompileWithOptions(src, nil, o)
if err != nil {
return nil, fmt.Errorf("error while trying to compile smart contract file: %w", err)
}

View file

@ -1,6 +1,7 @@
package compiler_test
import (
"fmt"
"io/ioutil"
"os"
"path"
@ -175,3 +176,24 @@ func TestEventWarnings(t *testing.T) {
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

@ -121,7 +121,21 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
}
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.
// Skip in this case.
tv := c.typeAndValueOf(args[0])