blob: 7bccea2afa73c5e565e883ef1a635a085e68350c [file] [log] [blame]
/*
* Copyright (C) 2014 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.ast;
import com.android.jack.Jack;
import com.android.jack.JackAbortException;
import com.android.jack.Options;
import com.android.jack.config.id.JavaVersionPropertyId.JavaVersion;
import com.android.jack.ir.ast.JAsgOperation;
import com.android.jack.ir.ast.JAsgOperation.NonReusedAsg;
import com.android.jack.ir.ast.JBlock;
import com.android.jack.ir.ast.JCatchBlock;
import com.android.jack.ir.ast.JClass;
import com.android.jack.ir.ast.JClassOrInterface;
import com.android.jack.ir.ast.JEqOperation;
import com.android.jack.ir.ast.JExpressionStatement;
import com.android.jack.ir.ast.JIfStatement;
import com.android.jack.ir.ast.JInterface;
import com.android.jack.ir.ast.JLocal;
import com.android.jack.ir.ast.JLocalRef;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodBody;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JMethodIdWide;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JNeqOperation;
import com.android.jack.ir.ast.JNullLiteral;
import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
import com.android.jack.ir.ast.JSession;
import com.android.jack.ir.ast.JStatement;
import com.android.jack.ir.ast.JThrowStatement;
import com.android.jack.ir.ast.JTryStatement;
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.ir.sourceinfo.SourceInfo;
import com.android.jack.ir.sourceinfo.SourceInfoFactory;
import com.android.jack.lookup.JMethodLookupException;
import com.android.jack.reporting.Reporter.Severity;
import com.android.jack.scheduling.feature.SourceVersion7;
import com.android.jack.scheduling.filter.SourceTypeFilter;
import com.android.jack.transformations.LocalVarCreator;
import com.android.jack.transformations.TransformationException;
import com.android.jack.transformations.request.Replace;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.jack.util.NamingTools;
import com.android.sched.item.Description;
import com.android.sched.item.Name;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.ExclusiveAccess;
import com.android.sched.schedulable.Filter;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.Support;
import com.android.sched.schedulable.Transform;
import com.android.sched.schedulable.Use;
import com.android.sched.util.config.ThreadConfig;
import java.util.Collections;
import javax.annotation.Nonnull;
/**
* This {@link RunnableSchedulable} generates the code that will handle auto-closeable resources
* in try-with-resources statements.
*
* try (
* Res1 res1 = new Res1();
* ...
* ResN resN = new ResN();
* )
* {
* // statements
* }
* catch (...) {}
* finally {}
*
* =>
*
* try {
* exceptionToThrow = null;
* Res1 res1 = null;
* ...
* ResN resN = null;
*
* try {
* res1 = new Res1();
* ...
* resN = new ResN();
*
* // statements
*
* } catch (Throwable twrExceptionInTry) {
* exceptionToThrow = twrExceptionInTry;
* throw twrExceptionInTry;
* } finally {
* try {
* if (resN != null) {
* resN.close();
* }
* } catch (Throwable twrExceptionThrownByClose_) {
* if (exceptionToThrow == null) {
* exceptionToThrow = twrExceptionThrownByClose_;
* } else if (exceptionToThrow != twrExceptionThrownByClose_) {
* exceptionToThrow.addSupressed(twrExceptionThrownByClose_);
* }
* }
* ... for all resources till res1 ...
*
* if (exceptionToThrow != null) {
* throw exceptionToThrow
* }
* }
* }
* catch (...) {}
* finally {}
*
*/
@Description("Generates the code that will handle auto-closeable resources in try-with-resources" +
"statements.")
@Name("TryWithResourcesTransformer")
@Constraint(need = {JTryStatement.class,
JTryStatement.TryWithResourcesForm.class})
@Transform(add = {JTryStatement.class,
JCatchBlock.class,
JBlock.class,
JLocalRef.class,
NonReusedAsg.class,
JNullLiteral.class,
JExpressionStatement.class,
JEqOperation.class,
JNeqOperation.class,
JMethodCall.class,
JIfStatement.class,
JThrowStatement.class},
remove = JTryStatement.TryWithResourcesForm.class)
@Use({LocalVarCreator.class, SourceInfoFactory.class})
@Support(SourceVersion7.class)
@Filter(SourceTypeFilter.class)
// Uses getMethodIdWide on potential phantom: scans hierarchy and create method.
@ExclusiveAccess(JSession.class)
public class TryWithResourcesTransformer implements RunnableSchedulable<JMethod> {
@Nonnull
private final com.android.jack.util.filter.Filter<JMethod> filter =
ThreadConfig.get(Options.METHOD_FILTER);
@Nonnull
private final SourceInfoFactory sourceInfoFactory = Jack.getSession().getSourceInfoFactory();
private class Visitor extends JVisitor {
@Nonnull
private final JMethodBody currentMethodBody;
@Nonnull
private final LocalVarCreator localVarCreator;
@Nonnull
private final TransformationRequest request;
@Nonnull
private static final String AUTO_CLOSEABLE_SIGNATURE = "Ljava/lang/AutoCloseable;";
@Nonnull
private static final String CLOSE_METHOD_NAME = "close";
@Nonnull
private static final String THROWABLE_SIGNATURE = "Ljava/lang/Throwable;";
@Nonnull
private static final String ADD_SUPPRESSED_METHOD_NAME = "addSuppressed";
public Visitor(@Nonnull JMethod method, @Nonnull LocalVarCreator localVarCreator,
@Nonnull TransformationRequest request) {
JMethodBody body = (JMethodBody) method.getBody();
assert body != null;
this.currentMethodBody = body;
this.localVarCreator = localVarCreator;
this.request = request;
}
@Override
public void endVisit(@Nonnull JTryStatement x) {
if (x.getResourcesDeclarations().size() > 0) {
SourceInfo trySourceInfo = x.getSourceInfo();
SourceInfo endOfTrySourceInfos = sourceInfoFactory.create(
trySourceInfo.getEndLine(), trySourceInfo.getEndLine(),
trySourceInfo.getFileSourceInfo());
SourceInfo firstLineSourceInfos = sourceInfoFactory.create(
trySourceInfo.getStartLine(), trySourceInfo.getStartLine(),
trySourceInfo.getFileSourceInfo());
JBlock finalTryBlock = new JBlock(trySourceInfo);
// Declare exception to throw in the end, if any, and initialize it to null;
JClass throwableClass = Jack.getSession().getPhantomLookup().getClass(THROWABLE_SIGNATURE);
JLocal exceptionToThrow =
localVarCreator.createTempLocal(throwableClass, firstLineSourceInfos, request);
JAsgOperation assign = new JAsgOperation(firstLineSourceInfos,
exceptionToThrow.makeRef(firstLineSourceInfos), new JNullLiteral(firstLineSourceInfos));
finalTryBlock.addStmt(new JExpressionStatement(firstLineSourceInfos, assign));
// Init all resources to null
for (JStatement resInit : x.getResourcesDeclarations()) {
JAsgOperation asgOp = (JAsgOperation) ((JExpressionStatement) resInit).getExpr();
JLocal resourceLocal = ((JLocalRef) asgOp.getLhs()).getLocal();
assign = new JAsgOperation(firstLineSourceInfos,
resourceLocal.makeRef(firstLineSourceInfos), new JNullLiteral(firstLineSourceInfos));
finalTryBlock.addStmt(new JExpressionStatement(firstLineSourceInfos, assign));
}
// Inner try
JBlock tryBlock = x.getTryBlock();
JBlock finallyBlock = new JBlock(endOfTrySourceInfos);
// Copy resources initialisation statements
for (int i = x.getResourcesDeclarations().size() - 1; i >= 0; i--) {
tryBlock.addStmt(0, x.getResourcesDeclarations().get(i));
}
// Save exception in catch block if any
JLocal tryException = new JLocal(endOfTrySourceInfos,
NamingTools.getNonSourceConflictingName("twrExceptionInTry"), throwableClass,
JModifier.SYNTHETIC, currentMethodBody);
JCatchBlock catchBlock = new JCatchBlock(endOfTrySourceInfos,
Collections.singletonList(throwableClass), tryException);
JAsgOperation save =
new JAsgOperation(endOfTrySourceInfos, exceptionToThrow.makeRef(endOfTrySourceInfos),
tryException.makeRef(endOfTrySourceInfos));
catchBlock.addStmt(new JExpressionStatement(endOfTrySourceInfos, save));
catchBlock.addStmt(new JThrowStatement(endOfTrySourceInfos,
exceptionToThrow.makeRef(endOfTrySourceInfos)));
JTryStatement innerTry = new JTryStatement(endOfTrySourceInfos,
Collections.<JStatement>emptyList(),
tryBlock,
Collections.singletonList(catchBlock),
finallyBlock);
JMethodIdWide closeMethodId;
JMethodIdWide addSuppressedMethodId;
try {
// Lookup AutoCloseable.close() method
JInterface autoCloseableInterface =
Jack.getSession().getPhantomLookup().getInterface(AUTO_CLOSEABLE_SIGNATURE);
closeMethodId = autoCloseableInterface.getMethodIdWide(
CLOSE_METHOD_NAME, Collections.<JType>emptyList(), MethodKind.INSTANCE_VIRTUAL);
// Lookup Throwable.addSuppressed(Throwable t) method
addSuppressedMethodId = throwableClass.getMethodIdWide(ADD_SUPPRESSED_METHOD_NAME,
Collections.singletonList(throwableClass), MethodKind.INSTANCE_VIRTUAL);
} catch (JMethodLookupException e) {
TransformationException transformationException =
new TransformationException(new MissingJavaSupportException(JavaVersion.JAVA_7, e));
Jack.getSession().getReporter().report(Severity.FATAL, transformationException);
throw new JackAbortException(transformationException);
}
// Fill finally block
for (int i = x.getResourcesDeclarations().size() - 1; i >= 0; i--) {
// Try to close resources and handle exception suppression
JStatement resInit = x.getResourcesDeclarations().get(i);
JAsgOperation asgOp = (JAsgOperation) ((JExpressionStatement) resInit).getExpr();
JLocal resourceLocal = ((JLocalRef) asgOp.getLhs()).getLocal();
// If resource != null ...
JNeqOperation isNotNull = new JNeqOperation(endOfTrySourceInfos,
resourceLocal.makeRef(endOfTrySourceInfos), new JNullLiteral(endOfTrySourceInfos));
// ... close it
JMethodCall closeCall = new JMethodCall(endOfTrySourceInfos,
resourceLocal.makeRef(endOfTrySourceInfos),
(JClassOrInterface) resourceLocal.getType(),
closeMethodId,
JPrimitiveTypeEnum.VOID.getType(),
true);
JBlock thenBlock = new JBlock(endOfTrySourceInfos);
thenBlock.addStmt(new JExpressionStatement(endOfTrySourceInfos, closeCall));
JIfStatement ifStmt = new JIfStatement(endOfTrySourceInfos, isNotNull, thenBlock, null);
// Try to catch exception triggered by close()
JBlock tryBlockAroundClose = new JBlock(endOfTrySourceInfos);
JLocal exceptionThrownByClose = new JLocal(endOfTrySourceInfos,
NamingTools.getNonSourceConflictingName("twrExceptionThrownByClose_" + i),
throwableClass, JModifier.SYNTHETIC, currentMethodBody);
catchBlock = new JCatchBlock(endOfTrySourceInfos,
Collections.singletonList(throwableClass), exceptionThrownByClose);
tryBlockAroundClose.addStmt(ifStmt);
JTryStatement tryClose = new JTryStatement(endOfTrySourceInfos,
Collections.<JStatement>emptyList(),
tryBlockAroundClose,
Collections.<JCatchBlock>singletonList(catchBlock),
null);
finallyBlock.addStmt(tryClose);
// If exceptionToThrow == null ...
JEqOperation isNull = new JEqOperation(endOfTrySourceInfos,
exceptionToThrow.makeRef(endOfTrySourceInfos), new JNullLiteral(endOfTrySourceInfos));
// ... then make it the exception thrown by close() ...
thenBlock = new JBlock(endOfTrySourceInfos);
asgOp =
new JAsgOperation(endOfTrySourceInfos, exceptionToThrow.makeRef(endOfTrySourceInfos),
exceptionThrownByClose.makeRef(endOfTrySourceInfos));
thenBlock.addStmt(new JExpressionStatement(endOfTrySourceInfos, asgOp));
// ... else add exception thrown by close() to the list of suppressed exceptions
JBlock callSuppressBlock = new JBlock(endOfTrySourceInfos);
JNeqOperation ifExceptionsDiffer =
new JNeqOperation(endOfTrySourceInfos, exceptionToThrow.makeRef(endOfTrySourceInfos),
exceptionThrownByClose.makeRef(endOfTrySourceInfos));
JIfStatement elseIf =
new JIfStatement(endOfTrySourceInfos, ifExceptionsDiffer, callSuppressBlock, null);
JMethodCall addSuppressCall = new JMethodCall(endOfTrySourceInfos,
exceptionToThrow.makeRef(endOfTrySourceInfos),
throwableClass,
addSuppressedMethodId,
JPrimitiveTypeEnum.VOID.getType(),
closeMethodId.canBeVirtual());
addSuppressCall.addArg(exceptionThrownByClose.makeRef(endOfTrySourceInfos));
callSuppressBlock.addStmt(new JExpressionStatement(endOfTrySourceInfos, addSuppressCall));
ifStmt = new JIfStatement(endOfTrySourceInfos, isNull, thenBlock, elseIf);
catchBlock.addStmt(ifStmt);
}
// Throw an exception if any
JThrowStatement throwStmt =
new JThrowStatement(endOfTrySourceInfos, exceptionToThrow.makeRef(endOfTrySourceInfos));
JNeqOperation ifNotNull = new JNeqOperation(endOfTrySourceInfos,
exceptionToThrow.makeRef(endOfTrySourceInfos), new JNullLiteral(endOfTrySourceInfos));
JIfStatement ifExceptionToThrow =
new JIfStatement(endOfTrySourceInfos, ifNotNull, throwStmt, null);
finallyBlock.addStmt(ifExceptionToThrow);
finalTryBlock.addStmt(innerTry);
// Replace try block by the new one and keep existing catch and finally blocks
request.append(new Replace(x.getTryBlock(), finalTryBlock));
}
x.setResourcesDeclarations(Collections.<JStatement>emptyList());
}
}
@Override
public void run(@Nonnull JMethod method) {
if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) {
return;
}
TransformationRequest request = new TransformationRequest(method);
new Visitor(method, new LocalVarCreator(method, "$twr"), request).accept(method);
request.commit();
}
}