blob: c80233abc1ff54154f071a1164ef9875c7d3fc68 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed 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 com.android.jack.transformations;
import com.android.jack.Jack;
import com.android.jack.Options;
import com.android.jack.ir.SourceInfo;
import com.android.jack.ir.SourceOrigin;
import com.android.jack.ir.ast.FieldKind;
import com.android.jack.ir.ast.JAsgOperation;
import com.android.jack.ir.ast.JAssertStatement;
import com.android.jack.ir.ast.JBlock;
import com.android.jack.ir.ast.JClass;
import com.android.jack.ir.ast.JClassLiteral;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JExpression;
import com.android.jack.ir.ast.JExpressionStatement;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JFieldId;
import com.android.jack.ir.ast.JFieldRef;
import com.android.jack.ir.ast.JIfStatement;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JNewInstance;
import com.android.jack.ir.ast.JPrefixNotOperation;
import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
import com.android.jack.ir.ast.JThrowStatement;
import com.android.jack.ir.ast.JType;
import com.android.jack.ir.ast.JVisitor;
import com.android.jack.ir.ast.MethodKind;
import com.android.jack.lookup.CommonTypes;
import com.android.jack.lookup.JLookupException;
import com.android.jack.shrob.obfuscation.OriginalNames;
import com.android.jack.transformations.ast.BooleanTestOutsideIf;
import com.android.jack.transformations.ast.NewInstanceRemoved;
import com.android.jack.transformations.request.AppendField;
import com.android.jack.transformations.request.Replace;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
import com.android.jack.util.NamingTools;
import com.android.jack.util.filter.Filter;
import com.android.sched.item.Description;
import com.android.sched.item.Name;
import com.android.sched.item.Synchronized;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.Transform;
import com.android.sched.util.config.ThreadConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
/**
* This {@link RunnableSchedulable} transforms "assert" into a "throw" if assertions are enabled.
*/
@Description("Transforms assert into a throw if assertions are enabled")
@Name("AssertionTransformer")
@Synchronized
@Constraint(need = {JAssertStatement.class, OriginalNames.class})
@Transform(add = {AssertionTransformerSchedulingSeparator.SeparatorTag.class,
BooleanTestOutsideIf.class,
JIfStatement.class,
JThrowStatement.class,
JPrefixNotOperation.class,
JMethodCall.class,
JBlock.class,
JFieldRef.class,
JField.class,
JClassLiteral.class,
JNewInstance.class,
JAsgOperation.NonReusedAsg.class,
InitializationExpression.class,
JExpressionStatement.class},
remove = {JAssertStatement.class, ThreeAddressCodeForm.class, NewInstanceRemoved.class})
public class AssertionTransformer implements RunnableSchedulable<JMethod> {
@Nonnull
private final Filter<JMethod> filter = ThreadConfig.get(Options.METHOD_FILTER);
private static class Visitor extends JVisitor {
@Nonnull
private final JDefinedClassOrInterface currentType;
@Nonnull
private static final String ASSERTION_FIELD_NAME =
NamingTools.getNonSourceConflictingName("assertionsDisabled");
public Visitor(@Nonnull JDefinedClassOrInterface type) {
this.currentType = type;
}
@Nonnull
private JFieldId getOrCreateAssertionstatusField(@Nonnull TransformationRequest request) {
try {
JFieldId id = currentType.getFieldId(ASSERTION_FIELD_NAME,
JPrimitiveTypeEnum.BOOLEAN.getType(), FieldKind.STATIC);
JField field = id.getField();
assert field != null;
if (field.getEnclosingType() == currentType) {
// return only direct field
return id;
}
} catch (JLookupException e) {
// fallback to create the field
}
return addAssertionStatusToType(currentType, request);
}
@Nonnull
private JFieldId addAssertionStatusToType(@Nonnull JDefinedClassOrInterface type,
@Nonnull TransformationRequest request) {
SourceInfo sourceInfo = SourceOrigin.UNKNOWN;
// Create field $assertionsDisabled
int modifier = JModifier.FINAL | JModifier.STATIC | JModifier.SYNTHETIC;
JField assertionStatus = new JField(SourceOrigin.UNKNOWN, ASSERTION_FIELD_NAME,
currentType, JPrimitiveTypeEnum.BOOLEAN.getType(), modifier);
JFieldId assertionStatusId = assertionStatus.getId();
request.append(new AppendField(currentType, assertionStatus));
// A.$assertionsDisabled = !A.class.desiredAssertionStatus();
JClass javaLangClass =
Jack.getSession().getPhantomLookup().getClass(CommonTypes.JAVA_LANG_CLASS);
JClassLiteral thisClass = new JClassLiteral(sourceInfo, type, javaLangClass);
JFieldRef lhs = new JFieldRef(sourceInfo, null, assertionStatusId, type);
JExpression rhs = new JPrefixNotOperation(sourceInfo,
new JMethodCall(sourceInfo, thisClass, javaLangClass,
javaLangClass.getOrCreateMethodId("desiredAssertionStatus",
Collections.<JType>emptyList(), MethodKind.INSTANCE_VIRTUAL),
JPrimitiveTypeEnum.BOOLEAN.getType(), true /* isVirtualDispatch */));
JAsgOperation asg = new JAsgOperation(SourceOrigin.UNKNOWN, lhs, rhs);
assertionStatus.addMarker(new InitializationExpression(asg.makeStatement()));
return assertionStatusId;
}
@Override
public void endVisit(@Nonnull JAssertStatement assertSt) {
// assert test : message
// =>
// if (!$assertionsDisabled)
// if (!test)
// throw new AssertionError(message);
TransformationRequest request = new TransformationRequest(assertSt);
JFieldId assertionStatus = getOrCreateAssertionstatusField(request);
JExpression assertionEnabledCondition =
new JPrefixNotOperation(assertSt.getSourceInfo(), new JFieldRef(assertSt.getSourceInfo(),
null, assertionStatus, currentType));
JExpression testExpression = assertSt.getTestExpr();
JExpression notTestCondition =
new JPrefixNotOperation(testExpression.getSourceInfo(), testExpression);
List<JType> ctorDescriptor = new ArrayList<JType>();
if (assertSt.getArg() != null) {
ctorDescriptor.add(
Jack.getSession().getPhantomLookup().getClass(CommonTypes.JAVA_LANG_OBJECT));
}
JClass assertionError =
Jack.getSession().getPhantomLookup()
.getClass(CommonTypes.JAVA_LANG_ASSERTION_ERROR);
JNewInstance newAssertionError = new JNewInstance(assertSt.getSourceInfo(),
assertionError,
assertionError.getOrCreateMethodId(NamingTools.INIT_NAME, ctorDescriptor,
MethodKind.INSTANCE_NON_VIRTUAL));
if (assertSt.getArg() != null) {
newAssertionError.addArg(assertSt.getArg());
}
JThrowStatement throwAssertionError =
new JThrowStatement(assertSt.getSourceInfo(), newAssertionError);
JBlock blockThrow = new JBlock(assertSt.getSourceInfo());
blockThrow.addStmt(throwAssertionError);
JIfStatement ifNotTest = new JIfStatement(assertSt.getSourceInfo(),
notTestCondition, blockThrow, null);
JBlock thenAssertionEnabled = new JBlock(assertSt.getSourceInfo());
thenAssertionEnabled.addStmt(ifNotTest);
JIfStatement ifAssertionEnabled = new JIfStatement(assertSt.getSourceInfo(),
assertionEnabledCondition, thenAssertionEnabled, null);
request.append(new Replace(assertSt, ifAssertionEnabled));
request.commit();
}
}
@Override
public synchronized void run(@Nonnull JMethod method) throws Exception {
if (method.getEnclosingType().isExternal() || method.isNative() || method.isAbstract()
|| !filter.accept(this.getClass(), method)) {
return;
}
Visitor visitor = new Visitor(method.getEnclosingType());
visitor.accept(method);
}
}