diff --git a/cmd/xgo/runtime_gen/core/version.go b/cmd/xgo/runtime_gen/core/version.go index 214ae6dd..7f15d140 100755 --- a/cmd/xgo/runtime_gen/core/version.go +++ b/cmd/xgo/runtime_gen/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.28" -const REVISION = "2eaf6cf94cd17d2888d6708cb76d194b334c8957+1" -const NUMBER = 202 +const VERSION = "1.0.29" +const REVISION = "5d121a999ca8ce5c744a110d4f42b31ef4490e8f+1" +const NUMBER = 204 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 8fa85853..7f2e5d75 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -2,9 +2,9 @@ package main import "fmt" -const VERSION = "1.0.28" -const REVISION = "2eaf6cf94cd17d2888d6708cb76d194b334c8957+1" -const NUMBER = 202 +const VERSION = "1.0.29" +const REVISION = "5d121a999ca8ce5c744a110d4f42b31ef4490e8f+1" +const NUMBER = 204 func getRevision() string { revSuffix := "" diff --git a/patch/syntax/vars.go b/patch/syntax/vars.go index a8363383..bf6512c4 100644 --- a/patch/syntax/vars.go +++ b/patch/syntax/vars.go @@ -584,8 +584,16 @@ func (ctx *BlockContext) traverseCallExpr(node *syntax.CallExpr, globaleNames ma } } - // NOTE: we skip capturing a name as a function - // node.Fun = ctx.traverseExpr(node.Fun, globaleNames, imports) + // NOTE: previousely we skip capturing a name as a function(i.e. the next statement is commented out) + // reason: we cannot tell if the receiver is + // a pointer or a value. If the target function requires a + // pointer while we assign a value, then effect will lost, such + // as lock and unlock. + // + // however, since we have isVarOKToTrap() to check if a variable is ok to trap, so this can be turned on, resulting in workaround: + // A.B() -> typeOfA(A).B() which makes things work + + node.Fun = ctx.traverseExpr(node.Fun, globaleNames, imports) for i, arg := range node.ArgList { node.ArgList[i] = ctx.traverseExpr(arg, globaleNames, imports) } @@ -762,6 +770,7 @@ func (ctx *BlockContext) trapSelector(node syntax.Expr, sel *syntax.SelectorExpr return newName, true } +// check if node is either X of an X.Y, or (X).Y or ((X)).Y... func (ctx *BlockContext) isVarOKToTrap(node syntax.Node) bool { // a variable can only trapped when it will not // cause an implicit pointer diff --git a/runtime/core/version.go b/runtime/core/version.go index 214ae6dd..7f15d140 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.28" -const REVISION = "2eaf6cf94cd17d2888d6708cb76d194b334c8957+1" -const NUMBER = 202 +const VERSION = "1.0.29" +const REVISION = "5d121a999ca8ce5c744a110d4f42b31ef4490e8f+1" +const NUMBER = 204 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/runtime/test/README.md b/runtime/test/README.md new file mode 100644 index 00000000..5383b79e --- /dev/null +++ b/runtime/test/README.md @@ -0,0 +1,10 @@ +# Tests of the runtime package +Runnable tests are listed in file [../../script/run-test/main.go](../../script/run-test/main.go). + +# Run tests +```sh +# all + +# specific test +go run -tags dev ./cmd/xgo test --with-goroot go1.17.13 --project-dir ./runtime/test/mock_var -v -run TestThirdPartyTypeMethodVar +``` \ No newline at end of file diff --git a/runtime/test/debug/debug_test.go b/runtime/test/debug/debug_test.go index 5af0ed50..bcf75e1a 100644 --- a/runtime/test/debug/debug_test.go +++ b/runtime/test/debug/debug_test.go @@ -5,12 +5,25 @@ package debug -import "testing" +import ( + "testing" -const XGO_VERSION = "" + "github.com/xhd2015/xgo/runtime/mock" + "github.com/xhd2015/xgo/runtime/test/mock_var/sub" +) -func TestListStdlib(t *testing.T) { - if XGO_VERSION == "" { - t.Fatalf("fail") +var c sub.Mapping = sub.Mapping{ + 1: "hello", +} + +func TestThirdPartyTypeMethodVar(t *testing.T) { + mock.Patch(&c, func() sub.Mapping { + return sub.Mapping{ + 1: "mock", + } + }) + txt := c.Get(1) + if txt != "mock" { + t.Fatalf("expect c[1] to be %s, actual: %s", "mock", txt) } } diff --git a/runtime/test/mock_var/mock_var_test.go b/runtime/test/mock_var/mock_var_test.go index 6a024b06..c0735644 100644 --- a/runtime/test/mock_var/mock_var_test.go +++ b/runtime/test/mock_var/mock_var_test.go @@ -15,6 +15,10 @@ var a int = 123 // xgo:notrap var b int +var c sub.Mapping = sub.Mapping{ + 1: "hello", +} + func TestMockVarTest(t *testing.T) { mock.Mock(&a, func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error { results.GetFieldIndex(0).Set(456) @@ -36,3 +40,27 @@ func TestMockVarInOtherPkg(t *testing.T) { t.Fatalf("expect sub.A to be %s, actual: %s", "mockA", b) } } + +func TestThirdPartyTypeMethodVarWithoutWrapShouldNotWork(t *testing.T) { + mock.Patch(&c, func() sub.Mapping { + return sub.Mapping{ + 1: "mock", + } + }) + txt := c.Get(1) + if txt != "hello" { + t.Fatalf("expect c[1] to be %s, actual: %s", "hello", txt) + } +} + +func TestThirdPartyTypeMethodVarWithWrapShouldWork(t *testing.T) { + mock.Patch(&c, func() sub.Mapping { + return sub.Mapping{ + 1: "mock", + } + }) + txt := sub.Mapping(c).Get(1) + if txt != "mock" { + t.Fatalf("expect c[1] to be %s, actual: %s", "mock", txt) + } +} diff --git a/runtime/test/mock_var/sub/sub.go b/runtime/test/mock_var/sub/sub.go index 3aaa8733..4a8825d7 100644 --- a/runtime/test/mock_var/sub/sub.go +++ b/runtime/test/mock_var/sub/sub.go @@ -1,3 +1,9 @@ package sub var A string = "subA" + +type Mapping map[int]string + +func (c Mapping) Get(i int) string { + return c[i] +} diff --git a/runtime/test/trap_args/closure_test.go b/runtime/test/trap_args/closure_test.go index 1fbc8d44..cd38f846 100644 --- a/runtime/test/trap_args/closure_test.go +++ b/runtime/test/trap_args/closure_test.go @@ -18,8 +18,11 @@ var gcUnnamed = func(context.Context) { func TestClosureShouldRetrieveCtxInfoAtTrapTime(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, "test", "mock") + + // avoid local variable affects interceptor + gclocal := gc callAndCheck(func() { - gc(ctx) + gclocal(ctx) }, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { if !f.FirstArgCtx { t.Fatalf("expect closure also mark firstArgCtx, actually not marked") @@ -37,8 +40,9 @@ func TestClosureShouldRetrieveCtxInfoAtTrapTime(t *testing.T) { func TestClosureUnnamedArgShouldRetrieveCtxInfo(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, "test", "mock") + localGcUnnamed := gcUnnamed callAndCheck(func() { - gcUnnamed(ctx) + localGcUnnamed(ctx) }, func(trapCtx context.Context, f *core.FuncInfo, args, result core.Object) error { if !f.FirstArgCtx { t.Fatalf("expect closure also mark firstArgCtx, actually not marked") diff --git a/test/xgo_test/proof_of_var_mock/proof_of_var_mock_test.go b/test/xgo_test/proof_of_var_mock/proof_of_var_mock_test.go new file mode 100644 index 00000000..8c66415b --- /dev/null +++ b/test/xgo_test/proof_of_var_mock/proof_of_var_mock_test.go @@ -0,0 +1,47 @@ +// var mock: +// +// if variable is not a pointer, taking its address, then use it as receiver, it can be converted to value receiver implicitly +// otherwise if variable is a pointer, taking its address does not work. +package proof_of_var_mock + +import ( + "testing" +) + +type Stub struct { +} + +type PtrStub *Stub + +func (c Stub) A() { +} +func (c *Stub) B() { +} + +// invalid receiver type PtrStub (pointer or interface type) +// +// func (c PtrStub) X(){ +// +// } + +var stub Stub = Stub{} +var pstub *Stub = &Stub{} + +func TestVariableCanBeTrapped(t *testing.T) { + + stub.A() + stub.B() + + __mock_stub := &stub + __mock_stub.A() + __mock_stub.B() + + pstub.A() + pstub.B() + + // __mock_pstub.B undefined (type **Stub has no field or method + // + // __mock_pstub := &pstub + // __mock_pstub.A() + // __mock_pstub.B() +}