找回密码
 register
搜索
查看: 35|回复: 0

洱海月 移除交易物品有空指针崩溃点

[复制链接]
  • 打卡等级:本地老炮
  • 打卡总天数:533
  • 打卡月天数:22
  • 打卡总奖励:530
  • 最近打卡:2026-06-24 01:45:59
Waylee 发表于 2026-4-1 17:18 | 显示全部楼层 |阅读模式 | Google Chrome | Windows 10

马上注册,查看网站隐藏内容!!

您需要 登录 才可以下载或查看,没有账号?register

×

崩溃点:

01/04/26 17:17:08.62 [:000000a8] lua call [1fa to :a8 : 2691 msgsz = 0] error : ./framework/lualib/skynet.lua:988: ./framework/lualib/skynet.lua:452: ./framework/lualib/skynet/queue.lua:20: ./framework/lualib/skynet.lua:720: call failed
stack traceback:
    [C]: in function 'error'
    ./framework/lualib/skynet.lua:720: in upvalue 'yield_call'
    ./framework/lualib/skynet.lua:737: in function 'skynet.call'
    ./services/msgagent.lua:2028: in local 'f'
    ./lualib/net.lua:130: in function 'net.dispatch_message'
    ./services/msgagent.lua:2456: in local 'f'
    ./services/msgagent.lua:2633: in function <./services/msgagent.lua:2631>
    [C]: in function 'xpcall'
    ./framework/lualib/skynet/queue.lua:34: in upvalue 'lock'
    ./services/msgagent.lua:2631: in upvalue 'f'
    ./framework/lualib/skynet.lua:402: in function <./framework/lualib/skynet.lua:374>
stack traceback:
    [C]: in function 'assert'
    ./framework/lualib/skynet/queue.lua:20: in function <./framework/lualib/skynet/queue.lua:12>
    (...tail calls...)
    ./services/msgagent.lua:2631: in upvalue 'f'
    ./framework/lualib/skynet.lua:402: in function <./framework/lualib/skynet.lua:374>
stack traceback:
    [C]: in function 'assert'
    ./framework/lualib/skynet.lua:988: in function 'skynet.dispatch_message'
01/04/26 17:17:08.63 [:000001fa] lua call [a8 to :1fa : 2691 msgsz = 78] error : ./framework/lualib/skynet.lua:988: ./framework/lualib/skynet.lua:452: ./services/scene/scenecore.lua:8847: attempt to index a nil value (local 'item')
stack traceback:
    ./services/scene/scenecore.lua:8847: in function 'scene.scenecore.char_exchange_sync_item_II'
    (...tail calls...)
    ./services/scene/scene.lua:26: in upvalue 'realFun'
    ./lualib/profile.lua:56: in upvalue 'f'
    ./framework/lualib/skynet.lua:402: in function <./framework/lualib/skynet.lua:374>
stack traceback:
    [C]: in function 'assert'
    ./framework/lualib/skynet.lua:988: in function 'skynet.dispatch_message'

玩家交易移除物品空指针崩溃修复说明

问题现象

玩家交易过程中,服务端处理“从交易盒移除物品”的入口在:

  • services/scene/scenecore.lua
  • char_exchange_sync_item_II
  • OPT_REMOVEITEM

原逻辑里,如果客户端重放异常封包,或者传入一个已经为空的交易格,服务端会先对空对象调用:

item:get_guid()

这会直接触发空指针错误,严重时可能把场景逻辑打崩。

也就是说,这不是普通的“交易失败”,而是一个会把服务端执行流打断的崩溃点。


根因分析

原始代码顺序有问题

文件:services/scene/scenecore.lua

原始代码:

