New diamond recursion defects (O(2^D) → O(N)): - godot-0009: Font::_is_cyclic no visited set — CJK fallback diamond, 2648x at F=4,D=8 - godot-0010: Font::_update_rids_fb no visited set — duplicate RIDs + O(N^2) hot path - meson-0002: get_internal_static_libraries_recurse link_whole guard missing — 132x at D=10 - typescript-0003: hasBaseType inner check() no visited set — 1024x at D=10; hot on instanceof New O(N²) defects: - typeorm-0004: SubjectTopologicalSorter Array.indexOf dedup — 200x at N=400 - typeorm-0005: DepGraph.createDFS result.indexOf + addDependency edge dedup — 300x at N=600 CLEAN confirmed (diamond recursion sweep): bazel, cargo, cmake, composer, dgl, diesel, doctrine-orm, efcore, helm, mybatis, networkx-deeper, ninja, npm-arborist, peewee, pip, rubygems, seaorm, sqlalchemy, swift UNDF: 571→578 assigned; MOAD count: 629→635
72 lines
2.4 KiB
Diff
72 lines
2.4 KiB
Diff
# UNDF: UNDF-2026-000000404
|
|
--- a/scene/resources/font.h
|
|
+++ b/scene/resources/font.h
|
|
@@ -31,6 +31,7 @@
|
|
#include "core/io/resource.h"
|
|
#include "core/templates/hash_map.h"
|
|
+#include "core/templates/hash_set.h"
|
|
#include "core/templates/list.h"
|
|
#include "core/variant/typed_array.h"
|
|
#include "servers/text_server.h"
|
|
@@ -96,7 +97,8 @@ class Font : public Resource {
|
|
static void _bind_methods();
|
|
|
|
virtual void _update_rids_fb(const Font *p_f, int p_depth) const;
|
|
- virtual void _update_rids() const;
|
|
+ virtual void _update_rids() const; // delegates to _update_rids_fb
|
|
virtual void reset_state() override;
|
|
|
|
public:
|
|
- virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const;
|
|
- virtual bool _is_base_cyclic(const Ref<Font> &p_f, int p_depth) const;
|
|
+ // FIX godot-0009: add visited HashSet to _is_cyclic to avoid O(F^D) re-traversal
|
|
+ virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const; // public wrapper
|
|
+ bool _is_cyclic_internal(const Ref<Font> &p_f, int p_depth, HashSet<const Font *> &r_visited) const;
|
|
+ virtual bool _is_base_cyclic(const Ref<Font> &p_f, int p_depth) const;
|
|
virtual void _invalidate_rids();
|
|
--- a/scene/resources/font.cpp
|
|
+++ b/scene/resources/font.cpp
|
|
@@ -134,16 +134,28 @@ void Font::_invalidate_rids() {
|
|
|
|
bool Font::_is_cyclic(const Ref<Font> &p_f, int p_depth) const {
|
|
- ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, true);
|
|
- if (p_f.is_null()) {
|
|
- return false;
|
|
- }
|
|
- if (p_f == this) {
|
|
- return true;
|
|
- }
|
|
- for (int i = 0; i < p_f->fallbacks.size(); i++) {
|
|
- const Ref<Font> &f = p_f->fallbacks[i];
|
|
- if (_is_cyclic(f, p_depth + 1)) {
|
|
- return true;
|
|
- }
|
|
- }
|
|
- return false;
|
|
+ // FIX godot-0009: was O(F^D) — shared fonts in diamond fallback graph
|
|
+ // re-traversed exponentially; now O(N) with visited set.
|
|
+ HashSet<const Font *> visited;
|
|
+ return _is_cyclic_internal(p_f, p_depth, visited);
|
|
+}
|
|
+
|
|
+bool Font::_is_cyclic_internal(const Ref<Font> &p_f, int p_depth, HashSet<const Font *> &r_visited) const {
|
|
+ ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, true);
|
|
+ if (p_f.is_null()) {
|
|
+ return false;
|
|
+ }
|
|
+ if (p_f == this) {
|
|
+ return true;
|
|
+ }
|
|
+ const Font *raw = p_f.ptr();
|
|
+ if (r_visited.has(raw)) {
|
|
+ return false; // already proven non-cyclic from this node; skip
|
|
+ }
|
|
+ r_visited.insert(raw);
|
|
+ for (int i = 0; i < p_f->fallbacks.size(); i++) {
|
|
+ const Ref<Font> &f = p_f->fallbacks[i];
|
|
+ if (_is_cyclic_internal(f, p_depth + 1, r_visited)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
}
|