org.codehaus.groovy.classgen.Verifier Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy Show documentation
Show all versions of groovy Show documentation
Groovy: A powerful multi-faceted language for the JVM
The newest version!
/*
* 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.classgen;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaClass;
import groovy.transform.CompileStatic;
import groovy.transform.Generated;
import groovy.transform.Internal;
import groovy.transform.NonSealed;
import groovy.transform.Sealed;
import groovy.transform.stc.POJO;
import org.apache.groovy.ast.tools.ClassNodeUtils;
import org.apache.groovy.util.BeanUtils;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CodeVisitorSupport;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.DynamicVariable;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.GroovyClassVisitor;
import org.codehaus.groovy.ast.GroovyCodeVisitor;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.VariableScope;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.classgen.asm.MopWriter;
import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter.ClassNodeSkip;
import org.codehaus.groovy.classgen.asm.WriterController;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.syntax.RuntimeParserException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.transform.trait.Traits;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.beans.Transient;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.stream.Collectors.joining;
import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.hasAnnotation;
import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.isGenerated;
import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated;
import static org.apache.groovy.ast.tools.ConstructorNodeUtils.getFirstIfSpecialConstructorCall;
import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
import static org.apache.groovy.ast.tools.MethodNodeUtils.getCodeAsBlock;
import static org.apache.groovy.ast.tools.MethodNodeUtils.getPropertyName;
import static org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptorWithoutReturnType;
import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveLong;
import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInterfacesAndSuperInterfaces;
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
import static org.codehaus.groovy.ast.tools.GenericsUtils.addMethodGenerics;
import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
import static org.codehaus.groovy.ast.tools.PropertyNodeUtils.adjustPropertyModifiersForMethod;
/**
* Verifies the AST node and adds any default AST code before bytecode generation occurs.
*
* Checks include:
*
* - Methods with duplicate signatures
* - Duplicate interfaces
* - Reassigned final variables/parameters
* - Uninitialized variables
* - Bad code in object initializers or constructors
* - Mismatches in modifiers or return types between implementations and interfaces/abstract classes
* - Invalid record component names
*
*
* Added code includes:
*
* - Methods needed to implement GroovyObject
* - Property accessor methods
* - Covariant methods
* - Additional methods/constructors as needed for default parameters
*
*/
public class Verifier implements GroovyClassVisitor, Opcodes {
public static final String SWAP_INIT = "__$swapInit";
public static final String STATIC_METACLASS_BOOL = "__$stMC";
public static final String INITIAL_EXPRESSION = "INITIAL_EXPRESSION";
public static final String DEFAULT_PARAMETER_GENERATED = "DEFAULT_PARAMETER_GENERATED";
private static final ClassNode GENERATED_ANNOTATION = ClassHelper.make(Generated.class);
private static final ClassNode INTERNAL_ANNOTATION = ClassHelper.make(Internal .class);
private static final ClassNode TRANSIENT_ANNOTATION = ClassHelper.make(Transient.class);
// NOTE: timeStamp constants shouldn't belong to Verifier but kept here for binary compatibility
public static final String __TIMESTAMP = "__timeStamp";
public static final String __TIMESTAMP__ = "__timeStamp__239_neverHappen";
private static final Class SEALED_TYPE = Sealed.class;
private static final Class NON_SEALED_TYPE = NonSealed.class;
private static final Parameter[] SET_METACLASS_PARAMS = {new Parameter(ClassHelper.METACLASS_TYPE, "mc")};
private ClassNode classNode;
private MethodNode methodNode;
public ClassNode getClassNode() {
return classNode;
}
protected void setClassNode(final ClassNode classNode) {
this.classNode = classNode;
}
public MethodNode getMethodNode() {
return methodNode;
}
private static FieldNode getMetaClassField(final ClassNode node) {
FieldNode metaClassField = node.getDeclaredField("metaClass");
if (metaClassField != null) {
ClassNode mcFieldType = metaClassField.getType();
if (!mcFieldType.equals(ClassHelper.METACLASS_TYPE)) {
throw new RuntimeParserException("The class " + node.getName() +
" cannot declare field 'metaClass' of type " + mcFieldType.getName() + " as it needs to be of " +
"the type " + ClassHelper.METACLASS_TYPE.getName() + " for internal groovy purposes", metaClassField);
}
return metaClassField;
}
ClassNode current = node;
while (!isObjectType(current)) {
current = current.getSuperClass();
if (current == null) break;
metaClassField = current.getDeclaredField("metaClass");
if (metaClassField == null) continue;
if (isPrivate(metaClassField.getModifiers())) continue;
return metaClassField;
}
return null;
}
private static FieldNode setMetaClassFieldIfNotExists(final ClassNode node, FieldNode metaClassField) {
if (metaClassField == null) {
String classInternalName = BytecodeHelper.getClassInternalName(node);
metaClassField =
node.addField("metaClass", ACC_PRIVATE | ACC_SYNTHETIC | ACC_TRANSIENT, ClassHelper.METACLASS_TYPE,
bytecodeX(ClassHelper.METACLASS_TYPE, mv -> {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, classInternalName, "$getStaticMetaClass", "()Lgroovy/lang/MetaClass;", false);
})
);
metaClassField.setSynthetic(true);
}
return metaClassField;
}
//--------------------------------------------------------------------------
@Override
public void visitClass(final ClassNode node) {
this.classNode = node;
if (node.isRecord()) {
detectInvalidRecordComponentNames(node);
if (node.isAbstract()) {
throw new RuntimeParserException("Record '" + node.getNameWithoutPackage() + "' must not be abstract", node);
}
}
checkForDuplicateInterfaces(node);
if (node.isInterface() || Traits.isTrait(node)) {
// interfaces have no constructors but this expects one,
// so create a dummy but do not add it to the class node
addInitialization(node, new ConstructorNode(0, null));
node.visitContents(this);
if (node.getNodeMetaData(ClassNodeSkip.class) == null) {
node.setNodeMetaData(ClassNodeSkip.class, Boolean.TRUE);
}
if (node.isInterface()) {
// GROOVY-11273
checkFinalVariables(node);
}
return;
}
addDefaultParameterMethods(node);
addDefaultParameterConstructors(node);
boolean skipGroovify = hasAnnotation(node, ClassHelper.make(POJO.class))
&& hasAnnotation(node, ClassHelper.make(CompileStatic.class));
if (!skipGroovify) {
String classInternalName = BytecodeHelper.getClassInternalName(node);
addStaticMetaClassField(node, classInternalName);
addFastPathHelperFieldsAndHelperMethod(node, classInternalName);
if (!node.isDerivedFrom(ClassHelper.GROOVY_OBJECT_SUPPORT_TYPE))
addGroovyObjectInterfaceAndMethods(node, classInternalName);
}
addDefaultConstructor(node);
addInitialization(node);
checkReturnInObjectInitializer(node.getObjectInitializerStatements());
node.getObjectInitializerStatements().clear();
node.visitContents(this);
checkForDuplicateMethods(node);
checkForDuplicateConstructors(node);
addCovariantMethods(node);
detectNonSealedClasses(node);
checkFinalVariables(node);
}
private static final String[] INVALID_COMPONENTS = {"clone", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"};
private static void detectInvalidRecordComponentNames(final ClassNode node) {
for (FieldNode fn : node.getFields()) {
if (Arrays.binarySearch(INVALID_COMPONENTS, fn.getName()) >= 0) {
throw new RuntimeParserException("Illegal record component name '" + fn.getName() + "'", fn);
}
}
}
private static void detectNonSealedClasses(final ClassNode node) {
if (isFinal(node.getModifiers())) return;
if (Boolean.TRUE.equals(node.getNodeMetaData(SEALED_TYPE))) return;
if (Boolean.TRUE.equals(node.getNodeMetaData(NON_SEALED_TYPE))) return;
ClassNode sn = node.getSuperClass();
boolean found = false;
while (sn != null && !sn.equals(ClassHelper.OBJECT_TYPE)) {
if (sn.isSealed()) {
found = true;
break;
}
sn = sn.getSuperClass();
}
if (found) {
node.putNodeMetaData(NON_SEALED_TYPE, Boolean.TRUE);
}
}
private void checkFinalVariables(final ClassNode node) {
GroovyClassVisitor visitor = new FinalVariableAnalyzer(null, getFinalVariablesCallback());
visitor.visitClass(node);
}
protected FinalVariableAnalyzer.VariableNotFinalCallback getFinalVariablesCallback() {
return new FinalVariableAnalyzer.VariableNotFinalCallback() {
@Override
public void variableNotFinal(Variable var, final Expression bexp) {
if (var instanceof VariableExpression) {
var = ((VariableExpression) var).getAccessedVariable();
}
if (var instanceof VariableExpression && isFinal(var.getModifiers())) {
throw new RuntimeParserException("The variable [" + var.getName() + "] is declared final but is reassigned", bexp);
}
if (var instanceof Parameter && isFinal(var.getModifiers())) {
throw new RuntimeParserException("The parameter [" + var.getName() + "] is declared final but is reassigned", bexp);
}
}
@Override
public void variableNotAlwaysInitialized(final VariableExpression var) {
if (isFinal(var.getAccessedVariable().getModifiers()))
throw new RuntimeParserException("The variable [" + var.getName() + "] may be uninitialized", var);
}
};
}
private static Set getAllInterfaces(final ClassNode cn) {
return getInterfacesAndSuperInterfaces(cn);
}
private static void checkForDuplicateInterfaces(final ClassNode cn) {
ClassNode[] interfaces = cn.getInterfaces();
int nInterfaces = interfaces.length;
if (nInterfaces == 0) return;
if (nInterfaces > 1) {
List interfaceNames = new ArrayList<>(nInterfaces);
for (ClassNode in : interfaces) interfaceNames.add(in.getName());
if (interfaceNames.size() != new HashSet<>(interfaceNames).size()) {
throw new RuntimeParserException("Duplicate interfaces in implements list: " + interfaceNames, cn);
}
}
// GROOVY-5106: check for same interface with different type argument(s)
List< Set > allInterfaces = new ArrayList<>(nInterfaces + 1);
for (ClassNode in : interfaces) allInterfaces.add(getAllInterfaces(in));
allInterfaces.add(getAllInterfaces(cn.getUnresolvedSuperClass()));
if (nInterfaces == 1 && allInterfaces.get(1).isEmpty())
return; // no peer interface(s) to verify
for (int i = 0; i < nInterfaces; i += 1) {
for (ClassNode in : allInterfaces.get(i)) {
if (in.redirect().getGenericsTypes() != null) {
for (int j = i + 1; j < nInterfaces + 1; j += 1) {
Set set = allInterfaces.get(j);
if (set.contains(in)) {
for (ClassNode t : set) { // find match and check generics
if (t.equals(in)) {
String one = in.toString(false), two = t.toString(false);
if (!one.equals(two)) {
if (Traits.isTrait(in)) { // GROOVY-11508
cn.getModule().getContext().addWarning("The trait " + in.getNameWithoutPackage() +
" is implemented more than once with different arguments: " + one + " and " + two, cn);
} else {
throw new RuntimeParserException("The interface " + in.getNameWithoutPackage() +
" cannot be implemented more than once with different arguments: " + one + " and " + two, cn);
}
}
break;
}
}
}
}
}
}
}
}
private static void checkForDuplicateMethods(final ClassNode cn) {
Set descriptors = new HashSet<>();
for (MethodNode mn : cn.getMethods()) {
if (mn.isSynthetic()) continue;
String mySig = methodDescriptorWithoutReturnType(mn);
if (descriptors.contains(mySig)) {
if (mn.isScriptBody() || mySig.equals(scriptBodySignatureWithoutReturnType(cn))) {
throw new RuntimeParserException("The method " + mn.getText() +
" is a duplicate of the one declared for this script's body code", sourceOf(mn));
} else {
throw new RuntimeParserException("The method " + mn.getText() +
" duplicates another method of the same signature", sourceOf(mn));
}
}
descriptors.add(mySig);
}
}
private static void checkForDuplicateConstructors(final ClassNode cn) {
Set descriptors = new HashSet<>();
for (ConstructorNode cons : cn.getDeclaredConstructors()) {
String mySig = methodDescriptorWithoutReturnType(cons);
if (descriptors.contains(mySig)) {
throw new RuntimeParserException("The constructor " + cons.getText() +
" duplicates another constructor of the same signature", sourceOf(cons));
}
descriptors.add(mySig);
}
}
private static String scriptBodySignatureWithoutReturnType(final ClassNode cn) {
for (MethodNode mn : cn.getMethods()) {
if (mn.isScriptBody()) return methodDescriptorWithoutReturnType(mn);
}
return null;
}
private static FieldNode checkFieldDoesNotExist(final ClassNode node, final String fieldName) {
FieldNode ret = node.getDeclaredField(fieldName);
if (ret != null) {
if (isPublic(ret.getModifiers()) &&
isPrimitiveBoolean(ret.getType().redirect())) {
return ret;
}
throw new RuntimeParserException("The class " + node.getName() +
" cannot declare field '" + fieldName + "' as this" +
" field is needed for internal groovy purposes", ret);
}
return null;
}
private static void addFastPathHelperFieldsAndHelperMethod(final ClassNode node, final String classInternalName) {
if (Boolean.TRUE.equals(node.getNodeMetaData(ClassNodeSkip.class))) return;
FieldNode stMCB = checkFieldDoesNotExist(node, STATIC_METACLASS_BOOL);
if (stMCB == null) {
stMCB = node.addField(
STATIC_METACLASS_BOOL,
ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC | ACC_TRANSIENT,
ClassHelper.boolean_TYPE, null);
stMCB.setSynthetic(true);
}
}
protected void addDefaultConstructor(final ClassNode node) {
if (!node.getDeclaredConstructors().isEmpty()) return;
ConstructorNode constructor = new ConstructorNode(ACC_PUBLIC, new BlockStatement());
constructor.setHasNoRealSourcePosition(true);
markAsGenerated(node, constructor);
node.addConstructor(constructor);
}
private void addStaticMetaClassField(final ClassNode node, final String classInternalName) {
String _staticClassInfoFieldName = "$staticClassInfo";
while (node.getDeclaredField(_staticClassInfoFieldName) != null)
_staticClassInfoFieldName = _staticClassInfoFieldName + "$";
final String staticMetaClassFieldName = _staticClassInfoFieldName;
FieldNode staticMetaClassField = node.addField(staticMetaClassFieldName, ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, ClassHelper.make(ClassInfo.class, false), null);
staticMetaClassField.setSynthetic(true);
ClassNode classNode = this.classNode;
node.addSyntheticMethod(
"$getStaticMetaClass",
ACC_PROTECTED,
ClassHelper.make(MetaClass.class),
Parameter.EMPTY_ARRAY,
ClassNode.EMPTY_ARRAY,
new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(final MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
if (BytecodeHelper.isClassLiteralPossible(node) || BytecodeHelper.isSameCompilationUnit(classNode, node)) {
BytecodeHelper.visitClassLiteral(mv, node);
} else {
mv.visitMethodInsn(INVOKESTATIC, classInternalName, "$get$$class$" + classInternalName.replace('/', '$'), "()Ljava/lang/Class;", false);
}
Label l1 = new Label();
mv.visitJumpInsn(IF_ACMPEQ, l1);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/ScriptBytecodeAdapter", "initMetaClass", "(Ljava/lang/Object;)Lgroovy/lang/MetaClass;", false);
mv.visitInsn(ARETURN);
mv.visitLabel(l1);
mv.visitFieldInsn(GETSTATIC, classInternalName, staticMetaClassFieldName, "Lorg/codehaus/groovy/reflection/ClassInfo;");
mv.visitVarInsn(ASTORE, 1);
mv.visitVarInsn(ALOAD, 1);
Label l0 = new Label();
mv.visitJumpInsn(IFNONNULL, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/reflection/ClassInfo", "getClassInfo", "(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;", false);
mv.visitInsn(DUP);
mv.visitVarInsn(ASTORE, 1);
mv.visitFieldInsn(PUTSTATIC, classInternalName, staticMetaClassFieldName, "Lorg/codehaus/groovy/reflection/ClassInfo;");
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "org/codehaus/groovy/reflection/ClassInfo", "getMetaClass", "()Lgroovy/lang/MetaClass;", false);
mv.visitInsn(ARETURN);
}
})
);
}
protected void addGroovyObjectInterfaceAndMethods(final ClassNode node, final String classInternalName) {
if (!node.isDerivedFromGroovyObject()) node.addInterface(ClassHelper.GROOVY_OBJECT_TYPE);
FieldNode metaClassField = getMetaClassField(node);
boolean shouldAnnotate = (classNode.getModule().getContext() != null);
if (!node.hasMethod("getMetaClass", Parameter.EMPTY_ARRAY)) {
metaClassField = setMetaClassFieldIfNotExists(node, metaClassField);
Statement getMetaClassCode = new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(final MethodVisitor mv) {
Label nullLabel = new Label();
/*
* the code is:
* if (this.metaClass==null) {
* this.metaClass = this.$getStaticMetaClass()
* return this.metaClass
* } else {
* return this.metaClass
* }
* with the optimization that the result of the
* first this.metaClass is duped on the operand
* stack and reused for the return in the else part
*/
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, classInternalName, "metaClass", "Lgroovy/lang/MetaClass;");
mv.visitInsn(DUP);
mv.visitJumpInsn(IFNULL, nullLabel);
mv.visitInsn(ARETURN);
mv.visitLabel(nullLabel);
mv.visitInsn(POP);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKEVIRTUAL, classInternalName, "$getStaticMetaClass", "()Lgroovy/lang/MetaClass;", false);
mv.visitFieldInsn(PUTFIELD, classInternalName, "metaClass", "Lgroovy/lang/MetaClass;");
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, classInternalName, "metaClass", "Lgroovy/lang/MetaClass;");
mv.visitInsn(ARETURN);
}
});
MethodNode methodNode = addMethod(node, !shouldAnnotate, "getMetaClass", ACC_PUBLIC,
ClassHelper.METACLASS_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, getMetaClassCode);
if (shouldAnnotate) {
methodNode.addAnnotation(GENERATED_ANNOTATION);
methodNode.addAnnotation(INTERNAL_ANNOTATION );
methodNode.addAnnotation(TRANSIENT_ANNOTATION);
}
}
if (!node.hasMethod("setMetaClass", SET_METACLASS_PARAMS)) {
metaClassField = setMetaClassFieldIfNotExists(node, metaClassField);
Statement setMetaClassCode;
if (isFinal(metaClassField.getModifiers())) {
setMetaClassCode = stmt(ctorX(ClassHelper.make(IllegalArgumentException.class), constX("cannot set read-only meta class")));
} else {
setMetaClassCode = new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(final MethodVisitor mv) {
/*
* the code is (metaclass is stored in 1):
* this.metaClass = <1>
*/
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, classInternalName, "metaClass", "Lgroovy/lang/MetaClass;");
mv.visitInsn(RETURN);
}
});
}
MethodNode methodNode = addMethod(node, !shouldAnnotate, "setMetaClass", ACC_PUBLIC,
ClassHelper.VOID_TYPE, SET_METACLASS_PARAMS, ClassNode.EMPTY_ARRAY, setMetaClassCode);
if (shouldAnnotate) {
methodNode.addAnnotation(GENERATED_ANNOTATION);
methodNode.addAnnotation(INTERNAL_ANNOTATION);
}
}
}
/**
* Helper method to add a new method to a ClassNode. Depending on the shouldBeSynthetic flag the
* call will either be made to ClassNode.addSyntheticMethod() or ClassNode.addMethod(). If a non-synthetic method
* is to be added the ACC_SYNTHETIC modifier is removed if it has been accidentally supplied.
*/
protected MethodNode addMethod(final ClassNode node, final boolean shouldBeSynthetic, final String name, final int modifiers, final ClassNode returnType, final Parameter[] parameters, final ClassNode[] exceptions, final Statement code) {
if (shouldBeSynthetic) {
return node.addSyntheticMethod(name, modifiers, returnType, parameters, exceptions, code);
} else {
return node.addMethod(name, modifiers & ~ACC_SYNTHETIC, returnType, parameters, exceptions, code);
}
}
@Deprecated // for binary compatibility only; do not use or override
protected void addMethod$$bridge(final ClassNode node, final boolean shouldBeSynthetic, final String name, final int modifiers, final ClassNode returnType, final Parameter[] parameters, final ClassNode[] exceptions, final Statement code) {
addMethod(node, shouldBeSynthetic, name, modifiers, returnType, parameters, exceptions, code);
}
@Deprecated(since = "2.4.0")
protected void addTimeStamp(final ClassNode node) {
}
private static void checkReturnInObjectInitializer(final List init) {
GroovyCodeVisitor visitor = new CodeVisitorSupport() {
@Override
public void visitClosureExpression(final ClosureExpression expression) {
// return is OK in closures in object initializers
}
@Override
public void visitReturnStatement(final ReturnStatement statement) {
throw new RuntimeParserException("'return' is not allowed in object initializer", statement);
}
};
for (Statement stmt : init) {
stmt.visit(visitor);
}
}
@Override
public void visitConstructor(final ConstructorNode node) {
Statement stmt = node.getCode();
if (stmt != null) {
stmt.visit(new VerifierCodeVisitor(getClassNode()));
// check for uninitialized-this references
stmt.visit(new CodeVisitorSupport() {
@Override
public void visitClosureExpression(final ClosureExpression ce) {
boolean oldInClosure = inClosure;
inClosure = true;
super.visitClosureExpression(ce);
inClosure = oldInClosure;
}
@Override
public void visitConstructorCallExpression(final ConstructorCallExpression cce) {
boolean oldIsSpecialConstructorCall = inSpecialConstructorCall;
inSpecialConstructorCall |= cce.isSpecialCall();
super.visitConstructorCallExpression(cce);
inSpecialConstructorCall = oldIsSpecialConstructorCall;
}
@Override
public void visitMethodCallExpression(final MethodCallExpression mce) {
if (inSpecialConstructorCall && isThisObjectExpression(mce)) {
boolean outerOrStaticMember = false;
if (mce.getMethodTarget() != null) {
outerOrStaticMember = mce.getMethodTarget().isStatic() || classNode.getOuterClasses().contains(mce.getMethodTarget().getDeclaringClass());
} else if (mce.isImplicitThis()) { // GROOVY-11352
outerOrStaticMember = classNode.getOuterClasses().stream().anyMatch(oc -> oc.hasPossibleStaticMethod(mce.getMethodAsString(), mce.getArguments()));
}
if (!outerOrStaticMember) {
if (mce.isImplicitThis()) {
throw newVariableError(mce.getMethodAsString(), mce.getMethod());
} else {
throw newVariableError(mce.getObjectExpression().getText(), mce.getObjectExpression());
}
}
mce.getMethod().visit(this);
mce.getArguments().visit(this);
} else {
super.visitMethodCallExpression(mce);
}
}
@Override
public void visitVariableExpression(final VariableExpression ve) {
// before this/super ctor call completes, only params and static or outer members are accessible
if (inSpecialConstructorCall && (ve.isThisExpression() || ve.isSuperExpression() || isNonStaticMemberAccess(ve))) {
throw newVariableError(ve.getName(), ve.getLineNumber() > 0 ? ve : node); // TODO: context for default argument
}
}
//
private boolean inClosure, inSpecialConstructorCall;
private boolean isNonStaticMemberAccess(final VariableExpression ve) {
Variable variable = ve.getAccessedVariable();
return !inClosure && variable != null && !isStatic(variable.getModifiers())
&& !(variable instanceof DynamicVariable) && !(variable instanceof Parameter);
}
private boolean isThisObjectExpression(final MethodCallExpression mce) {
if (mce.isImplicitThis()) {
return true;
} else if (mce.getObjectExpression() instanceof VariableExpression) {
VariableExpression var = (VariableExpression) mce.getObjectExpression();
return var.isThisExpression() || var.isSuperExpression();
} else {
return false;
}
}
private GroovyRuntimeException newVariableError(final String name, final ASTNode node) {
RuntimeParserException rpe = new RuntimeParserException("Cannot reference '" + name +
"' before supertype constructor has been called. Possible causes:\n" +
"You attempted to access an instance field, method, or property.\n" +
"You attempted to construct a non-static inner class.", node);
rpe.setModule(getClassNode().getModule());
return rpe;
}
});
}
}
@Override
public void visitMethod(final MethodNode node) {
// GROOVY-3712: if it's a MOP method, it's an error as they aren't supposed to exist before ACG is invoked
if (MopWriter.isMopMethod(node.getName())) {
throw new RuntimeParserException("Found unexpected MOP methods in the class node for " + classNode.getName() + "(" + node.getName() + ")", classNode);
}
adjustTypesIfMainMethod(node);
this.methodNode = node;
addReturnIfNeeded(node);
Statement stmt = node.getCode();
if (stmt != null) {
stmt.visit(new VerifierCodeVisitor(getClassNode()));
}
}
private static void adjustTypesIfMainMethod(final MethodNode node) {
if (node.isPublic() && node.isStatic() && "main".equals(node.getName())) {
Parameter[] params = node.getParameters();
if (params.length == 1) {
Parameter param = params[0];
if (param.getType() == null || (isObjectType(param.getType()) && !param.getType().isGenericsPlaceHolder())) {
param.setType(ClassHelper.STRING_TYPE.makeArray());
if (isObjectType(node.getReturnType())) {
node.setReturnType(ClassHelper.VOID_TYPE);
}
}
}
}
}
protected void addReturnIfNeeded(final MethodNode node) {
ReturnAdder adder = new ReturnAdder();
adder.visitMethod(node);
}
@Override
public void visitField(final FieldNode node) {
}
private boolean methodNeedsReplacement(final MethodNode m) {
// no method found, we need to replace
if (m == null) return true;
// method is in current class, nothing to be done
if (m.getDeclaringClass() == getClassNode()) return false;
// do not overwrite final
if (isFinal(m.getModifiers())) return false;
return true;
}
@Override
public void visitProperty(final PropertyNode node) {
String name = node.getName();
FieldNode field = node.getField();
String getterName = node.getGetterName();
if (getterName == null) {
getterName = "get" + capitalize(name);
}
String setterName = node.getSetterNameOrDefault();
Statement getterBlock = node.getGetterBlock();
if (getterBlock == null && !node.isPrivate()) {
MethodNode getter = classNode.getGetterMethod(getterName, !node.isStatic());
if (getter == null && isPrimitiveBoolean(node.getType())) {
getter = classNode.getGetterMethod("is" + capitalize(name), !node.isStatic());
}
if (methodNeedsReplacement(getter)) {
getterBlock = createGetterBlock(node, field);
}
}
Statement setterBlock = node.getSetterBlock();
if (setterBlock == null && !node.isPrivate() && !node.isFinal()) {
MethodNode setter = classNode.getSetterMethod(setterName, false); // atypical: allow setter with non-void return type
if (methodNeedsReplacement(setter)) {
setterBlock = createSetterBlock(node, field);
}
}
int accessorModifiers = adjustPropertyModifiersForMethod(node);
if (getterBlock != null) {
visitGetter(node, field, getterBlock, accessorModifiers, getterName);
if (node.getGetterName() == null && isPrimitiveBoolean(node.getType())) {
visitGetter(node, field, getterBlock, accessorModifiers, "is" + capitalize(name));
}
}
if (setterBlock != null) {
MethodNode setter = new MethodNode(setterName, accessorModifiers, ClassHelper.VOID_TYPE, params(param(node.getType(),"value")), ClassNode.EMPTY_ARRAY, setterBlock);
setter.setSynthetic(true);
addPropertyMethod(setter);
if (!field.isSynthetic()) {
copyAnnotations(node, setter);
}
visitMethod(setter);
}
}
private void visitGetter(final PropertyNode node, final FieldNode field, final Statement getterBlock, final int getterModifiers, final String getterName) {
MethodNode getter = new MethodNode(getterName, getterModifiers, node.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, getterBlock);
getter.setSynthetic(true);
addPropertyMethod(getter);
if (!field.isSynthetic() || classNode.getNodeMetaData("_RECORD_HEADER") != null) {
copyAnnotations(node, getter);
}
visitMethod(getter);
}
private static void copyAnnotations(final PropertyNode node, final MethodNode accessor) {
accessor.addAnnotations(node.getAnnotations());
accessor.putNodeMetaData("_SKIPPABLE_ANNOTATIONS", Boolean.TRUE);
}
protected void addPropertyMethod(final MethodNode method) {
classNode.addMethod(method);
markAsGenerated(classNode, method);
// GROOVY-4415 / GROOVY-4645: check that there's no abstract method which corresponds to this one
String methodName = method.getName();
Parameter[] parameters = method.getParameters();
ClassNode methodReturnType = method.getReturnType();
for (MethodNode node : classNode.getAbstractMethods()) {
if (node.getName().equals(methodName)
&& node.getDeclaringClass().equals(classNode)
&& node.getParameters().length == parameters.length) {
if (parameters.length == 1) { // setter
ClassNode abstractMethodParameterType = node.getParameters()[0].getType();
ClassNode methodParameterType = parameters[0].getType();
if (!methodParameterType.isDerivedFrom(abstractMethodParameterType)
&& !methodParameterType.implementsInterface(abstractMethodParameterType)) {
continue;
}
}
ClassNode nodeReturnType = node.getReturnType();
if (!methodReturnType.isDerivedFrom(nodeReturnType)
&& !methodReturnType.implementsInterface(nodeReturnType)) {
continue;
}
// matching method, remove abstract status and use the same body
node.setModifiers(node.getModifiers() ^ ACC_ABSTRACT);
node.setCode(method.getCode());
}
}
}
@FunctionalInterface
public interface DefaultArgsAction {
void call(ArgumentListExpression arguments, Parameter[] parameters, MethodNode method);
}
/**
* Creates a new method for each combination of default parameter expressions.
*/
protected void addDefaultParameterMethods(final ClassNode type) {
List methods = new ArrayList<>(type.getMethods());
addDefaultParameters(methods, (arguments, params, method) -> {
BlockStatement block = new BlockStatement();
MethodNode newMethod = new MethodNode(method.getName(), method.getModifiers() & ~ACC_ABSTRACT, method.getReturnType(), params, method.getExceptions(), block);
MethodNode oldMethod = type.getDeclaredMethod(method.getName(), params);
if (oldMethod != null) {
throw new RuntimeParserException(
"The method with default parameters \"" + method.getTypeDescriptor() +
"\" defines a method \"" + newMethod.getTypeDescriptor() +
"\" that is already defined.",
sourceOf(method));
}
newMethod.addAnnotations(method.getAnnotations());
newMethod.setGenericsTypes(method.getGenericsTypes());
// GROOVY-5632, GROOVY-9151: check for references to parameters that have been removed
GroovyCodeVisitor visitor = new CodeVisitorSupport() {
private boolean inClosure;
@Override
public void visitClosureExpression(final ClosureExpression e) {
boolean saved = inClosure; inClosure = true;
super.visitClosureExpression(e);
inClosure = saved;
}
@Override
public void visitVariableExpression(final VariableExpression e) {
if (e.getAccessedVariable() instanceof Parameter) {
Parameter p = (Parameter) e.getAccessedVariable();
if (!Arrays.asList(params).contains(p) && Arrays.asList(method.getParameters()).contains(p)) { // GROOVY-10602
VariableScope blockScope = block.getVariableScope();
VariableExpression localVariable = (VariableExpression) blockScope.getDeclaredVariable(p.getName());
if (localVariable == null) {
// create a variable declaration so that the name can be found in the new method
localVariable = localVarX(p.getName(), p.getType());
localVariable.setModifiers(p.getModifiers());
blockScope.putDeclaredVariable(localVariable);
localVariable.setInStaticContext(blockScope.isInStaticContext());
block.addStatement(declS(localVariable, p.getInitialExpression()));
}
if (!localVariable.isClosureSharedVariable()) {
localVariable.setClosureSharedVariable(inClosure);
}
}
}
}
};
visitor.visitArgumentlistExpression(arguments);
// if variable was created to capture an initial value expression, reference it in arguments as well
for (ListIterator it = arguments.getExpressions().listIterator(); it.hasNext(); ) {
Expression argument = it.next();
if (argument instanceof CastExpression) {
argument = ((CastExpression) argument).getExpression();
}
for (Parameter p : method.getParameters()) {
if (p.hasInitialExpression() && p.getInitialExpression() == argument) {
if (block.getVariableScope().getDeclaredVariable(p.getName()) != null) {
it.set(varX(p.getName()));
}
break;
}
}
}
// delegate to original method using arguments derived from defaults
MethodCallExpression call = callThisX(method.getName(), arguments);
call.setMethodTarget(method);
call.setImplicitThis(true);
if (method.isVoidMethod()) {
block.addStatement(new ExpressionStatement(call));
} else {
block.addStatement(new ReturnStatement(call));
}
// GROOVY-5681: set anon. inner enclosing method reference
visitor = new CodeVisitorSupport() {
@Override
public void visitConstructorCallExpression(final ConstructorCallExpression call) {
if (call.isUsingAnonymousInnerClass()) {
call.getType().setEnclosingMethod(newMethod);
}
super.visitConstructorCallExpression(call);
}
};
visitor.visitBlockStatement(block);
addPropertyMethod(newMethod);
newMethod.putNodeMetaData(DEFAULT_PARAMETER_GENERATED, Boolean.TRUE);
});
}
/**
* Creates a new constructor for each combination of default parameter expressions.
*/
protected void addDefaultParameterConstructors(final ClassNode type) {
List constructors = new ArrayList<>(type.getDeclaredConstructors());
addDefaultParameters(constructors, (arguments, params, method) -> {
// GROOVY-9151: check for references to parameters that have been removed
for (ListIterator it = arguments.getExpressions().listIterator(); it.hasNext(); ) {
Expression argument = it.next();
if (argument instanceof CastExpression) {
argument = ((CastExpression) argument).getExpression();
}
if (argument instanceof VariableExpression) {
VariableExpression v = (VariableExpression) argument;
if (v.getAccessedVariable() instanceof Parameter) {
Parameter p = (Parameter) v.getAccessedVariable();
if (p.getInitialExpression() instanceof ConstantExpression && !Arrays.asList(params).contains(p)){
// for simple default value, replace argument "(Type) param" with "(Type) <>"
it.set(castX(method.getParameters()[it.nextIndex() - 1].getType(), p.getInitialExpression()));
}
}
}
}
GroovyCodeVisitor visitor = new CodeVisitorSupport() {
@Override
public void visitVariableExpression(final VariableExpression e) {
if (e.getAccessedVariable() instanceof Parameter) {
Parameter p = (Parameter) e.getAccessedVariable();
if (p.hasInitialExpression() && !Arrays.asList(params).contains(p)) {
String error = String.format(
"The generated constructor \"%s(%s)\" references parameter '%s' which has been replaced by a default value expression.",
type.getNameWithoutPackage(),
Arrays.stream(params).map(Parameter::getType).map(ClassNodeUtils::formatTypeName).collect(joining(",")),
p.getName());
throw new RuntimeParserException(error, sourceOf(method));
}
}
}
};
visitor.visitArgumentlistExpression(arguments);
ConstructorNode old = type.getDeclaredConstructor(params);
if (old == null || isGenerated(old)) { type.removeConstructor(old);
// delegate to original constructor using arguments derived from defaults
addConstructor(params, (ConstructorNode) method, stmt(ctorThisX(arguments)), type);
} else {
String warning = "Default argument(s) specify duplicate constructor: " +
old.getTypeDescriptor().replace("void ", type.getNameWithoutPackage());
type.getModule().getContext().addWarning(warning, method.getLineNumber() > 0 ? method : type);
}
});
}
protected void addConstructor(final Parameter[] newParams, final ConstructorNode ctor, final Statement code, final ClassNode type) {
ConstructorNode newConstructor = type.addConstructor(ctor.getModifiers(), newParams, ctor.getExceptions(), code);
newConstructor.putNodeMetaData(DEFAULT_PARAMETER_GENERATED, Boolean.TRUE);
markAsGenerated(type, newConstructor);
// TODO: Copy annotations, etc.?
// set anon. inner enclosing method reference
code.visit(new CodeVisitorSupport() {
@Override
public void visitConstructorCallExpression(final ConstructorCallExpression call) {
if (call.isUsingAnonymousInnerClass()) {
call.getType().setEnclosingMethod(newConstructor);
}
super.visitConstructorCallExpression(call);
}
});
}
/**
* Creates a new helper method for each combination of default parameter expressions.
*/
protected void addDefaultParameters(final List extends MethodNode> methods, final DefaultArgsAction action) {
for (MethodNode method : methods) {
if (method.hasDefaultValue()) {
addDefaultParameters(action, method);
}
}
}
protected void addDefaultParameters(final DefaultArgsAction action, final MethodNode method) {
Parameter[] parameters = method.getParameters();
var n = Arrays.stream(parameters).filter(Parameter::hasInitialExpression).count();
for (int i = 1; i <= n; i += 1) { // drop parameters with value from right to left
Parameter[] newParams = new Parameter[parameters.length - i];
ArgumentListExpression arguments = new ArgumentListExpression();
int index = 0;
int j = 1;
for (Parameter parameter : parameters) {
if (parameter == null) {
throw new GroovyBugError("Parameter should not be null for method " + methodNode.getName());
} else if (parameter.isReceiver()) { // JSR 308
newParams[index++] = parameter;
} else {
Expression e;
if (j > n - i && parameter.hasInitialExpression()) {
e = parameter.getInitialExpression();
} else {
newParams[index++] = parameter;
e = varX(parameter);
}
arguments.addExpression(castX(parameter.getType(), e));
if (parameter.hasInitialExpression()) j += 1;
}
}
action.call(arguments, newParams, method);
}
for (Parameter parameter : parameters) {
Expression value = parameter.getInitialExpression();
if (value != null) {
// move the default expression from parameter to node metadata
parameter.putNodeMetaData(Verifier.INITIAL_EXPRESSION, value);
parameter.setInitialExpression(null);
}
}
}
protected void addClosureCode(InnerClassNode node) {
// add a new invoke
}
protected void addInitialization(final ClassNode node) {
boolean addSwapInit = moveOptimizedConstantsInitialization(node);
for (ConstructorNode cn : node.getDeclaredConstructors()) {
addInitialization(node, cn);
}
if (addSwapInit) {
BytecodeSequence seq = new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(MethodVisitor mv) {
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(node), SWAP_INIT, "()V", false);
}
});
List swapCall = new ArrayList<>(1);
swapCall.add(seq);
node.addStaticInitializerStatements(swapCall, true);
}
}
protected void addInitialization(final ClassNode node, final ConstructorNode constructorNode) {
Statement firstStatement = constructorNode.getFirstStatement();
// if some transformation decided to generate constructor then it probably knows best
if (firstStatement instanceof BytecodeSequence) return;
ConstructorCallExpression specialCtorCall = getFirstIfSpecialConstructorCall(firstStatement);
// in case of this(...) let the other constructor initialize
if (specialCtorCall != null && specialCtorCall.isThisCall()) return;
boolean isEnum = node.isEnum();
List statements = new ArrayList<>();
List staticStatements = new ArrayList<>();
List initStmtsAfterEnumValuesInit = new ArrayList<>();
if (!Traits.isTrait(node)) {
Set explicitStaticPropsInEnum = !isEnum
? Collections.emptySet() : getExplicitStaticProperties(node);
for (FieldNode fn : node.getFields()) {
addFieldInitialization(statements, staticStatements, fn, isEnum,
initStmtsAfterEnumValuesInit, explicitStaticPropsInEnum);
}
}
BlockStatement block = getCodeAsBlock(constructorNode);
List blockStatements = block.getStatements();
if (!blockStatements.isEmpty()) {
if (specialCtorCall != null) {
blockStatements.remove(0);
statements.add(0, firstStatement);
// GROOVY-7686: place local variable references above super ctor call
if (node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous()) {
extractVariableReferenceInitializers(statements).forEach(s -> statements.add(0, s));
}
}
if (node instanceof InnerClassNode) {
// GROOVY-4471: place this$0 init above other field init and super ctor call;
// there can be field initializers that depend on method/property dispatching
Statement initThis$0 = getImplicitThis$0Stmt(blockStatements);
if (initThis$0 != null) {
statements.add(0, initThis$0);
}
}
statements.addAll(node.getObjectInitializerStatements());
statements.addAll(blockStatements);
} else {
statements.addAll(node.getObjectInitializerStatements());
}
BlockStatement newBlock = new BlockStatement(statements, block.getVariableScope());
newBlock.setSourcePosition(block);
constructorNode.setCode(newBlock);
if (!staticStatements.isEmpty()) {
if (isEnum) {
// GROOVY-3161: initialization statements for explicitly declared static
// fields inside an enum should come after enum values are initialized
staticStatements.removeAll(initStmtsAfterEnumValuesInit);
node.addStaticInitializerStatements(staticStatements, true);
if (!initStmtsAfterEnumValuesInit.isEmpty()) {
node.positionStmtsAfterEnumInitStmts(initStmtsAfterEnumValuesInit);
}
} else {
node.addStaticInitializerStatements(staticStatements, true);
}
}
}
private static Set getExplicitStaticProperties(final ClassNode cn) {
Set staticProperties = new HashSet<>();
for (PropertyNode pn : cn.getProperties()) {
if (!pn.isSynthetic() && pn.getField().isStatic()) {
staticProperties.add(pn.getField().getName());
}
}
for (FieldNode fn : cn.getFields()) {
if (!fn.isSynthetic() && fn.isStatic() && fn.getType() != cn) {
staticProperties.add(fn.getName());
}
}
return staticProperties;
}
/**
* When InnerClassVisitor adds this.this$0 = $p$n
, it adds it
* as a BlockStatement having that ExpressionStatement.
*/
private static Statement getImplicitThis$0Stmt(final List statements) {
for (Statement stmt : statements) {
if (stmt instanceof BlockStatement) {
List stmts = ((BlockStatement) stmt).getStatements();
for (Statement bstmt : stmts) {
if (bstmt instanceof ExpressionStatement) {
if (extractImplicitThis$0StmtFromExpression(stmts, bstmt)) return bstmt;
}
}
} else if (stmt instanceof ExpressionStatement) {
if (extractImplicitThis$0StmtFromExpression(statements, stmt)) return stmt;
}
}
return null;
}
private static boolean extractImplicitThis$0StmtFromExpression(final List stmts, final Statement exprStmt) {
Expression expr = ((ExpressionStatement) exprStmt).getExpression();
if (expr instanceof BinaryExpression) {
Expression lExpr = ((BinaryExpression) expr).getLeftExpression();
if (lExpr instanceof FieldExpression) {
if ("this$0".equals(((FieldExpression) lExpr).getFieldName())) {
stmts.remove(exprStmt); // remove from here and let the caller reposition it
return true;
}
}
}
return false;
}
private static List extractVariableReferenceInitializers(final List statements) {
List localVariableReferences = new ArrayList<>();
for (ListIterator it = statements.listIterator(1); it.hasNext();) {
// the first statement is the super constructor call ^
Statement stmt = it.next();
if (stmt instanceof ExpressionStatement
&& ((ExpressionStatement) stmt).getExpression() instanceof BinaryExpression) {
BinaryExpression expr = (BinaryExpression) ((ExpressionStatement) stmt).getExpression();
if (expr.getOperation().getType() == Types.ASSIGN
&& expr.getLeftExpression() instanceof FieldExpression
&& expr.getLeftExpression().getType().equals(ClassHelper.REFERENCE_TYPE)
&& (((FieldExpression) expr.getLeftExpression()).getField().getModifiers() & Opcodes.ACC_SYNTHETIC) != 0
/* also could check if the right expression is a variable expression that references ctor parameter */) {
localVariableReferences.add(stmt);
it.remove();
}
}
}
return localVariableReferences;
}
// TODO: add generics to collections
protected void addFieldInitialization(final List list, final List staticList, final FieldNode fieldNode, final boolean isEnumClassNode, final List initStmtsAfterEnumValuesInit, final Set explicitStaticPropsInEnum) {
Expression expression = fieldNode.getInitialExpression();
if (expression != null) {
final FieldExpression fe = fieldX(fieldNode);
if (fieldNode.getType().equals(ClassHelper.REFERENCE_TYPE) && ((fieldNode.getModifiers() & ACC_SYNTHETIC) != 0)) {
fe.setUseReferenceDirectly(true);
}
Statement statement = stmt(binX(
fe,
Token.newSymbol(Types.ASSIGN, fieldNode.getLineNumber(), fieldNode.getColumnNumber()),
expression
));
if (fieldNode.isStatic()) {
// GROOVY-3311: pre-defined constants added by compiler for numbers/characters should be
// initialized first so that code dependent on them does not see their values as empty
Expression initialValueExpression = fieldNode.getInitialValueExpression();
Expression transformed = transformInlineConstants(initialValueExpression, fieldNode.getType());
if (transformed instanceof ConstantExpression) {
ConstantExpression cexp = (ConstantExpression) transformed;
cexp = transformToPrimitiveConstantIfPossible(cexp);
if (fieldNode.isFinal() && ClassHelper.isStaticConstantInitializerType(cexp.getType()) && cexp.getType().equals(fieldNode.getType())) {
fieldNode.setInitialValueExpression(transformed);
return; // GROOVY-5150: primitive type constants will be initialized directly
}
staticList.add(0, statement);
} else {
staticList.add(statement);
}
fieldNode.setInitialValueExpression(null); // to avoid double initialization in case of several constructors
/*
* If it is a statement for an explicitly declared static field inside an enum, store its
* reference. For enums, they need to be handled differently as such init statements should
* come after the enum values have been initialized inside block. GROOVY-3161.
*/
if (isEnumClassNode && explicitStaticPropsInEnum.contains(fieldNode.getName())) {
initStmtsAfterEnumValuesInit.add(statement);
}
} else {
list.add(statement);
}
}
}
/**
* Capitalizes the start of the given bean property name.
*/
public static String capitalize(final String name) {
return BeanUtils.capitalize(name);
}
protected Statement createGetterBlock(final PropertyNode propertyNode, final FieldNode field) {
String owner = BytecodeHelper.getClassInternalName(classNode);
return new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(final MethodVisitor mv) {
if (field.isStatic()) {
mv.visitFieldInsn(GETSTATIC, owner, field.getName(), BytecodeHelper.getTypeDescription(field.getType()));
} else {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, owner, field.getName(), BytecodeHelper.getTypeDescription(field.getType()));
}
BytecodeHelper.doReturn(mv, field.getType());
}
});
}
protected Statement createSetterBlock(final PropertyNode propertyNode, final FieldNode field) {
String owner = BytecodeHelper.getClassInternalName(classNode);
return new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(final MethodVisitor mv) {
if (field.isStatic()) {
BytecodeHelper.load(mv, field.getType(), 0);
mv.visitFieldInsn(PUTSTATIC, owner, field.getName(), BytecodeHelper.getTypeDescription(field.getType()));
} else {
mv.visitVarInsn(ALOAD, 0);
BytecodeHelper.load(mv, field.getType(), 1);
mv.visitFieldInsn(PUTFIELD, owner, field.getName(), BytecodeHelper.getTypeDescription(field.getType()));
}
mv.visitInsn(RETURN);
}
});
}
public void visitGenericType(final GenericsType genericsType) {
}
public static Long getTimestampFromFieldName(final String fieldName) {
if (fieldName.startsWith(__TIMESTAMP__)) {
try {
return Long.decode(fieldName.substring(__TIMESTAMP__.length()));
} catch (NumberFormatException e) {
return Long.MAX_VALUE;
}
}
return null;
}
public static long getTimestamp(final Class> clazz) {
if (clazz.getClassLoader() instanceof GroovyClassLoader.InnerLoader) {
GroovyClassLoader.InnerLoader innerLoader = (GroovyClassLoader.InnerLoader) clazz.getClassLoader();
return innerLoader.getTimeStamp();
}
for (Field field : clazz.getFields()) {
if (isStatic(field.getModifiers())) {
Long timestamp = getTimestampFromFieldName(field.getName());
if (timestamp != null) {
return timestamp;
}
}
}
return Long.MAX_VALUE;
}
protected void addCovariantMethods(final ClassNode classNode) {
// unimplemented abstract methods from interfaces
Map absInterfaceMethods = ClassNodeUtils.getDeclaredMethodsFromInterfaces(classNode);
Map allInterfaceMethods = new HashMap<>(absInterfaceMethods);
ClassNodeUtils.addDeclaredMethodsFromAllInterfaces(classNode, allInterfaceMethods);
List declaredMethods = new ArrayList<>(classNode.getMethods());
for (Iterator methodsIterator = declaredMethods.iterator(); methodsIterator.hasNext(); ) {
MethodNode m = methodsIterator.next();
// remove all static, private and package-private methods
if (m.isStatic() || !(m.isPublic() || m.isProtected())) {
methodsIterator.remove();
}
absInterfaceMethods.remove(m.getTypeDescriptor());
MethodNode interfaceMethod = allInterfaceMethods.get(m.getTypeDescriptor());
if (interfaceMethod != null && !m.isPublic() && !m.isStaticConstructor() && ((m.getModifiers() & ACC_SYNTHETIC) == 0)) {
throw new RuntimeParserException("The method " + m.getName() + " should be public as it implements the corresponding method from interface " + interfaceMethod.getDeclaringClass(), sourceOf(m));
}
}
// interfaces may have private/static methods in JDK9
absInterfaceMethods.values().removeIf(interfaceMethod -> interfaceMethod.isPrivate() || interfaceMethod.isStatic());
Map methodsToAdd = new HashMap<>();
Map genericsSpec = Collections.emptyMap();
addCovariantMethods(classNode, declaredMethods, absInterfaceMethods, methodsToAdd, genericsSpec);
if (!methodsToAdd.isEmpty()) {
Map declaredMethodsMap = new HashMap<>();
for (MethodNode mn : declaredMethods) {
declaredMethodsMap.put(mn.getTypeDescriptor(), mn);
}
for (Map.Entry entry : methodsToAdd.entrySet()) {
// we skip bridge methods implemented in current class already
MethodNode mn = declaredMethodsMap.get(entry.getKey());
if (mn == null || !mn.getDeclaringClass().equals(classNode)) {
addPropertyMethod(entry.getValue());
}
}
}
}
private void addCovariantMethods(final ClassNode classNode, final List declaredMethods, final Map interfaceMethods, final Map methodsToAdd, final Map oldGenericsSpec) {
ClassNode sc = classNode.getUnresolvedSuperClass(false);
if (sc != null) {
Map genericsSpec = createGenericsSpec(sc, oldGenericsSpec);
List scMethods = sc.getMethods();
// add bridge methods in original class for overridden methods with changed signature
storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, scMethods);
// add bridge methods in original class for any interface methods overridden by super
if (!interfaceMethods.isEmpty()) {
for (MethodNode method : scMethods) {
if (!method.isStatic())
storeMissingCovariantMethods(interfaceMethods.values(), method, methodsToAdd, Collections.emptyMap(), true);
}
}
addCovariantMethods(sc.redirect(), declaredMethods, interfaceMethods, methodsToAdd, genericsSpec);
}
for (ClassNode anInterface : classNode.getInterfaces()) {
List ifaceMethods = anInterface.getMethods();
Map genericsSpec = createGenericsSpec(anInterface, oldGenericsSpec);
storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, ifaceMethods);
addCovariantMethods(anInterface, declaredMethods, interfaceMethods, methodsToAdd, genericsSpec);
}
}
private void storeMissingCovariantMethods(final List declaredMethods, final Map methodsToAdd, final Map genericsSpec, final List superClassMethods) {
for (MethodNode declaredMethod : declaredMethods) {
if (!declaredMethod.isStatic())
storeMissingCovariantMethods(superClassMethods, declaredMethod, methodsToAdd, genericsSpec, false);
}
}
private void storeMissingCovariantMethods(final Iterable superClassMethods, final MethodNode overrideCandidate, final Map methodsToAdd, final Map genericsSpec, final boolean ignoreError) {
for (MethodNode method : superClassMethods) {
if (!method.isPrivate()
&& (method.getModifiers() & ACC_BRIDGE) == 0
&& (overrideCandidate.getModifiers() & ACC_BRIDGE) == 0
&& method.getName().equals(overrideCandidate.getName())) {
MethodNode bridgeMethod = getCovariantImplementation(method, overrideCandidate, genericsSpec, ignoreError);
if (bridgeMethod != null) {
methodsToAdd.put(bridgeMethod.getTypeDescriptor(), bridgeMethod);
return;
}
}
}
}
private MethodNode getCovariantImplementation(final MethodNode oldMethod, final MethodNode overrideCandidate, Map genericsSpec, final boolean ignoreError) {
if (oldMethod.getGenericsTypes() != null)
genericsSpec = addMethodGenerics(oldMethod, genericsSpec);
boolean equalParameters = equalParametersNormal(overrideCandidate, oldMethod);
if (!equalParameters && !equalParametersWithGenerics(overrideCandidate, oldMethod, genericsSpec)) return null;
ClassNode nmr = overrideCandidate.getReturnType();
ClassNode omr = oldMethod.getReturnType();
boolean equalReturnType = nmr.equals(omr);
// TODO: ClassNode nmrCorrected = correctToGenericsSpec(..., nmr)?
ClassNode omrCorrected = correctToGenericsSpec(genericsSpec, omr);
if (!isAssignable(nmr, omrCorrected)) {
if (ignoreError) return null;
throw new RuntimeParserException(
"The return type of " +
overrideCandidate.getTypeDescriptor() +
" in " + overrideCandidate.getDeclaringClass().getName() +
" is incompatible with " + omrCorrected.getName() +
" in " + oldMethod.getDeclaringClass().getName(),
sourceOf(overrideCandidate));
}
if (equalReturnType && equalParameters) return null;
if (oldMethod.isFinal()) {
throw new RuntimeParserException(
"Cannot override final method " +
oldMethod.getTypeDescriptor() +
" in " + oldMethod.getDeclaringClass().getName(),
sourceOf(overrideCandidate));
}
if (oldMethod.isStatic() != overrideCandidate.isStatic()) {
throw new RuntimeParserException(
"Cannot override method " +
oldMethod.getTypeDescriptor() +
" in " + oldMethod.getDeclaringClass().getName() +
" with disparate static modifier",
sourceOf(overrideCandidate));
}
if (!equalReturnType) {
boolean oldM = ClassHelper.isPrimitiveType(omr);
boolean newM = ClassHelper.isPrimitiveType(nmr);
if (oldM || newM) {
String message;
if (oldM && newM) {
message = " with old and new method having different primitive return types";
} else if (newM) {
message = " with new method having a primitive return type and old method not";
} else /*oldM*/ {
message = " with old method having a primitive return type and new method not";
}
throw new RuntimeParserException(
"Cannot override method " +
oldMethod.getTypeDescriptor() +
" in " + oldMethod.getDeclaringClass().getName() +
message,
sourceOf(overrideCandidate));
}
}
/*
* abstract class A {
* def m(Object o)
* }
* class B extends A {
* def m(String s) { ... }
* //def m(Object o) { this.m((String)o) } -- bridge method
* class C extends B {
* def m(String s) { ... }
* // no bridge necessary; B#m(Object) uses INVOKEVIRTUAL
* }
*/
MethodNode newMethod = ClassNodeUtils.getMethod(overrideCandidate.getDeclaringClass(), overrideCandidate.getName(),
(MethodNode m) -> !m.isAbstract() && m.getReturnType().equals(omr) && equalParametersNormal(m, oldMethod));
if (newMethod != null && newMethod != oldMethod && newMethod != overrideCandidate) {
return null; // bridge method would be redundant
}
// if we reach this point there is least one parameter or return type
// that is different in its specified form, so create a bridge method
String owner = BytecodeHelper.getClassInternalName(classNode);
return new MethodNode(
oldMethod.getName(),
overrideCandidate.getModifiers() | ACC_SYNTHETIC | ACC_BRIDGE,
cleanType(omr),
cleanParameters(oldMethod.getParameters()),
oldMethod.getExceptions(),
new BytecodeSequence(new BytecodeInstruction() {
@Override
public void visit(final MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
Parameter[] para = oldMethod.getParameters();
Parameter[] goal = overrideCandidate.getParameters();
int doubleSlotOffset = 0;
for (int i = 0, n = para.length; i < n; i += 1) {
ClassNode type = para[i].getType();
BytecodeHelper.load(mv, type, i + 1 + doubleSlotOffset);
if (isPrimitiveDouble(type.redirect())
|| isPrimitiveLong(type.redirect())) {
doubleSlotOffset += 1;
}
if (!type.equals(goal[i].getType())) {
BytecodeHelper.doCast(mv, goal[i].getType());
}
}
mv.visitMethodInsn(INVOKEVIRTUAL, owner, overrideCandidate.getName(), BytecodeHelper.getMethodDescriptor(nmr, overrideCandidate.getParameters()), false);
BytecodeHelper.doReturn(mv, oldMethod.getReturnType());
}
})
);
}
private static boolean isAssignable(final ClassNode node, final ClassNode testNode) {
if (node.isArray() && testNode.isArray()) {
return isArrayAssignable(node.getComponentType(), testNode.getComponentType());
}
if (testNode.isInterface()) {
if (node.equals(testNode) || node.implementsInterface(testNode)) return true;
}
return node.isDerivedFrom(testNode);
}
private static boolean isArrayAssignable(final ClassNode node, final ClassNode testNode) {
if (node.isArray() && testNode.isArray()) {
return isArrayAssignable(node.getComponentType(), testNode.getComponentType());
}
return isAssignable(node, testNode);
}
private static Parameter[] cleanParameters(final Parameter[] parameters) {
return Arrays.stream(parameters).map(p -> param(cleanType(p.getType()), p.getName())).toArray(Parameter[]::new);
}
private static ClassNode cleanType(final ClassNode type) {
// TODO: Should this be directly handled by getPlainNodeReference?
if (type.isArray()) return cleanType(type.getComponentType()).makeArray();
return type.getPlainNodeReference();
}
private static boolean equalParametersNormal(final MethodNode m1, final MethodNode m2) {
Parameter[] p1 = m1.getParameters();
Parameter[] p2 = m2.getParameters();
if (p1.length != p2.length) return false;
for (int i = 0, n = p2.length; i < n; i += 1) {
ClassNode type = p2[i].getType();
ClassNode parameterType = p1[i].getType();
if (!parameterType.equals(type)) return false;
}
return true;
}
private static boolean equalParametersWithGenerics(final MethodNode m1, final MethodNode m2, final Map genericsSpec) {
Parameter[] p1 = m1.getParameters();
Parameter[] p2 = m2.getParameters();
if (p1.length != p2.length) return false;
for (int i = 0, n = p2.length; i < n; i += 1) {
ClassNode type = p2[i].getType();
ClassNode genericsType = correctToGenericsSpec(genericsSpec, type);
ClassNode parameterType = p1[i].getType();
if (!parameterType.equals(genericsType)) return false;
}
return true;
}
private static boolean moveOptimizedConstantsInitialization(final ClassNode node) {
if (node.isInterface() && !Traits.isTrait(node)) return false;
String name = SWAP_INIT;
int mods = ACC_STATIC | ACC_SYNTHETIC | ACC_PUBLIC;
BlockStatement methodCode = block(new SwapInitStatement());
boolean swapInitRequired = false;
for (FieldNode fn : node.getFields()) {
if (!fn.isStatic() || !fn.isSynthetic() || !fn.getName().startsWith("$const$")) continue;
if (fn.getInitialExpression() == null) continue;
final FieldExpression fe = fieldX(fn);
if (fn.getType().equals(ClassHelper.REFERENCE_TYPE)) fe.setUseReferenceDirectly(true);
ConstantExpression init = (ConstantExpression) fn.getInitialExpression();
init = constX(init.getValue(), true);
Statement statement = stmt(binX(
fe,
Token.newSymbol(Types.ASSIGN, fn.getLineNumber(), fn.getColumnNumber()),
init
));
fn.setInitialValueExpression(null);
methodCode.addStatement(statement);
swapInitRequired = true;
}
if (swapInitRequired) {
node.addSyntheticMethod(
name, mods, ClassHelper.VOID_TYPE,
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, methodCode);
}
return swapInitRequired;
}
private static ASTNode sourceOf(final MethodNode methodNode) {
if (methodNode.getLineNumber() < 1) {
ClassNode declaringClass = methodNode.getDeclaringClass();
if (methodNode.isSynthetic()) {
String propertyName = getPropertyName(methodNode);
if (propertyName != null) {
PropertyNode propertyNode = declaringClass.getProperty(propertyName);
if (propertyNode != null && propertyNode.getLineNumber() > 0) {
return propertyNode;
}
}
}
return declaringClass;
}
return methodNode;
}
/**
* When constant expressions are created, the value is always wrapped to a non-primitive type.
* Some constant expressions are optimized to return primitive types, but not all primitives are
* handled. This method guarantees to return a similar constant expression but with a primitive type
* instead of a boxed type.
*
* Additionally, single char strings are converted to 'char' types.
*
* @param constantExpression a constant expression
* @return the same instance of constant expression if the type is already primitive, or a primitive
* constant if possible.
*/
public static ConstantExpression transformToPrimitiveConstantIfPossible(final ConstantExpression constantExpression) {
Object value = constantExpression.getValue();
if (value == null) return constantExpression;
ConstantExpression result;
ClassNode type = constantExpression.getType();
if (ClassHelper.isPrimitiveType(type)) return constantExpression;
if (value instanceof String && ((String) value).length() == 1) {
result = constX(((String) value).charAt(0));
result.setType(ClassHelper.char_TYPE);
} else {
type = ClassHelper.getUnwrapper(type);
result = constX(value, true);
result.setType(type);
}
return result;
}
//--------------------------------------------------------------------------
private static class SwapInitStatement extends BytecodeSequence {
private WriterController controller;
SwapInitStatement() {
super(new SwapInitInstruction());
((SwapInitInstruction) getInstructions().get(0)).statement = this;
}
@Override
public void visit(final GroovyCodeVisitor visitor) {
if (visitor instanceof AsmClassGenerator) {
controller = ((AsmClassGenerator) visitor).getController();
}
super.visit(visitor);
}
private static class SwapInitInstruction extends BytecodeInstruction {
private SwapInitStatement statement;
@Override
public void visit(final MethodVisitor mv) {
statement.controller.getCallSiteWriter().makeCallSiteArrayInitializer();
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy