自动编写 MCVE(重现错误的最小源代码)?
Writing an MCVE (minimal source code that reproduces an error) automatically?
当我想问一个问题,例如Whosebug 我通常要post的源代码。
问题是,我使用了相当大的自定义框架,类 结构等,与问题相关的部分可能在很多地方被本地化(有时很难检测到哪些代码部分是问题很重要)。我无法 post 完整的源代码(它太大而无法有效阅读)。
出于这个原因,我通常会努力编写重现问题的最小代码(通常是一个 main.cpp
而不是成吨的 类)。
我想知道 - 是否可以使该过程自动化?
这里要做的典型事情是用它们的主体替换 methods/functions' 调用,将文件合并为一个 .cpp
,删除所有 "not called" 方法和 类,未使用的变量等。
这里真正的困难在于区分 "it doesn't do what I want"、"the bug went away because I removed the essential code" 和 "it now crashes because I removed something important" 的情况。实际上,在标记一些代码 "don't need it" 后按下删除键是简单的部分。
找出显示问题所必需的内容是困难的部分,而且很难将其自动化 - 因为有必要了解代码应该做什么和实际做什么之间的区别。只是随机删除代码是行不通的,因为 "new" 代码可能会被破坏,因为您删除了一些必要的步骤,而不是因为您删除了未使用的 crud - 只有 [理解问题的] 人才可以做到这一点。
考虑一下:
Object* something;
void Initialize()
{
something = new Object(1, 2, 3);
}
int main()
{
Initialize();
// Some more code, some of which SOMETIMES sets something = NULL.
something->doStuff(); // Will crash if object is NULL.
}
如果我们删除 Initialize
,代码将每次都失败,而不仅仅是每三次。但是是因为Object没有初始化,而不是代码的bug[可能是我们应该在something->doStuff()
之前加上if (something)
,或者因为我们不应该设置成NULL
] 在 "some more code" 中,因此 "don't do that"]。
当我处理一个棘手的问题时,尤其是在我们有测试系统自动生成代码以在不同条件下测试不同功能的工作中,我的第一步是制作[或采用一些现有的]代码 "small standalone test",它既小又简单,而且只做 "what is necessary",而不是试图减少成千上万行做很多额外事情的复杂代码。
对于某些项目,有一些工具可以帮助识别 "which bit of the code is the problem",例如 [bugpoint
][1] 可以发现 LLVM 中的哪个 "pass" 很容易导致崩溃。
如果您有支持此功能的版本控制系统,您可以 "bisect" 编写代码以得出引入特定错误的版本 [至少有时是这样]。然而,我在工作中有一个案例,我的一些代码 "apparently broke things",但事实证明其他一些代码是 "broken all the time since a long time back",因为其他代码没有清除 API 的指针字段手册说应该设置为 NULL
,而我的代码正在检查指针以查明它是否指向正确的类型——当值为 "whatever happens to be in that part of the stack" 时会出现严重错误,所以它不是NULL
而不是有效指针。我只是添加了使此错误明显而不是隐藏自身的代码。
当我想问一个问题,例如Whosebug 我通常要post的源代码。
问题是,我使用了相当大的自定义框架,类 结构等,与问题相关的部分可能在很多地方被本地化(有时很难检测到哪些代码部分是问题很重要)。我无法 post 完整的源代码(它太大而无法有效阅读)。
出于这个原因,我通常会努力编写重现问题的最小代码(通常是一个 main.cpp
而不是成吨的 类)。
我想知道 - 是否可以使该过程自动化?
这里要做的典型事情是用它们的主体替换 methods/functions' 调用,将文件合并为一个 .cpp
,删除所有 "not called" 方法和 类,未使用的变量等。
这里真正的困难在于区分 "it doesn't do what I want"、"the bug went away because I removed the essential code" 和 "it now crashes because I removed something important" 的情况。实际上,在标记一些代码 "don't need it" 后按下删除键是简单的部分。
找出显示问题所必需的内容是困难的部分,而且很难将其自动化 - 因为有必要了解代码应该做什么和实际做什么之间的区别。只是随机删除代码是行不通的,因为 "new" 代码可能会被破坏,因为您删除了一些必要的步骤,而不是因为您删除了未使用的 crud - 只有 [理解问题的] 人才可以做到这一点。
考虑一下:
Object* something;
void Initialize()
{
something = new Object(1, 2, 3);
}
int main()
{
Initialize();
// Some more code, some of which SOMETIMES sets something = NULL.
something->doStuff(); // Will crash if object is NULL.
}
如果我们删除 Initialize
,代码将每次都失败,而不仅仅是每三次。但是是因为Object没有初始化,而不是代码的bug[可能是我们应该在something->doStuff()
之前加上if (something)
,或者因为我们不应该设置成NULL
] 在 "some more code" 中,因此 "don't do that"]。
当我处理一个棘手的问题时,尤其是在我们有测试系统自动生成代码以在不同条件下测试不同功能的工作中,我的第一步是制作[或采用一些现有的]代码 "small standalone test",它既小又简单,而且只做 "what is necessary",而不是试图减少成千上万行做很多额外事情的复杂代码。
对于某些项目,有一些工具可以帮助识别 "which bit of the code is the problem",例如 [bugpoint
][1] 可以发现 LLVM 中的哪个 "pass" 很容易导致崩溃。
如果您有支持此功能的版本控制系统,您可以 "bisect" 编写代码以得出引入特定错误的版本 [至少有时是这样]。然而,我在工作中有一个案例,我的一些代码 "apparently broke things",但事实证明其他一些代码是 "broken all the time since a long time back",因为其他代码没有清除 API 的指针字段手册说应该设置为 NULL
,而我的代码正在检查指针以查明它是否指向正确的类型——当值为 "whatever happens to be in that part of the stack" 时会出现严重错误,所以它不是NULL
而不是有效指针。我只是添加了使此错误明显而不是隐藏自身的代码。