80 lines
2.8 KiB
Diff
80 lines
2.8 KiB
Diff
# UNDF: UNDF-2026-000000028
|
|
diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c
|
|
index a1b2c3d..f7c8d9e 100644
|
|
--- a/libpromises/evalfunction.c
|
|
+++ b/libpromises/evalfunction.c
|
|
@@ -5768,7 +5768,10 @@ static FnCallResult FnCallSetop(EvalContext *ctx,
|
|
StringSet *set_b = StringSetNew();
|
|
if (!unique_mode)
|
|
{
|
|
JsonIterator iter = JsonIteratorInit(json_b);
|
|
const JsonElement *e;
|
|
while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
|
|
{
|
|
StringSetAdd(set_b, xstrdup(JsonPrimitiveGetAsString(e)));
|
|
}
|
|
}
|
|
|
|
+ /* CWE-407 fix for unique() mode: build a StringSet from the input first
|
|
+ * so membership checks are O(1), then emit one Rlist per unique value.
|
|
+ * The defect: when unique_mode is true, set_b is always empty, so
|
|
+ * RlistAppendScalarIdemp() falls back to walking the growing returnlist
|
|
+ * on every call — O(N) per element, O(N²) total for N input values.
|
|
+ * Fix: use a dedicated StringSet to track seen values. O(N) total. */
|
|
+ StringSet *seen = unique_mode ? StringSetNew() : NULL;
|
|
+
|
|
Rlist *returnlist = NULL;
|
|
|
|
JsonIterator iter = JsonIteratorInit(json);
|
|
const JsonElement *e;
|
|
while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
|
|
{
|
|
const char *value = JsonPrimitiveGetAsString(e);
|
|
|
|
// Yes, this is an XOR. But it's more legible this way.
|
|
if (!unique_mode && difference_mode && StringSetContains(set_b, value))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!unique_mode && !difference_mode && !StringSetContains(set_b, value))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
- RlistAppendScalarIdemp(&returnlist, value);
|
|
+ if (unique_mode)
|
|
+ {
|
|
+ /* O(1) hash lookup replaces the O(N) Rlist walk. */
|
|
+ if (!StringSetContains(seen, value))
|
|
+ {
|
|
+ StringSetAdd(seen, xstrdup(value));
|
|
+ RlistAppendScalar(&returnlist, value);
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* intersection / difference: set_b already deduplicates by
|
|
+ * construction; a given value can appear multiple times in
|
|
+ * json_a but set_b membership already filters correctly.
|
|
+ * Use Idemp here to preserve the previous dedup behaviour for
|
|
+ * the non-unique paths (they are not the hot path). */
|
|
+ RlistAppendScalarIdemp(&returnlist, value);
|
|
+ }
|
|
}
|
|
|
|
JsonDestroyMaybe(json, allocated);
|
|
if (json_b != NULL)
|
|
{
|
|
JsonDestroyMaybe(json_b, allocated_b);
|
|
}
|
|
|
|
+ if (seen != NULL)
|
|
+ {
|
|
+ StringSetDestroy(seen);
|
|
+ }
|
|
+
|
|
StringSetDestroy(set_b);
|
|
|
|
return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } };
|
|
}
|