blob: c40062ca029e816da970c82988b05d9e99c740db [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
import static org.objectweb.asm.Opcodes.F_NEW;
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.frame.SingleFrameType;
import com.android.tools.r8.cf.code.frame.WideFrameType;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.conversion.CfSourceCode;
import com.android.tools.r8.ir.conversion.CfState;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
import com.android.tools.r8.utils.IntObjConsumer;
import com.android.tools.r8.utils.collections.ImmutableDeque;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.function.Consumer;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class CfFrame extends CfInstruction implements Cloneable {
public static final Int2ObjectSortedMap<FrameType> EMPTY_LOCALS = Int2ObjectSortedMaps.emptyMap();
public static final Deque<FrameType> EMPTY_STACK = ImmutableDeque.of();
public abstract static class FrameType {
public static DoubleFrameType doubleType() {
return DoubleFrameType.SINGLETON;
}
public static IntFrameType intType() {
return IntFrameType.SINGLETON;
}
public static LongFrameType longType() {
return LongFrameType.SINGLETON;
}
public static FrameType initialized(DexType type) {
if (type.isPrimitiveType()) {
if (type.isWideType()) {
return type.isDoubleType() ? doubleType() : longType();
} else if (type.isIntType()) {
return intType();
}
}
return new SingleInitializedType(type);
}
public static FrameType uninitializedNew(CfLabel label, DexType typeToInitialize) {
return new UninitializedNew(label, typeToInitialize);
}
public static FrameType uninitializedThis() {
return UninitializedThis.SINGLETON;
}
public static OneWord oneWord() {
return OneWord.SINGLETON;
}
public static TwoWord twoWord() {
return TwoWord.SINGLETON;
}
public FrameType asFrameType() {
return this;
}
abstract Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
public boolean isDouble() {
return false;
}
public boolean isInt() {
return false;
}
public boolean isLong() {
return false;
}
public boolean isNullType() {
return false;
}
public boolean isObject() {
return false;
}
public DexType getObjectType(ProgramMethod context) {
assert false : "Unexpected use of getObjectType() for non-object FrameType";
return null;
}
public boolean isPrimitive() {
return false;
}
public final boolean isSingle() {
return !isWide();
}
public SingleFrameType asSingle() {
return null;
}
public SingleInitializedType asSingleInitializedType() {
return null;
}
public boolean isWide() {
return false;
}
public WideFrameType asWide() {
return null;
}
public boolean isUninitializedNew() {
return false;
}
public boolean isUninitializedObject() {
return false;
}
public CfLabel getUninitializedLabel() {
return null;
}
public boolean isUninitializedThis() {
return false;
}
public boolean isInitialized() {
return false;
}
public boolean isSingleInitialized() {
return false;
}
public DexType getInitializedType(DexItemFactory dexItemFactory) {
return null;
}
public DexType getUninitializedNewType() {
return null;
}
public boolean isOneWord() {
return false;
}
public boolean isSpecific() {
return true;
}
public boolean isTwoWord() {
return false;
}
FrameType map(java.util.function.Function<DexType, DexType> func) {
if (isObject()) {
if (isInitialized()) {
DexType type = asSingleInitializedType().getInitializedType();
DexType newType = func.apply(type);
if (type != newType) {
return initialized(newType);
}
}
if (isUninitializedNew()) {
DexType type = getUninitializedNewType();
DexType newType = func.apply(type);
if (type != newType) {
return uninitializedNew(getUninitializedLabel(), newType);
}
}
}
return this;
}
private FrameType() {}
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
public static FrameType fromPreciseMemberType(MemberType memberType, DexItemFactory factory) {
assert memberType.isPrecise();
switch (memberType) {
case OBJECT:
return FrameType.initialized(factory.objectType);
case BOOLEAN_OR_BYTE:
return FrameType.initialized(factory.intType);
case CHAR:
return FrameType.initialized(factory.intType);
case SHORT:
return FrameType.initialized(factory.intType);
case INT:
return FrameType.initialized(factory.intType);
case FLOAT:
return FrameType.initialized(factory.floatType);
case LONG:
return FrameType.longType();
case DOUBLE:
return FrameType.doubleType();
default:
throw new Unreachable("Unexpected MemberType: " + memberType);
}
}
public static FrameType fromNumericType(NumericType numericType, DexItemFactory factory) {
return FrameType.initialized(numericType.toDexType(factory));
}
}
public abstract static class SingletonFrameType extends FrameType {
@Override
public final boolean equals(Object obj) {
return this == obj;
}
@Override
public final int hashCode() {
return System.identityHashCode(this);
}
}
@Override
public boolean isFrame() {
return true;
}
@Override
public CfFrame asFrame() {
return this;
}
@Override
public int getCompareToId() {
return CfCompareHelper.FRAME_COMPARE_ID;
}
@Override
public int internalAcceptCompareTo(
CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
// The frame should be determined by the code so it should for equal iff the code is equal.
// Thus we just require the frame to be in place.
return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
}
public abstract static class SinglePrimitiveType extends SingletonFrameType
implements SingleFrameType {
@Override
public boolean isInitialized() {
return true;
}
@Override
public boolean isPrimitive() {
return true;
}
@Override
public SingleFrameType asSingle() {
return this;
}
}
public static class IntFrameType extends SinglePrimitiveType {
private static final IntFrameType SINGLETON = new IntFrameType();
private IntFrameType() {}
@Override
public DexType getInitializedType(DexItemFactory dexItemFactory) {
return dexItemFactory.intType;
}
@Override
public boolean isInt() {
return true;
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return Opcodes.INTEGER;
}
@Override
public SingleFrameType join(SingleFrameType frameType) {
if (this == frameType) {
return this;
}
if (frameType.isOneWord() || frameType.isUninitializedObject()) {
return oneWord();
}
assert frameType.isInitialized();
return CfAssignability.hasIntVerificationType(
frameType.asSingleInitializedType().getInitializedType())
? this
: oneWord();
}
@Override
public String toString() {
return "int";
}
}
public static class SingleInitializedType extends FrameType implements SingleFrameType {
private final DexType type;
private SingleInitializedType(DexType type) {
assert type != null;
assert !type.isIntType();
this.type = type;
}
@Override
public SingleInitializedType asSingleInitializedType() {
return this;
}
@Override
public SingleFrameType join(SingleFrameType frameType) {
if (equals(frameType)) {
return this;
}
if (frameType.isOneWord() || frameType.isUninitializedObject()) {
return oneWord();
}
assert frameType.isInitialized();
if (frameType.isPrimitive()) {
if (frameType.isInt()) {
return CfAssignability.hasIntVerificationType(type) ? frameType : oneWord();
}
// The rest of the primitives are still represented using SingleInitializedType.
DexType otherType = frameType.asSingleInitializedType().getInitializedType();
return CfAssignability.hasIntVerificationType(type)
&& CfAssignability.hasIntVerificationType(otherType)
? intType()
: oneWord();
}
DexType otherType = frameType.asSingleInitializedType().getInitializedType();
assert type != otherType;
if (type.isPrimitiveType()) {
return oneWord();
}
assert type.isReferenceType();
if (isNullType()) {
return otherType.isReferenceType() ? frameType : oneWord();
}
if (frameType.isNullType()) {
return this;
}
assert type.isArrayType() || type.isClassType();
assert otherType.isArrayType() || otherType.isClassType();
// TODO(b/214496607): Implement join of different reference types using class hierarchy.
throw new Unimplemented();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SingleInitializedType initializedType = (SingleInitializedType) obj;
return type == initializedType.type;
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public String toString() {
return "Initialized(" + type.toString() + ")";
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
DexType rewrittenType = graphLens.lookupType(type);
if (rewrittenType == DexItemFactory.nullValueType) {
return Opcodes.NULL;
}
switch (rewrittenType.toShorty()) {
case 'L':
return namingLens.lookupInternalName(rewrittenType);
case 'I':
return Opcodes.INTEGER;
case 'F':
return Opcodes.FLOAT;
case 'J':
return Opcodes.LONG;
case 'D':
return Opcodes.DOUBLE;
default:
throw new Unreachable("Unexpected value type: " + rewrittenType);
}
}
@Override
public SingleFrameType asSingle() {
return this;
}
@Override
public boolean isWide() {
return false;
}
@Override
public boolean isInitialized() {
return true;
}
@Override
public boolean isPrimitive() {
return type.isPrimitiveType();
}
@Override
public boolean isSingleInitialized() {
return true;
}
public DexType getInitializedType() {
return type;
}
@Override
public DexType getInitializedType(DexItemFactory dexItemFactory) {
return getInitializedType();
}
@Override
public boolean isNullType() {
return type.isNullValueType();
}
@Override
public boolean isObject() {
return type.isReferenceType();
}
@Override
public DexType getObjectType(ProgramMethod context) {
assert isObject() : "Unexpected use of getObjectType() for non-object FrameType";
return type;
}
}
public abstract static class WideInitializedType extends SingletonFrameType
implements WideFrameType {
@Override
public boolean isInitialized() {
return true;
}
@Override
public boolean isPrimitive() {
return true;
}
@Override
public boolean isWide() {
return true;
}
@Override
public WideFrameType asWide() {
return this;
}
@Override
public WideFrameType join(WideFrameType frameType) {
return this == frameType ? this : twoWord();
}
}
private static class DoubleFrameType extends WideInitializedType {
private static final DoubleFrameType SINGLETON = new DoubleFrameType();
private DoubleFrameType() {}
@Override
public boolean isDouble() {
return true;
}
@Override
public DexType getInitializedType(DexItemFactory dexItemFactory) {
return dexItemFactory.doubleType;
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return Opcodes.DOUBLE;
}
@Override
public String toString() {
return "double";
}
}
public static class LongFrameType extends WideInitializedType {
private static final LongFrameType SINGLETON = new LongFrameType();
private LongFrameType() {}
@Override
public boolean isLong() {
return true;
}
@Override
public DexType getInitializedType(DexItemFactory dexItemFactory) {
return dexItemFactory.longType;
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return Opcodes.LONG;
}
@Override
public String toString() {
return "long";
}
}
private static class UninitializedNew extends FrameType implements SingleFrameType {
private final CfLabel label;
private final DexType type;
private UninitializedNew(CfLabel label, DexType type) {
this.label = label;
this.type = type;
}
@Override
public SingleFrameType asSingle() {
return this;
}
@Override
public SingleFrameType join(SingleFrameType frameType) {
return equals(frameType) ? this : oneWord();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UninitializedNew uninitializedNew = (UninitializedNew) o;
return label == uninitializedNew.label && type == uninitializedNew.type;
}
@Override
public int hashCode() {
return Objects.hash(label, type);
}
@Override
public String toString() {
return "uninitialized new";
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return label.getLabel();
}
@Override
public boolean isObject() {
return true;
}
@Override
public DexType getObjectType(ProgramMethod context) {
return type;
}
@Override
public boolean isUninitializedNew() {
return true;
}
@Override
public boolean isUninitializedObject() {
return true;
}
@Override
public CfLabel getUninitializedLabel() {
return label;
}
@Override
public DexType getUninitializedNewType() {
return type;
}
}
private static class UninitializedThis extends SingletonFrameType implements SingleFrameType {
private static final UninitializedThis SINGLETON = new UninitializedThis();
private UninitializedThis() {}
@Override
public SingleFrameType asSingle() {
return this;
}
@Override
public SingleFrameType join(SingleFrameType frameType) {
if (this == frameType) {
return this;
}
return oneWord();
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return Opcodes.UNINITIALIZED_THIS;
}
@Override
public String toString() {
return "uninitialized this";
}
@Override
public boolean isObject() {
return true;
}
@Override
public DexType getObjectType(ProgramMethod context) {
return context.getHolderType();
}
@Override
public boolean isUninitializedObject() {
return true;
}
@Override
public boolean isUninitializedThis() {
return true;
}
}
private static class OneWord extends SingletonFrameType implements SingleFrameType {
private static final OneWord SINGLETON = new OneWord();
private OneWord() {}
@Override
public boolean isOneWord() {
return true;
}
@Override
public boolean isSpecific() {
return false;
}
@Override
public SingleFrameType asSingle() {
return this;
}
@Override
public SingleFrameType join(SingleFrameType frameType) {
return this;
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return Opcodes.TOP;
}
@Override
public String toString() {
return "oneword";
}
}
private static class TwoWord extends SingletonFrameType implements WideFrameType {
private static final TwoWord SINGLETON = new TwoWord();
private TwoWord() {}
@Override
public boolean isSpecific() {
return false;
}
@Override
public boolean isTwoWord() {
return true;
}
@Override
public boolean isWide() {
return true;
}
@Override
public WideFrameType asWide() {
return this;
}
@Override
public WideFrameType join(WideFrameType frameType) {
// The join of wide with one of {double, long, wide} is wide.
return this;
}
@Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
throw new Unreachable("Should only be used for verification");
}
@Override
public String toString() {
return "twoword";
}
}
private final Int2ObjectSortedMap<FrameType> locals;
private final Deque<FrameType> stack;
// Constructor used by CfCodePrinter.
public CfFrame() {
this(EMPTY_LOCALS, EMPTY_STACK);
}
// Constructor used by CfCodePrinter.
public CfFrame(Int2ObjectAVLTreeMap<FrameType> locals) {
this((Int2ObjectSortedMap<FrameType>) locals, EMPTY_STACK);
assert !locals.isEmpty() || locals == EMPTY_LOCALS : "Should use EMPTY_LOCALS instead";
}
// Constructor used by CfCodePrinter.
public CfFrame(Deque<FrameType> stack) {
this(EMPTY_LOCALS, stack);
assert !stack.isEmpty() || stack == EMPTY_STACK : "Should use EMPTY_STACK instead";
}
// Constructor used by CfCodePrinter.
public CfFrame(Int2ObjectAVLTreeMap<FrameType> locals, Deque<FrameType> stack) {
this((Int2ObjectSortedMap<FrameType>) locals, stack);
assert !locals.isEmpty() || locals == EMPTY_LOCALS : "Should use EMPTY_LOCALS instead";
assert !stack.isEmpty() || stack == EMPTY_STACK : "Should use EMPTY_STACK instead";
}
// Internal constructor that does not require locals to be of the type Int2ObjectAVLTreeMap.
private CfFrame(Int2ObjectSortedMap<FrameType> locals, Deque<FrameType> stack) {
assert locals.values().stream().allMatch(Objects::nonNull);
assert stack.stream().allMatch(Objects::nonNull);
this.locals = locals;
this.stack = stack;
}
public static Builder builder() {
return new Builder();
}
@Override
public CfFrame clone() {
return new CfFrame(locals, stack);
}
public CfFrame mutableCopy() {
return new CfFrame(
(Int2ObjectSortedMap<FrameType>) new Int2ObjectAVLTreeMap<>(locals),
new ArrayDeque<>(stack));
}
public void forEachLocal(IntObjConsumer<FrameType> consumer) {
for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
consumer.accept(entry.getIntKey(), entry.getValue());
}
}
public Int2ObjectSortedMap<FrameType> getLocals() {
return locals;
}
public Deque<FrameType> getStack() {
return stack;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CfFrame frame = (CfFrame) obj;
return locals.equals(frame.locals) && Iterables.elementsEqual(stack, frame.stack);
}
@Override
public int hashCode() {
// Generates a hash that is identical to Objects.hash(locals, stack[0], ..., stack[n]).
int result = 31 + locals.hashCode();
for (FrameType frameType : stack) {
result = 31 * result + frameType.hashCode();
}
return result;
}
@Override
public void write(
AppView<?> appView,
ProgramMethod context,
DexItemFactory dexItemFactory,
GraphLens graphLens,
InitClassLens initClassLens,
NamingLens namingLens,
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
int stackCount = computeStackCount();
Object[] stackTypes = computeStackTypes(stackCount, graphLens, namingLens);
int localsCount = computeLocalsCount();
Object[] localsTypes = computeLocalsTypes(localsCount, graphLens, namingLens);
visitor.visitFrame(F_NEW, localsCount, localsTypes, stackCount, stackTypes);
}
@Override
public int bytecodeSizeUpperBound() {
return 0;
}
private int computeStackCount() {
return stack.size();
}
public int computeStackSize() {
int size = 0;
for (FrameType frameType : stack) {
size += frameType.isWide() ? 2 : 1;
}
return size;
}
private Object[] computeStackTypes(int stackCount, GraphLens graphLens, NamingLens namingLens) {
assert stackCount == stack.size();
if (stackCount == 0) {
return null;
}
Object[] stackTypes = new Object[stackCount];
int index = 0;
for (FrameType frameType : stack) {
stackTypes[index++] = frameType.getTypeOpcode(graphLens, namingLens);
}
return stackTypes;
}
private int computeLocalsCount() {
if (locals.isEmpty()) {
return 0;
}
// Compute the size of locals. Absent indexes are denoted by a single-width element (ie, TOP).
int maxRegister = locals.lastIntKey();
int localsCount = 0;
for (int i = 0; i <= maxRegister; i++) {
localsCount++;
FrameType type = locals.get(i);
if (type != null && type.isWide()) {
i++;
}
}
return localsCount;
}
private Object[] computeLocalsTypes(int localsCount, GraphLens graphLens, NamingLens namingLens) {
if (localsCount == 0) {
return null;
}
int maxRegister = locals.lastIntKey();
Object[] localsTypes = new Object[localsCount];
int localIndex = 0;
for (int i = 0; i <= maxRegister; i++) {
FrameType type = locals.get(i);
localsTypes[localIndex++] =
type == null ? Opcodes.TOP : type.getTypeOpcode(graphLens, namingLens);
if (type != null && type.isWide()) {
i++;
}
}
return localsTypes;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
@Override
public void print(CfPrinter printer) {
printer.print(this);
}
@Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
code.setStateFromFrame(this);
}
@Override
public boolean emitsIR() {
return false;
}
@Override
public ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
@Override
public void evaluate(
CfFrameVerificationHelper frameBuilder,
DexMethod context,
AppView<?> appView,
DexItemFactory dexItemFactory) {
frameBuilder.checkFrameAndSet(this);
}
@Override
public CfFrameState evaluate(
CfFrameState frame,
ProgramMethod context,
AppView<?> appView,
DexItemFactory dexItemFactory) {
return frame.check(appView, this);
}
public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
if (uninitializedType.isInitialized()) {
throw CfCodeStackMapValidatingException.error(
"Cannot instantiate already instantiated type " + uninitializedType);
}
CfFrame.Builder builder = CfFrame.builder().allocateStack(stack.size());
forEachLocal(
(localIndex, frameType) ->
builder.store(
localIndex, getInitializedFrameType(uninitializedType, frameType, initType)));
for (FrameType frameType : stack) {
builder.push(getInitializedFrameType(uninitializedType, frameType, initType));
}
return builder.build();
}
public static FrameType getInitializedFrameType(
FrameType unInit, FrameType other, DexType newType) {
assert !unInit.isInitialized();
if (other.isInitialized()) {
return other;
}
if (unInit.isUninitializedThis() && other.isUninitializedThis()) {
return FrameType.initialized(newType);
}
if (unInit.isUninitializedNew()
&& other.isUninitializedNew()
&& unInit.getUninitializedLabel() == other.getUninitializedLabel()) {
return FrameType.initialized(newType);
}
return other;
}
public CfFrame map(java.util.function.Function<DexType, DexType> func) {
boolean mapped = false;
for (int var : locals.keySet()) {
CfFrame.FrameType originalType = locals.get(var);
CfFrame.FrameType mappedType = originalType.map(func);
mapped = originalType != mappedType;
if (mapped) {
break;
}
}
if (!mapped) {
for (FrameType frameType : stack) {
CfFrame.FrameType mappedType = frameType.map(func);
mapped = frameType != mappedType;
if (mapped) {
break;
}
}
}
if (!mapped) {
return this;
}
Builder builder = builder();
for (Int2ObjectMap.Entry<FrameType> entry : locals.int2ObjectEntrySet()) {
builder.store(entry.getIntKey(), entry.getValue().map(func));
}
for (FrameType frameType : stack) {
builder.push(frameType.map(func));
}
return builder.build();
}
public static class Builder {
private Int2ObjectSortedMap<FrameType> locals = EMPTY_LOCALS;
private Deque<FrameType> stack = EMPTY_STACK;
private boolean hasIncompleteUninitializedNew = false;
private boolean seenStore = false;
public Builder allocateStack(int size) {
assert stack == EMPTY_STACK;
if (size > 0) {
stack = new ArrayDeque<>(size);
}
return this;
}
public Builder appendLocal(FrameType frameType) {
// Mixing appendLocal() and store() is somewhat error prone. Catch it if we ever do it.
assert !seenStore;
int localIndex = locals.size();
return internalStore(localIndex, frameType);
}
public Builder apply(Consumer<Builder> consumer) {
consumer.accept(this);
return this;
}
public boolean hasIncompleteUninitializedNew() {
return hasIncompleteUninitializedNew;
}
public Builder setHasIncompleteUninitializedNew() {
hasIncompleteUninitializedNew = true;
return this;
}
public boolean hasLocal(int localIndex) {
return locals.containsKey(localIndex);
}
public FrameType getLocal(int localIndex) {
assert hasLocal(localIndex);
return locals.get(localIndex);
}
public Builder push(FrameType frameType) {
ensureMutableStack();
stack.addLast(frameType);
return this;
}
public Builder setLocals(Int2ObjectSortedMap<FrameType> locals) {
this.locals = locals;
return this;
}
public Builder setStack(Deque<FrameType> stack) {
this.stack = stack;
return this;
}
public Builder store(int localIndex, FrameType frameType) {
seenStore = true;
return internalStore(localIndex, frameType);
}
private Builder internalStore(int localIndex, FrameType frameType) {
ensureMutableLocals();
locals.put(localIndex, frameType);
if (frameType.isWide()) {
locals.put(localIndex + 1, frameType);
}
return this;
}
public CfFrame build() {
return new CfFrame(locals, stack);
}
public CfFrame buildMutable() {
ensureMutableLocals();
ensureMutableStack();
return build();
}
private void ensureMutableLocals() {
if (locals == EMPTY_LOCALS) {
locals = new Int2ObjectAVLTreeMap<>();
}
}
private void ensureMutableStack() {
if (stack == EMPTY_STACK) {
stack = new ArrayDeque<>();
}
}
}
}