java-topology/defects/cocos2d-0003/test/cocos2d-0003-test.cpp
russell@unturf.com 1b98cac200 cocos2d-x: 3 CWE-407 defects, MOAD 0002-0005 CLEAN
cocos2d-0001: EventDispatcher _toRemovedListeners std::find O(L*R) MEDIUM 2.4x
cocos2d-0002: PhysicsWorld collisionBeginCallback std::find O(J_body*J_world) MEDIUM 11.6x
cocos2d-0003: BoneNode::visit _boneSkins.contains O(C*S) per frame MEDIUM 7.3x

MOAD-0002 (Intertangle): heavy singleton pattern (Director, etc.) but architectural, not patchable
MOAD-0003 (Leaked Context): no thread_local usage, CLEAN
MOAD-0004 (Logged Secret): no credential logging, CLEAN
MOAD-0005 (Thundering Herd): TextureCache uses unordered_map, CLEAN

6/6 unit tests PASS.
2026-03-31 12:10:19 -04:00

90 lines
3.1 KiB
C++

// Unit test for cocos2d-0003: BoneNode::visit _boneSkins.contains O(C*S) per frame
// Defect: In the per-frame render traversal (visit), for each child node,
// _boneSkins.contains(node) does O(S) linear scan of the skins vector.
// With C children and S skins per bone, this is O(C*S) per bone per frame.
// Fix: Maintain an std::unordered_set<Node*> _boneSkinSet alongside the vector
// for O(1) membership test.
#include <vector>
#include <unordered_set>
#include <chrono>
#include <cstdio>
#include <cassert>
#include <algorithm>
struct Node { int id; };
// Defect: linear scan of skins vector for each child
int defectVisit(const std::vector<Node*>& children,
const std::vector<Node*>& boneSkins) {
int rendered = 0;
for (auto* node : children) {
// O(S) linear scan
if (std::find(boneSkins.begin(), boneSkins.end(), node) != boneSkins.end())
continue;
rendered++;
}
return rendered;
}
// Fixed: hash set for O(1) lookup
int fixedVisit(const std::vector<Node*>& children,
const std::unordered_set<Node*>& boneSkinSet) {
int rendered = 0;
for (auto* node : children) {
if (boneSkinSet.count(node) > 0)
continue;
rendered++;
}
return rendered;
}
int main() {
const int C = 500; // children per bone (complex character)
const int S = 200; // skins per bone
const int FRAMES = 1000; // per-frame render traversal
std::vector<Node> pool(C);
for (int i = 0; i < C; i++) pool[i].id = i;
std::vector<Node*> children;
for (int i = 0; i < C; i++) children.push_back(&pool[i]);
// First S children are skins
std::vector<Node*> boneSkins;
std::unordered_set<Node*> boneSkinSet;
for (int i = 0; i < S; i++) {
boneSkins.push_back(&pool[i]);
boneSkinSet.insert(&pool[i]);
}
// === Defect ===
auto t0 = std::chrono::high_resolution_clock::now();
int defectTotal = 0;
for (int f = 0; f < FRAMES; f++)
defectTotal += defectVisit(children, boneSkins);
auto t1 = std::chrono::high_resolution_clock::now();
double defectUs = std::chrono::duration<double, std::micro>(t1 - t0).count();
// === Fixed ===
auto t2 = std::chrono::high_resolution_clock::now();
int fixedTotal = 0;
for (int f = 0; f < FRAMES; f++)
fixedTotal += fixedVisit(children, boneSkinSet);
auto t3 = std::chrono::high_resolution_clock::now();
double fixedUs = std::chrono::duration<double, std::micro>(t3 - t2).count();
assert(defectTotal == fixedTotal);
int expectedRendered = (C - S) * FRAMES;
assert(defectTotal == expectedRendered);
double ratio = defectUs / fixedUs;
printf("cocos2d-0003: BoneNode::visit _boneSkins.contains O(C*S) per frame\n");
printf(" C=%d children, S=%d skins, FRAMES=%d\n", C, S, FRAMES);
printf(" defect: %.1f us\n", defectUs);
printf(" fixed: %.1f us\n", fixedUs);
printf(" ratio: %.1fx\n", ratio);
printf(" PASS: %s\n", (defectTotal == expectedRendered) ? "true" : "false");
return (defectTotal == expectedRendered) ? 0 : 1;
}