blob: 60c20ec96edcb769c8771ecaaf45b76a0f59d508 [file] [log] [blame]
/*
* Copyright (C) 2013 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.jill.frontend.java;
import com.android.jill.JillException;
import com.android.jill.Options;
import com.android.jill.backend.jayce.JayceWriter;
import com.android.jill.backend.jayce.Token;
import com.android.jill.frontend.java.analyzer.JillAnalyzer;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
/**
* Method body writer.
*/
public class MethodBodyWriter extends JillWriter implements Opcodes {
@Nonnull
private static final String LAMBDA_META_FACTORY = "java/lang/invoke/LambdaMetafactory";
@Nonnull
private static final String ALT_META_FACTORY = "altMetafactory";
@Nonnull
private static final String META_FACTORY = "metafactory";
@Nonnegative
private static final int LAMBDA_FLAG_SERIALIZABLE = 1;
@Nonnegative
private static final int LAMBDA_FLAG_MARKERS = 2;
@Nonnegative
private static final int LAMBDA_FLAG_BRDIGES = 4;
@Nonnull
private static final String JAVA_IO_SERIALIZABLE = "Ljava/io/Serializable;";
@Nonnull
private static final String LAMBDA_MTH_PREFIX = "lambda$";
@Nonnegative
public static final int LAMBDA_METHOD = 0x200000;
@Nonnull
private final Map<String, Variable> nameToVar = new HashMap<String, Variable>();
@Nonnull
private final Map<Variable, Variable> parameter2Var = new LinkedHashMap<Variable, Variable>();
public static final int CONSTRUCTOR = 0x10000;
/**
* Kinds of method call dispatch.
*/
public enum DispatchKind {
VIRTUAL,
DIRECT
}
/**
* Kinds of method.
*/
public enum MethodKind {
STATIC,
INSTANCE_NON_VIRTUAL,
INSTANCE_VIRTUAL
}
/**
* Kinds of method call receiver.
*/
public enum MethodCallReceiverKind {
CLASS,
INTERFACE
}
/**
* kinds of field reference.
*/
public enum FieldRefKind {
INSTANCE,
STATIC;
}
private static class Case {
@Nonnull
LabelNode labelNode;
@CheckForNull
Integer key;
@Nonnull
String caseId;
public Case(
@Nonnull LabelNode labelNode, @Nonnegative int switchIdx, @CheckForNull Integer key) {
this.labelNode = labelNode;
this.key = key;
caseId = switchIdx + "_" + (this.key != null ? this.key.toString() : "default");
}
}
private static class CmpOperands{
@Nonnegative
int opcode;
@Nonnull
Variable lhs;
@Nonnull
Variable rhs;
public CmpOperands(@Nonnegative int opcode, @Nonnull Variable lhs, @Nonnull Variable rhs) {
this.opcode = opcode;
this.lhs = lhs;
this.rhs = rhs;
}
}
@Nonnull
private final HashMap<Variable, CmpOperands> cmpOperands =
new HashMap<Variable, MethodBodyWriter.CmpOperands>();
@Nonnull
private final AnnotationWriter annotWriter;
@Nonnegative
private static final int NO_MODIFIER = 0;
private static final int TOP_OF_STACK = -1;
@Nonnull
private final Set<String> currentCatchList = new HashSet<String>();
@Nonnegative
private int currentLine = 0;
@Nonnull
private final ClassNode currentClass;
@Nonnull
private final MethodNode currentMethod;
@Nonnull
private final Analyzer<BasicValue> analyzer;
@Nonnegative
private int unusedVarCount = 0;
@Nonnegative
private int currentPc = 0;
private int startLine = SourceInfoWriter.NO_LINE;
private int endLine = SourceInfoWriter.NO_LINE;
@Nonnull
private final Options options;
private int lambdaCount = 0;
@Nonnull
private final Map<TryCatchBlockNode, Variable> catchBlockToCatchedVariable =
new HashMap<TryCatchBlockNode, Variable>();
@Nonnull
private final List<MethodNode> additionalMethods = new ArrayList<MethodNode>();
public MethodBodyWriter(@Nonnull JayceWriter writer,
@Nonnull AnnotationWriter annotWriter,
@Nonnull ClassNode cn, @Nonnull MethodNode mn,
@Nonnull SourceInfoWriter sourceInfoWriter,
@Nonnull Options options) {
super(writer, sourceInfoWriter);
this.annotWriter = annotWriter;
this.options = options;
currentClass = cn;
BasicInterpreter bi = new JillAnalyzer();
analyzer = new Analyzer<BasicValue>(bi);
if (mn.instructions.size() != 0) {
currentMethod = getMethodWithoutJSR(mn);
try {
analyzer.analyze(currentClass.name, currentMethod);
removeDeadCode();
analyzer.analyze(currentClass.name, currentMethod);
} catch (AnalyzerException e) {
throw new JillException("Variable analyser fails.", e);
}
} else {
currentMethod = mn;
}
}
@Nonnull
public List<MethodNode> getAdditionalMethods() {
return additionalMethods;
}
public void write() throws IOException {
if (AsmHelper.isAnnotation(currentClass)) {
writeAnnotationMethod();
} else if (AsmHelper.isConstructor(currentMethod)) {
writeConstructor();
} else {
writeMethod();
}
}
private void writeConstructor() throws IOException {
computeStartAndEndLine();
sourceInfoWriter.writeDebugBegin(currentClass, startLine);
writer.writeKeyword(Token.CONSTRUCTOR);
writer.writeOpen();
writeParameters();
writer.writeInt(AsmHelper.getModifiers(currentMethod));
annotWriter.writeAnnotations(currentMethod);
writeMethodBody();
writer.writeOpenNodeList(); // Markers
writeOriginalTypeInfoMarker();
writeThrownExceptionMarker();
writer.writeCloseNodeList();
sourceInfoWriter.writeDebugEnd(currentClass, endLine);
writer.writeClose();
}
@Nonnull
private String updateMethodNameForLambdaIfNeeded(@Nonnull String methodName) {
if (methodName.startsWith(LAMBDA_MTH_PREFIX)) {
return stringLegalizer(currentClass.name) + "_" + methodName;
}
return methodName;
}
private void writeMethod() throws IOException {
computeStartAndEndLine();
sourceInfoWriter.writeDebugBegin(currentClass, startLine);
writer.writeKeyword(Token.METHOD);
writer.writeOpen();
writer.writeString(updateMethodNameForLambdaIfNeeded(currentMethod.name));
writer.writeId(Type.getReturnType(currentMethod.desc).getDescriptor());
writeParameters();
MethodKind methodKind;
int modifier;
if (currentMethod.name.startsWith(LAMBDA_MTH_PREFIX)) {
// If the method represents the body of a lambda expression, do not reuse modifier and
// methodKind but force its value.
modifier = LAMBDA_METHOD | ACC_SYNTHETIC;
if (AsmHelper.isInterface(currentClass)) {
modifier |= ACC_PUBLIC;
}
if (AsmHelper.isStatic(currentMethod)) {
modifier |= ACC_STATIC;
methodKind = MethodKind.STATIC;
} else {
methodKind = MethodKind.INSTANCE_VIRTUAL;
}
} else {
if (AsmHelper.isStatic(currentMethod)) {
methodKind = MethodKind.STATIC;
} else if (AsmHelper.isConstructor(currentMethod) || AsmHelper.isPrivate(currentMethod)) {
methodKind = MethodKind.INSTANCE_NON_VIRTUAL;
} else {
methodKind = MethodKind.INSTANCE_VIRTUAL;
}
modifier = AsmHelper.getModifiers(currentMethod);
if (AsmHelper.isStaticInit(currentMethod)) {
modifier |= CONSTRUCTOR;
}
}
writer.writeMethodKindEnum(methodKind);
writer.writeInt(modifier);
annotWriter.writeAnnotations(currentMethod);
writeMethodBody();
writer.writeOpenNodeList(); // Markers
writeOriginalTypeInfoMarker();
writeThrownExceptionMarker();
writer.writeCloseNodeList();
sourceInfoWriter.writeDebugEnd(currentClass, endLine);
writer.writeClose();
}
private void writeAnnotationMethod() throws IOException {
computeStartAndEndLine();
sourceInfoWriter.writeDebugBegin(currentClass, startLine);
writer.writeKeyword(Token.ANNOTATION_METHOD);
writer.writeOpen();
writer.writeString(currentMethod.name);
writer.writeId(Type.getReturnType(currentMethod.desc).getDescriptor());
writer.writeInt(AsmHelper.getModifiers(currentMethod));
annotWriter.writeAnnotations(currentMethod);
if (currentMethod.annotationDefault != null) {
annotWriter.writeValue(currentMethod.annotationDefault);
} else {
writer.writeNull();
}
writer.writeOpenNodeList(); // Markers
writeOriginalTypeInfoMarker();
writer.writeCloseNodeList();
sourceInfoWriter.writeDebugEnd(currentClass, endLine);
writer.writeClose();
}
private void writeOriginalTypeInfoMarker() throws IOException {
if (AsmHelper.hasValidGenericSignature(currentMethod)) {
writer.writeKeyword(Token.GENERIC_SIGNATURE);
writer.writeOpen();
writer.writeString(currentMethod.signature);
writer.writeClose();
} else {
writer.writeNull();
}
}
private void writeThrownExceptionMarker() throws IOException {
if (currentMethod.exceptions != null && !currentMethod.exceptions.isEmpty()) {
writer.writeKeyword(Token.THROWN_EXCEPTION);
writer.writeOpen();
writer.writeIds(AsmHelper.getDescriptorsFromInternalNames(currentMethod.exceptions));
writer.writeClose();
}
}
@Nonnull
private MethodNode getMethodWithoutJSR(@Nonnull MethodNode mn) {
JSRInlinerAdapter jsrInliner =
new JSRInlinerAdapter(null, mn.access, mn.name, mn.desc, mn.signature,
mn.exceptions.toArray(new String[mn.exceptions.size()]));
mn.accept(jsrInliner);
return jsrInliner;
}
private void writeMethodBody() throws IOException {
currentCatchList.clear();
writer.clearCatchBlockIds();
if (AsmHelper.isNative(currentMethod)) {
writeNativeMethodBody();
} else if (AsmHelper.isAbstract(currentMethod)) {
writer.writeNull();
} else {
createCaughtVariables();
currentLine = startLine;
writeJavaMethodBody();
}
assert writer.isCurrentCatchBlockListEmpty();
}
private void computeStartAndEndLine() {
for (AbstractInsnNode insn : currentMethod.instructions.toArray()) {
if (insn instanceof LineNumberNode) {
LineNumberNode lnn = (LineNumberNode) insn;
if (startLine == SourceInfoWriter.NO_LINE) {
startLine = lnn.line;
endLine = lnn.line + 1;
continue;
}
if (lnn.line < startLine) {
startLine = lnn.line;
} else if (lnn.line > endLine) {
endLine = lnn.line;
}
}
}
}
private void createCaughtVariables() {
for (TryCatchBlockNode tryCatchNode : currentMethod.tryCatchBlocks) {
Variable declaringCatchVariable = null;
Type caughtType;
if (tryCatchNode.type == null) {
// Jack represents finally by a catch on java.lang.Object.
caughtType = Type.getType(Object.class);
} else {
// If there are multi catches, it is not possible to compute precisely the common type of
// exceptions without having the full classpath and by loading all classes. Jill uses
// Throwable as common type even when a more precise type is known.
// This type will be cast with a reinterpret cast to the right type when it will be used.
caughtType = Type.getType(Throwable.class);
}
String id = "-e_" + (unusedVarCount++);
declaringCatchVariable = new Variable(id, id, caughtType, null);
catchBlockToCatchedVariable.put(tryCatchNode, declaringCatchVariable);
}
}
private void writeNativeMethodBody() throws IOException {
sourceInfoWriter.writeUnknwonDebugBegin();
writer.writeKeyword(Token.NATIVE_METHOD_BODY);
writer.writeOpen();
sourceInfoWriter.writeUnknownDebugEnd();
writer.writeClose();
}
private void writeJavaMethodBody() throws IOException {
sourceInfoWriter.writeDebugBegin(currentClass, startLine);
writer.writeKeyword(Token.METHOD_BODY);
writer.writeOpen();
writeLocals();
writeBody();
sourceInfoWriter.writeDebugEnd(currentClass, endLine);
writer.writeClose();
}
private void writeBody() throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
if (currentMethod.instructions.size() == 0) {
if (options.isTolerant()) {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.THROW_STATEMENT);
writer.writeOpen();
writer.writeKeyword(Token.NEW_INSTANCE);
writer.writeOpen();
// Type of created object
writer.writeId("Ljava/lang/AssertionError;");
// Empty argument types
writer.writeIds(Collections.<String>emptyList());
// No arguments
writer.writeOpenNodeList();
writer.writeCloseNodeList();
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
} else {
throw new JillException("Method should have instructions.");
}
} else {
for (Map.Entry<Variable, Variable> entry : parameter2Var.entrySet()) {
Variable p = entry.getKey();
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeLocalRef(entry.getValue());
if (p.getType() == Type.BOOLEAN_TYPE) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, p, Type.INT_TYPE.getDescriptor());
} else {
writeLocalRef(p);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
Frame<BasicValue>[] frames = analyzer.getFrames();
for (int insnIdx = 0; insnIdx < currentMethod.instructions.size(); insnIdx++) {
currentPc = insnIdx;
AbstractInsnNode insn = currentMethod.instructions.get(insnIdx);
Frame<BasicValue> currentFrame = frames[insnIdx];
// There's no next frame if insn is a return, and the last instruction.
Frame<BasicValue> nextFrame = (insnIdx < frames.length - 1) ? frames[insnIdx + 1] : null;
if (insn instanceof JumpInsnNode) {
writeInsn(currentFrame, (JumpInsnNode) insn, insnIdx);
} else if (insn instanceof LdcInsnNode) {
assert nextFrame != null;
writeInsn(nextFrame, (LdcInsnNode) insn);
} else if (insn instanceof InsnNode) {
writeInsn(currentFrame, nextFrame, (InsnNode) insn);
} else if (insn instanceof VarInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (VarInsnNode) insn);
} else if (insn instanceof LabelNode) {
computeCatchList((LabelNode) insn);
writeCatchBlock((LabelNode) insn, insnIdx, frames);
writeLabelInsn(insnIdx);
} else if (insn instanceof FieldInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (FieldInsnNode) insn);
} else if (insn instanceof MethodInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (MethodInsnNode) insn);
} else if (insn instanceof LineNumberNode) {
currentLine = ((LineNumberNode) insn).line;
} else if (insn instanceof FrameNode) {
// Nothing to do.
} else if (insn instanceof TypeInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (TypeInsnNode) insn);
} else if (insn instanceof TableSwitchInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (TableSwitchInsnNode) insn, insnIdx);
} else if (insn instanceof LookupSwitchInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (LookupSwitchInsnNode) insn, insnIdx);
} else if (insn instanceof IntInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (IntInsnNode) insn);
} else if (insn instanceof IincInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (IincInsnNode) insn);
} else if (insn instanceof MultiANewArrayInsnNode) {
assert nextFrame != null;
writeInsn(currentFrame, nextFrame, (MultiANewArrayInsnNode) insn);
} else if (insn instanceof InvokeDynamicInsnNode) {
InvokeDynamicInsnNode iDyn = (InvokeDynamicInsnNode) insn;
String handleName = iDyn.bsm.getName();
if (iDyn.bsm.getOwner().equals(LAMBDA_META_FACTORY)
&& (handleName.equals(META_FACTORY) || handleName.equals(ALT_META_FACTORY))) {
writeLambda(currentFrame, nextFrame, iDyn);
} else {
throw new JillException("Method " + handleName + " in "
+ iDyn.bsm.getOwner().replace('/', '.') + " is not supported with invoke dynamic");
}
} else {
throw new JillException("Unsupported instruction.");
}
}
// Current solution for comparison requires its result to be consumed by an "if" or a "pop"
// instruction.
if (!cmpOperands.isEmpty()) {
throw new AssertionError("A comparison has not been followed by an if");
}
}
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeLambda(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull InvokeDynamicInsnNode iDyn) throws IOException {
Handle mthImplementingLambda = (Handle) iDyn.bsmArgs[1];
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.LAMBDA);
writer.writeOpen();
// Write captured variables
writer.writeOpenNodeList();
Type[] capturedVarTypes = Type.getArgumentTypes(iDyn.desc);
int stackArgIndex = capturedVarTypes.length;
while (stackArgIndex > 0) {
// Add reinterpreter cast to precisely type captured variable
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame,
capturedVarTypes[capturedVarTypes.length - stackArgIndex].getDescriptor(),
-stackArgIndex);
stackArgIndex--;
}
writer.writeCloseNodeList();
// Receiver kind of the enclosing type of the method implementing the lambda
MethodCallReceiverKind receiverKind;
if (AsmHelper.isInterface(currentClass)) {
writer.writeReceiverKindEnum(MethodCallReceiverKind.INTERFACE);
} else {
writer.writeReceiverKindEnum(MethodCallReceiverKind.CLASS);
}
if (!mthImplementingLambda.getName().contains("lambda$")) {
mthImplementingLambda = generateMethodRef(iDyn, mthImplementingLambda, capturedVarTypes);
}
// Enclosing type of the method implementing the lambda
writer.writeId(Type.getObjectType(mthImplementingLambda.getOwner()).getDescriptor());
// name, argument types, method king and return type of the method implementing the lambda
writer.writeString(updateMethodNameForLambdaIfNeeded(mthImplementingLambda.getName()));
Type[] argumentTypes = Type.getArgumentTypes(mthImplementingLambda.getDesc());
List<String> argsTypeIds = new ArrayList<String>(argumentTypes.length);
for (Type argType : argumentTypes) {
argsTypeIds.add(argType.getDescriptor());
}
writer.writeIds(argsTypeIds);
switch (mthImplementingLambda.getTag()) {
case Opcodes.H_INVOKESTATIC: {
writer.writeMethodKindEnum(MethodKind.STATIC);
break;
}
case Opcodes.H_INVOKESPECIAL: {
writer.writeMethodKindEnum(MethodKind.INSTANCE_VIRTUAL);
break;
}
default: {
throw new AssertionError();
}
}
writer.writeId(Type.getReturnType(mthImplementingLambda.getDesc()).getDescriptor());
// SAM type
writer.writeId(Type.getReturnType(iDyn.desc).getDescriptor());
int bridgesCountIdx = writeBounds(iDyn, /*boundsCountIdx=*/ 4);
// Method to implement
writeMethodId(iDyn.name, (Type) iDyn.bsmArgs[0]);
// Method to enforce
writeMethodId(iDyn.name, (Type) iDyn.bsmArgs[2]);
writeBridges(iDyn, bridgesCountIdx);
// Markers
writer.writeOpen();
writer.writeInt(1);
writer.writeKeyword(Token.LAMBDA_FROM_JILL);
writer.writeOpen();
writer.writeClose();
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
@Nonnegative
private int writeBounds(@Nonnull InvokeDynamicInsnNode iDyn, @Nonnegative int boundsCountIdx)
throws IOException {
int bridgesCountIdx = boundsCountIdx;
List<String> bounds;
if (iDyn.bsmArgs.length > 3) {
int val = ((Integer) iDyn.bsmArgs[3]).intValue();
bounds = new ArrayList<String>(getBoundsCount(iDyn, boundsCountIdx));
if ((val & LAMBDA_FLAG_MARKERS) == LAMBDA_FLAG_MARKERS) {
int boundsCount = ((Integer) iDyn.bsmArgs[boundsCountIdx]).intValue();
int firstBounds = boundsCountIdx + 1;
bridgesCountIdx = firstBounds + boundsCount;
for (int boundIdx = firstBounds; boundIdx < bridgesCountIdx; boundIdx++) {
bounds.add(((Type) iDyn.bsmArgs[boundIdx]).getDescriptor());
}
}
if ((val & LAMBDA_FLAG_SERIALIZABLE) == LAMBDA_FLAG_SERIALIZABLE) {
bounds.add(JAVA_IO_SERIALIZABLE);
}
} else {
bounds = Collections.emptyList();
}
if (bounds.size() > 0) {
writer.writeIds(bounds);
} else {
writer.writeOpenNodeList();
writer.writeCloseNodeList();
}
return bridgesCountIdx;
}
@Nonnegative
private int getBoundsCount(@Nonnull InvokeDynamicInsnNode iDyn, @Nonnegative int boundsCountIdx) {
int boundsCount = 0;
int val = ((Integer) iDyn.bsmArgs[3]).intValue();
if ((val & LAMBDA_FLAG_MARKERS) == LAMBDA_FLAG_MARKERS) {
boundsCount = ((Integer) iDyn.bsmArgs[boundsCountIdx]).intValue();
}
if ((val & LAMBDA_FLAG_SERIALIZABLE) == LAMBDA_FLAG_SERIALIZABLE) {
boundsCount++;
}
return boundsCount;
}
private void writeBridges(@Nonnull InvokeDynamicInsnNode iDyn, @Nonnegative int bridgesCountIdx)
throws IOException {
if (iDyn.bsmArgs.length > 3) {
int val = ((Integer) iDyn.bsmArgs[3]).intValue();
if ((val & LAMBDA_FLAG_BRDIGES) == LAMBDA_FLAG_BRDIGES) {
int bridgesCount = ((Integer) iDyn.bsmArgs[bridgesCountIdx]).intValue();
writer.writeOpen();
writer.writeInt(bridgesCount);
int firstBridge = bridgesCountIdx + 1;
for (int bridgeIdx = firstBridge; bridgeIdx < firstBridge + bridgesCount; bridgeIdx++) {
writeMethodId(iDyn.name, (Type) iDyn.bsmArgs[bridgeIdx]);
}
writer.writeClose();
return;
}
}
writer.writeOpenNodeList();
writer.writeCloseNodeList();
}
/**
* This method will generate a new method that will be referenced from the lambda representing a
* method reference. This method will realize the call described by the method reference.
*/
@Nonnull
private Handle generateMethodRef(@Nonnull InvokeDynamicInsnNode iDyn, @Nonnull Handle mthtoCall,
@Nonnull Type[] capturedVarTypes) {
int mthImplementingLambdaTag = mthtoCall.getTag();
Type returnType = getReturnTypeOfMthToGenerate(mthtoCall);
String mthToGenerate = getDescOfMethToGenerate(iDyn, mthtoCall, capturedVarTypes);
// Stack size is at least the size of the returned type
int stackSize = returnType.getSize();
int localSize = 0;
MethodNode mn = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC,
getNextLambdaMethodName(), mthToGenerate, null, null);
if (mthImplementingLambdaTag == Opcodes.H_NEWINVOKESPECIAL) {
mn.visitTypeInsn(NEW, returnType.getInternalName());
mn.visitInsn(DUP);
/*
* 2 means result of new instruction + duplicated value (one for invoke the constructor and
* one to return the allocated object)
*/
stackSize += 2;
}
if (mthRefNeedInstance(mthtoCall)) {
// Instance is always the first parameter of the generated method
Type[] argTypeOfMethodToGenerate = Type.getArgumentTypes(mthToGenerate);
assert argTypeOfMethodToGenerate.length >= 1;
Type instanceType = argTypeOfMethodToGenerate[0];
mn.visitVarInsn(instanceType.getOpcode(ILOAD), 0);
localSize += instanceType.getSize();
stackSize += instanceType.getSize();
}
for (Type argType : Type.getArgumentTypes(mthtoCall.getDesc())) {
mn.visitVarInsn(argType.getOpcode(ILOAD), localSize);
localSize += argType.getSize();
stackSize += argType.getSize();
}
mn.visitMethodInsn(getCallOpcodeFromTag(mthImplementingLambdaTag), mthtoCall.getOwner(),
mthtoCall.getName(), mthtoCall.getDesc());
mn.visitInsn(returnType.getOpcode(IRETURN));
mn.visitMaxs(stackSize, localSize);
additionalMethods.add(mn);
mthtoCall =
new Handle(Opcodes.H_INVOKESTATIC, currentClass.name, mn.name, mthToGenerate);
return mthtoCall;
}
@Nonnegative
private int getCallOpcodeFromTag(@Nonnegative int handleTag) {
switch (handleTag) {
case Opcodes.H_INVOKESTATIC: {
return INVOKESTATIC;
}
case Opcodes.H_INVOKEINTERFACE: {
return INVOKEINTERFACE;
}
case Opcodes.H_INVOKESPECIAL: {
return INVOKESPECIAL;
}
case Opcodes.H_INVOKEVIRTUAL: {
return INVOKEVIRTUAL;
}
case Opcodes.H_NEWINVOKESPECIAL: {
return INVOKESPECIAL;
}
default: {
throw new AssertionError();
}
}
}
@Nonnull
private String getDescOfMethToGenerate(@Nonnull InvokeDynamicInsnNode iDyn,
@Nonnull Handle mthtoCall, @Nonnull Type[] capturedVarTypes) {
Type[] argumentTypes = Type.getArgumentTypes(mthtoCall.getDesc());
Type[] lambdaMethodArgTypes = null;
int argTypeIdxOfMthToGenerate = 0;
if (mthRefNeedInstance(mthtoCall)) {
lambdaMethodArgTypes = new Type [argumentTypes.length + /*instance of method call*/ 1];
Type instanceType = null;
if (capturedVarTypes.length != 0) {
// instance of method call is captured, take its type from the captured variable
assert capturedVarTypes.length == 1;
instanceType = capturedVarTypes[0];
} else {
// instance is not captured, takes it from the first argument of the implemented interface
Type[] typeOfInterfaceMth =
Type.getArgumentTypes(((Type) iDyn.bsmArgs[2]).getDescriptor());
instanceType = typeOfInterfaceMth[0];
}
lambdaMethodArgTypes[argTypeIdxOfMthToGenerate++] = instanceType;
} else {
lambdaMethodArgTypes = new Type [argumentTypes.length];
}
for (Type argType : argumentTypes) {
lambdaMethodArgTypes[argTypeIdxOfMthToGenerate++] = argType;
}
String lambdaMethodDesc =
Type.getMethodDescriptor(getReturnTypeOfMthToGenerate(mthtoCall), lambdaMethodArgTypes);
return lambdaMethodDesc;
}
@Nonnull
private Type getReturnTypeOfMthToGenerate(@Nonnull Handle mthtoCall) {
Type returnType = Type.getReturnType(mthtoCall.getDesc());
if (mthtoCall.getTag() == Opcodes.H_NEWINVOKESPECIAL) {
// For '::new', return type is not contained in method descriptor since constructor return
// void, thus the return type is the owner of the constructor.
returnType = Type.getObjectType(mthtoCall.getOwner());
}
return returnType;
}
private boolean mthRefNeedInstance(@Nonnull Handle mthtoCall) {
return mthtoCall.getTag() == Opcodes.H_INVOKESPECIAL
|| mthtoCall.getTag() == Opcodes.H_INVOKEINTERFACE
|| mthtoCall.getTag() == Opcodes.H_INVOKEVIRTUAL;
}
@Nonnull
private String getNextLambdaMethodName() {
String lambdaMethodName = stringLegalizer(currentClass.name) + "_mthref$" + lambdaCount;
while (methodNameExist(currentClass, lambdaMethodName)) {
lambdaCount++;
lambdaMethodName = stringLegalizer(currentClass.name) + "_mthref$" + lambdaCount;
}
lambdaCount++;
return lambdaMethodName;
}
private boolean methodNameExist(@Nonnull ClassNode cn, @Nonnull String newMethodName) {
for (MethodNode mn : currentClass.methods) {
if (mn.name.equals(newMethodName)) {
return true;
}
}
return false;
}
private void writeMethodId(@Nonnull String methodName, @Nonnull Type type) throws IOException {
writer.writeKeyword(Token.METHODID_WITH_RETURN_TYPE);
writer.writeOpen();
writer.writeString(methodName);
writer.writeMethodKindEnum(MethodKind.INSTANCE_VIRTUAL);
writer.writeId(type.getReturnType().getDescriptor());
Type[] argumentTypes = type.getArgumentTypes();
List<String> argsTypeIds = new ArrayList<String>(argumentTypes.length);
for (Type argType : argumentTypes) {
argsTypeIds.add(argType.getDescriptor());
}
writer.writeIds(argsTypeIds);
writer.writeClose();
}
private void writeCatchBlock(@Nonnull LabelNode labelNode, @Nonnegative int labelIdx,
@Nonnull Frame<BasicValue>[] frames) throws IOException {
for (TryCatchBlockNode tryCatchNode : currentMethod.tryCatchBlocks) {
if (tryCatchNode.handler == labelNode) {
// Always create a variable that will be typed with catched exception. Reuse computed
// variable is not possible since type could be lost due to merging.
Variable declaringCatchVariable = catchBlockToCatchedVariable.get(tryCatchNode);
// Compute line of catch block, currentLine contains the end of catch block but Jill needs
// the line that start the catch block. Go ahead on pseudo instructions to find the line
// number of the first instruction that belongs to the catch block, Opcode == -1 means
// pseudo instruction (LineNumber, Frame, Label). Keep the current line unchanged if
// no line number found before the first concrete instruction.
AbstractInsnNode insnIntoCatch = tryCatchNode.handler.getNext();
while (insnIntoCatch != null && insnIntoCatch.getOpcode() == -1) {
if (insnIntoCatch instanceof LineNumberNode) {
currentLine = ((LineNumberNode) insnIntoCatch).line;
}
insnIntoCatch = insnIntoCatch.getNext();
}
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.CATCH_BLOCK);
writer.writeOpen();
writer.writeId(getCatchId(tryCatchNode.handler));
// Take into account multi catches by computing the list of caught types for this handler
List<String> ids = new ArrayList<String>();
if (tryCatchNode.type == null) {
// Jack represents finally by a catch on java.lang.Object.
ids.add(Type.getType(Object.class).getDescriptor());
} else {
ids.add(Type.getObjectType(tryCatchNode.type).getDescriptor());
for (TryCatchBlockNode tryCatchNode2 : currentMethod.tryCatchBlocks) {
if (labelNode == tryCatchNode2.handler && tryCatchNode != tryCatchNode2
&& !tryCatchNode.type.equals(tryCatchNode2.type)) {
ids.add(Type.getObjectType(tryCatchNode2.type).getDescriptor());
}
}
}
writer.writeIds(ids);
writeLocal(declaringCatchVariable);
writer.writeOpenNodeList();
if (frames[labelIdx] != null) {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(frames[labelIdx], TOP_OF_STACK);
writeLocalRef(declaringCatchVariable);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
writeGoto(tryCatchNode.handler);
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break; // Write catch block only one time even if the handler is used severals times.
}
}
}
private void computeCatchList(@Nonnull LabelNode labelNode) {
for (TryCatchBlockNode tryCatchNode : currentMethod.tryCatchBlocks) {
String id = getCatchId(tryCatchNode.handler);
if (tryCatchNode.start == labelNode) {
currentCatchList.add(id);
} else if (tryCatchNode.end == labelNode) {
currentCatchList.remove(id);
}
}
}
@Nonnull
private String getCatchId(@Nonnull LabelNode labelNode) {
int insnIndex = currentMethod.instructions.indexOf(labelNode);
return Integer.toString(insnIndex) + "-catch";
}
private void writeLabelInsn(@Nonnegative int insnIdx)
throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.LABELED_STATEMENT);
writer.writeOpen();
String id = Integer.toString(insnIdx);
writer.writeString(id);
writer.writeId(id);
writeEmptyBlock();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeEmptyBlock() throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull IincInsnNode iincInsn) throws IOException {
// Uninitialized variable means dead store. Do not generate them.
if (nextFrame.getLocal(iincInsn.var) != BasicValue.UNINITIALIZED_VALUE) {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeLocalAccess(nextFrame, iincInsn.var);
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ADD_OPERATION);
writer.writeOpen();
writeLocalAccess(frame, iincInsn.var);
writeValue(iincInsn.incr, currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull IntInsnNode intInsn) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
switch (intInsn.getOpcode()) {
case BIPUSH: {
writeValue(intInsn.operand, currentClass, currentLine);
break;
}
case SIPUSH: {
writeValue(intInsn.operand, currentClass, currentLine);
break;
}
case NEWARRAY: {
switch (intInsn.operand) {
case T_BOOLEAN: {
writeNewArray(frame, "[Z", 1);
break;
}
case T_CHAR: {
writeNewArray(frame, "[C", 1);
break;
}
case T_FLOAT: {
writeNewArray(frame, "[F", 1);
break;
}
case T_DOUBLE: {
writeNewArray(frame, "[D", 1);
break;
}
case T_BYTE: {
writeNewArray(frame, "[B", 1);
break;
}
case T_SHORT: {
writeNewArray(frame, "[S", 1);
break;
}
case T_INT: {
writeNewArray(frame, "[I", 1);
break;
}
case T_LONG: {
writeNewArray(frame, "[J", 1);
break;
}
default: {
throw new JillException("Unsupported array type.");
}
}
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[intInsn.getOpcode()]);
}
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull MultiANewArrayInsnNode manaIns) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeNewArray(frame, manaIns.desc, manaIns.dims);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeNewArray(
@Nonnull Frame<BasicValue> frame, @Nonnull String typeDesc, @Nonnegative int dims)
throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.NEW_ARRAY);
writer.writeOpen();
writer.writeId(typeDesc);
writer.writeOpenNodeList();
for (int i = (dims - 1); i >= 0; i--) {
writeStackAccess(frame, TOP_OF_STACK - i);
}
writer.writeCloseNodeList();
writer.writeOpenNodeList(); // Empty initializers list.
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeArrayRef(@Nonnull Frame<BasicValue> frame, int startIdx,
@Nonnegative int opcode) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ARRAY_REF);
writer.writeOpen();
Type refType = frame.getStack(frame.getStackSize() + startIdx).getType();
// Ensure reference to array, or null. Null case can happen in this case:
// int a[] = null;
// return a[0] <- aload_0, iconst_0, iaload
assert refType.getSort() == Type.ARRAY || "null".equals(refType.getInternalName());
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.REINTERPRETCAST_OPERATION);
writer.writeOpen();
switch (opcode) {
case BALOAD:
case BASTORE: {
if (refType.getDescriptor().equals("[Z")) {
writer.writeId(Type.getType("[Z").getDescriptor());
} else {
writer.writeId(Type.getType("[B").getDescriptor());
}
break;
}
case CALOAD:
case CASTORE: {
writer.writeId(Type.getType("[C").getDescriptor());
break;
}
case SALOAD:
case SASTORE: {
writer.writeId(Type.getType("[S").getDescriptor());
break;
}
case IALOAD:
case IASTORE: {
writer.writeId(Type.getType("[I").getDescriptor());
break;
}
case LALOAD:
case LASTORE: {
writer.writeId(Type.getType("[J").getDescriptor());
break;
}
case FALOAD:
case FASTORE: {
writer.writeId(Type.getType("[F").getDescriptor());
break;
}
case DALOAD:
case DASTORE: {
writer.writeId(Type.getType("[D").getDescriptor());
break;
}
case AALOAD:
case AASTORE: {
writer.writeId(Type.getType("[Ljava/lang/Object;").getDescriptor());
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[opcode]);
}
}
writeStackAccess(frame, startIdx);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeStackAccess(frame, startIdx + 1);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull LookupSwitchInsnNode switchInsn, @Nonnegative int idx) throws IOException {
List<String> cases = new ArrayList<String>();
List<Case> casesLabelNodeAndKey = new ArrayList<Case>();
Case defaultCase = new Case(switchInsn.dflt, idx, null);
casesLabelNodeAndKey.add(defaultCase);
cases.add(defaultCase.caseId);
int caseIdx = 0;
for (LabelNode labelNode : switchInsn.labels) {
Case c = new Case(labelNode, idx, switchInsn.keys.get(caseIdx++));
casesLabelNodeAndKey.add(c);
cases.add(c.caseId);
}
writeSwitch(frame, cases, casesLabelNodeAndKey);
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull TableSwitchInsnNode switchInsn, @Nonnegative int idx) throws IOException {
List<String> cases = new ArrayList<String>();
List<Case> casesLabelNodeAndKey = new ArrayList<Case>();
Case defaultCase = new Case(switchInsn.dflt, idx, null);
casesLabelNodeAndKey.add(defaultCase);
cases.add(defaultCase.caseId);
int key = switchInsn.min;
for (LabelNode labelNode : switchInsn.labels) {
Case c = new Case(labelNode, idx, Integer.valueOf(key++));
casesLabelNodeAndKey.add(c);
cases.add(c.caseId);
}
writeSwitch(frame, cases, casesLabelNodeAndKey);
}
private void writeSwitch(@Nonnull Frame<BasicValue> frame, @Nonnull List<String> cases,
@Nonnull List<Case> casesLabelNodeAndKey)
throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.SWITCH_STATEMENT);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writer.writeIds(cases);
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
for (Case c : casesLabelNodeAndKey) {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.CASE_STATEMENT);
writer.writeOpen();
writer.writeId(c.caseId);
writeValue(c.key, currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeGoto(c.labelNode);
}
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull TypeInsnNode typeInsn) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
String descriptor = Type.getObjectType(typeInsn.desc).getDescriptor();
switch (typeInsn.getOpcode()) {
case NEW: {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ALLOC);
writer.writeOpen();
writer.writeId(descriptor);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case ANEWARRAY: {
writeNewArray(frame, "[" + descriptor, 1);
break;
}
case INSTANCEOF: {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.REINTERPRETCAST_OPERATION);
writer.writeOpen();
writer.writeId(Type.BOOLEAN_TYPE.getDescriptor());
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.INSTANCE_OF);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writer.writeId(descriptor);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case CHECKCAST: {
writeCastOperation(Token.DYNAMIC_CAST_OPERATION, frame, descriptor, TOP_OF_STACK);
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[typeInsn.getOpcode()]);
}
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull FieldInsnNode fldInsn) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
switch (fldInsn.getOpcode()) {
case PUTFIELD: {
writeInstanceFieldRef(fldInsn, frame, TOP_OF_STACK - 1);
if (Type.getType(fldInsn.desc) == Type.BOOLEAN_TYPE) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame,
Type.BOOLEAN_TYPE.getDescriptor(), TOP_OF_STACK);
} else {
writeStackAccess(frame, TOP_OF_STACK);
}
break;
}
case PUTSTATIC: {
writeStaticFieldRef(fldInsn);
if (Type.getType(fldInsn.desc) == Type.BOOLEAN_TYPE) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame,
Type.BOOLEAN_TYPE.getDescriptor(), TOP_OF_STACK);
} else {
writeStackAccess(frame, TOP_OF_STACK);
}
break;
}
case GETFIELD: {
writeStackAccess(nextFrame, TOP_OF_STACK);
if (Type.getType(fldInsn.desc) == Type.BOOLEAN_TYPE) {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.REINTERPRETCAST_OPERATION);
writer.writeOpen();
writer.writeId(Type.INT_TYPE.getDescriptor());
writeInstanceFieldRef(fldInsn, frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
} else {
writeInstanceFieldRef(fldInsn, frame, TOP_OF_STACK);
}
break;
}
case GETSTATIC: {
writeStackAccess(nextFrame, TOP_OF_STACK);
if (Type.getType(fldInsn.desc) == Type.BOOLEAN_TYPE) {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.REINTERPRETCAST_OPERATION);
writer.writeOpen();
writer.writeId(Type.INT_TYPE.getDescriptor());
writeStaticFieldRef(fldInsn);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
} else {
writeStaticFieldRef(fldInsn);
}
break;
}
default:
throw new JillException("Not yet supported " + Printer.OPCODES[fldInsn.getOpcode()]);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private boolean isCallToPolymorphicMethod(@Nonnull MethodInsnNode mthInsn) {
return mthInsn.owner.equals("java/lang/invoke/MethodHandle")
&& (mthInsn.name.equals("invoke") || mthInsn.name.equals("invokeExact"));
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull MethodInsnNode mthInsn) throws IOException {
switch (mthInsn.getOpcode()) {
case INVOKEINTERFACE:
case INVOKESTATIC:
case INVOKEVIRTUAL:
case INVOKESPECIAL: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
Type returnType = Type.getReturnType(mthInsn.desc);
if (returnType != Type.VOID_TYPE) {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
if (returnType == Type.BOOLEAN_TYPE) {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.REINTERPRETCAST_OPERATION);
writer.writeOpen();
writer.writeId(Type.INT_TYPE.getDescriptor());
}
}
DispatchKind dispatchKind;
MethodKind methodKind;
MethodCallReceiverKind receiverKind;
switch (mthInsn.getOpcode()) {
case INVOKEINTERFACE: {
dispatchKind = DispatchKind.VIRTUAL;
methodKind = MethodKind.INSTANCE_VIRTUAL;
receiverKind = MethodCallReceiverKind.INTERFACE;
break;
}
case INVOKESTATIC: {
dispatchKind = DispatchKind.DIRECT;
methodKind = MethodKind.STATIC;
receiverKind = MethodCallReceiverKind.CLASS;
break;
}
case INVOKEVIRTUAL: {
dispatchKind = DispatchKind.VIRTUAL;
methodKind = MethodKind.INSTANCE_VIRTUAL;
receiverKind = MethodCallReceiverKind.CLASS;
break;
}
case INVOKESPECIAL: {
if (mthInsn.owner.equals(currentClass.name) || mthInsn.name.equals("<init>")) {
dispatchKind = DispatchKind.DIRECT;
methodKind = MethodKind.INSTANCE_NON_VIRTUAL;
receiverKind = MethodCallReceiverKind.CLASS;
} else {
dispatchKind = DispatchKind.DIRECT;
methodKind = MethodKind.INSTANCE_VIRTUAL;
receiverKind = MethodCallReceiverKind.CLASS;
}
break;
}
default: {
throw new JillException("Opcode not supported " + Printer.OPCODES[mthInsn.getOpcode()]);
}
}
writeDebugBegin(currentClass, currentLine);
boolean isCallToPolymorphicMethod = isCallToPolymorphicMethod(mthInsn);
writer.writeKeyword(isCallToPolymorphicMethod ? Token.POLYMORPHIC_CALL : Token.METHOD_CALL);
writer.writeOpen();
Type receiverType = Type.getObjectType(mthInsn.owner);
int stackArgIndex = Type.getArgumentTypes(mthInsn.desc).length;
if (mthInsn.getOpcode() == INVOKESTATIC) {
writer.writeNull(); // Instance
} else {
// Add implicit argument 'this'
stackArgIndex++;
// Cast instance to receiver type
// Force to add the cast when it is a polymorphic call otherwise some checks will fails
// into Jack because instance will be type Object and not MethodHandle.
if ((receiverType.equals(frame.getStack(frame.getStackSize() - stackArgIndex).getType())
&& !isCallToPolymorphicMethod) || mthInsn.name.equals("<init>")) {
// It is not possible to add cast on object before call to init
writeStackAccess(frame, -stackArgIndex);
} else {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame, receiverType.getDescriptor(),
-stackArgIndex);
}
stackArgIndex--;
}
if (receiverType.getSort() == Type.ARRAY) {
// Currently Jack file does not support that array types are used as a receiver or
// declaring type into a method call.
receiverType = Type.getType(Object.class);
}
writer.writeId(receiverType.getDescriptor()); // Receiver type
writer.writeReceiverKindEnum(receiverKind);
writer.writeId(updateMethodNameForLambdaIfNeeded(mthInsn.name));
Type[] argumentTypes = Type.getArgumentTypes(mthInsn.desc);
List<String> argsTypeIds = new ArrayList<String>(argumentTypes.length);
for (Type argType : argumentTypes) {
argsTypeIds.add(argType.getDescriptor());
}
if (isCallToPolymorphicMethod) {
// All methods that have polymorphic signature take an array of Object as parameter.
List<String> changedArgsTypeIds = new ArrayList<String>(1);
changedArgsTypeIds.add("[Ljava/lang/Object;");
writer.writeIds(changedArgsTypeIds);
} else {
writer.writeIds(argsTypeIds);
}
writer.writeMethodKindEnum(methodKind);
if (isCallToPolymorphicMethod) {
// All methods that have polymorphic signature return an Object.
writer.writeId("Ljava/lang/Object;");
} else {
writer.writeId(returnType.getDescriptor());
}
int argIdx = 0;
writer.writeOpenNodeList();
while (stackArgIndex > 0) {
Type argType = argumentTypes[argIdx++];
if (argType.getSort() == Type.OBJECT || argType.getSort() == Type.ARRAY
|| argType.getSort() == Type.BYTE || argType.getSort() == Type.CHAR
|| argType.getSort() == Type.SHORT || argType.getSort() == Type.BOOLEAN) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame, argType.getDescriptor(),
-stackArgIndex);
} else {
writeStackAccess(frame, -stackArgIndex);
}
stackArgIndex--;
}
writer.writeCloseNodeList();
if (isCallToPolymorphicMethod) {
writer.writeId(returnType.getDescriptor()); // Call site return type
} else {
writer.writeDispatchKindEnum(dispatchKind);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
if (returnType != Type.VOID_TYPE) {
if (returnType == Type.BOOLEAN_TYPE) {
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[mthInsn.getOpcode()]);
}
}
}
private void writeInsn(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame,
@Nonnull VarInsnNode varInsn) throws IOException {
switch (varInsn.getOpcode()) {
case FLOAD:
case DLOAD:
case LLOAD:
case ILOAD:
case ALOAD: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
if (getLocalVariable(frame, varInsn.var).getType() == Type.BOOLEAN_TYPE) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, getLocalVariable(frame, varInsn.var),
Type.INT_TYPE.getDescriptor());
} else {
writeLocalAccess(frame, varInsn.var);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case FSTORE:
case DSTORE:
case LSTORE:
case ISTORE:
case ASTORE: {
// Uninitialized variable means dead store. Do not generate them.
if (nextFrame.getLocal(varInsn.var) != BasicValue.UNINITIALIZED_VALUE) {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeLocalAccess(nextFrame, varInsn.var);
Type destType = getLocalVariable(nextFrame, varInsn.var).getType();
if (destType == Type.BOOLEAN_TYPE) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame,
Type.BOOLEAN_TYPE.getDescriptor(), TOP_OF_STACK);
} else if (getStackVariable(frame, TOP_OF_STACK).getType() != destType) {
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame, destType.getDescriptor(),
TOP_OF_STACK);
} else {
writeStackAccess(frame, TOP_OF_STACK);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[varInsn.getOpcode()]);
}
}
}
private void writeInsn(@Nonnull Frame<BasicValue> frame,
@CheckForNull Frame<BasicValue> nextFrame, @Nonnull InsnNode insn) throws IOException {
switch (insn.getOpcode()) {
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeValue(insn.getOpcode() - ICONST_0, currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case ACONST_NULL: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeValue(currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case LCONST_0:
case LCONST_1: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeValue((long) (insn.getOpcode() - LCONST_0), currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case FCONST_0:
case FCONST_1:
case FCONST_2: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeValue((float) (insn.getOpcode() - FCONST_0), currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case DCONST_0:
case DCONST_1: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeValue((double) (insn.getOpcode() - DCONST_0), currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case D2L:
case F2L:
case I2L: {
assert nextFrame != null;
writePrimitiveTypeConversion(long.class, frame, nextFrame);
break;
}
case D2F:
case I2F:
case L2F:{
assert nextFrame != null;
writePrimitiveTypeConversion(float.class, frame, nextFrame);
break;
}
case F2D:
case I2D:
case L2D: {
assert nextFrame != null;
writePrimitiveTypeConversion(double.class, frame, nextFrame);
break;
}
case D2I:
case F2I:
case L2I: {
assert nextFrame != null;
writePrimitiveTypeConversion(int.class, frame, nextFrame);
break;
}
case I2B: {
assert nextFrame != null;
writePrimitiveTypeConversion(byte.class, frame, nextFrame);
break;
}
case I2C: {
assert nextFrame != null;
writePrimitiveTypeConversion(char.class, frame, nextFrame);
break;
}
case I2S: {
assert nextFrame != null;
writePrimitiveTypeConversion(short.class, frame, nextFrame);
break;
}
case DRETURN:
case LRETURN:
case FRETURN: {
writeReturn(frame, TOP_OF_STACK);
break;
}
case ARETURN:
case IRETURN: {
Type returnType = Type.getReturnType(currentMethod.desc);
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.RETURN_STATEMENT);
writer.writeOpen();
writeCastOperation(Token.REINTERPRETCAST_OPERATION, frame, returnType.getDescriptor(),
TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case RETURN: {
writeReturn(frame, 0);
break;
}
case DADD:
case FADD:
case IADD:
case LADD: {
assert nextFrame != null;
writeBinaryOperation(Token.ADD_OPERATION, frame, nextFrame);
break;
}
case LCMP:
case FCMPL:
case FCMPG:
case DCMPL:
case DCMPG: {
assert nextFrame != null;
Variable lhs = getStackVariable(frame, TOP_OF_STACK - 1);
Variable rhs = getStackVariable(frame, TOP_OF_STACK);
Variable result = getStackVariable(nextFrame, TOP_OF_STACK);
cmpOperands.put(result, new CmpOperands(insn.getOpcode(), lhs, rhs));
break;
}
case DSUB:
case FSUB:
case ISUB:
case LSUB: {
assert nextFrame != null;
writeBinaryOperation(Token.SUB_OPERATION, frame, nextFrame);
break;
}
case DMUL:
case FMUL:
case IMUL:
case LMUL: {
assert nextFrame != null;
writeBinaryOperation(Token.MUL_OPERATION, frame, nextFrame);
break;
}
case DDIV:
case FDIV:
case IDIV:
case LDIV: {
assert nextFrame != null;
writeBinaryOperation(Token.DIV_OPERATION, frame, nextFrame);
break;
}
case DREM:
case FREM:
case IREM:
case LREM: {
assert nextFrame != null;
writeBinaryOperation(Token.MOD_OPERATION, frame, nextFrame);
break;
}
case DNEG:
case FNEG:
case INEG:
case LNEG: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.PREFIX_NEG_OPERATION);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case ISHL:
case LSHL: {
assert nextFrame != null;
writeBinaryOperation(Token.SHL_OPERATION, frame, nextFrame);
break;
}
case ISHR:
case LSHR: {
assert nextFrame != null;
writeBinaryOperation(Token.SHR_OPERATION, frame, nextFrame);
break;
}
case IUSHR:
case LUSHR: {
assert nextFrame != null;
writeBinaryOperation(Token.SHRU_OPERATION, frame, nextFrame);
break;
}
case IAND:
case LAND: {
assert nextFrame != null;
writeBinaryOperation(Token.BIT_AND_OPERATION, frame, nextFrame);
break;
}
case IOR:
case LOR: {
assert nextFrame != null;
writeBinaryOperation(Token.BIT_OR_OPERATION, frame, nextFrame);
break;
}
case IXOR:
case LXOR: {
assert nextFrame != null;
writeBinaryOperation(Token.BIT_XOR_OPERATION, frame, nextFrame);
break;
}
case ARRAYLENGTH: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ARRAY_LENGTH);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case IALOAD:
case LALOAD:
case FALOAD:
case DALOAD:
case AALOAD:
case BALOAD:
case CALOAD:
case SALOAD: {
assert nextFrame != null;
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeArrayRef(frame, TOP_OF_STACK - 1, insn.getOpcode());
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case IASTORE:
case LASTORE:
case FASTORE:
case DASTORE:
case AASTORE:
case BASTORE:
case CASTORE:
case SASTORE: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeArrayRef(frame, TOP_OF_STACK - 2, insn.getOpcode());
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case MONITORENTER: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.LOCK);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case MONITOREXIT: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.UNLOCK);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case SWAP: {
// frame and nextFrame have the same height, but they have different types, thus frame must
// be use to known the type of variable to read and nextFrame must be use to known the type
// of variable to write.
Variable tmpVar = getTempVarFromTopOfStackMinus1(frame);
boolean topMinus1IsVirtual = isVirtualStackVariable(frame, TOP_OF_STACK - 1);
boolean topIsVirtual = isVirtualStackVariable(frame, TOP_OF_STACK);
if (topMinus1IsVirtual) {
// Virtual swap, stack variables will be transform as below:
// var_stk_top = value_stk_top; var_stk_top-1 is virtual
// =>
// var_stk_top is virtual, var_stk_top-1 = value_stk_top
cmpOperands.put(getStackVariable(nextFrame, TOP_OF_STACK),
cmpOperands.remove(getStackVariable(frame, TOP_OF_STACK - 1)));
} else {
// tmpVar = frame.stack[frame.stack.size() + TOP_OF_STACK - 1]
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeLocalRef(tmpVar);
writeStackAccess(frame, TOP_OF_STACK - 1);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
if (topIsVirtual) {
// Virtual swap, stack variables will be transform as below:
// var_stk_top is virtual; var_stk_top-1 = value_stk_top-1
// =>
// var_stk_top = value_stk_top-1, var_stk_top-1 is virtual
cmpOperands.put(getStackVariable(nextFrame, TOP_OF_STACK - 1),
cmpOperands.remove(getStackVariable(frame, TOP_OF_STACK)));
} else {
// nexFrame.stack[nexFrame.stack.size() + TOP_OF_STACK - 1] =
// frame.stack[frame.stack.size() + TOP_OF_STACK]
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK - 1);
}
// No need to assign the variable (nextFrame, top_of_stack) because it is virtual variable
if (!topMinus1IsVirtual) {
// nextFrame.stack[nextFrame.stack.size() + TOP_OF_STACK] = tmpVar
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeLocalRef(tmpVar);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
break;
}
case DUP: {
assert nextFrame != null;
writeDup(frame, nextFrame);
break;
}
case DUP2: {
assert nextFrame != null;
if (frame.getStack(frame.getStackSize() + TOP_OF_STACK).getSize() == 1) {
assert frame.getStack(frame.getStackSize() + TOP_OF_STACK - 1).getSize() == 1;
writeDup2(frame, nextFrame);
} else {
writeDup(frame, nextFrame);
}
break;
}
case DUP_X1: {
assert nextFrame != null;
assert frame.getStack(frame.getStackSize() + TOP_OF_STACK).getSize() == 1;
assert frame.getStack(frame.getStackSize() + TOP_OF_STACK - 1).getSize() == 1;
writeDupX1(frame, nextFrame);
break;
}
case DUP_X2: {
assert nextFrame != null;
Variable value1 = getStackVariable(frame, TOP_OF_STACK);
Variable value2 = getStackVariable(frame, TOP_OF_STACK - 1);
assert value1.getType().getSize() == 1;
if (value2.getType().getSize() == 1) {
Variable value3 = getStackVariable(frame, TOP_OF_STACK - 2);
assert value3.getType().getSize() == 1;
writeDupX2(frame, nextFrame);
} else {
writeDupX1(frame, nextFrame);
}
break;
}
case DUP2_X1: {
assert nextFrame != null;
Variable value1 = getStackVariable(frame, TOP_OF_STACK);
Variable value2 = getStackVariable(frame, TOP_OF_STACK - 1);
assert value2.getType().getSize() == 1;
if (value1.getType().getSize() == 1) {
Variable value3 = getStackVariable(frame, TOP_OF_STACK - 2);
assert value3.getType().getSize() == 1;
writeDup2X1(frame, nextFrame);
} else {
writeDupX1(frame, nextFrame);
}
break;
}
case DUP2_X2: {
assert nextFrame != null;
Variable value1 = getStackVariable(frame, TOP_OF_STACK);
Variable value2 = getStackVariable(frame, TOP_OF_STACK - 1);
if (value1.getType().getSize() == 1) {
Variable value3 = getStackVariable(frame, TOP_OF_STACK - 2);
if (value3.getType().getSize() == 1) {
Variable value4 = getStackVariable(frame, TOP_OF_STACK - 3);
assert value4.getType().getSize() == 1;
writeDup2X2(frame, nextFrame);
} else {
writeDup2X1(frame, nextFrame);
}
} else {
if (value2.getType().getSize() == 1) {
Variable value3 = getStackVariable(frame, TOP_OF_STACK - 2);
assert value3.getType().getSize() == 1;
writeDupX2(frame, nextFrame);
} else {
writeDupX1(frame, nextFrame);
}
}
break;
}
case POP: {
if (isVirtualStackVariable(frame, TOP_OF_STACK)) {
// Result of comparison must be pop
cmpOperands.remove(getStackVariable(frame, TOP_OF_STACK));
}
break;
}
case NOP:
break;
case POP2:{
if (isVirtualStackVariable(frame, TOP_OF_STACK)) {
// Result of comparison must be pop
cmpOperands.remove(getStackVariable(frame, TOP_OF_STACK));
}
if (getStackVariable(frame, TOP_OF_STACK).getType().getSize() == 1) {
if (isVirtualStackVariable(frame, TOP_OF_STACK - 1)) {
// Result of comparison must be pop
cmpOperands.remove(getStackVariable(frame, TOP_OF_STACK - 1));
}
}
break;
}
case ATHROW: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.THROW_STATEMENT);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[insn.getOpcode()]);
}
}
}
private boolean isVirtualStackVariable(@Nonnull Frame<BasicValue> frame, int stackIdx) {
Variable stackVar = getStackVariable(frame, stackIdx);
return cmpOperands.containsKey(stackVar);
}
private void writeInsn(@Nonnull Frame<BasicValue> nextFrame, @Nonnull LdcInsnNode ldcInsn)
throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeValue(ldcInsn.cst, currentClass, currentLine);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeInsn(
@Nonnull Frame<BasicValue> frame, @Nonnull JumpInsnNode jumpInsn, @Nonnegative int insIndex)
throws IOException {
switch (jumpInsn.getOpcode()) {
case IFNONNULL:
case IFNULL:
case IFEQ:
case IFGE:
case IFGT:
case IFLE:
case IFLT:
case IFNE: {
Variable topOfStackVariable = getStackVariable(frame, TOP_OF_STACK);
CmpOperands cmpOps = cmpOperands.get(topOfStackVariable);
if (cmpOps != null) {
// CmpOperands concerns double, float and long types
assert jumpInsn.getOpcode() != IFNONNULL && jumpInsn.getOpcode() != IFNULL;
// Not operator can be generate only for double and long types to manage comparisons with
// Nan.
Token comparisonToken = getConditionToken(jumpInsn.getOpcode());
boolean needNotoperator = needNotOperator(comparisonToken, cmpOps);
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.IF_STATEMENT);
writer.writeOpen();
if (needNotoperator) {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.PREFIX_NOT_OPERATION);
writer.writeOpen();
} else {
// Condition is inverted to be compliant with language level semantics
// This has been done for comparison to NaN, which forces the branching order.
comparisonToken = invertComparisonToken(comparisonToken);
}
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(comparisonToken);
writer.writeOpen();
writeLocalRef(cmpOps.lhs);
writeLocalRef(cmpOps.rhs);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
if (needNotoperator) {
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
int labeledStatmentIndex = insIndex + 1;
// Then block
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
writeGoto(labeledStatmentIndex);
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
// Else block
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
writeGoto(jumpInsn.label);
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
insertLabeledStatementIfNecessary(labeledStatmentIndex);
cmpOperands.remove(topOfStackVariable);
} else {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.IF_STATEMENT);
writer.writeOpen();
Token conditionalToken = getConditionToken(jumpInsn.getOpcode());
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(conditionalToken);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK);
Variable v = getStackVariable(frame, TOP_OF_STACK);
if (v.getType().equals(Type.BOOLEAN_TYPE)) {
writeValue(false, currentClass, currentLine);
} else if (v.getType().equals(Type.BYTE_TYPE)
|| v.getType().equals(Type.CHAR_TYPE)
|| v.getType().equals(Type.SHORT_TYPE)
|| v.getType().equals(Type.INT_TYPE)) {
writeValue(0, currentClass, currentLine);
} else {
writeValue(currentClass, currentLine);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
// Then block
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
writeGoto(jumpInsn.label);
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
// Else
writer.writeNull();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
break;
}
case IF_ICMPEQ:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ICMPLT:
case IF_ICMPNE:
case IF_ACMPEQ:
case IF_ACMPNE: {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.IF_STATEMENT);
writer.writeOpen();
Token conditionalToken = getConditionToken(jumpInsn.getOpcode());
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(conditionalToken);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK - 1);
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
// Then block
writer.writeKeyword(Token.BLOCK);
writer.writeOpen();
writer.writeOpenNodeList();
writeGoto(jumpInsn.label);
writer.writeCloseNodeList();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
// Else
writer.writeNull();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
break;
}
case GOTO: {
writeGoto(jumpInsn.label);
break;
}
default: {
throw new JillException("Not yet supported " + Printer.OPCODES[jumpInsn.getOpcode()]);
}
}
}
private void insertLabeledStatementIfNecessary(@Nonnegative int labeledStatmentIndex)
throws IOException {
AbstractInsnNode existing = currentMethod.instructions.get(labeledStatmentIndex);
if (existing instanceof LabelNode) {
return;
} else {
writeLabelInsn(labeledStatmentIndex);
}
}
@Nonnull
private Token getConditionToken(@Nonnegative int opcode) {
switch (opcode) {
case IFNULL:
case IF_ACMPEQ:
case IF_ICMPEQ:
case IFEQ:
return Token.EQ_OPERATION;
case IF_ICMPGE:
case IFGE:
return Token.GTE_OPERATION;
case IF_ICMPGT:
case IFGT:
return Token.GT_OPERATION;
case IF_ICMPLE:
case IFLE:
return Token.LTE_OPERATION;
case IF_ICMPLT:
case IFLT:
return Token.LT_OPERATION;
case IFNONNULL:
case IF_ACMPNE:
case IF_ICMPNE:
case IFNE:
return Token.NEQ_OPERATION;
}
throw new JillException("Unsupported condition.");
}
@Nonnull
private Token invertComparisonToken(@Nonnull Token cmpToken) {
switch (cmpToken) {
case GTE_OPERATION: {
return Token.LT_OPERATION;
}
case GT_OPERATION: {
return Token.LTE_OPERATION;
}
case LTE_OPERATION: {
return Token.GT_OPERATION;
}
case LT_OPERATION: {
return Token.GTE_OPERATION;
}
case EQ_OPERATION: {
return Token.NEQ_OPERATION;
}
case NEQ_OPERATION: {
return Token.EQ_OPERATION;
}
default: {
return cmpToken;
}
}
}
@Nonnull
private boolean needNotOperator(@Nonnull Token cmpToken, @Nonnull CmpOperands cmpOps) {
switch (cmpToken) {
case GTE_OPERATION:
case GT_OPERATION: {
return !isCmpg(cmpOps);
}
case LTE_OPERATION:
case LT_OPERATION: {
return !isCmpl(cmpOps);
}
default: {
return false;
}
}
}
private boolean isCmpl(@Nonnull CmpOperands cmpOps) {
return cmpOps.opcode == DCMPL || cmpOps.opcode == FCMPL;
}
private boolean isCmpg(@Nonnull CmpOperands cmpOps) {
return cmpOps.opcode == DCMPG || cmpOps.opcode == FCMPG;
}
private void writeGoto(LabelNode labelNode) throws IOException {
int insIndex = currentMethod.instructions.indexOf(labelNode);
writeGoto(insIndex);
}
private void writeGoto(int insIndex) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.GOTO);
writer.writeOpen();
writer.writeId(Integer.toString(insIndex));
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeReturn(@Nonnull Frame<BasicValue> frame, int stackIdx) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.RETURN_STATEMENT);
writer.writeOpen();
if (stackIdx == 0) {
writer.writeNull();
} else {
writeStackAccess(frame, stackIdx);
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeStackAccess(@Nonnull Frame<BasicValue> frame, int stackIdx)
throws IndexOutOfBoundsException, IOException {
writeLocalRef(getStackVariable(frame, stackIdx));
}
private void writeLocalAccess(@Nonnull Frame<BasicValue> frame, @Nonnegative int localIdx)
throws IndexOutOfBoundsException, IOException {
writeLocalRef(getLocalVariable(frame, localIdx));
}
private void writeLocalRef(@Nonnull Variable v) throws IOException {
if (v.isThis()) {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.THIS_REF);
writer.writeOpen();
writer.writeId(v.getType().getDescriptor());
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
} else {
Token token = v.isParameter() ? Token.PARAMETER_REF : Token.LOCAL_REF;
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(token);
writer.writeOpen();
writer.writeId(v.getId());
if (!v.isParameter()) {
writer.writeOpenNodeList();
writeDebugInformation(v);
writer.writeCloseNodeList();
}
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
}
private void writeDebugInformation(@Nonnull Variable v) throws IOException {
if (!v.hasLocalIndex()) {
return;
}
LocalVariableNode lvn = getLocalVariableNode(v.getLocalIndex());
if (lvn != null) {
writer.writeKeyword(Token.DEBUG_VARIABLE_INFORMATION);
writer.writeOpen();
writer.writeString(lvn.name);
writer.writeId(lvn.desc);
writer.writeString(lvn.signature);
writer.writeClose();
}
}
private void writeInstanceFieldRef(@Nonnull FieldInsnNode fldInsn,
@Nonnull Frame<BasicValue> frame, int offset) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.FIELD_REF);
writer.writeOpen();
writer.writeId(fldInsn.name);
writer.writeId(fldInsn.desc);
writer.writeId(Type.getObjectType(fldInsn.owner).getDescriptor());
writer.writeFieldRefKindEnum(FieldRefKind.INSTANCE);
writeStackAccess(frame, offset);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeStaticFieldRef(@Nonnull FieldInsnNode fldInsn)
throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.FIELD_REF);
writer.writeOpen();
writer.writeId(fldInsn.name);
writer.writeId(fldInsn.desc);
writer.writeId(Type.getObjectType(fldInsn.owner).getDescriptor());
writer.writeFieldRefKindEnum(FieldRefKind.STATIC);
writer.writeNull();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
public void dump() {
Textifier t = new Textifier();
Frame<BasicValue>[] frames = analyzer.getFrames();
List<Object> text = t.getText();
int insnIdx = 0;
currentMethod.accept(new TraceMethodVisitor(t));
for (Object o : text) {
if (insnIdx < frames.length && frames[insnIdx] != null) {
System.out.print(insnIdx + " : [");
for (int i = 0; i < frames[insnIdx].getLocals(); i++) {
BasicValue bv = frames[insnIdx].getLocal(i);
System.out.print(bv.toString() + " ");
}
System.out.print("| ");
for (int i = 0; i < frames[insnIdx].getStackSize(); i++) {
BasicValue bv = frames[insnIdx].getStack(i);
System.out.print(bv.toString() + " ");
}
System.out.println("]");
}
System.out.print(o);
insnIdx++;
}
}
private void writeLocals() throws IOException {
writer.writeOpenNodeList();
if (currentMethod.instructions.size() != 0) {
Iterator<Variable> varIt = collectLocals();
while (varIt.hasNext()) {
writeLocal(varIt.next());
}
}
writer.writeCloseNodeList();
}
private void writeLocal(Variable v) throws IOException {
sourceInfoWriter.writeUnknwonDebugBegin();
writer.writeKeyword(Token.LOCAL);
writer.writeOpen();
writer.writeId(v.getId());
writer.writeInt(v.isSynthetic() ? Opcodes.ACC_SYNTHETIC : NO_MODIFIER);
writer.writeId(v.getType().getDescriptor());
writer.writeId(v.getName());
writer.writeOpenNodeList(); // Empty annotation set, annotations on locals are not kept
writer.writeCloseNodeList();
writer.writeOpenNodeList(); // Markers
writer.writeCloseNodeList();
// TODO(mikaelpeltier): Add debug information.
sourceInfoWriter.writeUnknownDebugEnd();
writer.writeClose();
}
private void writePrimitiveTypeConversion(@Nonnull Class<?> targetType,
@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame) throws IOException {
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeCastOperation(Token.DYNAMIC_CAST_OPERATION, frame, Type.getDescriptor(targetType),
TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeCastOperation(@Nonnull Token cast, @Nonnull Variable var,
@Nonnull String typeDesc) throws IOException {
assert cast == Token.DYNAMIC_CAST_OPERATION || cast == Token.REINTERPRETCAST_OPERATION;
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(cast);
writer.writeOpen();
if (cast == Token.DYNAMIC_CAST_OPERATION) {
ArrayList<String> types = new ArrayList<String>(1);
types.add(typeDesc);
writer.writeIds(types);
} else {
writer.writeId(typeDesc);
}
writeLocalRef(var);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeCastOperation(@Nonnull Token cast, @Nonnull Frame<BasicValue> frame,
@Nonnull String typeDesc, int stackIdx) throws IOException {
writeCastOperation(cast, getStackVariable(frame, stackIdx), typeDesc);
}
private void writeDup(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
if (isVirtualStackVariable(frame, TOP_OF_STACK)) {
// Virtual dup, stack variables will be transform as below:
// var_stk_top is virtual
// =>
// var_stk_top is virtual, var_stk_top-1 is virtual
cmpOperands.put(getStackVariable(nextFrame, TOP_OF_STACK),
cmpOperands.get(getStackVariable(frame, TOP_OF_STACK)));
} else {
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK);
}
}
private void writeDupX1(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK);
writeAssign(frame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 1);
writeAssign(nextFrame, TOP_OF_STACK, nextFrame, TOP_OF_STACK - 2);
}
private void writeDupX2(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK);
writeAssign(frame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 1);
writeAssign(frame, TOP_OF_STACK - 2, nextFrame, TOP_OF_STACK - 2);
writeAssign(nextFrame, TOP_OF_STACK, nextFrame, TOP_OF_STACK - 3);
}
private void writeDup2(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
if (isVirtualStackVariable(frame, TOP_OF_STACK)) {
// Virtual dup, stack variables will be transform as below:
// var_stk_top is virtual, xxx
// =>
// new_var_stk_top is virtual, new_xxx, var_stk_top is virtual, xxx
cmpOperands.put(getStackVariable(nextFrame, TOP_OF_STACK),
cmpOperands.get(getStackVariable(frame, TOP_OF_STACK)));
} else {
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK);
}
if (isVirtualStackVariable(frame, TOP_OF_STACK - 1)) {
// Virtual dup, stack variables will be transform as below:
// xxx, var_stk_top-1 is virtual
// =>
// xxx, new_var_stk_top-1 is virtual, xxx, var_stk_top-1 is virtual
cmpOperands.put(getStackVariable(nextFrame, TOP_OF_STACK - 1),
cmpOperands.get(getStackVariable(frame, TOP_OF_STACK - 1)));
} else {
writeAssign(frame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 1);
}
}
private void writeDup2X1(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK);
writeAssign(frame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 1);
writeAssign(frame, TOP_OF_STACK - 2, nextFrame, TOP_OF_STACK - 2);
writeAssign(nextFrame, TOP_OF_STACK, nextFrame, TOP_OF_STACK - 3);
writeAssign(nextFrame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 4);
}
private void writeDup2X2(@Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
writeAssign(frame, TOP_OF_STACK, nextFrame, TOP_OF_STACK);
writeAssign(frame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 1);
writeAssign(frame, TOP_OF_STACK - 2, nextFrame, TOP_OF_STACK - 2);
writeAssign(frame, TOP_OF_STACK - 3, nextFrame, TOP_OF_STACK - 3);
writeAssign(nextFrame, TOP_OF_STACK, nextFrame, TOP_OF_STACK - 4);
writeAssign(nextFrame, TOP_OF_STACK - 1, nextFrame, TOP_OF_STACK - 5);
}
/**
* writes frame2.stack[frame.stack.size() + offset2] = frame1.stack[frame.stack.size() + offset1]
*
* @throws IOException
*/
private void writeAssign(@Nonnull Frame<BasicValue> frame1, int offset1,
@Nonnull Frame<BasicValue> frame2, int offset2) throws IOException {
assert !isBooleanAssignIssue(
getStackVariable(frame2, offset2),
getStackVariable(frame1, offset1));
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(frame2, offset2);
writeStackAccess(frame1, offset1);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
private void writeBinaryOperation(
@Nonnull Token op, @Nonnull Frame<BasicValue> frame, @Nonnull Frame<BasicValue> nextFrame)
throws IOException {
assert !isBooleanAssignIssue(
getStackVariable(frame, TOP_OF_STACK - 1),
getStackVariable(frame, TOP_OF_STACK));
writeDebugBegin(currentClass, currentLine);
writer.writeCatchBlockIds(currentCatchList);
writer.writeKeyword(Token.EXPRESSION_STATEMENT);
writer.writeOpen();
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(Token.ASG_OPERATION);
writer.writeOpen();
writeStackAccess(nextFrame, TOP_OF_STACK);
writeDebugBegin(currentClass, currentLine);
writer.writeKeyword(op);
writer.writeOpen();
writeStackAccess(frame, TOP_OF_STACK - 1);
writeStackAccess(frame, TOP_OF_STACK);
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
writeDebugEnd(currentClass, currentLine);
writer.writeClose();
}
@Nonnull
private Iterator<Variable> collectLocals() {
Set<Variable> locals = new LinkedHashSet<Variable>();
Frame<BasicValue>[] frames = analyzer.getFrames();
for (int frameIdx = 0; frameIdx < frames.length; frameIdx++) {
currentPc = frameIdx;
Frame<BasicValue> frame = frames[frameIdx];
if (frame != null) {
for (int localIdx = 0; localIdx < frame.getLocals(); localIdx++) {
BasicValue bv = frame.getLocal(localIdx);
if (bv != BasicValue.UNINITIALIZED_VALUE) {
Variable local = getLocalVariable(frame, localIdx);
if (!local.isParameter() && !local.isThis()) {
locals.add(local);
}
}
}
for (int stackIdx = 0; stackIdx < frame.getStackSize(); stackIdx++) {
Variable v = getStackVariable(frame, -stackIdx - 1);
locals.add(v);
}
}
}
// Do not forget to collect temporary variable required by some instructions.
for (int insnIdx = 0; insnIdx < currentMethod.instructions.size(); insnIdx++) {
AbstractInsnNode insn = currentMethod.instructions.get(insnIdx);
if (insn.getOpcode() == SWAP) {
locals.add(getTempVarFromTopOfStackMinus1(frames[insnIdx]));
}
}
return locals.iterator();
}
@Nonnull
private Variable getTempVarFromTopOfStackMinus1(@Nonnull Frame<BasicValue> frame) {
Variable topOfStackBeforeInst = getStackVariable(frame, TOP_OF_STACK - 1);
String tmpVarId = "-swap_tmp_" + typeToUntypedDesc(topOfStackBeforeInst.getType());
Variable tmpVariable = getVariable(tmpVarId, tmpVarId, topOfStackBeforeInst.getType(), null);
return tmpVariable;
}
private void writeParameters()
throws IOException {
writer.writeOpenNodeList();
int parameterIdx = 0;
int parameterAnnotationIdx = 0;
currentPc = 0;
if (!AsmHelper.isStatic(currentMethod)) {
Type parameterType = Type.getObjectType(currentClass.name);
LocalVariableNode lvn = getLocalVariableNode(parameterIdx);
String pid = getUnnamedParameterId(parameterIdx, parameterType);
Variable p = getVariableWithLocalIndex(parameterIdx, pid, lvn != null ? lvn.name : pid,
parameterType, lvn != null ? lvn.signature : null);
p.setThis();
Type untypedParameter = typeToUntyped(parameterType);
String lid = getUnnamedLocalId(parameterIdx, untypedParameter);
Variable local = getVariableWithLocalIndex(parameterIdx, lid, lid, untypedParameter, null);
parameter2Var.put(p, local);
parameterIdx++;
}
for (Type paramType : Type.getArgumentTypes(currentMethod.desc)) {
LocalVariableNode lvn = getLocalVariableNode(parameterIdx);
String pid = getUnnamedParameterId(parameterIdx, paramType);
Variable p = getVariableWithLocalIndex(parameterIdx, pid, lvn != null ? lvn.name : pid,
paramType, lvn != null ? lvn.signature : null);
p.setParameter();
writeParameter(paramType, parameterIdx, p, parameterAnnotationIdx++);
Type untypedParameter = typeToUntyped(paramType);
String lid = getUnnamedLocalId(parameterIdx, untypedParameter);
Variable local = getVariableWithLocalIndex(parameterIdx, lid, lid, untypedParameter, null);
parameter2Var.put(p, local);
parameterIdx += paramType.getSize();
}
writer.writeCloseNodeList();
}
private void writeParameter(@Nonnull Type paramType, @Nonnegative int localIdx,
@Nonnull Variable param, @Nonnegative int parameterAnnotationIdx) throws IOException {
sourceInfoWriter.writeUnknwonDebugBegin();
writer.writeKeyword(Token.PARAMETER);
writer.writeOpen();
writer.writeId(param.getId());
writer.writeInt(param.isSynthetic() ? Opcodes.ACC_SYNTHETIC : NO_MODIFIER);
writer.writeId(paramType.getDescriptor());
writer.writeString(param.getName());
annotWriter.writeAnnotations(currentMethod, parameterAnnotationIdx);
writer.writeOpenNodeList(); // Markers
if (param.hasSignature()) {
writer.writeKeyword(Token.GENERIC_SIGNATURE); // Marker generic signature
writer.writeOpen();
writer.writeString(param.getSignature());
writer.writeClose();
}
writer.writeCloseNodeList();
// TODO(mikaelpeltier) Add debug information of parameter
sourceInfoWriter.writeUnknownDebugEnd();
writer.writeClose();
}
@CheckForNull
private LocalVariableNode getLocalVariableNode(@Nonnegative int localIdx) {
assert localIdx >= 0;
if (options.isEmitDebugInfo() && currentMethod.localVariables != null) {
for (LocalVariableNode lvn : currentMethod.localVariables) {
int startScope = currentMethod.instructions.indexOf(lvn.start) - 1;
int endScope = currentMethod.instructions.indexOf(lvn.end);
if (lvn.index == localIdx && currentPc >= startScope && currentPc <= endScope) {
assert lvn.desc != null;
return lvn;
}
}
}
return null;
}
private void removeDeadCode() {
Frame<BasicValue>[] frames = analyzer.getFrames();
AbstractInsnNode[] insns = currentMethod.instructions.toArray();
for (int i = 0; i < frames.length; ++i) {
if (frames[i] == null) {
// do not remove labels, they may be used as local scope bounds or catch bounds.
AbstractInsnNode insn = insns[i];
if (insn instanceof LabelNode) {
continue;
}
currentMethod.instructions.remove(insn);
}
}
}
private boolean isBooleanAssignIssue(@Nonnull Variable lhs, @Nonnull Variable rhs) {
return isBooleanAssignIssue(lhs.getType(), rhs.getType());
}
private boolean isBooleanAssignIssue(@Nonnull Type lhs, @Nonnull Type rhs) {
return (lhs == Type.BOOLEAN_TYPE && rhs != Type.BOOLEAN_TYPE)
|| (rhs == Type.BOOLEAN_TYPE && lhs != Type.BOOLEAN_TYPE);
}
@Nonnull
private Variable getLocalVariable(@Nonnull Frame<BasicValue> frame, @Nonnegative int localIdx) {
BasicValue bv = frame.getLocal(localIdx);
assert bv != BasicValue.UNINITIALIZED_VALUE;
Variable v;
String id = getUnnamedLocalId(localIdx, bv.getType());
String localName = id;
Type localType = typeToUntyped(bv.getType());
v = getVariableWithLocalIndex(localIdx, id, localName, localType, null);
return v;
}
@Nonnull
private String getUnnamedParameterId(@Nonnegative int localIdx, @Nonnull Type localType) {
return "-p_" + localIdx + "_" + stringLegalizer(localType.getDescriptor());
}
@Nonnull
private String getUnnamedLocalId(@Nonnegative int localIdx, @Nonnull Type localType) {
return "-l_" + localIdx + "_" + typeToUntypedDesc(localType);
}
@Nonnull
private String getNamedLocalId(@Nonnull LocalVariableNode lvn) {
return (lvn.name + "_" + lvn.index + "_" + (lvn.signature != null ?
stringLegalizer(lvn.signature) : stringLegalizer(lvn.desc)));
}
@Nonnull
private Variable getStackVariable(@Nonnull Frame<BasicValue> frame, int stackIdx){
int stackHeight = frame.getStackSize() + stackIdx;
BasicValue bv = frame.getStack(stackHeight);
assert bv != BasicValue.UNINITIALIZED_VALUE;
String id = "-s_" + stackHeight + "_" + typeToUntypedDesc(bv.getType());
Variable variable = getVariable(id, id, typeToUntyped(bv.getType()), null);
return variable;
}
@Nonnull
private Variable getVariable(@Nonnull String id, @Nonnull String name, @Nonnull Type type,
@CheckForNull String signature) {
Variable var = nameToVar.get(id);
if (var == null) {
var = new Variable(id, name, type, signature);
nameToVar.put(id, var);
}
return var;
}
@Nonnull
private Variable getVariableWithLocalIndex(@Nonnegative int localIdx, @Nonnull String id,
@Nonnull String name, @Nonnull Type type, @CheckForNull String signature) {
Variable var = nameToVar.get(id);
if (var == null) {
// All variables which do no have LocalVariableNode are marked synthetic
var =
new Variable(id, name, type, signature, localIdx, getLocalVariableNode(localIdx) == null);
nameToVar.put(id, var);
}
return var;
}
@Nonnull
private String typeToUntypedDesc(@Nonnull Type type) {
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return "R";
} else if (type.getSort() == Type.BOOLEAN || type.getSort() == Type.BYTE
|| type.getSort() == Type.CHAR || type.getSort() == Type.SHORT
|| type.getSort() == Type.INT) {
return Type.INT_TYPE.getDescriptor();
}
return type.getDescriptor();
}
@Nonnull
private Type typeToUntyped(@Nonnull Type type) {
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return Type.getType("Ljava/lang/Object;");
} else if (type.getSort() == Type.BOOLEAN || type.getSort() == Type.BYTE
|| type.getSort() == Type.CHAR || type.getSort() == Type.SHORT
|| type.getSort() == Type.INT) {
return Type.INT_TYPE;
}
return type;
}
@Nonnull
private String stringLegalizer(@Nonnull String str) {
return str.replace('/', '_').replace(';', '_').replace('<', '_').replace('>', '_')
.replace(':', '_').replace('[', '_');
}
}