blob: b25fed69fa97fb09b5a4f85bd28ff7af83140183 [file] [log] [blame]
/*
* 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.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;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.powerassert.SourceText;
import org.codehaus.groovy.runtime.powerassert.SourceTextNotAvailableException;
import org.codehaus.groovy.syntax.Token;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import java.util.ArrayList;
import java.util.List;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.POP;
public class AssertionWriter {
// assert
private static final MethodCaller assertFailedMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "assertFailed");
private static class AssertionTracker {
int recorderIndex;
SourceText sourceText;
}
private final WriterController controller;
private AssertionTracker assertionTracker;
private AssertionTracker disabledTracker;
public AssertionWriter(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());
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;
}
}
statement.getBooleanExpression().visit(controller.getAcg());
Label exceptionThrower = operandStack.jump(IFEQ);
// do nothing, but clear the value recorder
if (rewriteAssert) {
//clean up assertion recorder
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);
if (rewriteAssert) {
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;
// now the optional exception expression
statement.getMessageExpression().visit(controller.getAcg());
operandStack.box();
assertFailedMethod.call(mv);
operandStack.remove(2); // assertFailed called static with 2 arguments
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);
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.visitLabel(afterAssert);
if (rewriteAssert) {
controller.getCompileStack().removeVar(savedTracker.recorderIndex);
}
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 void writeSourcelessAssertText(AssertStatement statement) {
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
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()) {
mv.visitLdcInsn(expressionText);
} else {
boolean first = true;
// let's create a new expression
mv.visitTypeInsn(NEW, "java/lang/StringBuffer");
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);
for (String name : list) {
String text = name + " = ";
if (first) {
first = false;
} else {
text = ", " + text;
}
mv.visitVarInsn(ALOAD, tempIndex);
mv.visitLdcInsn(text);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;", 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.visitInsn(POP);
operandStack.remove(1);
}
mv.visitVarInsn(ALOAD, tempIndex);
controller.getCompileStack().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) {
if (expression instanceof BooleanExpression) {
BooleanExpression boolExp = (BooleanExpression) expression;
addVariableNames(boolExp.getExpression(), list);
} else if (expression instanceof BinaryExpression) {
BinaryExpression binExp = (BinaryExpression) expression;
addVariableNames(binExp.getLeftExpression(), list);
addVariableNames(binExp.getRightExpression(), list);
} else if (expression instanceof VariableExpression) {
VariableExpression varExp = (VariableExpression) expression;
list.add(varExp.getName());
}
}
public void disableTracker() {
if (assertionTracker==null) return;
disabledTracker = assertionTracker;
assertionTracker = null;
}
public void reenableTracker() {
if (disabledTracker==null) return;
assertionTracker = disabledTracker;
disabledTracker = null;
}
}