141 lines
5.9 KiB
Diff
141 lines
5.9 KiB
Diff
# UNDF: UNDF-2026-000000173
|
|
diff --git a/src/mongo/db/query/index_tag.h b/src/mongo/db/query/index_tag.h
|
|
index 7465ab84..db15bf62 100644
|
|
--- a/src/mongo/db/query/index_tag.h
|
|
+++ b/src/mongo/db/query/index_tag.h
|
|
@@ -40,6 +40,7 @@
|
|
#include <deque>
|
|
#include <memory>
|
|
#include <string>
|
|
+#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
@@ -103,8 +104,11 @@ class RelevantTag final : public MatchExpression::TagData {
|
|
public:
|
|
RelevantTag() : elemMatchExpr(nullptr), pathPrefix("") {}
|
|
|
|
- std::vector<size_t> first;
|
|
- std::vector<size_t> notFirst;
|
|
+ // CWE-407 fix: changed from std::vector<size_t> to std::unordered_set<size_t> so that
|
|
+ // membership tests in isIndexAssigned() and stripInvalidAssignments*() are O(1) instead of
|
|
+ // O(n), eliminating the O(P*I) quadratic scan in the index selection strip passes.
|
|
+ std::unordered_set<size_t> first;
|
|
+ std::unordered_set<size_t> notFirst;
|
|
|
|
// We don't know the full path from a node unless we keep notes as we traverse from the
|
|
// root. We do this once and store it.
|
|
@@ -133,12 +137,12 @@ public:
|
|
|
|
void debugString(StringBuilder* builder) const override {
|
|
*builder << " || First: ";
|
|
- for (size_t i = 0; i < first.size(); ++i) {
|
|
- *builder << first[i] << " ";
|
|
+ for (size_t idx : first) {
|
|
+ *builder << idx << " ";
|
|
}
|
|
*builder << "notFirst: ";
|
|
- for (size_t i = 0; i < notFirst.size(); ++i) {
|
|
- *builder << notFirst[i] << " ";
|
|
+ for (size_t idx : notFirst) {
|
|
+ *builder << idx << " ";
|
|
}
|
|
*builder << "full path: " << path << "\n";
|
|
}
|
|
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
|
|
index 90392cbe..39904213 100644
|
|
--- a/src/mongo/db/query/planner_ixselect.cpp
|
|
+++ b/src/mongo/db/query/planner_ixselect.cpp
|
|
@@ -816,9 +816,9 @@ void QueryPlannerIXSelect::rateIndices(MatchExpression* node,
|
|
queryContext,
|
|
nodeIsNotChild)) {
|
|
if (keyPatternIndex == 0) {
|
|
- rt->first.push_back(i);
|
|
+ rt->first.insert(i);
|
|
} else {
|
|
- rt->notFirst.push_back(i);
|
|
+ rt->notFirst.insert(i);
|
|
}
|
|
}
|
|
++keyPatternIndex;
|
|
@@ -937,17 +937,14 @@ void QueryPlannerIXSelect::stripUnneededAssignments(MatchExpression* node,
|
|
|
|
// Look through all of the indices for which this predicate can be answered with
|
|
// the leading field of the index.
|
|
- for (std::vector<size_t>::const_iterator i = rt->first.begin(); i != rt->first.end();
|
|
- ++i) {
|
|
- size_t index = *i;
|
|
-
|
|
+ for (size_t index : rt->first) {
|
|
if (indices[index].unique && 1 == indices[index].keyPattern.nFields()) {
|
|
// Found an EQ predicate which can use a single-field unique index.
|
|
// Clear assignments from the entire tree, and add back a single assignment
|
|
// for 'child' to the unique index.
|
|
clearAssignments(node);
|
|
RelevantTag* newRt = indexTagCast<RelevantTag>(child->getTag());
|
|
- newRt->first.push_back(index);
|
|
+ newRt->first.insert(index);
|
|
|
|
// Tag state has been reset in the entire subtree at 'root'; nothing
|
|
// else for us to do.
|
|
@@ -975,16 +972,9 @@ static void removeIndexRelevantTag(MatchExpression* node, size_t idx) {
|
|
return;
|
|
}
|
|
|
|
- vector<size_t>::iterator firstIt = std::find(tag->first.begin(), tag->first.end(), idx);
|
|
- if (firstIt != tag->first.end()) {
|
|
- tag->first.erase(firstIt);
|
|
- }
|
|
-
|
|
- vector<size_t>::iterator notFirstIt =
|
|
- std::find(tag->notFirst.begin(), tag->notFirst.end(), idx);
|
|
- if (notFirstIt != tag->notFirst.end()) {
|
|
- tag->notFirst.erase(notFirstIt);
|
|
- }
|
|
+ // CWE-407 fix: unordered_set::erase(value) is O(1); replaces O(n) std::find + erase.
|
|
+ tag->first.erase(idx);
|
|
+ tag->notFirst.erase(idx);
|
|
}
|
|
|
|
namespace {
|
|
@@ -1081,10 +1071,8 @@ bool isIndexAssigned(RelevantTag* tag, size_t idx) {
|
|
return false;
|
|
}
|
|
|
|
- bool inFirst = tag->first.end() != std::find(tag->first.begin(), tag->first.end(), idx);
|
|
- bool inNotFirst =
|
|
- tag->notFirst.end() != std::find(tag->notFirst.begin(), tag->notFirst.end(), idx);
|
|
- return inFirst || inNotFirst;
|
|
+ // CWE-407 fix: O(1) set lookup replaces O(n) std::find on vector.
|
|
+ return tag->first.count(idx) || tag->notFirst.count(idx);
|
|
}
|
|
|
|
// Returns true for $and and $elemMatch as they consist of a set of predicates that are suitable for
|
|
@@ -1307,10 +1295,9 @@ static void stripInvalidAssignmentsToTextIndex(MatchExpression* node,
|
|
continue;
|
|
}
|
|
|
|
- bool inFirst = tag->first.end() != std::find(tag->first.begin(), tag->first.end(), idx);
|
|
-
|
|
- bool inNotFirst =
|
|
- tag->notFirst.end() != std::find(tag->notFirst.begin(), tag->notFirst.end(), idx);
|
|
+ // CWE-407 fix: O(1) set lookup replaces O(n) std::find.
|
|
+ bool inFirst = tag->first.count(idx) != 0;
|
|
+ bool inNotFirst = tag->notFirst.count(idx) != 0;
|
|
|
|
if (inFirst || inNotFirst) {
|
|
// Great! 'child' was assigned to our index.
|
|
@@ -1421,10 +1408,9 @@ static void stripInvalidAssignmentsTo2dsphereIndex(MatchExpression* node, size_t
|
|
continue;
|
|
}
|
|
|
|
- bool inFirst = tag->first.end() != std::find(tag->first.begin(), tag->first.end(), idx);
|
|
-
|
|
- bool inNotFirst =
|
|
- tag->notFirst.end() != std::find(tag->notFirst.begin(), tag->notFirst.end(), idx);
|
|
+ // CWE-407 fix: O(1) set lookup replaces O(n) std::find.
|
|
+ bool inFirst = tag->first.count(idx) != 0;
|
|
+ bool inNotFirst = tag->notFirst.count(idx) != 0;
|
|
|
|
// If there is an index assignment...
|
|
if (inFirst || inNotFirst) {
|