/**
*
*/
package soottocfg.soot.transformers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import soot.Body;
import soot.BooleanType;
import soot.Local;
import soot.Unit;
import soot.Value;
import soot.jimple.AssignStmt;
import soot.jimple.ClassConstant;
import soot.jimple.GotoStmt;
import soot.jimple.IfStmt;
import soot.jimple.IntConstant;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.JimpleBody;
import soot.jimple.NewExpr;
import soot.jimple.StaticFieldRef;
import soot.jimple.Stmt;
import soot.jimple.ThrowStmt;
import soottocfg.soot.util.SootTranslationHelpers;
/**
* @author schaef
*
*/
public class AssertionReconstruction extends AbstractSceneTransformer {
/*
* Code to handle Java Assertions.
*/
private static final String javaAssertionType = "java.lang.AssertionError";
private static final String javaAssertionFlag = "$assertionsDisabled";
public void applyTransformation() {
for (JimpleBody body : this.getSceneBodies()) {
transform(body);
}
}
private void transform(Body body) {
removeAssertionRelatedNonsense(body); //TODO: is it sufficient to do this
//for constructors?
reconstructJavaAssertions(body);
}
/**
* removes useless statements that are generated when java assertions are
* translated into bytecode.
*
* @param body
*/
public void removeAssertionRelatedNonsense(Body body) {
/*
* Look for the following sequence and remove it. $r0 = class
* "translation_tests/TranslationTest01"; $z0 = virtualinvoke
* $r0.<java.lang.Class: boolean desiredAssertionStatus()>(); if $z0 !=
* 0 goto label1; $z1 = 1; (0) goto label2; label1: (1) $z1_1 = 0;
* label2: <translation_tests.TranslationTest01: boolean
* $assertionsDisabled> = $z1_2;
*/
Set<Unit> unitsToRemove = new HashSet<Unit>();
Iterator<Unit> iterator = body.getUnits().iterator();
while (iterator.hasNext()) {
Unit u = iterator.next();
if (u instanceof AssignStmt) {
AssignStmt asgn = (AssignStmt) u;
if (asgn.getLeftOp() instanceof Local && asgn.getRightOp() instanceof ClassConstant) {
ClassConstant rhs = (ClassConstant) asgn.getRightOp();
final String c = body.getMethod().getDeclaringClass().getName().replace('.', '/');
// search for $r0 = class
// "translation_tests/TranslationTest01";
if (rhs.getValue().equals(c)) {
unitsToRemove.add(u);
final int lookAhead = 6;
Stmt[] uslessBlock = new Stmt[lookAhead];
for (int i = 0; i < lookAhead; i++) {
if (!iterator.hasNext()) {
unitsToRemove.clear();
break;
}
uslessBlock[i] = (Stmt) iterator.next();
unitsToRemove.add(uslessBlock[i]);
}
// $z0 = virtualinvoke $r0.<java.lang.Class: boolean
// desiredAssertionStatus()>();
if (!uslessBlock[0].containsInvokeExpr() || !uslessBlock[0].getInvokeExpr().getMethod()
.getName().equals("desiredAssertionStatus")) {
unitsToRemove.clear();
continue;
}
// if $z0 != 0 goto label1;
if (!(uslessBlock[1] instanceof IfStmt)
|| ((IfStmt) uslessBlock[1]).getTarget() != uslessBlock[4]) {
unitsToRemove.clear();
continue;
}
// $z1 = 1;
if (!(uslessBlock[2] instanceof AssignStmt)
|| !((AssignStmt) uslessBlock[2]).getRightOp().toString().equals("1")) {
unitsToRemove.clear();
continue;
}
// goto label2;
if (!(uslessBlock[3] instanceof GotoStmt)
|| ((GotoStmt) uslessBlock[3]).getTarget() != uslessBlock[5]) {
unitsToRemove.clear();
continue;
}
// $z1_1 = 0;
if (!(uslessBlock[4] instanceof AssignStmt)
|| !((AssignStmt) uslessBlock[4]).getRightOp().toString().equals("0")) {
unitsToRemove.clear();
continue;
}
// <translation_tests.TranslationTest01: boolean
// $assertionsDisabled> = $z1_2;
if (!(uslessBlock[5] instanceof AssignStmt) || !((AssignStmt) uslessBlock[5]).getLeftOp()
.toString().contains("$assertionsDisabled")) {
unitsToRemove.clear();
continue;
}
// we found the block.
break;
}
}
}
}
if (!unitsToRemove.isEmpty()) {
// System.out.println("removed useless block in
// "+body.getMethod().getBytecodeSignature());
body.getUnits().removeAll(unitsToRemove);
body.validate();
}
}
/** @formatter:off
* Look for parts of the body that have been created from Java assert
* statements. These are always of the form:
*
* $z0 = <Assert: boolean $assertionsDisabled>;
* if $z0 != 0 goto label1;
* [[some statements]]
* if [[some condition]] goto label1;
* $r1 = new java.lang.AssertionError;
* specialinvoke $r1.<java.lang.AssertionError:
* void <init>()>();
* throw $r1;
*
* and replace those blocks by:
* [[some statements]]
* $assert_condition = [[some condition]];
* staticinvoke <JayHornAssertions: void
* super_crazy_assertion(boolean[])>($assert_condition);
*
* @param body
*/
public void reconstructJavaAssertions(Body body) {
Set<Unit> unitsToRemove = new HashSet<Unit>();
Map<Unit, Value> assertionsToInsert = new HashMap<Unit, Value>();
Map<Unit, Unit> postAssertionGotos = new HashMap<Unit, Unit>();
//This is a hack to ensure that the generated assertions
//have a nice line number. The actual assertion in the byte code
//is always off by one.
Map<Unit, Unit> assertionToLineNumberUnit = new HashMap<Unit, Unit>();
// body.getUnits().insertAfter(toInsert, point);
Iterator<Unit> iterator = body.getUnits().iterator();
while (iterator.hasNext()) {
Unit u = iterator.next();
if (isSootAssertionFlag(u)) {
// u := $z0 = <Assert: boolean $assertionsDisabled>;
//This has also the line number tag that we want for the assert
//statement! Anything after that is off by one.
unitsToRemove.add(u);
Unit unitWithInterestingLineNumber = u;
// u := if $z0 != 0 goto label1;
u = iterator.next();
if (!(u instanceof IfStmt)) {
throw new RuntimeException("");
}
//remember where to go if the assertion holds.
// Unit postAssertionStatement = ((IfStmt)u).getTarget();
unitsToRemove.add(u);
// now search for the new java.lang.AssertionError
// Unit previousUnit = null;
// Unit lastAssertion = null;
while (iterator.hasNext()) {
u = iterator.next();
// if (u instanceof IfStmt) {
// unitsToRemove.add(u);
// IfStmt ite = (IfStmt) u;
// if (ite.getTarget().equals(postAssertionStatement)) {
// assertionsToInsert.put(u, ite.getCondition());
// assertionToLineNumberUnit.put(u, unitWithInterestingLineNumber);
// } else {
// //this is a negated assertion.
// assertionsToInsert.put(u, ConditionFlipper.flip((ConditionExpr) ite.getCondition())) ;
// assertionToLineNumberUnit.put(u, unitWithInterestingLineNumber);
//
// }
// lastAssertion = u;
// }
if (isNewJavaAssertionError(u)) {
//make an assert false
// u := $r1 = new java.lang.AssertionError;
unitsToRemove.add(u);
assertionsToInsert.put(u, IntConstant.v(0));
assertionToLineNumberUnit.put(u, unitWithInterestingLineNumber);
// lastAssertion = u;
// if (lastAssertion!=null) {
// postAssertionGotos.put(lastAssertion, postAssertionStatement);
// }
break;
}
// previousUnit = u;
}
// u := specialinvoke $r1.<java.lang.AssertionError: void
// <init>()>();
//but there might be some initialization code in between that
//we have to skip.
if (!iterator.hasNext()) {
/* Only observed that for cases of trivial asserts.
* See issue #84:
* assert(cond || true);
*/
break;
}
u = iterator.next();
while (!(u instanceof InvokeStmt) ||
!((InvokeStmt)u).getInvokeExpr().getMethod().getSignature().contains("java.lang.AssertionError: void <init>")) {
if (!iterator.hasNext()) {
throw new RuntimeException("Assertion reconstruction failed");
}
u = iterator.next();
}
unitsToRemove.add(u);
// u := throw $r1;
u = iterator.next();
if (!(u instanceof ThrowStmt)) {
throw new RuntimeException(u.toString());
}
unitsToRemove.add(u);
continue;
}
}
Local assertionLocal = null;
if (!assertionsToInsert.isEmpty()) {
assertionLocal = Jimple.v().newLocal("$assert_"+(body.getLocals().size()), BooleanType.v());
body.getLocals().add(assertionLocal);
}
// remove all boilerplate statements
// generated from the assertions.
for (Entry<Unit, Value> entry : assertionsToInsert.entrySet()) {
List<Unit> unitsToInsert = new LinkedList<Unit>();
unitsToInsert.add(this.assignStmtFor(assertionLocal, entry.getValue(), assertionToLineNumberUnit.get(entry.getKey())));
unitsToInsert.add(SootTranslationHelpers.v().makeAssertion(assertionLocal, assertionToLineNumberUnit.get(entry.getKey())));
if (postAssertionGotos.containsKey(entry.getKey())) {
//go to the proper successor if the assertion passes.
unitsToInsert.add(this.gotoStmtFor(postAssertionGotos.get(entry.getKey()), entry.getKey()));
}
body.getUnits().insertBefore(unitsToInsert, entry.getKey());
unitsToRemove.add(entry.getKey());
}
body.getUnits().removeAll(unitsToRemove);
body.validate();
}
/**
* Checks if u has been created from a Java assert statement and is of the
* form: $r1 = new java.lang.AssertionError;
*
* @param u
* @return
*/
private boolean isNewJavaAssertionError(Unit u) {
if (u instanceof AssignStmt) {
AssignStmt ids = (AssignStmt) u;
if (ids.getRightOp() instanceof NewExpr) {
NewExpr ne = (NewExpr) ids.getRightOp();
return ne.getType().toString().equals(javaAssertionType);
}
}
return false;
}
/**
* Checks if u has been created from a Java assert statement and is of the
* form: $z0 = <Assert: boolean $assertionsDisabled>;
*
* @param u
* @return
*/
private boolean isSootAssertionFlag(Unit u) {
if (u instanceof AssignStmt) {
AssignStmt ids = (AssignStmt) u;
if (ids.getRightOp() instanceof StaticFieldRef) {
StaticFieldRef sfr = (StaticFieldRef) ids.getRightOp();
return sfr.getField().getName().equals(javaAssertionFlag);
}
}
return false;
}
}