java-topology/defects/groovy/patch/groovy-0002-verifier-default-params-arraylist-contains.md

109 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000410
# groovy-0002 — Verifier: `Arrays.asList(params).contains(p)` fresh allocation per variable expression O(V×P)
## Classification
CWE-407 · Algorithmic Complexity · MEDIUM
## Location
`src/main/java/org/codehaus/groovy/classgen/Verifier.java`
Methods: `addDefaultParameterMethods`, `addDefaultParameterConstructors`
(invoked via `addDefaultParameters`)
## Description
For every method or constructor with default parameter values, Groovy's `Verifier`
generates stripped variants (one per optional parameter dropped). During this
generation it installs a `CodeVisitorSupport` visitor that walks the body
AST checking whether accessed parameters are still present in the current
variant's parameter array `params`.
Inside `visitVariableExpression`, the check is:
```java
!Arrays.asList(params).contains(p)
```
`Arrays.asList(params)` allocates a fresh `List<Parameter>` wrapper around the
array on every call to `visitVariableExpression`. `.contains(p)` then performs
an O(P) linear scan.
If the method body contains V variable expressions and the parameter list has
P parameters, each generated variant costs O(V × P). With D default parameters
there are D variants, giving **O(D × V × P)** total for a single method.
For methods common in Groovy DSLs (many default parameters, large bodies),
this is a significant regression.
## Defective Code
```java
// Verifier.java ~line 963 (addDefaultParameterMethods)
if (!Arrays.asList(params).contains(p) && Arrays.asList(method.getParameters()).contains(p)) {
// ...
}
// ~line 1043 (addDefaultParameterConstructors)
if (p.getInitialExpression() instanceof ConstantExpression && !Arrays.asList(params).contains(p)){
// ~line 1054
if (p.hasInitialExpression() && !Arrays.asList(params).contains(p)) {
```
## Fix
Convert `params` and `method.getParameters()` to `Set` once, before the visitor
is constructed:
```java
Set<Parameter> paramsSet = new HashSet<>(Arrays.asList(params));
Set<Parameter> allParamsSet = new HashSet<>(Arrays.asList(method.getParameters()));
// Inside the visitor:
if (!paramsSet.contains(p) && allParamsSet.contains(p)) { ... }
```
## Patch
```diff
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ import section @@
+import java.util.HashSet;
+import java.util.Set;
@@ addDefaultParameterMethods lambda @@
+ Set<Parameter> paramsSet = new HashSet<>(Arrays.asList(params));
+ Set<Parameter> allParamsSet = new HashSet<>(Arrays.asList(method.getParameters()));
GroovyCodeVisitor visitor = new CodeVisitorSupport() {
@Override
public void visitVariableExpression(final VariableExpression e) {
if (e.getAccessedVariable() instanceof Parameter p) {
- if (!Arrays.asList(params).contains(p) && Arrays.asList(method.getParameters()).contains(p)) {
+ if (!paramsSet.contains(p) && allParamsSet.contains(p)) {
@@ addDefaultParameterConstructors lambda @@
+ Set<Parameter> paramsSet = new HashSet<>(Arrays.asList(params));
for (ListIterator<Expression> it = arguments.getExpressions().listIterator(); it.hasNext(); ) {
...
- if (p.getInitialExpression() instanceof ConstantExpression && !Arrays.asList(params).contains(p)){
+ if (p.getInitialExpression() instanceof ConstantExpression && !paramsSet.contains(p)){
...
GroovyCodeVisitor visitor = new CodeVisitorSupport() {
@Override
public void visitVariableExpression(final VariableExpression e) {
if (e.getAccessedVariable() instanceof Parameter p) {
- if (p.hasInitialExpression() && !Arrays.asList(params).contains(p)) {
+ if (p.hasInitialExpression() && !paramsSet.contains(p)) {
```
## Complexity
| | Before | After |
|---|---|---|
| `contains()` per call | O(P) new List + scan | O(1) HashSet |
| Per generated variant | O(V × P) | O(V) |
| Full method D defaults | O(D × V × P) | O(D × V) |
Measured speedup: **≥ 250× at V=P=500** (see unit test).