使用 Tcl C API 的 Lib 可能由于错误的 refCount 使用而崩溃

Posted

技术标签:

【中文标题】使用 Tcl C API 的 Lib 可能由于错误的 refCount 使用而崩溃【英文标题】:Lib using Tcl C API is crashing maybe due bad refCount usage 【发布时间】:2021-06-28 16:57:51 【问题描述】:

我是 Tcl C API 的初学者,我正在尝试了解如何使用它。我有这段代码从get_my_list proc 中获取一个tcl 列表,然后我对其进行迭代以发送一些信息,在这种情况下,与ABC 属性相关的信息是通过@987654325 获得的@过程。当我在一个基本上只有这段代码的非常简单的示例中运行它时,一切正常,但是当我在一个大的 tcl 项目中添加这个库时,它最终崩溃了。我怀疑这是由于 tcl 对象的引用计数使用不当,我的意思是,它们的生命周期。在下面的示例中,我可能做错了什么?我正在使用 Tcl 8.6。

Tcl_Obj* Get_Info(Tcl_Interp *interp, const char* info, const char* attr) 
    char cmd[256];
    sprintf(cmd, "get_attr_info %s %s", info, attr);
    Tcl_Eval(interp, cmd);
    return Tcl_GetObjResult(interp);


static int Copy_Info(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 
    Tcl_Eval(interp, "get_my_list");
    Tcl_Obj *const my_list = Tcl_GetObjResult(interp);
    int my_list_size;
    Tcl_ListObjLength(interp, my_list, &my_list_size);
    Tcl_IncrRefCount(my_list);

    for (int i = 0; i < my_list_size; ++i) 
        Tcl_Obj* current_info_obj;
        Tcl_ListObjIndex(interp, my_list, i, &current_info_obj);
        const char* current_info_ctr = Tcl_GetStringFromObj(current_info_obj, NULL);

        /* getting info A */
        Tcl_Obj* info_a_obj = Get_Info(current_info_ctr, "A");
        const char* info_a = Tcl_GetStringFromObj(info_a_obj, NULL);
        Copy_Info_A(info_a);

        /* getting info B */
        Tcl_Obj* info_b_obj = Get_Info(current_info_ctr, "B");
        const char* info_b = Tcl_GetStringFromObj(info_b_obj, NULL);
        Copy_Info_B(info_b);

        /* getting info C */
        Tcl_Obj* info_c_obj = Get_Info(current_info_ctr, "C");
        const char* info_c = Tcl_GetStringFromObj(info_c_obj, NULL);
        Copy_Info_C(info_c);
    

    Tcl_DecrRefCount(my_list);
    Tcl_FreeResult(interp);

    return TCL_OK;

【问题讨论】:

【参考方案1】:

棘手的一点是,您在 Get_Info() 中调用 Tcl_Eval(),它可以通过对您没有自己引用的值的引用计数管理来做各种事情,包括闪烁掉您在my_list 中引用的值,它可以将地毯从其元素下方拉出。特别是,current_info_obj 中引用的对象必须Tcl_ListObjIndex() 之后到循环体的末尾增加其引用计数。

// ...
for (int i = 0; i < my_list_size; ++i) 
    Tcl_Obj* current_info_obj;
    Tcl_ListObjIndex(interp, my_list, i, &current_info_obj);
    // HOLD THE REFERENCE; IT OWNS THE current_info_ctr STRING FOR US
    Tcl_IncrRefCount(current_info_obj);
    const char* current_info_ctr = Tcl_GetStringFromObj(current_info_obj, NULL);

    /* getting info A */
    Tcl_Obj* info_a_obj = Get_Info(current_info_ctr, "A");
    const char* info_a = Tcl_GetStringFromObj(info_a_obj, NULL);
    Copy_Info_A(info_a);

    /* getting info B */
    Tcl_Obj* info_b_obj = Get_Info(current_info_ctr, "B");
    const char* info_b = Tcl_GetStringFromObj(info_b_obj, NULL);
    Copy_Info_B(info_b);

    /* getting info C */
    Tcl_Obj* info_c_obj = Get_Info(current_info_ctr, "C");
    const char* info_c = Tcl_GetStringFromObj(info_c_obj, NULL);
    Copy_Info_C(info_c);

    // RELEASE THE REFERENCE
    Tcl_DecrRefCount(current_info_obj);

// ...

改进您的代码

您也可以使用快捷方式,使用Tcl_GetString(objPtr) 代替Tcl_GetStringFromObj(objPtr, NULL)。你可以把它放在Get_Info 中。您可能还应该考虑改用 Tcl_EvalObjv(),因为这是一种更快的 API(它避免了一定程度的解析),但使用起来更复杂。

const char *Get_Info(Tcl_Interp *interp, Tcl_Obj* info, const char* attr) 
    Tcl_Obj *argv[3], *result;

    argv[0] = Tcl_NewStringObj("get_attr_info", -1); // cacheable
    argv[1] = info;
    argv[2] = Tcl_NewStringObj(attr, -1);
    Tcl_IncrRefCount(argv[0]);
    Tcl_IncrRefCount(argv[1]);
    Tcl_IncrRefCount(argv[2]);

    // It's very bad form to omit error handling
    if (Tcl_EvalObjv(interp, 3, argv, 0) != TCL_OK) 
        Tcl_Panic("problem: %s", Tcl_GetString(Tcl_GetObjResult(interp)));
    
    result = Tcl_GetObjResult(interp);
    Tcl_DecrRefCount(argv[0]);
    Tcl_DecrRefCount(argv[1]);
    Tcl_DecrRefCount(argv[2]);
    return Tcl_GetString(result);

在某些情况下,可以减少引用计数处理量。不要担心一开始就这样做!最好绝对避免崩溃!

Tcl_EvalObjv 是(有效地)Tcl_Eval 在交互模式下解析后调用以实际调度命令,它是字节码引擎调用以评估任何非内联命令的方法。这比将字符串解析为单词要高效得多。

【讨论】:

调试引用计数管理相当困难。使用启用内存调试(配置时选项)的 Tcl 构建会有所帮助。 另外,我可能会首先在 Tcl 代码中放置这样一个循环,然后让 C 代码在没有发现代码的情况下执行 Copy_Info_A(info_a) 完成了测试并按照您的建议进行了改进。一切都很完美。非常感谢!

以上是关于使用 Tcl C API 的 Lib 可能由于错误的 refCount 使用而崩溃的主要内容,如果未能解决你的问题,请参考以下文章

lib.tcl

tcl错误代码01401-1

Caused by: java.lang.IllegalStateException: 由于StackOverflower错误,无法完成对web应用程序[/lib]的批注的扫描。可能的根本原因包...

由于 lib 错误,Visual Studio 未编译

如何解决 C++Builder 中的链接器错误“LIBCURL.LIB 包含无效的 OMF 记录,类型 0x21(可能是 COFF)”?

将 Qt 函数导出到 Tcl