Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}

// Fix animation by changing node transform.
// CWE-407 fix (redot-0007): build HashSet for O(1) .has() — was O(B) Vector scan per track.
HashSet<int> bones_to_process_set;
{
Vector<int> _btp = src_skeleton->get_parentless_bones();
for (int _i = 0; _i < _btp.size(); _i++) { bones_to_process_set.insert(_btp[_i]); }
}
bones_to_process = src_skeleton->get_parentless_bones();
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
Expand Down Expand Up @@ -200,7 +206,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
int bone_idx = src_skeleton->find_bone(bn);
int key_len = anim->track_get_key_count(i);
if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D) {
if (bones_to_process.has(bone_idx)) {
if (bones_to_process_set.has(bone_idx)) { // CWE-407 fix (redot-0007): O(1)
for (int j = 0; j < key_len; j++) {
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, global_transform.basis.xform(ps) + global_transform.origin);
Expand All @@ -211,7 +217,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
anim->track_set_key_value(i, j, ps * scl);
}
}
} else if (bones_to_process.has(bone_idx)) {
} else if (bones_to_process_set.has(bone_idx)) { // CWE-407 fix (redot-0007): O(1)
if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
for (int j = 0; j < key_len; j++) {
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
Expand Down Expand Up @@ -625,7 +631,8 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory

// Scan hierarchy and populate a whitelist of unmapped bones without mapped descendants.
// When both is_using_modifier and is_using_global_pose are enabled, this array is used for detecting warning.
Vector<int> keep_bone_rest;
// CWE-407 fix (redot-0007): was Vector<int> — O(K) .has() called inside O(T) track loop.
HashSet<int> keep_bone_rest;
if (is_using_modifier || keep_global_rest_leftovers) {
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
while (bones_to_process.size() > 0) {
Expand Down Expand Up @@ -658,7 +665,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}

if (!found_mapped) {
keep_bone_rest.push_back(src_idx); // No mapped descendants. Add to whitelist.
keep_bone_rest.insert(src_idx);
}
}
}
Expand Down
29 changes: 17 additions & 12 deletions modules/gltf/gltf_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,11 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state) {
}

Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const {
Vector<String> extensions_used = p_state->extensions_used;
// CWE-407 fix (redot-0008): extensions_used is now HashSet — convert to sorted Vector for JSON.
Vector<String> extensions_used;
for (const String &ext : p_state->extensions_used) {
extensions_used.push_back(ext);
}
Vector<String> extensions_required = p_state->extensions_required;
if (!p_state->lights.is_empty()) {
extensions_used.push_back("KHR_lights_punctual");
Expand Down Expand Up @@ -442,12 +446,9 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
Dictionary khr_node_visibility;
extensions["KHR_node_visibility"] = khr_node_visibility;
khr_node_visibility["visible"] = gltf_node->visible;
if (!p_state->extensions_used.has("KHR_node_visibility")) {
p_state->extensions_used.push_back("KHR_node_visibility");
if (_visibility_mode == VISIBILITY_MODE_INCLUDE_REQUIRED) {
p_state->extensions_required.push_back("KHR_node_visibility");
}
}
// CWE-407 fix (redot-0008): add_used_extension() guards both extensions_used (HashSet, O(1))
// and extensions_required (has() check) — safe to call on every hidden node.
p_state->add_used_extension("KHR_node_visibility", _visibility_mode == VISIBILITY_MODE_INCLUDE_REQUIRED);
}
if (gltf_node->mesh != -1) {
node["mesh"] = gltf_node->mesh;
Expand Down Expand Up @@ -5509,9 +5510,8 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
}
if (!gltf_animation->get_pointer_tracks().is_empty()) {
// Serialize glTF pointer tracks with the KHR_animation_pointer extension.
if (!p_state->extensions_used.has("KHR_animation_pointer")) {
p_state->extensions_used.push_back("KHR_animation_pointer");
}
// CWE-407 fix (redot-0008): HashSet.insert() is idempotent O(1).
p_state->extensions_used.insert("KHR_animation_pointer");
for (KeyValue<String, GLTFAnimation::Channel<Variant>> &pointer_track_iter : gltf_animation->get_pointer_tracks()) {
const String &json_pointer = pointer_track_iter.key;
const GLTFAnimation::Channel<Variant> &pointer_track = pointer_track_iter.value;
Expand Down Expand Up @@ -8921,7 +8921,8 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
state->scene_name = p_node->get_name();
} else {
if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) {
state->extensions_used.append("GODOT_single_root");
// CWE-407 fix (redot-0008): HashSet.insert() is idempotent O(1).
state->extensions_used.insert("GODOT_single_root");
}
_convert_scene_node(state, p_node, -1, -1);
}
Expand Down Expand Up @@ -8992,8 +8993,12 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(p_state.is_null(), ERR_PARSE_ERROR);
if (p_state->json.has("extensionsUsed")) {
// CWE-407 fix (redot-0008): populate HashSet from parsed JSON array.
Vector<String> ext_array = p_state->json["extensionsUsed"];
p_state->extensions_used = ext_array;
p_state->extensions_used.clear();
for (int i = 0; i < ext_array.size(); i++) {
p_state->extensions_used.insert(ext_array[i]);
}
}
if (p_state->json.has("extensionsRequired")) {
Vector<String> ext_array = p_state->json["extensionsRequired"];
Expand Down
15 changes: 12 additions & 3 deletions modules/gltf/gltf_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension);
ClassDB::bind_method(D_METHOD("get_extensions_required"), &GLTFState::get_extensions_required);
ClassDB::bind_method(D_METHOD("has_used_extension", "extension_name"), &GLTFState::has_used_extension);
ClassDB::bind_method(D_METHOD("append_data_to_buffers", "data", "deduplication"), &GLTFState::append_data_to_buffers);
ClassDB::bind_method(D_METHOD("append_gltf_node", "gltf_node", "godot_scene_node", "parent_node_index"), &GLTFState::append_gltf_node);

Expand Down Expand Up @@ -144,16 +146,23 @@ void GLTFState::_bind_methods() {
}

void GLTFState::add_used_extension(const String &p_extension_name, bool p_required) {
if (!extensions_used.has(p_extension_name)) {
extensions_used.push_back(p_extension_name);
}
// CWE-407 fix (redot-0008): HashSet.insert() is idempotent O(1) — was O(E) has()+push_back().
extensions_used.insert(p_extension_name);
if (p_required) {
if (!extensions_required.has(p_extension_name)) {
extensions_required.push_back(p_extension_name);
}
}
}

Vector<String> GLTFState::get_extensions_required() const {
return extensions_required;
}

bool GLTFState::has_used_extension(const String &p_extension) const {
return extensions_used.has(p_extension);
}

Dictionary GLTFState::get_json() {
return json;
}
Expand Down
6 changes: 5 additions & 1 deletion modules/gltf/gltf_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ class GLTFState : public Resource {
Vector<Ref<GLTFTextureSampler>> texture_samplers;
Ref<GLTFTextureSampler> default_texture_sampler;
Vector<Ref<Texture2D>> images;
Vector<String> extensions_used;
// CWE-407 fix (redot-0008): was Vector<String> — O(E) .has() called per node/animation track.
HashSet<String> extensions_used;
Vector<String> extensions_required;
Vector<Ref<Image>> source_images;

Expand Down Expand Up @@ -125,6 +126,9 @@ class GLTFState : public Resource {
}

void add_used_extension(const String &p_extension, bool p_required = false);
// CWE-407 test accessors: expose read-only views of the extension sets for assertions.
Vector<String> get_extensions_required() const;
bool has_used_extension(const String &p_extension) const;
GLTFBufferViewIndex append_data_to_buffers(const Vector<uint8_t> &p_data, const bool p_deduplication);
GLTFNodeIndex append_gltf_node(Ref<GLTFNode> p_gltf_node, Node *p_godot_scene_node, GLTFNodeIndex p_parent_node_index);

Expand Down
30 changes: 22 additions & 8 deletions modules/godot_physics_2d/godot_body_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ class GodotBody2D : public GodotCollisionObject2D {
};

Vector<AreaCMP> areas;
// CWE-407 fix (redot-0002): stores refCount per RID for O(1) hot-path lookup.
// areas Vector is preserved for priority-ordered iteration in integrate_forces().
// Storing refCount (not position) avoids staleness after areas.sort() or ordered_insert().
HashMap<RID, int> area_refcount;

struct Contact {
Vector2 local_pos;
Expand Down Expand Up @@ -164,20 +168,30 @@ class GodotBody2D : public GodotCollisionObject2D {
GodotPhysicsDirectBodyState2D *get_direct_state();

_FORCE_INLINE_ void add_area(GodotArea2D *p_area) {
int index = areas.find(AreaCMP(p_area));
if (index > -1) {
areas.write[index].refCount += 1;
// CWE-407 fix (redot-0002): area_refcount is the sole refCount source — O(1).
// Hot path (existing overlap): O(1) HashMap increment, no Vector scan.
// Cold path (new overlap): O(N) ordered_insert + O(1) HashMap insert.
// areas Vector is only used for priority-ordered iteration in integrate_forces();
// integrate_forces() does not read AreaCMP::refCount, so Vector's field is unused.
RID rid = p_area->get_self();
HashMap<RID, int>::Iterator it = area_refcount.find(rid);
if (it != area_refcount.end()) {
it->value += 1;
} else {
areas.ordered_insert(AreaCMP(p_area));
area_refcount[rid] = 1;
}
}

_FORCE_INLINE_ void remove_area(GodotArea2D *p_area) {
int index = areas.find(AreaCMP(p_area));
if (index > -1) {
areas.write[index].refCount -= 1;
if (areas[index].refCount < 1) {
areas.remove_at(index);
// CWE-407 fix (redot-0002): O(1) refCount check — O(N) Vector find only on final removal.
RID rid = p_area->get_self();
HashMap<RID, int>::Iterator it = area_refcount.find(rid);
if (it != area_refcount.end()) {
it->value -= 1;
if (it->value < 1) {
areas.remove_at(areas.find(AreaCMP(p_area)));
area_refcount.erase(it);
}
}
}
Expand Down
28 changes: 20 additions & 8 deletions modules/godot_physics_3d/godot_body_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ class GodotBody3D : public GodotCollisionObject3D {
HashMap<GodotConstraint3D *, int> constraint_map;

Vector<AreaCMP> areas;
// CWE-407 fix (redot-0003): stores refCount per RID for O(1) hot-path lookup.
// areas Vector preserved for priority-ordered iteration in integrate_forces().
// Storing refCount (not position) avoids staleness after areas.sort() or ordered_insert().
HashMap<RID, int> area_refcount;

struct Contact {
Vector3 local_pos;
Expand Down Expand Up @@ -158,20 +162,28 @@ class GodotBody3D : public GodotCollisionObject3D {
GodotPhysicsDirectBodyState3D *get_direct_state();

_FORCE_INLINE_ void add_area(GodotArea3D *p_area) {
int index = areas.find(AreaCMP(p_area));
if (index > -1) {
areas.write[index].refCount += 1;
// CWE-407 fix (redot-0003): area_refcount is the sole refCount source — O(1).
// Hot path (existing overlap): O(1) HashMap increment, no Vector scan.
// Cold path (new overlap): O(N) ordered_insert + O(1) HashMap insert.
RID rid = p_area->get_self();
HashMap<RID, int>::Iterator it = area_refcount.find(rid);
if (it != area_refcount.end()) {
it->value += 1;
} else {
areas.ordered_insert(AreaCMP(p_area));
area_refcount[rid] = 1;
}
}

_FORCE_INLINE_ void remove_area(GodotArea3D *p_area) {
int index = areas.find(AreaCMP(p_area));
if (index > -1) {
areas.write[index].refCount -= 1;
if (areas[index].refCount < 1) {
areas.remove_at(index);
// CWE-407 fix (redot-0003): O(1) refCount check — O(N) Vector find only on final removal.
RID rid = p_area->get_self();
HashMap<RID, int>::Iterator it = area_refcount.find(rid);
if (it != area_refcount.end()) {
it->value -= 1;
if (it->value < 1) {
areas.remove_at(areas.find(AreaCMP(p_area)));
area_refcount.erase(it);
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions modules/godot_physics_3d/godot_soft_body_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,20 +654,26 @@ void GodotSoftBody3D::generate_bending_constraints(int p_distance) {

// Special optimized case for distance == 2.
if (p_distance == 2) {
// CWE-407 fix (redot-0004): was LocalVector<int>.has() — O(n) per link, O(n²) total.
// node_link_set provides O(1) membership; LocalVector preserved for iteration.
LocalVector<LocalVector<int>> node_links;
LocalVector<HashSet<int>> node_link_set;

// Build node links.
node_links.resize(nodes.size());
node_link_set.resize(nodes.size());

for (Link &link : links) {
const int ia = (int)(link.n[0] - &nodes[0]);
const int ib = (int)(link.n[1] - &nodes[0]);
if (!node_links[ia].has(ib)) {
if (!node_link_set[ia].has(ib)) {
node_links[ia].push_back(ib);
node_link_set[ia].insert(ib);
}

if (!node_links[ib].has(ia)) {
if (!node_link_set[ib].has(ia)) {
node_links[ib].push_back(ia);
node_link_set[ib].insert(ia);
}
}
for (uint32_t ii = 0; ii < node_links.size(); ii++) {
Expand Down
7 changes: 4 additions & 3 deletions scene/3d/skeleton_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ void Skeleton3D::_update_process_order() const {

for (int i = 0; i < len; i++) {
bonesptr[i].child_bones.clear();
bonesptr[i].child_bones_set.clear();
}

for (int i = 0; i < len; i++) {
Expand All @@ -233,10 +234,10 @@ void Skeleton3D::_update_process_order() const {
if (bonesptr[i].parent != -1) {
int parent_bone_idx = bonesptr[i].parent;

// Check to see if this node is already added to the parent.
if (!bonesptr[parent_bone_idx].child_bones.has(i)) {
// Add the child node.
// CWE-407 fix (redot-0006): child_bones_set.has() is O(1); Vector.has() was O(C) linear scan.
if (!bonesptr[parent_bone_idx].child_bones_set.has(i)) {
bonesptr[parent_bone_idx].child_bones.push_back(i);
bonesptr[parent_bone_idx].child_bones_set.insert(i);
} else {
ERR_PRINT("Skeleton3D parenthood graph is cyclic");
}
Expand Down
2 changes: 2 additions & 0 deletions scene/3d/skeleton_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class Skeleton3D : public Node3D {

int parent = -1;
Vector<int> child_bones;
// CWE-407 fix (redot-0006): shadow index for O(1) has() — Vector preserved for ordered iteration and indexed access.
HashSet<int> child_bones_set;

Transform3D rest;
Transform3D global_rest;
Expand Down
19 changes: 15 additions & 4 deletions scene/3d/spring_bone_simulator_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,15 @@ void SpringBoneSimulator3D::_find_collisions() {
}
}

// CWE-407 fix (redot-0012): build O(1) lookup structures once instead of O(N)
// LocalVector::has/find inside S×C inner loops. O(S×C×N) → O(N + S×C).
HashSet<ObjectID> collision_set;
HashMap<ObjectID, int> collision_index_map;
for (uint32_t ci = 0; ci < collisions.size(); ci++) {
collision_set.insert(collisions[ci]);
collision_index_map[collisions[ci]] = (int)ci;
}

bool setting_updated = false;

for (int i = 0; i < settings.size(); i++) {
Expand All @@ -1452,7 +1461,8 @@ void SpringBoneSimulator3D::_find_collisions() {
continue;
}
ObjectID id = n->get_instance_id();
if (!collisions.has(id)) {
// CWE-407 fix (redot-0012): O(1) via HashSet instead of O(N) LocalVector::has.
if (!collision_set.has(id)) {
setting_collisions.write[j] = NodePath(); // Clear path if not found.
} else {
cache.push_back(id);
Expand All @@ -1468,11 +1478,12 @@ void SpringBoneSimulator3D::_find_collisions() {
continue;
}
ObjectID id = n->get_instance_id();
int find = collisions.find(id);
if (find < 0) {
// CWE-407 fix (redot-0012): O(1) via HashMap instead of O(N) LocalVector::find.
const int *find_ptr = collision_index_map.getptr(id);
if (!find_ptr) {
setting_exclude_collisions.write[j] = NodePath(); // Clear path if not found.
} else {
masks.push_back((uint32_t)find);
masks.push_back((uint32_t)*find_ptr);
}
}
uint32_t mask_index = 0;
Expand Down
Loading