blob: 07cb4a6588a51530eb8add7eae2ea5bb23d8617d [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.util;
import com.android.jack.ir.ast.JAssertStatement;
import com.android.jack.ir.ast.JBlock;
import com.android.jack.ir.ast.JBreakStatement;
import com.android.jack.ir.ast.JCaseStatement;
import com.android.jack.ir.ast.JCatchBlock;
import com.android.jack.ir.ast.JContinueStatement;
import com.android.jack.ir.ast.JDoStatement;
import com.android.jack.ir.ast.JExpression;
import com.android.jack.ir.ast.JExpressionStatement;
import com.android.jack.ir.ast.JFieldInitializer;
import com.android.jack.ir.ast.JForStatement;
import com.android.jack.ir.ast.JGoto;
import com.android.jack.ir.ast.JIfStatement;
import com.android.jack.ir.ast.JLabel;
import com.android.jack.ir.ast.JLabeledStatement;
import com.android.jack.ir.ast.JLiteral;
import com.android.jack.ir.ast.JLocal;
import com.android.jack.ir.ast.JLocalRef;
import com.android.jack.ir.ast.JLock;
import com.android.jack.ir.ast.JLoop;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodBody;
import com.android.jack.ir.ast.JParameter;
import com.android.jack.ir.ast.JParameterRef;
import com.android.jack.ir.ast.JReturnStatement;
import com.android.jack.ir.ast.JStatement;
import com.android.jack.ir.ast.JSwitchStatement;
import com.android.jack.ir.ast.JThis;
import com.android.jack.ir.ast.JThisRef;
import com.android.jack.ir.ast.JThrowStatement;
import com.android.jack.ir.ast.JTryStatement;
import com.android.jack.ir.ast.JUnlock;
import com.android.jack.ir.ast.JWhileStatement;
import com.android.jack.transformations.finallyblock.InlinedFinallyMarker;
import com.android.jack.transformations.request.AddJLocalInMethodBody;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.sched.marker.Marker;
import com.android.sched.schedulable.Constraint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* A general purpose statement cloner.
* <p>
* When cloning a block, the target of a {@code JGoto} or a {@code JLocalRef} is set to the cloned
* version of the target if the target belongs to the group of statements.
*<p>
* Warning: The cloning of {@link JParameterRef} whose enclosing method is different than the target
* method is not supported.
*/
@Constraint(no = {JLoop.class, JBreakStatement.class, JContinueStatement.class,
JFieldInitializer.class})
public class CloneStatementVisitor extends CloneExpressionVisitor {
@CheckForNull
protected JStatement statement;
@Nonnull
private Map<JLabeledStatement, JLabeledStatement> clonedLabeledStmts = Collections.emptyMap();
@Nonnull
private Map<JLocal, JLocal> clonedLocals = Collections.emptyMap();
@Nonnull
private Map<JCatchBlock, JCatchBlock> clonedCatchBlocks = Collections.emptyMap();
@Nonnull
private List<JGoto> clonedGotos = Collections.emptyList();
@Nonnull
private final TransformationRequest trRequest;
@Nonnull
private Map<JStatement, JStatement> clonedStmts = Collections.emptyMap();
@Nonnull
private List<Marker> clonedMarkers = Collections.emptyList();
@Nonnull
private final JMethod targetMethod;
/**
* Build a {@code CloneStatementVisitor}.
* @param trRequest a request for the modifications.
* @param targetMethod the method whose body will contain the cloned statement.
*/
public CloneStatementVisitor(@Nonnull TransformationRequest trRequest,
@Nonnull JMethod targetMethod) {
this.trRequest = trRequest;
this.targetMethod = targetMethod;
}
@Nonnull
public JMethod getTargetMethod() {
return targetMethod;
}
public List<Marker> getClonedMarkers() {
return clonedMarkers;
}
@Nonnull
public <T extends JStatement> T cloneStatement(@Nonnull T stmt) {
clonedLabeledStmts = new HashMap<JLabeledStatement, JLabeledStatement>();
clonedLocals = new HashMap<JLocal, JLocal>();
clonedGotos = new ArrayList<JGoto>();
clonedStmts = new HashMap<JStatement, JStatement>();
clonedMarkers = new ArrayList<Marker>();
clonedCatchBlocks = new HashMap<JCatchBlock, JCatchBlock>();
T statement = internalCloneStatement(stmt);
fixGotos();
fixMarkers();
return statement;
}
protected void fixMarkers() {
// TODO(mikaelpeltier) Think how to modify marker to reflect cloning
for (Marker m : clonedMarkers) {
if (m instanceof InlinedFinallyMarker) {
InlinedFinallyMarker newMarker = (InlinedFinallyMarker) m;
JStatement newStmt = clonedStmts.get(newMarker.getTryStmt());
if (newStmt != null) {
newMarker.setTryStmt((JTryStatement) newStmt);
}
}
}
}
private void fixGotos() {
for (JGoto clonedGoto : clonedGotos) {
JLabeledStatement target = clonedGoto.getTargetBlock();
JLabeledStatement clonedTarget = clonedLabeledStmts.get(target);
if (clonedTarget != null) {
clonedGoto.replace(target, clonedTarget);
}
}
}
@SuppressWarnings("unchecked")
@Nonnull
protected <T extends JStatement> T internalCloneStatement(@Nonnull T stmt) {
JStatement alreadyCloned = clonedStmts.get(stmt);
if (alreadyCloned != null) {
return (T) alreadyCloned;
}
// double check that the expression is successfully cloned
statement = null;
this.accept(stmt);
JStatement clonedStatement = statement;
if (clonedStatement == null) {
throw new AssertionError("Unable to clone statement " + stmt.toString());
}
for (Marker m : stmt.getAllMarkers()) {
Marker newMarker = m.cloneIfNeeded();
clonedMarkers.add(newMarker);
clonedStatement.addMarker(newMarker);
}
clonedStmts.put(stmt, statement);
return (T) clonedStatement;
}
@Override
public boolean visit(@Nonnull JAssertStatement assertStatement) {
JExpression arg = assertStatement.getArg();
JExpression clonedArg;
if (arg != null) {
clonedArg = cloneExpression(arg);
} else {
clonedArg = null;
}
JExpression clonedTestExpr = cloneExpression(assertStatement.getTestExpr());
statement =
updateCatchBlockList(new JAssertStatement(assertStatement.getSourceInfo(), clonedTestExpr,
clonedArg), assertStatement);
return false;
}
@Override
public boolean visit(@Nonnull JCatchBlock catchBlock) {
statement = updateCatchBlockList(cloneCatchBlock(catchBlock), catchBlock);
return false;
}
@Nonnull
private JCatchBlock cloneCatchBlock(@Nonnull JCatchBlock catchBlock) {
JCatchBlock newBlock = clonedCatchBlocks.get(catchBlock);
if (newBlock == null) {
JLocal clonedLocal = clonedLocals.get(catchBlock.getCatchVar());
if (clonedLocal == null) {
clonedLocal = cloneLocal(catchBlock.getCatchVar());
}
newBlock =
new JCatchBlock(catchBlock.getSourceInfo(), catchBlock.getCatchTypes(),
clonedLocal);
clonedCatchBlocks.put(catchBlock, newBlock);
for (JStatement stmt : catchBlock.getStatements()) {
newBlock.addStmt(internalCloneStatement(stmt));
}
}
return newBlock;
}
@Override
public boolean visit(@Nonnull JBlock block) {
JBlock newBlock = new JBlock(block.getSourceInfo());
for (JStatement stmt : block.getStatements()) {
newBlock.addStmt(internalCloneStatement(stmt));
}
statement = updateCatchBlockList(newBlock, block);
return false;
}
@Override
public boolean visit(@Nonnull JBreakStatement breakStatement) {
assert false : "Not supported";
return false;
}
@Override
public boolean visit(@Nonnull JCaseStatement caseStatement) {
JLiteral caseExpr = caseStatement.getExpr();
JLiteral clonedCaseExpr = caseExpr != null ? cloneExpression(caseExpr) : null;
statement =
updateCatchBlockList(new JCaseStatement(caseStatement.getSourceInfo(), clonedCaseExpr),
caseStatement);
return false;
}
@Override
public boolean visit(@Nonnull JContinueStatement continueStatement) {
assert false : "Not supported";
return false;
}
@Override
public boolean visit(@Nonnull JFieldInitializer init) {
throw new AssertionError();
}
@Nonnull
private JLocal cloneLocal(@Nonnull JLocal var) {
JMethodBody methodBody = (JMethodBody) targetMethod.getBody();
assert methodBody != null;
if (methodBody.getLocals().contains(var)) {
return var;
}
String varName = var.getName();
JLocal clonedVar =
new JLocal(
var.getSourceInfo(),
varName == null ? null : cloneLocalName(varName),
var.getType(),
var.getModifier(),
methodBody);
// If parent is JCatchBLock, cloned variable will be add into rather than into method body
if (!(var.getParent() instanceof JCatchBlock)) {
trRequest.append(new AddJLocalInMethodBody(clonedVar, methodBody));
}
clonedLocals.put(var, clonedVar);
return clonedVar;
}
@Nonnull
protected String cloneLocalName(@Nonnull String orgName) {
return orgName;
}
@Override
public boolean visit(@Nonnull JDoStatement doStatement) {
assert false : "Not supported";
return false;
}
@Override
public boolean visit(@Nonnull JExpressionStatement expressionStatement) {
statement =
updateCatchBlockList(cloneExpression(expressionStatement.getExpr()).makeStatement(),
expressionStatement);
return false;
}
/**
* @return The cloned statement with the catch block list updated
*/
@Nonnull
protected JStatement updateCatchBlockList(@Nonnull JStatement clonedStmt,
@Nonnull JStatement orignalStmt) {
for (JCatchBlock catchBlocks : orignalStmt.getJCatchBlocks()) {
clonedStmt.appendCatchBlock(cloneCatchBlock(catchBlocks));
}
return clonedStmt;
}
@Override
public boolean visit(@Nonnull JForStatement forStatement) {
assert false : "Not supported";
return false;
}
@Override
public boolean visit(@Nonnull JGoto gotoStatement) {
JGoto newGoto = new JGoto(gotoStatement.getSourceInfo(), gotoStatement.getTargetBlock());
clonedGotos.add(newGoto);
statement = updateCatchBlockList(newGoto, gotoStatement);
return false;
}
@Override
public boolean visit(@Nonnull JIfStatement ifStatement) {
JExpression clonedCond = cloneExpression(ifStatement.getIfExpr());
JStatement clonedThen = internalCloneStatement(ifStatement.getThenStmt());
JStatement elseStmt = ifStatement.getElseStmt();
JStatement clonedElse = null;
if (elseStmt != null) {
clonedElse = internalCloneStatement(elseStmt);
}
statement =
updateCatchBlockList(new JIfStatement(ifStatement.getSourceInfo(), clonedCond, clonedThen,
clonedElse), ifStatement);
return false;
}
@Override
public boolean visit(@Nonnull JLabeledStatement labeledStatement) {
JStatement clonedBody = internalCloneStatement(labeledStatement.getBody());
JLabel label = labeledStatement.getLabel();
JLabel newLabel = new JLabel(label.getSourceInfo(), label.getName());
JLabeledStatement newLabeledStatement = new JLabeledStatement(labeledStatement.getSourceInfo(),
newLabel, clonedBody);
clonedLabeledStmts.put(labeledStatement, newLabeledStatement);
statement = updateCatchBlockList(newLabeledStatement, labeledStatement);
return false;
}
@Override
public boolean visit(@Nonnull JLock lockStatement) {
JExpression clonedExpr = cloneExpression(lockStatement.getLockExpr());
statement =
updateCatchBlockList(new JLock(lockStatement.getSourceInfo(), clonedExpr), lockStatement);
return false;
}
@Override
public boolean visit(@Nonnull JUnlock unlockStatement) {
JExpression clonedExpr = cloneExpression(unlockStatement.getLockExpr());
statement =
updateCatchBlockList(new JUnlock(unlockStatement.getSourceInfo(), clonedExpr),
unlockStatement);
return false;
}
@Override
public boolean visit(@Nonnull JReturnStatement returnStatement) {
JExpression clonedExpr = null;
JExpression expr = returnStatement.getExpr();
if (expr != null) {
clonedExpr = cloneExpression(expr);
}
statement =
updateCatchBlockList(new JReturnStatement(returnStatement.getSourceInfo(), clonedExpr),
returnStatement);
return false;
}
@Override
public boolean visit(@Nonnull JSwitchStatement switchStatement) {
JExpression clonedExpr = cloneExpression(switchStatement.getExpr());
JBlock clonedBody = internalCloneStatement(switchStatement.getBody());
List<JCaseStatement> cases = switchStatement.getCases();
List<JCaseStatement> clonedCases = new ArrayList<JCaseStatement>();
for (JCaseStatement currentCase : cases) {
clonedCases.add(internalCloneStatement(currentCase));
}
JCaseStatement clonedDefaultCase = null;
JCaseStatement defaultCase = switchStatement.getDefaultCase();
if (defaultCase != null) {
clonedDefaultCase = internalCloneStatement(defaultCase);
}
statement =
updateCatchBlockList(new JSwitchStatement(switchStatement.getSourceInfo(), clonedExpr,
clonedBody, clonedCases, clonedDefaultCase), switchStatement);
return false;
}
@Override
public boolean visit(@Nonnull JThrowStatement throwStatement) {
JExpression clonedExpr = cloneExpression(throwStatement.getExpr());
statement =
updateCatchBlockList(new JThrowStatement(throwStatement.getSourceInfo(), clonedExpr),
throwStatement);
return false;
}
@Override
public boolean visit(@Nonnull JTryStatement tryStatement) {
JBlock clonedTryBlock = internalCloneStatement(tryStatement.getTryBlock());
assert clonedTryBlock != null;
List<JCatchBlock> catchBlocks = tryStatement.getCatchBlocks();
List<JCatchBlock> clonedCatchBlocks = new ArrayList<JCatchBlock>(catchBlocks.size());
for (JCatchBlock catchBlock : catchBlocks) {
clonedCatchBlocks.add(internalCloneStatement(catchBlock));
}
JBlock clonedFinallyBlock = null;
JBlock finallyBlock = tryStatement.getFinallyBlock();
if (finallyBlock != null) {
clonedFinallyBlock = internalCloneStatement(finallyBlock);
}
List<JStatement> resourcesDeclarations = tryStatement.getResourcesDeclarations();
List<JStatement> clonedResourcesDeclarations =
new ArrayList<JStatement>(resourcesDeclarations.size());
for (JStatement stmt : resourcesDeclarations) {
clonedResourcesDeclarations.add(internalCloneStatement(stmt));
}
statement = updateCatchBlockList(new JTryStatement(tryStatement.getSourceInfo(),
clonedResourcesDeclarations,
clonedTryBlock,
clonedCatchBlocks,
clonedFinallyBlock), tryStatement);
return false;
}
@Override
public boolean visit(@Nonnull JWhileStatement whileStatement) {
assert false : "Not supported";
return false;
}
@Override
public boolean visit(@Nonnull JThisRef jThisRef) {
JThis jThis = targetMethod.getThis();
assert jThis != null;
assert jThis.getType().isSameType(jThisRef.getType());
expression = jThis.makeRef(jThisRef.getSourceInfo());
return false;
}
@Override
public boolean visit(@Nonnull JLocalRef localRef) {
JLocal clonedLocal = clonedLocals.get(localRef.getLocal());
if (clonedLocal == null) {
clonedLocal = cloneLocal(localRef.getLocal());
}
expression = clonedLocal.makeRef(localRef.getSourceInfo());
return false;
}
@Override
public boolean visit(@Nonnull JParameterRef parameterRef) {
JParameter parameter = parameterRef.getParameter();
assert parameter.getEnclosingMethod() == targetMethod;
expression = parameter.makeRef(parameterRef.getSourceInfo());
return false;
}
}