Files
test2/source/blender/depsgraph/intern/node/deg_node_id.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

203 lines
5.4 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
2017-12-20 16:35:48 +01:00
/** \file
* \ingroup depsgraph
2017-12-20 16:35:48 +01:00
*/
#include "intern/node/deg_node_id.hh"
2017-12-20 16:35:48 +01:00
#include <cstdio>
2017-12-20 16:35:48 +01:00
#include <cstring> /* required for STREQ later on. */
#include "BLI_string.h"
#include "BLI_utildefines.h"
2017-12-20 16:35:48 +01:00
#include "DNA_ID.h"
#include "DNA_anim_types.h"
2024-01-15 12:44:04 -05:00
#include "BKE_lib_id.hh"
2017-12-20 16:35:48 +01:00
#include "DEG_depsgraph.hh"
2017-12-20 16:35:48 +01:00
2017-12-20 16:40:49 +01:00
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/node/deg_node_component.hh"
#include "intern/node/deg_node_factory.hh"
#include "intern/node/deg_node_time.hh"
2017-12-20 16:35:48 +01:00
namespace blender::deg {
2017-12-20 16:35:48 +01:00
const char *linkedStateAsString(eDepsNode_LinkedState_Type linked_state)
{
switch (linked_state) {
case DEG_ID_LINKED_INDIRECTLY:
return "INDIRECTLY";
case DEG_ID_LINKED_VIA_SET:
return "VIA_SET";
case DEG_ID_LINKED_DIRECTLY:
return "DIRECTLY";
}
BLI_assert_msg(0, "Unhandled linked state, should never happen.");
return "UNKNOWN";
}
IDNode::ComponentIDKey::ComponentIDKey(NodeType type, const char *name) : type(type), name(name) {}
2017-12-20 16:35:48 +01:00
bool IDNode::ComponentIDKey::operator==(const ComponentIDKey &other) const
2017-12-20 16:35:48 +01:00
{
2017-12-20 16:40:49 +01:00
return type == other.type && STREQ(name, other.name);
2017-12-20 16:35:48 +01:00
}
uint64_t IDNode::ComponentIDKey::hash() const
{
const int type_as_int = int(type);
return BLI_ghashutil_combine_hash(BLI_ghashutil_uinthash(type_as_int),
BLI_ghashutil_strhash_p(name));
}
void IDNode::init(const ID *id, const char * /*subdata*/)
2017-12-20 16:35:48 +01:00
{
BLI_assert(id != nullptr);
2017-12-20 16:40:49 +01:00
/* Store ID-pointer. */
id_type = GS(id->name);
2017-12-20 16:40:49 +01:00
id_orig = (ID *)id;
id_orig_session_uid = id->session_uid;
2017-12-20 16:40:49 +01:00
eval_flags = 0;
previous_eval_flags = 0;
customdata_masks = DEGCustomDataMeshMasks();
previous_customdata_masks = DEGCustomDataMeshMasks();
2017-12-20 16:40:49 +01:00
linked_state = DEG_ID_LINKED_INDIRECTLY;
is_visible_on_build = true;
is_enabled_on_eval = true;
is_collection_fully_expanded = false;
has_base = false;
is_user_modified = false;
id_cow_recalc_backup = 0;
visible_components_mask = 0;
previously_visible_components_mask = 0;
2017-12-20 16:40:49 +01:00
}
2017-12-20 16:35:48 +01:00
Fix #119589: use-after-free when accessing not-fully-evaluated object geometry While the evaluated result is not well defined, we expect Blender to not crash when there are dependency cycles. The evaluation of one object often takes the evaluated geometry of another object into account. This works fine if the other object is already fully evaluated. However, if there is a dependency cycle, the other object may not be evaluated already. Currently, we have no way to check for this and were mostly just relying on luck that the other objects geometry is in some valid state (even if it's not the fully evaluated geometry). This patch adds the ability to explicitly check if an objects geometry is fully evaluated already, so that it can be accessed by other objects. If there are not dependency cycles, this should always be true. If not, it may be false sometimes, and in this case the other objects geometry should be ignored. The same also applies to the object transforms and the geometry of a collection. For that, new functions are added in `DEG_depsgraph_query.hh`. Those should be used whenever accessing another objects or collections object during depsgraph evaluation. More similar functions may be added in the future. ``` bool DEG_object_geometry_is_evaluated(const Object &object); bool DEG_object_transform_is_evaluated(const Object &object); bool DEG_collection_geometry_is_evaluated(const Collection &collection); ``` To determine if the these components are fully evaluated, a reference to the corresponding depsgraph is needed. A possible solution to that is to pass the depsgraph through the call stack to these functions. While possible, there are a couple of annoyances. For one, the parameter would need to be added in many new places. I don't have an exact number, but it's like 50 or so. Another complication is that under some circumstances, multiple depsgraphs may have to be passed around, for example when evaluating node tools (also see `GeoNodesOperatorDepsgraphs`). To simplify the patch and other code in the future, a different route is taken where the depsgraph pointer is added to `ID_Runtime`, making it readily accessible similar to the `ID.orig_id`. The depsgraph pointer is set in the same place where the `orig_id` is set. As a nice side benefit, this also improves the situation in simple cases like having two cubes with a boolean modifier and they union each other. Pull Request: https://projects.blender.org/blender/blender/pulls/123444
2024-06-20 15:24:38 +02:00
void IDNode::init_copy_on_write(Depsgraph &depsgraph, ID *id_cow_hint)
2017-12-20 16:40:49 +01:00
{
/* Create pointer as early as possible, so we can use it for function
* bindings. Rest of data we'll be copying to the new datablock when
* it is actually needed. */
if (id_cow_hint != nullptr) {
// BLI_assert(deg_eval_copy_is_needed(id_orig));
if (deg_eval_copy_is_needed(id_orig)) {
2017-12-20 16:40:49 +01:00
id_cow = id_cow_hint;
}
else {
id_cow = id_orig;
}
}
else if (deg_eval_copy_is_needed(id_orig)) {
2017-12-20 16:40:49 +01:00
id_cow = (ID *)BKE_libblock_alloc_notest(GS(id_orig->name));
DEG_COW_PRINT(
"Create shallow copy for %s: id_orig=%p id_cow=%p\n", id_orig->name, id_orig, id_cow);
Fix #119589: use-after-free when accessing not-fully-evaluated object geometry While the evaluated result is not well defined, we expect Blender to not crash when there are dependency cycles. The evaluation of one object often takes the evaluated geometry of another object into account. This works fine if the other object is already fully evaluated. However, if there is a dependency cycle, the other object may not be evaluated already. Currently, we have no way to check for this and were mostly just relying on luck that the other objects geometry is in some valid state (even if it's not the fully evaluated geometry). This patch adds the ability to explicitly check if an objects geometry is fully evaluated already, so that it can be accessed by other objects. If there are not dependency cycles, this should always be true. If not, it may be false sometimes, and in this case the other objects geometry should be ignored. The same also applies to the object transforms and the geometry of a collection. For that, new functions are added in `DEG_depsgraph_query.hh`. Those should be used whenever accessing another objects or collections object during depsgraph evaluation. More similar functions may be added in the future. ``` bool DEG_object_geometry_is_evaluated(const Object &object); bool DEG_object_transform_is_evaluated(const Object &object); bool DEG_collection_geometry_is_evaluated(const Collection &collection); ``` To determine if the these components are fully evaluated, a reference to the corresponding depsgraph is needed. A possible solution to that is to pass the depsgraph through the call stack to these functions. While possible, there are a couple of annoyances. For one, the parameter would need to be added in many new places. I don't have an exact number, but it's like 50 or so. Another complication is that under some circumstances, multiple depsgraphs may have to be passed around, for example when evaluating node tools (also see `GeoNodesOperatorDepsgraphs`). To simplify the patch and other code in the future, a different route is taken where the depsgraph pointer is added to `ID_Runtime`, making it readily accessible similar to the `ID.orig_id`. The depsgraph pointer is set in the same place where the `orig_id` is set. As a nice side benefit, this also improves the situation in simple cases like having two cubes with a boolean modifier and they union each other. Pull Request: https://projects.blender.org/blender/blender/pulls/123444
2024-06-20 15:24:38 +02:00
deg_tag_eval_copy_id(depsgraph, id_cow, id_orig);
2017-12-20 16:40:49 +01:00
}
else {
id_cow = id_orig;
}
2017-12-20 16:35:48 +01:00
}
/* Free 'id' node. */
IDNode::~IDNode()
2017-12-20 16:35:48 +01:00
{
2017-12-20 16:40:49 +01:00
destroy();
}
void IDNode::destroy()
2017-12-20 16:40:49 +01:00
{
if (id_orig == nullptr) {
2017-12-20 16:40:49 +01:00
return;
}
/* Free memory used by this evaluated ID. */
2020-11-06 12:30:59 +11:00
if (!ELEM(id_cow, id_orig, nullptr)) {
deg_free_eval_copy_datablock(id_cow);
2017-12-20 16:40:49 +01:00
MEM_freeN(id_cow);
id_cow = nullptr;
DEG_COW_PRINT(
"Destroy evaluated ID for %s: id_orig=%p id_cow=%p\n", id_orig->name, id_orig, id_cow);
2017-12-20 16:40:49 +01:00
}
for (ComponentNode *comp_node : components.values()) {
delete comp_node;
}
2017-12-20 16:40:49 +01:00
/* Tag that the node is freed. */
id_orig = nullptr;
2017-12-20 16:35:48 +01:00
}
string IDNode::identifier() const
{
char orig_ptr[24], cow_ptr[24];
2023-05-09 12:50:37 +10:00
SNPRINTF(orig_ptr, "%p", id_orig);
SNPRINTF(cow_ptr, "%p", id_cow);
return string(nodeTypeAsString(type)) + " : " + name + " (orig: " + orig_ptr +
", eval: " + cow_ptr + ", is_visible_on_build " +
(is_visible_on_build ? "true" : "false") + ")";
}
2019-02-15 16:00:54 +01:00
ComponentNode *IDNode::find_component(NodeType type, const char *name) const
2017-12-20 16:35:48 +01:00
{
ComponentIDKey key(type, name);
return components.lookup_default(key, nullptr);
2017-12-20 16:35:48 +01:00
}
2019-02-15 16:00:54 +01:00
ComponentNode *IDNode::add_component(NodeType type, const char *name)
2017-12-20 16:35:48 +01:00
{
ComponentNode *comp_node = find_component(type, name);
2017-12-20 16:35:48 +01:00
if (!comp_node) {
DepsNodeFactory *factory = type_get_factory(type);
BLI_assert(factory);
comp_node = (ComponentNode *)factory->create_node(this->id_orig, "", name);
2017-12-20 16:35:48 +01:00
/* Register. */
ComponentIDKey key(type, comp_node->name.c_str());
components.add_new(key, comp_node);
2017-12-20 16:35:48 +01:00
comp_node->owner = this;
}
return comp_node;
}
void IDNode::tag_update(Depsgraph *graph, eUpdateSource source)
2017-12-20 16:35:48 +01:00
{
for (ComponentNode *comp_node : components.values()) {
/* Relations update does explicit animation update when needed. Here we ignore animation
* component to avoid loss of possible unkeyed changes. */
if (comp_node->type == NodeType::ANIMATION && source == DEG_UPDATE_SOURCE_RELATIONS) {
continue;
}
comp_node->tag_update(graph, source);
2017-12-20 16:35:48 +01:00
}
}
void IDNode::finalize_build(Depsgraph *graph)
2017-12-20 16:35:48 +01:00
{
2017-12-20 16:40:49 +01:00
/* Finalize build of all components. */
for (ComponentNode *comp_node : components.values()) {
2017-12-20 16:40:49 +01:00
comp_node->finalize_build(graph);
2017-12-20 16:35:48 +01:00
}
visible_components_mask = get_visible_components_mask();
}
IDComponentsMask IDNode::get_visible_components_mask() const
{
IDComponentsMask result = 0;
for (ComponentNode *comp_node : components.values()) {
if (comp_node->possibly_affects_visible_id) {
const int component_type_as_int = int(comp_node->type);
BLI_assert(component_type_as_int < 64);
result |= (1ULL << component_type_as_int);
}
}
return result;
2017-12-20 16:35:48 +01:00
}
} // namespace blender::deg