Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix patching type method #24

Merged
merged 1 commit into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ jobs:
run: ~/.xgo/bin/xgo revision

- name: Check Go Version
run: ~/.xgo/bin/xgo exec go version
run: ~/.xgo/bin/xgo exec go version

- name: Check spelling of files
uses: crate-ci/typos@master
continue-on-error: true
with:
files: ./
2 changes: 1 addition & 1 deletion cmd/xgo/patch_compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func patchGcMain(goroot string, goVersion *goinfo.GoVersion) error {
} else if go117 || go118 || go119 || go120 || go121 {
inlineCall := `inline.InlinePackage(profile)`
if go119AndUnder {
// go1.19 and under does not hae PGO
// go1.19 and under does not have PGO
inlineCall = `inline.InlinePackage()`
}
// go1.20 does not respect rewritten content when inlined
Expand Down
4 changes: 2 additions & 2 deletions cmd/xgo/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package main
import "fmt"

const VERSION = "1.0.12"
const REVISION = "f3d7271450fef6b7575368a82d6fe254c894a97e+1"
const NUMBER = 147
const REVISION = "3f5b60b1befac59464d3efbe2e9c2b5eeb35cf7b+1"
const NUMBER = 150

func getRevision() string {
return fmt.Sprintf("%s %s BUILD_%d", VERSION, REVISION, NUMBER)
Expand Down
8 changes: 4 additions & 4 deletions patch/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ func debugReplaceBody(fn *ir.Func) {
// debug
if false {
str := NewStringLit(fn.Pos(), "debug")
nd := fn.Body[0]
ue := nd.(*ir.UnaryExpr)
ce := ue.X.(*ir.ConvExpr)
ce.X = str
fnBody := fn.Body[0]
unaryExpr := fnBody.(*ir.UnaryExpr)
convExpr := unaryExpr.X.(*ir.ConvExpr)
convExpr.X = str
xgo_record.SetRewrittenBody(fn, fn.Body)
return
}
Expand Down
10 changes: 5 additions & 5 deletions patch/syntax/rewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,10 @@ func copyFields(fields []*syntax.Field) []*syntax.Field {
return c
}

func copyBasicLits(lits []*syntax.BasicLit) []*syntax.BasicLit {
c := make([]*syntax.BasicLit, len(lits))
for i := 0; i < len(lits); i++ {
c[i] = copyBasicLit(lits[i])
func copyBasicLiterals(literals []*syntax.BasicLit) []*syntax.BasicLit {
c := make([]*syntax.BasicLit, len(literals))
for i := 0; i < len(literals); i++ {
c[i] = copyBasicLit(literals[i])
}
return c
}
Expand Down Expand Up @@ -420,7 +420,7 @@ func copyExpr(expr syntax.Expr) syntax.Expr {
case *syntax.StructType:
x := *expr
x.FieldList = copyFields(expr.FieldList)
x.TagList = copyBasicLits(expr.TagList)
x.TagList = copyBasicLiterals(expr.TagList)
return &x
case *syntax.FuncType:
return copyFuncType(expr)
Expand Down
4 changes: 2 additions & 2 deletions runtime/core/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

const VERSION = "1.0.12"
const REVISION = "f3d7271450fef6b7575368a82d6fe254c894a97e+1"
const NUMBER = 147
const REVISION = "3f5b60b1befac59464d3efbe2e9c2b5eeb35cf7b+1"
const NUMBER = 150

// these fields will be filled by compiler
const XGO_VERSION = ""
Expand Down
59 changes: 35 additions & 24 deletions runtime/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,55 @@ import (
"github.com/xhd2015/xgo/runtime/trap"
)

// a marker to indicate the
// original function should be called
var ErrCallOld = errors.New("mock: call old")

type Interceptor func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error

// Mock setup mock on given function `fn`.
// `fn` can be a function or a method,
// when `fn` is a method, only the bound
// if `fn` is a method, only the bound
// instance will be mocked, other instances
// are not affected.
// The returned function can be used to cancel
// the passed interceptor.
func Mock(fn interface{}, interceptor Interceptor) func() {
return mockByFunc(fn, interceptor)
recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn)
return mock(recvPtr, fnInfo, funcPC, trappingPC, interceptor)
}

func MockByName(pkgPath string, funcName string, interceptor Interceptor) func() {
funcInfo := functab.GetFuncByPkg(pkgPath, funcName)
if funcInfo == nil {
panic(fmt.Errorf("failed to setup mock for: %s.%s", pkgPath, funcName))
}
return mock(nil, funcInfo, 0, 0, interceptor)
recv, fn, funcPC, trappingPC := getFuncByName(pkgPath, funcName)
return mock(recv, fn, funcPC, trappingPC, interceptor)
}

// Can instance be nil?
func MockMethodByName(instance interface{}, method string, interceptor Interceptor) func() {
recvPtr, fn, funcPC, trappingPC := getMethodByName(instance, method)
return mock(recvPtr, fn, funcPC, trappingPC, interceptor)
}

func getFunc(fn interface{}) (recvPtr interface{}, fnInfo *core.FuncInfo, funcPC uintptr, trappingPC uintptr) {
// if the target function is a method, then a
// recv ptr must be given
recvPtr, fnInfo, funcPC, trappingPC = trap.InspectPC(fn)
if fnInfo == nil {
pc := reflect.ValueOf(fn).Pointer()
panic(fmt.Errorf("failed to setup mock for: %v", runtime.FuncForPC(pc).Name()))
}
return recvPtr, fnInfo, funcPC, trappingPC
}

func getFuncByName(pkgPath string, funcName string) (recvPtr interface{}, fn *core.FuncInfo, funcPC uintptr, trappingPC uintptr) {
fn = functab.GetFuncByPkg(pkgPath, funcName)
if fn == nil {
panic(fmt.Errorf("failed to setup mock for: %s.%s", pkgPath, funcName))
}
return nil, fn, 0, 0
}

func getMethodByName(instance interface{}, method string) (recvPtr interface{}, fn *core.FuncInfo, funcPC uintptr, trappingPC uintptr) {
// extract instance's reflect.Type
// use that type to query for reflect mapping in functab:
// reflectTypeMapping map[reflect.Type]map[string]*funcInfo
Expand All @@ -45,34 +69,25 @@ func MockMethodByName(instance interface{}, method string, interceptor Intercept
if funcMapping == nil {
panic(fmt.Errorf("failed to setup mock for type %T", instance))
}
fn := funcMapping[method]
fn = funcMapping[method]
if fn == nil {
panic(fmt.Errorf("failed to setup mock for: %T.%s", instance, method))
}

addr := reflect.New(t)
addr.Elem().Set(reflect.ValueOf(instance))
return mock(addr.Interface(), fn, 0, 0, interceptor)

return addr.Interface(), fn, 0, 0
}

// Deprecated: use Mock instead
func AddFuncInterceptor(fn interface{}, interceptor Interceptor) func() {
return mockByFunc(fn, interceptor)
return Mock(fn, interceptor)
}

// TODO: ensure them run in last?
// no abort, run mocks
// mocks are special in that they on run in pre stage
func mockByFunc(fn interface{}, interceptor Interceptor) func() {
// if the target function is a method, then a
// recv ptr must be given
recvPtr, fnInfo, funcPC, trappingPC := trap.InspectPC(fn)
if fnInfo == nil {
pc := reflect.ValueOf(fn).Pointer()
panic(fmt.Errorf("failed to setup mock for: %v", runtime.FuncForPC(pc).Name()))
}
return mock(recvPtr, fnInfo, funcPC, trappingPC, interceptor)
}

// if mockFnInfo is a function, mockRecvPtr is always nil
// if mockFnInfo is a method,
Expand Down Expand Up @@ -109,10 +124,6 @@ func mock(mockRecvPtr interface{}, mockFnInfo *core.FuncInfo, funcPC uintptr, tr
// check they pointing to the same variable
re := reflect.ValueOf(recvPtr).Elem().Interface()
me := reflect.ValueOf(mockRecvPtr).Elem().Interface()
if f.RecvPtr {
// compare pointer
// unsafe.Pointer(&re)
}
if re != me {
// if *recvPtr != *mockRecvPtr {
return nil, nil
Expand Down
14 changes: 10 additions & 4 deletions runtime/mock/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import (
)

func PatchByName(pkgPath string, funcName string, replacer interface{}) func() {
return MockByName(pkgPath, funcName, buildInterceptorFromPatch(replacer))
recvPtr, funcInfo, funcPC, trappingPC := getFuncByName(pkgPath, funcName)
return mock(recvPtr, funcInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer))
}

func PatchMethodByName(instance interface{}, method string, replacer interface{}) func() {
return MockMethodByName(instance, method, buildInterceptorFromPatch(replacer))
recvPtr, funcInfo, funcPC, trappingPC := getMethodByName(instance, method)
return mock(recvPtr, funcInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer))
}

func buildInterceptorFromPatch(replacer interface{}) func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error {
func buildInterceptorFromPatch(recvPtr interface{}, replacer interface{}) func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error {
v := reflect.ValueOf(replacer)
t := v.Type()
if t.Kind() != reflect.Func {
Expand All @@ -26,12 +28,16 @@ func buildInterceptorFromPatch(replacer interface{}) func(ctx context.Context, f
panic("replacer is nil")
}
nIn := t.NumIn()

// first arg ctx: true => [recv,args[1:]...]
// first arg ctx: false => [recv, args[0:]...]
return func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error {
// assemble arguments
callArgs := make([]reflect.Value, nIn)
src := 0
dst := 0
if fn.RecvType != "" {
if fn.RecvType != "" && recvPtr != nil {
// patching an instance method
src++
}
if fn.FirstArgCtx {
Expand Down
3 changes: 2 additions & 1 deletion runtime/mock/patch_go1.17.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
package mock

func Patch(fn interface{}, replacer interface{}) func() {
return Mock(fn, buildInterceptorFromPatch(replacer))
recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn)
return mock(recvPtr, fnInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer))
}
3 changes: 2 additions & 1 deletion runtime/mock/patch_go1.18.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ package mock
// TODO: what if `fn` is a Type function
// instead of an instance method?
func Patch[T any](fn T, replacer T) func() {
return Mock(fn, buildInterceptorFromPatch(replacer))
recvPtr, fnInfo, funcPC, trappingPC := getFunc(fn)
return mock(recvPtr, fnInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer))
}
21 changes: 21 additions & 0 deletions runtime/test/patch/patch_type_method_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package patch

import (
"testing"

"github.com/xhd2015/xgo/runtime/mock"
)

func TestPatchTypeMethod(t *testing.T) {
ins := &struct_{
s: "world",
}
mock.Patch((*struct_).greet, func(ins *struct_) string {
return "mock " + ins.s
})

res := ins.greet()
if res != "mock world" {
t.Fatalf("expect patched result to be %q, actual: %q", "mock world", res)
}
}
Loading