From 2e73e372b3213cef9971f6c0f884e79c6eecf736 Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Mon, 14 Feb 2022 00:23:37 +0300 Subject: [PATCH] all: allow calling quasigo functions from quasigo (#378) Now it's possible to call one bytecode function from another. --- analyzer/testdata/src/quasigo/rules.go | 14 +++- ruleguard/ir_loader.go | 7 +- ruleguard/quasigo/compile.go | 55 ++++++++++++--- ruleguard/quasigo/compile_test.go | 71 ++++++++++++-------- ruleguard/quasigo/disasm.go | 4 ++ ruleguard/quasigo/eval.go | 17 +++++ ruleguard/quasigo/eval_bench_test.go | 4 +- ruleguard/quasigo/eval_test.go | 28 ++------ ruleguard/quasigo/gen_opcodes.go | 3 + ruleguard/quasigo/opcode_string.go | 43 ++++++------ ruleguard/quasigo/opcodes.gen.go | 85 ++++++++++++++---------- ruleguard/quasigo/quasigo.go | 10 ++- ruleguard/quasigo/quasigo_test.go | 42 ++++++++++-- ruleguard/quasigo/testdata/funcs/main.go | 46 +++++++++++++ ruleguard/ruleguard_error_test.go | 4 -- 15 files changed, 301 insertions(+), 132 deletions(-) create mode 100644 ruleguard/quasigo/testdata/funcs/main.go diff --git a/analyzer/testdata/src/quasigo/rules.go b/analyzer/testdata/src/quasigo/rules.go index 5219a746..8a7d5754 100644 --- a/analyzer/testdata/src/quasigo/rules.go +++ b/analyzer/testdata/src/quasigo/rules.go @@ -1,3 +1,4 @@ +//go:build ignore // +build ignore package gorules @@ -7,12 +8,16 @@ import ( "github.com/quasilyte/go-ruleguard/dsl/types" ) +func derefPointer(ptr *types.Pointer) *types.Pointer { + return types.AsPointer(ptr.Elem()) +} + func tooManyPointers(ctx *dsl.VarFilterContext) bool { indir := 0 ptr := types.AsPointer(ctx.Type) for ptr != nil { indir++ - ptr = types.AsPointer(ptr.Elem()) + ptr = derefPointer(ptr) } return indir >= 3 } @@ -33,11 +38,16 @@ func isPointer(ctx *dsl.VarFilterContext) bool { return ptr != nil } -func isInterface(ctx *dsl.VarFilterContext) bool { +func isInterfaceImpl(ctx *dsl.VarFilterContext) bool { // Nil can be used on either side. return nil != types.AsInterface(ctx.Type.Underlying()) } +func isInterface(ctx *dsl.VarFilterContext) bool { + // Forwarding a call to other function. + return isInterfaceImpl(ctx) +} + func isError(ctx *dsl.VarFilterContext) bool { // Testing Interface.String() method. iface := types.AsInterface(ctx.Type.Underlying()) diff --git a/ruleguard/ir_loader.go b/ruleguard/ir_loader.go index ff107f97..7ea50216 100644 --- a/ruleguard/ir_loader.go +++ b/ruleguard/ir_loader.go @@ -206,9 +206,10 @@ func (l *irLoader) compileFilterFuncs(filename string, irfile *ir.File) error { continue } ctx := &quasigo.CompileContext{ - Env: l.state.env, - Types: f.Types, - Fset: fset, + Env: l.state.env, + Package: f.Pkg, + Types: f.Types, + Fset: fset, } compiled, err := quasigo.Compile(ctx, decl) if err != nil { diff --git a/ruleguard/quasigo/compile.go b/ruleguard/quasigo/compile.go index 3ad1fd72..b81fb8f1 100644 --- a/ruleguard/quasigo/compile.go +++ b/ruleguard/quasigo/compile.go @@ -124,9 +124,12 @@ func (cl *compiler) compileFunc(fn *ast.FuncDecl) *Func { } compiled := &Func{ - code: cl.code, - constants: cl.constants, - intConstants: cl.intConstants, + code: cl.code, + constants: cl.constants, + intConstants: cl.intConstants, + numObjectParams: len(cl.params), + numIntParams: len(cl.intParams), + name: cl.ctx.Package.Path() + "." + fn.Name.String(), } if len(cl.locals) != 0 { dbg.localNames = make([]string, len(cl.locals)) @@ -575,19 +578,51 @@ func (cl *compiler) compileCallExpr(call *ast.CallExpr) { if sig.Variadic() { variadic = sig.Params().Len() - 1 } - if !cl.compileNativeCall(key, variadic, expr, call.Args) { - panic(cl.errorf(call.Fun, "can't compile a call to %s func", key)) + if expr != nil { + cl.compileExpr(expr) + } + if cl.compileNativeCall(key, variadic, expr, call.Args) { + return } + if cl.compileCall(key, sig, call.Args) { + return + } + panic(cl.errorf(call.Fun, "can't compile a call to %s func", key)) } -func (cl *compiler) compileNativeCall(key funcKey, variadic int, expr ast.Expr, args []ast.Expr) bool { - funcID, ok := cl.ctx.Env.nameToNativeFuncID[key] +func (cl *compiler) compileCall(key funcKey, sig *types.Signature, args []ast.Expr) bool { + if sig.Variadic() { + return false + } + + funcID, ok := cl.ctx.Env.nameToFuncID[key] if !ok { return false } - if expr != nil { - cl.compileExpr(expr) + + for _, arg := range args { + cl.compileExpr(arg) + } + + var op opcode + if sig.Results().Len() == 0 { + op = opVoidCall + } else if typeIsInt(sig.Results().At(0).Type()) { + op = opIntCall + } else { + op = opCall } + + cl.emit16(op, int(funcID)) + return true +} + +func (cl *compiler) compileNativeCall(key funcKey, variadic int, funcExpr ast.Expr, args []ast.Expr) bool { + funcID, ok := cl.ctx.Env.nameToNativeFuncID[key] + if !ok { + return false + } + if len(args) == 1 { // Check that it's not a f(g()) call, where g() returns // a multi-value result; we can't compile that yet. @@ -619,7 +654,7 @@ func (cl *compiler) compileNativeCall(key funcKey, variadic int, expr ast.Expr, } } if len(variadicArgs) > 255 { - panic(cl.errorf(expr, "too many variadic args")) + panic(cl.errorf(funcExpr, "too many variadic args")) } // Even if len(variadicArgs) is 0, we still need to overwrite // the old variadicLen value, so the variadic func is not confused diff --git a/ruleguard/quasigo/compile_test.go b/ruleguard/quasigo/compile_test.go index b9899e66..f0ae65a2 100644 --- a/ruleguard/quasigo/compile_test.go +++ b/ruleguard/quasigo/compile_test.go @@ -358,11 +358,28 @@ func TestCompile(t *testing.T) { ` PushLocal 0 # v`, ` ReturnTop`, }, + + `return add1(10)`: { + ` PushIntConst 0 # value=10`, + ` IntCall 0 # testpkg.add1`, + ` ReturnIntTop`, + }, + + `return concat(concat("x", "y"), "z")`: { + ` PushConst 0 # value="x"`, + ` PushConst 1 # value="y"`, + ` Call 1 # testpkg.concat`, + ` PushConst 2 # value="z"`, + ` Call 1 # testpkg.concat`, + ` ReturnTop`, + }, } makePackageSource := func(body string) string { return ` - package testpkg + package ` + testPackage + ` + func add1(x int) int { return x + 1 } + func concat(s1, s2 string) string { return s1 + s2 } func f(i int, s string, b bool, eface interface{}) interface{} { ` + body + ` } @@ -373,37 +390,37 @@ func TestCompile(t *testing.T) { ` } - env := quasigo.NewEnv() - env.AddNativeFunc(testPackage, "imul", func(stack *quasigo.ValueStack) { - panic("should not be called") - }) - env.AddNativeFunc(testPackage, "idiv", func(stack *quasigo.ValueStack) { - panic("should not be called") - }) - env.AddNativeFunc(testPackage, "atoi", func(stack *quasigo.ValueStack) { - panic("should not be called") - }) - env.AddNativeFunc(testPackage, "sprintf", func(stack *quasigo.ValueStack) { - panic("should not be called") - }) - env.AddNativeFunc("builtin", "PrintInt", func(stack *quasigo.ValueStack) { - panic("should not be called") - }) - env.AddNativeFunc("builtin", "Print", func(stack *quasigo.ValueStack) { - panic("should not be called") - }) - for testSrc, disasmLines := range tests { + env := quasigo.NewEnv() + env.AddNativeFunc(testPackage, "imul", func(stack *quasigo.ValueStack) { + panic("should not be called") + }) + env.AddNativeFunc(testPackage, "idiv", func(stack *quasigo.ValueStack) { + panic("should not be called") + }) + env.AddNativeFunc(testPackage, "atoi", func(stack *quasigo.ValueStack) { + panic("should not be called") + }) + env.AddNativeFunc(testPackage, "sprintf", func(stack *quasigo.ValueStack) { + panic("should not be called") + }) + env.AddNativeFunc("builtin", "PrintInt", func(stack *quasigo.ValueStack) { + panic("should not be called") + }) + env.AddNativeFunc("builtin", "Print", func(stack *quasigo.ValueStack) { + panic("should not be called") + }) src := makePackageSource(testSrc) - parsed, err := parseGoFile(src) + parsed, err := parseGoFile(testPackage, src) if err != nil { - t.Errorf("parse %s: %v", testSrc, err) - continue + t.Fatalf("parse %s: %v", testSrc, err) } - compiled, err := compileTestFunc(env, "f", parsed) + compiled, err := compileTestFile(env, "f", testPackage, parsed) if err != nil { - t.Errorf("compile %s: %v", testSrc, err) - continue + t.Fatal(err) + } + if compiled == nil { + t.Fatal("can't find f function") } want := disasmLines have := strings.Split(quasigo.Disasm(env, compiled), "\n") diff --git a/ruleguard/quasigo/disasm.go b/ruleguard/quasigo/disasm.go index 0125907d..cafc9ed5 100644 --- a/ruleguard/quasigo/disasm.go +++ b/ruleguard/quasigo/disasm.go @@ -39,6 +39,10 @@ func disasm(env *Env, fn *Func) string { id := decode16(code, pc+1) arg = id comment = env.nativeFuncs[id].name + case opCall, opIntCall, opVoidCall: + id := decode16(code, pc+1) + arg = id + comment = env.userFuncs[id].name case opPushParam: index := int(code[pc+1]) arg = index diff --git a/ruleguard/quasigo/eval.go b/ruleguard/quasigo/eval.go index 64eb61ef..311da15a 100644 --- a/ruleguard/quasigo/eval.go +++ b/ruleguard/quasigo/eval.go @@ -129,6 +129,23 @@ func eval(env *EvalEnv, fn *Func, top, intTop int) CallResult { fn := env.nativeFuncs[id].mappedFunc fn(stack) pc += 3 + case opCall: + id := decode16(code, pc+1) + fn := env.userFuncs[id] + result := eval(env, fn, len(stack.objects)-fn.numObjectParams, len(stack.ints)-fn.numIntParams) + stack.Push(result.Value()) + pc += 3 + case opIntCall: + id := decode16(code, pc+1) + fn := env.userFuncs[id] + result := eval(env, fn, len(stack.objects)-fn.numObjectParams, len(stack.ints)-fn.numIntParams) + stack.PushInt(result.IntValue()) + pc += 3 + case opVoidCall: + id := decode16(code, pc+1) + fn := env.userFuncs[id] + eval(env, fn, len(stack.objects)-fn.numObjectParams, len(stack.ints)-fn.numIntParams) + pc += 3 case opJump: offset := decode16(code, pc+1) diff --git a/ruleguard/quasigo/eval_bench_test.go b/ruleguard/quasigo/eval_bench_test.go index 10e10cfe..004db583 100644 --- a/ruleguard/quasigo/eval_bench_test.go +++ b/ruleguard/quasigo/eval_bench_test.go @@ -143,7 +143,7 @@ func pushArgs(env *quasigo.EvalEnv, args ...interface{}) { func compileBenchFunc(t testing.TB, paramsSig, bodySrc string) (*quasigo.Env, *quasigo.Func) { makePackageSource := func(body string) string { return ` - package test + package ` + testPackage + ` import "fmt" var _ = fmt.Sprintf func f(` + paramsSig + `) interface{} { @@ -161,7 +161,7 @@ func compileBenchFunc(t testing.TB, paramsSig, bodySrc string) (*quasigo.Env, *q }) qfmt.ImportAll(env) src := makePackageSource(bodySrc) - parsed, err := parseGoFile(src) + parsed, err := parseGoFile(testPackage, src) if err != nil { t.Fatalf("parse %s: %v", bodySrc, err) } diff --git a/ruleguard/quasigo/eval_test.go b/ruleguard/quasigo/eval_test.go index f6ce9cad..53bdbdc5 100644 --- a/ruleguard/quasigo/eval_test.go +++ b/ruleguard/quasigo/eval_test.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "go/ast" "io/ioutil" "os" "os/exec" @@ -170,7 +169,7 @@ func TestEval(t *testing.T) { t.Fatalf("unexpected result type: %T", result) } return ` - package test + package ` + testPackage + ` import "github.com/quasilyte/go-ruleguard/ruleguard/quasigo/internal/evaltest" func target(i int, s string, b bool, foo, nilfoo *evaltest.Foo, nileface interface{}) ` + returnType + ` { ` + body + ` @@ -213,7 +212,7 @@ func TestEval(t *testing.T) { for i := range tests { test := tests[i] src := makePackageSource(test.src, test.result) - parsed, err := parseGoFile(src) + parsed, err := parseGoFile(testPackage, src) if err != nil { t.Fatalf("parse %s: %v", test.src, err) } @@ -261,7 +260,7 @@ func TestEvalFile(t *testing.T) { return "", err } env := quasigo.NewEnv() - parsed, err := parseGoFile(string(src)) + parsed, err := parseGoFile("main", string(src)) if err != nil { return "", fmt.Errorf("parse: %v", err) } @@ -284,24 +283,9 @@ func TestEvalFile(t *testing.T) { qstrconv.ImportAll(env) qfmt.ImportAll(env) - var mainFunc *quasigo.Func - for _, decl := range parsed.ast.Decls { - decl, ok := decl.(*ast.FuncDecl) - if !ok { - continue - } - ctx := &quasigo.CompileContext{ - Env: env, - Types: parsed.types, - Fset: parsed.fset, - } - fn, err := quasigo.Compile(ctx, decl) - if err != nil { - return "", fmt.Errorf("compile %s func: %v", decl.Name, err) - } - if decl.Name.String() == "main" { - mainFunc = fn - } + mainFunc, err := compileTestFile(env, "main", "main", parsed) + if err != nil { + return "", err } if mainFunc == nil { return "", errors.New("can't find main() function") diff --git a/ruleguard/quasigo/gen_opcodes.go b/ruleguard/quasigo/gen_opcodes.go index 6f8f26d5..c8d51203 100644 --- a/ruleguard/quasigo/gen_opcodes.go +++ b/ruleguard/quasigo/gen_opcodes.go @@ -45,6 +45,9 @@ var opcodePrototypes = []opcodeProto{ {"SetVariadicLen", "op len:u8", stackUnchanged}, {"CallNative", "op funcid:u16", "(args...) -> (results...)"}, + {"Call", "op funcid:u16", "(args...) -> (result)"}, + {"IntCall", "op funcid:u16", "(args...) -> (result:int)"}, + {"VoidCall", "op funcid:u16", "(args...) -> ()"}, {"IsNil", "op", "(value) -> (result:bool)"}, {"IsNotNil", "op", "(value) -> (result:bool)"}, diff --git a/ruleguard/quasigo/opcode_string.go b/ruleguard/quasigo/opcode_string.go index 27dfc494..3136214b 100644 --- a/ruleguard/quasigo/opcode_string.go +++ b/ruleguard/quasigo/opcode_string.go @@ -34,29 +34,32 @@ func _() { _ = x[opJumpTrue-23] _ = x[opSetVariadicLen-24] _ = x[opCallNative-25] - _ = x[opIsNil-26] - _ = x[opIsNotNil-27] - _ = x[opNot-28] - _ = x[opEqInt-29] - _ = x[opNotEqInt-30] - _ = x[opGtInt-31] - _ = x[opGtEqInt-32] - _ = x[opLtInt-33] - _ = x[opLtEqInt-34] - _ = x[opEqString-35] - _ = x[opNotEqString-36] - _ = x[opConcat-37] - _ = x[opAdd-38] - _ = x[opSub-39] - _ = x[opStringSlice-40] - _ = x[opStringSliceFrom-41] - _ = x[opStringSliceTo-42] - _ = x[opStringLen-43] + _ = x[opCall-26] + _ = x[opIntCall-27] + _ = x[opVoidCall-28] + _ = x[opIsNil-29] + _ = x[opIsNotNil-30] + _ = x[opNot-31] + _ = x[opEqInt-32] + _ = x[opNotEqInt-33] + _ = x[opGtInt-34] + _ = x[opGtEqInt-35] + _ = x[opLtInt-36] + _ = x[opLtEqInt-37] + _ = x[opEqString-38] + _ = x[opNotEqString-39] + _ = x[opConcat-40] + _ = x[opAdd-41] + _ = x[opSub-42] + _ = x[opStringSlice-43] + _ = x[opStringSliceFrom-44] + _ = x[opStringSliceTo-45] + _ = x[opStringLen-46] } -const _opcode_name = "InvalidPopDupPushParamPushIntParamPushLocalPushIntLocalPushFalsePushTruePushConstPushIntConstConvIntToIfaceSetLocalSetIntLocalIncLocalDecLocalReturnTopReturnIntTopReturnFalseReturnTrueReturnJumpJumpFalseJumpTrueSetVariadicLenCallNativeIsNilIsNotNilNotEqIntNotEqIntGtIntGtEqIntLtIntLtEqIntEqStringNotEqStringConcatAddSubStringSliceStringSliceFromStringSliceToStringLen" +const _opcode_name = "InvalidPopDupPushParamPushIntParamPushLocalPushIntLocalPushFalsePushTruePushConstPushIntConstConvIntToIfaceSetLocalSetIntLocalIncLocalDecLocalReturnTopReturnIntTopReturnFalseReturnTrueReturnJumpJumpFalseJumpTrueSetVariadicLenCallNativeCallIntCallVoidCallIsNilIsNotNilNotEqIntNotEqIntGtIntGtEqIntLtIntLtEqIntEqStringNotEqStringConcatAddSubStringSliceStringSliceFromStringSliceToStringLen" -var _opcode_index = [...]uint16{0, 7, 10, 13, 22, 34, 43, 55, 64, 72, 81, 93, 107, 115, 126, 134, 142, 151, 163, 174, 184, 190, 194, 203, 211, 225, 235, 240, 248, 251, 256, 264, 269, 276, 281, 288, 296, 307, 313, 316, 319, 330, 345, 358, 367} +var _opcode_index = [...]uint16{0, 7, 10, 13, 22, 34, 43, 55, 64, 72, 81, 93, 107, 115, 126, 134, 142, 151, 163, 174, 184, 190, 194, 203, 211, 225, 235, 239, 246, 254, 259, 267, 270, 275, 283, 288, 295, 300, 307, 315, 326, 332, 335, 338, 349, 364, 377, 386} func (i opcode) String() string { if i >= opcode(len(_opcode_index)-1) { diff --git a/ruleguard/quasigo/opcodes.gen.go b/ruleguard/quasigo/opcodes.gen.go index a12aecd5..a3ec270d 100644 --- a/ruleguard/quasigo/opcodes.gen.go +++ b/ruleguard/quasigo/opcodes.gen.go @@ -108,77 +108,89 @@ const ( // Stack effect: (args...) -> (results...) opCallNative opcode = 25 - // Encoding: 0x1a (width=1) - // Stack effect: (value) -> (result:bool) - opIsNil opcode = 26 + // Encoding: 0x1a funcid:u16 (width=3) + // Stack effect: (args...) -> (result) + opCall opcode = 26 - // Encoding: 0x1b (width=1) - // Stack effect: (value) -> (result:bool) - opIsNotNil opcode = 27 + // Encoding: 0x1b funcid:u16 (width=3) + // Stack effect: (args...) -> (result:int) + opIntCall opcode = 27 - // Encoding: 0x1c (width=1) - // Stack effect: (value:bool) -> (result:bool) - opNot opcode = 28 + // Encoding: 0x1c funcid:u16 (width=3) + // Stack effect: (args...) -> () + opVoidCall opcode = 28 // Encoding: 0x1d (width=1) - // Stack effect: (x:int y:int) -> (result:bool) - opEqInt opcode = 29 + // Stack effect: (value) -> (result:bool) + opIsNil opcode = 29 // Encoding: 0x1e (width=1) - // Stack effect: (x:int y:int) -> (result:bool) - opNotEqInt opcode = 30 + // Stack effect: (value) -> (result:bool) + opIsNotNil opcode = 30 // Encoding: 0x1f (width=1) - // Stack effect: (x:int y:int) -> (result:bool) - opGtInt opcode = 31 + // Stack effect: (value:bool) -> (result:bool) + opNot opcode = 31 // Encoding: 0x20 (width=1) // Stack effect: (x:int y:int) -> (result:bool) - opGtEqInt opcode = 32 + opEqInt opcode = 32 // Encoding: 0x21 (width=1) // Stack effect: (x:int y:int) -> (result:bool) - opLtInt opcode = 33 + opNotEqInt opcode = 33 // Encoding: 0x22 (width=1) // Stack effect: (x:int y:int) -> (result:bool) - opLtEqInt opcode = 34 + opGtInt opcode = 34 // Encoding: 0x23 (width=1) - // Stack effect: (x:string y:string) -> (result:bool) - opEqString opcode = 35 + // Stack effect: (x:int y:int) -> (result:bool) + opGtEqInt opcode = 35 // Encoding: 0x24 (width=1) - // Stack effect: (x:string y:string) -> (result:bool) - opNotEqString opcode = 36 + // Stack effect: (x:int y:int) -> (result:bool) + opLtInt opcode = 36 // Encoding: 0x25 (width=1) - // Stack effect: (x:string y:string) -> (result:string) - opConcat opcode = 37 + // Stack effect: (x:int y:int) -> (result:bool) + opLtEqInt opcode = 37 // Encoding: 0x26 (width=1) - // Stack effect: (x:int y:int) -> (result:int) - opAdd opcode = 38 + // Stack effect: (x:string y:string) -> (result:bool) + opEqString opcode = 38 // Encoding: 0x27 (width=1) - // Stack effect: (x:int y:int) -> (result:int) - opSub opcode = 39 + // Stack effect: (x:string y:string) -> (result:bool) + opNotEqString opcode = 39 // Encoding: 0x28 (width=1) - // Stack effect: (s:string from:int to:int) -> (result:string) - opStringSlice opcode = 40 + // Stack effect: (x:string y:string) -> (result:string) + opConcat opcode = 40 // Encoding: 0x29 (width=1) - // Stack effect: (s:string from:int) -> (result:string) - opStringSliceFrom opcode = 41 + // Stack effect: (x:int y:int) -> (result:int) + opAdd opcode = 41 // Encoding: 0x2a (width=1) - // Stack effect: (s:string to:int) -> (result:string) - opStringSliceTo opcode = 42 + // Stack effect: (x:int y:int) -> (result:int) + opSub opcode = 42 // Encoding: 0x2b (width=1) + // Stack effect: (s:string from:int to:int) -> (result:string) + opStringSlice opcode = 43 + + // Encoding: 0x2c (width=1) + // Stack effect: (s:string from:int) -> (result:string) + opStringSliceFrom opcode = 44 + + // Encoding: 0x2d (width=1) + // Stack effect: (s:string to:int) -> (result:string) + opStringSliceTo opcode = 45 + + // Encoding: 0x2e (width=1) // Stack effect: (s:string) -> (result:int) - opStringLen opcode = 43 + opStringLen opcode = 46 ) type opcodeInfo struct { @@ -213,6 +225,9 @@ var opcodeInfoTable = [256]opcodeInfo{ opJumpTrue: {width: 3}, opSetVariadicLen: {width: 2}, opCallNative: {width: 3}, + opCall: {width: 3}, + opIntCall: {width: 3}, + opVoidCall: {width: 3}, opIsNil: {width: 1}, opIsNotNil: {width: 1}, opNot: {width: 1}, diff --git a/ruleguard/quasigo/quasigo.go b/ruleguard/quasigo/quasigo.go index daf5aef9..8ac75771 100644 --- a/ruleguard/quasigo/quasigo.go +++ b/ruleguard/quasigo/quasigo.go @@ -84,8 +84,9 @@ type CompileContext struct { // being compiled; then it should be used to execute these functions. Env *Env - Types *types.Info - Fset *token.FileSet + Package *types.Package + Types *types.Info + Fset *token.FileSet } // Compile prepares an executable version of fn. @@ -136,6 +137,11 @@ type Func struct { constants []interface{} intConstants []int + + numObjectParams int + numIntParams int + + name string } // ValueStack is used to manipulate runtime values during the evaluation. diff --git a/ruleguard/quasigo/quasigo_test.go b/ruleguard/quasigo/quasigo_test.go index d3466e7d..79e0cdbd 100644 --- a/ruleguard/quasigo/quasigo_test.go +++ b/ruleguard/quasigo/quasigo_test.go @@ -15,11 +15,12 @@ const testPackage = "testpkg" type parsedTestFile struct { ast *ast.File + pkg *types.Package types *types.Info fset *token.FileSet } -func parseGoFile(src string) (*parsedTestFile, error) { +func parseGoFile(pkgPath, src string) (*parsedTestFile, error) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "test.go", src, 0) if err != nil { @@ -33,15 +34,45 @@ func parseGoFile(src string) (*parsedTestFile, error) { Uses: map[*ast.Ident]types.Object{}, Defs: map[*ast.Ident]types.Object{}, } - _, err = typechecker.Check(testPackage, fset, []*ast.File{file}, info) + pkg, err := typechecker.Check(pkgPath, fset, []*ast.File{file}, info) result := &parsedTestFile{ ast: file, + pkg: pkg, types: info, fset: fset, } return result, err } +func compileTestFile(env *quasigo.Env, targetFunc, pkgPath string, parsed *parsedTestFile) (*quasigo.Func, error) { + var resultFunc *quasigo.Func + for _, decl := range parsed.ast.Decls { + decl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + if decl.Body == nil { + continue + } + ctx := &quasigo.CompileContext{ + Env: env, + Package: parsed.pkg, + Types: parsed.types, + Fset: parsed.fset, + } + fn, err := quasigo.Compile(ctx, decl) + if err != nil { + return nil, fmt.Errorf("compile %s func: %v", decl.Name, err) + } + if decl.Name.String() == targetFunc { + resultFunc = fn + } else { + env.AddFunc(pkgPath, decl.Name.String(), fn) + } + } + return resultFunc, nil +} + func compileTestFunc(env *quasigo.Env, fn string, parsed *parsedTestFile) (*quasigo.Func, error) { var target *ast.FuncDecl for _, decl := range parsed.ast.Decls { @@ -59,9 +90,10 @@ func compileTestFunc(env *quasigo.Env, fn string, parsed *parsedTestFile) (*quas } ctx := &quasigo.CompileContext{ - Env: env, - Types: parsed.types, - Fset: parsed.fset, + Env: env, + Package: parsed.pkg, + Types: parsed.types, + Fset: parsed.fset, } return quasigo.Compile(ctx, target) } diff --git a/ruleguard/quasigo/testdata/funcs/main.go b/ruleguard/quasigo/testdata/funcs/main.go new file mode 100644 index 00000000..699a94c6 --- /dev/null +++ b/ruleguard/quasigo/testdata/funcs/main.go @@ -0,0 +1,46 @@ +package main + +func ten() int { return 10 } + +func helloWorld() { + println("Hello, world!") +} + +func add1(x int) int { + return x + 1 +} + +func strlen(s string) int { + return len(s) +} + +func concat(s1, s2 string) string { + return s1 + s2 +} + +func concat3(s1, s2, s3 string) string { + return concat(concat(s1, s2), s3) +} + +func hasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func main() { + helloWorld() + println(ten()) + println(add1(ten())) + println(strlen("hello")) + println(concat("foo", "bar")) + println(concat3("", "", "")) + println(concat3("x", "", "")) + println(concat3("", "x", "")) + println(concat3("", "", "x")) + println(concat3("a", "b", "c")) + println(concat3("hello", "world", "")) + println(hasPrefix("", "")) + println(hasPrefix("", "hello")) + println(hasPrefix("hello", "")) + println(hasPrefix("hello", "hello")) + println(hasPrefix("hello, world", "hello")) +} diff --git a/ruleguard/ruleguard_error_test.go b/ruleguard/ruleguard_error_test.go index c93f2734..b3f30a7d 100644 --- a/ruleguard/ruleguard_error_test.go +++ b/ruleguard/ruleguard_error_test.go @@ -54,10 +54,6 @@ func TestParseFilterFuncError(t *testing.T) { `b := 0; return (b << 1) != 0`, `can't compile binary << yet`, }, - { - `return g(ctx)`, - `can't compile a call to gorules.g func`, - }, { `return new(int) != nil`, `can't compile new() builtin function call yet`,