elseif ei.opt == packet_def.CGExchangeSynchItemII.OPT.OPT_REMOVEITEM then
    if ei.to_type == packet_def.CGExchangeSynchItemII.POS.POS_BAG then
        local item = my_item_container:get_item(ei.from_index)
        local prop_bag_container = obj_me:get_prop_bag_container()
        local bag_index = prop_bag_container:get_index_by_guid(item:get_guid())
        if not bag_index then
            local msg = packet_def.GCExchangeError.new()
            msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
            self:send2client(obj_me, msg)
            return
        end
        if item then
            my_item_container:erase_item(ei.from_index)
            item_operator:unlock_item(prop_bag_container, bag_index)
            item:set_in_exchange(false)
            ...
        end
    end
end

问题就在这一句:

local bag_index = prop_bag_container:get_index_by_guid(item:get_guid())

它发生在:

if item then
    ...
end

之前。

换句话说,代码还没确认 item 存在,就已经先使用 item:get_guid() 了。

my_item_container:get_item(ei.from_index) 返回 nil 时,就会直接报错。


触发方式

理论上至少有下面几种情况会命中:

  1. 客户端向一个空交易格发送 OPT_REMOVEITEM
  2. 同一个移除封包被异常重放两次,第一次已经移除了物品,第二次格子为空。
  3. 交易状态已被其他逻辑清掉,但客户端又补发了旧的移除包。

这些情况本来都应该被服务端当作“非法操作”拒绝,而不是进入 Lua 运行时错误。


修复目标

这次修复只做一件事:

  1. 在读取 item:get_guid() 之前,先确认 item 不为空。

也就是说:

  1. 异常包应该被安全拒绝。
  2. 正常移除交易物品流程不能受影响。
  3. 修复必须尽量小,避免影响已有交易逻辑。

修改文件

本次只修改一个文件:

  1. services/scene/scenecore.lua

修复方案

原始代码

local item = my_item_container:get_item(ei.from_index)
local prop_bag_container = obj_me:get_prop_bag_container()
local bag_index = prop_bag_container:get_index_by_guid(item:get_guid())
if not bag_index then
    local msg = packet_def.GCExchangeError.new()
    msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
    self:send2client(obj_me, msg)
    return
end
if item then
    my_item_container:erase_item(ei.from_index)
    item_operator:unlock_item(prop_bag_container, bag_index)
    item:set_in_exchange(false)
    ...
end

修改后代码

local item = my_item_container:get_item(ei.from_index)
local prop_bag_container = obj_me:get_prop_bag_container()
if not item then
    local msg = packet_def.GCExchangeError.new()
    msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
    self:send2client(obj_me, msg)
    return
end
local bag_index = prop_bag_container:get_index_by_guid(item:get_guid())
if not bag_index then
    local msg = packet_def.GCExchangeError.new()
    msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
    self:send2client(obj_me, msg)
    return
end
my_item_container:erase_item(ei.from_index)
item_operator:unlock_item(prop_bag_container, bag_index)
item:set_in_exchange(false)
...

完整修复片段

建议直接对照这一段修改:

elseif ei.opt == packet_def.CGExchangeSynchItemII.OPT.OPT_REMOVEITEM then
    if ei.to_type == packet_def.CGExchangeSynchItemII.POS.POS_BAG then
        local item = my_item_container:get_item(ei.from_index)
        local prop_bag_container = obj_me:get_prop_bag_container()
        if not item then
            local msg = packet_def.GCExchangeError.new()
            msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
            self:send2client(obj_me, msg)
            return
        end
        local bag_index = prop_bag_container:get_index_by_guid(item:get_guid())
        if not bag_index then
            local msg = packet_def.GCExchangeError.new()
            msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
            self:send2client(obj_me, msg)
            return
        end
        my_item_container:erase_item(ei.from_index)
        item_operator:unlock_item(prop_bag_container, bag_index)
        item:set_in_exchange(false)
        if bag_index ~= define.INVAILD_ID then
            local msg = packet_def.GCExchangeSynchII.new()
            msg.is_my_self = 1
            msg.opt = packet_def.CGExchangeSynchItemII.OPT.OPT_REMOVEITEM
            msg.to_type = packet_def.CGExchangeSynchItemII.POS.POS_BAG
            msg.to_index = bag_index
            msg.from_index = ei.from_index
            self:send2client(obj_me, msg)

            msg = packet_def.GCExchangeSynchII.new()
            msg.is_my_self = 0
            msg.opt = packet_def.CGExchangeSynchItemII.OPT.OPT_REMOVEITEM
            msg.from_index = ei.from_index
            self:send2client(obj_tar, msg)
            return
        else
            local msg = packet_def.GCExchangeError.new()
            msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
            self:send2client(obj_me, msg)
        end
    end
