Fix and regression test for crash during node group updates #104696

Closed
Lukas Tönne wants to merge 4 commits from LukasTonne/blender:nodes-undefined-group-tree-update-crash into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
3 changed files with 102 additions and 0 deletions

View File

@ -833,6 +833,7 @@ if(WITH_GTESTS)
intern/lib_id_test.cc
intern/lib_remap_test.cc
intern/nla_test.cc
intern/node_test.cc
intern/tracking_test.cc
)
set(TEST_INC

View File

@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. */
#include "testing/testing.h"
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BKE_callbacks.h"
#include "BKE_context.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_main_namemap.h"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "DNA_ID.h"
#include "DNA_node_types.h"
#include "RNA_define.h"
#include "CLG_log.h"
namespace blender::bke::node::tests {
struct NodeTreeTestContext {
Review

Did you use some other tests as reference here? How to decide what goes into NodeTreeTestContext and what into NodeTreeTest?

Generally, I think the fix can be committed first without this test, but it's interesting to see how we could test this. Wouldn't mind adding this test separately, but I'd want to check if this is really initializing all the things that would have to be initialized.

Did you use some other tests as reference here? How to decide what goes into `NodeTreeTestContext` and what into `NodeTreeTest`? Generally, I think the fix can be committed first without this test, but it's interesting to see how we could test this. Wouldn't mind adding this test separately, but I'd want to check if this is really initializing all the things that would have to be initialized.
Review

Yes, i copied most of this from other tests, e.g. blend_file_loading_base_test.cc, lib_id_test.cc.

I only used the necessary parts for making node trees ID blocks and updating them. All the stuff in SetUpTestSuite/TearDownTestSuite is for one-off initialization that can stay over multiple tests. The "context" is temporary stuff (make new Main and a context) that gets thrown away after each test. Could do that in the virtual SetUp/TearDown for each fixture, but i like the explicitness of the "context". No strong opinion.

Yes, i copied most of this from other tests, e.g. `blend_file_loading_base_test.cc`, `lib_id_test.cc`. I only used the necessary parts for making node trees ID blocks and updating them. All the stuff in `SetUpTestSuite`/`TearDownTestSuite` is for one-off initialization that can stay over multiple tests. The "context" is temporary stuff (make new `Main` and a context) that gets thrown away after each test. Could do that in the virtual `SetUp`/`TearDown` for each fixture, but i like the explicitness of the "context". No strong opinion.
Review

Here's a for just the fix, into the release branch: #105564

Here's a for just the fix, into the release branch: #105564
Main *bmain = nullptr;
bContext *C = nullptr;
NodeTreeTestContext()
{
BKE_idtype_init();
bmain = BKE_main_new();
C = CTX_create();
}
~NodeTreeTestContext()
{
CTX_free(C);
BKE_main_free(bmain);
}
};
class NodeTreeTest : public testing::Test {
public:
static void SetUpTestSuite()
{
CLG_init();
BKE_callback_global_init();
RNA_init();
BKE_node_system_init();
}
static void TearDownTestSuite()
{
RNA_exit();
BKE_node_system_exit();
BKE_callback_global_finalize();
CLG_exit();
}
void TearDown() override
{
}
};
/* Regression test: node groups with trees with undefined type could crash on update. */
TEST_F(NodeTreeTest, NT_undefined_node_group_update)
{
NodeTreeTestContext ctx;
/* Invalid idname that results in an "undefined" tree type. */
bNodeTree *group_tree = ntreeAddTree(ctx.bmain, "GroupTree", "???");
EXPECT_NE(group_tree, nullptr);
EXPECT_EQ(group_tree->typeinfo, &NodeTreeTypeUndefined);
/* Node tree with a node group linking to the undefined tree. */
bNodeTree *node_tree = ntreeAddTree(ctx.bmain, "NodeTree", "GeometryNodeTree");
EXPECT_NE(node_tree, nullptr);
EXPECT_STREQ(node_tree->typeinfo->idname, "GeometryNodeTree");
bNode *group_node = nodeAddNode(ctx.C, node_tree, "GeometryNodeGroup");
EXPECT_NE(group_node, nullptr);
EXPECT_STREQ(group_node->typeinfo->idname, "GeometryNodeGroup");
group_node->id = &group_tree->id;
EXPECT_NE(group_tree->runtime, nullptr);
bNodeTreeRuntime *rt = static_cast<bNodeTreeRuntime *>(group_tree->runtime);
BKE_ntree_update_tag_all(group_tree);
EXPECT_NE(rt->changed_flag, 0);
BKE_ntree_update_main(ctx.bmain, nullptr);
}
} // namespace blender::bke::node::tests

View File

@ -23,6 +23,11 @@ static const aal::RelationsInNode &get_relations_in_node(const bNode &node, Reso
{
if (node.is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node.id)) {
/* Undefined tree types have no relations. */
LukasTonne marked this conversation as resolved
Review

Comment style, missing dot. Same in other places.

Comment style, missing dot. Same in other places.
if (!ntreeIsRegistered(group)) {
LukasTonne marked this conversation as resolved
Review

Better change ntreeIsRegistered to take a const node tree.

Better change `ntreeIsRegistered` to take a `const` node tree.

This has already been done, so you can simply remove the const cast

This has already been done, so you can simply remove the const cast
return scope.construct<aal::RelationsInNode>();
}
BLI_assert(group->runtime->anonymous_attribute_relations);
return *group->runtime->anonymous_attribute_relations;
}