undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
220
tests/support/MongodbPipelineAlgorithm.java
Normal file
220
tests/support/MongodbPipelineAlgorithm.java
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package support;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* MongodbPipelineAlgorithm — models two CWE-407 sites in MongoDB aggregation pipeline.
|
||||
*
|
||||
* ── mongodb-0001 document_source_streaming_group.cpp:142 (MEDIUM) ──────────
|
||||
*
|
||||
* DEFECT: During $group stage initialization, a list of id field names is built
|
||||
* from the BSON spec (idFieldNames vector). For each element in monotonicIdFields,
|
||||
* the code calls std::find(idFieldNames.begin(), idFieldNames.end(), fieldName)
|
||||
* to resolve the index position. With N id fields and M monotonic fields, this
|
||||
* is O(N × M) — quadratic when N ≈ M.
|
||||
*
|
||||
* Site: document_source_streaming_group.cpp:142
|
||||
* for (const auto& fieldNameElem : monotonicIdFields) {
|
||||
* auto it = std::find(idFieldNames.begin(), idFieldNames.end(), fieldName);
|
||||
* // O(N) per iteration
|
||||
* }
|
||||
*
|
||||
* Fix: Build an unordered_map<StringData, size_t> from idFieldNames once, then
|
||||
* resolve each monotonicIdField in O(1). Total: O(N + M) instead of O(N × M).
|
||||
*
|
||||
* ── mongodb-0002 document_source_internal_unpack_bucket.cpp:1076 (HIGH) ────
|
||||
*
|
||||
* DEFECT: During pipeline optimization, pushDownComputedMetaProjection() tracks
|
||||
* which DocumentSource* stages it has already processed to prevent infinite loops.
|
||||
* The tracker is a std::vector<DocumentSource*> (_triedComputedMetaPushDownFor).
|
||||
* Each call does std::find(...) — O(n) pointer comparison — before pushing back.
|
||||
* As the optimization loop processes more stages, the vector grows and each check
|
||||
* becomes more expensive. Total: O(S²) where S = stages processed.
|
||||
*
|
||||
* Site: document_source_internal_unpack_bucket.h:457
|
||||
* std::vector<DocumentSource*> _triedComputedMetaPushDownFor;
|
||||
*
|
||||
* Site: document_source_internal_unpack_bucket.cpp:1076
|
||||
* if (std::find(_triedComputedMetaPushDownFor.begin(),
|
||||
* _triedComputedMetaPushDownFor.end(),
|
||||
* nextTransform) != _triedComputedMetaPushDownFor.end())
|
||||
* return boost::none;
|
||||
* _triedComputedMetaPushDownFor.push_back(nextTransform);
|
||||
*
|
||||
* Fix: Replace vector with absl::flat_hash_set<DocumentSource*> (or
|
||||
* std::unordered_set<DocumentSource*>). Pointer identity is correct here —
|
||||
* each DocumentSource* is a unique stage object. No custom hash required.
|
||||
* O(1) lookup replaces O(n) scan.
|
||||
*
|
||||
* Comparison counts:
|
||||
* 0001 defective: N * M (N idFields × M monotonicFields)
|
||||
* 0001 fixed: N + M (build map once, then M O(1) lookups)
|
||||
* 0002 defective: 0+1+2+...+(S-1) = S*(S-1)/2 (triangular — each new stage
|
||||
* scans all previously processed stages)
|
||||
* 0002 fixed: S (one O(1) hash lookup per stage)
|
||||
*/
|
||||
public class MongodbPipelineAlgorithm {
|
||||
|
||||
// ── 0001: Streaming group field index resolution ──────────────────────────
|
||||
|
||||
public static final class FieldIndexResult {
|
||||
public final List<Integer> indexes; // resolved positions
|
||||
public final long comparisons;
|
||||
|
||||
public FieldIndexResult(List<Integer> indexes, long comparisons) {
|
||||
this.indexes = indexes;
|
||||
this.comparisons = comparisons;
|
||||
}
|
||||
}
|
||||
|
||||
/** Defective: O(N × M) — std::find scan per monotonic field. */
|
||||
public static FieldIndexResult defectiveResolveIndexes(
|
||||
List<String> idFieldNames, List<String> monotonicIdFields) {
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
long comparisons = 0;
|
||||
|
||||
for (String fieldName : monotonicIdFields) {
|
||||
// std::find: scan idFieldNames from beginning
|
||||
for (int i = 0; i < idFieldNames.size(); i++) {
|
||||
comparisons++;
|
||||
if (idFieldNames.get(i).equals(fieldName)) {
|
||||
indexes.add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(indexes);
|
||||
return new FieldIndexResult(indexes, comparisons);
|
||||
}
|
||||
|
||||
/** Fixed: O(N + M) — HashMap built once, O(1) per monotonic field lookup. */
|
||||
public static FieldIndexResult fixedResolveIndexes(
|
||||
List<String> idFieldNames, List<String> monotonicIdFields) {
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
long comparisons = idFieldNames.size(); // cost of building the map
|
||||
|
||||
// Build reverse map once — O(N)
|
||||
Map<String, Integer> nameToIndex = new HashMap<>();
|
||||
for (int i = 0; i < idFieldNames.size(); i++) {
|
||||
nameToIndex.put(idFieldNames.get(i), i);
|
||||
}
|
||||
|
||||
// O(1) per monotonic field
|
||||
for (String fieldName : monotonicIdFields) {
|
||||
comparisons++;
|
||||
Integer idx = nameToIndex.get(fieldName);
|
||||
if (idx != null) indexes.add(idx);
|
||||
}
|
||||
|
||||
Collections.sort(indexes);
|
||||
return new FieldIndexResult(indexes, comparisons);
|
||||
}
|
||||
|
||||
// ── 0002: Optimization loop stage tracking ────────────────────────────────
|
||||
//
|
||||
// Models pushDownComputedMetaProjection() loop.
|
||||
// Each "stage" is an integer ID (representing a DocumentSource* pointer).
|
||||
// The optimization processes S stages; each must be checked against the
|
||||
// already-processed set before being added.
|
||||
|
||||
public static final class OptimizationResult {
|
||||
public final Set<Integer> processed; // set of processed stage IDs
|
||||
public final long comparisons;
|
||||
|
||||
public OptimizationResult(Set<Integer> processed, long comparisons) {
|
||||
this.processed = processed;
|
||||
this.comparisons = comparisons;
|
||||
}
|
||||
}
|
||||
|
||||
/** Defective: std::vector<DocumentSource*> — O(n) scan per stage. */
|
||||
public static OptimizationResult defectiveTrackStages(List<Integer> stages) {
|
||||
List<Integer> tried = new ArrayList<>();
|
||||
long comparisons = 0;
|
||||
|
||||
for (int stage : stages) {
|
||||
// std::find: scan tried list for pointer equality
|
||||
boolean found = false;
|
||||
for (int t : tried) {
|
||||
comparisons++;
|
||||
if (t == stage) { found = true; break; }
|
||||
}
|
||||
if (!found) tried.add(stage);
|
||||
}
|
||||
|
||||
return new OptimizationResult(new HashSet<>(tried), comparisons);
|
||||
}
|
||||
|
||||
/** Fixed: std::unordered_set<DocumentSource*> — O(1) per stage. */
|
||||
public static OptimizationResult fixedTrackStages(List<Integer> stages) {
|
||||
Set<Integer> tried = new HashSet<>();
|
||||
long comparisons = 0;
|
||||
|
||||
for (int stage : stages) {
|
||||
comparisons++; // O(1) hash lookup
|
||||
tried.add(stage);
|
||||
}
|
||||
|
||||
return new OptimizationResult(tried, comparisons);
|
||||
}
|
||||
|
||||
// ── Self-test ─────────────────────────────────────────────────────────────
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("MongodbPipelineAlgorithm — mongodb-0001 + mongodb-0002");
|
||||
System.out.println();
|
||||
|
||||
// 0001: field index resolution
|
||||
System.out.println("── mongodb-0001: streaming group field index resolution ──");
|
||||
System.out.println("Defect: std::find on idFieldNames vector — O(N×M)");
|
||||
System.out.println("Fix: HashMap<field→index> built once — O(N+M)");
|
||||
System.out.println();
|
||||
System.out.printf("%-8s %-8s %-14s %-10s %s%n",
|
||||
"N fields", "M mono", "Defective ops", "Fixed ops", "Speedup");
|
||||
System.out.println("─".repeat(56));
|
||||
|
||||
for (int n : new int[]{5, 10, 20, 50, 100, 200}) {
|
||||
List<String> idFields = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) idFields.add("field_" + i);
|
||||
// monotonic = last half of id fields (worst case: found at end)
|
||||
List<String> mono = new ArrayList<>(idFields.subList(n / 2, n));
|
||||
|
||||
FieldIndexResult def = defectiveResolveIndexes(idFields, mono);
|
||||
FieldIndexResult fix = fixedResolveIndexes(idFields, mono);
|
||||
assert def.indexes.equals(fix.indexes) : "index mismatch at N=" + n;
|
||||
|
||||
System.out.printf("%-8d %-8d %-14d %-10d %.1fx%n",
|
||||
n, mono.size(), def.comparisons, fix.comparisons,
|
||||
(double) def.comparisons / Math.max(1, fix.comparisons));
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
|
||||
// 0002: optimization loop tracking
|
||||
System.out.println("── mongodb-0002: optimization stage tracker ──");
|
||||
System.out.println("Defect: std::vector<DocumentSource*> + std::find — O(S²)");
|
||||
System.out.println("Fix: std::unordered_set<DocumentSource*> — O(S)");
|
||||
System.out.println();
|
||||
System.out.printf("%-10s %-14s %-10s %s%n",
|
||||
"S stages", "Defective ops", "Fixed ops", "Speedup");
|
||||
System.out.println("─".repeat(46));
|
||||
|
||||
Random rng = new Random(42);
|
||||
for (int s : new int[]{10, 20, 50, 100, 200, 500, 1000}) {
|
||||
// Mix of unique stages + some revisits (duplicates)
|
||||
List<Integer> stages = new ArrayList<>();
|
||||
for (int i = 0; i < s; i++) stages.add(i);
|
||||
for (int i = 0; i < s / 4; i++) stages.add(rng.nextInt(s)); // revisits
|
||||
Collections.shuffle(stages, rng);
|
||||
|
||||
OptimizationResult def = defectiveTrackStages(stages);
|
||||
OptimizationResult fix = fixedTrackStages(stages);
|
||||
assert def.processed.equals(fix.processed) : "processed set mismatch at S=" + s;
|
||||
|
||||
System.out.printf("%-10d %-14d %-10d %.1fx%n",
|
||||
stages.size(), def.comparisons, fix.comparisons,
|
||||
(double) def.comparisons / Math.max(1, fix.comparisons));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue