github.com/tencent/goom@v1.0.1/internal/iface/make_interface.go (about)

     1  package iface
     2  
     3  import (
     4  	"reflect"
     5  	"unsafe"
     6  
     7  	"github.com/tencent/goom/internal/bytecode/stub"
     8  	"github.com/tencent/goom/internal/hack"
     9  )
    10  
    11  // IContext 接口 Mock 代码函数的接收体
    12  // 避免被 mock 的接口变量为 nil, 无法通过单测逻辑中 mock==nil 的判断
    13  type IContext struct {
    14  	// Data 可以传递任意数据
    15  	Data interface{}
    16  	// 代理上下文数据
    17  	p *PContext
    18  }
    19  
    20  // Cancel 取消接口代理
    21  func (c *IContext) Cancel() {
    22  	*c.p.originIface = *c.p.originIfaceValue
    23  	c.p.canceled = true
    24  }
    25  
    26  // Canceled 是否已经被取消
    27  func (c *IContext) Canceled() bool {
    28  	return c.p.canceled
    29  }
    30  
    31  // Cached 获取缓存数据
    32  func (c *IContext) Cached(key string) (v *hack.Iface, ok bool) {
    33  	v, ok = c.p.ifaceCache[key]
    34  	return
    35  }
    36  
    37  // Cache 缓存数据
    38  func (c *IContext) Cache(key string, value *hack.Iface) {
    39  	c.p.ifaceCache[key] = value
    40  }
    41  
    42  // NewContext 构造上下文
    43  func NewContext() *IContext {
    44  	return &IContext{
    45  		Data: nil,
    46  		p: &PContext{
    47  			ifaceCache: make(map[string]*hack.Iface, 32),
    48  		},
    49  	}
    50  }
    51  
    52  // PContext 代理上下文
    53  // 适配 proxy 包的 Context
    54  type PContext struct {
    55  	// ifaceCache iface 缓存
    56  	ifaceCache map[string]*hack.Iface
    57  	// originIface 原始接口地址
    58  	originIface *hack.Iface
    59  	// originIfaceValue 原始接口值
    60  	originIfaceValue *hack.Iface
    61  	// proxyFunc 代理函数, 需要内存持续持有
    62  	proxyFunc reflect.Value
    63  	// canceled 是否已经被取消
    64  	canceled bool
    65  }
    66  
    67  // PFunc 代理函数类型的签名
    68  type PFunc func(args []reflect.Value) (results []reflect.Value)
    69  
    70  // notImplement 未实现的接口方法被调用的函数, 未配置 mock 的接口方法默认会跳转到调用此函数
    71  func notImplement() {
    72  	panic("method not implements. (please write a mocker on it)")
    73  }
    74  
    75  // MakeInterface 构造 interface 对象, 包含 receive、funcTab 等数据
    76  func MakeInterface(ctx *IContext, funcTabIndex int, itabFunc uintptr, typ reflect.Type) *hack.Iface {
    77  	funcTabData := [hack.MaxMethod]uintptr{}
    78  	notImplements := reflect.ValueOf(notImplement).Pointer()
    79  	for i := 0; i < hack.MaxMethod; i++ {
    80  		funcTabData[i] = notImplements
    81  	}
    82  	funcTabData[funcTabIndex] = itabFunc
    83  
    84  	// 伪造 iface
    85  	structType := reflect.TypeOf(&IContext{})
    86  	return &hack.Iface{
    87  		Tab: &hack.Itab{
    88  			Inter: (*uintptr)((*hack.Iface)(unsafe.Pointer(&typ)).Data),
    89  			Type:  (*uintptr)((*hack.Iface)(unsafe.Pointer(&structType)).Data),
    90  			Fun:   funcTabData,
    91  		},
    92  		Data: unsafe.Pointer(ctx),
    93  	}
    94  }
    95  
    96  // BackUpTo 备份缓存 iface 指针到 IContext 中
    97  func BackUpTo(ctx *IContext, iface unsafe.Pointer) {
    98  	if ctx.p.originIfaceValue == nil {
    99  		ctx.p.originIface = (*hack.Iface)(iface)
   100  		originIfaceValue := *(*hack.Iface)(iface)
   101  		ctx.p.originIfaceValue = &originIfaceValue
   102  	}
   103  }
   104  
   105  // GenCallableMethod 生成可以直接 CALL 的接口方法实现, 带上下文 (rdx)
   106  func GenCallableMethod(ctx *IContext, apply interface{}, proxy PFunc) uintptr {
   107  	var (
   108  		methodCaller uintptr
   109  		err          error
   110  	)
   111  
   112  	if proxy == nil {
   113  		// 生成桩代码,rdx 寄存器还原
   114  		applyValue := reflect.ValueOf(apply)
   115  		mockFuncPtr := (*hack.Value)(unsafe.Pointer(&applyValue)).Ptr
   116  		methodCaller, err = MakeMethodCaller(mockFuncPtr)
   117  	} else {
   118  		// 生成桩代码,rdx 寄存器还原, 生成的调用将跳转到 proxy 函数
   119  		methodTyp := reflect.TypeOf(apply)
   120  		mockFunc := reflect.MakeFunc(methodTyp, proxy)
   121  		callStub := reflect.ValueOf(stub.MakeFuncStub).Pointer()
   122  		mockFuncPtr := (*hack.Value)(unsafe.Pointer(&mockFunc)).Ptr
   123  		methodCaller, err = MakeMethodCallerWithCtx(mockFuncPtr, callStub)
   124  		ctx.p.proxyFunc = mockFunc
   125  	}
   126  
   127  	if err != nil {
   128  		panic(err)
   129  	}
   130  	return methodCaller
   131  }