tolua环境中利用元表实现变量改变、访问的追溯

tolua环境中利用元表实现变量改变、访问的追溯

工作中在遇到某些bug的时候,经常需要对某个变量进行追溯,查看他在什么时候发生改变,改变的值是什么,一般来说可以使用print直接将值输出打点的形式进行调试,但对于某些代码不熟悉的情况,这种方式肯定需要消耗大量的时间去阅读一些无关的代码,所以笔者构思利用元表来对代码直接进行追溯。

环境

本文的代码支持cocos2dx-lua,其他环境未经过测试,如果需要请自行修改调试。

本文的代码目前只支持对已有的table进行追溯,对于local出来的变量或者全局变量并不适用(一个猜想,如果local环境与全局环境也是一个table的话,也是适用的,但是这里并没有研究)。

代码

话不多说直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
--+----------------------------------
--+ @author #ccffee
--+----------------------------------
__selfprint = print
__selfdump = dump
---移花接木,将一个表(该表)的元表改变为传入的表,将传入表的元表改为该表原来的元表
---@param t1 table 该表
---@param t2 table 传入的表,如果这个表原先就有元表,那么这个函数会覆盖掉原来的元表
function setmetatableParent(t1, t2)
---获取t1原来的元表
local mt = getmetatable(t1)
if type(t1) == 'userdata' then
mt = getmetatable(tolua.getpeer(t1))
end
if not mt then mt = {} end
---将t2设置为t1的元表
if type(t1) == "userdata" then
local peer = tolua.getpeer(t1)
if not peer then
peer = {}
tolua.setpeer(t1, peer)
end
setmetatable(peer, t2)
else
setmetatable(t1, t2)
end
---将t1原来的元表设置为t2的元表
setmetatable(t2, mt)
end

---这个方法用于追溯一个table上某个key值变量在何处被改变、被访问
---@param t table target所在的表
---@param targetKey string 要追查改变变量的key值
---@param isNeedShowGet boolean 是否需要显示访问
function debugTraceChange(t, targetKey, isNeedShowGet)
local swap = t[targetKey]
t[targetKey] = nil

local traceTable = {
__index = function(this, key)
if key == targetKey then
if isNeedShowGet then
__selfprint('------------------------------------------------------------------')
__selfprint(targetKey .. '对应值被访问')
__selfprint(debug.traceback())
__selfprint('------------------------------------------------------------------')
end
return swap
end

return getmetatable(this)[key]
end,
__newindex = function(this, key, value)
if key == targetKey then
__selfprint('------------------------------------------------------------------')
__selfprint(targetKey .. '对应值被改变,value为:' .. tostring(value))
if type(value) == 'table' then
__selfdump(value)
end
__selfprint(debug.traceback())
__selfprint('------------------------------------------------------------------')
swap = value
return
end
rawset(this, key, value)
end
}

setmetatableParent(t, traceTable)
end

使用

如下测试代码:

1
2
3
4
5
6
7
8
local t1 = {}
t1.a1 = 2

debugTraceChange(t1, 'a1')

t1.a1 = 3

t1.a1 = 4

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[LUA-print] ------------------------------------------------------------------
[LUA-print] a1对应值被改变,value为:3
[LUA-print] stack traceback:
[string "selfConfig.lua"]:328: in function '__newindex'
[string "selfDebug.lua"]:23: in function 'selfDebug'
[string "selfDebug.lua"]:29: in main chunk
[C]: in function 'require'
[string "selfConfig.lua"]:201: in function 'func'
[LUA-print] ------------------------------------------------------------------
[LUA-print] ------------------------------------------------------------------
[LUA-print] a1对应值被改变,value为:4
[LUA-print] stack traceback:
[string "selfConfig.lua"]:328: in function '__newindex'
[string "selfDebug.lua"]:25: in function 'selfDebug'
[string "selfDebug.lua"]:29: in main chunk
[C]: in function 'require'
[string "selfConfig.lua"]:201: in function 'func'
[LUA-print] ------------------------------------------------------------------

说明

代码中的__selfprint__selfdump是笔者用来屏蔽项目中其他输出用的,这里可以直接使用printdump

代码中使用了元表的__index来检测值的读取,使用__newindex来检测值的改变,并且实现了setmetatableParent方法来兼容cocos2dx-lua的面向对象实现(cocos2dx-lua也是使用元表来实现面向对象的),setmetatableParent方法主要是将传入的表,这里就是对应的检测元表,作为另外一个表的元表,并且将另外一个表的元表作为该表的元表,相当于在原型链中插入了一个原型。其余的关键点自行百度,这里就不再赘述。


tolua环境中利用元表实现变量改变、访问的追溯
http://ccffee.fun/2022/07/19/tolua环境中利用元表实现变量改变、访问的追溯/
作者
ccffee
发布于
2022年7月19日
许可协议