ImmutableClassNode for protecting highly-available ClassNode instances
diff --git a/src/main/java/org/codehaus/groovy/antlr/AntlrParserPlugin.java b/src/main/java/org/codehaus/groovy/antlr/AntlrParserPlugin.java
index c7c095f..1e2ae0e 100644
--- a/src/main/java/org/codehaus/groovy/antlr/AntlrParserPlugin.java
+++ b/src/main/java/org/codehaus/groovy/antlr/AntlrParserPlugin.java
@@ -43,6 +43,7 @@
import org.codehaus.groovy.ast.EnumConstantClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.ImmutableClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
@@ -123,6 +124,7 @@
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.Reader;
+import java.lang.annotation.Annotation;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
@@ -419,7 +421,7 @@
if (node.getNumberOfChildren() == 0) {
String name = identifier(node);
// import is like "import Foo"
- ClassNode type = ClassHelper.make(name);
+ ClassNode type = makeClassNode(name);
configureAST(type, importNode);
addImport(type, name, alias, annotations);
imp = last(output.getImports());
@@ -434,7 +436,7 @@
if (isStatic) {
// import is like "import static foo.Bar.*"
// packageName is actually a className in this case
- ClassNode type = ClassHelper.make(packageName);
+ ClassNode type = makeClassNode(packageName);
configureAST(type, importNode);
addStaticStarImport(type, packageName, annotations);
imp = output.getStaticStarImports().get(packageName);
@@ -453,14 +455,14 @@
if (isStatic) {
// import is like "import static foo.Bar.method"
// packageName is really class name in this case
- ClassNode type = ClassHelper.make(packageName);
+ ClassNode type = makeClassNode(packageName);
configureAST(type, importNode);
addStaticImport(type, name, alias, annotations);
imp = output.getStaticImports().get(alias == null ? name : alias);
configureAST(imp, importNode);
} else {
// import is like "import foo.Bar"
- ClassNode type = ClassHelper.make(packageName + "." + name);
+ ClassNode type = makeClassNode(packageName + "." + name);
configureAST(type, importNode);
addImport(type, name, alias, annotations);
imp = last(output.getImports());
@@ -631,7 +633,7 @@
List<AnnotationNode> annotations = new ArrayList<>();
if (isType(TRAIT_DEF, classDef)) {
- annotations.add(new AnnotationNode(ClassHelper.makeCached(Trait.class)));
+ annotations.add(makeAnnotationNode(Trait.class));
}
AST node = classDef.getFirstChild();
@@ -876,7 +878,6 @@
checkNoInvalidModifier(methodDef, "Method", modifiers, Opcodes.ACC_VOLATILE, "volatile");
node = node.getNextSibling();
}
-
if (isAnInterface()) {
modifiers |= Opcodes.ACC_ABSTRACT;
}
@@ -1022,7 +1023,6 @@
modifiers = modifiers(node, annotations, modifiers);
node = node.getNextSibling();
}
-
if (classNode.isInterface()) {
modifiers |= Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
if ((modifiers & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0) {
@@ -1292,7 +1292,7 @@
protected AnnotationNode annotation(AST annotationNode) {
annotationBeingDef = true;
AST node = annotationNode.getFirstChild();
- AnnotationNode annotatedNode = new AnnotationNode(ClassHelper.make(qualifiedName(node)));
+ AnnotationNode annotatedNode = new AnnotationNode(makeType(annotationNode));
configureAST(annotatedNode, annotationNode);
while (true) {
node = node.getNextSibling();
@@ -3002,7 +3002,7 @@
answer = makeArray(makeTypeWithArguments(node), node);
} else {
checkTypeArgs(node, false);
- answer = ClassHelper.make(qualifiedName(node));
+ answer = makeClassNode(qualifiedName(node));
if (answer.isUsingGenerics()) {
ClassNode proxy = ClassHelper.makeWithoutCaching(answer.getName());
proxy.setRedirect(answer);
@@ -3115,6 +3115,21 @@
}
}
+ protected static AnnotationNode makeAnnotationNode(Class<? extends Annotation> type) {
+ AnnotationNode node = new AnnotationNode(ClassHelper.makeCached(type));
+ return node;
+ }
+
+ protected static ClassNode makeClassNode(String name) {
+ ClassNode node = ClassHelper.make(name);
+ if (node instanceof ImmutableClassNode && !ClassHelper.isPrimitiveType(node)) {
+ ClassNode wrapper = ClassHelper.makeWithoutCaching(name);
+ wrapper.setRedirect(node);
+ node = wrapper;
+ }
+ return node;
+ }
+
protected static Token makeToken(int typeCode, AST node) {
return Token.newSymbol(typeCode, node.getLine(), node.getColumn());
}
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
index 17bcc8d..214126d 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
@@ -179,7 +179,7 @@
final SoftReference<ClassNode> classNodeSoftReference = ClassHelperCache.classCache.get(c);
ClassNode classNode;
if (classNodeSoftReference == null || (classNode = classNodeSoftReference.get()) == null) {
- classNode = new ClassNode(c);
+ classNode = new ImmutableClassNode(c);
ClassHelperCache.classCache.put(c, new SoftReference<ClassNode>(classNode));
VMPluginFactory.getPlugin().setAdditionalClassInformation(classNode);
}
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index 15f6eb0..3218b4e 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -110,7 +110,7 @@
*/
public class ClassNode extends AnnotatedNode implements Opcodes {
- private static class MapOfLists {
+ protected static class MapOfLists {
Map<Object, List<MethodNode>> map;
List<MethodNode> get(Object key) {
@@ -129,8 +129,8 @@
}
public static final ClassNode[] EMPTY_ARRAY = new ClassNode[0];
- public static final ClassNode THIS = new ClassNode(Object.class);
- public static final ClassNode SUPER = new ClassNode(Object.class);
+ public static final ClassNode THIS = new ImmutableClassNode(Object.class);
+ public static final ClassNode SUPER = new ImmutableClassNode(Object.class);
private String name;
private int modifiers;
@@ -139,7 +139,7 @@
private MixinNode[] mixins;
private List<Statement> objectInitializers;
private List<ConstructorNode> constructors;
- private MapOfLists methods;
+ protected MapOfLists methods;
private List<MethodNode> methodsList;
private LinkedList<FieldNode> fields;
private List<PropertyNode> properties;
@@ -164,7 +164,7 @@
// clazz!=null when resolved
protected Class clazz;
// only false when this classNode is constructed from a class
- private volatile boolean lazyInitDone = true;
+ protected volatile boolean lazyInitDone = true;
// not null if if the ClassNode is an array
private ClassNode componentType;
// if not null this instance is handled as proxy
diff --git a/src/main/java/org/codehaus/groovy/ast/GenericsType.java b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
index 700204f..bf0cd51 100644
--- a/src/main/java/org/codehaus/groovy/ast/GenericsType.java
+++ b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
@@ -41,6 +41,15 @@
private final ClassNode[] upperBounds;
private boolean placeholder, resolved, wildcard;
+ public GenericsType(final ClassNode type) {
+ this(type, null, null);
+ }
+
+ protected GenericsType(final ClassNode[] upperBounds, final ClassNode lowerBound) {
+ this.lowerBound = lowerBound;
+ this.upperBounds = upperBounds;
+ }
+
public GenericsType(final ClassNode type, final ClassNode[] upperBounds, final ClassNode lowerBound) {
setType(type);
this.lowerBound = lowerBound;
@@ -49,10 +58,6 @@
setName(placeholder ? type.getUnresolvedName() : type.getName());
}
- public GenericsType(final ClassNode basicType) {
- this(basicType, null, null);
- }
-
public ClassNode getType() {
return type;
}
diff --git a/src/main/java/org/codehaus/groovy/ast/ImmutableClassNode.java b/src/main/java/org/codehaus/groovy/ast/ImmutableClassNode.java
new file mode 100644
index 0000000..df90540
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/ast/ImmutableClassNode.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.ast;
+
+import org.codehaus.groovy.GroovyBugError;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link ClassNode} where the {@link GenericsType} information is immutable.
+ */
+public class ImmutableClassNode extends ClassNode {
+
+ private volatile boolean genericsInitialized;
+ private volatile boolean writeProtected;
+
+ public ImmutableClassNode(final Class<?> c) {
+ super(c);
+ }
+
+ // ASTNode overrides:
+
+ @Override
+ public void setColumnNumber(int n) {}
+
+ @Override
+ public void setLastColumnNumber(int n) {}
+
+ @Override
+ public void setLastLineNumber(int n) {}
+
+ @Override
+ public void setLineNumber(int n) {}
+
+ @Override
+ public void setNodeMetaData(Object k, Object v) {}
+
+ @Override
+ public Object putNodeMetaData(Object k, Object v) {
+ return getNodeMetaData(k);
+ }
+
+ @Override
+ public void setSourcePosition(ASTNode n) {}
+
+ // AnnotatedNode overrides:
+
+ @Override
+ public void setDeclaringClass(ClassNode cn) {}
+
+ @Override
+ public void setHasNoRealSourcePosition(boolean b) {}
+
+ @Override
+ public void setSynthetic(boolean b) {}
+
+ // ClassNode overrides:
+
+ @Override
+ public List<MethodNode> getDeclaredMethods(final String name) {
+ if (lazyInitDone && !writeProtected) {
+ synchronized (methods) {
+ if (!writeProtected) {
+ writeProtected = true;
+ if (methods.map == null || methods.map.isEmpty()) {
+ methods.map = Collections.emptyMap();
+ } else {
+ for (Object key : methods.map.keySet()) {
+ List<MethodNode> list = methods.get(key);
+ methods.map.put(key, Collections.unmodifiableList(list));
+ }
+ methods.map = Collections.unmodifiableMap(methods.map);
+ }
+ }
+ }
+ }
+ return super.getDeclaredMethods(name);
+ }
+
+ @Override
+ public void setAnnotated(boolean b) {}
+
+ @Override
+ protected void setCompileUnit(CompileUnit cu) {}
+
+ @Override
+ public void setEnclosingMethod(MethodNode mn) {}
+
+ @Override
+ public void setGenericsPlaceHolder(boolean b) {}
+
+ //public void setInterfaces(ClassNode[] cn) {}
+
+ @Override
+ public void setModifiers(int bf) {}
+
+ @Override
+ public void setModule(ModuleNode mn) {}
+
+ @Override
+ public String setName(String s) {
+ return getName();
+ }
+
+ //public void setRedirect(ClassNode cn) {}
+
+ @Override
+ public void setSuperClass(ClassNode cn) {}
+
+ @Override
+ public void setScript(boolean b) {}
+
+ @Override
+ public void setScriptBody(boolean b) {}
+
+ @Override
+ public void setStaticClass(boolean b) {}
+
+ @Override
+ public void setSyntheticPublic(boolean b) {}
+
+ //public void setUnresolvedSuperClass(ClassNode cn) {}
+
+ @Override
+ public void setUsingGenerics(boolean b) {}
+
+ @Override
+ public void setGenericsTypes(GenericsType[] genericsTypes) {
+ if (genericsInitialized && genericsTypes != super.getGenericsTypes()) {
+ throw new GroovyBugError("Attempt to change an immutable Groovy class: " + getName());
+ }
+ if (genericsTypes != null) {
+ GenericsType[] immutable = new GenericsType[genericsTypes.length];
+ for (int i = 0, n = genericsTypes.length; i < n; i += 1) {
+ immutable[i] = new ImmutableGenericsType(genericsTypes[i], getName());
+ }
+ genericsTypes = immutable;
+ }
+ super.setGenericsTypes(genericsTypes);
+ genericsInitialized = true;
+ }
+
+ static class ImmutableGenericsType extends GenericsType {
+
+ ImmutableGenericsType(final GenericsType delegate, final String typeName) {
+ super(delegate.getUpperBounds(), delegate.getLowerBound());
+ this.typeName = typeName;
+ super.setName(delegate.getName());
+ super.setType(delegate.getType());
+ super.setResolved(delegate.isResolved());
+ super.setWildcard(delegate.isWildcard());
+ super.setPlaceholder(delegate.isPlaceholder());
+ }
+
+ private final String typeName;
+
+ @Override
+ public void setType(ClassNode cn) {
+ throw new GroovyBugError("Attempt to change an immutable Groovy class: " + typeName);
+ }
+
+ @Override
+ public void setPlaceholder(boolean b) {
+ throw new GroovyBugError("Attempt to change an immutable Groovy class: " + typeName);
+ }
+
+ @Override
+ public void setResolved(boolean b) {
+ throw new GroovyBugError("Attempt to change an immutable Groovy class: " + typeName);
+ }
+
+ @Override
+ public void setName(String s) {
+ throw new GroovyBugError("Attempt to change an immutable Groovy class: " + typeName);
+ }
+
+ @Override
+ public void setWildcard(boolean b) {
+ throw new GroovyBugError("Attempt to change an immutable Groovy class: " + typeName);
+ }
+ }
+}
diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
index 07c3c88..a3db9b6 100644
--- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
@@ -33,6 +33,7 @@
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.GenericsType.GenericsTypeName;
+import org.codehaus.groovy.ast.ImmutableClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
@@ -1028,6 +1029,11 @@
if (className != null) {
ClassNode type = ClassHelper.make(className);
if (resolve(type)) {
+ if (type instanceof ImmutableClassNode && !ClassHelper.isPrimitiveType(type)) {
+ ClassNode wrapper = ClassHelper.makeWithoutCaching(className);
+ wrapper.setRedirect(type);
+ type = wrapper;
+ }
return new ClassExpression(type);
}
}
diff --git a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index b4ae68b..8fbf7b5 100644
--- a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -49,6 +49,7 @@
import org.codehaus.groovy.ast.EnumConstantClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.ImmutableClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
@@ -127,6 +128,7 @@
import java.io.BufferedReader;
import java.io.IOException;
+import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -281,6 +283,7 @@
import static org.apache.groovy.parser.antlr4.GroovyLangParser.QualifiedClassNameContext;
import static org.apache.groovy.parser.antlr4.GroovyLangParser.QualifiedClassNameListContext;
import static org.apache.groovy.parser.antlr4.GroovyLangParser.QualifiedNameContext;
+import static org.apache.groovy.parser.antlr4.GroovyLangParser.QualifiedNameElementContext;
import static org.apache.groovy.parser.antlr4.GroovyLangParser.QualifiedStandardClassNameContext;
import static org.apache.groovy.parser.antlr4.GroovyLangParser.RegexExprAltContext;
import static org.apache.groovy.parser.antlr4.GroovyLangParser.RelationalExprAltContext;
@@ -499,18 +502,18 @@
if (hasStatic) {
if (hasStar) { // e.g. import static java.lang.Math.*
String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
- ClassNode type = ClassHelper.make(qualifiedName);
+ ClassNode type = makeClassNode(qualifiedName);
configureAST(type, ctx);
moduleNode.addStaticStarImport(type.getText(), type, annotationNodeList);
importNode = last(moduleNode.getStaticStarImports().values());
} else { // e.g. import static java.lang.Math.pow
- List<GroovyParserRuleContext> identifierList = new LinkedList<>(ctx.qualifiedName().qualifiedNameElement());
+ List<? extends QualifiedNameElementContext> identifierList = ctx.qualifiedName().qualifiedNameElement();
int identifierListSize = identifierList.size();
String name = identifierList.get(identifierListSize - 1).getText();
ClassNode classNode =
- ClassHelper.make(
+ makeClassNode(
identifierList.stream()
.limit(identifierListSize - 1)
.map(ParseTree::getText)
@@ -534,7 +537,7 @@
} else { // e.g. import java.util.Map
String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
String name = last(ctx.qualifiedName().qualifiedNameElement()).getText();
- ClassNode classNode = ClassHelper.make(qualifiedName);
+ ClassNode classNode = makeClassNode(qualifiedName);
String alias = hasAlias
? ctx.alias.getText()
: name;
@@ -549,6 +552,21 @@
return configureAST(importNode, ctx);
}
+ private static AnnotationNode makeAnnotationNode(Class<? extends Annotation> type) {
+ AnnotationNode node = new AnnotationNode(ClassHelper.make(type));
+ return node;
+ }
+
+ private static ClassNode makeClassNode(String name) {
+ ClassNode node = ClassHelper.make(name);
+ if (node instanceof ImmutableClassNode && !ClassHelper.isPrimitiveType(node)) {
+ ClassNode wrapper = ClassHelper.makeWithoutCaching(name);
+ wrapper.setRedirect(node);
+ node = wrapper;
+ }
+ return node;
+ }
+
// statement { --------------------------------------------------------------------
@Override
@@ -1122,7 +1140,7 @@
boolean isInterfaceWithDefaultMethods = (isInterface && this.containsDefaultMethods(ctx));
if (isInterfaceWithDefaultMethods || asBoolean(ctx.TRAIT())) {
- classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(Trait.class)));
+ classNode.addAnnotation(makeAnnotationNode(Trait.class));
}
classNode.addAnnotations(modifierManager.getAnnotations());
@@ -4125,7 +4143,7 @@
}
private ClassNode createClassNode(GroovyParserRuleContext ctx) {
- ClassNode result = ClassHelper.make(ctx.getText());
+ ClassNode result = makeClassNode(ctx.getText());
if (!isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) { // type in the "instanceof" expression should not have proxy to redirect to it
result = this.proxyClassNode(result);