http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java new file mode 100644 index 0000000..5e13cd7 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java @@ -0,0 +1,342 @@ +/* + * 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 org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.PackageNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.tools.ParameterUtils; +import org.codehaus.groovy.control.AnnotationConstantsVisitor; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.ErrorCollector; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; +import org.objectweb.asm.Opcodes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec; +import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse; +import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec; + +/** + * A specialized Groovy AST visitor meant to perform additional verifications upon the + * current AST. Currently it does checks on annotated nodes and annotations itself. + * <p> + * Current limitations: + * - annotations on local variables are not supported + */ +public class ExtendedVerifier extends ClassCodeVisitorSupport { + public static final String JVM_ERROR_MESSAGE = "Please make sure you are running on a JVM >= 1.5"; + + private final SourceUnit source; + private ClassNode currentClass; + + public ExtendedVerifier(SourceUnit sourceUnit) { + this.source = sourceUnit; + } + + public void visitClass(ClassNode node) { + AnnotationConstantsVisitor acv = new AnnotationConstantsVisitor(); + acv.visitClass(node, this.source); + this.currentClass = node; + if (node.isAnnotationDefinition()) { + visitAnnotations(node, AnnotationNode.ANNOTATION_TARGET); + } else { + visitAnnotations(node, AnnotationNode.TYPE_TARGET); + } + PackageNode packageNode = node.getPackage(); + if (packageNode != null) { + visitAnnotations(packageNode, AnnotationNode.PACKAGE_TARGET); + } + node.visitContents(this); + } + + public void visitField(FieldNode node) { + visitAnnotations(node, AnnotationNode.FIELD_TARGET); + } + + @Override + public void visitDeclarationExpression(DeclarationExpression expression) { + visitAnnotations(expression, AnnotationNode.LOCAL_VARIABLE_TARGET); + } + + public void visitConstructor(ConstructorNode node) { + visitConstructorOrMethod(node, AnnotationNode.CONSTRUCTOR_TARGET); + } + + public void visitMethod(MethodNode node) { + visitConstructorOrMethod(node, AnnotationNode.METHOD_TARGET); + } + + private void visitConstructorOrMethod(MethodNode node, int methodTarget) { + visitAnnotations(node, methodTarget); + for (int i = 0; i < node.getParameters().length; i++) { + Parameter parameter = node.getParameters()[i]; + visitAnnotations(parameter, AnnotationNode.PARAMETER_TARGET); + } + + if (this.currentClass.isAnnotationDefinition() && !node.isStaticConstructor()) { + ErrorCollector errorCollector = new ErrorCollector(this.source.getConfiguration()); + AnnotationVisitor visitor = new AnnotationVisitor(this.source, errorCollector); + visitor.setReportClass(currentClass); + visitor.checkReturnType(node.getReturnType(), node); + if (node.getParameters().length > 0) { + addError("Annotation members may not have parameters.", node.getParameters()[0]); + } + if (node.getExceptions().length > 0) { + addError("Annotation members may not have a throws clause.", node.getExceptions()[0]); + } + ReturnStatement code = (ReturnStatement) node.getCode(); + if (code != null) { + visitor.visitExpression(node.getName(), code.getExpression(), node.getReturnType()); + visitor.checkCircularReference(currentClass, node.getReturnType(), code.getExpression()); + } + this.source.getErrorCollector().addCollectorContents(errorCollector); + } + Statement code = node.getCode(); + if (code != null) { + code.visit(this); + } + + } + + public void visitProperty(PropertyNode node) { + } + + protected void visitAnnotations(AnnotatedNode node, int target) { + if (node.getAnnotations().isEmpty()) { + return; + } + this.currentClass.setAnnotated(true); + if (!isAnnotationCompatible()) { + addError("Annotations are not supported in the current runtime. " + JVM_ERROR_MESSAGE, node); + return; + } + Map<String, List<AnnotationNode>> runtimeAnnotations = new LinkedHashMap<String, List<AnnotationNode>>(); + for (AnnotationNode unvisited : node.getAnnotations()) { + AnnotationNode visited = visitAnnotation(unvisited); + String name = visited.getClassNode().getName(); + if (visited.hasRuntimeRetention()) { + List<AnnotationNode> seen = runtimeAnnotations.get(name); + if (seen == null) { + seen = new ArrayList<AnnotationNode>(); + } + seen.add(visited); + runtimeAnnotations.put(name, seen); + } + boolean isTargetAnnotation = name.equals("java.lang.annotation.Target"); + + // Check if the annotation target is correct, unless it's the target annotating an annotation definition + // defining on which target elements the annotation applies + if (!isTargetAnnotation && !visited.isTargetAllowed(target)) { + addError("Annotation @" + name + " is not allowed on element " + + AnnotationNode.targetToName(target), visited); + } + visitDeprecation(node, visited); + visitOverride(node, visited); + } + checkForDuplicateAnnotations(node, runtimeAnnotations); + } + + private void checkForDuplicateAnnotations(AnnotatedNode node, Map<String, List<AnnotationNode>> runtimeAnnotations) { + for (Map.Entry<String, List<AnnotationNode>> next : runtimeAnnotations.entrySet()) { + if (next.getValue().size() > 1) { + ClassNode repeatable = null; + AnnotationNode repeatee = next.getValue().get(0); + List<AnnotationNode> repeateeAnnotations = repeatee.getClassNode().getAnnotations(); + for (AnnotationNode anno : repeateeAnnotations) { + ClassNode annoClassNode = anno.getClassNode(); + if (annoClassNode.getName().equals("java.lang.annotation.Repeatable")) { + Expression value = anno.getMember("value"); + if (value instanceof ClassExpression) { + ClassExpression ce = (ClassExpression) value; + if (ce.getType() != null && ce.getType().isAnnotationDefinition()) { + repeatable = ce.getType(); + break; + } + } + } + } + if (repeatable != null) { + AnnotationNode collector = new AnnotationNode(repeatable); + collector.setRuntimeRetention(true); // checked earlier + List<Expression> annos = new ArrayList<Expression>(); + for (AnnotationNode an : next.getValue()) { + annos.add(new AnnotationConstantExpression(an)); + } + collector.addMember("value", new ListExpression(annos)); + node.addAnnotation(collector); + node.getAnnotations().removeAll(next.getValue()); + } + } + } + } + + private static void visitDeprecation(AnnotatedNode node, AnnotationNode visited) { + if (visited.getClassNode().isResolved() && visited.getClassNode().getName().equals("java.lang.Deprecated")) { + if (node instanceof MethodNode) { + MethodNode mn = (MethodNode) node; + mn.setModifiers(mn.getModifiers() | Opcodes.ACC_DEPRECATED); + } else if (node instanceof FieldNode) { + FieldNode fn = (FieldNode) node; + fn.setModifiers(fn.getModifiers() | Opcodes.ACC_DEPRECATED); + } else if (node instanceof ClassNode) { + ClassNode cn = (ClassNode) node; + cn.setModifiers(cn.getModifiers() | Opcodes.ACC_DEPRECATED); + } + } + } + + // TODO GROOVY-5011 handle case of @Override on a property + private void visitOverride(AnnotatedNode node, AnnotationNode visited) { + ClassNode annotationClassNode = visited.getClassNode(); + if (annotationClassNode.isResolved() && annotationClassNode.getName().equals("java.lang.Override")) { + if (node instanceof MethodNode && !Boolean.TRUE.equals(node.getNodeMetaData(Verifier.DEFAULT_PARAMETER_GENERATED))) { + boolean override = false; + MethodNode origMethod = (MethodNode) node; + ClassNode cNode = origMethod.getDeclaringClass(); + if (origMethod.hasDefaultValue()) { + List<MethodNode> variants = cNode.getDeclaredMethods(origMethod.getName()); + for (MethodNode m : variants) { + if (m.getAnnotations().contains(visited) && isOverrideMethod(m)) { + override = true; + break; + } + } + } else { + override = isOverrideMethod(origMethod); + } + + if (!override) { + addError("Method '" + origMethod.getName() + "' from class '" + cNode.getName() + "' does not override " + + "method from its superclass or interfaces but is annotated with @Override.", visited); + } + } + } + } + + private static boolean isOverrideMethod(MethodNode method) { + ClassNode cNode = method.getDeclaringClass(); + ClassNode next = cNode; + outer: + while (next != null) { + Map genericsSpec = createGenericsSpec(next); + MethodNode mn = correctToGenericsSpec(genericsSpec, method); + if (next != cNode) { + ClassNode correctedNext = correctToGenericsSpecRecurse(genericsSpec, next); + MethodNode found = getDeclaredMethodCorrected(genericsSpec, mn, correctedNext); + if (found != null) break; + } + List<ClassNode> ifaces = new ArrayList<ClassNode>(); + ifaces.addAll(Arrays.asList(next.getInterfaces())); + Map updatedGenericsSpec = new HashMap(genericsSpec); + while (!ifaces.isEmpty()) { + ClassNode origInterface = ifaces.remove(0); + if (!origInterface.equals(ClassHelper.OBJECT_TYPE)) { + updatedGenericsSpec = createGenericsSpec(origInterface, updatedGenericsSpec); + ClassNode iNode = correctToGenericsSpecRecurse(updatedGenericsSpec, origInterface); + MethodNode found2 = getDeclaredMethodCorrected(updatedGenericsSpec, mn, iNode); + if (found2 != null) break outer; + ifaces.addAll(Arrays.asList(iNode.getInterfaces())); + } + } + ClassNode superClass = next.getUnresolvedSuperClass(); + if (superClass != null) { + next = correctToGenericsSpecRecurse(updatedGenericsSpec, superClass); + } else { + next = null; + } + } + return next != null; + } + + private static MethodNode getDeclaredMethodCorrected(Map genericsSpec, MethodNode mn, ClassNode correctedNext) { + for (MethodNode orig : correctedNext.getDeclaredMethods(mn.getName())) { + MethodNode method = correctToGenericsSpec(genericsSpec, orig); + if (ParameterUtils.parametersEqual(method.getParameters(), mn.getParameters())) { + return method; + } + } + return null; + } + + /** + * Resolve metadata and details of the annotation. + * + * @param unvisited the node to visit + * @return the visited node + */ + private AnnotationNode visitAnnotation(AnnotationNode unvisited) { + ErrorCollector errorCollector = new ErrorCollector(this.source.getConfiguration()); + AnnotationVisitor visitor = new AnnotationVisitor(this.source, errorCollector); + AnnotationNode visited = visitor.visit(unvisited); + this.source.getErrorCollector().addCollectorContents(errorCollector); + return visited; + } + + /** + * Check if the current runtime allows Annotation usage. + * + * @return true if running on a 1.5+ runtime + */ + protected boolean isAnnotationCompatible() { + return CompilerConfiguration.isPostJDK5(this.source.getConfiguration().getTargetBytecode()); + } + + public void addError(String msg, ASTNode expr) { + this.source.getErrorCollector().addErrorAndContinue( + new SyntaxErrorMessage( + new SyntaxException(msg + '\n', expr.getLineNumber(), expr.getColumnNumber(), expr.getLastLineNumber(), expr.getLastColumnNumber()), this.source) + ); + } + + @Override + protected SourceUnit getSourceUnit() { + return source; + } + + // TODO use it or lose it + public void visitGenericType(GenericsType genericsType) { + + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java b/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java new file mode 100644 index 0000000..76c4e1f --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/FinalVariableAnalyzer.java @@ -0,0 +1,363 @@ +/* + * 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 org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.EmptyExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.PostfixExpression; +import org.codehaus.groovy.ast.expr.PrefixExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.CatchStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.TryCatchStatement; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; + +import java.lang.reflect.Modifier; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +public class FinalVariableAnalyzer extends ClassCodeVisitorSupport { + + private final SourceUnit sourceUnit; + private final VariableNotFinalCallback callback; + + private Set<VariableExpression> declaredFinalVariables = null; + private boolean inAssignment = false; + + private enum VariableState { + is_uninitialized(false), + is_final(true), + is_var(false); + + private final boolean isFinal; + + VariableState(final boolean isFinal) { + this.isFinal = isFinal; + } + + public VariableState getNext() { + switch (this) { + case is_uninitialized: + return is_final; + default: + return is_var; + } + } + + public boolean isFinal() { + return isFinal; + } + } + + private final Deque<Map<Variable, VariableState>> assignmentTracker = new LinkedList<Map<Variable, VariableState>>(); + + public FinalVariableAnalyzer(final SourceUnit sourceUnit) { + this(sourceUnit, null); + } + + public FinalVariableAnalyzer(final SourceUnit sourceUnit, final VariableNotFinalCallback callback) { + this.callback = callback; + this.sourceUnit = sourceUnit; + pushState(); + } + + private Map<Variable, VariableState> pushState() { + Map<Variable, VariableState> state = new StateMap(); + assignmentTracker.add(state); + return state; + } + + private static Variable getTarget(Variable v) { + if (v instanceof VariableExpression) { + Variable t = ((VariableExpression) v).getAccessedVariable(); + if (t == v) return t; + return getTarget(t); + } + return v; + } + + private Map<Variable, VariableState> popState() { + return assignmentTracker.removeLast(); + } + + private Map<Variable, VariableState> getState() { + return assignmentTracker.getLast(); + } + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + public boolean isEffectivelyFinal(Variable v) { + VariableState state = getState().get(v); + return (v instanceof Parameter && state == null) + || (state != null && state.isFinal()); + } + + @Override + public void visitBlockStatement(final BlockStatement block) { + Set<VariableExpression> old = declaredFinalVariables; + declaredFinalVariables = new HashSet<VariableExpression>(); + super.visitBlockStatement(block); + if (callback != null) { + Map<Variable, VariableState> state = getState(); + for (VariableExpression declaredFinalVariable : declaredFinalVariables) { + VariableState variableState = state.get(declaredFinalVariable.getAccessedVariable()); + if (variableState == null || variableState != VariableState.is_final) { + callback.variableNotAlwaysInitialized(declaredFinalVariable); + } + } + } + declaredFinalVariables = old; + } + + @Override + public void visitBinaryExpression(final BinaryExpression expression) { + boolean assignment = StaticTypeCheckingSupport.isAssignment(expression.getOperation().getType()); + boolean isDeclaration = expression instanceof DeclarationExpression; + Expression leftExpression = expression.getLeftExpression(); + Expression rightExpression = expression.getRightExpression(); + if (isDeclaration && leftExpression instanceof VariableExpression) { + VariableExpression var = (VariableExpression) leftExpression; + if (Modifier.isFinal(var.getModifiers())) { + declaredFinalVariables.add(var); + } + } + leftExpression.visit(this); + inAssignment = assignment; + rightExpression.visit(this); + inAssignment = false; + if (assignment) { + if (leftExpression instanceof Variable) { + boolean uninitialized = + isDeclaration && rightExpression == EmptyExpression.INSTANCE; + recordAssignment((Variable) leftExpression, isDeclaration, uninitialized, false, expression); + } else if (leftExpression instanceof TupleExpression) { + TupleExpression te = (TupleExpression) leftExpression; + for (Expression next : te.getExpressions()) { + if (next instanceof Variable) { + recordAssignment((Variable) next, isDeclaration, false, false, next); + } + } + } + } + } + + @Override + public void visitClosureExpression(final ClosureExpression expression) { + boolean old = inAssignment; + inAssignment = false; + super.visitClosureExpression(expression); + inAssignment = old; + } + + @Override + public void visitPrefixExpression(final PrefixExpression expression) { + inAssignment = expression.getExpression() instanceof VariableExpression; + super.visitPrefixExpression(expression); + inAssignment = false; + checkPrePostfixOperation(expression.getExpression(), expression); + } + + @Override + public void visitPostfixExpression(final PostfixExpression expression) { + inAssignment = expression.getExpression() instanceof VariableExpression; + super.visitPostfixExpression(expression); + inAssignment = false; + checkPrePostfixOperation(expression.getExpression(), expression); + } + + private void checkPrePostfixOperation(final Expression variable, final Expression originalExpression) { + if (variable instanceof Variable) { + recordAssignment((Variable) variable, false, false, true, originalExpression); + if (variable instanceof VariableExpression) { + Variable accessed = ((VariableExpression) variable).getAccessedVariable(); + if (accessed != variable) { + recordAssignment(accessed, false, false, true, originalExpression); + } + } + } + } + + @Override + public void visitVariableExpression(final VariableExpression expression) { + super.visitVariableExpression(expression); + if (inAssignment) { + Map<Variable, VariableState> state = getState(); + Variable key = expression.getAccessedVariable(); + VariableState variableState = state.get(key); + if (variableState == VariableState.is_uninitialized) { + variableState = VariableState.is_var; + state.put(key, variableState); + } + } + } + + @Override + public void visitIfElse(final IfStatement ifElse) { + visitStatement(ifElse); + ifElse.getBooleanExpression().visit(this); + Map<Variable, VariableState> ifState = pushState(); + ifElse.getIfBlock().visit(this); + popState(); + Statement elseBlock = ifElse.getElseBlock(); + Map<Variable, VariableState> elseState = pushState(); + if (elseBlock instanceof EmptyStatement) { + // dispatching to EmptyStatement will not call back visitor, + // must call our visitEmptyStatement explicitly + visitEmptyStatement((EmptyStatement) elseBlock); + } else { + elseBlock.visit(this); + } + popState(); + + // merge if/else branches + Map<Variable, VariableState> curState = getState(); + Set<Variable> allVars = new HashSet<Variable>(); + allVars.addAll(curState.keySet()); + allVars.addAll(ifState.keySet()); + allVars.addAll(elseState.keySet()); + for (Variable var : allVars) { + VariableState beforeValue = curState.get(var); + VariableState ifValue = ifState.get(var); + VariableState elseValue = elseState.get(var); + // merge if and else values + VariableState mergedIfElse; + mergedIfElse = ifValue != null && ifValue.isFinal + && elseValue != null && elseValue.isFinal ? VariableState.is_final : VariableState.is_var; + if (beforeValue == null) { + curState.put(var, mergedIfElse); + } else { + if (beforeValue == VariableState.is_uninitialized) { + curState.put(var, mergedIfElse); + } else if (ifValue != null || elseValue != null) { + curState.put(var, VariableState.is_var); + } + } + } + } + + @Override + public void visitTryCatchFinally(final TryCatchStatement statement) { + visitStatement(statement); + Map<Variable, VariableState> beforeTryCatch = new HashMap<Variable, VariableState>(getState()); + statement.getTryStatement().visit(this); + for (CatchStatement catchStatement : statement.getCatchStatements()) { + catchStatement.visit(this); + } + Statement finallyStatement = statement.getFinallyStatement(); + // we need to recall which final variables are unassigned so cloning the current state + Map<Variable, VariableState> afterTryCatchState = new HashMap<Variable, VariableState>(getState()); + if (finallyStatement instanceof EmptyStatement) { + // dispatching to EmptyStatement will not call back visitor, + // must call our visitEmptyStatement explicitly + visitEmptyStatement((EmptyStatement) finallyStatement); + } else { + finallyStatement.visit(this); + } + // and now we must reset to uninitialized state variables which were only initialized during try/catch + Map<Variable, VariableState> afterFinally = new HashMap<Variable, VariableState>(getState()); + for (Map.Entry<Variable, VariableState> entry : afterFinally.entrySet()) { + Variable var = entry.getKey(); + VariableState afterFinallyState = entry.getValue(); + VariableState beforeTryCatchState = beforeTryCatch.get(var); + if (afterFinallyState == VariableState.is_final + && beforeTryCatchState != VariableState.is_final + && afterTryCatchState.get(var) != beforeTryCatchState) { + getState().put(var, beforeTryCatchState == null ? VariableState.is_uninitialized : beforeTryCatchState); + } + } + } + + private void recordAssignment( + Variable var, + boolean isDeclaration, + boolean uninitialized, + boolean forceVariable, + Expression expression) { + if (var == null) { + return; + } + if (!isDeclaration && var.isClosureSharedVariable()) { + getState().put(var, VariableState.is_var); + } + VariableState variableState = getState().get(var); + if (variableState == null) { + variableState = uninitialized ? VariableState.is_uninitialized : VariableState.is_final; + if (getTarget(var) instanceof Parameter) { + variableState = VariableState.is_var; + } + } else { + variableState = variableState.getNext(); + } + if (forceVariable) { + variableState = VariableState.is_var; + } + getState().put(var, variableState); + if (variableState == VariableState.is_var && callback != null) { + callback.variableNotFinal(var, expression); + } + } + + public interface VariableNotFinalCallback { + /** + * Callback called whenever an assignment transforms an effectively final variable into a non final variable + * (aka, breaks the "final" modifier contract) + * + * @param var the variable detected as not final + * @param bexp the expression responsible for the contract to be broken + */ + void variableNotFinal(Variable var, Expression bexp); + + /** + * Callback used whenever a variable is declared as final, but can remain in an uninitialized state + * + * @param var the variable detected as potentially uninitialized + */ + void variableNotAlwaysInitialized(VariableExpression var); + } + + private static class StateMap extends HashMap<Variable, VariableState> { + @Override + public VariableState get(final Object key) { + return super.get(getTarget((Variable) key)); + } + + @Override + public VariableState put(final Variable key, final VariableState value) { + return super.put(getTarget(key), value); + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java b/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java new file mode 100644 index 0000000..8b8a510 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java @@ -0,0 +1,113 @@ +/* + * 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 org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CompileUnit; +import org.codehaus.groovy.ast.MethodNode; + + +/** + * A context shared across generations of a class and its inner classes + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class GeneratorContext { + + private int innerClassIdx = 1; + private int closureClassIdx = 1; + private final CompileUnit compileUnit; + + public GeneratorContext(CompileUnit compileUnit) { + this.compileUnit = compileUnit; + } + + public GeneratorContext(CompileUnit compileUnit, int innerClassOffset) { + this.compileUnit = compileUnit; + this.innerClassIdx = innerClassOffset; + } + + public int getNextInnerClassIdx() { + return innerClassIdx++; + } + + public CompileUnit getCompileUnit() { + return compileUnit; + } + + public String getNextClosureInnerName(ClassNode owner, ClassNode enclosingClass, MethodNode enclosingMethod) { + String methodName = ""; + if (enclosingMethod != null) { + methodName = enclosingMethod.getName(); + + if (enclosingClass.isDerivedFrom(ClassHelper.CLOSURE_TYPE)) { + methodName = ""; + } else { + methodName = "_"+encodeAsValidClassName(methodName); + } + } + return methodName + "_closure" + closureClassIdx++; + } + + + private static final int MIN_ENCODING = ' '; + private static final int MAX_ENCODING = ']'; + private static final boolean[] CHARACTERS_TO_ENCODE = new boolean[MAX_ENCODING-MIN_ENCODING+1]; + static { + CHARACTERS_TO_ENCODE[' '-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['!'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['/'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['.'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE[';'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['$'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['<'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['>'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['['-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE[']'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE[':'-MIN_ENCODING] = true; + CHARACTERS_TO_ENCODE['\\'-MIN_ENCODING] = true; + } + + public static String encodeAsValidClassName(String name) { + final int l = name.length(); + StringBuilder b = null; + int lastEscape = -1; + for(int i = 0; i < l; ++i) { + final int encodeIndex = name.charAt(i) - MIN_ENCODING; + if (encodeIndex >= 0 && encodeIndex < CHARACTERS_TO_ENCODE.length) { + if (CHARACTERS_TO_ENCODE[encodeIndex]) { + if(b == null) { + b = new StringBuilder(name.length() + 3); + b.append(name, 0, i); + } else { + b.append(name, lastEscape + 1, i); + } + b.append('_'); + lastEscape = i; + } + } + } + if(b == null) return name.toString(); + if (lastEscape == -1) throw new GroovyBugError("unexpected escape char control flow in "+name); + b.append(name, lastEscape + 1, l); + return b.toString(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java new file mode 100644 index 0000000..a8d84c8 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java @@ -0,0 +1,454 @@ +/* + * 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 org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.TupleExpression; +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.Statement; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.List; + +import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; + +public class InnerClassCompletionVisitor extends InnerClassVisitorHelper implements Opcodes { + + private final SourceUnit sourceUnit; + private ClassNode classNode; + private FieldNode thisField = null; + + private static final String + CLOSURE_INTERNAL_NAME = BytecodeHelper.getClassInternalName(CLOSURE_TYPE), + CLOSURE_DESCRIPTOR = BytecodeHelper.getTypeDescription(CLOSURE_TYPE); + + public InnerClassCompletionVisitor(CompilationUnit cu, SourceUnit su) { + sourceUnit = su; + } + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + @Override + public void visitClass(ClassNode node) { + this.classNode = node; + thisField = null; + InnerClassNode innerClass = null; + if (!node.isEnum() && !node.isInterface() && node instanceof InnerClassNode) { + innerClass = (InnerClassNode) node; + thisField = innerClass.getField("this$0"); + if (innerClass.getVariableScope() == null && innerClass.getDeclaredConstructors().isEmpty()) { + // add dummy constructor + innerClass.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY, null, null); + } + } + if (node.isEnum() || node.isInterface()) return; + // use Iterator.hasNext() to check for available inner classes + if (node.getInnerClasses().hasNext()) addDispatcherMethods(node); + if (innerClass == null) return; + super.visitClass(node); + addDefaultMethods(innerClass); + } + + @Override + public void visitConstructor(ConstructorNode node) { + addThisReference(node); + super.visitConstructor(node); + } + + private static String getTypeDescriptor(ClassNode node, boolean isStatic) { + return BytecodeHelper.getTypeDescription(getClassNode(node, isStatic)); + } + + private static String getInternalName(ClassNode node, boolean isStatic) { + return BytecodeHelper.getClassInternalName(getClassNode(node, isStatic)); + } + + private static void addDispatcherMethods(ClassNode classNode) { + final int objectDistance = getObjectDistance(classNode); + + // since we added an anonymous inner class we should also + // add the dispatcher methods + + // add method dispatcher + Parameter[] parameters = new Parameter[]{ + new Parameter(ClassHelper.STRING_TYPE, "name"), + new Parameter(ClassHelper.OBJECT_TYPE, "args") + }; + MethodNode method = classNode.addSyntheticMethod( + "this$dist$invoke$" + objectDistance, + ACC_PUBLIC + ACC_SYNTHETIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + BlockStatement block = new BlockStatement(); + setMethodDispatcherCode(block, VariableExpression.THIS_EXPRESSION, parameters); + method.setCode(block); + + // add property setter + parameters = new Parameter[]{ + new Parameter(ClassHelper.STRING_TYPE, "name"), + new Parameter(ClassHelper.OBJECT_TYPE, "value") + }; + method = classNode.addSyntheticMethod( + "this$dist$set$" + objectDistance, + ACC_PUBLIC + ACC_SYNTHETIC, + ClassHelper.VOID_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + setPropertySetterDispatcher(block, VariableExpression.THIS_EXPRESSION, parameters); + method.setCode(block); + + // add property getter + parameters = new Parameter[]{ + new Parameter(ClassHelper.STRING_TYPE, "name") + }; + method = classNode.addSyntheticMethod( + "this$dist$get$" + objectDistance, + ACC_PUBLIC + ACC_SYNTHETIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + setPropertyGetterDispatcher(block, VariableExpression.THIS_EXPRESSION, parameters); + method.setCode(block); + } + + private void getThis(MethodVisitor mv, String classInternalName, String outerClassDescriptor, String innerClassInternalName) { + mv.visitVarInsn(ALOAD, 0); + if (CLOSURE_TYPE.equals(thisField.getType())) { + mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", CLOSURE_DESCRIPTOR); + mv.visitMethodInsn(INVOKEVIRTUAL, CLOSURE_INTERNAL_NAME, "getThisObject", "()Ljava/lang/Object;", false); + mv.visitTypeInsn(CHECKCAST, innerClassInternalName); + } else { + mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", outerClassDescriptor); + } + } + + private void addDefaultMethods(InnerClassNode node) { + final boolean isStatic = isStatic(node); + + ClassNode outerClass = node.getOuterClass(); + final String classInternalName = org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName(node); + final String outerClassInternalName = getInternalName(outerClass, isStatic); + final String outerClassDescriptor = getTypeDescriptor(outerClass, isStatic); + final int objectDistance = getObjectDistance(outerClass); + + // add missing method dispatcher + Parameter[] parameters = new Parameter[]{ + new Parameter(ClassHelper.STRING_TYPE, "name"), + new Parameter(ClassHelper.OBJECT_TYPE, "args") + }; + + String methodName = "methodMissing"; + if (isStatic) + addCompilationErrorOnCustomMethodNode(node, methodName, parameters); + + MethodNode method = node.addSyntheticMethod( + methodName, + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + BlockStatement block = new BlockStatement(); + if (isStatic) { + setMethodDispatcherCode(block, new ClassExpression(outerClass), parameters); + } else { + block.addStatement( + new BytecodeSequence(new BytecodeInstruction() { + public void visit(MethodVisitor mv) { + getThis(mv,classInternalName,outerClassDescriptor,outerClassInternalName); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$invoke$" + objectDistance, "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", false); + mv.visitInsn(ARETURN); + } + }) + ); + } + method.setCode(block); + + // add static missing method dispatcher + methodName = "$static_methodMissing"; + if (isStatic) + addCompilationErrorOnCustomMethodNode(node, methodName, parameters); + + method = node.addSyntheticMethod( + methodName, + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + setMethodDispatcherCode(block, new ClassExpression(outerClass), parameters); + method.setCode(block); + + // add property setter dispatcher + parameters = new Parameter[]{ + new Parameter(ClassHelper.STRING_TYPE, "name"), + new Parameter(ClassHelper.OBJECT_TYPE, "val") + }; + + methodName = "propertyMissing"; + if (isStatic) + addCompilationErrorOnCustomMethodNode(node, methodName, parameters); + + method = node.addSyntheticMethod( + methodName, + Opcodes.ACC_PUBLIC, + ClassHelper.VOID_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + if (isStatic) { + setPropertySetterDispatcher(block, new ClassExpression(outerClass), parameters); + } else { + block.addStatement( + new BytecodeSequence(new BytecodeInstruction() { + public void visit(MethodVisitor mv) { + getThis(mv,classInternalName,outerClassDescriptor,outerClassInternalName); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$set$" + objectDistance, "(Ljava/lang/String;Ljava/lang/Object;)V", false); + mv.visitInsn(RETURN); + } + }) + ); + } + method.setCode(block); + + // add static property missing setter dispatcher + methodName = "$static_propertyMissing"; + if (isStatic) + addCompilationErrorOnCustomMethodNode(node, methodName, parameters); + + method = node.addSyntheticMethod( + methodName, + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + ClassHelper.VOID_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + setPropertySetterDispatcher(block, new ClassExpression(outerClass), parameters); + method.setCode(block); + + // add property getter dispatcher + parameters = new Parameter[]{ + new Parameter(ClassHelper.STRING_TYPE, "name") + }; + + methodName = "propertyMissing"; + if (isStatic) + addCompilationErrorOnCustomMethodNode(node, methodName, parameters); + + method = node.addSyntheticMethod( + methodName, + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + if (isStatic) { + setPropertyGetterDispatcher(block, new ClassExpression(outerClass), parameters); + } else { + block.addStatement( + new BytecodeSequence(new BytecodeInstruction() { + public void visit(MethodVisitor mv) { + getThis(mv,classInternalName,outerClassDescriptor,outerClassInternalName); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$get$" + objectDistance, "(Ljava/lang/String;)Ljava/lang/Object;", false); + mv.visitInsn(ARETURN); + } + }) + ); + } + method.setCode(block); + + // add static property missing getter dispatcher + methodName = "$static_propertyMissing"; + if (isStatic) + addCompilationErrorOnCustomMethodNode(node, methodName, parameters); + + method = node.addSyntheticMethod( + methodName, + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + ClassHelper.OBJECT_TYPE, + parameters, + ClassNode.EMPTY_ARRAY, + null + ); + + block = new BlockStatement(); + setPropertyGetterDispatcher(block, new ClassExpression(outerClass), parameters); + method.setCode(block); + } + + /** + * Adds a compilation error if a {@link MethodNode} with the given <tt>methodName</tt> and + * <tt>parameters</tt> exists in the {@link InnerClassNode}. + */ + private void addCompilationErrorOnCustomMethodNode(InnerClassNode node, String methodName, Parameter[] parameters) { + MethodNode existingMethodNode = node.getMethod(methodName, parameters); + // if there is a user-defined methodNode, add compiler error msg and continue + if (existingMethodNode != null && !existingMethodNode.isSynthetic()) { + addError("\"" +methodName + "\" implementations are not supported on static inner classes as " + + "a synthetic version of \"" + methodName + "\" is added during compilation for the purpose " + + "of outer class delegation.", + existingMethodNode); + } + } + + private void addThisReference(ConstructorNode node) { + if (!shouldHandleImplicitThisForInnerClass(classNode)) return; + Statement code = node.getCode(); + + // add "this$0" field init + + //add this parameter to node + Parameter[] params = node.getParameters(); + Parameter[] newParams = new Parameter[params.length + 1]; + System.arraycopy(params, 0, newParams, 1, params.length); + String name = getUniqueName(params, node); + + Parameter thisPara = new Parameter(classNode.getOuterClass().getPlainNodeReference(), name); + newParams[0] = thisPara; + node.setParameters(newParams); + + BlockStatement block = null; + if (code == null) { + block = new BlockStatement(); + } else if (!(code instanceof BlockStatement)) { + block = new BlockStatement(); + block.addStatement(code); + } else { + block = (BlockStatement) code; + } + BlockStatement newCode = new BlockStatement(); + addFieldInit(thisPara, thisField, newCode); + ConstructorCallExpression cce = getFirstIfSpecialConstructorCall(block); + if (cce == null) { + cce = new ConstructorCallExpression(ClassNode.SUPER, new TupleExpression()); + block.getStatements().add(0, new ExpressionStatement(cce)); + } + if (shouldImplicitlyPassThisPara(cce)) { + // add thisPara to this(...) + TupleExpression args = (TupleExpression) cce.getArguments(); + List<Expression> expressions = args.getExpressions(); + VariableExpression ve = new VariableExpression(thisPara.getName()); + ve.setAccessedVariable(thisPara); + expressions.add(0, ve); + } + if (cce.isSuperCall()) { + // we have a call to super here, so we need to add + // our code after that + block.getStatements().add(1, newCode); + } + node.setCode(block); + } + + private boolean shouldImplicitlyPassThisPara(ConstructorCallExpression cce) { + boolean pass = false; + ClassNode superCN = classNode.getSuperClass(); + if (cce.isThisCall()) { + pass = true; + } else if (cce.isSuperCall()) { + // if the super class is another non-static inner class in the same outer class hierarchy, implicit this + // needs to be passed + if (!superCN.isEnum() && !superCN.isInterface() && superCN instanceof InnerClassNode) { + InnerClassNode superInnerCN = (InnerClassNode) superCN; + if (!isStatic(superInnerCN) && classNode.getOuterClass().isDerivedFrom(superCN.getOuterClass())) { + pass = true; + } + } + } + return pass; + } + + private String getUniqueName(Parameter[] params, ConstructorNode node) { + String namePrefix = "$p"; + outer: + for (int i = 0; i < 100; i++) { + namePrefix = namePrefix + "$"; + for (Parameter p : params) { + if (p.getName().equals(namePrefix)) continue outer; + } + return namePrefix; + } + addError("unable to find a unique prefix name for synthetic this reference in inner class constructor", node); + return namePrefix; + } + + private static ConstructorCallExpression getFirstIfSpecialConstructorCall(BlockStatement code) { + if (code == null) return null; + + final List<Statement> statementList = code.getStatements(); + if (statementList.isEmpty()) return null; + + final Statement statement = statementList.get(0); + if (!(statement instanceof ExpressionStatement)) return null; + + Expression expression = ((ExpressionStatement) statement).getExpression(); + if (!(expression instanceof ConstructorCallExpression)) return null; + ConstructorCallExpression cce = (ConstructorCallExpression) expression; + if (cce.isSpecialCall()) return cce; + return null; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java new file mode 100644 index 0000000..87815a8 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java @@ -0,0 +1,288 @@ +/* + * 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 org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +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.VariableScope; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +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.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; +import org.objectweb.asm.Opcodes; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcodes { + + private final SourceUnit sourceUnit; + private ClassNode classNode; + private static final int PUBLIC_SYNTHETIC = Opcodes.ACC_PUBLIC + Opcodes.ACC_SYNTHETIC; + private FieldNode thisField = null; + private MethodNode currentMethod; + private FieldNode currentField; + private boolean processingObjInitStatements = false; + private boolean inClosure = false; + + public InnerClassVisitor(CompilationUnit cu, SourceUnit su) { + sourceUnit = su; + } + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + @Override + public void visitClass(ClassNode node) { + this.classNode = node; + thisField = null; + InnerClassNode innerClass = null; + if (!node.isEnum() && !node.isInterface() && node instanceof InnerClassNode) { + innerClass = (InnerClassNode) node; + if (!isStatic(innerClass) && innerClass.getVariableScope() == null) { + thisField = innerClass.addField("this$0", PUBLIC_SYNTHETIC, node.getOuterClass().getPlainNodeReference(), null); + } + } + + super.visitClass(node); + + if (node.isEnum() || node.isInterface()) return; + if (innerClass == null) return; + + if (node.getSuperClass().isInterface()) { + node.addInterface(node.getUnresolvedSuperClass()); + node.setUnresolvedSuperClass(ClassHelper.OBJECT_TYPE); + } + } + + @Override + public void visitClosureExpression(ClosureExpression expression) { + boolean inClosureOld = inClosure; + inClosure = true; + super.visitClosureExpression(expression); + inClosure = inClosureOld; + } + + @Override + protected void visitObjectInitializerStatements(ClassNode node) { + processingObjInitStatements = true; + super.visitObjectInitializerStatements(node); + processingObjInitStatements = false; + } + + @Override + protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + this.currentMethod = node; + visitAnnotations(node); + visitClassCodeContainer(node.getCode()); + // GROOVY-5681: initial expressions should be visited too! + for (Parameter param : node.getParameters()) { + if (param.hasInitialExpression()) { + param.getInitialExpression().visit(this); + } + visitAnnotations(param); + } + this.currentMethod = null; + } + + @Override + public void visitField(FieldNode node) { + this.currentField = node; + super.visitField(node); + this.currentField = null; + } + + @Override + public void visitProperty(PropertyNode node) { + final FieldNode field = node.getField(); + final Expression init = field.getInitialExpression(); + field.setInitialValueExpression(null); + super.visitProperty(node); + field.setInitialValueExpression(init); + } + + @Override + public void visitConstructorCallExpression(ConstructorCallExpression call) { + super.visitConstructorCallExpression(call); + if (!call.isUsingAnonymousInnerClass()) { + passThisReference(call); + return; + } + + InnerClassNode innerClass = (InnerClassNode) call.getType(); + ClassNode outerClass = innerClass.getOuterClass(); + ClassNode superClass = innerClass.getSuperClass(); + if (superClass instanceof InnerClassNode + && !superClass.isInterface() + && !(superClass.isStaticClass()||((superClass.getModifiers()&ACC_STATIC)==ACC_STATIC))) { + insertThis0ToSuperCall(call, innerClass); + } + if (!innerClass.getDeclaredConstructors().isEmpty()) return; + if ((innerClass.getModifiers() & ACC_STATIC) != 0) return; + + VariableScope scope = innerClass.getVariableScope(); + if (scope == null) return; + + // expressions = constructor call arguments + List<Expression> expressions = ((TupleExpression) call.getArguments()).getExpressions(); + // block = init code for the constructor we produce + BlockStatement block = new BlockStatement(); + // parameters = parameters of the constructor + final int additionalParamCount = 1 + scope.getReferencedLocalVariablesCount(); + List<Parameter> parameters = new ArrayList<Parameter>(expressions.size() + additionalParamCount); + // superCallArguments = arguments for the super call == the constructor call arguments + List<Expression> superCallArguments = new ArrayList<Expression>(expressions.size()); + + // first we add a super() call for all expressions given in the + // constructor call expression + int pCount = additionalParamCount; + for (Expression expr : expressions) { + pCount++; + // add one parameter for each expression in the + // constructor call + Parameter param = new Parameter(ClassHelper.OBJECT_TYPE, "p" + pCount); + parameters.add(param); + // add to super call + superCallArguments.add(new VariableExpression(param)); + } + + // add the super call + ConstructorCallExpression cce = new ConstructorCallExpression( + ClassNode.SUPER, + new TupleExpression(superCallArguments) + ); + + block.addStatement(new ExpressionStatement(cce)); + + // we need to add "this" to access unknown methods/properties + // this is saved in a field named this$0 + pCount = 0; + expressions.add(pCount, VariableExpression.THIS_EXPRESSION); + boolean isStatic = isStaticThis(innerClass,scope); + ClassNode outerClassType = getClassNode(outerClass, isStatic); + if (!isStatic && inClosure) outerClassType = ClassHelper.CLOSURE_TYPE; + outerClassType = outerClassType.getPlainNodeReference(); + Parameter thisParameter = new Parameter(outerClassType, "p" + pCount); + parameters.add(pCount, thisParameter); + + thisField = innerClass.addField("this$0", PUBLIC_SYNTHETIC, outerClassType, null); + addFieldInit(thisParameter, thisField, block); + + // for each shared variable we add a reference and save it as field + for (Iterator it = scope.getReferencedLocalVariablesIterator(); it.hasNext();) { + pCount++; + org.codehaus.groovy.ast.Variable var = (org.codehaus.groovy.ast.Variable) it.next(); + VariableExpression ve = new VariableExpression(var); + ve.setClosureSharedVariable(true); + ve.setUseReferenceDirectly(true); + expressions.add(pCount, ve); + + ClassNode rawReferenceType = ClassHelper.REFERENCE_TYPE.getPlainNodeReference(); + Parameter p = new Parameter(rawReferenceType, "p" + pCount); + parameters.add(pCount, p); + p.setOriginType(var.getOriginType()); + final VariableExpression initial = new VariableExpression(p); + initial.setSynthetic(true); + initial.setUseReferenceDirectly(true); + final FieldNode pField = innerClass.addFieldFirst(ve.getName(), PUBLIC_SYNTHETIC,rawReferenceType, initial); + pField.setHolder(true); + pField.setOriginType(ClassHelper.getWrapper(var.getOriginType())); + } + + innerClass.addConstructor(ACC_SYNTHETIC, parameters.toArray(new Parameter[parameters.size()]), ClassNode.EMPTY_ARRAY, block); + } + + private boolean isStaticThis(InnerClassNode innerClass, VariableScope scope) { + if (inClosure) return false; + boolean ret = innerClass.isStaticClass(); + if ( innerClass.getEnclosingMethod()!=null) { + ret = ret || innerClass.getEnclosingMethod().isStatic(); + } else if (currentField!=null) { + ret = ret || currentField.isStatic(); + } else if (currentMethod!=null && "<clinit>".equals(currentMethod.getName())) { + ret = true; + } + return ret; + } + + // this is the counterpart of addThisReference(). To non-static inner classes, outer this should be + // passed as the first argument implicitly. + private void passThisReference(ConstructorCallExpression call) { + ClassNode cn = call.getType().redirect(); + if (!shouldHandleImplicitThisForInnerClass(cn)) return; + + boolean isInStaticContext = true; + if (currentMethod != null) + isInStaticContext = currentMethod.getVariableScope().isInStaticContext(); + else if (currentField != null) + isInStaticContext = currentField.isStatic(); + else if (processingObjInitStatements) + isInStaticContext = false; + + // if constructor call is not in static context, return + if (isInStaticContext) { + // constructor call is in static context and the inner class is non-static - 1st arg is supposed to be + // passed as enclosing "this" instance + // + Expression args = call.getArguments(); + if (args instanceof TupleExpression && ((TupleExpression) args).getExpressions().isEmpty()) { + addError("No enclosing instance passed in constructor call of a non-static inner class", call); + } + return; + } + insertThis0ToSuperCall(call, cn); + + } + + private void insertThis0ToSuperCall(final ConstructorCallExpression call, final ClassNode cn) { + // calculate outer class which we need for this$0 + ClassNode parent = classNode; + int level = 0; + for (; parent != null && parent != cn.getOuterClass(); parent = parent.getOuterClass()) { + level++; + } + + // if constructor call is not in outer class, don't pass 'this' implicitly. Return. + if (parent == null) return; + + //add this parameter to node + Expression argsExp = call.getArguments(); + if (argsExp instanceof TupleExpression) { + TupleExpression argsListExp = (TupleExpression) argsExp; + Expression this0 = VariableExpression.THIS_EXPRESSION; + for (int i = 0; i != level; ++i) + this0 = new PropertyExpression(this0, "this$0"); + argsListExp.getExpressions().add(0, this0); + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java new file mode 100644 index 0000000..7ed0629 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java @@ -0,0 +1,143 @@ +/* + * 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 org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.Parameter; +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.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.GStringExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.SpreadExpression; +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.syntax.Token; +import org.codehaus.groovy.syntax.Types; +import org.objectweb.asm.Opcodes; + +import java.util.ArrayList; +import java.util.List; + +public abstract class InnerClassVisitorHelper extends ClassCodeVisitorSupport { + protected static void setPropertyGetterDispatcher(BlockStatement block, Expression thiz, Parameter[] parameters) { + List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>(); + gStringStrings.add(new ConstantExpression("")); + gStringStrings.add(new ConstantExpression("")); + List<Expression> gStringValues = new ArrayList<Expression>(); + gStringValues.add(new VariableExpression(parameters[0])); + block.addStatement( + new ReturnStatement( + new PropertyExpression( + thiz, + new GStringExpression("$name", gStringStrings, gStringValues) + ) + ) + ); + } + + protected static void setPropertySetterDispatcher(BlockStatement block, Expression thiz, Parameter[] parameters) { + List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>(); + gStringStrings.add(new ConstantExpression("")); + gStringStrings.add(new ConstantExpression("")); + List<Expression> gStringValues = new ArrayList<Expression>(); + gStringValues.add(new VariableExpression(parameters[0])); + block.addStatement( + new ExpressionStatement( + new BinaryExpression( + new PropertyExpression( + thiz, + new GStringExpression("$name", gStringStrings, gStringValues) + ), + Token.newSymbol(Types.ASSIGN, -1, -1), + new VariableExpression(parameters[1]) + ) + ) + ); + } + + protected static void setMethodDispatcherCode(BlockStatement block, Expression thiz, Parameter[] parameters) { + List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>(); + gStringStrings.add(new ConstantExpression("")); + gStringStrings.add(new ConstantExpression("")); + List<Expression> gStringValues = new ArrayList<Expression>(); + gStringValues.add(new VariableExpression(parameters[0])); + block.addStatement( + new ReturnStatement( + new MethodCallExpression( + thiz, + new GStringExpression("$name", gStringStrings, gStringValues), + new ArgumentListExpression( + new SpreadExpression(new VariableExpression(parameters[1])) + ) + ) + ) + ); + } + + protected static boolean isStatic(InnerClassNode node) { + VariableScope scope = node.getVariableScope(); + if (scope != null) return scope.isInStaticContext(); + return (node.getModifiers() & Opcodes.ACC_STATIC) != 0; + } + + protected static ClassNode getClassNode(ClassNode node, boolean isStatic) { + if (isStatic) node = ClassHelper.CLASS_Type; + return node; + } + + protected static int getObjectDistance(ClassNode node) { + int count = 0; + while (node != null && node != ClassHelper.OBJECT_TYPE) { + count++; + node = node.getSuperClass(); + } + return count; + } + + protected static void addFieldInit(Parameter p, FieldNode fn, BlockStatement block) { + VariableExpression ve = new VariableExpression(p); + FieldExpression fe = new FieldExpression(fn); + block.addStatement(new ExpressionStatement( + new BinaryExpression(fe, Token.newSymbol(Types.ASSIGN, -1, -1), ve) + )); + } + + protected static boolean shouldHandleImplicitThisForInnerClass(ClassNode cn) { + if (cn.isEnum() || cn.isInterface()) return false; + if ((cn.getModifiers() & Opcodes.ACC_STATIC) != 0) return false; + + if (!(cn instanceof InnerClassNode)) return false; + InnerClassNode innerClass = (InnerClassNode) cn; + // scope != null means aic, we don't handle that here + if (innerClass.getVariableScope() != null) return false; + // static inner classes don't need this$0 + return (innerClass.getModifiers() & Opcodes.ACC_STATIC) == 0; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java b/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java new file mode 100644 index 0000000..22f3059 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/ReturnAdder.java @@ -0,0 +1,279 @@ +/* + * 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 org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.VariableScope; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.BreakStatement; +import org.codehaus.groovy.ast.stmt.CaseStatement; +import org.codehaus.groovy.ast.stmt.CatchStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.SwitchStatement; +import org.codehaus.groovy.ast.stmt.SynchronizedStatement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.ast.stmt.TryCatchStatement; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class to add return statements. + * Extracted from Verifier as it can be useful for some AST transformations + */ +public class ReturnAdder { + + private static final ReturnStatementListener DEFAULT_LISTENER = new ReturnStatementListener() { + public void returnStatementAdded(final ReturnStatement returnStatement) { + } + }; + + /** + * If set to 'true', then returns are effectively added. This is useful whenever you just want + * to check what returns are produced without eventually adding them. + */ + private final boolean doAdd; + + private final ReturnStatementListener listener; + + public ReturnAdder() { + doAdd = true; + listener = DEFAULT_LISTENER; + } + + public ReturnAdder(ReturnStatementListener listener) { + this.listener = listener; + this.doAdd = false; + } + + /** + * Adds return statements in method code whenever an implicit return is detected. + * @param node the method node where to add return statements + * @deprecated Use {@link #visitMethod(org.codehaus.groovy.ast.MethodNode)} instead + */ + @Deprecated + public static void addReturnIfNeeded(MethodNode node) { + ReturnAdder adder = new ReturnAdder(); + adder.visitMethod(node); + } + + public void visitMethod(MethodNode node) { + Statement statement = node.getCode(); + if (!node.isVoidMethod()) { + if (statement != null) // it happens with @interface methods + { + final Statement code = addReturnsIfNeeded(statement, node.getVariableScope()); + if (doAdd) node.setCode(code); + } + } else if (!node.isAbstract() && node.getReturnType().redirect()!=ClassHelper.VOID_TYPE) { + if (!(statement instanceof BytecodeSequence)) { + BlockStatement newBlock = new BlockStatement(); + Statement code = node.getCode(); + if (code instanceof BlockStatement) { + newBlock.setVariableScope(((BlockStatement) code).getVariableScope()); + } + if (statement instanceof BlockStatement) { + newBlock.addStatements(((BlockStatement)statement).getStatements()); + } else { + newBlock.addStatement(statement); + } + final ReturnStatement returnStatement = ReturnStatement.RETURN_NULL_OR_VOID; + listener.returnStatementAdded(returnStatement); + newBlock.addStatement(returnStatement); + newBlock.setSourcePosition(statement); + if (doAdd) node.setCode(newBlock); + } + } + } + + private Statement addReturnsIfNeeded(Statement statement, VariableScope scope) { + if ( statement instanceof ReturnStatement + || statement instanceof BytecodeSequence + || statement instanceof ThrowStatement) + { + return statement; + } + + if (statement instanceof EmptyStatement) { + final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL); + listener.returnStatementAdded(returnStatement); + return returnStatement; + } + + if (statement instanceof ExpressionStatement) { + ExpressionStatement expStmt = (ExpressionStatement) statement; + Expression expr = expStmt.getExpression(); + ReturnStatement ret = new ReturnStatement(expr); + ret.setSourcePosition(expr); + ret.setStatementLabel(statement.getStatementLabel()); + listener.returnStatementAdded(ret); + return ret; + } + + if (statement instanceof SynchronizedStatement) { + SynchronizedStatement sync = (SynchronizedStatement) statement; + final Statement code = addReturnsIfNeeded(sync.getCode(), scope); + if (doAdd) sync.setCode(code); + return sync; + } + + if (statement instanceof IfStatement) { + IfStatement ifs = (IfStatement) statement; + final Statement ifBlock = addReturnsIfNeeded(ifs.getIfBlock(), scope); + final Statement elseBlock = addReturnsIfNeeded(ifs.getElseBlock(), scope); + if (doAdd) { + ifs.setIfBlock(ifBlock); + ifs.setElseBlock(elseBlock); + } + return ifs; + } + + if (statement instanceof SwitchStatement) { + SwitchStatement swi = (SwitchStatement) statement; + for (CaseStatement caseStatement : swi.getCaseStatements()) { + final Statement code = adjustSwitchCaseCode(caseStatement.getCode(), scope, false); + if (doAdd) caseStatement.setCode(code); + } + final Statement defaultStatement = adjustSwitchCaseCode(swi.getDefaultStatement(), scope, true); + if (doAdd) swi.setDefaultStatement(defaultStatement); + return swi; + } + + if (statement instanceof TryCatchStatement) { + TryCatchStatement trys = (TryCatchStatement) statement; + final boolean[] missesReturn = new boolean[1]; + new ReturnAdder(new ReturnStatementListener() { + @Override + public void returnStatementAdded(ReturnStatement returnStatement) { + missesReturn[0] = true; + } + }).addReturnsIfNeeded(trys.getFinallyStatement(), scope); + boolean hasFinally = !(trys.getFinallyStatement() instanceof EmptyStatement); + + // if there is no missing return in the finally block and the block exists + // there is nothing to do + if (hasFinally && !missesReturn[0]) return trys; + + // add returns to try and catch blocks + final Statement tryStatement = addReturnsIfNeeded(trys.getTryStatement(), scope); + if (doAdd) trys.setTryStatement(tryStatement); + final int len = trys.getCatchStatements().size(); + for (int i = 0; i != len; ++i) { + final CatchStatement catchStatement = trys.getCatchStatement(i); + final Statement code = addReturnsIfNeeded(catchStatement.getCode(), scope); + if (doAdd) catchStatement.setCode(code); + } + return trys; + } + + if (statement instanceof BlockStatement) { + BlockStatement block = (BlockStatement) statement; + + final List list = block.getStatements(); + if (!list.isEmpty()) { + int idx = list.size() - 1; + Statement last = addReturnsIfNeeded((Statement) list.get(idx), block.getVariableScope()); + if (doAdd) list.set(idx, last); + if (!statementReturns(last)) { + final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL); + listener.returnStatementAdded(returnStatement); + if (doAdd) list.add(returnStatement); + } + } else { + ReturnStatement ret = new ReturnStatement(ConstantExpression.NULL); + ret.setSourcePosition(block); + listener.returnStatementAdded(ret); + return ret; + } + + BlockStatement newBlock = new BlockStatement(list, block.getVariableScope()); + newBlock.setSourcePosition(block); + return newBlock; + } + + if (statement == null) { + final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL); + listener.returnStatementAdded(returnStatement); + return returnStatement; + } else { + final List list = new ArrayList(); + list.add(statement); + final ReturnStatement returnStatement = new ReturnStatement(ConstantExpression.NULL); + listener.returnStatementAdded(returnStatement); + list.add(returnStatement); + + BlockStatement newBlock = new BlockStatement(list, new VariableScope(scope)); + newBlock.setSourcePosition(statement); + return newBlock; + } + } + + private Statement adjustSwitchCaseCode(Statement statement, VariableScope scope, boolean defaultCase) { + if(statement instanceof BlockStatement) { + final List list = ((BlockStatement)statement).getStatements(); + if (!list.isEmpty()) { + int idx = list.size() - 1; + Statement last = (Statement) list.get(idx); + if(last instanceof BreakStatement) { + if (doAdd) { + list.remove(idx); + return addReturnsIfNeeded(statement, scope); + } else { + BlockStatement newStmt = new BlockStatement(); + for (int i=0;i<idx; i++) { + newStmt.addStatement((Statement) list.get(i)); + } + return addReturnsIfNeeded(newStmt, scope); + } + } else if(defaultCase) { + return addReturnsIfNeeded(statement, scope); + } + } + } + return statement; + } + + private static boolean statementReturns(Statement last) { + return ( + last instanceof ReturnStatement || + last instanceof BlockStatement || + last instanceof IfStatement || + last instanceof ExpressionStatement || + last instanceof EmptyStatement || + last instanceof TryCatchStatement || + last instanceof BytecodeSequence || + last instanceof ThrowStatement || + last instanceof SynchronizedStatement + ); + } + + /** + * Implement this method in order to be notified whenever a return statement is generated. + */ + public interface ReturnStatementListener { + void returnStatementAdded(ReturnStatement returnStatement); + } +}
