This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 11d8e65 GROOVY-10314: classgen: no extra finally if try or catch(es)
can't reach
11d8e65 is described below
commit 11d8e65751ae4b6556dd0da13e5997e5ff36e4d7
Author: Eric Milles <[email protected]>
AuthorDate: Sat Oct 16 15:33:22 2021 -0500
GROOVY-10314: classgen: no extra finally if try or catch(es) can't reach
---
.../codehaus/groovy/ast/tools/GeneralUtils.java | 33 ++++++++++++++++
.../groovy/classgen/AsmClassGenerator.java | 46 +++++++---------------
.../groovy/classgen/asm/StatementWriter.java | 36 ++++++++++-------
src/test/groovy/bugs/Groovy10314.groovy | 38 ++++++++++++++++++
4 files changed, 107 insertions(+), 46 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
index 4b67b70..dd0b883 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -62,6 +62,8 @@ import org.codehaus.groovy.ast.stmt.ForStatement;
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 org.codehaus.groovy.classgen.BytecodeExpression;
@@ -1071,4 +1073,35 @@ public class GeneralUtils {
}
return sb.toString();
}
+
+ public static boolean maybeFallsThrough(final Statement statement) {
+ if (statement.isEmpty()) return true;
+
+ if (statement instanceof ReturnStatement) {
+ return false;
+ } else if (statement instanceof ThrowStatement) {
+ return false;
+ } else if (statement instanceof BlockStatement) {
+ List<Statement> list = ((BlockStatement)
statement).getStatements();
+ final int last = list.size() - 1;
+ if (!maybeFallsThrough(list.get(last))) return false;
+ for (int i = 0; i < last; i += 1)
+ if (!maybeFallsThrough(list.get(i))) return false;
+ } else if (statement instanceof IfStatement) {
+ return maybeFallsThrough(((IfStatement) statement).getElseBlock())
+ || maybeFallsThrough(((IfStatement) statement).getIfBlock());
+ } else if (statement instanceof SwitchStatement) {
+ // TODO
+ } else if (statement instanceof TryCatchStatement) {
+ TryCatchStatement tryCatch = (TryCatchStatement) statement;
+ if (!maybeFallsThrough(tryCatch.getFinallyStatement())) return
false;
+ for (CatchStatement cs : tryCatch.getCatchStatements())
+ if (maybeFallsThrough(cs.getCode())) return true;
+ return maybeFallsThrough(tryCatch.getTryStatement());
+ } else if (statement instanceof SynchronizedStatement) {
+ return maybeFallsThrough(((SynchronizedStatement)
statement).getCode());
+ }
+
+ return true;
+ }
}
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index 1ba6a2a..616fcb0 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -152,6 +152,7 @@ import static
org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.maybeFallsThrough;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX;
import static
org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PROPERTY_OWNER;
@@ -579,7 +580,7 @@ public class AsmClassGenerator extends ClassGenerator {
MethodVisitor mv = controller.getMethodVisitor();
if (isConstructor && (code == null || !((ConstructorNode)
node).firstStatementIsSpecialConstructorCall())) {
boolean hasCallToSuper = false;
- if (code != null && isInnerClass()) {
+ if (code != null && controller.getClassNode().getOuterClass() !=
null) {
// GROOVY-4471: if the class is an inner class node, there are
chances that
// the call to super is already added so we must ensure not to
add it twice
if (code instanceof BlockStatement) {
@@ -602,7 +603,7 @@ public class AsmClassGenerator extends ClassGenerator {
code.visit(this);
}
- if (!isLastStatementReturnOrThrow(code)) {
+ if (code == null || maybeFallsThrough(code)) {
if (code != null) { // GROOVY-7647, GROOVY-9373
controller.visitLineNumber(code.getLastLineNumber());
}
@@ -627,21 +628,6 @@ public class AsmClassGenerator extends ClassGenerator {
controller.getCompileStack().clear();
}
- private boolean isLastStatementReturnOrThrow(final Statement code) {
- if (code instanceof BlockStatement) {
- BlockStatement blockStatement = (BlockStatement) code;
- List<Statement> statementList = blockStatement.getStatements();
- int nStatements = statementList.size();
- if (nStatements > 0) {
- Statement lastStatement = statementList.get(nStatements - 1);
- if (lastStatement instanceof ReturnStatement || lastStatement
instanceof ThrowStatement) {
- return true;
- }
- }
- }
- return false;
- }
-
private void visitAnnotationDefaultExpression(final AnnotationVisitor av,
final ClassNode type, final Expression exp) {
if (exp instanceof ClosureExpression) {
ClassNode closureClass =
controller.getClosureWriter().getOrAddClosureClass((ClosureExpression) exp,
ACC_PUBLIC);
@@ -880,10 +866,11 @@ public class AsmClassGenerator extends ClassGenerator {
* Loads either this object or if we're inside a closure then load the top
level owner
*/
protected void loadThisOrOwner() {
- if (isInnerClass()) {
-
fieldX(controller.getClassNode().getDeclaredField("owner")).visit(this);
- } else {
+ ClassNode classNode = controller.getClassNode();
+ if (classNode.getOuterClass() == null) {
loadThis(VariableExpression.THIS_EXPRESSION);
+ } else {
+ fieldX(classNode.getDeclaredField("owner")).visit(this);
}
}
@@ -1348,7 +1335,6 @@ public class AsmClassGenerator extends ClassGenerator {
}
}
-
/**
* RHS instance field. should move most of the code in the BytecodeHelper
*/
@@ -2339,6 +2325,13 @@ public class AsmClassGenerator extends ClassGenerator {
// Implementation methods
//--------------------------------------------------------------------------
+ public boolean addInnerClass(final ClassNode innerClass) {
+ ModuleNode mn = controller.getClassNode().getModule();
+ innerClass.setModule(mn);
+ mn.getUnit().addGeneratedInnerClass((InnerClassNode) innerClass);
+ return innerClasses.add(innerClass);
+ }
+
public static int argumentSize(final Expression arguments) {
if (arguments instanceof TupleExpression) {
TupleExpression tupleExpression = (TupleExpression) arguments;
@@ -2378,10 +2371,6 @@ public class AsmClassGenerator extends ClassGenerator {
return false;
}
- private boolean isInnerClass() {
- return controller.getClassNode().getOuterClass() != null;
- }
-
@Deprecated
public static boolean isThisExpression(final Expression expression) {
return ExpressionUtils.isThisExpression(expression);
@@ -2401,13 +2390,6 @@ public class AsmClassGenerator extends ClassGenerator {
return (parameters.length > 0 && parameters[parameters.length -
1].getType().isArray());
}
- public boolean addInnerClass(final ClassNode innerClass) {
- ModuleNode mn = controller.getClassNode().getModule();
- innerClass.setModule(mn);
- mn.getUnit().addGeneratedInnerClass((InnerClassNode)innerClass);
- return innerClasses.add(innerClass);
- }
-
public void onLineNumber(final ASTNode statement, final String message) {
if (statement == null || statement instanceof BlockStatement) return;
diff --git
a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java
b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java
index 7e0222e..671e928 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java
@@ -54,6 +54,7 @@ import java.util.Optional;
import java.util.function.Consumer;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.maybeFallsThrough;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.CHECKCAST;
@@ -332,8 +333,11 @@ public class StatementWriter {
// skip past catch block(s)
Label finallyStart = new Label();
- mv.visitJumpInsn(GOTO, finallyStart);
-
+ boolean fallthroughFinally = false;
+ if (maybeFallsThrough(tryStatement)) {
+ mv.visitJumpInsn(GOTO, finallyStart);
+ fallthroughFinally = true;
+ }
closeRange(tryBlock, mv);
// pop for BlockRecorder
compileStack.pop();
@@ -355,13 +359,16 @@ public class StatementWriter {
// end of catch
closeRange(catches, mv);
- mv.visitJumpInsn(GOTO, finallyStart);
+ if (maybeFallsThrough(catchStatement.getCode())) {
+ mv.visitJumpInsn(GOTO, finallyStart);
+ fallthroughFinally = true;
+ }
compileStack.writeExceptionTable(tryBlock, catchBlock,
BytecodeHelper.getClassInternalName(catchStatement.getExceptionType()));
}
// used to handle exceptions in catches and regularly visited finals
- Label catchAll = new Label();
+ Label catchAll = new Label(), afterCatchAll = new Label();
// add "catch all" block to exception table for try part; we do this
// after the exception blocks so they are not superseded by this one
@@ -372,26 +379,27 @@ public class StatementWriter {
// pop for BlockRecorder
compileStack.pop();
- // start finally
- mv.visitLabel(finallyStart);
- finallyStatement.visit(controller.getAcg());
+ if (fallthroughFinally) {
+ mv.visitLabel(finallyStart);
+ finallyStatement.visit(controller.getAcg());
- // skip past all-catching block
- Label afterCatchAll = new Label();
- mv.visitJumpInsn(GOTO, afterCatchAll);
+ // skip over the catch-finally-rethrow
+ mv.visitJumpInsn(GOTO, afterCatchAll);
+ }
mv.visitLabel(catchAll);
operandStack.push(ClassHelper.make(Throwable.class));
- int anyThrowableIndex =
compileStack.defineTemporaryVariable("throwable", true);
+ int anyThrowable = compileStack.defineTemporaryVariable("throwable",
true);
finallyStatement.visit(controller.getAcg());
// load the throwable and rethrow it
- mv.visitVarInsn(ALOAD, anyThrowableIndex);
+ mv.visitVarInsn(ALOAD, anyThrowable);
mv.visitInsn(ATHROW);
- mv.visitLabel(afterCatchAll);
- compileStack.removeVar(anyThrowableIndex);
+ if (fallthroughFinally)
+ mv.visitLabel(afterCatchAll);
+ compileStack.removeVar(anyThrowable);
}
private BlockRecorder makeBlockRecorder(final Statement finallyStatement) {
diff --git a/src/test/groovy/bugs/Groovy10314.groovy
b/src/test/groovy/bugs/Groovy10314.groovy
new file mode 100644
index 0000000..537a58e
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy10314.groovy
@@ -0,0 +1,38 @@
+/*
+ * 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 groovy.bugs
+
+import org.codehaus.groovy.classgen.asm.AbstractBytecodeTestCase
+
+final class Groovy10314 extends AbstractBytecodeTestCase {
+
+ void testTryFinallyWhereAllPathsReturn() {
+ def result = compile method:'test', '''
+ def test() {
+ try {
+ print 'hello'
+ } finally {
+ print ' world'
+ }
+ }
+ '''
+
+ assert !result.hasStrictSequence(['NOP', 'NOP'])
+ }
+}