You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/12/19 16:49:43 UTC
[groovy] 01/02: classgen: `StringBuilder` for `assert` with message and general cleanup
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
commit 37fb2c2f75fae2bd39bf1d099d2cc40468849a52
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Dec 19 10:14:45 2022 -0600
classgen: `StringBuilder` for `assert` with message and general cleanup
---
.../groovy/classgen/asm/AssertionWriter.java | 256 +++++++++++----------
1 file changed, 134 insertions(+), 122 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/AssertionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/AssertionWriter.java
index 55dafb6b33..e76b794134 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/AssertionWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/AssertionWriter.java
@@ -21,7 +21,6 @@ package org.codehaus.groovy.classgen.asm;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.AssertStatement;
@@ -36,6 +35,7 @@ import org.objectweb.asm.MethodVisitor;
import java.util.ArrayList;
import java.util.List;
+import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.DUP;
@@ -60,123 +60,130 @@ public class AssertionWriter {
private AssertionTracker assertionTracker;
private AssertionTracker disabledTracker;
- public AssertionWriter(WriterController wc) {
+ public AssertionWriter(final WriterController wc) {
this.controller = wc;
}
- public void writeAssertStatement(AssertStatement statement) {
- MethodVisitor mv = controller.getMethodVisitor();
- OperandStack operandStack = controller.getOperandStack();
-
- // don't rewrite assertions with message
- boolean rewriteAssert = isNull(statement.getMessageExpression());
+ public void writeAssertStatement(final AssertStatement statement) {
AssertionTracker oldTracker = assertionTracker;
- Janitor janitor = new Janitor();
- final Label tryStart = new Label();
- if (rewriteAssert){
- assertionTracker = new AssertionTracker();
- try {
- // because source position seems to be more reliable for statements
- // than for expressions, we get the source text for the whole statement
- assertionTracker.sourceText = new SourceText(statement, controller.getSourceUnit(), janitor);
- mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/powerassert/ValueRecorder");
- mv.visitInsn(DUP);
- mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/powerassert/ValueRecorder", "<init>", "()V", false);
- //TODO: maybe use more specialized type here
- controller.getOperandStack().push(ClassHelper.OBJECT_TYPE);
- assertionTracker.recorderIndex = controller.getCompileStack().defineTemporaryVariable("recorder", true);
- mv.visitLabel(tryStart);
- } catch (SourceTextNotAvailableException e) {
- // set assertionTracker to null to deactivate AssertionWriter#record calls
- assertionTracker = null;
- // don't rewrite assertions w/o source text
- rewriteAssert = false;
+ assertionTracker = null;
+ try {
+ MethodVisitor mv = controller.getMethodVisitor();
+ OperandStack operandStack = controller.getOperandStack();
+ CompileStack compileStack = controller.getCompileStack();
+
+ SourceText sourceText;
+ // don't rewrite assertions with message or no source
+ if (!isNullConstant(statement.getMessageExpression())
+ || (sourceText = getSourceText(statement)) == null) {
+ // test the condition
+ statement.getBooleanExpression().visit(controller.getAcg());
+ Label falseBranch = operandStack.jump(IFEQ);
+ Label afterAssert = new Label();
+ mv.visitJumpInsn(GOTO, afterAssert);
+
+ mv.visitLabel(falseBranch);
+ writeSourcelessAssertText(statement);
+ operandStack.push(ClassHelper.STRING_TYPE);
+ statement.getMessageExpression().visit(controller.getAcg());
+ operandStack.box();
+ throwAssertError();
+
+ mv.visitLabel(afterAssert);
+ return;
}
- }
- statement.getBooleanExpression().visit(controller.getAcg());
+ assertionTracker = new AssertionTracker();
+ assertionTracker.sourceText = sourceText;
- Label exceptionThrower = operandStack.jump(IFEQ);
+ mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/powerassert/ValueRecorder");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/powerassert/ValueRecorder", "<init>", "()V", false);
+ operandStack.push(ClassHelper.OBJECT_TYPE); // TODO: maybe use more specialized type here
+ assertionTracker.recorderIndex = compileStack.defineTemporaryVariable("recorder", true);
+ Label tryStart = new Label(), tryEnd = new Label();
+ mv.visitLabel(tryStart);
+
+ // test the condition
+ statement.getBooleanExpression().visit(controller.getAcg());
+ Label falseBranch = operandStack.jump(IFEQ);
- // do nothing, but clear the value recorder
- if (rewriteAssert) {
- //clean up assertion recorder
+ // clear the value recorder and proceed normally
mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, "org/codehaus/groovy/runtime/powerassert/ValueRecorder", "clear", "()V", false);
- }
- Label afterAssert = new Label();
- mv.visitJumpInsn(GOTO, afterAssert);
- mv.visitLabel(exceptionThrower);
+ Label afterAssert = new Label();
+ mv.visitJumpInsn(GOTO, afterAssert);
- if (rewriteAssert) {
+ mv.visitLabel(falseBranch);
+ // load the (power assert) expression text
mv.visitLdcInsn(assertionTracker.sourceText.getNormalizedText());
mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/powerassert/AssertionRenderer", "render", "(Ljava/lang/String;Lorg/codehaus/groovy/runtime/powerassert/ValueRecorder;)Ljava/lang/String;", false);
- } else {
- writeSourcelessAssertText(statement);
- }
- operandStack.push(ClassHelper.STRING_TYPE);
- AssertionTracker savedTracker = assertionTracker;
- assertionTracker = null;
+ operandStack.push(ClassHelper.STRING_TYPE);
- // now the optional exception expression
- statement.getMessageExpression().visit(controller.getAcg());
- operandStack.box();
- assertFailedMethod.call(mv);
- operandStack.remove(2); // assertFailed called static with 2 arguments
+ int recorderIndex = assertionTracker.recorderIndex;
+ assertionTracker = null; // deactivate AssertionWriter#record calls
+
+ // load the optional message expression
+ statement.getMessageExpression().visit(controller.getAcg());
+ operandStack.box();
+ throwAssertError();
- if (rewriteAssert) {
- final Label tryEnd = new Label();
mv.visitLabel(tryEnd);
mv.visitJumpInsn(GOTO, afterAssert);
- // finally block to clean assertion recorder
- final Label catchAny = new Label();
- mv.visitLabel(catchAny);
- mv.visitVarInsn(ALOAD, savedTracker.recorderIndex);
+
+ // catch-all block to clear value recorder
+ Label catchAll = new Label();
+ mv.visitLabel(catchAll);
+ mv.visitVarInsn(ALOAD, recorderIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, "org/codehaus/groovy/runtime/powerassert/ValueRecorder", "clear", "()V", false);
- mv.visitInsn(ATHROW);
- // add catch any block to exception table
- controller.getCompileStack().addExceptionBlock(tryStart, tryEnd, catchAny, null);
- }
+ mv.visitInsn(ATHROW); // re-throw
+ compileStack.addExceptionBlock(tryStart, tryEnd, catchAll, null);
+
+ mv.visitLabel(afterAssert);
- mv.visitLabel(afterAssert);
- if (rewriteAssert) {
- controller.getCompileStack().removeVar(savedTracker.recorderIndex);
+ compileStack.removeVar(recorderIndex);
+ } finally {
+ assertionTracker = oldTracker;
}
- assertionTracker = oldTracker;
- // close possibly open file handles from getting a sample for
- // power asserts
- janitor.cleanup();
}
- private boolean isNull(Expression messageExpression) {
- return messageExpression instanceof ConstantExpression && ((ConstantExpression) messageExpression).isNullExpression();
+ private SourceText getSourceText(final AssertStatement statement) {
+ Janitor janitor = new Janitor();
+ try {
+ // because source position seems to be more reliable for statements
+ // than for expressions, get the source text for the whole statement
+ return new SourceText(statement, controller.getSourceUnit(), janitor);
+ } catch (SourceTextNotAvailableException e) {
+ return null;
+ } finally {
+ // close open file handles from getting a sample for power asserts
+ janitor.cleanup();
+ }
}
- private void writeSourcelessAssertText(AssertStatement statement) {
+ private void writeSourcelessAssertText(final AssertStatement statement) {
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
+ CompileStack compileStack = controller.getCompileStack();
BooleanExpression booleanExpression = statement.getBooleanExpression();
- // push expression string onto stack
String expressionText = booleanExpression.getText();
- List<String> list = new ArrayList<String>();
- addVariableNames(booleanExpression, list);
- if (list.isEmpty()) {
+ // push expression string onto stack
+ List<String> names = new ArrayList<>();
+ addVariableNames(booleanExpression, names);
+ if (names.isEmpty()) {
mv.visitLdcInsn(expressionText);
} else {
- boolean first = true;
-
- // let's create a new expression
- mv.visitTypeInsn(NEW, "java/lang/StringBuffer");
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitLdcInsn(expressionText + ". Values: ");
- mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuffer", "<init>", "(Ljava/lang/String;)V", false);
- //TODO: maybe use more special type StringBuffer here
- operandStack.push(ClassHelper.OBJECT_TYPE);
- int tempIndex = controller.getCompileStack().defineTemporaryVariable("assert", true);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
+ operandStack.push(ClassHelper.OBJECT_TYPE); // TODO: maybe use more special type StringBuilder here
+ int tempIndex = compileStack.defineTemporaryVariable("assert", true);
- for (String name : list) {
+ boolean first = true;
+ for (String name : names) {
String text = name + " = ";
if (first) {
first = false;
@@ -186,52 +193,23 @@ public class AssertionWriter {
mv.visitVarInsn(ALOAD, tempIndex);
mv.visitLdcInsn(text);
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);
mv.visitInsn(POP);
mv.visitVarInsn(ALOAD, tempIndex);
new VariableExpression(name).visit(controller.getAcg());
operandStack.box();
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "toString", "(Ljava/lang/Object;)Ljava/lang/String;", false);
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;", false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitInsn(POP);
operandStack.remove(1);
}
mv.visitVarInsn(ALOAD, tempIndex);
- controller.getCompileStack().removeVar(tempIndex);
+ compileStack.removeVar(tempIndex);
}
}
- public void record(Expression expression) {
- if (assertionTracker==null) return;
- record(assertionTracker.sourceText.getNormalizedColumn(expression.getLineNumber(), expression.getColumnNumber()));
- }
-
- public void record(Token op) {
- if (assertionTracker==null) return;
- record(assertionTracker.sourceText.getNormalizedColumn(op.getStartLine(), op.getStartColumn()));
- }
-
- private void record(int normalizedColumn) {
- if (assertionTracker==null) return;
-
- MethodVisitor mv = controller.getMethodVisitor();
- OperandStack operandStack = controller.getOperandStack();
-
- operandStack.dup();
- operandStack.box();
-
- mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
- operandStack.push(ClassHelper.OBJECT_TYPE);
- //helper.swapWithObject(ClassHelper.OBJECT_TYPE);
- operandStack.swap();
- mv.visitLdcInsn(normalizedColumn);
- mv.visitMethodInsn(INVOKEVIRTUAL, "org/codehaus/groovy/runtime/powerassert/ValueRecorder", "record", "(Ljava/lang/Object;I)Ljava/lang/Object;", false);
- mv.visitInsn(POP);
- operandStack.remove(2);
- }
-
- private void addVariableNames(Expression expression, List<String> list) {
+ private static void addVariableNames(final Expression expression, final List<String> list) {
if (expression instanceof BooleanExpression) {
BooleanExpression boolExp = (BooleanExpression) expression;
addVariableNames(boolExp.getExpression(), list);
@@ -245,16 +223,50 @@ public class AssertionWriter {
}
}
+ private void throwAssertError() { // TODO: GROOVY-10878
+ assertFailedMethod.call(controller.getMethodVisitor());
+ controller.getOperandStack().remove(2); // assertFailed called with 2 arguments
+ }
+
+ //--------------------------------------------------------------------------
+
public void disableTracker() {
- if (assertionTracker==null) return;
- disabledTracker = assertionTracker;
- assertionTracker = null;
+ if (assertionTracker != null) {
+ disabledTracker = assertionTracker;
+ assertionTracker = null;
+ }
}
public void reenableTracker() {
- if (disabledTracker==null) return;
- assertionTracker = disabledTracker;
- disabledTracker = null;
+ if (disabledTracker != null) {
+ assertionTracker = disabledTracker;
+ disabledTracker = null;
+ }
}
+ public void record(final Token token) {
+ if (assertionTracker != null)
+ record(assertionTracker.sourceText.getNormalizedColumn(token.getStartLine(), token.getStartColumn()));
+ }
+
+ public void record(final Expression expression) {
+ if (assertionTracker != null)
+ record(assertionTracker.sourceText.getNormalizedColumn(expression.getLineNumber(), expression.getColumnNumber()));
+ }
+
+ private void record(final int normalizedColumn) {
+ MethodVisitor mv = controller.getMethodVisitor();
+ OperandStack operandStack = controller.getOperandStack();
+
+ operandStack.dup();
+ operandStack.box();
+
+ mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
+ operandStack.push(ClassHelper.OBJECT_TYPE);
+ operandStack.swap();
+ mv.visitLdcInsn(normalizedColumn);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "org/codehaus/groovy/runtime/powerassert/ValueRecorder", "record", "(Ljava/lang/Object;I)Ljava/lang/Object;", false);
+ mv.visitInsn(POP);
+ operandStack.remove(2);
+ }
}