From b147ce78f86c67822534cf99b9dc30c9166dc573 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Wed, 9 Jun 2021 22:55:23 -0400 Subject: [PATCH 01/35] Preliminary support for record classes in `Procyon.CompilerTools`. Fixed some design issues with `ContextTrackingVisitor`. --- .../strobel/assembler/ir/MetadataReader.java | 1 + .../ir/attributes/AttributeNames.java | 1 + .../ir/attributes/RecordAttribute.java | 16 + .../ir/attributes/RecordComponentInfo.java | 43 ++ .../assembler/metadata/BuiltinTypes.java | 10 + .../assembler/metadata/ClassFileReader.java | 54 +++ .../assembler/metadata/CompilerTarget.java | 42 +- .../com/strobel/assembler/metadata/Flags.java | 73 +++- .../metadata/RecordTypeDefinition.java | 47 +++ .../assembler/metadata/TypeDefinition.java | 4 + .../metadata/signatures/SignatureParser.java | 37 +- .../strobel/decompiler/DecompilerHelpers.java | 12 + .../languages/java/JavaFormattingOptions.java | 4 + .../languages/java/JavaOutputVisitor.java | 35 +- .../java/ast/AstMethodBodyBuilder.java | 10 +- .../languages/java/ast/ClassType.java | 3 +- .../java/ast/ContextTrackingVisitor.java | 14 +- .../languages/java/ast/EntityDeclaration.java | 1 + .../decompiler/languages/java/ast/Roles.java | 1 + .../AddStandardAnnotationsTransform.java | 8 +- .../ast/transforms/BreakTargetRelocation.java | 5 +- .../EclipseEnumSwitchRewriterTransform.java | 4 +- .../EliminateSyntheticAccessorsTransform.java | 8 +- .../ast/transforms/EnumRewriterTransform.java | 8 +- .../EnumSwitchRewriterTransform.java | 4 +- .../IntroduceInitializersTransform.java | 4 +- .../java/ast/transforms/LambdaTransform.java | 4 +- .../RemoveHiddenMembersTransform.java | 8 +- .../RewriteLegacyClassConstantsTransform.java | 6 +- .../RewriteLocalClassesTransform.java | 4 +- .../RewriteRecordClassesTransform.java | 372 ++++++++++++++++++ .../transforms/TransformationPipeline.java | 1 + .../patterns/ParameterReferenceNode.java | 2 +- 33 files changed, 762 insertions(+), 84 deletions(-) create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordAttribute.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/RecordTypeDefinition.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/MetadataReader.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/MetadataReader.java index a81d6356..ec8779f0 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/MetadataReader.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/MetadataReader.java @@ -23,6 +23,7 @@ import com.strobel.core.ArrayUtilities; import com.strobel.core.VerifyArgument; +import java.util.Collections; import java.util.List; /** diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java index 205d5d43..8696710f 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java @@ -41,6 +41,7 @@ public final class AttributeNames { public static final String RuntimeInvisibleParameterAnnotations = "RuntimeInvisibleParameterAnnotations"; public static final String AnnotationDefault = "AnnotationDefault"; public static final String MethodParameters = "MethodParameters"; + public static final String Record = "Record"; private AttributeNames() { throw ContractUtils.unreachable(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordAttribute.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordAttribute.java new file mode 100644 index 00000000..dd2a91b6 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordAttribute.java @@ -0,0 +1,16 @@ +package com.strobel.assembler.ir.attributes; + +import java.util.List; + +public final class RecordAttribute extends SourceAttribute { + private final List _components; + + public RecordAttribute(final int length, final List components) { + super(AttributeNames.Record, length); + _components = components; + } + + public final List getComponents() { + return _components; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java new file mode 100644 index 00000000..28f282e7 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java @@ -0,0 +1,43 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.annotations.Nullable; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.VerifyArgument; +import com.sun.xml.internal.bind.v2.model.core.TypeRef; + +import java.util.List; + +public final class RecordComponentInfo { + private final String _name; + private final String _descriptor; + private final TypeReference _type; + private final List _attributes; + + public RecordComponentInfo(final String name, final String descriptor, final TypeReference type, final List attributes) { + _name = VerifyArgument.notNull(name, "name"); + _descriptor = VerifyArgument.notNull(descriptor, "descriptor"); + _type = VerifyArgument.notNull(type, "type"); + _attributes = VerifyArgument.notNull(attributes, "attributes"); + } + + @NotNull + public String getName() { + return _name; + } + + @NotNull + public String getDescriptor() { + return _descriptor; + } + + @NotNull + public TypeReference getType() { + return _type; + } + + @NotNull + public List getAttributes() { + return _attributes; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/BuiltinTypes.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/BuiltinTypes.java index 014bba58..0211b6a0 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/BuiltinTypes.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/BuiltinTypes.java @@ -35,6 +35,7 @@ public final class BuiltinTypes { public final static TypeDefinition Bottom; public final static TypeDefinition Null; public final static TypeDefinition Class; + public final static TypeDefinition Record; static { Boolean = new PrimitiveType(JvmType.Boolean); @@ -70,6 +71,15 @@ public final class BuiltinTypes { } Class = ClassFileReader.readClass(metadataSystem, buffer); + + buffer.reset(); + + if (typeLoader.tryLoadType("java/lang/Record", buffer)) { + Record = ClassFileReader.readClass(metadataSystem, buffer); + } + else { + Record = RecordTypeDefinition.INSTANCE; + } } public static TypeDefinition fromPrimitiveTypeCode(final int code) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java index a8db2c15..ac9a50d5 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java @@ -256,6 +256,60 @@ protected SourceAttribute readAttributeCore(final String name, final Buffer buff return new InnerClassesAttribute(length, ArrayUtilities.asUnmodifiableList(entries)); } + + case AttributeNames.Record: { + // Record_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 components_count; + // record_component_info components[components_count]; + // } + + final int componentCount = buffer.readUnsignedShort(); + final RecordComponentInfo[] components = new RecordComponentInfo[componentCount]; + + // record_component_info { + // u2 name_index; + // u2 descriptor_index; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + for (int i = 0; i < components.length; i++) { + final String componentName = _scope.lookupConstant(buffer.readUnsignedShort()); + final String componentDescriptor = _scope.lookupConstant(buffer.readUnsignedShort()); + final int componentAttributeCount = buffer.readUnsignedShort(); + final List componentAttributes; + + TypeReference componentType; + + try { + componentType = getParser().parseTypeSignature(componentDescriptor); + } + catch (final java.lang.Error | Exception ignored) { + componentType = BuiltinTypes.Object; + } + + if (componentAttributeCount > 0) { + final SourceAttribute[] cAttr = new SourceAttribute[componentAttributeCount]; + + for (int j = 0; j < cAttr.length; j++) { + cAttr[j] = readAttribute(buffer); + } + + componentAttributes = ArrayUtilities.asUnmodifiableList(cAttr); + } + else { + componentAttributes = Collections.emptyList(); + } + + components[i] = new RecordComponentInfo(componentName, componentDescriptor, componentType, componentAttributes); + } + + _scope._typeDefinition.setFlags(_scope._typeDefinition.getFlags() | Flags.RECORD); + + return new RecordAttribute(length, ArrayUtilities.asUnmodifiableList(components)); + } } return super.readAttributeCore(name, buffer, originalOffset, length); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/CompilerTarget.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/CompilerTarget.java index bc8967af..a472caca 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/CompilerTarget.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/CompilerTarget.java @@ -47,7 +47,47 @@ public enum CompilerTarget { /** * JDK 8. */ - JDK1_8("1.8", 52, 0); + JDK1_8("1.8", 52, 0), + + /** + * JDK 9. + */ + JDK9("9", 53, 0), + + /** + * JDK 10. + */ + JDK10("10", 54, 0), + + /** + * JDK 11. + */ + JDK11("11", 55, 0), + + /** + * JDK 12. + */ + JDK12("12", 56, 0), + + /** + * JDK 13. + */ + JDK13("13", 57, 0), + + /** + * JDK 14. + */ + JDK14("14", 58, 0), + + /** + * JDK 15. + */ + JDK15("15", 59, 0), + + /** + * JDK 16. + */ + JDK16("16", 60, 0); private static final CompilerTarget[] VALUES = values(); private static final CompilerTarget MIN = VALUES[0]; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java index efd99cda..c0733ddd 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java @@ -306,12 +306,12 @@ public static enum Kind { public static final int MANDATED = 1 << 15; public static final int StandardFlags = 0x0fff; - public static final int ModifierFlags = StandardFlags & ~INTERFACE; // Because the following access flags are overloaded with other // bit positions, we translate them when reading and writing class // files into unique bits positions: ACC_SYNTHETIC <-> SYNTHETIC, // for example. + public static final int ACC_FINAL = 0x0010; public static final int ACC_SUPER = 0x0020; public static final int ACC_BRIDGE = 0x0040; public static final int ACC_VARARGS = 0x0080; @@ -488,23 +488,68 @@ public static enum Kind { */ public static final long DEOBFUSCATED = 1L << 47; + /** + * Flag to indicate that a class is a record. The flag is also used to mark fields that are + * part of the state vector of a record and to mark the canonical constructor + */ + public static final long RECORD = 1L << 61; // ClassSymbols, MethodSymbols and VarSymbols + + /** + * Flag to mark a record constructor as a compact one + */ + public static final long COMPACT_RECORD_CONSTRUCTOR = 1L << 51; // MethodSymbols only + + /** + * Flag to mark a record field that was not initialized in the compact constructor + */ + public static final long UNINITIALIZED_FIELD = 1L << 51; // VarSymbols only + + /** + * Flag is set for compiler-generated record members, it could be applied to + * accessors and fields + */ + public static final int GENERATED_MEMBER = 1 << 24; // MethodSymbols and VarSymbols + + /** + * Flag to indicate sealed class/interface declaration. + */ + public static final long SEALED = 1L << 62; // ClassSymbols + + /** + * Flag to indicate that the class/interface was declared with the non-sealed modifier. + */ + public static final long NON_SEALED = 1L << 63; // ClassSymbols + /** * Modifier masks. */ public static final int - AccessFlags = PUBLIC | PROTECTED | PRIVATE, - LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, - MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags, - ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION, - InterfaceVarFlags = FINAL | STATIC | PUBLIC, - VarFlags = AccessFlags | FINAL | STATIC | - VOLATILE | TRANSIENT | ENUM, - ConstructorFlags = AccessFlags, - InterfaceMethodFlags = ABSTRACT | PUBLIC, - MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | - SYNCHRONIZED | FINAL | STRICTFP; - - public static final long LocalVarFlags = FINAL | PARAMETER; + AccessFlags = PUBLIC | PROTECTED | PRIVATE, + LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, + StaticLocalFlags = LocalClassFlags | STATIC | INTERFACE, + MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags, + MemberStaticClassFlags = MemberClassFlags | STATIC, + ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION, + InterfaceVarFlags = FINAL | STATIC | PUBLIC, + VarFlags = AccessFlags | FINAL | STATIC | + VOLATILE | TRANSIENT | ENUM, + ConstructorFlags = AccessFlags, + InterfaceMethodFlags = ABSTRACT | PUBLIC, + MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | + SYNCHRONIZED | FINAL | STRICTFP, + RecordMethodFlags = AccessFlags | ABSTRACT | STATIC | + SYNCHRONIZED | FINAL | STRICTFP; + public static final long + ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED, + ExtendedMemberClassFlags = (long)MemberClassFlags | SEALED | NON_SEALED, + ExtendedMemberStaticClassFlags = (long) MemberStaticClassFlags | SEALED | NON_SEALED, + ExtendedClassFlags = (long)ClassFlags | SEALED | NON_SEALED, + ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED, + InterfaceMethodMask = ABSTRACT | PRIVATE | STATIC | PUBLIC | STRICTFP | DEFAULT, + AnnotationTypeElementMask = ABSTRACT | PUBLIC, + LocalVarFlags = FINAL | PARAMETER, + ReceiverParamFlags = PARAMETER; + public static Set asModifierSet(final long flags) { Set modifiers = modifierSets.get(flags); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/RecordTypeDefinition.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/RecordTypeDefinition.java new file mode 100644 index 00000000..3b97a4a3 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/RecordTypeDefinition.java @@ -0,0 +1,47 @@ +package com.strobel.assembler.metadata; + +import com.strobel.assembler.Collection; +import com.strobel.core.VerifyArgument; + +class RecordTypeDefinition extends TypeDefinition { + final static TypeDefinition INSTANCE = new RecordTypeDefinition(); + + private RecordTypeDefinition() { + setBaseType(BuiltinTypes.Object); + setCompilerVersion(CompilerTarget.JDK15.majorVersion, CompilerTarget.JDK15.minorVersion); + setFlags((Flags.AccessFlags | Flags.ClassFlags) & (Flags.PUBLIC | Flags.SUPER | Flags.ABSTRACT)); + setName("Record"); + setPackageName("java.lang"); + setSimpleName("Record"); + + final Collection methodDefinitions = getDeclaredMethodsInternal(); + + for (final MethodDefinition baseMethod : BuiltinTypes.Object.getDeclaredMethods()) { + methodDefinitions.add(new RecordMethod(this, baseMethod)); + } + + setResolver(MetadataSystem.instance()); + } + + private final static class RecordMethod extends MethodDefinition { + RecordMethod(final RecordTypeDefinition declaringType, final MethodDefinition baseMethod) { + VerifyArgument.notNull(declaringType, "declaringType"); + VerifyArgument.notNull(baseMethod, "baseMethod"); + + setName(baseMethod.getName()); + setReturnType(baseMethod.getReturnType()); + setDeclaringType(declaringType); + setFlags((Flags.AccessFlags | Flags.MethodFlags) & (baseMethod.getFlags() | Flags.ABSTRACT)); + + if (baseMethod.isConstructor()) { + setFlags((getFlags() & (~Flags.PUBLIC)) | Flags.PROTECTED); + } + + final ParameterDefinitionCollection ps = getParametersInternal(); + + for (final ParameterDefinition bp : baseMethod.getParameters()) { + ps.add(new ParameterDefinition(bp.getSlot(), bp.getName(), bp.getParameterType())); + } + } + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java index f54c058e..6467fdce 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java @@ -383,6 +383,10 @@ public final boolean isEnum() { return Flags.testAny(getFlags(), Flags.ENUM); } + public final boolean isRecord() { + return Flags.testAny(getFlags(), Flags.RECORD); + } + public final boolean isAnonymous() { return Flags.testAny(getFlags(), Flags.ANONYMOUS); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/signatures/SignatureParser.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/signatures/SignatureParser.java index 14daf7a8..e4e558ed 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/signatures/SignatureParser.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/signatures/SignatureParser.java @@ -33,6 +33,9 @@ public final class SignatureParser { private final static boolean DEBUG = Boolean.getBoolean("DEBUG"); private final static TypeArgument[] EMPTY_TYPE_ARGUMENTS = new TypeArgument[0]; + private final static TypeSignature[] EMPTY_TYPE_SIGNATURES = new TypeSignature[0]; + private final static FieldTypeSignature[] EMPTY_FIELD_TYPE_SIGNATURES = new FieldTypeSignature[0]; + private final static FormalTypeParameter[] EMPTY_FORMAL_TYPE_PARAMETERS = new FormalTypeParameter[0]; private final static char EOI = ':'; private char[] input; // the input signature @@ -112,15 +115,13 @@ private FormalTypeParameter[] parseZeroOrMoreFormalTypeParameters() { return parseFormalTypeParameters(); } else { - return new FormalTypeParameter[0]; + return EMPTY_FORMAL_TYPE_PARAMETERS; } } private FormalTypeParameter[] parseFormalTypeParameters() { final Collection ftps = new ArrayList<>(3); - assert (current() == '<'); - if (current() != '<') { throw error("expected <"); } @@ -181,8 +182,6 @@ private FieldTypeSignature parseFieldTypeSignature() { } private ClassTypeSignature parseClassTypeSignature() { - assert (current() == 'L'); - if (current() != 'L') { throw error("expected a class type"); } @@ -213,7 +212,7 @@ private SimpleClassTypeSignature parseSimpleClassTypeSignature(final boolean dol case '/': case '.': case '$': { - return SimpleClassTypeSignature.make(id, dollar, new TypeArgument[0]); + return SimpleClassTypeSignature.make(id, dollar, EMPTY_TYPE_ARGUMENTS); } case '<': { @@ -236,7 +235,6 @@ private void parseClassTypeSignatureSuffix(final List private TypeArgument[] parseTypeArguments() { final Collection tas = new ArrayList<>(3); - assert (current() == '<'); if (current() != '<') { throw error("expected <"); } @@ -282,7 +280,6 @@ private TypeArgument parseTypeArgument() { // TypeVariableSignature -> T identifier private TypeVariableSignature parseTypeVariableSignature() { - assert (current() == 'T'); if (current() != 'T') { throw error("expected a type variable usage"); } @@ -368,14 +365,12 @@ private FieldTypeSignature[] parseZeroOrMoreBounds() { if (current() == ':') { advance(); - switch (current()) { - case ':': // empty class bound - fts.add(BottomSignature.make()); - break; - - default: // parse class bound - fts.add(parseFieldTypeSignature()); - break; + // parse class bound + if (current() == ':') { // empty class bound + fts.add(BottomSignature.make()); + } + else { + fts.add(parseFieldTypeSignature()); } // zero or more interface bounds @@ -385,7 +380,7 @@ private FieldTypeSignature[] parseZeroOrMoreBounds() { } } - return fts.toArray(new FieldTypeSignature[fts.size()]); + return fts.toArray(EMPTY_FIELD_TYPE_SIGNATURES); } private ClassTypeSignature[] parseSuperInterfaces() { @@ -413,7 +408,7 @@ private MethodTypeSignature parseMethodTypeSignature() { // (TypeSignature*) private TypeSignature[] parseFormalParameters() { if (current() != '(') { - throw error("expected ("); + return EMPTY_TYPE_SIGNATURES; } advance(); final TypeSignature[] pts = parseZeroOrMoreTypeSignatures(); @@ -448,11 +443,6 @@ private TypeSignature[] parseZeroOrMoreTypeSignatures() { stop = true; } } - /* while( matches(current(), - 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 'L', 'T', '[') - ) { - ts.add(parseTypeSignature()); - }*/ final TypeSignature[] ta = new TypeSignature[ts.size()]; return ts.toArray(ta); } @@ -483,7 +473,6 @@ private FieldTypeSignature[] parseZeroOrMoreThrowsSignatures() { // ThrowSignature -> ^ FieldTypeSignature private FieldTypeSignature parseThrowsSignature() { - assert (current() == '^'); if (current() != '^') { throw error("expected throws signature"); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerHelpers.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerHelpers.java index 7f27dc4a..631d9c67 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerHelpers.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerHelpers.java @@ -238,9 +238,12 @@ public static void writeOperand(final ITextOutput writer, final Object operand, } public static void writeDynamicCallSite(final ITextOutput output, final DynamicCallSite operand) { + writeMethodHandle(output, operand.getBootstrapMethodHandle()); + output.write(", "); output.writeReference(operand.getMethodName(), operand.getMethodType()); output.writeDelimiter(":"); writeMethodSignature(output, operand.getMethodType()); + writeOperandList(output, operand.getBootstrapArguments()); } public static String offsetToString(final int offset) { @@ -427,6 +430,15 @@ private static void writeOperandList(final ITextOutput writer, final Instruction } } + private static void writeOperandList(final ITextOutput writer, final List operands) { + for (int i = 0, n = operands.size(); i < n; i++) { + if (i != 0) { + writer.write(", "); + } + writeOperand(writer, operands.get(i)); + } + } + private static void formatMethodSignature( final ITextOutput writer, final IMethodSignature signature, diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java index 7476c977..85d3c0c7 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java @@ -34,6 +34,7 @@ public class JavaFormattingOptions { public BraceStyle InterfaceBraceStyle = BraceStyle.DoNotChange; public BraceStyle AnnotationBraceStyle = BraceStyle.DoNotChange; public BraceStyle EnumBraceStyle = BraceStyle.DoNotChange; + public BraceStyle RecordBraceStyle = BraceStyle.EndOfLine; public BraceStyle MethodBraceStyle = BraceStyle.DoNotChange; public BraceStyle InitializerBlockBraceStyle = BraceStyle.DoNotChange; public BraceStyle ConstructorBraceStyle = BraceStyle.DoNotChange; @@ -72,6 +73,7 @@ public class JavaFormattingOptions { public boolean SpaceBeforeConstructorDeclarationParameterComma; public boolean SpaceAfterConstructorDeclarationParameterComma; public boolean SpaceWithinConstructorDeclarationParentheses; + public boolean SpaceWithinRecordDeclarationParentheses; public boolean SpaceWithinEnumDeclarationParentheses; public boolean SpaceBeforeIndexerDeclarationBracket; public boolean SpaceWithinIndexerDeclarationBracket; @@ -252,6 +254,8 @@ public static JavaFormattingOptions createDefault() { options.BlankLinesBetweenEventFields = 0; options.BlankLinesBetweenMembers = 1; + options.SpaceWithinRecordDeclarationParentheses = false; + options.KeepCommentsAtFirstColumn = true; return options; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java index e02485ab..73f84676 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java @@ -1470,6 +1470,7 @@ public Void visitConstructorDeclaration(final ConstructorDeclaration node, final final TypeDeclaration type = parent instanceof TypeDeclaration ? (TypeDeclaration) parent : null; final MethodDefinition constructor = node.getUserData(Keys.METHOD_DEFINITION); + final TypeDefinition declaringType = constructor.getDeclaringType(); final LineNumberTableAttribute lineNumberTable = SourceAttribute.find( AttributeNames.LineNumberTable, @@ -1485,7 +1486,10 @@ public Void visitConstructorDeclaration(final ConstructorDeclaration node, final writeIdentifier(node.getNameToken(), type != null ? type.getName() : node.getName()); endNode(node.getNameToken()); space(policy.SpaceBeforeConstructorDeclarationParentheses); - writeCommaSeparatedListInParenthesis(node.getParameters(), policy.SpaceWithinMethodDeclarationParentheses); + + if (constructor == null || declaringType == null || !declaringType.isRecord() || !node.getParameters().isEmpty()) { + writeCommaSeparatedListInParenthesis(node.getParameters(), policy.SpaceWithinMethodDeclarationParentheses); + } final AstNodeCollection thrownTypes = node.getThrownTypes(); @@ -1581,14 +1585,19 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) type.isAnonymous() && node.getParent() instanceof AnonymousObjectCreationExpression; + final ClassType classType = node.getClassType(); + if (!isTrulyAnonymous) { writeAnnotations(node.getAnnotations(), true); writeModifiers(node.getModifiers()); - switch (node.getClassType()) { + switch (classType) { case ENUM: writeKeyword(Roles.ENUM_KEYWORD); break; + case RECORD: + writeKeyword(Roles.RECORD_KEYWORD); + break; case INTERFACE: writeKeyword(Roles.INTERFACE_KEYWORD); break; @@ -1603,6 +1612,11 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) node.getNameToken().acceptVisitor(this, ignored); writeTypeParameters(node.getTypeParameters()); + if (classType == ClassType.RECORD) { + writeCommaSeparatedListInParenthesis(node.getChildrenByRole(EntityDeclaration.RECORD_COMPONENT), + policy.SpaceWithinRecordDeclarationParentheses); + } + if (!node.getBaseType().isNull()) { space(); writeKeyword(Roles.EXTENDS_KEYWORD); @@ -1613,7 +1627,7 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) if (any(node.getInterfaces())) { final Collection interfaceTypes; - if (node.getClassType() == ClassType.ANNOTATION) { + if (classType == ClassType.ANNOTATION) { interfaceTypes = new ArrayList<>(); for (final AstType t : node.getInterfaces()) { @@ -1633,7 +1647,7 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) if (any(interfaceTypes)) { space(); - if (node.getClassType() == ClassType.INTERFACE || node.getClassType() == ClassType.ANNOTATION) { + if (classType == ClassType.INTERFACE || classType == ClassType.ANNOTATION) { writeKeyword(Roles.EXTENDS_KEYWORD); } else { @@ -1646,13 +1660,24 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) } } + if (classType == ClassType.RECORD && node.getMembers().isEmpty()) { + openBrace(BraceStyle.BannerStyle); + closeBrace(BraceStyle.BannerStyle); + endNode(node); + newLine(); + return null; + } + final BraceStyle braceStyle; final AstNodeCollection members = node.getMembers(); - switch (node.getClassType()) { + switch (classType) { case ENUM: braceStyle = policy.EnumBraceStyle; break; + case RECORD: + braceStyle = policy.RecordBraceStyle; + break; case INTERFACE: braceStyle = policy.InterfaceBraceStyle; break; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstMethodBodyBuilder.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstMethodBodyBuilder.java index 84f4b1cb..69537005 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstMethodBodyBuilder.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstMethodBodyBuilder.java @@ -1070,6 +1070,10 @@ private AstNode transformByteCode(final com.strobel.decompiler.ast.Expression by final Expression inlinedAssembly = inlineAssembly(byteCode, arguments); + if (operand instanceof DynamicCallSite) { + inlinedAssembly.putUserData(Keys.DYNAMIC_CALL_SITE, (DynamicCallSite) operand); + } + if (isTopLevel) { return new CommentStatement(" " + inlinedAssembly.toString()); } @@ -1344,6 +1348,7 @@ private List adjustArgumentsForMethodCallCore( return arguments.subList(first, last + 1); } + @SuppressWarnings("SameParameterValue") private boolean isCastRequired(final TypeReference targetType, final TypeReference sourceType, final boolean exactMatch) { if (targetType == null || sourceType == null) { return false; @@ -1362,10 +1367,9 @@ private boolean isCastRequired(final TypeReference targetType, final TypeReferen private static Expression inlineAssembly(final com.strobel.decompiler.ast.Expression byteCode, final List arguments) { if (byteCode.getOperand() != null) { - arguments.add(0, new IdentifierExpression( byteCode.getOffset(), - formatByteCodeOperand(byteCode.getOperand()))); + arguments.add(0, new IdentifierExpression(byteCode.getOffset(), formatByteCodeOperand(byteCode.getOperand()))); } - return new IdentifierExpression( byteCode.getOffset(), byteCode.getCode().getName()).invoke(arguments); + return new IdentifierExpression(byteCode.getOffset(), byteCode.getCode().getName()).invoke(arguments); } private static String formatByteCodeOperand(final Object operand) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ClassType.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ClassType.java index e423fd08..27694c5d 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ClassType.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ClassType.java @@ -21,5 +21,6 @@ public enum ClassType CLASS, INTERFACE, ANNOTATION, - ENUM + ENUM, + RECORD } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ContextTrackingVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ContextTrackingVisitor.java index 83549d4b..d2c7ef87 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ContextTrackingVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ContextTrackingVisitor.java @@ -42,14 +42,14 @@ protected final boolean inMethod() { return context.getCurrentMethod() != null; } - public TResult visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + public final TResult visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { final TypeDefinition oldType = context.getCurrentType(); final MethodDefinition oldMethod = context.getCurrentMethod(); try { context.setCurrentType(typeDeclaration.getUserData(Keys.TYPE_DEFINITION)); context.setCurrentMethod(null); - return super.visitTypeDeclaration(typeDeclaration, p); + return visitTypeDeclarationOverride(typeDeclaration, p); } finally { context.setCurrentType(oldType); @@ -57,17 +57,25 @@ public TResult visitTypeDeclaration(final TypeDeclaration typeDeclaration, final } } + protected TResult visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { + return super.visitTypeDeclaration(typeDeclaration, p); + } + public TResult visitMethodDeclaration(final MethodDeclaration node, final Void p) { assert context.getCurrentMethod() == null; try { context.setCurrentMethod(node.getUserData(Keys.METHOD_DEFINITION)); - return super.visitMethodDeclaration(node, p); + return visitMethodDeclarationOverride(node, p); } finally { context.setCurrentMethod(null); } } + protected TResult visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { + return super.visitMethodDeclaration(node, p); + } + public TResult visitConstructorDeclaration(final ConstructorDeclaration node, final Void p) { assert (context.getCurrentMethod() == null); try { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java index 4117bef7..4ea6fa1d 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java @@ -33,6 +33,7 @@ public abstract class EntityDeclaration extends AstNode { public final static Role UNATTACHED_ANNOTATION_ROLE = new Role<>("UnattachedAnnotation", Annotation.class); public final static Role MODIFIER_ROLE = new Role<>("Modifier", JavaModifierToken.class); public final static Role PRIVATE_IMPLEMENTATION_TYPE_ROLE = new Role<>("PrivateImplementationType", AstType.class, AstType.NULL); + public final static Role RECORD_COMPONENT = new Role<>("ParameterDeclaration", ParameterDeclaration.class); private boolean _anyModifiers; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java index ae7f378e..e799d55f 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java @@ -80,6 +80,7 @@ public final class Roles { public final static TokenRole DEFAULT_KEYWORD = new TokenRole("default", TokenRole.FLAG_KEYWORD); public final static TokenRole PACKAGE_KEYWORD = new TokenRole("package", TokenRole.FLAG_KEYWORD); public final static TokenRole ENUM_KEYWORD = new TokenRole("enum", TokenRole.FLAG_KEYWORD); + public final static TokenRole RECORD_KEYWORD = new TokenRole("record", TokenRole.FLAG_KEYWORD); public final static TokenRole INTERFACE_KEYWORD = new TokenRole("interface", TokenRole.FLAG_KEYWORD); public final static TokenRole CLASS_KEYWORD = new TokenRole("class", TokenRole.FLAG_KEYWORD); public final static TokenRole ANNOTATION_KEYWORD = new TokenRole("@interface", TokenRole.FLAG_KEYWORD); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/AddStandardAnnotationsTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/AddStandardAnnotationsTransform.java index b325cd09..c62103fe 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/AddStandardAnnotationsTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/AddStandardAnnotationsTransform.java @@ -40,10 +40,10 @@ public AddStandardAnnotationsTransform(final DecompilerContext context) { } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { tryAddOverrideAnnotation(node); tryAddDeprecatedAnnotationToMember(node); - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } @Override @@ -65,9 +65,9 @@ public Void visitEnumValueDeclaration(final EnumValueDeclaration node, final Voi } @Override - public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { tryAddDeprecatedAnnotationToType(typeDeclaration); - return super.visitTypeDeclaration(typeDeclaration, p); + return super.visitTypeDeclarationOverride(typeDeclaration, p); } private void tryAddOverrideAnnotation(final MethodDeclaration node) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/BreakTargetRelocation.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/BreakTargetRelocation.java index a77bf7eb..abe63aab 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/BreakTargetRelocation.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/BreakTargetRelocation.java @@ -52,9 +52,8 @@ private final static class LabelInfo { } @Override - @SuppressWarnings("ConstantConditions") - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { - super.visitMethodDeclaration(node, p); + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { + super.visitMethodDeclarationOverride(node, p); runForMethod(node); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseEnumSwitchRewriterTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseEnumSwitchRewriterTransform.java index 635a77da..6e89127a 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseEnumSwitchRewriterTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseEnumSwitchRewriterTransform.java @@ -134,7 +134,7 @@ public Void visitFieldDeclaration(final FieldDeclaration node, final Void data) } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final MethodDefinition methodDefinition = node.getUserData(Keys.METHOD_DEFINITION); if (isSwitchMapMethod(methodDefinition)) { @@ -169,7 +169,7 @@ public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { } } - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } private void rewrite() { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EliminateSyntheticAccessorsTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EliminateSyntheticAccessorsTransform.java index 88858f15..6b075176 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EliminateSyntheticAccessorsTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EliminateSyntheticAccessorsTransform.java @@ -516,7 +516,7 @@ private PhaseOneVisitor() { } @Override - public Void visitTypeDeclaration(final TypeDeclaration node, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration node, final Void p) { final TypeDefinition type = node.getUserData(Keys.TYPE_DEFINITION); if (type != null) { @@ -525,11 +525,11 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void p) { } } - return super.visitTypeDeclaration(node, p); + return super.visitTypeDeclarationOverride(node, p); } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final MethodDefinition method = node.getUserData(Keys.METHOD_DEFINITION); if (method != null) { @@ -540,7 +540,7 @@ public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { } } - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } private boolean tryMatchAccessor(final MethodDeclaration node) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumRewriterTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumRewriterTransform.java index d31ad198..88c389f4 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumRewriterTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumRewriterTransform.java @@ -53,7 +53,7 @@ protected Visitor(final DecompilerContext context) { } @Override - public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { final MemberReference oldValuesField = _valuesField; final Map oldValueFields = _valueFields; final Map oldValueInitializers = _valueInitializers; @@ -66,7 +66,7 @@ public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Vo _valueInitializers = valueInitializers; try { - super.visitTypeDeclaration(typeDeclaration, p); + super.visitTypeDeclarationOverride(typeDeclaration, p); } finally { _valuesField = oldValuesField; @@ -274,7 +274,7 @@ else if (currentType.isAnonymous()) { } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final TypeDefinition currentType = context.getCurrentType(); if (currentType != null && currentType.isEnum() && !context.getSettings().getShowSyntheticMembers()) { @@ -310,7 +310,7 @@ public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { } } - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } private void rewrite( diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumSwitchRewriterTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumSwitchRewriterTransform.java index d2468807..1c652c9f 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumSwitchRewriterTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EnumSwitchRewriterTransform.java @@ -65,7 +65,7 @@ protected Visitor(final DecompilerContext context) { } @Override - public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); @@ -85,7 +85,7 @@ public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Vo _isSwitchMapWrapper = isSwitchMapWrapper; try { - super.visitTypeDeclaration(typeDeclaration, p); + super.visitTypeDeclarationOverride(typeDeclaration, p); } finally { _isSwitchMapWrapper = oldIsSwitchMapWrapper; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroduceInitializersTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroduceInitializersTransform.java index 06285e03..22b978e8 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroduceInitializersTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroduceInitializersTransform.java @@ -113,7 +113,7 @@ public Void visitAnonymousObjectCreationExpression(final AnonymousObjectCreation } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final MethodDefinition oldInitializer = _currentInitializerMethod; final MethodDefinition oldConstructor = _currentConstructor; @@ -129,7 +129,7 @@ public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { } try { - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } finally { _currentConstructor = oldConstructor; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/LambdaTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/LambdaTransform.java index 84c429bd..155b49cd 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/LambdaTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/LambdaTransform.java @@ -38,14 +38,14 @@ public void run(final AstNode compilationUnit) { compilationUnit.acceptVisitor( new ContextTrackingVisitor(context) { @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final MemberReference methodReference = node.getUserData(Keys.MEMBER_REFERENCE); if (methodReference instanceof MethodReference) { _methodDeclarations.put(makeMethodKey((MethodReference) methodReference), node); } - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } }, null diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RemoveHiddenMembersTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RemoveHiddenMembersTransform.java index a4989f66..af08a6f7 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RemoveHiddenMembersTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RemoveHiddenMembersTransform.java @@ -38,7 +38,7 @@ public RemoveHiddenMembersTransform(final DecompilerContext context) { } @Override - public Void visitTypeDeclaration(final TypeDeclaration node, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration node, final Void p) { if (!(node.getParent() instanceof CompilationUnit)) { final TypeDefinition type = node.getUserData(Keys.TYPE_DEFINITION); @@ -48,7 +48,7 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void p) { } } - return super.visitTypeDeclaration(node, p); + return super.visitTypeDeclarationOverride(node, p); } @Override @@ -64,7 +64,7 @@ public Void visitFieldDeclaration(final FieldDeclaration node, final Void data) } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final MethodDefinition method = node.getUserData(Keys.METHOD_DEFINITION); if (method != null) { @@ -81,7 +81,7 @@ public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { } } - return super.visitMethodDeclaration(node, p); + return super.visitMethodDeclarationOverride(node, p); } private final static INode DEFAULT_CONSTRUCTOR_BODY; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java index 7dbc882e..2e96f67d 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java @@ -100,7 +100,7 @@ protected boolean shouldContinue() { } @Override - public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { if (_currentType != null) { return null; } @@ -108,7 +108,7 @@ public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Vo _currentType = typeDeclaration; try { - return super.visitTypeDeclaration(typeDeclaration, p); + return super.visitTypeDeclarationOverride(typeDeclaration, p); } finally { _currentType = null; @@ -141,7 +141,7 @@ protected Void visitChildren(final AstNode node, final Void data) { } @Override - public Void visitMethodDeclaration(final MethodDeclaration node, final Void p) { + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { final MethodDefinition m = node.getUserData(Keys.METHOD_DEFINITION); if (isClassMethodCandidate(m) && PATTERN.matches(node)) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLocalClassesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLocalClassesTransform.java index 768cb896..6ac42e80 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLocalClassesTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLocalClassesTransform.java @@ -116,14 +116,14 @@ protected PhaseOneVisitor(final DecompilerContext context) { } @Override - public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { final TypeDefinition type = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); if (type != null && (isLocalOrAnonymous(type) || type.isAnonymous())) { _localTypes.put(type, typeDeclaration); } - return super.visitTypeDeclaration(typeDeclaration, p); + return super.visitTypeDeclarationOverride(typeDeclaration, p); } } } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java new file mode 100644 index 00000000..41d91922 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java @@ -0,0 +1,372 @@ +package com.strobel.decompiler.languages.java.ast.transforms; + +import com.strobel.annotations.NotNull; +import com.strobel.assembler.ir.attributes.AttributeNames; +import com.strobel.assembler.ir.attributes.RecordAttribute; +import com.strobel.assembler.ir.attributes.RecordComponentInfo; +import com.strobel.assembler.ir.attributes.SourceAttribute; +import com.strobel.assembler.metadata.DynamicCallSite; +import com.strobel.assembler.metadata.MetadataHelper; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.core.Predicate; +import com.strobel.core.StringUtilities; +import com.strobel.core.StrongBox; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.patterns.AnyNode; +import com.strobel.decompiler.patterns.Match; +import com.strobel.decompiler.patterns.NamedNode; +import com.strobel.decompiler.patterns.ParameterReferenceNode; +import com.strobel.decompiler.patterns.Pattern; +import com.strobel.decompiler.patterns.Repeat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.strobel.core.CollectionUtilities.*; + +public class RewriteRecordClassesTransform extends ContextTrackingVisitor { + protected final static Map GENERATED_METHOD_SIGNATURES; + + protected final static BlockStatement INVOKE_DYNAMIC_BODY = new BlockStatement( + new ReturnStatement( + new NamedNode( + "invocation", + new InvocationExpression( + new IdentifierExpression(Expression.MYSTERY_OFFSET, "invokedynamic"), + new Repeat(new AnyNode()).toExpression() + ) + ).toExpression() + ) + ); + + protected final static ExpressionStatement ASSIGNMENT_PATTERN = + new ExpressionStatement( + new AssignmentExpression( + new NamedNode( + "assignment", + new MemberReferenceExpression( + new ThisReferenceExpression(Expression.MYSTERY_OFFSET), + Pattern.ANY_STRING + ) + ).toExpression(), + AssignmentOperatorType.ASSIGN, + new ParameterReferenceNode(-1, "parameter").toExpression() + ) + ); + + protected final static ExpressionStatement SUPER_CONSTRUCTOR_CALL = + new ExpressionStatement( + new InvocationExpression( + new SuperReferenceExpression(Expression.MYSTERY_OFFSET) + ) + ); + + static { + final HashMap generatedMethodNames = new HashMap<>(); + + generatedMethodNames.put("toString", "()Ljava/lang/String;"); + generatedMethodNames.put("hashCode", "()I"); + generatedMethodNames.put("equals", "(Ljava/lang/Object;)Z"); + + GENERATED_METHOD_SIGNATURES = Collections.unmodifiableMap(generatedMethodNames); + } + + private RecordState _currentRecord; + + public RewriteRecordClassesTransform(final DecompilerContext context) { + super(context); + } + + @Override + protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { + final RecordState oldRecord = _currentRecord; + + final TypeDefinition definition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); + + final RecordAttribute recordAttribute = definition != null && definition.isRecord() + ? SourceAttribute.find(AttributeNames.Record, definition.getSourceAttributes()) + : null; + + final RecordState recordState = recordAttribute != null ? new RecordState(definition, recordAttribute, typeDeclaration) : null; + + _currentRecord = recordState; + + try { + super.visitTypeDeclarationOverride(typeDeclaration, p); + + if (recordState != null) { + recordState.tryRewrite(); + } + + return null; + } + finally { + _currentRecord = oldRecord; + } + } + + @Override + protected Void visitMethodDeclarationOverride(final MethodDeclaration node, final Void p) { + final RecordState recordState = _currentRecord; + final MethodDefinition method; + + super.visitMethodDeclarationOverride(node, p); + + if (recordState == null || (method = context.getCurrentMethod()) == null) { + return null; + } + + final Match indyMatch; + final String expectedSignature = GENERATED_METHOD_SIGNATURES.get(method.getName()); + + if (expectedSignature != null && + StringUtilities.equals(expectedSignature, method.getErasedSignature()) && + (indyMatch = INVOKE_DYNAMIC_BODY.match(node.getBody())).success()) { + + final DynamicCallSite callSite = first(indyMatch.get("invocation")).getUserData(Keys.DYNAMIC_CALL_SITE); + + if (callSite != null && "java/lang/runtime/ObjectMethods".equals(callSite.getBootstrapMethod().getDeclaringType().getInternalName())) { + recordState.removableMethods.add(node); + return null; + } + } + + final RecordComponentInfo componentInfo = recordState.recordComponents.get(node.getName()); + + if (componentInfo != null && + MetadataHelper.isSameType(componentInfo.getType(), node.getReturnType().toTypeReference())) { + + recordState.removableAccessors.put(componentInfo, node); + } + + return null; + } + + @Override + public Void visitFieldDeclaration(final FieldDeclaration node, final Void data) { + super.visitFieldDeclaration(node, data); + + final RecordState recordState = _currentRecord; + + if (recordState == null) { + return null; + } + + final RecordComponentInfo componentInfo = recordState.recordComponents.get(node.getName()); + + if (componentInfo != null && + MetadataHelper.isSameType(componentInfo.getType(), node.getReturnType().toTypeReference())) { + + recordState.removableFields.put(componentInfo, node); + } + + return null; + } + + @Override + public Void visitConstructorDeclaration(final ConstructorDeclaration node, final Void p) { + final RecordState recordState = _currentRecord; + final RecordState.Constructor oldConstructor = recordState != null ? recordState.currentConstructor : null; + + if (recordState != null) { + RecordState.Constructor recordConstructor = recordState.constructors.get(node); + + if (recordConstructor == null) { + recordState.constructors.put(node, recordConstructor = new RecordState.Constructor(node)); + } + + recordState.currentConstructor = recordConstructor; + } + + try { + return super.visitConstructorDeclaration(node, p); + } + finally { + if (recordState != null) { + recordState.currentConstructor = oldConstructor; + } + } + } + + @Override + public Void visitExpressionStatement(final ExpressionStatement node, final Void data) { + super.visitExpressionStatement(node, data); + + final RecordState recordState = _currentRecord; + final RecordState.Constructor recordConstructor = recordState != null ? recordState.currentConstructor : null; + + if (recordConstructor == null) { + return null; + } + + if (SUPER_CONSTRUCTOR_CALL.matches(node)) { + recordConstructor.removableSuperCall.set(node); + return null; + } + + final Match match = ASSIGNMENT_PATTERN.match(node); + + if (!match.success()) { + return null; + } + + final MemberReferenceExpression f = first(match.get("assignment")); + final IdentifierExpression p = first(match.get("parameter")); + final RecordComponentInfo componentInfo = recordState.recordComponents.get(f.getMemberName()); + + if (componentInfo != null) { + recordConstructor.removableAssignments.put(componentInfo, node); + + final ConstructorDeclaration constructor = recordConstructor.constructor; + + final ParameterDeclaration parameter = firstOrDefault( + constructor.getParameters(), + new Predicate() { + @Override + public boolean test(final ParameterDeclaration declaration) { + return StringUtilities.equals(declaration.getName(), p.getIdentifier()); + } + } + ); + + if (parameter != null) { + recordConstructor.removableParameters.put(componentInfo, parameter); + } + } + + return null; + } + + protected final static class RecordState { + final @NotNull TypeDefinition recordDefinition; + final @NotNull RecordAttribute recordAttribute; + final @NotNull TypeDeclaration recordDeclaration; + final @NotNull Map constructors; + final @NotNull List removableMethods; + final @NotNull Map removableAccessors; + final @NotNull Map removableFields; + final @NotNull Map recordComponents; + + Constructor currentConstructor; + + public RecordState(final TypeDefinition recordDefinition, final RecordAttribute recordAttribute, final TypeDeclaration recordDeclaration) { + this.recordDefinition = recordDefinition; + this.recordAttribute = recordAttribute; + this.recordDeclaration = recordDeclaration; + this.constructors = new HashMap<>(); + this.removableAccessors = new HashMap<>(); + this.removableFields = new HashMap<>(); + this.removableMethods = new ArrayList<>(); + + final Map recordComponents = new HashMap<>(); + + for (final RecordComponentInfo component : recordAttribute.getComponents()) { + recordComponents.put(component.getName(), component); + } + + this.recordComponents = Collections.unmodifiableMap(recordComponents); + } + + @SuppressWarnings("UnusedReturnValue") + public final boolean tryRewrite() { + if (canRewrite()) { + rewrite0(); + return true; + } + return false; + } + + public final boolean canRewrite() { + final List components = recordAttribute.getComponents(); + final int componentCount = components.size(); + + final Constructor constructor; + + if (removableAccessors.size() != componentCount || + removableFields.size() != componentCount || + constructors.size() != 1 || + (constructor = single(constructors.values())).removableParameters.size() != componentCount || + constructor.removableAssignments.size() != componentCount || + constructor.removableSuperCall.get() == null) { + + return false; + } + + for (final RecordComponentInfo component : components) { + if (!removableAccessors.containsKey(component) || + !constructor.removableAssignments.containsKey(component) || + !constructor.removableParameters.containsKey(component)) { + + return false; + } + } + + return true; + } + + private void rewrite0() { + recordDeclaration.getBaseType().remove(); + recordDeclaration.setClassType(ClassType.RECORD); + recordDeclaration.getModifiers().clear(); + + for (final MethodDeclaration accessor : removableAccessors.values()) { + accessor.remove(); + } + + final RecordState.Constructor constructor = single(constructors.values()); + final ExpressionStatement superCall = constructor.removableSuperCall.get(); + + if (superCall != null) { + superCall.remove(); + } + + for (final ExpressionStatement assignment : constructor.removableAssignments.values()) { + assignment.remove(); + } + + for (final ParameterDeclaration p : constructor.removableParameters.values()) { + p.remove(); + p.getModifiers().clear(); + recordDeclaration.addChild(p, EntityDeclaration.RECORD_COMPONENT); + } + + for (final MethodDeclaration method : removableMethods) { + method.remove(); + } + + for (final MethodDeclaration accessor : removableAccessors.values()) { + accessor.remove(); + } + + for (final FieldDeclaration field : removableFields.values()) { + field.remove(); + } + + final ConstructorDeclaration constructorDeclaration = single(constructors.keySet()); + + if (constructorDeclaration.getBody().getStatements().isEmpty()) { + constructorDeclaration.remove(); + } + } + + public final static class Constructor { + final @NotNull ConstructorDeclaration constructor; + final @NotNull Map removableParameters; + final @NotNull Map removableAssignments; + final @NotNull StrongBox removableSuperCall; + + Constructor(final ConstructorDeclaration constructor) { + this.constructor = VerifyArgument.notNull(constructor, "constructor"); + this.removableParameters = new HashMap<>(); + this.removableAssignments = new HashMap<>(); + this.removableSuperCall = new StrongBox<>(); + } + } + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java index 761b7411..d32545eb 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java @@ -47,6 +47,7 @@ public static IAstTransform[] createPipeline(final DecompilerContext context) { new EliminateSyntheticAccessorsTransform(context), new LambdaTransform(context), new RewriteNewArrayLambdas(context), + new RewriteRecordClassesTransform(context), new RewriteLocalClassesTransform(context), new IntroduceOuterClassReferencesTransform(context), new RewriteInnerClassConstructorCalls(context), diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/ParameterReferenceNode.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/ParameterReferenceNode.java index cf9afe4a..e36807f8 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/ParameterReferenceNode.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/ParameterReferenceNode.java @@ -50,7 +50,7 @@ public boolean matches(final INode other, final Match match) { if (variable != null && variable.isParameter() && - variable.getOriginalParameter().getPosition() == _parameterPosition) { + (_parameterPosition < 0 || variable.getOriginalParameter().getPosition() == _parameterPosition)) { if (_groupName != null) { match.add(_groupName, identifier); From be056da6eb6ca1bffc99f07c5fb88183757a9047 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Fri, 11 Jun 2021 23:21:02 -0400 Subject: [PATCH 02/35] Removed accidental import. --- .../strobel/assembler/ir/attributes/RecordComponentInfo.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java index 28f282e7..48e45b6c 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/RecordComponentInfo.java @@ -1,10 +1,8 @@ package com.strobel.assembler.ir.attributes; import com.strobel.annotations.NotNull; -import com.strobel.annotations.Nullable; import com.strobel.assembler.metadata.TypeReference; import com.strobel.core.VerifyArgument; -import com.sun.xml.internal.bind.v2.model.core.TypeRef; import java.util.List; From afc4fae74b0ddc3fb008493f08f1d3f5249cf6d4 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Fri, 11 Jun 2021 23:53:36 -0400 Subject: [PATCH 03/35] Preliminary support for decompiling `module-info` classes. --- .../strobel/assembler/ir/ConstantPool.java | 130 +++++- .../ir/attributes/AttributeNames.java | 4 + .../ir/attributes/ModuleAttribute.java | 86 ++++ .../ir/attributes/ModuleDependency.java | 37 ++ .../attributes/ModuleMainClassAttribute.java | 20 + .../attributes/ModulePackagesAttribute.java | 39 ++ .../ir/attributes/ModuleTargetAttribute.java | 18 + .../assembler/ir/attributes/PackageInfo.java | 55 +++ .../assembler/ir/attributes/ServiceInfo.java | 33 ++ .../assembler/metadata/ClassFileReader.java | 163 ++++++++ .../metadata/ConstantPoolPrinter.java | 14 + .../com/strobel/assembler/metadata/Flags.java | 76 +++- .../assembler/metadata/ModuleReference.java | 25 ++ .../assembler/metadata/PackageReference.java | 15 +- .../assembler/metadata/TypeDefinition.java | 4 + .../assembler/metadata/TypeReference.java | 2 + .../strobel/decompiler/AnsiTextOutput.java | 40 +- .../languages/BytecodeLanguage.java | 381 ++++++++++++++++-- .../decompiler/languages/EntityType.java | 3 +- .../languages/java/JavaFormattingOptions.java | 1 + .../languages/java/JavaOutputVisitor.java | 23 +- .../languages/java/TextOutputFormatter.java | 82 +++- .../languages/java/ast/AstBuilder.java | 50 ++- .../languages/java/ast/CompilationUnit.java | 1 + .../java/ast/DepthFirstAstVisitor.java | 5 + .../languages/java/ast/IAstVisitor.java | 1 + .../languages/java/ast/JavaNameResolver.java | 7 +- .../decompiler/languages/java/ast/Keys.java | 2 + .../languages/java/ast/ModuleDeclaration.java | 90 +++++ .../languages/java/ast/NodeType.java | 3 +- .../decompiler/languages/java/ast/Roles.java | 2 + 31 files changed, 1300 insertions(+), 112 deletions(-) create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleAttribute.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleDependency.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleMainClassAttribute.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModulePackagesAttribute.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleTargetAttribute.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/PackageInfo.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ServiceInfo.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ModuleReference.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ModuleDeclaration.java diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/ConstantPool.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/ConstantPool.java index d14a81f7..3c97a3b2 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/ConstantPool.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/ConstantPool.java @@ -406,6 +406,12 @@ public static ConstantPool read(final Buffer b) { case InvokeDynamicInfo: new InvokeDynamicInfoEntry(pool, b.readUnsignedShort(), b.readUnsignedShort()); break; + case Module: + new ModuleEntry(pool, b.readUnsignedShort()); + break; + case Package: + new PackageEntry(pool, b.readUnsignedShort()); + break; } } @@ -520,7 +526,9 @@ public static enum Tag { NameAndTypeDescriptor(12), MethodHandle(15), MethodType(16), - InvokeDynamicInfo(18); + InvokeDynamicInfo(18), + Module(19), + Package(20); public final int value; @@ -529,16 +537,20 @@ public static enum Tag { } public static Tag fromValue(final int value) { - VerifyArgument.inRange(Tag.Utf8StringConstant.value, Tag.InvokeDynamicInfo.value, value, "value"); + VerifyArgument.inRange(minTag.value, maxTag.value, value, "value"); return lookup[value]; } + private final static Tag minTag; + private final static Tag maxTag; private final static Tag[] lookup; static { final Tag[] values = Tag.values(); - lookup = new Tag[Tag.InvokeDynamicInfo.value + 1]; + minTag = values[0]; + maxTag = values[values.length - 1]; + lookup = new Tag[maxTag.value + 1]; for (final Tag tag : values) { lookup[tag.value] = tag; @@ -566,6 +578,8 @@ public interface Visitor { void visitMethodType(MethodTypeEntry info); void visitStringConstant(StringConstantEntry info); void visitUtf8StringConstant(Utf8StringConstantEntry info); + void visitModule(ModuleEntry info); + void visitPackage(PackageEntry info); void visitEnd(); // @@ -631,6 +645,14 @@ public void visitStringConstant(final StringConstantEntry info) { public void visitUtf8StringConstant(final Utf8StringConstantEntry info) { } + @Override + public void visitModule(final ModuleEntry info) { + } + + @Override + public void visitPackage(final PackageEntry info) { + } + @Override public void visitEnd() { } @@ -745,6 +767,18 @@ public void visitUtf8StringConstant(final Utf8StringConstantEntry info) { codeStream.writeUtf8(info.value); } + @Override + public void visitModule(final ModuleEntry info) { + codeStream.writeByte(info.getTag().value); + codeStream.writeShort(info.nameIndex); + } + + @Override + public void visitPackage(final PackageEntry info) { + codeStream.writeByte(info.getTag().value); + codeStream.writeShort(info.nameIndex); + } + @Override public void visitEnd() { } @@ -1073,6 +1107,96 @@ public String toString() { } } + public static class ModuleEntry extends ConstantEntry { + + public final int nameIndex; + + public ModuleEntry(final ConstantPool owner, final int nameIndex) { + super(owner); + + this.nameIndex = nameIndex; + owner._newKey.set(getTag(), nameIndex); + owner._entryMap.put(owner._newKey.clone(), this); + owner._newKey.clear(); + } + + @Override + void fixupKey(final Key key) { + key.set(Tag.Module, nameIndex); + } + + public Tag getTag() { + return Tag.Module; + } + + public int byteLength() { + return 3; + } + + public String getName() { + return ((Utf8StringConstantEntry) owner.get(nameIndex, Tag.Utf8StringConstant)).value; + } + + public void accept(final Visitor visitor) { + visitor.visitModule(this); + } + + @Override + public String toString() { + return "ModuleEntry[nameIndex: " + nameIndex + "]"; + } + + @Override + public Object getConstantValue() { + return getName(); + } + } + + public static class PackageEntry extends ConstantEntry { + + public final int nameIndex; + + public PackageEntry(final ConstantPool owner, final int nameIndex) { + super(owner); + + this.nameIndex = nameIndex; + owner._newKey.set(getTag(), nameIndex); + owner._entryMap.put(owner._newKey.clone(), this); + owner._newKey.clear(); + } + + @Override + void fixupKey(final Key key) { + key.set(Tag.Package, nameIndex); + } + + public Tag getTag() { + return Tag.Package; + } + + public int byteLength() { + return 3; + } + + public String getName() { + return ((Utf8StringConstantEntry) owner.get(nameIndex, Tag.Utf8StringConstant)).value; + } + + public void accept(final Visitor visitor) { + visitor.visitPackage(this); + } + + @Override + public String toString() { + return "PackageEntry[nameIndex: " + nameIndex + "]"; + } + + @Override + public Object getConstantValue() { + return getName(); + } + } + public static final class DoubleConstantEntry extends ConstantEntry { public final double value; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java index 8696710f..25518064 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/AttributeNames.java @@ -42,6 +42,10 @@ public final class AttributeNames { public static final String AnnotationDefault = "AnnotationDefault"; public static final String MethodParameters = "MethodParameters"; public static final String Record = "Record"; + public static final String Module = "Module"; + public static final String ModulePackages = "ModulePackages"; + public static final String ModuleMainClass = "ModuleMainClass"; + public static final String ModuleTarget = "ModuleTarget"; private AttributeNames() { throw ContractUtils.unreachable(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleAttribute.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleAttribute.java new file mode 100644 index 00000000..86af6eda --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleAttribute.java @@ -0,0 +1,86 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.annotations.Nullable; +import com.strobel.assembler.metadata.Flags; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.ArrayUtilities; +import com.strobel.core.VerifyArgument; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; + +public final class ModuleAttribute extends SourceAttribute { + private final String _name; + private final String _version; + private final EnumSet _flags; + private final List _requires; + private final List _exports; + private final List _opens; + private final List _uses; + private final List _provides; + + public ModuleAttribute( + final int length, + final String moduleName, + final String version, + final int flags, + final ModuleDependency[] requires, + final PackageInfo[] exports, + final PackageInfo[] opens, + final TypeReference[] uses, + final ServiceInfo[] provides) { + + super(AttributeNames.Module, length); + + _name = VerifyArgument.notNull(moduleName, "moduleName"); + _version = version; + _flags = Flags.asFlagSet(flags, Flags.Kind.Module); + _requires = ArrayUtilities.isNullOrEmpty(requires) ? Collections.emptyList() : ArrayUtilities.asUnmodifiableList(requires); + _exports = ArrayUtilities.isNullOrEmpty(exports) ? Collections.emptyList() : ArrayUtilities.asUnmodifiableList(exports); + _opens = ArrayUtilities.isNullOrEmpty(opens) ? Collections.emptyList() : ArrayUtilities.asUnmodifiableList(opens); + _uses = ArrayUtilities.isNullOrEmpty(uses) ? Collections.emptyList() : ArrayUtilities.asUnmodifiableList(uses); + _provides = ArrayUtilities.isNullOrEmpty(provides) ? Collections.emptyList() : ArrayUtilities.asUnmodifiableList(provides); + } + + @NotNull + public final String getModuleName() { + return _name; + } + + @NotNull + public final EnumSet getFlags() { + return _flags; + } + + @Nullable + public final String getVersion() { + return _version; + } + + @NotNull + public final List getRequires() { + return _requires; + } + + @NotNull + public final List getExports() { + return _exports; + } + + @NotNull + public final List getOpens() { + return _opens; + } + + @NotNull + public final List getUses() { + return _uses; + } + + @NotNull + public List getProvides() { + return _provides; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleDependency.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleDependency.java new file mode 100644 index 00000000..05c1a784 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleDependency.java @@ -0,0 +1,37 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.annotations.Nullable; +import com.strobel.assembler.metadata.Flags; +import com.strobel.core.VerifyArgument; + +import java.util.EnumSet; + +public final class ModuleDependency { + public final static ModuleDependency[] EMPTY = new ModuleDependency[0]; + + private final String _name; + private final String _version; + private final EnumSet _flags; + + public ModuleDependency(final @NotNull String name, final @Nullable String version, final int flags) { + _name = VerifyArgument.notNull(name, "name"); + _version = version; + _flags = Flags.asFlagSet(flags, Flags.Kind.Requires); + } + + @NotNull + public final String getName() { + return _name; + } + + @Nullable + public final String getVersion() { + return _version; + } + + @NotNull + public EnumSet getFlags() { + return _flags; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleMainClassAttribute.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleMainClassAttribute.java new file mode 100644 index 00000000..238f2b1f --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleMainClassAttribute.java @@ -0,0 +1,20 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.VerifyArgument; + +public final class ModuleMainClassAttribute extends SourceAttribute { + private final TypeReference _mainClass; + + public ModuleMainClassAttribute(final TypeReference mainClass) { + super(AttributeNames.ModuleMainClass, 8); + _mainClass = VerifyArgument.notNull(mainClass, "mainClass"); + } + + @NotNull + public final TypeReference getMainClass() { + return _mainClass; + } +} + diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModulePackagesAttribute.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModulePackagesAttribute.java new file mode 100644 index 00000000..badf2878 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModulePackagesAttribute.java @@ -0,0 +1,39 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.assembler.metadata.PackageReference; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.ArrayUtilities; +import com.strobel.core.VerifyArgument; + +import java.util.Collections; +import java.util.List; + +public final class ModulePackagesAttribute extends SourceAttribute { + private final List _packages; + + public ModulePackagesAttribute(final String[] packages) { + super(AttributeNames.ModulePackages, 8 + (packages == null ? 0 : packages.length * 2)); + + if (ArrayUtilities.isNullOrEmpty(packages)) { + _packages = Collections.emptyList(); + } + else { + VerifyArgument.noNullElements(packages, "packages"); + + final PackageReference[] packageReferences = new PackageReference[packages.length]; + + for (int i = 0; i < packageReferences.length; i++) { + packageReferences[i] = PackageReference.parse(packages[i]); + } + + _packages = ArrayUtilities.asUnmodifiableList(packageReferences); + } + } + + @NotNull + public final List getPackages() { + return _packages; + } +} + diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleTargetAttribute.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleTargetAttribute.java new file mode 100644 index 00000000..17878a4b --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ModuleTargetAttribute.java @@ -0,0 +1,18 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.core.VerifyArgument; + +public final class ModuleTargetAttribute extends SourceAttribute { + private final String _platform; + + public ModuleTargetAttribute(final String platform) { + super(AttributeNames.ModuleTarget, 5); + _platform = VerifyArgument.notNull(platform, "platform"); + } + + @NotNull + public final String getPlatform() { + return _platform; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/PackageInfo.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/PackageInfo.java new file mode 100644 index 00000000..4b7e8733 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/PackageInfo.java @@ -0,0 +1,55 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.assembler.metadata.Flags; +import com.strobel.assembler.metadata.ModuleReference; +import com.strobel.assembler.metadata.PackageReference; +import com.strobel.core.ArrayUtilities; +import com.strobel.core.VerifyArgument; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; + +public final class PackageInfo { + public final static PackageInfo[] EMPTY = new PackageInfo[0]; + + private final EnumSet _flags; + private final PackageReference _package; + private final List _modules; + + public PackageInfo(final String name, final int flags, final String[] modules) { + _package = PackageReference.parse(VerifyArgument.notNull(name, "name")); + _flags = Flags.asFlagSet(flags, Flags.Kind.ExportsOpens); + + if (ArrayUtilities.isNullOrEmpty(modules)) { + _modules = Collections.emptyList(); + } + else { + VerifyArgument.noNullElements(modules, "modules"); + + final ModuleReference[] moduleReferences = new ModuleReference[modules.length]; + + for (int i = 0; i < moduleReferences.length; i++) { + moduleReferences[i] = new ModuleReference(modules[i], null); + } + + _modules = ArrayUtilities.asUnmodifiableList(moduleReferences); + } + } + + @NotNull + public EnumSet getFlags() { + return _flags; + } + + @NotNull + public final PackageReference getPackage() { + return _package; + } + + @NotNull + public final List getModules() { + return _modules; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ServiceInfo.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ServiceInfo.java new file mode 100644 index 00000000..452fb663 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/ir/attributes/ServiceInfo.java @@ -0,0 +1,33 @@ +package com.strobel.assembler.ir.attributes; + +import com.strobel.annotations.NotNull; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.ArrayUtilities; +import com.strobel.core.VerifyArgument; + +import java.util.Collections; +import java.util.List; + +public final class ServiceInfo { + public final static ServiceInfo[] EMPTY = new ServiceInfo[0]; + + private final TypeReference _interface; + private final List _implementations; + + public ServiceInfo(final TypeReference serviceInterface, final TypeReference[] implementations) { + _interface = VerifyArgument.notNull(serviceInterface, "serviceInterface"); + + _implementations = ArrayUtilities.isNullOrEmpty(implementations) ? Collections.emptyList() + : ArrayUtilities.asUnmodifiableList(implementations); + } + + @NotNull + public TypeReference getInterface() { + return _interface; + } + + @NotNull + public List getImplementations() { + return _implementations; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java index ac9a50d5..0481fc17 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ClassFileReader.java @@ -310,11 +310,161 @@ protected SourceAttribute readAttributeCore(final String name, final Buffer buff return new RecordAttribute(length, ArrayUtilities.asUnmodifiableList(components)); } + + case AttributeNames.Module: { + // Module_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // + // u2 module_name_index; + // u2 module_flags; + // u2 module_version_index; + // + // u2 requires_count; {...} requires[requires_count]; + // u2 exports_count; {...} exports[exports_count]; + // u2 opens_count; {...} opens[opens_count]; + // u2 uses_count; {...} uses[uses_count]; + // u2 provides_count; {...} provides[provides_count]; + // } + + final String moduleName = _scope.lookupConstant(buffer.readUnsignedShort()); + final int flags = buffer.readUnsignedShort(); + final int versionIndex = buffer.readUnsignedShort(); + final String moduleVersion = versionIndex > 0 ? _scope.lookupConstant(versionIndex) : null; + + final int requiresCount = buffer.readUnsignedShort(); + final ModuleDependency[] requires = requiresCount > 0 ? new ModuleDependency[requiresCount] : ModuleDependency.EMPTY; + + for (int i = 0; i < requiresCount; i++) { + requires[i] = readModuleDependency(buffer); + } + + final int exportsCount = buffer.readUnsignedShort(); + final PackageInfo[] exports = exportsCount > 0 ? new PackageInfo[exportsCount] : PackageInfo.EMPTY; + + for (int i = 0; i < exportsCount; i++) { + exports[i] = readPackageInfo(buffer); + } + + final int opensCount = buffer.readUnsignedShort(); + final PackageInfo[] opens = opensCount > 0 ? new PackageInfo[opensCount] : PackageInfo.EMPTY; + + for (int i = 0; i < opensCount; i++) { + opens[i] = readPackageInfo(buffer); + } + + final int usesCount = buffer.readUnsignedShort(); + final TypeReference[] uses = usesCount > 0 ? new TypeReference[usesCount] : TypeReference.EMPTY_REFERENCES; + + for (int i = 0; i < usesCount; i++) { + uses[i] = _scope.lookupType(buffer.readUnsignedShort()); + } + + final int providesCount = buffer.readUnsignedShort(); + final ServiceInfo[] provides = providesCount > 0 ? new ServiceInfo[providesCount] : ServiceInfo.EMPTY; + + for (int i = 0; i < providesCount; i++) { + provides[i] = readServiceInfo(buffer); + } + + return new ModuleAttribute(length, moduleName, moduleVersion, flags, requires, exports, opens, uses, provides); + } + + case AttributeNames.ModulePackages: { + // ModulePackages_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 package_count; + // u2 package_index[package_count]; + // } + + final int packageCount = buffer.readUnsignedShort(); + final String[] packages = packageCount > 0 ? new String[packageCount] : EmptyArrayCache.EMPTY_STRING_ARRAY; + + for (int i = 0; i < packageCount; i++) { + packages[i] = _scope.lookupConstant(buffer.readUnsignedShort()); + } + + return new ModulePackagesAttribute(packages); + } + + case AttributeNames.ModuleMainClass: { + // ModuleMainClass_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 main_class_index; + // } + + return new ModuleMainClassAttribute(_scope.lookupType(buffer.readUnsignedShort())); + } + + case AttributeNames.ModuleTarget: { + // ModuleTarget_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 target_index; + // } + + return new ModuleTargetAttribute(_scope.lookupConstant(buffer.readUnsignedShort())); + } } return super.readAttributeCore(name, buffer, originalOffset, length); } + protected final ModuleDependency readModuleDependency(final Buffer buffer) { + // u2 requires_count; + // { u2 requires_index; + // u2 requires_flags; + // u2 requires_version_index; + // } requires[requires_count]; + + final String name = _scope.lookupConstant(buffer.readUnsignedShort()); + final int flags = buffer.readUnsignedShort(); + final int versionIndex = buffer.readUnsignedShort(); + final String version = versionIndex > 0 ? _scope.lookupConstant(versionIndex) : null; + + return new ModuleDependency(name, version, flags); + } + + protected final PackageInfo readPackageInfo(final Buffer buffer) { + // u2 opens_count; + // { u2 opens_index; + // u2 opens_flags; + // u2 opens_to_count; + // u2 opens_to_index[opens_to_count]; + // } opens[opens_count]; + + final String name = _scope.lookupConstant(buffer.readUnsignedShort()); + final int flags = buffer.readUnsignedShort(); + final int moduleCount = buffer.readUnsignedShort(); + final String[] modules = moduleCount > 0 ? new String[moduleCount] : EmptyArrayCache.EMPTY_STRING_ARRAY; + + for (int i = 0; i < moduleCount; i++) { + modules[i] = _scope.lookupConstant(buffer.readUnsignedShort()); + } + + return new PackageInfo(name, flags, modules); + } + + protected final ServiceInfo readServiceInfo(final Buffer buffer) { + // u2 provides_count; + // { u2 provides_index; + // u2 provides_with_count; + // u2 provides_with_index[provides_with_count]; + // } provides[provides_count]; + + final TypeReference serviceInterface = _scope.lookupType(buffer.readUnsignedShort()); + final int implementationCount = buffer.readUnsignedShort(); + final TypeReference[] implementations = implementationCount > 0 ? new TypeReference[implementationCount] : TypeReference.EMPTY_REFERENCES; + + for (int i = 0; i < implementationCount; i++) { + implementations[i] = _scope.lookupType(buffer.readUnsignedShort()); + } + + return new ServiceInfo(serviceInterface, implementations); + } + @SuppressWarnings("ConstantConditions") private void readAttributesPhaseOne(final Buffer buffer, final SourceAttribute[] attributes) { for (int i = 0; i < attributes.length; i++) { @@ -496,6 +646,7 @@ final TypeDefinition readClass() { populateNamedInnerTypes(); populateAnonymousInnerTypes(); checkEnclosingMethodAttributes(); + checkModuleAttribute(); } finally { if (declaringMethod != null) { @@ -514,6 +665,18 @@ final TypeDefinition readClass() { } } + private void checkModuleAttribute() { + final ModuleAttribute moduleAttribute = SourceAttribute.find(AttributeNames.Module, _attributes); + + if (moduleAttribute != null && + _typeDefinition.getDeclaredMethods().isEmpty() && + _typeDefinition.getDeclaredFields().isEmpty() && + _typeDefinition.getDeclaredTypes().isEmpty()) { + + _typeDefinition.setFlags(_typeDefinition.getFlags() | Flags.MODULE); + } + } + private void checkEnclosingMethodAttributes() { final InnerClassesAttribute innerClasses = SourceAttribute.find(AttributeNames.InnerClasses, _attributes); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ConstantPoolPrinter.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ConstantPoolPrinter.java index e6c7d844..d5cf2b2d 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ConstantPoolPrinter.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ConstantPoolPrinter.java @@ -288,6 +288,20 @@ public void visitUtf8StringConstant(final ConstantPool.Utf8StringConstantEntry i DecompilerHelpers.writePrimitiveValue(_output, info.getConstantValue()); } + @Override + public void visitModule(final ConstantPool.ModuleEntry info) { + _output.writeDelimiter("#"); + _output.writeLiteral(format("%1$-14d", info.nameIndex)); + _output.writeComment(format("// %1$s", StringUtilities.escape(info.getName(), false, _settings.isUnicodeOutputEnabled()))); + } + + @Override + public void visitPackage(final ConstantPool.PackageEntry info) { + _output.writeDelimiter("#"); + _output.writeLiteral(format("%1$-14d", info.nameIndex)); + _output.writeComment(format("// %1$s", StringUtilities.escape(info.getName(), false, _settings.isUnicodeOutputEnabled()))); + } + @Override public void visitEnd() { } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java index c0733ddd..b9992e5b 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/Flags.java @@ -166,6 +166,45 @@ public static EnumSet asFlagSet(final long mask) { public static EnumSet asFlagSet(final long mask, final Kind kind) { final EnumSet flags = EnumSet.noneOf(Flag.class); + if (kind == Kind.Module) { + if ((mask & ACC_OPEN) != 0) { + flags.add(Flag.OPEN); + } + if ((mask & ACC_SYNTHETIC) != 0) { + flags.add(Flag.SYNTHETIC); + } + if ((mask & ACC_MANDATED) != 0) { + flags.add(Flag.MANDATED); + } + return flags; + } + + if (kind == Kind.Requires) { + if ((mask & ACC_TRANSITIVE) != 0) { + flags.add(Flag.TRANSITIVE); + } + if ((mask & ACC_STATIC_PHASE) != 0) { + flags.add(Flag.STATIC_PHASE); + } + if ((mask & ACC_SYNTHETIC) != 0) { + flags.add(Flag.SYNTHETIC); + } + if ((mask & ACC_MANDATED) != 0) { + flags.add(Flag.MANDATED); + } + return flags; + } + + if (kind == Kind.ExportsOpens) { + if ((mask & ACC_SYNTHETIC) != 0) { + flags.add(Flag.SYNTHETIC); + } + if ((mask & ACC_MANDATED) != 0) { + flags.add(Flag.MANDATED); + } + return flags; + } + if ((mask & PUBLIC) != 0) { flags.add(Flag.PUBLIC); } @@ -267,7 +306,10 @@ public static enum Kind { Class, InnerClass, Field, - Method + Method, + Module, + Requires, + ExportsOpens } /* @@ -311,10 +353,19 @@ public static enum Kind { // bit positions, we translate them when reading and writing class // files into unique bits positions: ACC_SYNTHETIC <-> SYNTHETIC, // for example. - public static final int ACC_FINAL = 0x0010; - public static final int ACC_SUPER = 0x0020; - public static final int ACC_BRIDGE = 0x0040; - public static final int ACC_VARARGS = 0x0080; + public static final int ACC_FINAL = 0x0010; + public static final int ACC_SUPER = 0x0020; + public static final int ACC_BRIDGE = 0x0040; + public static final int ACC_VARARGS = 0x0080; + public static final int ACC_MODULE = 0x8000; + + public static final int ACC_OPEN = 0x0020; + public static final int ACC_SYNTHETIC = 0x1000; + public static final int ACC_MANDATED = 0x8000; + + public static final int ACC_TRANSITIVE = 0x0020; + public static final int ACC_STATIC_PHASE = 0x0040; + /***************************************** * Internal compiler flags (no bits in the lower 16). @@ -488,6 +539,11 @@ public static enum Kind { */ public static final long DEOBFUSCATED = 1L << 47; + /** + * Flag to indicate class symbol is for module-info + */ + public static final long MODULE = 1L<<51; + /** * Flag to indicate that a class is a record. The flag is also used to mark fields that are * part of the state vector of a record and to mark the canonical constructor @@ -524,6 +580,9 @@ public static enum Kind { * Modifier masks. */ public static final int + ModuleFlags = ACC_OPEN | ACC_SYNTHETIC | ACC_MANDATED, + RequiresFlags = ACC_TRANSITIVE | ACC_STATIC_PHASE | ACC_SYNTHETIC | ACC_MANDATED, + ExportsOpensFlags = ACC_SYNTHETIC | ACC_MANDATED, AccessFlags = PUBLIC | PROTECTED | PRIVATE, LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, StaticLocalFlags = LocalClassFlags | STATIC | INTERFACE, @@ -597,7 +656,7 @@ public static Set asModifierSet(final long flags) { return modifiers; } - + public static int toModifiers(final long flags) { int modifiers = 0; @@ -713,7 +772,10 @@ public enum Flag { ACYCLIC("acyclic"), PARAMETER("parameter"), VARARGS("varargs"), - PACKAGE("package"); + PACKAGE("package"), + OPEN("open"), + TRANSITIVE("transitive"), + STATIC_PHASE("static"); public final String name; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ModuleReference.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ModuleReference.java new file mode 100644 index 00000000..64369885 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ModuleReference.java @@ -0,0 +1,25 @@ +package com.strobel.assembler.metadata; + +import com.strobel.annotations.NotNull; +import com.strobel.annotations.Nullable; +import com.strobel.core.VerifyArgument; + +public final class ModuleReference { + private final String _name; + private final String _version; + + public ModuleReference(final @NotNull String name, final @Nullable String version) { + _name = VerifyArgument.notNull(name, "name"); + _version = version; + } + + @NotNull + public final String getName() { + return _name; + } + + @Nullable + public final String getVersion() { + return _version; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/PackageReference.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/PackageReference.java index 014349d8..d68a719a 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/PackageReference.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/PackageReference.java @@ -28,6 +28,7 @@ public class PackageReference { private final String _name; private String _fullName; + private String _internalName; private PackageReference() { _parent = null; @@ -58,12 +59,24 @@ public final String getFullName() { _fullName = getName(); } else { - _fullName = _parent.getFullName() + "." + getName(); + _fullName = _parent.getFullName() + '.' + getName(); } } return _fullName; } + public final String getInternalName() { + if (_internalName == null) { + if (_parent == null || _parent.equals(GLOBAL)) { + _internalName = getName(); + } + else { + _internalName = _parent.getInternalName() + '/' + getName(); + } + } + return _internalName; + } + public final PackageReference getParent() { return _parent; } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java index 6467fdce..01737a20 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeDefinition.java @@ -387,6 +387,10 @@ public final boolean isRecord() { return Flags.testAny(getFlags(), Flags.RECORD); } + public final boolean isModule() { + return Flags.testAny(getFlags(), Flags.MODULE); + } + public final boolean isAnonymous() { return Flags.testAny(getFlags(), Flags.ANONYMOUS); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeReference.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeReference.java index 25738999..77717e31 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeReference.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/TypeReference.java @@ -26,6 +26,8 @@ import java.util.List; public abstract class TypeReference extends MemberReference implements IGenericParameterProvider, IGenericContext { + public final static TypeReference[] EMPTY_REFERENCES = new TypeReference[0]; + private String _name; private TypeReference _declaringType; private ArrayType _arrayType; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/AnsiTextOutput.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/AnsiTextOutput.java index d580d2a9..55d55d5e 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/AnsiTextOutput.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/AnsiTextOutput.java @@ -18,6 +18,8 @@ import com.strobel.assembler.ir.Instruction; import com.strobel.assembler.ir.OpCode; +import com.strobel.assembler.ir.attributes.ModuleAttribute; +import com.strobel.assembler.ir.attributes.ModuleDependency; import com.strobel.assembler.metadata.*; import com.strobel.core.StringUtilities; import com.strobel.decompiler.ast.AstCode; @@ -57,6 +59,7 @@ static String get(final char c) { private final Ansi _type; private final Ansi _typeVariable; private final Ansi _package; + private final Ansi _module; private final Ansi _method; private final Ansi _field; private final Ansi _local; @@ -91,6 +94,7 @@ public AnsiTextOutput(final Writer writer, final ColorScheme colorScheme) { _type = new Ansi(Ansi.Attribute.NORMAL, new Ansi.AnsiColor(light ? 25 : 45), null); _typeVariable = new Ansi(Ansi.Attribute.NORMAL, new Ansi.AnsiColor(light ? 29 : 79), null); _package = new Ansi(Ansi.Attribute.NORMAL, new Ansi.AnsiColor(light ? 32 : 111), null); + _module = new Ansi(Ansi.Attribute.NORMAL, new Ansi.AnsiColor(light ? 38 : 117), null); _method = new Ansi(Ansi.Attribute.NORMAL, new Ansi.AnsiColor(light ? 162 : 212), null); _field = new Ansi(Ansi.Attribute.NORMAL, new Ansi.AnsiColor(light ? 136 : 222), null); _local = new Ansi(Ansi.Attribute.NORMAL, (Ansi.AnsiColor) null, null); @@ -186,9 +190,7 @@ public void writeDefinition(final String text, final Object definition, final bo else if (definition instanceof TypeReference) { colorizedText = colorizeType(text, (TypeReference) definition); } - else if (definition instanceof MethodReference || - definition instanceof IMethodSignature) { - + else if (definition instanceof IMethodSignature) { colorizedText = colorize(text, _method); } else if (definition instanceof FieldReference) { @@ -234,9 +236,7 @@ public void writeReference(final String text, final Object reference, final bool else if (reference instanceof TypeReference) { colorizedText = colorizeType(text, (TypeReference) reference); } - else if (reference instanceof MethodReference || - reference instanceof IMethodSignature) { - + else if (reference instanceof IMethodSignature) { colorizedText = colorize(text, _method); } else if (reference instanceof FieldReference) { @@ -251,6 +251,12 @@ else if (reference instanceof VariableReference || else if (reference instanceof PackageReference) { colorizedText = colorizePackage(text); } + else if (reference instanceof ModuleAttribute || + reference instanceof ModuleDependency || + reference instanceof ModuleReference) { + + colorizedText = colorize(text, _module); + } else if (reference instanceof Label || reference instanceof com.strobel.decompiler.ast.Label) { @@ -263,7 +269,6 @@ else if (reference instanceof Label || writeAnsi(text, colorizedText); } - @SuppressWarnings("ConstantConditions") private String colorizeType(final String text, final TypeReference type) { return colorizeTypeCore(new StringBuilder(), text, type).toString(); } @@ -359,6 +364,7 @@ private StringBuilder colorizeTypeCore(final StringBuilder sb, final String text return sb; } + @SuppressWarnings("UnusedReturnValue") private StringBuilder colorizeDelimitedName(final StringBuilder sb, final String typeName, final Ansi typeColor) { final int end = typeName.length(); @@ -375,6 +381,7 @@ private StringBuilder colorizeDelimitedName(final StringBuilder sb, final String switch (ch) { case '[': case '.': + case '/': case '$': sb.append(colorize(typeName.substring(start, i), typeColor)); sb.append(colorize(Delimiters.get(ch), _delimiter)); @@ -393,25 +400,8 @@ private StringBuilder colorizeDelimitedName(final StringBuilder sb, final String } private String colorizePackage(final String text) { - final String[] packageParts = text.split("\\."); final StringBuilder sb = new StringBuilder(text.length() * 2); - - for (int i = 0; i < packageParts.length; i++) { - if (i != 0) { - sb.append(colorize(".", _delimiter)); - } - - final String packagePart = packageParts[i]; - - if ("*".equals(packagePart)) { - sb.append(packagePart); - } - else { - sb.append(colorize(packagePart, _package)); - } - } - - return sb.toString(); + return colorizeDelimitedName(sb, text, _package).toString(); } public enum ColorScheme { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/BytecodeLanguage.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/BytecodeLanguage.java index bfa32e3a..7c5bca88 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/BytecodeLanguage.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/BytecodeLanguage.java @@ -37,6 +37,7 @@ import static java.lang.String.format; +@SuppressWarnings("DuplicatedCode") public class BytecodeLanguage extends Language { @Override public String getName() { @@ -54,23 +55,36 @@ public TypeDecompilationResults decompileType(final TypeDefinition type, final I VerifyArgument.notNull(output, "output"); VerifyArgument.notNull(options, "options"); - if (type.isInterface()) { - if (type.isAnnotation()) { - output.writeKeyword("@interface"); + final ModuleAttribute moduleAttribute = type.isModule() ? SourceAttribute.find(AttributeNames.Module, type.getSourceAttributes()) + : null; + + final boolean isModule = moduleAttribute != null; + + if (isModule) { + output.writeKeyword("module"); + output.write(' '); + output.writeReference(moduleAttribute.getModuleName(), moduleAttribute); + } + else { + if (type.isInterface()) { + if (type.isAnnotation()) { + output.writeKeyword("@interface"); + } + else { + output.writeKeyword("interface"); + } + } + else if (type.isEnum()) { + output.writeKeyword("enum"); } else { - output.writeKeyword("interface"); + output.writeKeyword("class"); } - } - else if (type.isEnum()) { - output.writeKeyword("enum"); - } - else { - output.writeKeyword("class"); + + output.write(' '); + DecompilerHelpers.writeType(output, type, NameSyntax.TYPE_NAME, true); } - output.write(' '); - DecompilerHelpers.writeType(output, type, NameSyntax.TYPE_NAME, true); output.writeLine(); output.indent(); @@ -95,19 +109,25 @@ else if (type.isEnum()) { } } - for (final FieldDefinition field : type.getDeclaredFields()) { + if (isModule) { output.writeLine(); - decompileField(field, output, options); + writeModuleBody(output, moduleAttribute, false); } + else { + for (final FieldDefinition field : type.getDeclaredFields()) { + output.writeLine(); + decompileField(field, output, options); + } - for (final MethodDefinition method : type.getDeclaredMethods()) { - output.writeLine(); + for (final MethodDefinition method : type.getDeclaredMethods()) { + output.writeLine(); - try { - decompileMethod(method, output, options); - } - catch (final MethodBodyParseException e) { - writeMethodBodyParseError(output, e); + try { + decompileMethod(method, output, options); + } + catch (final MethodBodyParseException e) { + writeMethodBodyParseError(output, e); + } } } } @@ -125,6 +145,211 @@ else if (type.isEnum()) { return new TypeDecompilationResults(null /*no line number mapping*/); } + private static boolean newlineIfNeeded(final ITextOutput output, final boolean needNewLine) { + if (needNewLine) { + output.writeLine(); + } + return false; + } + + public static void writeModuleBody(final ITextOutput output, final ModuleAttribute module, final boolean useDottedNames) { + final List requires = module.getRequires(); + + boolean needNewLine = false; + boolean lastHadMultiple = false; + + for (int i = 0, n = requires.size(); i < n; i++) { + writeModuleDependency(output, requires.get(i)); + needNewLine = true; + } + + needNewLine = newlineIfNeeded(output, needNewLine); + + final List exports = module.getExports(); + + for (int i = 0, n = exports.size(); i < n; i++) { + final PackageInfo export = exports.get(i); + final boolean hasMultiple = export.getModules().size() > 1; + + if (lastHadMultiple || hasMultiple && i > 0) { + output.writeLine(); + } + + writePackageInfo(output, export, useDottedNames, true); + lastHadMultiple = hasMultiple; + needNewLine = true; + } + + needNewLine = newlineIfNeeded(output, needNewLine); + lastHadMultiple = false; + + final List opens = module.getOpens(); + + for (int i = 0, n = opens.size(); i < n; i++) { + final PackageInfo open = opens.get(i); + final boolean hasMultiple = open.getModules().size() > 1; + + if (lastHadMultiple || hasMultiple && i > 0) { + output.writeLine(); + } + + writePackageInfo(output, open, useDottedNames, false); + lastHadMultiple = hasMultiple; + needNewLine = true; + } + + needNewLine = newlineIfNeeded(output, needNewLine); + // lastHadMultiple = false; + + final List uses = module.getUses(); + + for (int i = 0, n = uses.size(); i < n; i++) { + final TypeReference reference = uses.get(i); + + output.writeKeyword("uses"); + output.write(' '); + output.writeReference(useDottedNames ? reference.getFullName() : reference.getInternalName(), reference); + output.writeDelimiter(";"); + output.writeLine(); + needNewLine = true; + } + + newlineIfNeeded(output, needNewLine); + lastHadMultiple = false; + + final List provides = module.getProvides(); + + for (int i = 0, n = provides.size(); i < n; i++) { + final ServiceInfo service = provides.get(i); + final boolean hasMultiple = service.getImplementations().size() > 1; + + if (lastHadMultiple || hasMultiple && i > 0) { + output.writeLine(); + } + + writeServiceInfo(output, service, useDottedNames); + lastHadMultiple = hasMultiple; + } + } + + private static void writeModuleDependency(final ITextOutput output, final ModuleDependency dependency) { + final EnumSet flags = dependency.getFlags(); + + output.writeKeyword("requires"); + output.write(' '); + + if (flags.contains(Flags.Flag.TRANSITIVE)) { + output.writeKeyword(Flags.Flag.TRANSITIVE.name); + output.write(' '); + } + + if (flags.contains(Flags.Flag.STATIC_PHASE)) { + output.writeKeyword(Flags.Flag.STATIC_PHASE.name); + output.write(' '); + } + + output.writeReference(dependency.getName(), dependency); + + if (flags.contains(Flags.Flag.MANDATED)) { + output.write(' '); + output.writeKeyword(Flags.Flag.MANDATED.name); + } + + output.writeDelimiter(";"); + output.writeLine(); + } + + private static void writePackageInfo(final ITextOutput output, final PackageInfo export, final boolean useDottedNames, final boolean isExport) { + final EnumSet flags = export.getFlags(); + final int startColumn = output.getColumn(); + + output.writeKeyword(isExport ? "exports" : "opens"); + output.write(' '); + + if (flags.contains(Flags.Flag.TRANSITIVE)) { + output.writeKeyword(Flags.Flag.TRANSITIVE.name); + output.write(' '); + } + + if (flags.contains(Flags.Flag.STATIC_PHASE)) { + output.writeKeyword(Flags.Flag.STATIC_PHASE.name); + output.write(' '); + } + + final String packageName = useDottedNames ? export.getPackage().getFullName() : export.getPackage().getInternalName(); + + output.writeReference(packageName, export.getPackage()); + + if (flags.contains(Flags.Flag.MANDATED)) { + output.write(' '); + output.writeKeyword(Flags.Flag.MANDATED.name); + } + + final List modules = export.getModules(); + + if (!modules.isEmpty()) { + output.write(' '); + output.writeKeyword("to"); + output.write(' '); + + final int endColumn = output.getColumn(); + final int padding = endColumn - startColumn; + final String paddingText = padding > 0 ? StringUtilities.repeat(' ', padding) : ""; + + for (int i = 0, n = modules.size(); i < n; i++) { + final ModuleReference module = modules.get(i); + + if (i > 0) { + output.writeDelimiter(","); + output.writeLine(); + output.write(paddingText); + } + + output.writeReference(module.getName(), module); + } + } + + output.writeDelimiter(";"); + output.writeLine(); + } + + private static void writeServiceInfo(final ITextOutput output, final ServiceInfo service, final boolean useDottedNames) { + final List implementations = service.getImplementations(); + final int startColumn = output.getColumn(); + + output.writeKeyword("provides"); + output.write(' '); + + final TypeReference serviceInterface = service.getInterface(); + + output.writeReference(useDottedNames ? serviceInterface.getFullName() : serviceInterface.getInternalName(), serviceInterface); + + if (!implementations.isEmpty()) { + output.write(' '); + output.writeKeyword("with"); + output.write(' '); + + final int endColumn = output.getColumn(); + final int padding = endColumn - startColumn; + final String paddingText = padding > 0 ? StringUtilities.repeat(' ', padding) : ""; + + for (int i = 0, n = implementations.size(); i < n; i++) { + final TypeReference implementation = implementations.get(i); + + if (i > 0) { + output.writeDelimiter(","); + output.writeLine(); + output.write(paddingText); + } + + output.writeReference(useDottedNames ? implementation.getFullName() : implementation.getInternalName(), implementation); + } + } + + output.writeDelimiter(";"); + output.writeLine(); + } + private void writeMethodBodyParseError(final ITextOutput output, final Throwable error) { output.indent(); @@ -146,31 +371,33 @@ private void writeMethodBodyParseError(final ITextOutput output, final Throwable private void writeTypeAttribute(final ITextOutput output, final TypeDefinition type, final SourceAttribute attribute) { if (attribute instanceof BlobAttribute) { writeBlobAttribute(output, (BlobAttribute) attribute); - return; } switch (attribute.getName()) { case AttributeNames.SourceFile: { - output.writeAttribute(AttributeNames.SourceFile); - output.write(": "); + assert attribute instanceof SourceFileAttribute; + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); output.writeTextLiteral(((SourceFileAttribute) attribute).getSourceFile()); output.writeLine(); break; } case AttributeNames.Deprecated: { - output.writeAttribute(AttributeNames.Deprecated); + output.writeAttribute(attribute.getName()); output.writeLine(); break; } case AttributeNames.EnclosingMethod: { + assert attribute instanceof EnclosingMethodAttribute; + final TypeReference enclosingType = ((EnclosingMethodAttribute) attribute).getEnclosingType(); final MethodReference enclosingMethod = ((EnclosingMethodAttribute) attribute).getEnclosingMethod(); if (enclosingType != null) { output.writeAttribute("EnclosingType"); - output.write(": "); + output.writeDelimiter(": "); output.writeReference(enclosingType.getInternalName(), enclosingType); output.writeLine(); } @@ -178,8 +405,8 @@ private void writeTypeAttribute(final ITextOutput output, final TypeDefinition t if (enclosingMethod != null) { final TypeReference declaringType = enclosingMethod.getDeclaringType(); - output.writeAttribute(AttributeNames.EnclosingMethod); - output.write(": "); + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); output.writeReference(declaringType.getInternalName(), declaringType); output.writeDelimiter("."); output.writeReference(enclosingMethod.getName(), enclosingMethod); @@ -192,11 +419,14 @@ private void writeTypeAttribute(final ITextOutput output, final TypeDefinition t } case AttributeNames.InnerClasses: { + assert attribute instanceof InnerClassesAttribute; + final InnerClassesAttribute innerClasses = (InnerClassesAttribute) attribute; final List entries = innerClasses.getEntries(); - output.writeAttribute(AttributeNames.InnerClasses); - output.writeLine(": "); + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); + output.writeLine(); output.indent(); try { @@ -212,19 +442,22 @@ private void writeTypeAttribute(final ITextOutput output, final TypeDefinition t } case AttributeNames.Signature: { - output.writeAttribute(AttributeNames.Signature); - output.write(": "); + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); DecompilerHelpers.writeGenericSignature(output, type); output.writeLine(); break; } case AttributeNames.BootstrapMethods: { + assert attribute instanceof BootstrapMethodsAttribute; + final BootstrapMethodsAttribute innerClasses = (BootstrapMethodsAttribute) attribute; final List entries = innerClasses.getBootstrapMethods(); - output.writeAttribute(AttributeNames.BootstrapMethods); - output.writeLine(": "); + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); + output.writeLine(); output.indent(); try { @@ -232,8 +465,8 @@ private void writeTypeAttribute(final ITextOutput output, final TypeDefinition t for (final BootstrapMethodsTableEntry entry : entries) { output.writeLiteral(i++); - output.write(": "); - writeBootstrapMethodEntry(output, type, entry); + output.writeDelimiter(": "); + writeBootstrapMethodEntry(output, entry); } } finally { @@ -242,6 +475,70 @@ private void writeTypeAttribute(final ITextOutput output, final TypeDefinition t break; } + + case AttributeNames.ModulePackages: { + assert attribute instanceof ModulePackagesAttribute; + + final ModulePackagesAttribute packagesAttribute = (ModulePackagesAttribute) attribute; + final List packages = packagesAttribute.getPackages(); + final int startColumn = output.getColumn(); + + output.writeAttribute(packagesAttribute.getName()); + output.writeDelimiter(": "); + + if (packages.isEmpty()) { + output.writeLine(); + break; + } + + final int endColumn = output.getColumn(); + final int padding = endColumn - startColumn; + final String paddingText = padding > 0 ? StringUtilities.repeat(' ', padding) : ""; + + for (int i = 0; i < packages.size(); i++) { + if (i > 0) { + output.write(paddingText); + } + + final PackageReference reference = packages.get(i); + + output.writeReference(reference.getFullName(), reference); + output.writeLine(); + } + + break; + } + + case AttributeNames.ModuleMainClass: { + assert attribute instanceof ModuleMainClassAttribute; + + final ModuleMainClassAttribute mainClass = (ModuleMainClassAttribute) attribute; + + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); + output.writeLine(); + DecompilerHelpers.writeType(output, mainClass.getMainClass()); + + break; + } + + case AttributeNames.ModuleTarget: { + assert attribute instanceof ModuleTargetAttribute; + + final ModuleTargetAttribute mainClass = (ModuleTargetAttribute) attribute; + + output.writeAttribute(attribute.getName()); + output.writeDelimiter(": "); + output.writeTextLiteral(mainClass.getPlatform()); + output.writeLine(); + + break; + } + + case AttributeNames.Module: { + // Write during type output. + break; + } } } @@ -265,7 +562,7 @@ private void writeBlobAttribute(final ITextOutput output, final BlobAttribute at output.writeLine(); } - private void writeBootstrapMethodEntry(final ITextOutput output, final TypeDefinition type, final BootstrapMethodsTableEntry entry) { + private void writeBootstrapMethodEntry(final ITextOutput output, final BootstrapMethodsTableEntry entry) { DecompilerHelpers.writeMethodHandle(output, entry.getMethodHandle()); output.writeLine(); @@ -437,7 +734,7 @@ public void decompileField(final FieldDefinition field, final ITextOutput output try { if (formattingOptions.showFieldFlags) { output.writeAttribute("Flags"); - output.write(": "); + output.writeDelimiter(": "); for (int i = 0; i < flagStrings.size(); i++) { if (i != 0) { @@ -472,7 +769,7 @@ private void writeFieldAttribute(final ITextOutput output, final FieldDefinition final Object constantValue = ((ConstantValueAttribute) attribute).getValue(); output.writeAttribute("ConstantValue"); - output.write(": "); + output.writeDelimiter(": "); if (constantValue != null) { final String typeDescriptor = constantValue.getClass().getName().replace('.', '/'); @@ -496,7 +793,7 @@ private void writeFieldAttribute(final ITextOutput output, final FieldDefinition case AttributeNames.Signature: { output.writeAttribute("Signature"); - output.write(": "); + output.writeDelimiter(": "); DecompilerHelpers.writeType(output, field.getFieldType(), NameSyntax.SIGNATURE, false); output.writeLine(); break; @@ -636,7 +933,7 @@ private void writeMethodHeader(final ITextOutput output, final MethodDefinition try { output.writeAttribute("Flags"); - output.write(": "); + output.writeDelimiter(": "); for (int i = 0; i < flagStrings.size(); i++) { if (i != 0) { @@ -990,7 +1287,7 @@ private void writeMethodBody(final ITextOutput output, final MethodDefinition me } } - @SuppressWarnings({ "ConstantConditions", "UnusedParameters" }) + @SuppressWarnings("UnusedParameters") private void writeMethodEnd(final ITextOutput output, final MethodDefinition method, final DecompilationOptions options) { final MethodBody body = method.getBody(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/EntityType.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/EntityType.java index a9ef62b0..fdca2813 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/EntityType.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/EntityType.java @@ -23,5 +23,6 @@ public enum EntityType { FIELD, METHOD, CONSTRUCTOR, - PARAMETER + PARAMETER, + MODULE_DEFINITION } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java index 85d3c0c7..e8149526 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaFormattingOptions.java @@ -34,6 +34,7 @@ public class JavaFormattingOptions { public BraceStyle InterfaceBraceStyle = BraceStyle.DoNotChange; public BraceStyle AnnotationBraceStyle = BraceStyle.DoNotChange; public BraceStyle EnumBraceStyle = BraceStyle.DoNotChange; + public BraceStyle ModuleBraceStyle = BraceStyle.DoNotChange; public BraceStyle RecordBraceStyle = BraceStyle.EndOfLine; public BraceStyle MethodBraceStyle = BraceStyle.DoNotChange; public BraceStyle InitializerBlockBraceStyle = BraceStyle.DoNotChange; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java index 73f84676..60c7758f 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java @@ -18,6 +18,7 @@ import com.strobel.assembler.ir.attributes.AttributeNames; import com.strobel.assembler.ir.attributes.LineNumberTableAttribute; +import com.strobel.assembler.ir.attributes.ModuleAttribute; import com.strobel.assembler.ir.attributes.SourceAttribute; import com.strobel.assembler.metadata.FieldDefinition; import com.strobel.assembler.metadata.Flags; @@ -31,6 +32,7 @@ import com.strobel.core.VerifyArgument; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.ITextOutput; +import com.strobel.decompiler.languages.BytecodeLanguage; import com.strobel.decompiler.languages.LineNumberPosition; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.TextOutputFormatter.LineNumberMode; @@ -201,7 +203,7 @@ void openBrace(final BraceStyle style) { writeSpecialsUpToRole(Roles.LEFT_BRACE); space( - (style == BraceStyle.EndOfLine || style == BraceStyle.BannerStyle) && + (style == BraceStyle.EndOfLine || style == BraceStyle.BannerStyle || style == BraceStyle.DoNotChange) && lastWritten != LastWritten.Whitespace && lastWritten != LastWritten.LeftParenthesis ); @@ -1732,6 +1734,25 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) return null; } + @Override + public Void visitModuleDeclaration(final ModuleDeclaration node, final Void ignored) { + startNode(node); + + final TypeDefinition type = node.getUserData(Keys.TYPE_DEFINITION); + final ModuleAttribute attribute = SourceAttribute.find(AttributeNames.Module, type.getSourceAttributes()); + final BraceStyle braceStyle = policy.ModuleBraceStyle; + + writeKeyword(Roles.MODULE_KEYWORD); + node.getNameToken().acceptVisitor(this, ignored); + openBrace(braceStyle); + + BytecodeLanguage.writeModuleBody(output, attribute, true); + + closeBrace(braceStyle); + newLine(); + return null; + } + @Override public Void visitCompilationUnit(final CompilationUnit node, final Void ignored) { for (final AstNode child : node.getChildren()) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/TextOutputFormatter.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/TextOutputFormatter.java index 3dbc9046..a4f81781 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/TextOutputFormatter.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/TextOutputFormatter.java @@ -18,6 +18,7 @@ import com.strobel.assembler.metadata.MemberReference; import com.strobel.assembler.metadata.MethodReference; +import com.strobel.assembler.metadata.ModuleReference; import com.strobel.assembler.metadata.PackageReference; import com.strobel.assembler.metadata.ParameterDefinition; import com.strobel.assembler.metadata.TypeReference; @@ -40,7 +41,7 @@ public class TextOutputFormatter implements IOutputFormatter { private boolean firstUsingDeclaration; private boolean lastUsingDeclaration; private LineNumberMode lineNumberMode; - + /** * whether or not to emit debug line number comments into the source code */ @@ -48,15 +49,21 @@ public enum LineNumberMode { WITH_DEBUG_LINE_NUMBERS, WITHOUT_DEBUG_LINE_NUMBERS, } - - /** when writing out line numbers, keeps track of the most recently used one to avoid redundancy */ + + /** + * when writing out line numbers, keeps track of the most recently used one to avoid redundancy + */ private int lastObservedLineNumber = OffsetToLineNumberConverter.UNKNOWN_LINE_NUMBER; - - /** converts from bytecode offset to line number */ + + /** + * converts from bytecode offset to line number + */ private OffsetToLineNumberConverter offset2LineNumber = OffsetToLineNumberConverter.NOOP_CONVERTER; - - /** maps original line numbers to decompiler-emitted line numbers and columns */ - private final List lineNumberPositions = new ArrayList(); + + /** + * maps original line numbers to decompiler-emitted line numbers and columns + */ + private final List lineNumberPositions = new ArrayList(); @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final Stack startLocations = new Stack<>(); @@ -80,35 +87,36 @@ public void startNode(final AstNode node) { } nodeStack.push(node); - + // Build a data structure of Java line numbers. int offset = Expression.MYSTERY_OFFSET; String prefix = null; - if ( node instanceof Expression) { + if (node instanceof Expression) { offset = ((Expression) node).getOffset(); prefix = "/*EL:"; - } else if ( node instanceof Statement) { + } + else if (node instanceof Statement) { offset = ((Statement) node).getOffset(); prefix = "/*SL:"; } - if ( offset != Expression.MYSTERY_OFFSET) { + if (offset != Expression.MYSTERY_OFFSET) { // Convert to a line number. - int lineNumber = offset2LineNumber.getLineForOffset( offset); - if ( lineNumber > lastObservedLineNumber) { + int lineNumber = offset2LineNumber.getLineForOffset(offset); + if (lineNumber > lastObservedLineNumber) { // Record a data structure mapping original to actual line numbers. int lineOfComment = output.getRow(); int columnOfComment = output.getColumn(); - LineNumberPosition pos = new LineNumberPosition( lineNumber, lineOfComment, columnOfComment); - lineNumberPositions.add( pos); + LineNumberPosition pos = new LineNumberPosition(lineNumber, lineOfComment, columnOfComment); + lineNumberPositions.add(pos); lastObservedLineNumber = lineNumber; - if ( lineNumberMode == LineNumberMode.WITH_DEBUG_LINE_NUMBERS) { + if (lineNumberMode == LineNumberMode.WITH_DEBUG_LINE_NUMBERS) { // Emit a comment showing the original line number. String commentStr = prefix + lineNumber + "*/"; - output.writeComment( commentStr); + output.writeComment(commentStr); } } } - + startLocations.push(new TextLocation(output.getRow(), output.getColumn())); if (node instanceof EntityDeclaration && @@ -151,6 +159,13 @@ public void writeIdentifier(final String identifier) { return; } + reference = getCurrentModuleReference(); + + if (reference != null) { + output.writeReference(identifier, reference); + return; + } + reference = getCurrentTypeReference(); if (reference != null) { @@ -434,6 +449,12 @@ private Object getCurrentDefinition() { return null; } + definition = parent.getUserData(Keys.MODULE_REFERENCE); + + if (definition != null) { + return definition; + } + definition = parent.getUserData(Keys.TYPE_DEFINITION); if (definition != null) { @@ -478,6 +499,25 @@ private MemberReference getCurrentTypeReference() { return null; } + private ModuleReference getCurrentModuleReference() { + final AstNode node = nodeStack.peek(); + final ModuleReference moduleReference = node.getUserData(Keys.MODULE_REFERENCE); + + if (moduleReference != null) { + return moduleReference; + } + + if (node instanceof Identifier) { + final AstNode parent = node.getParent(); + + if (parent instanceof ModuleDeclaration) { + return parent.getUserData(Keys.MODULE_REFERENCE); + } + } + + return null; + } + private PackageReference getCurrentPackageReference() { final AstNode node = nodeStack.peek(); @@ -580,13 +620,13 @@ private boolean isImportDeclaration(final AstNode node) { } @Override - public void resetLineNumberOffsets( OffsetToLineNumberConverter offset2LineNumber) { + public void resetLineNumberOffsets(OffsetToLineNumberConverter offset2LineNumber) { // Forget what we used to know about the stream of line number offsets and start from // scratch. Also capture the new converter., lastObservedLineNumber = OffsetToLineNumberConverter.UNKNOWN_LINE_NUMBER; this.offset2LineNumber = offset2LineNumber; } - + /** * Returns the mapping from original to decompiler-emitted line numbers. */ diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstBuilder.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstBuilder.java index 566a07ff..f53e9713 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstBuilder.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstBuilder.java @@ -19,6 +19,7 @@ import com.strobel.assembler.ir.attributes.AnnotationDefaultAttribute; import com.strobel.assembler.ir.attributes.AttributeNames; import com.strobel.assembler.ir.attributes.LineNumberTableAttribute; +import com.strobel.assembler.ir.attributes.ModuleAttribute; import com.strobel.assembler.ir.attributes.SourceAttribute; import com.strobel.assembler.metadata.*; import com.strobel.assembler.metadata.annotations.*; @@ -122,7 +123,16 @@ public final void runTransformations(final Predicate transformAbo } public final void addType(final TypeDefinition type) { - final TypeDeclaration astType = createType(type); + if (type.isModule()) { + final ModuleAttribute attribute = SourceAttribute.find(AttributeNames.Module, type.getSourceAttributes()); + + if (attribute != null) { + _compileUnit.addChild(createModuleNoCache(type, attribute), CompilationUnit.MODULE_ROLE); + return; + } + } + + final EntityDeclaration astType = createType(type); final String packageName = type.getPackageName(); if (_compileUnit.getPackage().isNull() && !StringUtilities.isNullOrWhitespace(packageName)) { @@ -150,6 +160,28 @@ public final TypeDeclaration createType(final TypeDefinition type) { return createTypeNoCache(type); } + protected final ModuleDeclaration createModuleNoCache(final TypeDefinition type, final ModuleAttribute attribute) { + VerifyArgument.notNull(type, "type"); + + final TypeDefinition oldCurrentType = _context.getCurrentType(); + + _context.setCurrentType(type); + + try { + final ModuleDeclaration declaration = new ModuleDeclaration(); + + declaration.setName(attribute.getModuleName()); + declaration.putUserData(Keys.TYPE_DEFINITION, type); + declaration.putUserData(Keys.MODULE_REFERENCE, new ModuleReference(attribute.getModuleName(), attribute.getVersion())); + + return declaration; + + } + finally { + _context.setCurrentType(oldCurrentType); + } + } + protected final TypeDeclaration createTypeNoCache(final TypeDefinition type) { VerifyArgument.notNull(type, "type"); @@ -320,17 +352,17 @@ else if (type.hasSuperBound()) { else { final TypeReference typeToImport; - String unqualifiedName; + StringBuilder unqualifiedName; if (packageDeclaration != null && StringUtilities.equals(packageDeclaration.getName(), nameSource.getPackageName())) { - unqualifiedName = nameSource.getSimpleName(); - name = unqualifiedName; + unqualifiedName = new StringBuilder(nameSource.getSimpleName()); + name = unqualifiedName.toString(); } if (nameSource.isNested()) { - unqualifiedName = nameSource.getSimpleName(); + unqualifiedName = new StringBuilder(nameSource.getSimpleName()); TypeReference current = nameSource; @@ -341,15 +373,15 @@ else if (type.hasSuperBound()) { break; } - unqualifiedName = current.getSimpleName() + "." + unqualifiedName; + unqualifiedName.insert(0, current.getSimpleName() + "."); } - name = unqualifiedName; + name = unqualifiedName.toString(); typeToImport = current; } else { typeToImport = nameSource; - unqualifiedName = nameSource.getSimpleName(); + unqualifiedName = new StringBuilder(nameSource.getSimpleName()); } if (options.getAddImports() && !areImportsSuppressed() && !_typeDeclarations.containsKey(typeToImport.getInternalName())) { @@ -377,7 +409,7 @@ else if (type.hasSuperBound()) { if (name == null) { if (importedName.equals(typeToImport.getFullName())) { - name = unqualifiedName; + name = unqualifiedName.toString(); } else { final String packageName = nameSource.getPackageName(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CompilationUnit.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CompilationUnit.java index b05a11f7..20670d55 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CompilationUnit.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CompilationUnit.java @@ -27,6 +27,7 @@ public class CompilationUnit extends AstNode { public final static Role TYPE_ROLE = Roles.TOP_LEVEL_TYPE_ROLE; + public final static Role MODULE_ROLE = Roles.MODULE; public final static Role IMPORT_ROLE = new Role<>("Import", ImportDeclaration.class, ImportDeclaration.NULL); private AstNode _topExpression; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/DepthFirstAstVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/DepthFirstAstVisitor.java index df9538c2..6cc7948f 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/DepthFirstAstVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/DepthFirstAstVisitor.java @@ -244,6 +244,11 @@ public S visitTypeDeclaration(final TypeDeclaration node, final T data) { return visitChildren(node, data); } + @Override + public S visitModuleDeclaration(final ModuleDeclaration node, final T data) { + return visitChildren(node, data); + } + @Override public S visitCompilationUnit(final CompilationUnit node, final T data) { return visitChildren(node, data); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IAstVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IAstVisitor.java index b80c5ebb..0db48ccb 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IAstVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IAstVisitor.java @@ -59,6 +59,7 @@ public interface IAstVisitor { R visitParameterDeclaration(ParameterDeclaration node, T data); R visitFieldDeclaration(FieldDeclaration node, T data); R visitTypeDeclaration(TypeDeclaration node, T data); + R visitModuleDeclaration(ModuleDeclaration node, T data); R visitCompilationUnit(CompilationUnit node, T data); R visitPackageDeclaration(PackageDeclaration node, T data); R visitArraySpecifier(ArraySpecifier node, T data); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/JavaNameResolver.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/JavaNameResolver.java index a8d5ad37..66e9261a 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/JavaNameResolver.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/JavaNameResolver.java @@ -62,7 +62,7 @@ private static List resolveCore( private final static class FindDeclarationVisitor implements IAstVisitor> { private final NameResolveMode _mode; - private boolean _isStaticContext = false; + private boolean _isStaticContext; FindDeclarationVisitor(final NameResolveMode mode, final boolean isStaticContext) { _mode = VerifyArgument.notNull(mode, "mode"); @@ -707,6 +707,11 @@ public Set visitTypeDeclaration(final TypeDeclaration node, final String ); } + @Override + public Set visitModuleDeclaration(final ModuleDeclaration node, final String data) { + return Collections.emptySet(); + } + @Override public Set visitLocalTypeDeclarationStatement(final LocalTypeDeclarationStatement node, final String name) { final TypeDeclaration typeDeclaration = node.getTypeDeclaration(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Keys.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Keys.java index 3d0d8250..b063c57c 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Keys.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Keys.java @@ -16,6 +16,7 @@ package com.strobel.decompiler.languages.java.ast; +import com.strobel.assembler.ir.attributes.ModuleAttribute; import com.strobel.assembler.metadata.*; import com.strobel.componentmodel.Key; import com.strobel.core.ArrayUtilities; @@ -35,6 +36,7 @@ public final class Keys { public final static Key FIELD_DEFINITION = Key.create("FieldDefinition"); public final static Key METHOD_DEFINITION = Key.create("MethodDefinition"); public final static Key TYPE_DEFINITION = Key.create("TypeDefinition"); + public final static Key MODULE_REFERENCE = Key.create("ModuleReference"); public final static Key TYPE_REFERENCE = Key.create("TypeReference"); public final static Key ANONYMOUS_BASE_TYPE_REFERENCE = Key.create("AnonymousBaseTypeReference"); public final static Key DYNAMIC_CALL_SITE = Key.create("DynamicCallSite"); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ModuleDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ModuleDeclaration.java new file mode 100644 index 00000000..495a3208 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ModuleDeclaration.java @@ -0,0 +1,90 @@ +/* + * ModuleDeclaration.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package com.strobel.decompiler.languages.java.ast; + +import com.strobel.decompiler.languages.EntityType; +import com.strobel.decompiler.patterns.INode; +import com.strobel.decompiler.patterns.Match; + +public class ModuleDeclaration extends EntityDeclaration { + public final JavaTokenNode getLeftBraceToken() { + return getChildByRole(Roles.LEFT_BRACE); + } + + public final AstNodeCollection getMembers() { + return getChildrenByRole(Roles.TYPE_MEMBER); + } + + public final JavaTokenNode getRightBraceToken() { + return getChildByRole(Roles.RIGHT_BRACE); + } + + @Override + public NodeType getNodeType() { + return NodeType.MODULE_DECLARATION; + } + + @Override + public EntityType getEntityType() { + return EntityType.MODULE_DEFINITION; + } + + @Override + public R acceptVisitor(final IAstVisitor visitor, final T data) { + return visitor.visitModuleDeclaration(this, data); + } + + @Override + public ModuleDeclaration clone() { + return (ModuleDeclaration) super.clone(); + } + + @Override + public boolean matches(final INode other, final Match match) { + if (other instanceof ModuleDeclaration) { + final ModuleDeclaration otherDeclaration = (ModuleDeclaration) other; + + return !otherDeclaration.isNull() && + matchString(getName(), otherDeclaration.getName()); + } + + return false; + } + + // + + public final static ModuleDeclaration NULL = new NullModuleDeclaration(); + + private static final class NullModuleDeclaration extends ModuleDeclaration { + @Override + public final boolean isNull() { + return true; + } + + @Override + public R acceptVisitor(final IAstVisitor visitor, final T data) { + return null; + } + + @Override + public boolean matches(final INode other, final Match match) { + return other == null || other.isNull(); + } + } + + // +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NodeType.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NodeType.java index b86f1311..45bd4ed1 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NodeType.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NodeType.java @@ -25,5 +25,6 @@ public enum NodeType { EXPRESSION, TOKEN, WHITESPACE, - PATTERN + PATTERN, + MODULE_DECLARATION } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java index e799d55f..0b29961e 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Roles.java @@ -45,6 +45,7 @@ public final class Roles { public final static Role ANNOTATION = new Role<>("Annotation", Annotation.class); public final static Role VARIABLE = new Role<>("Variable", VariableInitializer.class, VariableInitializer.NULL); public final static Role TYPE_MEMBER = new Role<>("TypeMember", EntityDeclaration.class); + public final static Role MODULE = new Role<>("Module", ModuleDeclaration.class, ModuleDeclaration.NULL); public final static Role TOP_LEVEL_TYPE_ROLE = new Role<>("TopLevelType", TypeDeclaration.class, TypeDeclaration.NULL); public final static Role LOCAL_TYPE_DECLARATION = new Role<>("LocalTypeDeclaration", TypeDeclaration.class, TypeDeclaration.NULL); public final static Role THROWN_TYPE = new Role<>("ThrownType", AstType.class, AstType.NULL); @@ -83,6 +84,7 @@ public final class Roles { public final static TokenRole RECORD_KEYWORD = new TokenRole("record", TokenRole.FLAG_KEYWORD); public final static TokenRole INTERFACE_KEYWORD = new TokenRole("interface", TokenRole.FLAG_KEYWORD); public final static TokenRole CLASS_KEYWORD = new TokenRole("class", TokenRole.FLAG_KEYWORD); + public final static TokenRole MODULE_KEYWORD = new TokenRole("module", TokenRole.FLAG_KEYWORD); public final static TokenRole ANNOTATION_KEYWORD = new TokenRole("@interface", TokenRole.FLAG_KEYWORD); public final static TokenRole EXTENDS_KEYWORD = new TokenRole("extends", TokenRole.FLAG_KEYWORD); public final static TokenRole IMPLEMENTS_KEYWORD = new TokenRole("implements", TokenRole.FLAG_KEYWORD); From 1c82d296bdc8c4dfdbe92b8ab9e084e98caabc90 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Sat, 12 Jun 2021 16:13:50 -0400 Subject: [PATCH 04/35] Added a transform to handle the newer try-with-resources patterns. --- .../MergeResourceTryStatementsVisitor.java | 73 +++++ .../NewTryWithResourcesTransform.java | 266 ++++++++++++++++++ .../transforms/TransformationPipeline.java | 2 + .../transforms/TryWithResourcesTransform.java | 81 +----- 4 files changed, 350 insertions(+), 72 deletions(-) create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java new file mode 100644 index 00000000..073d429b --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java @@ -0,0 +1,73 @@ +package com.strobel.decompiler.languages.java.ast.transforms; + +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.BlockStatement; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.TryCatchStatement; +import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; + +import java.util.ArrayList; +import java.util.List; + +public class MergeResourceTryStatementsVisitor extends ContextTrackingVisitor { + public MergeResourceTryStatementsVisitor(final DecompilerContext context) { + super(context); + } + + @Override + public Void visitTryCatchStatement(final TryCatchStatement node, final Void data) { + super.visitTryCatchStatement(node, data); + + if (node.getResources().isEmpty()) { + return null; + } + + final List resources = new ArrayList<>(); + + TryCatchStatement current = node; + + while (current.getCatchClauses().isEmpty() && + current.getFinallyBlock().isNull()) { + + final AstNode parent = current.getParent(); + + if (parent instanceof BlockStatement && + parent.getParent() instanceof TryCatchStatement) { + + final TryCatchStatement parentTry = (TryCatchStatement) parent.getParent(); + + if (parentTry.getTryBlock().getStatements().hasSingleElement()) { + if (!current.getResources().isEmpty()) { + resources.addAll(0, current.getResources()); + } + + current = parentTry; + continue; + } + } + + break; + } + + final BlockStatement tryContent = node.getTryBlock(); + + if (current != node) { + for (final VariableDeclarationStatement resource : resources) { + resource.remove(); + current.getResources().add(resource); + } + + tryContent.remove(); + current.setTryBlock(tryContent); + + final BlockStatement block = current.getFinallyBlock(); + + if (!block.isNull() && block.getStatements().isEmpty()) { + block.remove(); + } + } + + return null; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java new file mode 100644 index 00000000..be5509ef --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java @@ -0,0 +1,266 @@ +/* + * NewTryWithResourcesTransform.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package com.strobel.decompiler.languages.java.ast.transforms; + +import com.strobel.core.Predicate; +import com.strobel.core.StringUtilities; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.patterns.*; +import com.strobel.decompiler.semantics.ResolveResult; + +import javax.lang.model.element.Modifier; + +import static com.strobel.core.CollectionUtilities.*; + +public class NewTryWithResourcesTransform extends ContextTrackingVisitor { + private final Statement _resourceDeclaration; + private final TryCatchStatement _tryPattern; + private final JavaResolver _resolver; + + public NewTryWithResourcesTransform(final DecompilerContext context) { + super(context); + + _resolver = new JavaResolver(context); + + final VariableDeclarationStatement rv = new VariableDeclarationStatement( + new AnyNode().toType(), + Pattern.ANY_STRING, + new AnyNode().toExpression() + ); + + rv.addModifier(Modifier.FINAL); + + _resourceDeclaration = new NamedNode("resource", rv).toStatement(); + + final TryCatchStatement tryPattern = new TryCatchStatement(Expression.MYSTERY_OFFSET); + + + + + final TryCatchStatement nestedTryWithResourceDisposal = new TryCatchStatement(); + + nestedTryWithResourceDisposal.setTryBlock(new AnyNode().toBlockStatement()); + nestedTryWithResourceDisposal.getCatchClauses().add(new Repeat(new AnyNode()).toCatchClause()); + nestedTryWithResourceDisposal.setFinallyBlock( + new BlockStatement( + new Repeat(new AnyNode()).toStatement(), + new NamedNode( + "resourceDisposal", + new Choice( + new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close")), + new IfElseStatement( + Expression.MYSTERY_OFFSET, + new BinaryOperatorExpression(new DeclaredVariableBackReference("resource").toExpression(), + BinaryOperatorType.INEQUALITY, + new NullReferenceExpression()), + new BlockStatement(new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close"))) + ) + ) + ).toStatement() + ) + ); + + + + + final BlockStatement tryContent = new NamedNode( + "tryContent", + new BlockStatement( + new Repeat(new AnyNode()).toStatement(), + new Choice( + new NamedNode( + "resourceDisposal", + new Choice( + new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close")), + new IfElseStatement( + Expression.MYSTERY_OFFSET, + new BinaryOperatorExpression(new DeclaredVariableBackReference("resource").toExpression(), + BinaryOperatorType.INEQUALITY, + new NullReferenceExpression()), + new BlockStatement(new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close"))) + ) + ) + ), + nestedTryWithResourceDisposal + ).toStatement(), + new Repeat( + new NamedNode( + "outerResourceDisposal", + new Choice( + new IfElseStatement( + Expression.MYSTERY_OFFSET, + new BinaryOperatorExpression(new NamedNode("otherId", + new IdentifierExpression(Expression.MYSTERY_OFFSET, + Pattern.ANY_STRING)).toExpression(), + BinaryOperatorType.INEQUALITY, + new NullReferenceExpression()), + new BlockStatement(new ExpressionStatement(new IdentifierExpressionBackReference("otherId").toExpression().invoke("close"))) + ), + new ExpressionStatement(new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING).invoke("close")) + ).toStatement() + ) + ).toStatement(), + new OptionalNode(new AnyNode("finalStatement")).toStatement() + ) + ).toBlockStatement(); + + tryPattern.setTryBlock(tryContent); + + final TryCatchStatement disposeTry = new TryCatchStatement(Expression.MYSTERY_OFFSET); + + disposeTry.setTryBlock(new BlockStatement(new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close")))); + + final Expression outerException = new NamedNode("error", new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression(); + + final CatchClause disposeCatch = new CatchClause( + new BlockStatement( + new ExpressionStatement( + outerException.invoke("addSuppressed", + new NamedNode("innerError", new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression()) + ) + ) + ); + + disposeCatch.setVariableName(Pattern.ANY_STRING); + disposeCatch.getExceptionTypes().add(new SimpleType("Throwable")); + + disposeTry.getCatchClauses().add(disposeCatch); + + final CatchClause catchClause = new CatchClause( + new BlockStatement( + new Choice(new NamedNode("disposeTry", disposeTry), + new IfElseStatement(Expression.MYSTERY_OFFSET, + new BinaryOperatorExpression(new DeclaredVariableBackReference("resource").toExpression(), + BinaryOperatorType.INEQUALITY, + new NullReferenceExpression()), + new BlockStatement(new NamedNode("disposeTry", disposeTry).toStatement()))).toStatement(), + new ThrowStatement(new BackReference("error").toExpression()) + ) + ); + + catchClause.setVariableName(Pattern.ANY_STRING); + catchClause.getExceptionTypes().add(new SimpleType("Throwable")); + tryPattern.getCatchClauses().add(catchClause); + + _tryPattern = tryPattern; + } + + @Override + public void run(final AstNode compilationUnit) { + if (_tryPattern == null) { + return; + } + + super.run(compilationUnit); + + new MergeResourceTryStatementsVisitor(context).run(compilationUnit); + } + + @Override + public Void visitTryCatchStatement(final TryCatchStatement node, final Void data) { + super.visitTryCatchStatement(node, data); + + if (!(node.getParent() instanceof BlockStatement)) { + return null; + } + + final BlockStatement parent = (BlockStatement) node.getParent(); + final Statement initializeResource = node.getPreviousSibling(BlockStatement.STATEMENT_ROLE); + + if (initializeResource == null) { + return null; + } + + final Match m = Match.createNew(); + + if (_resourceDeclaration.matches(initializeResource, m) && + _tryPattern.matches(node, m)) { + + final VariableDeclarationStatement resourceDeclaration = first(m.get("resource")); + + if (!(resourceDeclaration.getParent() instanceof BlockStatement)) { + return null; + } + + final ResolveResult resourceResult = _resolver.apply(resourceDeclaration); + + if (resourceResult == null || resourceResult.getType() == null) { + return null; + } + + final BlockStatement tryContent = first(m.get("tryContent")); + final IdentifierExpression caughtException = first(m.get("error")); + final IdentifierExpression innerError = first(m.get("innerError")); + + final CatchClause caughtParent = firstOrDefault(caughtException.getAncestors(CatchClause.class), + new Predicate() { + @Override + public boolean test(final CatchClause clause) { + return StringUtilities.equals(caughtException.getIdentifier(), clause.getVariableName()); + } + }); + + final CatchClause innerErrorParent = firstOrDefault(innerError.getAncestors(CatchClause.class), + new Predicate() { + @Override + public boolean test(final CatchClause clause) { + return StringUtilities.equals(innerError.getIdentifier(), clause.getVariableName()); + } + }); + + if (caughtParent == null || innerErrorParent == null || !caughtParent.isAncestorOf(innerErrorParent)) { + return null; + } + + final Statement lastStatement; + final Statement firstStatement = firstOrDefault(tryContent.getStatements()); + + if (firstStatement != null && (lastStatement = lastOrDefault(tryContent.getStatements())) != null) { + final DefiniteAssignmentAnalysis analysis = new DefiniteAssignmentAnalysis(context, tryContent); + + analysis.setAnalyzedRange(firstStatement, lastStatement); + + analysis.analyze(resourceDeclaration.getVariables().firstOrNullObject().getInitializer().getText(), + DefiniteAssignmentStatus.DEFINITELY_NOT_ASSIGNED); + + if (analysis.isPotentiallyAssigned()) { + // Resource declarations are effectively final; if it's reassigned, we can't rewrite. + return null; + } + } + + resourceDeclaration.remove(); + node.getResources().add(resourceDeclaration); + caughtParent.remove(); + + final AstNode resourceDisposal = firstOrDefault(m.get("resourceDisposal")); + + if (resourceDisposal != null) { + resourceDisposal.remove(); + } + + for (final Statement outerResourceDisposal : m.get("outerResourceDisposal")) { + outerResourceDisposal.remove(); + parent.getStatements().insertAfter(node, outerResourceDisposal); + } + } + + return null; + } +} + diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java index 761b7411..a590c3f9 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TransformationPipeline.java @@ -41,6 +41,8 @@ public static IAstTransform[] createPipeline(final DecompilerContext context) { new LabelCleanupTransform(context), new TryWithResourcesTransform(context), new DeclareVariablesTransform(context), + new NewTryWithResourcesTransform(context), + new MergeResourceTryStatementsVisitor(context), new StringSwitchRewriterTransform(context), new EclipseStringSwitchRewriterTransform(context), new SimplifyAssignmentsTransform(context), diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java index 08fdd348..d78ef020 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java @@ -27,15 +27,13 @@ import com.strobel.decompiler.semantics.ResolveResult; import javax.lang.model.element.Modifier; -import java.util.ArrayList; -import java.util.List; import static com.strobel.core.CollectionUtilities.*; import static com.strobel.decompiler.languages.java.ast.transforms.ConvertLoopsTransform.*; public class TryWithResourcesTransform extends ContextTrackingVisitor { - private final static INode RESOURCE_INIT_PATTERN; - private final static INode CLEAR_SAVED_EXCEPTION_PATTERN; + private final static INode J7_RESOURCE_INIT_PATTERN; + private final static INode J7_CLEAR_SAVED_EXCEPTION_PATTERN; static { final Expression resource = new NamedNode( @@ -48,7 +46,7 @@ public class TryWithResourcesTransform extends ContextTrackingVisitor { new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING) ).toExpression(); - RESOURCE_INIT_PATTERN = new ExpressionStatement( + J7_RESOURCE_INIT_PATTERN = new ExpressionStatement( new AssignmentExpression( resource, AssignmentOperatorType.ASSIGN, @@ -56,7 +54,7 @@ public class TryWithResourcesTransform extends ContextTrackingVisitor { ) ); - CLEAR_SAVED_EXCEPTION_PATTERN = new ExpressionStatement( + J7_CLEAR_SAVED_EXCEPTION_PATTERN = new ExpressionStatement( new AssignmentExpression( savedException, AssignmentOperatorType.ASSIGN, @@ -169,8 +167,6 @@ public void run(final AstNode compilationUnit) { } super.run(compilationUnit); - - new MergeResourceTryStatementsVisitor(context).run(compilationUnit); } @Override @@ -183,20 +179,17 @@ public Void visitTryCatchStatement(final TryCatchStatement node, final Void data final BlockStatement parent = (BlockStatement) node.getParent(); - final Statement p = node.getPreviousSibling(BlockStatement.STATEMENT_ROLE); - final Statement pp = p != null ? p.getPreviousSibling(BlockStatement.STATEMENT_ROLE) : null; + final Statement clearCaughtException = node.getPreviousSibling(BlockStatement.STATEMENT_ROLE); + final Statement initializeResource = clearCaughtException != null ? clearCaughtException.getPreviousSibling(BlockStatement.STATEMENT_ROLE) : null; - if (pp == null) { + if (initializeResource == null) { return null; } - final Statement initializeResource = pp; - final Statement clearCaughtException = p; - final Match m = Match.createNew(); - if (RESOURCE_INIT_PATTERN.matches(initializeResource, m) && - CLEAR_SAVED_EXCEPTION_PATTERN.matches(clearCaughtException, m) && + if (J7_RESOURCE_INIT_PATTERN.matches(initializeResource, m) && + J7_CLEAR_SAVED_EXCEPTION_PATTERN.matches(clearCaughtException, m) && _tryPattern.matches(node, m)) { final IdentifierExpression resource = first(m.get("resource")); @@ -311,60 +304,4 @@ public Void visitTryCatchStatement(final TryCatchStatement node, final Void data return null; } - - private final static class MergeResourceTryStatementsVisitor extends ContextTrackingVisitor { - MergeResourceTryStatementsVisitor(final DecompilerContext context) { - super(context); - } - - @Override - public Void visitTryCatchStatement(final TryCatchStatement node, final Void data) { - super.visitTryCatchStatement(node, data); - - if (node.getResources().isEmpty()) { - return null; - } - - final List resources = new ArrayList<>(); - - TryCatchStatement current = node; - - while (current.getCatchClauses().isEmpty() && - current.getFinallyBlock().isNull()) { - - final AstNode parent = current.getParent(); - - if (parent instanceof BlockStatement && - parent.getParent() instanceof TryCatchStatement) { - - final TryCatchStatement parentTry = (TryCatchStatement) parent.getParent(); - - if (parentTry.getTryBlock().getStatements().hasSingleElement()) { - if (!current.getResources().isEmpty()) { - resources.addAll(0, current.getResources()); - } - - current = parentTry; - continue; - } - } - - break; - } - - final BlockStatement tryContent = node.getTryBlock(); - - if (current != node) { - for (final VariableDeclarationStatement resource : resources) { - resource.remove(); - current.getResources().add(resource); - } - - tryContent.remove(); - current.setTryBlock(tryContent); - } - - return null; - } - } } From a7d29090d3c0580b6a625a0a3ec1b27e1521f7b6 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Sat, 12 Jun 2021 20:34:00 -0400 Subject: [PATCH 05/35] Minor improvements to try-with-resources handling. --- .../MergeResourceTryStatementsVisitor.java | 34 +++++ .../NewTryWithResourcesTransform.java | 138 ++++++++++++++---- .../transforms/TryWithResourcesTransform.java | 7 +- 3 files changed, 147 insertions(+), 32 deletions(-) diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java index 073d429b..38c036a3 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/MergeResourceTryStatementsVisitor.java @@ -4,15 +4,40 @@ import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.BlockStatement; import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.ExpressionStatement; import com.strobel.decompiler.languages.java.ast.TryCatchStatement; import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; +import com.strobel.decompiler.patterns.AnyNode; +import com.strobel.decompiler.patterns.DeclaredVariableBackReference; +import com.strobel.decompiler.patterns.Match; +import com.strobel.decompiler.patterns.NamedNode; +import com.strobel.decompiler.patterns.Pattern; +import javax.lang.model.element.Modifier; import java.util.ArrayList; import java.util.List; +import static com.strobel.core.CollectionUtilities.first; + public class MergeResourceTryStatementsVisitor extends ContextTrackingVisitor { + private final BlockStatement _emptyResource; + public MergeResourceTryStatementsVisitor(final DecompilerContext context) { super(context); + + final VariableDeclarationStatement rv = new VariableDeclarationStatement( + new AnyNode().toType(), + Pattern.ANY_STRING, + new AnyNode().toExpression() + ); + + rv.addModifier(Modifier.FINAL); + + _emptyResource = new BlockStatement( + new NamedNode("resourceDeclaration", rv).toStatement(), + new NamedNode("resourceDisposal", + new ExpressionStatement(new DeclaredVariableBackReference("resourceDeclaration").toExpression().invoke("close"))).toStatement() + ); } @Override @@ -30,6 +55,15 @@ public Void visitTryCatchStatement(final TryCatchStatement node, final Void data while (current.getCatchClauses().isEmpty() && current.getFinallyBlock().isNull()) { + final Match m = _emptyResource.match(current.getTryBlock()); + + if (m.success()) { + final VariableDeclarationStatement innerResource = first(m.get("resourceDeclaration")); + + current.getTryBlock().getStatements().clear(); + current.getResources().add(innerResource); + } + final AstNode parent = current.getParent(); if (parent instanceof BlockStatement && diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java index be5509ef..ffe4a834 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/NewTryWithResourcesTransform.java @@ -16,6 +16,10 @@ package com.strobel.decompiler.languages.java.ast.transforms; +import com.strobel.assembler.metadata.IMetadataResolver; +import com.strobel.assembler.metadata.MetadataHelper; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; import com.strobel.core.Predicate; import com.strobel.core.StringUtilities; import com.strobel.decompiler.DecompilerContext; @@ -32,6 +36,8 @@ public class NewTryWithResourcesTransform extends ContextTrackingVisitor { private final TryCatchStatement _tryPattern; private final JavaResolver _resolver; + private AstBuilder _builder; + public NewTryWithResourcesTransform(final DecompilerContext context) { super(context); @@ -43,41 +49,44 @@ public NewTryWithResourcesTransform(final DecompilerContext context) { new AnyNode().toExpression() ); - rv.addModifier(Modifier.FINAL); + rv.setAnyModifiers(true); - _resourceDeclaration = new NamedNode("resource", rv).toStatement(); + _resourceDeclaration = new Choice(new NamedNode("resource", rv), + new ExpressionStatement( + new AssignmentExpression(new NamedNode("resource", + new IdentifierExpression(Pattern.ANY_STRING)).toExpression(), + new AnyNode().toExpression()) + )).toStatement(); final TryCatchStatement tryPattern = new TryCatchStatement(Expression.MYSTERY_OFFSET); - - - final TryCatchStatement nestedTryWithResourceDisposal = new TryCatchStatement(); nestedTryWithResourceDisposal.setTryBlock(new AnyNode().toBlockStatement()); nestedTryWithResourceDisposal.getCatchClauses().add(new Repeat(new AnyNode()).toCatchClause()); + + final Expression resourceReference = new Choice(new DeclaredVariableBackReference("resource"), + new IdentifierExpressionBackReference("resource")).toExpression(); + nestedTryWithResourceDisposal.setFinallyBlock( new BlockStatement( new Repeat(new AnyNode()).toStatement(), new NamedNode( "resourceDisposal", new Choice( - new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close")), + new ExpressionStatement(resourceReference.invoke("close")), new IfElseStatement( Expression.MYSTERY_OFFSET, - new BinaryOperatorExpression(new DeclaredVariableBackReference("resource").toExpression(), + new BinaryOperatorExpression(resourceReference.clone(), BinaryOperatorType.INEQUALITY, new NullReferenceExpression()), - new BlockStatement(new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close"))) + new BlockStatement(new ExpressionStatement(resourceReference.clone().invoke("close"))) ) ) ).toStatement() ) ); - - - final BlockStatement tryContent = new NamedNode( "tryContent", new BlockStatement( @@ -86,13 +95,13 @@ public NewTryWithResourcesTransform(final DecompilerContext context) { new NamedNode( "resourceDisposal", new Choice( - new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close")), + new ExpressionStatement(resourceReference.clone().invoke("close")), new IfElseStatement( Expression.MYSTERY_OFFSET, - new BinaryOperatorExpression(new DeclaredVariableBackReference("resource").toExpression(), + new BinaryOperatorExpression(resourceReference.clone(), BinaryOperatorType.INEQUALITY, new NullReferenceExpression()), - new BlockStatement(new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close"))) + new BlockStatement(new ExpressionStatement(resourceReference.clone().invoke("close"))) ) ) ), @@ -105,13 +114,12 @@ public NewTryWithResourcesTransform(final DecompilerContext context) { new IfElseStatement( Expression.MYSTERY_OFFSET, new BinaryOperatorExpression(new NamedNode("otherId", - new IdentifierExpression(Expression.MYSTERY_OFFSET, - Pattern.ANY_STRING)).toExpression(), + new IdentifierExpression(Pattern.ANY_STRING)).toExpression(), BinaryOperatorType.INEQUALITY, new NullReferenceExpression()), new BlockStatement(new ExpressionStatement(new IdentifierExpressionBackReference("otherId").toExpression().invoke("close"))) ), - new ExpressionStatement(new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING).invoke("close")) + new ExpressionStatement(new IdentifierExpression(Pattern.ANY_STRING).invoke("close")) ).toStatement() ) ).toStatement(), @@ -123,15 +131,15 @@ public NewTryWithResourcesTransform(final DecompilerContext context) { final TryCatchStatement disposeTry = new TryCatchStatement(Expression.MYSTERY_OFFSET); - disposeTry.setTryBlock(new BlockStatement(new ExpressionStatement(new DeclaredVariableBackReference("resource").toExpression().invoke("close")))); + disposeTry.setTryBlock(new BlockStatement(new ExpressionStatement(resourceReference.clone().invoke("close")))); - final Expression outerException = new NamedNode("error", new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression(); + final Expression outerException = new NamedNode("error", new IdentifierExpression(Pattern.ANY_STRING)).toExpression(); final CatchClause disposeCatch = new CatchClause( new BlockStatement( new ExpressionStatement( outerException.invoke("addSuppressed", - new NamedNode("innerError", new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression()) + new NamedNode("innerError", new IdentifierExpression(Pattern.ANY_STRING)).toExpression()) ) ) ); @@ -145,7 +153,7 @@ public NewTryWithResourcesTransform(final DecompilerContext context) { new BlockStatement( new Choice(new NamedNode("disposeTry", disposeTry), new IfElseStatement(Expression.MYSTERY_OFFSET, - new BinaryOperatorExpression(new DeclaredVariableBackReference("resource").toExpression(), + new BinaryOperatorExpression(resourceReference.clone(), BinaryOperatorType.INEQUALITY, new NullReferenceExpression()), new BlockStatement(new NamedNode("disposeTry", disposeTry).toStatement()))).toStatement(), @@ -166,9 +174,22 @@ public void run(final AstNode compilationUnit) { return; } - super.run(compilationUnit); + final AstBuilder builder = context.getUserData(Keys.AST_BUILDER); - new MergeResourceTryStatementsVisitor(context).run(compilationUnit); + if (builder == null) { + return; + } + + final AstBuilder oldBuilder = _builder; + + _builder = builder; + + try { + super.run(compilationUnit); + } + finally { + _builder = oldBuilder; + } } @Override @@ -191,15 +212,22 @@ public Void visitTryCatchStatement(final TryCatchStatement node, final Void data if (_resourceDeclaration.matches(initializeResource, m) && _tryPattern.matches(node, m)) { - final VariableDeclarationStatement resourceDeclaration = first(m.get("resource")); + final AstNode declaration = first(m.get("resource")); - if (!(resourceDeclaration.getParent() instanceof BlockStatement)) { + if (!(declaration instanceof VariableDeclarationStatement || declaration instanceof IdentifierExpression)) { return null; } - final ResolveResult resourceResult = _resolver.apply(resourceDeclaration); + final Statement declarationStatement = declaration instanceof Statement ? (Statement) declaration : declaration.getParent(Statement.class); - if (resourceResult == null || resourceResult.getType() == null) { + if (declarationStatement == null || declarationStatement.isNull() || !(declarationStatement.getParent() instanceof BlockStatement)) { + return null; + } + + final TypeReference resourceType; + final ResolveResult resourceResult = _resolver.apply(declaration); + + if (resourceResult == null || (resourceType = resourceResult.getType()) == null || isDefinitelyNotCloseable(resourceType)) { return null; } @@ -235,8 +263,16 @@ public boolean test(final CatchClause clause) { analysis.setAnalyzedRange(firstStatement, lastStatement); - analysis.analyze(resourceDeclaration.getVariables().firstOrNullObject().getInitializer().getText(), - DefiniteAssignmentStatus.DEFINITELY_NOT_ASSIGNED); + final String resourceName; + + if (declaration instanceof VariableDeclarationStatement) { + resourceName = ((VariableDeclarationStatement) declaration).getVariables().firstOrNullObject().getName(); + } + else { + resourceName = ((IdentifierExpression) declaration).getIdentifier(); + } + + analysis.analyze(resourceName, DefiniteAssignmentStatus.DEFINITELY_NOT_ASSIGNED); if (analysis.isPotentiallyAssigned()) { // Resource declarations are effectively final; if it's reassigned, we can't rewrite. @@ -244,8 +280,28 @@ public boolean test(final CatchClause clause) { } } - resourceDeclaration.remove(); - node.getResources().add(resourceDeclaration); + final VariableDeclarationStatement vd; + + if (declaration instanceof VariableDeclarationStatement) { + vd = (VariableDeclarationStatement) declaration; + } + else { + final IdentifierExpression identifier = (IdentifierExpression) declaration; + final AssignmentExpression assignment = declaration.getParent(AssignmentExpression.class); + + if (assignment == null || assignment.isNull()) { + return null; + } + + final Expression initializer = assignment.getRight(); + + initializer.remove(); + vd = new VariableDeclarationStatement(_builder.convertType(resourceType), identifier.getIdentifier(), initializer); + } + + vd.addModifier(Modifier.FINAL); + declarationStatement.remove(); + node.getResources().add(vd); caughtParent.remove(); final AstNode resourceDisposal = firstOrDefault(m.get("resourceDisposal")); @@ -262,5 +318,25 @@ public boolean test(final CatchClause clause) { return null; } + + static boolean isDefinitelyNotCloseable(final TypeReference t) { + if (t == null) { + return true; + } + + final TypeDefinition resolved = t.resolve(); + + if (resolved == null) { + return false; + } + + final IMetadataResolver resolver = resolved.getResolver(); + final TypeReference autoCloseable = resolver.lookupType("java/lang/AutoCloseable"); + final TypeDefinition acResolved; + + return autoCloseable != null && + (acResolved = autoCloseable.resolve()) != null && + !MetadataHelper.isAssignableFrom(acResolved, resolved); + } } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java index d78ef020..5c2cd2a3 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java @@ -16,6 +16,7 @@ package com.strobel.decompiler.languages.java.ast.transforms; +import com.strobel.assembler.metadata.TypeReference; import com.strobel.decompiler.DecompilerContext; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.AnyNode; @@ -30,6 +31,7 @@ import static com.strobel.core.CollectionUtilities.*; import static com.strobel.decompiler.languages.java.ast.transforms.ConvertLoopsTransform.*; +import static com.strobel.decompiler.languages.java.ast.transforms.NewTryWithResourcesTransform.isDefinitelyNotCloseable; public class TryWithResourcesTransform extends ContextTrackingVisitor { private final static INode J7_RESOURCE_INIT_PATTERN; @@ -193,9 +195,12 @@ public Void visitTryCatchStatement(final TryCatchStatement node, final Void data _tryPattern.matches(node, m)) { final IdentifierExpression resource = first(m.get("resource")); + + @SuppressWarnings("DuplicatedCode") + final TypeReference resourceType; final ResolveResult resourceResult = _resolver.apply(resource); - if (resourceResult == null || resourceResult.getType() == null) { + if (resourceResult == null || (resourceType = resourceResult.getType()) == null || isDefinitelyNotCloseable(resourceType)) { return null; } From b63786992bc3afdecd2d87aaebed8a6d7ca387e9 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Sat, 12 Jun 2021 20:35:14 -0400 Subject: [PATCH 06/35] Addendum to last commit. --- .../languages/java/ast/IdentifierExpression.java | 12 ++++++++---- .../com/strobel/decompiler/EnhancedTryTests.java | 9 +++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IdentifierExpression.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IdentifierExpression.java index 68fa531c..d963dff5 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IdentifierExpression.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IdentifierExpression.java @@ -20,13 +20,17 @@ import com.strobel.decompiler.patterns.Match; public class IdentifierExpression extends Expression { - public IdentifierExpression( int offset, final String identifier) { - super( offset); + public IdentifierExpression(final String identifier) { + this(MYSTERY_OFFSET, identifier); + } + + public IdentifierExpression(final int offset, final String identifier) { + super(offset); setIdentifier(identifier); } - public IdentifierExpression( int offset, final Identifier identifier) { - super( offset); + public IdentifierExpression(final int offset, final Identifier identifier) { + super(offset); setIdentifierToken(identifier); } diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EnhancedTryTests.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EnhancedTryTests.java index 8700b224..75951177 100644 --- a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EnhancedTryTests.java +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EnhancedTryTests.java @@ -308,22 +308,20 @@ public void testEnhancedTryTwoNestedInnerCatchesRuntimeException() throws Throwa } @Test - @Ignore public void testEnhancedTryEmptyBody() throws Throwable { verifyOutput( J.class, defaultSettings(), "private static final class J {\n" + " public void test() throws IOException {\n" + - " try (final StringWriter writer1 = new StringWriter()) {\n" + - " }\n" + + " final StringWriter writer1 = new StringWriter();\n" + + " writer1.close();\n" + " }\n" + "}\n" ); } @Test - @Ignore public void testEnhancedTryTwoResourcesEmptyBody() throws Throwable { verifyOutput( K.class, @@ -331,8 +329,7 @@ public void testEnhancedTryTwoResourcesEmptyBody() throws Throwable { "private static final class K {\n" + " public void test() throws IOException {\n" + " try (final StringWriter writer1 = new StringWriter();\n" + - " final StringWriter writer2 = new StringWriter()) {\n" + - " }\n" + + " final StringWriter writer2 = new StringWriter()) {}\n" + " }\n" + "}\n" ); From 4dc7360e1ee0e97ccc1667619d17b18ba2977d40 Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Sun, 13 Jun 2021 13:18:05 -0400 Subject: [PATCH 07/35] Improvements to record class handling, specifically with alternate constructors and explicit (hiding) accessors. Some code cleanup here and there. --- .../strobel/decompiler/ast/AstOptimizer.java | 28 ++-- .../languages/java/JavaOutputVisitor.java | 117 ++++++----------- .../languages/java/ast/AstNode.java | 17 +-- .../languages/java/ast/AstNodeCollection.java | 24 +++- .../languages/java/ast/AstType.java | 2 +- .../languages/java/ast/BlockStatement.java | 2 +- .../languages/java/ast/CatchClause.java | 2 +- .../languages/java/ast/EntityDeclaration.java | 13 ++ .../languages/java/ast/Expression.java | 2 +- .../languages/java/ast/MethodDeclaration.java | 40 ++++++ .../java/ast/ParameterDeclaration.java | 2 +- .../languages/java/ast/Statement.java | 2 +- .../java/ast/ThisReferenceExpression.java | 4 + .../java/ast/VariableInitializer.java | 2 +- .../EclipseStringSwitchRewriterTransform.java | 2 +- .../RewriteLegacyClassConstantsTransform.java | 4 +- .../transforms/RewriteNewArrayLambdas.java | 4 +- .../RewriteRecordClassesTransform.java | 124 ++++++++++++------ .../StringSwitchRewriterTransform.java | 13 +- .../transforms/TryWithResourcesTransform.java | 16 +-- .../strobel/decompiler/patterns/AllMatch.java | 22 ++++ .../strobel/decompiler/patterns/INode.java | 4 +- ...ence.java => IdentifierBackReference.java} | 112 ++++++++-------- .../decompiler/patterns/OptionalNode.java | 2 +- .../strobel/decompiler/patterns/Pattern.java | 17 +-- .../strobel/decompiler/patterns/Repeat.java | 2 +- 26 files changed, 341 insertions(+), 238 deletions(-) create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/AllMatch.java rename Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/{IdentifierExpressionBackReference.java => IdentifierBackReference.java} (61%) diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/ast/AstOptimizer.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/ast/AstOptimizer.java index f6f20ab3..cd33b227 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/ast/AstOptimizer.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/ast/AstOptimizer.java @@ -132,7 +132,7 @@ public static void optimize(final DecompilerContext context, final Block method, break; } - modified |= runOptimization(block, new RemoveInnerClassInitSecurityChecksOptimization(context, method)); + modified |= runOptimization(block, new RemoveInnerClassAccessNullChecksOptimization(context, method)); if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.PreProcessShortCircuitAssignments)) { done = true; @@ -346,6 +346,7 @@ public static void optimize(final DecompilerContext context, final Block method, LOG.fine("Finished bytecode AST optimization."); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private static boolean shouldPerformStep(final AstOptimizationStep abortBeforeStep, final AstOptimizationStep nextStep) { if (abortBeforeStep == nextStep) { return false; @@ -647,6 +648,7 @@ private static boolean rewriteSynchronizedCore(final TryCatchBlock tryCatch, fin lockInfo = null; } + //noinspection UnnecessaryBreak break test; } @@ -775,7 +777,7 @@ else if ((matchGetArgument(e, AstCode.MonitorEnter, a) || matchGetArgument(e, As // - @SuppressWarnings({ "ConstantConditions", "StatementWithEmptyBody" }) + @SuppressWarnings("StatementWithEmptyBody") static void removeRedundantCode(final Block method, final DecompilerSettings settings) { final Map labelReferenceCount = new IdentityHashMap<>(); @@ -1325,6 +1327,7 @@ private static void reduceBranchInstructionSet(final Block block) { continue; } + //noinspection SuspiciousListRemoveInLoop body.remove(i); break; } @@ -1405,8 +1408,8 @@ private static void reduceBranchInstructionSet(final Block block) { // - private final static class RemoveInnerClassInitSecurityChecksOptimization extends AbstractExpressionOptimization { - protected RemoveInnerClassInitSecurityChecksOptimization(final DecompilerContext context, final Block method) { + private final static class RemoveInnerClassAccessNullChecksOptimization extends AbstractExpressionOptimization { + protected RemoveInnerClassAccessNullChecksOptimization(final DecompilerContext context, final Block method) { super(context, method); } @@ -1420,17 +1423,16 @@ public boolean run(final List body, final Expression head, final int posit final StrongBox getClassMethod = new StrongBox<>(); final List arguments = new ArrayList<>(); + // matchGetArgument(previous, AstCode.InvokeStatic, getClassMethod, getClassArgument) && isRequireNonNull(getClassMethod.get()) if (position > 0) { final Node previous = body.get(position - 1); - arguments.clear(); - if (matchGetArguments(head, AstCode.InvokeSpecial, constructor, arguments) && arguments.size() > 1 && matchGetOperand(arguments.get(0), AstCode.Load, constructorTargetVariable) && matchGetOperand(arguments.get(1), AstCode.Load, constructorArgumentVariable) && - matchGetArgument(previous, AstCode.InvokeVirtual, getClassMethod, getClassArgument) && - isGetClassMethod(getClassMethod.get()) && + (matchGetArgument(previous, AstCode.InvokeVirtual, getClassMethod, getClassArgument) && isGetClassMethod(getClassMethod.get()) || + matchGetArgument(previous, AstCode.InvokeStatic, getClassMethod, getClassArgument) && isRequireNonNull(getClassMethod.get())) && matchGetOperand(getClassArgument.get(), AstCode.Load, getClassArgumentVariable) && getClassArgumentVariable.get() == constructorArgumentVariable.get()) { @@ -1460,7 +1462,13 @@ public boolean run(final List body, final Expression head, final int posit private static boolean isGetClassMethod(final MethodReference method) { return method.getParameters().isEmpty() && - StringUtilities.equals(method.getName(), "getClass"); + "getClass".equals(method.getName()); + } + + private static boolean isRequireNonNull(final MethodReference method) { + return "requireNonNull".equals(method.getName()) && + "java/util/Objects".equals(method.getDeclaringType().getInternalName()) && + method.getParameters().size() == 1; } private static boolean isEnclosedBy(final TypeReference innerType, final TypeReference outerType) { @@ -3252,7 +3260,6 @@ private AstCode getIncrementCode(final Expression add, final StrongBox i // - @SuppressWarnings("StatementWithEmptyBody") private static void flattenBasicBlocks(final Node node) { if (node instanceof Block) { final Block block = (Block) node; @@ -4184,6 +4191,7 @@ static boolean references(final Node node, final Variable v) { return false; } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private static boolean containsMatch(final Node node, final Expression pattern) { for (final Expression e : node.getSelfAndChildrenRecursive(Expression.class)) { if (e.isEquivalentTo(pattern)) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java index 60c7758f..7979de07 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/JavaOutputVisitor.java @@ -222,7 +222,9 @@ void closeBrace(final BraceStyle style) { void writeIdentifier(final Identifier identifier, final String text) { // for now, set the start location to *here* identifier.setStartLocation(new TextLocation(output.getRow(), output.getColumn())); - boolean wroteSpace = writeIdentifier(text); + + final boolean wroteSpace = writeIdentifier(text); + if (wroteSpace) { // shift the start position over by one identifier.setStartLocation(new TextLocation(identifier.getStartLocation().line(), identifier.getStartLocation().column() + 1)); @@ -268,7 +270,7 @@ void writeToken(final TokenRole tokenRole) { writeToken(tokenRole.getToken(), tokenRole); } - void writeToken(final String token, final Role role) { + void writeToken(final String token, final Role role) { writeSpecialsUpToRole(role); if (lastWritten == LastWritten.Plus && token.charAt(0) == '+' || @@ -358,7 +360,7 @@ void optionalComma() { } void semicolon() { - final Role role = containerStack.peek().getRole(); + final Role role = containerStack.peek().getRole(); if (!(role == ForStatement.INITIALIZER_ROLE || role == ForStatement.ITERATOR_ROLE)) { writeToken(Roles.SEMICOLON); newLine(); @@ -417,18 +419,6 @@ private void writeCommaSeparatedListInParenthesis( rightParenthesis(); } -/* - private void writeCommaSeparatedListInBrackets(final Iterable list, final boolean spaceWithin) { - writeToken(Roles.LEFT_BRACKET); - if (any(list)) { - space(spaceWithin); - writeCommaSeparatedList(list); - space(spaceWithin); - } - writeToken(Roles.RIGHT_BRACKET); - } -*/ - // // @@ -536,14 +526,7 @@ else if (parent instanceof WhileStatement) { final boolean addBraces; final AstNodeCollection statements = body.getStatements(); - switch (braceEnforcement) { - case RemoveBraces: - addBraces = false; - break; - default: - addBraces = true; - break; - } + addBraces = braceEnforcement != BraceEnforcement.RemoveBraces; if (addBraces) { openBrace(style); @@ -613,7 +596,7 @@ void writeKeyword(final String token) { writeKeyword(token, null); } - void writeKeyword(final String token, final Role tokenRole) { + void writeKeyword(final String token, final Role tokenRole) { if (tokenRole != null) { writeSpecialsUpToRole(tokenRole); } @@ -634,8 +617,8 @@ void visitNodeInPattern(final INode childNode) { if (childNode instanceof AstNode) { ((AstNode) childNode).acceptVisitor(this, null); } - else if (childNode instanceof IdentifierExpressionBackReference) { - visitIdentifierExpressionBackReference((IdentifierExpressionBackReference) childNode); + else if (childNode instanceof IdentifierBackReference) { + visitIdentifierExpressionBackReference((IdentifierBackReference) childNode); } else if (childNode instanceof Choice) { visitChoice((Choice) childNode); @@ -687,7 +670,7 @@ private void visitParameterReferenceNode(final ParameterReferenceNode node) { rightParenthesis(); } - private void visitIdentifierExpressionBackReference(final IdentifierExpressionBackReference node) { + private void visitIdentifierExpressionBackReference(final IdentifierBackReference node) { writeKeyword("identifierBackReference"); leftParenthesis(); writeIdentifier(node.getReferencedGroupName()); @@ -873,6 +856,7 @@ public Void visitNullReferenceExpression(final NullReferenceExpression node, fin } @Override + @SuppressWarnings("DuplicatedCode") public Void visitThisReferenceExpression(final ThisReferenceExpression node, final Void ignored) { node.setStartLocation(new TextLocation(output.getRow(), output.getColumn())); startNode(node); @@ -890,6 +874,7 @@ public Void visitThisReferenceExpression(final ThisReferenceExpression node, fin } @Override + @SuppressWarnings("DuplicatedCode") public Void visitSuperReferenceExpression(final SuperReferenceExpression node, final Void ignored) { node.setStartLocation(new TextLocation(output.getRow(), output.getColumn())); startNode(node); @@ -955,14 +940,7 @@ else if (parent instanceof WhileStatement) { final boolean addBraces; - switch (braceEnforcement) { - case RemoveBraces: - addBraces = false; - break; - default: - addBraces = true; - break; - } + addBraces = braceEnforcement != BraceEnforcement.RemoveBraces; if (addBraces) { openBrace(style); @@ -1578,6 +1556,7 @@ public Void visitLocalTypeDeclarationStatement(final LocalTypeDeclarationStateme } @Override + @SuppressWarnings("DuplicatedCode") public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) { startNode(node); @@ -1698,6 +1677,20 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) openBrace(braceStyle); + writeMembers(members); + + closeBrace(braceStyle); + + if (type == null || !type.isAnonymous()) { + optionalSemicolon(); + newLine(); + } + + endNode(node); + return null; + } + + private void writeMembers(final AstNodeCollection members) { boolean first = true; EntityDeclaration lastMember = null; @@ -1719,19 +1712,10 @@ public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) formatter.newLine(); } } - member.acceptVisitor(this, ignored); - lastMember = member; - } - - closeBrace(braceStyle); - if (type == null || !type.isAnonymous()) { - optionalSemicolon(); - newLine(); + member.acceptVisitor(this, null); + lastMember = member; } - - endNode(node); - return null; } @Override @@ -1916,6 +1900,7 @@ private boolean isBitwiseContext(AstNode parent, AstNode node) { return isBitwiseContext(TypeUtilities.skipParenthesesDown(comparand), null); } + break; } default: { @@ -1924,12 +1909,7 @@ private boolean isBitwiseContext(AstNode parent, AstNode node) { } } else if (parent instanceof UnaryOperatorExpression) { - switch (((UnaryOperatorExpression) parent).getOperator()) { - case BITWISE_NOT: - return true; - default: - return false; - } + return ((UnaryOperatorExpression) parent).getOperator() == UnaryOperatorType.BITWISE_NOT; } return false; @@ -1975,7 +1955,7 @@ else if (f == Float.NEGATIVE_INFINITY) { } return; } - formatter.writeLiteral(Float.toString(f) + "f"); + formatter.writeLiteral(f + "f"); lastWritten = LastWritten.Other; } else if (val instanceof Double) { @@ -2243,6 +2223,7 @@ else if (!isFirst && style == BraceStyle.BannerStyle) { } @Override + @SuppressWarnings("DuplicatedCode") public Void visitObjectCreationExpression(final ObjectCreationExpression node, final Void ignored) { startNode(node); @@ -2262,6 +2243,7 @@ public Void visitObjectCreationExpression(final ObjectCreationExpression node, f } @Override + @SuppressWarnings("DuplicatedCode") public Void visitAnonymousObjectCreationExpression(final AnonymousObjectCreationExpression node, final Void ignored) { startNode(node); @@ -2325,6 +2307,7 @@ public Void visitMethodGroupExpression(final MethodGroupExpression node, final V } @Override + @SuppressWarnings("DuplicatedCode") public Void visitEnumValueDeclaration(final EnumValueDeclaration node, final Void ignored) { startNode(node); writeAnnotations(node.getAnnotations(), true); @@ -2343,32 +2326,7 @@ public Void visitEnumValueDeclaration(final EnumValueDeclaration node, final Voi final BraceStyle braceStyle = policy.AnonymousClassBraceStyle; openBrace(braceStyle); - - boolean first = true; - EntityDeclaration lastMember = null; - - for (final EntityDeclaration member : node.getMembers()) { - if (first) { - first = false; - } - else { - final int blankLines; - - if (member instanceof FieldDeclaration && lastMember instanceof FieldDeclaration) { - blankLines = policy.BlankLinesBetweenFields; - } - else { - blankLines = policy.BlankLinesBetweenMembers; - } - - for (int i = 0; i < blankLines; i++) { - formatter.newLine(); - } - } - member.acceptVisitor(this, ignored); - lastMember = member; - } - + writeMembers(node.getMembers()); closeBrace(braceStyle); } @@ -2450,6 +2408,7 @@ public Void visitArrayCreationExpression(final ArrayCreationExpression node, fin boolean needType = true; + //noinspection RedundantIfStatement if (node.getDimensions().isEmpty() && node.getType() != null && (node.getParent() instanceof ArrayInitializerExpression || diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNode.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNode.java index e29a5bcd..3ec10308 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNode.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNode.java @@ -96,7 +96,6 @@ final void setRoleUnsafe(final Role role) { public abstract R acceptVisitor(final IAstVisitor visitor, final T data); @Override - @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException") public AstNode clone() { try { final AstNode clone = (AstNode) super.clone(); @@ -120,7 +119,7 @@ public AstNode clone() { return clone; } - catch (CloneNotSupportedException e) { + catch (final CloneNotSupportedException e) { throw new UndeclaredThrowableException(e); } } @@ -571,15 +570,11 @@ public final void replaceWith(final AstNode newNode) { verifyNotFrozen(); - final Role role = getRole(); + final Role role = getRole(); if (!role.isValid(newNode)) { throw new IllegalArgumentException( - String.format( - "The new node '%s' is not valid for role '%s'.", - newNode.getClass().getName(), - role.toString() - ) + String.format("The new node '%s' is not valid for role '%s'.", newNode.getClass().getName(), role) ); } @@ -638,7 +633,7 @@ public final T replaceWith(final Function oldRole = this.getRole(); remove(); @@ -751,7 +746,7 @@ public final void setRole(final Role role) { @Override public boolean matchesCollection( - final Role role, + final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { @@ -797,7 +792,7 @@ public boolean matches(final INode other, final Match match) { } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNodeCollection.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNodeCollection.java index f59b624f..cc34f0da 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNodeCollection.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstNodeCollection.java @@ -20,10 +20,12 @@ import com.strobel.core.CollectionUtilities; import com.strobel.core.Predicate; import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.patterns.INode; import com.strobel.decompiler.patterns.Match; import com.strobel.decompiler.patterns.Pattern; import com.strobel.decompiler.patterns.Role; import com.strobel.util.ContractUtils; +import com.strobel.util.EmptyArrayCache; import java.util.AbstractCollection; import java.util.Arrays; @@ -137,9 +139,8 @@ public void remove() { } @Override - @SuppressWarnings("NullableProblems") public Object[] toArray() { - return toArray(new Object[size()]); + return toArray(EmptyArrayCache.EMPTY_OBJECT_ARRAY); } @NotNull @@ -241,6 +242,19 @@ public final boolean matches(final AstNodeCollection other, final Match match ); } + public final boolean anyMatch(final INode other) { + return anyMatch(other, Match.createNew()); + } + + public final boolean anyMatch(final INode other, final Match match) { + for (final AstNode child : this) { + if (other.matches(child, match)) { + return true; + } + } + return false; + } + @Override public int hashCode() { return _node.hashCode() ^ _role.hashCode(); @@ -249,7 +263,7 @@ public int hashCode() { @Override public boolean equals(final Object obj) { if (obj instanceof AstNodeCollection) { - final AstNodeCollection other = (AstNodeCollection) obj; + final AstNodeCollection other = (AstNodeCollection) obj; return other._node == _node && other._role == _role; @@ -267,9 +281,7 @@ public final void replaceWith(final Iterable nodes) { return; } - for (final T node : nodeList) { - add(node); - } + addAll(nodeList); } public final void insertAfter(final T existingItem, final T newItem) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstType.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstType.java index b9bd8826..32a599c0 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstType.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/AstType.java @@ -105,7 +105,7 @@ public boolean matches(final INode other, final Match match) { } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return _child.matchesCollection(role, position, match, backtrackingInfo); } } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/BlockStatement.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/BlockStatement.java index c76f31b1..8d7db814 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/BlockStatement.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/BlockStatement.java @@ -131,7 +131,7 @@ public R acceptVisitor(final IAstVisitor visitor, } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CatchClause.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CatchClause.java index 4d05c5f6..b4ee7fdd 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CatchClause.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/CatchClause.java @@ -127,7 +127,7 @@ public R acceptVisitor(final IAstVisitor visitor, } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java index 4ea6fa1d..b0bee78c 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/EntityDeclaration.java @@ -120,6 +120,18 @@ protected final boolean matchAnnotationsAndModifiers(final EntityDeclaration oth getAnnotations().matches(other.getAnnotations(), match); } + public final void addModifier(final Modifier modifier) { + EntityDeclaration.addModifier(this, modifier); + } + + public final void removeModifier(final Modifier modifier) { + EntityDeclaration.removeModifier(this, modifier); + } + + public final void setModifiers(final List modifiers) { + EntityDeclaration.setModifiers(this, modifiers); + } + static List getModifiers(final AstNode node) { List modifiers = null; @@ -155,6 +167,7 @@ static void addModifier(final AstNode node, final Modifier modifier) { node.addChild(new JavaModifierToken(TextLocation.EMPTY, modifier), MODIFIER_ROLE); } + @SuppressWarnings("UnusedReturnValue") static boolean removeModifier(final AstNode node, final Modifier modifier) { final AstNodeCollection modifierTokens = node.getChildrenByRole(MODIFIER_ROLE); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Expression.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Expression.java index 7c25a00c..ccc54ae9 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Expression.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Expression.java @@ -122,7 +122,7 @@ public R acceptVisitor(final IAstVisitor visitor, } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/MethodDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/MethodDeclaration.java index 1a5f16c2..4319009d 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/MethodDeclaration.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/MethodDeclaration.java @@ -16,9 +16,12 @@ package com.strobel.decompiler.languages.java.ast; +import com.strobel.core.VerifyArgument; import com.strobel.decompiler.languages.EntityType; +import com.strobel.decompiler.patterns.BacktrackingInfo; import com.strobel.decompiler.patterns.INode; import com.strobel.decompiler.patterns.Match; +import com.strobel.decompiler.patterns.Pattern; import com.strobel.decompiler.patterns.Role; public class MethodDeclaration extends EntityDeclaration { @@ -102,4 +105,41 @@ public boolean matches(final INode other, final Match match) { return false; } + + // + + public static MethodDeclaration forPattern(final Pattern pattern) { + return new MethodDeclaration.PatternPlaceholder(VerifyArgument.notNull(pattern, "pattern")); + } + + private final static class PatternPlaceholder extends MethodDeclaration { + final Pattern child; + + PatternPlaceholder(final Pattern child) { + this.child = child; + } + + @Override + public NodeType getNodeType() { + return NodeType.PATTERN; + } + + @Override + public R acceptVisitor(final IAstVisitor visitor, final T data) { + return visitor.visitPatternPlaceholder(this, child, data); + } + + @Override + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + return child.matchesCollection(role, position, match, backtrackingInfo); + } + + @Override + public boolean matches(final INode other, final Match match) { + return child.matches(other, match); + } + } + + // + } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ParameterDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ParameterDeclaration.java index cad3c2e8..bd4d2d67 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ParameterDeclaration.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ParameterDeclaration.java @@ -122,7 +122,7 @@ public R acceptVisitor(final IAstVisitor visitor, } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Statement.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Statement.java index 6efb8f2c..4b0a3355 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Statement.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Statement.java @@ -126,7 +126,7 @@ public R acceptVisitor(final IAstVisitor visitor, } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ThisReferenceExpression.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ThisReferenceExpression.java index c042f675..86a26912 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ThisReferenceExpression.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/ThisReferenceExpression.java @@ -27,6 +27,10 @@ public final class ThisReferenceExpression extends Expression { private TextLocation _startLocation; private TextLocation _endLocation; + public ThisReferenceExpression() { + this(MYSTERY_OFFSET, TextLocation.EMPTY); + } + public ThisReferenceExpression(final int offset) { this(offset, TextLocation.EMPTY); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/VariableInitializer.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/VariableInitializer.java index 66340013..b26a7eaf 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/VariableInitializer.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/VariableInitializer.java @@ -152,7 +152,7 @@ public R acceptVisitor(final IAstVisitor visitor, } @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return child.matchesCollection(role, position, match, backtrackingInfo); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseStringSwitchRewriterTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseStringSwitchRewriterTransform.java index c0f4d756..e12c6bd2 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseStringSwitchRewriterTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/EclipseStringSwitchRewriterTransform.java @@ -91,7 +91,7 @@ public EclipseStringSwitchRewriterTransform(final DecompilerContext context) { // @Override - @SuppressWarnings("ConstantConditions") + @SuppressWarnings("DuplicatedCode") public Void visitSwitchStatement(final SwitchStatement node, final Void data) { super.visitSwitchStatement(node, data); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java index 2e96f67d..be7daa25 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteLegacyClassConstantsTransform.java @@ -210,14 +210,14 @@ private static MethodDeclaration createPattern() { new AstTypeMatch(noClassDefFoundError) .toType() .makeNew() - .invoke("initCause", new IdentifierExpressionBackReference("catch").toExpression().cast(throwable)) + .invoke("initCause", new IdentifierBackReference("catch").toExpression().cast(throwable)) .makeThrow() ), // Java 1.2 Pattern: throw new NoClassDefFoundError(ex.getMessage()); new BlockStatement( new AstTypeMatch(noClassDefFoundError) .toType() - .makeNew(new IdentifierExpressionBackReference("catch").toExpression().invoke("getMessage")) + .makeNew(new IdentifierBackReference("catch").toExpression().invoke("getMessage")) .makeThrow() ) ).toBlockStatement(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteNewArrayLambdas.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteNewArrayLambdas.java index 9ecf39c0..ce459d01 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteNewArrayLambdas.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteNewArrayLambdas.java @@ -23,7 +23,7 @@ import com.strobel.decompiler.DecompilerContext; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.AnyNode; -import com.strobel.decompiler.patterns.IdentifierExpressionBackReference; +import com.strobel.decompiler.patterns.IdentifierBackReference; import com.strobel.decompiler.patterns.Match; import com.strobel.decompiler.patterns.NamedNode; import com.strobel.decompiler.patterns.OptionalNode; @@ -64,7 +64,7 @@ public Void visitLambdaExpression(final LambdaExpression node, final Void data) final ArrayCreationExpression arrayCreation = new ArrayCreationExpression(Expression.MYSTERY_OFFSET); - arrayCreation.getDimensions().add(new IdentifierExpressionBackReference("size").toExpression()); + arrayCreation.getDimensions().add(new IdentifierBackReference("size").toExpression()); arrayCreation.setType(new NamedNode("type", new AnyNode()).toType()); pattern.setBody(arrayCreation); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java index 41d91922..f51ab23e 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteRecordClassesTransform.java @@ -15,16 +15,13 @@ import com.strobel.core.VerifyArgument; import com.strobel.decompiler.DecompilerContext; import com.strobel.decompiler.languages.java.ast.*; -import com.strobel.decompiler.patterns.AnyNode; -import com.strobel.decompiler.patterns.Match; -import com.strobel.decompiler.patterns.NamedNode; -import com.strobel.decompiler.patterns.ParameterReferenceNode; -import com.strobel.decompiler.patterns.Pattern; -import com.strobel.decompiler.patterns.Repeat; +import com.strobel.decompiler.patterns.*; +import javax.lang.model.element.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -67,6 +64,16 @@ public class RewriteRecordClassesTransform extends ContextTrackingVisitor ) ); + protected final static ExpressionStatement THIS_CONSTRUCTOR_CALL = + new ExpressionStatement( + new InvocationExpression( + new ThisReferenceExpression(Expression.MYSTERY_OFFSET), + new Repeat(new AnyNode()).toExpression() + ) + ); + + protected final static MethodDeclaration ACCESSOR; + static { final HashMap generatedMethodNames = new HashMap<>(); @@ -75,6 +82,25 @@ public class RewriteRecordClassesTransform extends ContextTrackingVisitor generatedMethodNames.put("equals", "(Ljava/lang/Object;)Z"); GENERATED_METHOD_SIGNATURES = Collections.unmodifiableMap(generatedMethodNames); + + final MethodDeclaration accessor = new MethodDeclaration(); + + accessor.setName(Pattern.ANY_STRING); + accessor.addModifier(Modifier.PUBLIC); + accessor.setReturnType(new AnyNode().toType()); + + accessor.setBody( + new BlockStatement( + new ReturnStatement( + new AllMatch( + new MemberReferenceExpression(new ThisReferenceExpression(), Pattern.ANY_STRING), + new IdentifierBackReference("accessor") + ).toExpression() + ) + ) + ); + + ACCESSOR = new NamedNode("accessor", accessor).toMethodDeclaration(); } private RecordState _currentRecord; @@ -137,12 +163,14 @@ protected Void visitMethodDeclarationOverride(final MethodDeclaration node, fina } } - final RecordComponentInfo componentInfo = recordState.recordComponents.get(node.getName()); + if (ACCESSOR.matches(node)) { + final RecordComponentInfo componentInfo = recordState.recordComponents.get(node.getName()); - if (componentInfo != null && - MetadataHelper.isSameType(componentInfo.getType(), node.getReturnType().toTypeReference())) { + if (componentInfo != null && + MetadataHelper.isSameType(componentInfo.getType(), node.getReturnType().toTypeReference())) { - recordState.removableAccessors.put(componentInfo, node); + recordState.removableAccessors.put(componentInfo, node); + } } return null; @@ -201,7 +229,7 @@ public Void visitExpressionStatement(final ExpressionStatement node, final Void final RecordState recordState = _currentRecord; final RecordState.Constructor recordConstructor = recordState != null ? recordState.currentConstructor : null; - if (recordConstructor == null) { + if (recordConstructor == null || !recordState.constructors.containsKey(recordConstructor.constructor)) { return null; } @@ -210,6 +238,11 @@ public Void visitExpressionStatement(final ExpressionStatement node, final Void return null; } + if (THIS_CONSTRUCTOR_CALL.matches(node)) { + recordState.constructors.remove(recordConstructor.constructor); + return null; + } + final Match match = ASSIGNMENT_PATTERN.match(node); if (!match.success()) { @@ -264,7 +297,7 @@ public RecordState(final TypeDefinition recordDefinition, final RecordAttribute this.removableFields = new HashMap<>(); this.removableMethods = new ArrayList<>(); - final Map recordComponents = new HashMap<>(); + final Map recordComponents = new LinkedHashMap<>(); for (final RecordComponentInfo component : recordAttribute.getComponents()) { recordComponents.put(component.getName(), component); @@ -286,28 +319,10 @@ public final boolean canRewrite() { final List components = recordAttribute.getComponents(); final int componentCount = components.size(); - final Constructor constructor; - - if (removableAccessors.size() != componentCount || - removableFields.size() != componentCount || - constructors.size() != 1 || - (constructor = single(constructors.values())).removableParameters.size() != componentCount || - constructor.removableAssignments.size() != componentCount || - constructor.removableSuperCall.get() == null) { - - return false; - } - - for (final RecordComponentInfo component : components) { - if (!removableAccessors.containsKey(component) || - !constructor.removableAssignments.containsKey(component) || - !constructor.removableParameters.containsKey(component)) { - - return false; - } - } - - return true; + return removableAccessors.size() <= componentCount && + removableFields.size() == componentCount && + constructors.size() == 1 && + single(constructors.values()).removableSuperCall.get() != null; } private void rewrite0() { @@ -326,13 +341,29 @@ private void rewrite0() { superCall.remove(); } - for (final ExpressionStatement assignment : constructor.removableAssignments.values()) { - assignment.remove(); + if (constructor.removableParameters.size() == recordComponents.size() && + constructor.removableAssignments.size() == recordComponents.size()) { + + for (final ExpressionStatement assignment : constructor.removableAssignments.values()) { + assignment.remove(); + } + + for (final ParameterDeclaration p : constructor.removableParameters.values()) { + p.remove(); + p.getModifiers().clear(); + } } - for (final ParameterDeclaration p : constructor.removableParameters.values()) { - p.remove(); - p.getModifiers().clear(); + final boolean generatedConstructor = constructor.removableParameters.size() == recordComponents.size(); + + for (final RecordComponentInfo component : recordComponents.values()) { + ParameterDeclaration p = generatedConstructor ? constructor.removableParameters.get(component) : null; + + if (p == null) { + final FieldDeclaration f = removableFields.get(component); + p = new ParameterDeclaration(f.getName(), f.getReturnType().clone()); + } + recordDeclaration.addChild(p, EntityDeclaration.RECORD_COMPONENT); } @@ -344,8 +375,21 @@ private void rewrite0() { accessor.remove(); } - for (final FieldDeclaration field : removableFields.values()) { + for (final Map.Entry entry : removableFields.entrySet()) { + final FieldDeclaration field = entry.getValue(); + field.remove(); + + final ParameterDeclaration parameter = constructor.removableParameters.get(entry.getKey()); + + if (parameter != null) { + for (final Annotation annotation : field.getChildrenByRole(Roles.ANNOTATION)) { + if (!parameter.getChildrenByRole(Roles.ANNOTATION).anyMatch(annotation)) { + annotation.remove(); + parameter.addChild(annotation, Roles.ANNOTATION); + } + } + } } final ConstructorDeclaration constructorDeclaration = single(constructors.keySet()); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/StringSwitchRewriterTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/StringSwitchRewriterTransform.java index e3f4479d..76fd91c5 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/StringSwitchRewriterTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/StringSwitchRewriterTransform.java @@ -23,7 +23,7 @@ import com.strobel.decompiler.DecompilerContext; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.AnyNode; -import com.strobel.decompiler.patterns.IdentifierExpressionBackReference; +import com.strobel.decompiler.patterns.IdentifierBackReference; import com.strobel.decompiler.patterns.Match; import com.strobel.decompiler.patterns.NamedNode; import com.strobel.decompiler.patterns.OptionalNode; @@ -99,7 +99,7 @@ public StringSwitchRewriterTransform(final DecompilerContext context) { Expression.MYSTERY_OFFSET, new MemberReferenceExpression( Expression.MYSTERY_OFFSET, - new IdentifierExpressionBackReference("input").toExpression(), + new IdentifierBackReference("input").toExpression(), "equals" ), new NamedNode("stringValue", new PrimitiveExpression( Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression() @@ -107,7 +107,7 @@ public StringSwitchRewriterTransform(final DecompilerContext context) { new BlockStatement( new ExpressionStatement( new AssignmentExpression( - new IdentifierExpressionBackReference("tableSwitchInput").toExpression(), + new IdentifierBackReference("tableSwitchInput").toExpression(), new NamedNode("tableSwitchCaseValue", new PrimitiveExpression( Expression.MYSTERY_OFFSET, PrimitiveExpression.ANY_VALUE)).toExpression() ) ), @@ -125,7 +125,7 @@ public StringSwitchRewriterTransform(final DecompilerContext context) { // @Override - @SuppressWarnings("ConstantConditions") + @SuppressWarnings("DuplicatedCode") public Void visitSwitchStatement(final SwitchStatement node, final Void data) { super.visitSwitchStatement(node, data); @@ -250,10 +250,11 @@ public boolean test(final SwitchSection s) { new Predicate() { @Override public boolean test(final CaseLabel c) { + final Object constantValue; return c.getExpression().isNull() || (c.getExpression() instanceof PrimitiveExpression && - ((PrimitiveExpression) c.getExpression()).getValue() instanceof Integer && - tableInputMap.containsKey(((PrimitiveExpression) c.getExpression()).getValue())); + (constantValue = ((PrimitiveExpression) c.getExpression()).getValue()) instanceof Integer && + tableInputMap.containsKey(constantValue)); } } ); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java index 08fdd348..acbcffb5 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/TryWithResourcesTransform.java @@ -20,7 +20,7 @@ import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.AnyNode; import com.strobel.decompiler.patterns.INode; -import com.strobel.decompiler.patterns.IdentifierExpressionBackReference; +import com.strobel.decompiler.patterns.IdentifierBackReference; import com.strobel.decompiler.patterns.Match; import com.strobel.decompiler.patterns.NamedNode; import com.strobel.decompiler.patterns.Pattern; @@ -91,11 +91,11 @@ public TryWithResourcesTransform(final DecompilerContext context) { new BlockStatement( new ExpressionStatement( new AssignmentExpression( - new IdentifierExpressionBackReference("savedException").toExpression(), + new IdentifierBackReference("savedException").toExpression(), new NamedNode("caughtException", new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression() ) ), - new ThrowStatement(new IdentifierExpressionBackReference("caughtException").toExpression()) + new ThrowStatement(new IdentifierBackReference("caughtException").toExpression()) ) ); @@ -109,7 +109,7 @@ public TryWithResourcesTransform(final DecompilerContext context) { disposeTry.setTryBlock( new BlockStatement( new ExpressionStatement( - new IdentifierExpressionBackReference("resource").toExpression().invoke("close") + new IdentifierBackReference("resource").toExpression().invoke("close") ) ) ); @@ -117,7 +117,7 @@ public TryWithResourcesTransform(final DecompilerContext context) { final CatchClause disposeCatch = new CatchClause( new BlockStatement( new ExpressionStatement( - new IdentifierExpressionBackReference("savedException").toExpression().invoke( + new IdentifierBackReference("savedException").toExpression().invoke( "addSuppressed", new NamedNode("caughtOnClose", new IdentifierExpression(Expression.MYSTERY_OFFSET, Pattern.ANY_STRING)).toExpression() ) @@ -134,14 +134,14 @@ public TryWithResourcesTransform(final DecompilerContext context) { new BlockStatement( new IfElseStatement( Expression.MYSTERY_OFFSET, new BinaryOperatorExpression( - new IdentifierExpressionBackReference("resource").toExpression(), + new IdentifierBackReference("resource").toExpression(), BinaryOperatorType.INEQUALITY, new NullReferenceExpression(Expression.MYSTERY_OFFSET) ), new BlockStatement( new IfElseStatement( Expression.MYSTERY_OFFSET, new BinaryOperatorExpression( - new IdentifierExpressionBackReference("savedException").toExpression(), + new IdentifierBackReference("savedException").toExpression(), BinaryOperatorType.INEQUALITY, new NullReferenceExpression(Expression.MYSTERY_OFFSET) ), @@ -150,7 +150,7 @@ public TryWithResourcesTransform(final DecompilerContext context) { ), new BlockStatement( new ExpressionStatement( - new IdentifierExpressionBackReference("resource").toExpression().invoke("close") + new IdentifierBackReference("resource").toExpression().invoke("close") ) ) ) diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/AllMatch.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/AllMatch.java new file mode 100644 index 00000000..7f47b5db --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/AllMatch.java @@ -0,0 +1,22 @@ +package com.strobel.decompiler.patterns; + +import com.strobel.core.VerifyArgument; + +public final class AllMatch extends Pattern { + private final INode[] _patterns; + + public AllMatch(final INode... patterns) { + _patterns = VerifyArgument.noNullElementsAndNotEmpty(patterns, "patterns"); + } + + @Override + public boolean matches(final INode other, final Match match) { + for (final INode pattern : _patterns) { + if (pattern.matches(other, match)) { + continue; + } + return false; + } + return true; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/INode.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/INode.java index bcc9073c..c34b7c18 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/INode.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/INode.java @@ -29,11 +29,11 @@ public interface INode { INode getNextSibling(); boolean matches(final INode other, final Match match); - boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo); + boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo); Match match(INode other); boolean matches(INode other); - public final static Function> CHILD_ITERATOR = new Function>() { + Function> CHILD_ITERATOR = new Function>() { @Override public Iterable apply(final INode input) { return new Iterable() { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/IdentifierExpressionBackReference.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/IdentifierBackReference.java similarity index 61% rename from Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/IdentifierExpressionBackReference.java rename to Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/IdentifierBackReference.java index e01edd75..be3c2801 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/IdentifierExpressionBackReference.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/IdentifierBackReference.java @@ -1,52 +1,60 @@ -/* - * IdentifierExpressionBackReference.java - * - * Copyright (c) 2013 Mike Strobel - * - * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; - * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. - * - * This source code is subject to terms and conditions of the Apache License, Version 2.0. - * A copy of the license can be found in the License.html file at the root of this distribution. - * By using this source code in any fashion, you are agreeing to be bound by the terms of the - * Apache License, Version 2.0. - * - * You must not remove this notice, or any other, from this software. - */ - -package com.strobel.decompiler.patterns; - -import com.strobel.core.StringUtilities; -import com.strobel.core.VerifyArgument; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.IdentifierExpression; -import com.strobel.decompiler.languages.java.ast.Roles; - -import static com.strobel.core.CollectionUtilities.*; - -public final class IdentifierExpressionBackReference extends Pattern { - private final String _referencedGroupName; - - public IdentifierExpressionBackReference(final String referencedGroupName) { - _referencedGroupName = VerifyArgument.notNull(referencedGroupName, "referencedGroupName"); - } - - public final String getReferencedGroupName() { - return _referencedGroupName; - } - - @Override - public final boolean matches(final INode other, final Match match) { - if (other instanceof IdentifierExpression && !any(((IdentifierExpression) other).getTypeArguments())) { - final INode referenced = lastOrDefault(match.get(_referencedGroupName)); - - return referenced instanceof AstNode && - StringUtilities.equals( - ((IdentifierExpression) other).getIdentifier(), - ((AstNode) referenced).getChildByRole(Roles.IDENTIFIER).getName() - ); - } - - return false; - } -} +/* + * IdentifierExpressionBackReference.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package com.strobel.decompiler.patterns; + +import com.strobel.core.StringUtilities; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.Roles; + +import static com.strobel.core.CollectionUtilities.*; + +public final class IdentifierBackReference extends Pattern { + private final String _referencedGroupName; + + public IdentifierBackReference(final String referencedGroupName) { + _referencedGroupName = VerifyArgument.notNull(referencedGroupName, "referencedGroupName"); + } + + public final String getReferencedGroupName() { + return _referencedGroupName; + } + + @Override + public final boolean matches(final INode other, final Match match) { + final Identifier identifier; + + if (other instanceof AstNode) { + if ((identifier = ((AstNode) other).getChildByRole(Roles.IDENTIFIER)) != null && + !identifier.isNull() && + !any(((AstNode) other).getChildrenByRole(Roles.TYPE_ARGUMENT))) { + + final INode referenced = lastOrDefault(match.get(_referencedGroupName)); + + return referenced instanceof AstNode && + StringUtilities.equals( + identifier.getName(), + ((AstNode) referenced).getChildByRole(Roles.IDENTIFIER).getName() + ); + } + } + + return false; + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/OptionalNode.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/OptionalNode.java index 2d26c2bd..0d378312 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/OptionalNode.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/OptionalNode.java @@ -31,7 +31,7 @@ public final INode getNode() { @Override public final boolean matchesCollection( - final Role role, + final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Pattern.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Pattern.java index 39858f38..60517318 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Pattern.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Pattern.java @@ -17,14 +17,7 @@ package com.strobel.decompiler.patterns; import com.strobel.core.StringUtilities; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.AstType; -import com.strobel.decompiler.languages.java.ast.BlockStatement; -import com.strobel.decompiler.languages.java.ast.CatchClause; -import com.strobel.decompiler.languages.java.ast.Expression; -import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; -import com.strobel.decompiler.languages.java.ast.Statement; -import com.strobel.decompiler.languages.java.ast.VariableInitializer; +import com.strobel.decompiler.languages.java.ast.*; import java.util.Stack; @@ -63,6 +56,10 @@ public final ParameterDeclaration toParameterDeclaration() { return ParameterDeclaration.forPattern(this); } + public final MethodDeclaration toMethodDeclaration() { + return MethodDeclaration.forPattern(this); + } + public final AstType toType() { return AstType.forPattern(this); } @@ -73,7 +70,7 @@ public boolean isNull() { } @Override - public Role getRole() { + public Role getRole() { return null; } @@ -91,7 +88,7 @@ public INode getNextSibling() { public abstract boolean matches(final INode other, final Match match); @Override - public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { + public boolean matchesCollection(final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { return matches(position, match); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Repeat.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Repeat.java index 4f8f974e..7611ce50 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Repeat.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/Repeat.java @@ -54,7 +54,7 @@ public final void setMaxCount(final int maxCount) { @Override public final boolean matchesCollection( - final Role role, + final Role role, final INode position, final Match match, final BacktrackingInfo backtrackingInfo) { From 6ad35c1bfd7b330b631b6d7391eced9b039d42ca Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Wed, 16 Jun 2021 10:21:28 -0400 Subject: [PATCH 08/35] Lots of bug fixes, support for indy-based string concatenation, better handling of intersection types. --- .idea/inspectionProfiles/Project_Default.xml | 7 +- .../flowanalysis/ControlFlowGraph.java | 6 +- .../flowanalysis/ControlFlowNode.java | 6 +- .../strobel/assembler/ir/MetadataReader.java | 18 +- .../metadata/CommonTypeReferences.java | 26 ++ .../assembler/metadata/CompilerTarget.java | 131 ++++++- .../metadata/CompoundTypeDefinition.java | 127 ++++++ .../metadata/CompoundTypeReference.java | 173 +++++---- .../metadata/CoreMetadataFactory.java | 2 +- .../metadata/DefaultTypeVisitor.java | 2 +- .../assembler/metadata/ICompoundType.java | 17 + .../assembler/metadata/LanguageFeature.java | 56 +++ .../assembler/metadata/MetadataHelper.java | 150 +++++++- .../assembler/metadata/MetadataResolver.java | 10 +- .../assembler/metadata/MethodBinder.java | 2 +- .../metadata/TypeMetadataVisitor.java | 2 +- .../assembler/metadata/TypeReference.java | 75 +++- .../metadata/TypeSubstitutionVisitor.java | 6 +- .../strobel/decompiler/DecompilerContext.java | 25 ++ .../strobel/decompiler/DecompilerHelpers.java | 4 +- .../decompiler/DecompilerSettings.java | 32 +- .../strobel/decompiler/ast/AstBuilder.java | 58 +++ .../strobel/decompiler/ast/AstOptimizer.java | 34 +- .../strobel/decompiler/ast/DefaultMap.java | 12 +- .../com/strobel/decompiler/ast/Inlining.java | 45 ++- .../com/strobel/decompiler/ast/Lambda.java | 12 + .../java/com/strobel/decompiler/ast/Node.java | 43 ++- .../decompiler/ast/PatternMatching.java | 71 +++- .../strobel/decompiler/ast/TypeAnalysis.java | 21 +- .../com/strobel/decompiler/ast/Variable.java | 2 + .../languages/java/JavaOutputVisitor.java | 94 ++++- .../languages/java/ast/AstBuilder.java | 28 +- .../java/ast/AstMethodBodyBuilder.java | 288 +++++++++----- .../languages/java/ast/AstNode.java | 2 +- .../languages/java/ast/AstType.java | 3 +- .../languages/java/ast/BytecodeConstant.java | 66 ++++ .../languages/java/ast/CatchClause.java | 22 ++ .../java/ast/ConvertTypeOptions.java | 9 + .../java/ast/DepthFirstAstVisitor.java | 15 + .../languages/java/ast/IAstVisitor.java | 3 + .../languages/java/ast/IfElseStatement.java | 10 +- .../java/ast/InlinedBytecodeExpression.java | 88 +++++ .../languages/java/ast/IntersectionType.java | 101 +++++ .../languages/java/ast/JavaNameResolver.java | 15 + .../languages/java/ast/JavaResolver.java | 11 +- .../decompiler/languages/java/ast/Keys.java | 1 + .../languages/java/ast/MethodDeclaration.java | 6 + .../languages/java/ast/NameVariables.java | 36 +- .../languages/java/ast/TryCatchStatement.java | 13 +- .../ast/transforms/ConvertLoopsTransform.java | 114 ++++-- .../DeclareLocalClassesTransform.java | 2 +- .../transforms/DeclareVariablesTransform.java | 15 +- .../EclipseStringSwitchRewriterTransform.java | 5 +- .../ast/transforms/EnumRewriterTransform.java | 2 +- .../EnumSwitchRewriterTransform.java | 156 ++++++-- .../InsertNecessaryConversionsTransform.java | 19 +- ...IntroduceStringConcatenationTransform.java | 156 +++++++- .../MergeResourceTryStatementsVisitor.java | 40 +- .../NewTryWithResourcesTransform.java | 362 ++++++++++++++---- .../RemoveImplicitBoxingTransform.java | 6 +- .../RewriteLegacyClassConstantsTransform.java | 4 +- .../RewriteRecordClassesTransform.java | 6 +- .../StringSwitchRewriterTransform.java | 5 +- .../transforms/TransformationPipeline.java | 2 +- .../transforms/TryWithResourcesTransform.java | 7 +- .../java/utilities/RedundantCastUtility.java | 27 +- .../patterns/IdentifierBackReference.java | 8 +- .../strobel/decompiler/DecompilerTest.java | 16 + .../strobel/decompiler/EnhancedTryTests.java | 8 +- .../com/strobel/core/CollectionUtilities.java | 13 +- .../main/java/com/strobel/core/Comparer.java | 34 ++ .../java/com/strobel/core/Predicates.java | 211 ++-------- .../main/java/com/strobel/core/StrongBox.java | 4 +- .../functions/{Block.java => Consumer.java} | 42 +- .../java/com/strobel/functions/Function.java | 2 +- .../java/com/strobel/functions/Functions.java | 15 + .../strobel/expressions/DebugViewWriter.java | 12 +- 77 files changed, 2520 insertions(+), 759 deletions(-) create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/CompoundTypeDefinition.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/ICompoundType.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/LanguageFeature.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/BytecodeConstant.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/InlinedBytecodeExpression.java create mode 100644 Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/IntersectionType.java rename Procyon.Core/src/main/java/com/strobel/functions/{Block.java => Consumer.java} (85%) create mode 100644 Procyon.Core/src/main/java/com/strobel/functions/Functions.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 7b77f710..fe325ec4 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,7 +2,6 @@