Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe

if (root.hasSwitch()) {
boolean changed = false;
if (SwitchPatternMatchProcessor.hasPatternMatch(root) && SwitchPatternMatchProcessor.processPatternMatching(root)) {
if (SwitchPatternMatchProcessor.hasPatternMatch(root) && SwitchPatternMatchProcessor.processPatternMatching(root, cl)) {
decompileRecord.add("ProcessSwitchPatternMatch", root);
changed = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class CondyHelper {

// TODO: handle other bootstraps (invoke, explicitCast)
private static final String CONSTANT_BOOTSTRAPS_CLASS = "java/lang/invoke/ConstantBootstraps";
private static final String CONSTANT_ENUMDESC_CLASS = "java/lang/Enum$EnumDesc";
private static final String CONSTANT_CLASSDESC_CLASS = "java/lang/constant/ClassDesc";

// converts a condy exprent into an equivalent "normal java" exprent
public static Exprent simplifyCondy(InvocationExprent condyExpr) {
Expand All @@ -34,75 +36,124 @@ public static Exprent simplifyCondy(InvocationExprent condyExpr) {
}

LinkConstant method = condyExpr.getBootstrapMethod();
if (!CONSTANT_BOOTSTRAPS_CLASS.equals(method.classname)) {
return condyExpr;
}
Exprent result = simplifyCondy(method, condyExpr.getName(), condyExpr.getExprType(), condyExpr.getBootstrapArguments());
return result != null ? result : condyExpr;
}

public static Exprent simplifyCondy(LinkConstant method, String name, VarType type, List<PooledConstant> constArgs) {
return switch (method.classname) {
case CONSTANT_BOOTSTRAPS_CLASS -> simplifyConstantBootstraps(method, name, type, constArgs);
case CONSTANT_ENUMDESC_CLASS -> simplifyEnumDesc(method, name, type, constArgs);
case CONSTANT_CLASSDESC_CLASS -> simplifyClassDesc(method, name, type, constArgs);
default -> null;
};
}

private static Exprent simplifyConstantBootstraps(LinkConstant method, String name, VarType type, List<PooledConstant> constArgs) {
switch (method.elementname) {
case "nullConstant": // -> null
// TODO: include target type?
return new ConstExprent(VarType.VARTYPE_NULL, null, null).setWasCondy(true);
case "primitiveClass": // -> int.class
String desc = condyExpr.getName();
String desc = name;
// the name of the constant is the descriptor of the primitive type, check that its valid
if (desc.length() != 1 || !("ZCBSIJFDV".contains(desc))) {
break;
}
VarType type = new VarType(desc, false);
return new ConstExprent(VarType.VARTYPE_CLASS, ExprProcessor.getCastTypeName(type), null).setWasCondy(true);
VarType primitiveType = new VarType(desc, false);
return new ConstExprent(VarType.VARTYPE_CLASS, ExprProcessor.getCastTypeName(primitiveType), null).setWasCondy(true);
case "enumConstant": // MyEnum.NAME
String typeName = condyExpr.getExprType().value;
return new FieldExprent(condyExpr.getName(), typeName, true, null, FieldDescriptor.parseDescriptor("L" + typeName + ";"), null, false, true);
String typeName = type.value;
return new FieldExprent(name, typeName, true, null, FieldDescriptor.parseDescriptor("L" + typeName + ";"), null, false, true);
case "getStaticFinal": { // MyClass.fieldName
// name of the constant is the field name
List<PooledConstant> constArgs = condyExpr.getBootstrapArguments();
String fieldType = condyExpr.getExprType().value;
String fieldType = type.value;
String ownerClass;
// if a constant argument is present, that argument must be a class that contains the field
if (constArgs.size() == 1) {
PooledConstant ownerName = constArgs.get(0);
if (ownerName instanceof PrimitiveConstant) {
ownerClass = ((PrimitiveConstant) ownerName).value.toString();
} else {
return condyExpr;
return null;
}
// otherwise, the field is declared in the type of the field
} else {
if (condyExpr.getExprType().type != VarType.VARTYPE_OBJECT.type) {
return condyExpr;
if (type.type != VarType.VARTYPE_OBJECT.type) {
return null;
}
ownerClass = fieldType;
}
return new FieldExprent(condyExpr.getName(), ownerClass, true, null, FieldDescriptor.parseDescriptor(fieldType), null, false, true);
return new FieldExprent(name, ownerClass, true, null, FieldDescriptor.parseDescriptor(fieldType), null, false, true);
}
case "fieldVarHandle":
case "staticFieldVarHandle": { // --> MethodHandles.lookup().find[Static]VarHandle(...)
if (!DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_COMPLEX_CONDYS)) {
return condyExpr;
return null;
}
boolean isStatic = method.elementname.startsWith("static");
List<PooledConstant> constArgs = condyExpr.getBootstrapArguments();
String fieldName = condyExpr.getName();
String fieldName = name;
// first argument is fieldname so should be primitive, second might be condy for primitive classes
if (constArgs.size() != 2 || !(constArgs.get(0) instanceof PrimitiveConstant)) {
return condyExpr;
return null;
}
String ownerClass = ((PrimitiveConstant) constArgs.get(0)).getString();
return constructVarHandle(fieldName, ownerClass, constArgs.get(1), isStatic);
}
case "arrayVarHandle": { // --> MethodHandles.arrayElementVarHandle(...)
if (!DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_COMPLEX_CONDYS)) {
return condyExpr;
return null;
}
// argument is the array class
List<PooledConstant> constArgs = condyExpr.getBootstrapArguments();
if (constArgs.size() != 1) {
return condyExpr;
return null;
}
return constructArrayVarHandleExprent(constArgs.get(0));
}
case "invoke": {
// first argument is the method to invoke
// remaining arguments are used as arguments for the method
if (constArgs.size() < 1 || !(constArgs.get(0) instanceof LinkConstant other)) {
return null;
}
return simplifyCondy(other, name, type, constArgs.subList(1, constArgs.size()));
}
}
return null;
}

private static Exprent simplifyEnumDesc(LinkConstant method, String name, VarType type, List<PooledConstant> constArgs) {
// First argument is a method call to get the class (which is wrapped with ConstantBootstraps.invoke)
// Second argument is the name of the enum value
if (constArgs.size() != 2
|| !(constArgs.get(0) instanceof LinkConstant getClass)
|| !(constArgs.get(1) instanceof PrimitiveConstant valueNameConstant)
|| valueNameConstant.type != CodeConstants.TYPE_OBJECT
|| !(valueNameConstant.value instanceof String valueName)) {
return null;
}
StructBootstrapMethodsAttribute bootstrap = DecompilerContext.getContextProperty(DecompilerContext.CURRENT_CLASS).getAttribute(StructGeneralAttribute.ATTRIBUTE_BOOTSTRAP_METHODS);
LinkConstant getClassMethod = bootstrap.getMethodReference(getClass.index1);
List<PooledConstant> getClassArgs = bootstrap.getMethodArguments(getClass.index1);
Exprent enumType = simplifyCondy(getClassMethod, getClass.elementname, new VarType(getClass.descriptor), getClassArgs);
if (!(enumType instanceof ConstExprent constExp)
|| !constExp.getExprType().equals(VarType.VARTYPE_CLASS)) {
return null;
}

String typeName = constExp.getValue().toString();
return new FieldExprent(valueName, typeName, true, null, FieldDescriptor.parseDescriptor("L" + typeName + ";"), null, false, true);
}

private static Exprent simplifyClassDesc(LinkConstant method, String name, VarType type, List<PooledConstant> constArgs) {
// Argument is the class name
if (constArgs.size() != 1
|| !(constArgs.get(0) instanceof PrimitiveConstant classNameConstant)
|| classNameConstant.type != CodeConstants.TYPE_OBJECT
|| !(classNameConstant.value instanceof String className)) {
return null;
}
return condyExpr;
return new ConstExprent(VarType.VARTYPE_CLASS, className, null);
}

private static Exprent constructVarHandle(String fieldName, String fieldOwner, PooledConstant fieldType, boolean isStatic) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent.FunctionType;
import org.jetbrains.java.decompiler.modules.decompiler.stats.*;
import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.attr.StructBootstrapMethodsAttribute;
import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
import org.jetbrains.java.decompiler.struct.consts.LinkConstant;
import org.jetbrains.java.decompiler.struct.consts.PooledConstant;
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
import org.jetbrains.java.decompiler.struct.gen.CodeType;
Expand All @@ -20,8 +23,8 @@
import java.util.stream.Stream;

public final class SwitchPatternMatchProcessor {
public static boolean processPatternMatching(Statement root) {
boolean ret = processPatternMatchingRec(root, root);
public static boolean processPatternMatching(Statement root, StructClass cl) {
boolean ret = processPatternMatchingRec(root, root, cl);

if (ret) {
SequenceHelper.condenseSequences(root);
Expand All @@ -30,24 +33,24 @@ public static boolean processPatternMatching(Statement root) {
return ret;
}

private static boolean processPatternMatchingRec(Statement stat, Statement root) {
private static boolean processPatternMatchingRec(Statement stat, Statement root, StructClass cl) {
ValidationHelper.validateStatement((RootStatement) root);

boolean ret = false;
for (Statement st : new ArrayList<>(stat.getStats())) {
ret |= processPatternMatchingRec(st, root);
ret |= processPatternMatchingRec(st, root, cl);
ValidationHelper.validateStatement((RootStatement) root);
}

if (stat instanceof SwitchStatement) {
ret |= processStatement((SwitchStatement) stat, root);
ret |= processStatement((SwitchStatement) stat, root, cl);
ValidationHelper.validateStatement((RootStatement) root);
}

return ret;
}

private static boolean processStatement(SwitchStatement stat, Statement root) {
private static boolean processStatement(SwitchStatement stat, Statement root, StructClass cl) {
if (stat.isPhantom()) {
return false;
}
Expand Down Expand Up @@ -261,10 +264,11 @@ private static boolean processStatement(SwitchStatement stat, Statement root) {
}
}
}
// either an integer, String, or Class

Exprent newValue = null;
if (bsa instanceof PrimitiveConstant) {
// either an integer, String, or Class
PrimitiveConstant p = (PrimitiveConstant) bsa;
Exprent newValue = null;
switch (p.type) {
case CodeConstants.CONSTANT_Integer:
newValue = new ConstExprent((Integer) p.value, false, null);
Expand Down Expand Up @@ -297,16 +301,25 @@ private static boolean processStatement(SwitchStatement stat, Statement root) {
default:
ValidationHelper.assertTrue(false, "unexpected case");
}
if (newValue != null) {
int ix = i;
Exprent nvx = newValue;
// make sure we replace the right constant, null can be shared with anything
stat.getCaseValues().get(replaceIndex).replaceAll(u ->
u instanceof ConstExprent
} else if (bsa instanceof LinkConstant link) {
// Enum
StructBootstrapMethodsAttribute bootstrap = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_BOOTSTRAP_METHODS);
if (link.type == CodeConstants.CONSTANT_Dynamic && bootstrap != null) {
List<PooledConstant> constants = bootstrap.getMethodArguments(link.index1);
LinkConstant other = bootstrap.getMethodReference(link.index1);
newValue = CondyHelper.simplifyCondy(other, link.elementname, new VarType(link.descriptor), constants);
}
}

if (newValue != null) {
int ix = i;
Exprent nvx = newValue;
// make sure we replace the right constant, null can be shared with anything
stat.getCaseValues().get(replaceIndex).replaceAll(u -> u instanceof ConstExprent
&& u.getExprType().typeFamily == TypeFamily.INTEGER
&& ((ConstExprent) u).getIntValue() == ix
? nvx : u);
}
? nvx
: u);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,13 @@ private boolean isAmbiguous() {

@Override
public TextBuffer toJava(int indent) {
return toJava(indent, true);
}

public TextBuffer toJava(int indent, boolean includeCondyComment) {
TextBuffer buf = new TextBuffer();

if (wasCondy) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to keep this. If this shows up in switch specific code, could a flag be set to suppress it when it's resugared?

if (wasCondy && includeCondyComment) {
buf.append("/* $VF: constant dynamic */ ");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ public TextBuffer toJava(int indent) {
((ConstExprent) value).setConstType(switchType);
}

if (value instanceof FieldExprent && ((FieldExprent) value).isStatic()) { // enum values
FieldExprent field = (FieldExprent) value;
if (value instanceof FieldExprent field && field.isStatic() && (!this.backing.getTopParent().mt.getBytecodeVersion().hasSwitchPatternMatch() || switchType.equals(field.getExprType()))) { // enum values
buf.appendField(field.getName(), false, field.getClassname(), field.getName(), field.getDescriptor());
} else if (value instanceof FieldExprent field) {
buf.append(field.toJava(indent, false));
} else if (value instanceof FunctionExprent && ((FunctionExprent) value).getFuncType() == FunctionExprent.FunctionType.INSTANCEOF) {
// Pattern matching variables

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ public TextBuffer toJava(int indent) {
if (value instanceof ConstExprent && !value.getExprType().equals(VarType.VARTYPE_NULL)) {
value = value.copy();
((ConstExprent)value).setConstType(switch_type);
} if (value instanceof FieldExprent field && field.isStatic()) { // enum values
} if (value instanceof FieldExprent field && field.isStatic() && (!getTopParent().mt.getBytecodeVersion().hasSwitchPatternMatch() || switch_type.equals(field.getExprType()))) { // enum values
buf.appendField(field.getName(), false, field.getClassname(), field.getName(), field.getDescriptor());
} else if (value instanceof FieldExprent field) {
buf.append(field.toJava(indent, false));
} else if (value instanceof FunctionExprent && ((FunctionExprent) value).getFuncType() == FunctionType.INSTANCEOF) {
// Pattern matching variables

Expand Down
Loading
Loading