117 lines
5 KiB
Markdown
117 lines
5 KiB
Markdown
# AngelScript — CWE-407 Disclosure Brief
|
||
**2026-03-27 · Patch available — awaiting upstream merge**
|
||
|
||
## Finding
|
||
|
||
Three O(n²) defects in AngelScript's module ownership transfer and switch-statement compiler. All patched. Patches ready for upstream review. Notably, the AngelScript source contains explicit `// TODO: optimize` comments at two of the defect sites, acknowledging the performance problem.
|
||
|
||
## The Defects
|
||
|
||
**angelscript-0001 (PATCHED — HIGH):** `sdk/angelscript/source/as_scriptengine.cpp:880`
|
||
|
||
```cpp
|
||
// In FindNewOwnerForSharedType() — called per shared type on module discard:
|
||
for (int n = 0; n < engine->modules.GetLength(); n++) {
|
||
asCModule *mod = engine->modules[n];
|
||
// 5 calls to IndexOf() per shared type transfer:
|
||
if (mod->sharedTypes.IndexOf(type) >= 0) { ... } // O(n) per call
|
||
}
|
||
// Engine comment at line 917:
|
||
// "TODO: optimize: If the modules already stored the shared types separately, this would be quicker"
|
||
```
|
||
|
||
`asCArray<T>::IndexOf()` is a linear scan. Called 5 times per shared type transfer for every remaining module.
|
||
|
||
**angelscript-0002 (PATCHED — HIGH):** `sdk/angelscript/source/as_scriptengine.cpp:953`
|
||
|
||
```cpp
|
||
// In FindNewOwnerForSharedFunc() — called per shared function on module discard:
|
||
if (mod->sharedFunctions.IndexOf(func) >= 0) { ... } // O(n) per call
|
||
```
|
||
|
||
Same root cause on shared functions. 5 linear scans per shared function transfer.
|
||
|
||
**angelscript-0003 (PATCHED — HIGH):** `sdk/angelscript/source/as_compiler.cpp`
|
||
|
||
```cpp
|
||
// In CompileSwitch() — per case value in while loop:
|
||
while (/* more cases */) {
|
||
if (caseValues.IndexOf(caseVal) >= 0) { // O(n) per case — duplicate check
|
||
Error(TXT_DUPLICATE_SWITCH_CASE);
|
||
}
|
||
caseValues.PushLast(caseVal);
|
||
}
|
||
// O(n²) duplicate case detection during switch compilation
|
||
```
|
||
|
||
`caseValues` is `asCArray<asDWORD>`. `IndexOf()` is O(n) per case. O(n²) total for n switch cases.
|
||
|
||
## Complexity Proof
|
||
|
||
**angelscript-0001/0002:** For T shared types/functions across M modules:
|
||
- Per module: 5 × O(T) `IndexOf()` calls
|
||
- Total: **O(M × T × 5)**
|
||
|
||
At T=200 types, M=100 modules: defective=3,980,000 comparisons, fixed=39,800 (`asCSet<>` shadow). **100× op reduction.**
|
||
|
||
**angelscript-0003:** For N case values in a switch statement:
|
||
- Per case: O(N) `IndexOf()` scan
|
||
- Total: **O(N²)**
|
||
|
||
At N=500 cases: defective=124,750 comparisons, fixed=500 (`asCSet<asDWORD>` shadow). **250× op reduction.**
|
||
|
||
## Impact
|
||
|
||
AngelScript is the scripting language embedded in many C++ game engines and applications, including Dry/Urho3D, and dozens of indie engines. It is used wherever C++ developers need a scripting layer that integrates tightly with C++ types.
|
||
|
||
angelscript-0001/0002 fire on every `module->Discard()` call — when a script module is unloaded and ownership of shared types/functions must be transferred. In applications that frequently reload scripts (modding systems, live editing workflows, hot-reload dev environments), this path runs repeatedly.
|
||
|
||
angelscript-0003 fires on every script compilation that includes a switch statement. Scripts with large switch statements (state machines, command parsers, opcode dispatchers) pay O(n²) at every compile. For switch statements with hundreds of cases (common in protocol implementations and scripted state machines), the overhead is significant.
|
||
|
||
## The Fix
|
||
|
||
**angelscript-0001/0002:** Add `asCSet<asCTypeInfo*>` and `asCSet<asCScriptFunction*>` shadow sets:
|
||
|
||
```cpp
|
||
// Before
|
||
if (mod->sharedTypes.IndexOf(type) >= 0) { ... }
|
||
|
||
// After
|
||
// CWE-407 fix: asCSet shadow for O(1) Exists() instead of O(n) IndexOf() scan.
|
||
if (mod->sharedTypeSet.Exists(type)) { ... }
|
||
```
|
||
|
||
Maintain `sharedTypeSet` and `sharedFuncSet` alongside the existing arrays. `asCSet` is already available in AngelScript's own container library.
|
||
|
||
**angelscript-0003:** Add `asCSet<asDWORD>` for case dedup:
|
||
|
||
```cpp
|
||
// Before
|
||
if (caseValues.IndexOf(caseVal) >= 0) { ... }
|
||
caseValues.PushLast(caseVal);
|
||
|
||
// After
|
||
// CWE-407 fix: asCSet for O(1) duplicate detection instead of O(n) IndexOf().
|
||
if (caseValueSet.Exists(caseVal)) { ... }
|
||
caseValueSet.Insert(caseVal);
|
||
caseValues.PushLast(caseVal); // preserved for downstream iteration
|
||
```
|
||
|
||
## Patch
|
||
|
||
Fix available: `defects/angelscript/patch/angelscript-0001-0003-ascset-shadow.patch`
|
||
|
||
Three-location patch across `as_scriptengine.cpp` (two sites) and `as_compiler.cpp`.
|
||
|
||
Unit test: `AngelScriptTest` 4/4 pass. angelscript-0001/0002: **100× speedup at T=200, M=100**. angelscript-0003: **250× speedup at N=500 cases**.
|
||
|
||
## What We Ask
|
||
|
||
A patch is ready for review.
|
||
|
||
1. Confirm receipt and assign an issue reference (AngelCode/angelscript or the official forums).
|
||
2. Assess severity — the `// TODO: optimize` comments in the source indicate these defects were already known; the patch resolves them.
|
||
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
|
||
4. We will credit the AngelScript team in the public disclosure. Preferred acknowledgment format welcome.
|
||
|
||
Contact: see cover email. This brief is confidential until coordinated disclosure.
|