4.1 KiB
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:
!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
// 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:
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
--- 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).