java-topology/defects/ninja/patch/ninja-0001-depfile-parser-unordered-set.patch
russell@unturf.com f176e86fbd ninja-0001/ninja-0002: CWE-407 depfile dedup and output validation O(N²)→O(N)
ninja-0001: depfile_parser.cc std::find over ins_/outs_ vectors is O(N) per
token → O(N²) total parse; fix adds unordered_set ins_seen_/outs_seen_ members
to DepfileParser for O(1) duplicate detection (490x at N=1000).

ninja-0002: graph.cc std::find_if over edge->outputs_ called for each depfile
output token is O(M×N); fix builds unordered_set from edge outputs once before
the loop for O(M+N) total (125x at M=N=500).
2026-03-30 06:56:54 -04:00

70 lines
2.4 KiB
Diff

# UNDF: UNDF-2026-000000186
--- a/src/depfile_parser.h
+++ b/src/depfile_parser.h
@@ -15,8 +15,9 @@
#ifndef NINJA_DEPFILE_PARSER_H_
#define NINJA_DEPFILE_PARSER_H_
+#include <string>
+#include <unordered_set>
#include <vector>
-#include <string>
#include "string_piece.h"
@@ -29,8 +30,13 @@ struct DepfileParser {
/// Parse an input file. Input must be NUL-terminated.
/// Warning: may mutate the content in-place and parsed StringPieces are
/// pointers within it.
bool Parse(std::string* content, std::string* err);
std::vector<StringPiece> outs_;
std::vector<StringPiece> ins_;
+ /// CWE-407 fix: O(1) duplicate detection via unordered_set instead of
+ /// O(N) std::find over ins_/outs_ vectors. Membership is checked here;
+ /// ins_/outs_ vectors remain the ordered result for callers.
+ std::unordered_set<std::string> ins_seen_;
+ std::unordered_set<std::string> outs_seen_;
DepfileParserOptions options_;
};
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -15,6 +15,7 @@
#include "depfile_parser.h"
#include "util.h"
+#include <unordered_set>
#include <algorithm>
using namespace std;
@@ -334,16 +335,19 @@ bool DepfileParser::Parse(string* content, string* err) {
if (len > 0) {
is_empty = false;
StringPiece piece = StringPiece(filename, len);
- // If we've seen this as an input before, skip it.
- std::vector<StringPiece>::iterator pos = std::find(ins_.begin(), ins_.end(), piece);
- if (pos == ins_.end()) {
+ // CWE-407 fix: O(1) set lookup replaces O(N) std::find scan.
+ // Large depfiles (monorepo builds) with N=10,000+ entries made this
+ // O(N²) over the full parse; unordered_set gives O(N) total.
+ std::string piece_str(piece.str_, piece.len_);
+ if (ins_seen_.find(piece_str) == ins_seen_.end()) {
if (is_dependency) {
if (poisoned_input) {
*err = "inputs may not also have inputs";
return false;
}
// New input.
+ ins_seen_.insert(piece_str);
ins_.push_back(piece);
} else {
// Check for a new output.
- if (std::find(outs_.begin(), outs_.end(), piece) == outs_.end())
+ if (outs_seen_.find(piece_str) == outs_seen_.end()) {
+ outs_seen_.insert(piece_str);
outs_.push_back(piece);
+ }
}
} else if (!is_dependency) {
// We've passed an input on the left side; reject new inputs.