Fork of the Fernflower decompiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fernflower/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java

488 lines
16 KiB

// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.java.decompiler.modules.decompiler.exps;
import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.main.ClassWriter;
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor;
import org.jetbrains.java.decompiler.modules.decompiler.vars.CheckTypesResult;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair;
import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.gen.VarType;
import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor;
import org.jetbrains.java.decompiler.struct.gen.generics.GenericMain;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.ListStack;
import org.jetbrains.java.decompiler.util.TextBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class NewExprent extends Exprent {
private InvocationExprent constructor;
private final VarType newType;
private final List<Exprent> lstDims;
private List<Exprent> lstArrayElements = new ArrayList<>();
private boolean directArrayInit;
private boolean isVarArgParam;
private boolean anonymous;
private boolean lambda;
private boolean enumConst;
public NewExprent(VarType newType, ListStack<Exprent> stack, int arrayDim, Set<Integer> bytecodeOffsets) {
this(newType, getDimensions(arrayDim, stack), bytecodeOffsets);
}
public NewExprent(VarType newType, List<Exprent> lstDims, Set<Integer> bytecodeOffsets) {
super(EXPRENT_NEW);
this.newType = newType;
this.lstDims = lstDims;
anonymous = false;
lambda = false;
if (newType.type == CodeConstants.TYPE_OBJECT && newType.arrayDim == 0) {
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(newType.value);
if (node != null && (node.type == ClassNode.CLASS_ANONYMOUS || node.type == ClassNode.CLASS_LAMBDA)) {
anonymous = true;
if (node.type == ClassNode.CLASS_LAMBDA) {
lambda = true;
}
}
}
addBytecodeOffsets(bytecodeOffsets);
}
private static List<Exprent> getDimensions(int arrayDim, ListStack<Exprent> stack) {
List<Exprent> lstDims = new ArrayList<>();
for (int i = 0; i < arrayDim; i++) {
lstDims.add(0, stack.pop());
}
return lstDims;
}
@Override
public VarType getExprType() {
return anonymous ? DecompilerContext.getClassProcessor().getMapRootClasses().get(newType.value).anonymousClassType : newType;
}
@Override
public CheckTypesResult checkExprTypeBounds() {
CheckTypesResult result = new CheckTypesResult();
if (newType.arrayDim != 0) {
for (Exprent dim : lstDims) {
result.addMinTypeExprent(dim, VarType.VARTYPE_BYTECHAR);
result.addMaxTypeExprent(dim, VarType.VARTYPE_INT);
}
if (newType.arrayDim == 1) {
VarType leftType = newType.decreaseArrayDim();
for (Exprent element : lstArrayElements) {
result.addMinTypeExprent(element, VarType.getMinTypeInFamily(leftType.typeFamily));
result.addMaxTypeExprent(element, leftType);
}
}
}
else if (constructor != null) {
return constructor.checkExprTypeBounds();
}
return result;
}
@Override
public List<Exprent> getAllExprents() {
List<Exprent> lst = new ArrayList<>();
if (newType.arrayDim != 0) {
lst.addAll(lstDims);
lst.addAll(lstArrayElements);
}
else if (constructor != null) {
Exprent constructor = this.constructor.getInstance();
if (constructor != null) { // should be true only for a lambda expression with a virtual content method
lst.add(constructor);
}
lst.addAll(this.constructor.getLstParameters());
}
return lst;
}
@Override
public Exprent copy() {
List<Exprent> lst = new ArrayList<>();
for (Exprent expr : lstDims) {
lst.add(expr.copy());
}
NewExprent ret = new NewExprent(newType, lst, bytecode);
ret.setConstructor(constructor == null ? null : (InvocationExprent)constructor.copy());
ret.setLstArrayElements(lstArrayElements);
ret.setDirectArrayInit(directArrayInit);
ret.setAnonymous(anonymous);
ret.setEnumConst(enumConst);
return ret;
}
@Override
public int getPrecedence() {
return 1; // precedence of new
}
@Override
public TextBuffer toJava(int indent, BytecodeMappingTracer tracer) {
TextBuffer buf = new TextBuffer();
if (anonymous) {
ClassNode child = DecompilerContext.getClassProcessor().getMapRootClasses().get(newType.value);
boolean selfReference = DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE) == child;
// IDEA-204310 - avoid backtracking later on for lambdas (causes spurious imports)
if (!enumConst && (!lambda || DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS))) {
String enclosing = null;
if (!lambda && constructor != null) {
enclosing = getQualifiedNewInstance(child.anonymousClassType.value, constructor.getLstParameters(), indent, tracer);
if (enclosing != null) {
buf.append(enclosing).append('.');
}
}
buf.append("new ");
if (selfReference) {
buf.append("<anonymous constructor>");
} else {
String typename = ExprProcessor.getCastTypeName(child.anonymousClassType);
if (enclosing != null) {
ClassNode anonymousNode = DecompilerContext.getClassProcessor().getMapRootClasses().get(child.anonymousClassType.value);
if (anonymousNode != null) {
typename = anonymousNode.simpleName;
}
else {
typename = typename.substring(typename.lastIndexOf('.') + 1);
}
}
GenericClassDescriptor descriptor = ClassWriter.getGenericClassDescriptor(child.classStruct);
if (descriptor != null) {
if (descriptor.superinterfaces.isEmpty()) {
buf.append(GenericMain.getGenericCastTypeName(descriptor.superclass));
}
else {
if (descriptor.superinterfaces.size() > 1 && !lambda) {
DecompilerContext.getLogger().writeMessage("Inconsistent anonymous class signature: " + child.classStruct.qualifiedName,
IFernflowerLogger.Severity.WARN);
}
buf.append(GenericMain.getGenericCastTypeName(descriptor.superinterfaces.get(0)));
}
}
else {
buf.append(typename);
}
}
}
buf.append('(');
if (!lambda && constructor != null) {
List<Exprent> parameters = constructor.getLstParameters();
List<VarVersionPair> mask = child.getWrapper().getMethodWrapper(CodeConstants.INIT_NAME, constructor.getStringDescriptor()).synthParameters;
if (mask == null) {
InvocationExprent superCall = child.superInvocation;
mask = ExprUtil.getSyntheticParametersMask(superCall.getClassname(), superCall.getStringDescriptor(), parameters.size());
}
int start = enumConst ? 2 : 0;
boolean firstParam = true;
for (int i = start; i < parameters.size(); i++) {
if (mask == null || mask.get(i) == null) {
if (!firstParam) {
buf.append(", ");
}
ExprProcessor.getCastedExprent(parameters.get(i), constructor.getDescriptor().params[i], buf, indent, true, tracer);
firstParam = false;
}
}
}
buf.append(')');
if (enumConst && buf.length() == 2) {
buf.setLength(0);
}
if (lambda) {
if (!DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS)) {
buf.setLength(0); // remove the usual 'new <class>()', it will be replaced with lambda style '() ->'
}
Exprent methodObject = constructor == null ? null : constructor.getInstance();
TextBuffer clsBuf = new TextBuffer();
new ClassWriter().classLambdaToJava(child, clsBuf, methodObject, indent, tracer);
buf.append(clsBuf);
tracer.incrementCurrentSourceLine(clsBuf.countLines());
}
else if (!selfReference) {
TextBuffer clsBuf = new TextBuffer();
new ClassWriter().classToJava(child, clsBuf, indent, tracer);
buf.append(clsBuf);
tracer.incrementCurrentSourceLine(clsBuf.countLines());
}
}
else if (directArrayInit) {
VarType leftType = newType.decreaseArrayDim();
buf.append('{');
for (int i = 0; i < lstArrayElements.size(); i++) {
if (i > 0) {
buf.append(", ");
}
ExprProcessor.getCastedExprent(lstArrayElements.get(i), leftType, buf, indent, false, tracer);
}
buf.append('}');
}
else if (newType.arrayDim == 0) {
if (!enumConst) {
String enclosing = null;
if (constructor != null) {
enclosing = getQualifiedNewInstance(newType.value, constructor.getLstParameters(), indent, tracer);
if (enclosing != null) {
buf.append(enclosing).append('.');
}
}
buf.append("new ");
String typename = ExprProcessor.getTypeName(newType);
if (enclosing != null) {
ClassNode newNode = DecompilerContext.getClassProcessor().getMapRootClasses().get(newType.value);
if (newNode != null) {
typename = newNode.simpleName;
}
else {
typename = typename.substring(typename.lastIndexOf('.') + 1);
}
}
buf.append(typename);
}
if (constructor != null) {
List<Exprent> parameters = constructor.getLstParameters();
List<VarVersionPair> mask = ExprUtil.getSyntheticParametersMask(constructor.getClassname(), constructor.getStringDescriptor(), parameters.size());
int start = enumConst ? 2 : 0;
if (!enumConst || start < parameters.size()) {
buf.append('(');
boolean firstParam = true;
for (int i = start; i < parameters.size(); i++) {
if (mask == null || mask.get(i) == null) {
Exprent expr = parameters.get(i);
VarType leftType = constructor.getDescriptor().params[i];
if (i == parameters.size() - 1 && expr.getExprType() == VarType.VARTYPE_NULL && probablySyntheticParameter(leftType.value)) {
break; // skip last parameter of synthetic constructor call
}
if (!firstParam) {
buf.append(", ");
}
ExprProcessor.getCastedExprent(expr, leftType, buf, indent, true, false, true, true, tracer);
firstParam = false;
}
}
buf.append(')');
}
}
}
else if (isVarArgParam) {
// just print the array elements
VarType leftType = newType.decreaseArrayDim();
for (int i = 0; i < lstArrayElements.size(); i++) {
if (i > 0) {
buf.append(", ");
}
// new String[][]{{"abc"}, {"DEF"}} => new String[]{"abc"}, new String[]{"DEF"}
Exprent element = lstArrayElements.get(i);
if (element.type == EXPRENT_NEW) {
((NewExprent) element).setDirectArrayInit(false);
}
ExprProcessor.getCastedExprent(element, leftType, buf, indent, false, tracer);
}
// if there is just one element of Object[] type it needs to be casted to resolve ambiguity
if (lstArrayElements.size() == 1) {
VarType elementType = lstArrayElements.get(0).getExprType();
if (elementType.type == CodeConstants.TYPE_OBJECT && elementType.value.equals("java/lang/Object") && elementType.arrayDim >= 1) {
buf.prepend("(Object)");
}
}
}
else {
buf.append("new ").append(ExprProcessor.getTypeName(newType));
if (lstArrayElements.isEmpty()) {
for (int i = 0; i < newType.arrayDim; i++) {
buf.append('[');
if (i < lstDims.size()) {
buf.append(lstDims.get(i).toJava(indent, tracer));
}
buf.append(']');
}
}
else {
for (int i = 0; i < newType.arrayDim; i++) {
buf.append("[]");
}
VarType leftType = newType.decreaseArrayDim();
buf.append('{');
for (int i = 0; i < lstArrayElements.size(); i++) {
if (i > 0) {
buf.append(", ");
}
ExprProcessor.getCastedExprent(lstArrayElements.get(i), leftType, buf, indent, false, tracer);
}
buf.append('}');
}
}
return buf;
}
private static boolean probablySyntheticParameter(String className) {
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(className);
return node != null && node.type == ClassNode.CLASS_ANONYMOUS;
}
private static String getQualifiedNewInstance(String classname, List<Exprent> lstParams, int indent, BytecodeMappingTracer tracer) {
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(classname);
if (node != null && node.type != ClassNode.CLASS_ROOT && node.type != ClassNode.CLASS_LOCAL
&& (node.access & CodeConstants.ACC_STATIC) == 0) {
if (!lstParams.isEmpty()) {
Exprent enclosing = lstParams.get(0);
boolean isQualifiedNew = false;
if (enclosing.type == Exprent.EXPRENT_VAR) {
VarExprent varEnclosing = (VarExprent)enclosing;
StructClass current_class = ((ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE)).classStruct;
String this_classname = varEnclosing.getProcessor().getThisVars().get(new VarVersionPair(varEnclosing));
if (!current_class.qualifiedName.equals(this_classname)) {
isQualifiedNew = true;
}
}
else {
isQualifiedNew = true;
}
if (isQualifiedNew) {
return enclosing.toJava(indent, tracer).toString();
}
}
}
return null;
}
@Override
public void replaceExprent(Exprent oldExpr, Exprent newExpr) {
if (oldExpr == constructor) {
constructor = (InvocationExprent)newExpr;
}
if (constructor != null) {
constructor.replaceExprent(oldExpr, newExpr);
}
for (int i = 0; i < lstDims.size(); i++) {
if (oldExpr == lstDims.get(i)) {
lstDims.set(i, newExpr);
}
}
for (int i = 0; i < lstArrayElements.size(); i++) {
if (oldExpr == lstArrayElements.get(i)) {
lstArrayElements.set(i, newExpr);
}
}
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof NewExprent)) return false;
NewExprent ne = (NewExprent)o;
return InterpreterUtil.equalObjects(newType, ne.getNewType()) &&
InterpreterUtil.equalLists(lstDims, ne.getLstDims()) &&
InterpreterUtil.equalObjects(constructor, ne.getConstructor()) &&
directArrayInit == ne.directArrayInit &&
InterpreterUtil.equalLists(lstArrayElements, ne.getLstArrayElements());
}
public InvocationExprent getConstructor() {
return constructor;
}
public void setConstructor(InvocationExprent constructor) {
this.constructor = constructor;
}
public List<Exprent> getLstDims() {
return lstDims;
}
public VarType getNewType() {
return newType;
}
public List<Exprent> getLstArrayElements() {
return lstArrayElements;
}
public void setLstArrayElements(List<Exprent> lstArrayElements) {
this.lstArrayElements = lstArrayElements;
}
public void setDirectArrayInit(boolean directArrayInit) {
this.directArrayInit = directArrayInit;
}
public void setVarArgParam(boolean isVarArgParam) {
this.isVarArgParam = isVarArgParam;
}
public boolean isLambda() {
return lambda;
}
public boolean isAnonymous() {
return anonymous;
}
public void setAnonymous(boolean anonymous) {
this.anonymous = anonymous;
}
public void setEnumConst(boolean enumConst) {
this.enumConst = enumConst;
}
}