gomonkey icon indicating copy to clipboard operation
gomonkey copied to clipboard

ApplyMethod possibly not work with generic types

Open qiuchengxuan opened this issue 1 year ago • 2 comments

这里定义了一个泛型的结构体,大致是这样:

type Table[T any] struct {
    ...
}

func (t *Table[T]) Add(network net.IPNet, value T) bool {
    ...
}

这里内容无关紧要,在使用gomonkey patch了叫Add的方法后,发现单元测试并未成功,调用的依旧是原本的Add。

进一步定位发现,reflect包获取到的方法名和实际的Add方法的signature并不相同,实际上方法地址也并不相同

reflect获取的方法名和地址:

0x1c56aa0
XXX.(*Table[string]).Add(SB) <autogenerated>
        <autogenerated>:1       0x1c56aa0       493b6610                cmp rsp, qword ptr [r14+0x10]
        <autogenerated>:1       0x1c56aa4       0f86cd000000            jbe 0x1c56b77

实际调用的方法名和地址:

XXX.(*Table[go.shape.string_0]).Add(SB) .../cidrtable.go
=>      cidrtable.go:29 0x1c56020       4c8da42458ffffff        lea r12, ptr [rsp+0xffffff58]
        cidrtable.go:29 0x1c56028       4d3b6610                cmp r12, qword ptr [r14+0x10]

gomonkey是通过取reflect记录的地址来修改实际调用的,但是看来这个方式对于泛型方法暂且不适用

golang版本1.18.2和1.18.5均存在上述问题

qiuchengxuan avatar Aug 26 '22 08:08 qiuchengxuan

请参考《gomonkey用户如何对泛型打桩》[https://www.jianshu.com/p/8a52eae7f786]

agiledragon avatar Sep 12 '22 12:09 agiledragon

请参考《gomonkey用户如何对泛型打桩》[https://www.jianshu.com/p/8a52eae7f786]

嗯,但是对于间接调用泛型方法的用例恐怕不可行

不过我似乎找到了解决办法,reflect拿到的方法地址,第一个调用将会是callsite调用的实际的泛型方法,那么这里找到这个第一个call指向的地址,就能正确的为泛型方法打桩,具体如下:

首先定义一个FindFirstCall

package gomonkey

import (
	"reflect"
	"unsafe"

	"golang.org/x/arch/x86/x86asm"
)

func FindFirstCall(ptr uintptr) uintptr {
	addr := ptr
	for addr < ptr+65535 {
		sliceHeader := reflect.SliceHeader{Data: addr, Len: 15, Cap: 15}
		code := *(*[]byte)(unsafe.Pointer(&sliceHeader))
		inst, err := x86asm.Decode(code, 64)
		if err != nil {
			return ptr
		}
		if inst.Op == x86asm.CALL {
			if v, ok := inst.Args[0].(x86asm.Rel); ok {
				return uintptr(int64(addr) + int64(inst.Len) + int64(v))
			}
		}
		addr += uintptr(inst.Len)
	}
	return ptr
}

然后ApplyMethod时根据类型名确定是否为泛型类型

func (this *Patches) ApplyMethod(target interface{}, methodName string, double interface{}) *Patches {
	rtype := castRType(target)
	m, ok := rtype.MethodByName(methodName)
	if !ok {
		panic("retrieve method by name failed")
	}
	d := reflect.ValueOf(double)
	generics := strings.ContainsRune(rtype.String(), '[')
	return this.ApplyCore(m.Func, d, generics)
}

最后ApplyCore时如果是泛型方法,那么需要找到实际的地址


func (this *Patches) ApplyCore(target, double reflect.Value, generics ...bool) *Patches {
	this.check(target, double)
	assTarget := *(*uintptr)(getPointer(target))
	if len(generics) > 0 && generics[0] {
		assTarget = FindFirstCall(assTarget)
	}
	original := replace(assTarget, uintptr(getPointer(double)))
	if _, ok := this.originals[assTarget]; !ok {
		this.originals[assTarget] = original
	}
	this.valueHolders[double] = double
	return this
}

这样是否可以?

qiuchengxuan avatar Sep 16 '22 14:09 qiuchengxuan