end

为什么这样修就够了

这个问题的根本原因不是交易状态机错了,而是访问顺序错了。

所以最小修复原则就是:

  1. 先判空。
  2. 再访问对象字段或方法。

这里不需要大改交易逻辑,也不需要改协议。

只要保证:

if not item then
    return illegal
end

item:get_guid() 之前执行,就已经能把崩溃问题彻底挡住。


为什么返回 ERR_ILLEGAL

当交易格为空时,客户端发来的“移除该格物品”本身就是非法状态。

所以这里最合理的处理方式不是静默吞掉,而是回:

define.EXCHANGE_ERR.ERR_ILLEGAL

这样做的好处是:

  1. 客户端能收到明确失败反馈。
  2. 服务端不会继续执行异常逻辑。
  3. 不会把这种错误误判成“背包空间问题”或“交易盒满了”。

建议测试项

1. 正常移除交易物品

步骤:

  1. 发起交易。
  2. 放入一个正常物品。
  3. 从交易盒移除该物品。

预期:

  1. 正常移除成功。
  2. 物品回到背包。
  3. lock 状态解除。
  4. in_exchange 状态置回 false

2. 对空交易格发送移除包

步骤:

  1. 发起交易,但不要往某个交易格放物品。
  2. 直接向该空格发送 OPT_REMOVEITEM

预期:

  1. 服务端不崩溃。
  2. 返回 GCExchangeError
  3. 错误码为 ERR_ILLEGAL

3. 重放移除封包

步骤:

  1. 放入一个交易物品。
  2. 正常移除一次。
  3. 再重放同一个移除包。

预期:

  1. 第一次正常移除。
  2. 第二次被服务端安全拒绝。
  3. 不出现 Lua 空指针错误。

4. 交易取消后重放旧移除包

步骤:

  1. 放入物品。
  2. 取消交易。
  3. 重放之前的 OPT_REMOVEITEM 包。

预期:

  1. 服务端安全拒绝。
  2. 不崩溃。
  3. 不影响后续新的交易流程。

修复后效果

修复前:

  1. 交易格为空。
  2. 客户端发送 OPT_REMOVEITEM
  3. 服务端执行 item:get_guid()
  4. 直接触发空指针错误。

修复后:

  1. 交易格为空。
  2. 客户端发送 OPT_REMOVEITEM
  3. 服务端先判空。
  4. 发现无物品,直接返回 ERR_ILLEGAL
  5. 不再触发崩溃。

总结

这次修复的核心非常明确:

任何来自客户端的对象访问,都必须先做空值保护,再取字段。

在交易系统里,这一条尤其重要,因为:

  1. 客户端可能重放旧包。
  2. 客户端可能发顺序错乱的包。
  3. 服务端必须把这类异常流量当作“非法请求”处理,而不是让它进入运行时崩溃。

这次修改虽然很小,但它修掉的是一个真正的高危稳定性问题。

您需要登录后才可以回帖 登录 | register

本版积分规则

QQ|雪舞知识库 ( 浙ICP备15015590号-1 | 萌ICP备20232229号|浙公网安备33048102000118号 )|天天打卡

GMT+8, 2026-6-24 05:47 , Processed in 0.061385 second(s), 25 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表