Skip to content

Commit

Permalink
support patching variables
Browse files Browse the repository at this point in the history
  • Loading branch information
xhd2015 committed Apr 6, 2024
1 parent 1b67f1d commit c81e0ac
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 14 deletions.
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.18"
const REVISION = "1211c519c8005ddbd66189cf64e958aa69e5789f+1"
const NUMBER = 163
const REVISION = "2c003e4f895ef51b9c22e7c5bb5018dd6007ff96+1"
const NUMBER = 164

func getRevision() string {
revSuffix := ""
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.18"
const REVISION = "1211c519c8005ddbd66189cf64e958aa69e5789f+1"
const NUMBER = 163
const REVISION = "2c003e4f895ef51b9c22e7c5bb5018dd6007ff96+1"
const NUMBER = 164

// these fields will be filled by compiler
const XGO_VERSION = ""
Expand Down
67 changes: 57 additions & 10 deletions runtime/mock/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/xhd2015/xgo/runtime/core"
"github.com/xhd2015/xgo/runtime/functab"
)

// Patch replaces `fn` with `replacer` in current goroutine,
Expand All @@ -28,10 +29,19 @@ func Patch(fn interface{}, replacer interface{}) func() {
panic(fmt.Errorf("replacer should have type: %T, actual: %T", fn, replacer))
}
} else if fnKind == reflect.Pointer {
replacerType := reflect.TypeOf(replacer)
wantType := reflect.FuncOf(nil, []reflect.Type{fnType.Elem()}, false)
var targetTypeStr string
var replacerTypeStr string
var match bool
if reflect.TypeOf(replacer).Kind() != reflect.Func {
// TODO: validate return value
t := fnType.Elem().String()
panic(fmt.Errorf("replacer should be a func()%s or func()*%s, actual: %T", t, t, replacer))
targetTypeStr = wantType.String()
replacerTypeStr = replacerType.String()
} else {
targetTypeStr, replacerTypeStr, match = checkFuncTypeMatch(wantType, replacerType, false)
}
if !match {
panic(fmt.Errorf("replacer should have type: %s, actual: %s", targetTypeStr, replacerTypeStr))
}
} else {
panic(fmt.Errorf("fn should be func or pointer to variable, actual: %T", fn))
Expand All @@ -52,12 +62,52 @@ func PatchByName(pkgPath string, funcName string, replacer interface{}) func() {

// check type
recvPtr, funcInfo, funcPC, trappingPC := getFuncByName(pkgPath, funcName)
if funcInfo.Func != nil {
calledType, replacerType, match := checkFuncTypeMatch(reflect.TypeOf(funcInfo.Func), t, recvPtr != nil)
if funcInfo.Kind == core.Kind_Func {
if funcInfo.Func != nil {
calledType, replacerType, match := checkFuncTypeMatch(reflect.TypeOf(funcInfo.Func), t, recvPtr != nil)
if !match {
panic(fmt.Errorf("replacer should have type: %s, actual: %s", calledType, replacerType))
}
}
} else if funcInfo.Kind == core.Kind_Var || funcInfo.Kind == core.Kind_VarPtr || funcInfo.Kind == core.Kind_Const {
varPtrType := reflect.TypeOf(funcInfo.Var)
var wantValueType reflect.Type
if funcInfo.Kind == core.Kind_Var {
wantValueType = reflect.FuncOf(nil, []reflect.Type{varPtrType.Elem()}, false)
} else {
// const: type is not pointer
wantValueType = reflect.FuncOf(nil, []reflect.Type{varPtrType}, false)
}

var targetTypeStr string
var replacerTypeStr string
var match bool

var matchPtr bool
replacerType := reflect.TypeOf(replacer)
if replacerType.Kind() != reflect.Func {
targetTypeStr = wantValueType.String()
replacerTypeStr = replacerType.String()
} else {
targetTypeStr, replacerTypeStr, match = checkFuncTypeMatch(wantValueType, replacerType, false)
if !match && funcInfo.Kind != core.Kind_VarPtr {
_, replacerTypeStr, match = checkFuncTypeMatch(reflect.FuncOf(nil, []reflect.Type{varPtrType}, false), replacerType, false)
matchPtr = true
}
}
if !match {
panic(fmt.Errorf("replacer should have type: %s, actual: %s", calledType, replacerType))
panic(fmt.Errorf("replacer should have type: %s, actual: %s", targetTypeStr, replacerTypeStr))
}
if matchPtr {
funcInfo = functab.Info(pkgPath, "*"+funcName)
if funcInfo == nil {
panic(fmt.Errorf("failed to patch: %s *%s", pkgPath, funcName))
}
}
} else {
panic(fmt.Errorf("unrecognized func type: %s", funcInfo.Kind.String()))
}

return mock(recvPtr, funcInfo, funcPC, trappingPC, buildInterceptorFromPatch(recvPtr, replacer))
}

Expand Down Expand Up @@ -151,6 +201,7 @@ func buildInterceptorFromPatch(recvPtr interface{}, replacer interface{}) func(c
}
}

// a,b must be func type
func checkFuncTypeMatch(a reflect.Type, b reflect.Type, skipAFirst bool) (atype string, btype string, match bool) {
na := a.NumIn()
nb := b.NumIn()
Expand Down Expand Up @@ -186,10 +237,6 @@ func checkFuncTypeMatch(a reflect.Type, b reflect.Type, skipAFirst bool) (atype
return "", "", true
}

// func func(fn reflect.Type, in []reflect.Type, out []reflect.Type, vardaric bool, skipAFirst bool) (atype string, btype string, match bool) {

// }

func formatFuncType(f reflect.Type, skipFirst bool) string {
n := f.NumIn()
i := 0
Expand Down
83 changes: 83 additions & 0 deletions runtime/test/patch/patch_var_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package patch

import (
"fmt"
"testing"

"github.com/xhd2015/xgo/runtime/mock"
Expand All @@ -17,3 +18,85 @@ func TestPatchVarTest(t *testing.T) {
t.Fatalf("expect patched varaibel a to be %d, actual: %d", 456, b)
}
}

func TestPatchVarWrongTypeShouldFailTest(t *testing.T) {
var pe interface{}
func() {
defer func() {
pe = recover()
}()
mock.Patch(&a, func() *int {
v := 456
return &v
})
b := a
if b != 456 {
t.Fatalf("expect patched varaibel a to be %d, actual: %d", 456, b)
}
}()
expectMsg := "replacer should have type: func() int, actual: func() *int"

if pe == nil {
t.Fatalf("expect panic: %q, actual nil", expectMsg)
}
msg := fmt.Sprint(pe)
if msg != expectMsg {
t.Fatalf("expect err %q, actual: %q", expectMsg, msg)
}
}

const pkgName = "github.com/xhd2015/xgo/runtime/test/patch"

func TestPatchVarByNameTest(t *testing.T) {
mock.PatchByName(pkgName, "a", func() int {
return 456
})
b := a
if b != 456 {
t.Fatalf("expect patched variable a to be %d, actual: %d", 456, b)
}
}

func TestPatchVarByNamePtrTest(t *testing.T) {
mock.PatchByName(pkgName, "a", func() *int {
x := 456
return &x
})
pb := &a
b := *pb
if b != 456 {
t.Fatalf("expect patched variable a to be %d, actual: %d", 456, b)
}
}

const testVersion = "1.0"

func TestPatchConstByNamePtrTest(t *testing.T) {
mock.PatchByName(pkgName, "testVersion", func() string {
return "1.5"
})
version := testVersion
if version != "1.5" {
t.Fatalf("expect patched version a to be %s, actual: %s", "1.5", version)
}
}

func TestPatchConstByNameWrongTypeShouldFail(t *testing.T) {
var pe interface{}
func() {
defer func() {
pe = recover()
}()
mock.PatchByName(pkgName, "a", func() string {
return "1.5"
})
}()
expectMsg := "replacer should have type: func() int, actual: func() string"
if pe == nil {
t.Fatalf("expect panic: %q, actual nil", expectMsg)
}
msg := fmt.Sprint(pe)
if msg != expectMsg {
t.Fatalf("expect err %q, actual: %q", expectMsg, msg)
}
}

0 comments on commit c81e0ac

Please sign in to comment.