diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/LanguageFeature.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/LanguageFeature.java index 4137d2d2..0de3011b 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/LanguageFeature.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/LanguageFeature.java @@ -1,10 +1,12 @@ package com.strobel.assembler.metadata; -import com.strobel.core.Closeables; import com.strobel.core.Comparer; import com.strobel.core.VerifyArgument; public enum LanguageFeature { + ENUM_CLASSES(CompilerTarget.JDK1_5), + FOR_EACH_LOOPS(CompilerTarget.JDK1_5), + TRY_WITH_RESOURCES(CompilerTarget.JDK1_7), DEFAULT_INTERFACE_METHODS(CompilerTarget.JDK1_8), STATIC_INTERFACE_METHODS(CompilerTarget.JDK1_8), LAMBDA_EXPRESSIONS(CompilerTarget.JDK1_8), diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerContext.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerContext.java index 9ef61829..c3ef9173 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerContext.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerContext.java @@ -106,4 +106,22 @@ public boolean isSupported(final TypeDefinition versionSource, final @NotNull La return feature.isAvailable(target, allowPreview); } + + public CompilerTarget target() { + return target(_currentType); + } + + public CompilerTarget target(final TypeDefinition versionSource) { + CompilerTarget target = _settings.getForcedCompilerTarget(); + + if (target == null && versionSource != null) { + target = versionSource.getCompilerTarget(); + } + + if (target == null) { + target = CompilerTarget.DEFAULT; + } + + return target; + } } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerSettings.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerSettings.java index 6f3c8c18..714354ea 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerSettings.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/DecompilerSettings.java @@ -23,6 +23,7 @@ import com.strobel.decompiler.languages.Languages; import com.strobel.decompiler.languages.java.JavaFormattingOptions; +@SuppressWarnings("BooleanMethodIsAlwaysInverted") public class DecompilerSettings { private ITypeLoader _typeLoader; private boolean _includeLineNumbersInBytecode = true; @@ -46,7 +47,7 @@ public class DecompilerSettings { private String _outputDirectory; private boolean _showDebugLineNumbers; private boolean _simplifyMemberReferences; - private int _minLinesForTextBlocks = 3; + private int _textBlockLineMinimum = 3; private CompilerTarget _forcedCompilerTarget; private boolean _arePreviewFeaturesEnabled; @@ -225,12 +226,12 @@ public final void setForceFullyQualifiedReferences(final boolean forceFullyQuali _forceFullyQualifiedReferences = forceFullyQualifiedReferences; } - public final int getMinLinesForTextBlocks() { - return _minLinesForTextBlocks; + public final int getTextBlockLineMinimum() { + return _textBlockLineMinimum; } - public final void setMinLinesForTextBlocks(final int minLinesForTextBlocks) { - _minLinesForTextBlocks = minLinesForTextBlocks; + public final void setTextBlockLineMinimum(final int textBlockLineMinimum) { + _textBlockLineMinimum = textBlockLineMinimum; } public final CompilerTarget getForcedCompilerTarget() { 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 3da7ef79..897834bd 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 @@ -32,7 +32,7 @@ import static com.strobel.core.CollectionUtilities.*; import static com.strobel.decompiler.ast.PatternMatching.*; -@SuppressWarnings("ConstantConditions") +@SuppressWarnings({ "ConstantConditions", "UnusedReturnValue" }) public final class AstOptimizer { private final static Logger LOG = Logger.getLogger(AstOptimizer.class.getSimpleName()); @@ -3485,6 +3485,10 @@ protected InlineLambdasOptimization(final DecompilerContext context, final Block @Override public boolean run(final List body, final Expression head, final int position) { + if (!context.isSupported(LanguageFeature.LAMBDA_EXPRESSIONS)) { + return false; + } + final StrongBox c = new StrongBox<>(); final List a = new ArrayList<>(); @@ -3552,7 +3556,7 @@ private Lambda tryInlineLambda(final Expression site, final DynamicCallSite call final MethodBody methodBody = resolvedMethod.getBody(); final List parameters = resolvedMethod.getParameters(); - final Variable[] parameterMap = new Variable[methodBody.getMaxLocals()]; +// final Variable[] parameterMap = new Variable[methodBody.getMaxLocals()]; final List nodes = new ArrayList<>(); final Block body = new Block(); @@ -3570,7 +3574,7 @@ private Lambda tryInlineLambda(final Expression site, final DynamicCallSite call variable.setType(context.getCurrentMethod().getDeclaringType()); variable.setOriginalParameter(context.getCurrentMethod().getBody().getThisParameter()); - parameterMap[0] = variable; +// parameterMap[0] = variable; lambdaParameters.add(variable); } @@ -3583,7 +3587,7 @@ private Lambda tryInlineLambda(final Expression site, final DynamicCallSite call variable.setOriginalParameter(p); variable.setLambdaParameter(true); - parameterMap[p.getSlot()] = variable; +// parameterMap[p.getSlot()] = variable; lambdaParameters.add(variable); } @@ -3808,6 +3812,7 @@ private interface ExpressionOptimization { private static abstract class AbstractBasicBlockOptimization implements BasicBlockOptimization { protected final static BasicBlock EMPTY_BLOCK = new BasicBlock(); + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") protected final Map labelGlobalRefCount = new DefaultMap<>(MutableInteger.SUPPLIER); protected final Map labelToBasicBlock = new DefaultMap<>(Suppliers.forValue(EMPTY_BLOCK)); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/IOutputFormatter.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/IOutputFormatter.java index f47c55a8..8c8301c9 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/IOutputFormatter.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/IOutputFormatter.java @@ -31,6 +31,7 @@ public interface IOutputFormatter { void writeToken(String token); void writeLiteral(String value); void writeTextLiteral(String value); + void writeTextBlock(String value); void space(); @@ -48,6 +49,6 @@ public interface IOutputFormatter { * instructs 'this' formatter to forget what it used to know about the sequence of line * number offsets in the source code */ - public void resetLineNumberOffsets( OffsetToLineNumberConverter offset2LineNumber); + void resetLineNumberOffsets( OffsetToLineNumberConverter offset2LineNumber); } 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 366efc2f..14067f94 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 @@ -32,8 +32,8 @@ import com.strobel.decompiler.languages.LineNumberPosition; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.TextOutputFormatter.LineNumberMode; -import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.languages.java.ast.WildcardType; +import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.languages.java.utilities.TypeUtilities; import com.strobel.decompiler.patterns.*; @@ -57,6 +57,7 @@ public final class JavaOutputVisitor implements IAstVisitor { final Stack positionStack = new Stack<>(); final ITextOutput output; + private TypeDefinition currentType; private LastWritten lastWritten; public JavaOutputVisitor(final ITextOutput output, final DecompilerSettings settings) { @@ -1706,136 +1707,144 @@ public Void visitBytecodeConstant(final BytecodeConstant node, final Void data) @Override @SuppressWarnings("DuplicatedCode") public Void visitTypeDeclaration(final TypeDeclaration node, final Void ignored) { - startNode(node); - final TypeDefinition type = node.getUserData(Keys.TYPE_DEFINITION); + final TypeDefinition previousType = this.currentType; - final boolean isTrulyAnonymous = type != null && - type.isAnonymous() && - node.getParent() instanceof AnonymousObjectCreationExpression; + this.currentType = type; - final ClassType classType = node.getClassType(); + try { + startNode(node); - if (!isTrulyAnonymous) { - writeAnnotations(node.getAnnotations(), true); - writeModifiers(node.getModifiers()); + final boolean isTrulyAnonymous = type != null && + type.isAnonymous() && + node.getParent() instanceof AnonymousObjectCreationExpression; + + final ClassType classType = node.getClassType(); + + if (!isTrulyAnonymous) { + writeAnnotations(node.getAnnotations(), true); + writeModifiers(node.getModifiers()); + + switch (classType) { + case ENUM: + writeKeyword(Roles.ENUM_KEYWORD); + break; + case RECORD: + writeKeyword(Roles.RECORD_KEYWORD); + break; + case INTERFACE: + writeKeyword(Roles.INTERFACE_KEYWORD); + break; + case ANNOTATION: + writeKeyword(Roles.ANNOTATION_KEYWORD); + break; + default: + writeKeyword(Roles.CLASS_KEYWORD); + break; + } - switch (classType) { - case ENUM: - writeKeyword(Roles.ENUM_KEYWORD); - break; - case RECORD: - writeKeyword(Roles.RECORD_KEYWORD); - break; - case INTERFACE: - writeKeyword(Roles.INTERFACE_KEYWORD); - break; - case ANNOTATION: - writeKeyword(Roles.ANNOTATION_KEYWORD); - break; - default: - writeKeyword(Roles.CLASS_KEYWORD); - break; - } + node.getNameToken().acceptVisitor(this, ignored); + writeTypeParameters(node.getTypeParameters()); - node.getNameToken().acceptVisitor(this, ignored); - writeTypeParameters(node.getTypeParameters()); + if (classType == ClassType.RECORD) { + writeCommaSeparatedListInParenthesis(node.getChildrenByRole(EntityDeclaration.RECORD_COMPONENT), + policy.SpaceWithinRecordDeclarationParentheses); + } - if (classType == ClassType.RECORD) { - writeCommaSeparatedListInParenthesis(node.getChildrenByRole(EntityDeclaration.RECORD_COMPONENT), - policy.SpaceWithinRecordDeclarationParentheses); - } + if (!node.getBaseType().isNull()) { + space(); + writeKeyword(Roles.EXTENDS_KEYWORD); + space(); + node.getBaseType().acceptVisitor(this, ignored); + } - if (!node.getBaseType().isNull()) { - space(); - writeKeyword(Roles.EXTENDS_KEYWORD); - space(); - node.getBaseType().acceptVisitor(this, ignored); - } + if (any(node.getInterfaces())) { + final Collection interfaceTypes; - if (any(node.getInterfaces())) { - final Collection interfaceTypes; + if (classType == ClassType.ANNOTATION) { + interfaceTypes = new ArrayList<>(); - if (classType == ClassType.ANNOTATION) { - interfaceTypes = new ArrayList<>(); + for (final AstType t : node.getInterfaces()) { + final TypeReference r = t.getUserData(Keys.TYPE_REFERENCE); - for (final AstType t : node.getInterfaces()) { - final TypeReference r = t.getUserData(Keys.TYPE_REFERENCE); + if (r != null && CommonTypeReferences.Annotation.isEquivalentTo(r)) { + continue; + } - if (r != null && CommonTypeReferences.Annotation.isEquivalentTo(r)) { - continue; + interfaceTypes.add(t); } + } + else { + interfaceTypes = node.getInterfaces(); + } - interfaceTypes.add(t); + if (any(interfaceTypes)) { + space(); + + if (classType == ClassType.INTERFACE || classType == ClassType.ANNOTATION) { + writeKeyword(Roles.EXTENDS_KEYWORD); + } + else { + writeKeyword(Roles.IMPLEMENTS_KEYWORD); + } + + space(); + writeCommaSeparatedList(node.getInterfaces()); } } - else { - interfaceTypes = node.getInterfaces(); - } + } - if (any(interfaceTypes)) { - space(); + if (classType == ClassType.RECORD && node.getMembers().isEmpty()) { + openBrace(BraceStyle.BannerStyle); + closeBrace(BraceStyle.BannerStyle); + endNode(node); + newLine(); + return null; + } - if (classType == ClassType.INTERFACE || classType == ClassType.ANNOTATION) { - writeKeyword(Roles.EXTENDS_KEYWORD); + final BraceStyle braceStyle; + final AstNodeCollection members = node.getMembers(); + + switch (classType) { + case ENUM: + braceStyle = policy.EnumBraceStyle; + break; + case RECORD: + braceStyle = policy.RecordBraceStyle; + break; + case INTERFACE: + braceStyle = policy.InterfaceBraceStyle; + break; + case ANNOTATION: + braceStyle = policy.AnnotationBraceStyle; + break; + default: + if (type != null && type.isAnonymous()) { + braceStyle = members.isEmpty() ? BraceStyle.BannerStyle : policy.AnonymousClassBraceStyle; } else { - writeKeyword(Roles.IMPLEMENTS_KEYWORD); + braceStyle = policy.ClassBraceStyle; } - - space(); - writeCommaSeparatedList(node.getInterfaces()); - } + break; } - } - 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 (classType) { - case ENUM: - braceStyle = policy.EnumBraceStyle; - break; - case RECORD: - braceStyle = policy.RecordBraceStyle; - break; - case INTERFACE: - braceStyle = policy.InterfaceBraceStyle; - break; - case ANNOTATION: - braceStyle = policy.AnnotationBraceStyle; - break; - default: - if (type != null && type.isAnonymous()) { - braceStyle = members.isEmpty() ? BraceStyle.BannerStyle : policy.AnonymousClassBraceStyle; - } - else { - braceStyle = policy.ClassBraceStyle; - } - break; - } + openBrace(braceStyle); - openBrace(braceStyle); + writeMembers(members); - writeMembers(members); + closeBrace(braceStyle); - closeBrace(braceStyle); + if (type == null || !type.isAnonymous()) { + optionalSemicolon(); + newLine(); + } - if (type == null || !type.isAnonymous()) { - optionalSemicolon(); - newLine(); + endNode(node); + return null; + } + finally { + this.currentType = previousType; } - - endNode(node); - return null; } private void writeMembers(final AstNodeCollection members) { @@ -2110,7 +2119,13 @@ void writePrimitiveValue(final Object val) { } if (val instanceof String) { - formatter.writeTextLiteral(StringUtilities.escape(val.toString(), true, settings.isUnicodeOutputEnabled())); + final String s = val.toString(); + if (canWriteTextBlock(s)) { + formatter.writeTextBlock(s); + } + else { + formatter.writeTextLiteral(StringUtilities.escape(s, true, settings.isUnicodeOutputEnabled())); + } lastWritten = LastWritten.Other; } else if (val instanceof Character) { @@ -2208,6 +2223,54 @@ else if (val instanceof Number) { } } + private CompilerTarget currentCompilerTarget() { + CompilerTarget target = settings.getForcedCompilerTarget(); + + if (target != null) { + return target; + } + + final TypeDefinition type = currentType; + + target = type != null ? type.getCompilerTarget() : null; + + if (target != null) { + return target; + } + + return CompilerTarget.DEFAULT; + } + + private boolean canWriteTextBlock(final String s) { + final int minLines = settings.getTextBlockLineMinimum(); + + return minLines > 0 && + LanguageFeature.TEXT_BLOCKS.isAvailable(currentCompilerTarget(), settings.arePreviewFeaturesEnabled()) && + countLines(s, minLines) >= minLines; + } + + private static int countLines(final String s, final int stopAfter) { + return countLines(s, 0, s.length(), stopAfter); + } + + @SuppressWarnings("SameParameterValue") + private static int countLines(final String s, final int from, final int end, final int stopAfter) { + int count = 1; + int index = from; + + while ((index = s.indexOf('\n', index)) > 0) { + if (index >= end) { + break; + } + if (++count >= stopAfter) { + return count; + } + ++index; + } + + return count; + } + @Override public Void visitCastExpression(final CastExpression node, final Void ignored) { startNode(node); @@ -2278,10 +2341,24 @@ public Void visitBinaryOperatorExpression(final BinaryOperatorExpression node, f @Override public Void visitInstanceOfExpression(final InstanceOfExpression node, final Void ignored) { startNode(node); + node.getExpression().acceptVisitor(this, ignored); space(); writeKeyword(InstanceOfExpression.INSTANCE_OF_KEYWORD_ROLE); + + final Identifier identifier = node.getIdentifier(); + final boolean isPattern = identifier != null && !identifier.isNull(); + + if (isPattern) { + writeModifiers(node.getModifiers()); + } + node.getType().acceptVisitor(this, ignored); + + if (isPattern) { + identifier.acceptVisitor(this, ignored); + } + endNode(node); return null; } 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 a4f81781..763f942c 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 @@ -22,6 +22,7 @@ import com.strobel.assembler.metadata.PackageReference; import com.strobel.assembler.metadata.ParameterDefinition; import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.StringUtilities; import com.strobel.core.VerifyArgument; import com.strobel.decompiler.ITextOutput; import com.strobel.decompiler.ast.Variable; @@ -63,12 +64,12 @@ public enum LineNumberMode { /** * maps original line numbers to decompiler-emitted line numbers and columns */ - private final List lineNumberPositions = new ArrayList(); + private final List lineNumberPositions = new ArrayList<>(); @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final Stack startLocations = new Stack<>(); - public TextOutputFormatter(final ITextOutput output, LineNumberMode lineNumberMode) { + public TextOutputFormatter(final ITextOutput output, final LineNumberMode lineNumberMode) { this.output = VerifyArgument.notNull(output, "output"); this.lineNumberMode = lineNumberMode; } @@ -101,17 +102,17 @@ else if (node instanceof Statement) { } if (offset != Expression.MYSTERY_OFFSET) { // Convert to a line number. - int lineNumber = offset2LineNumber.getLineForOffset(offset); + final 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); + final int lineOfComment = output.getRow(); + final int columnOfComment = output.getColumn(); + final LineNumberPosition pos = new LineNumberPosition(lineNumber, lineOfComment, columnOfComment); lineNumberPositions.add(pos); lastObservedLineNumber = lineNumber; if (lineNumberMode == LineNumberMode.WITH_DEBUG_LINE_NUMBERS) { // Emit a comment showing the original line number. - String commentStr = prefix + lineNumber + "*/"; + final String commentStr = prefix + lineNumber + "*/"; output.writeComment(commentStr); } } @@ -243,6 +244,50 @@ public void writeTextLiteral(final String value) { output.writeTextLiteral(value); } + @Override + public void writeTextBlock(final String value) { + final int columnStart = output.getColumn(); + final List lines = StringUtilities.split(value, false, '\n'); + final boolean endsWithNewline = value.endsWith("\n"); + + output.writeTextLiteral("\"\"\""); + output.writeLine(); + + final int columnEnd = output.getColumn(); + final int paddingSize = Math.max(0, columnStart - columnEnd); + final String padding = StringUtilities.repeat(' ', paddingSize); + + final int n = lines.size(); + + for (int i = 0; i < n - 1; i++) { + String line = lines.get(i); + + if (line.indexOf('\r') >= 0) { + line = line.replace("\r", "\\r"); + } + + if (line.contains("\"\"\"")) { + line = line.replace("\"\"\"", "\\\"\"\""); + } + + output.write(padding); + output.writeTextLiteral(line); + output.writeLine(); + } + + final String lastLine = lines.get(n - 1); + + output.write(padding); + output.writeTextLiteral(lastLine); + + if (endsWithNewline && !StringUtilities.isNullOrWhitespace(lastLine)) { + output.writeLine(); + output.write(padding); + } + + output.writeTextLiteral("\"\"\""); + } + @Override public void space() { output.write(' '); @@ -620,7 +665,7 @@ private boolean isImportDeclaration(final AstNode node) { } @Override - public void resetLineNumberOffsets(OffsetToLineNumberConverter offset2LineNumber) { + public void resetLineNumberOffsets(final 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; 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 3fcfde01..1fe7ccf1 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 @@ -373,7 +373,7 @@ public Iterable apply(final AstNode n) { @NotNull @SuppressWarnings("unchecked") - public final T getChildByRole(final Role role) { + public final T getChildByRole(final Role role) { VerifyArgument.notNull(role, "role"); final int roleIndex = role.getIndex(); @@ -388,11 +388,13 @@ public final T getChildByRole(final Role role) { } @NotNull - public final AstNodeCollection getChildrenByRole(final Role role) { - return new AstNodeCollection<>(this, role); + public final AstNodeCollection getChildrenByRole(final Role role) { + @SuppressWarnings("unchecked") + final Role r = (Role) role; + return new AstNodeCollection<>(this, r); } - protected final void setChildByRole(final Role role, final T newChild) { + protected final void setChildByRole(final Role role, final T newChild) { final T oldChild = getChildByRole(role); if (oldChild.isNull()) { @@ -449,7 +451,7 @@ final void addChildUnsafe(final AstNode child, final Role role) { } @SafeVarargs - public final void insertChildrenBefore(final AstNode nextSibling, final Role role, final T... children) { + public final void insertChildrenBefore(final AstNode nextSibling, final Role role, final T... children) { VerifyArgument.notNull(children, "children"); for (final T child : children) { @@ -457,7 +459,7 @@ public final void insertChildrenBefore(final AstNode nextSib } } - public final void insertChildBefore(final AstNode nextSibling, final T child, final Role role) { + public final void insertChildBefore(final AstNode nextSibling, final T child, final Role role) { VerifyArgument.notNull(role, "role"); if (nextSibling == null || nextSibling.isNull()) { @@ -487,7 +489,7 @@ public final void insertChildBefore(final AstNode nextSiblin } @SafeVarargs - public final void insertChildrenAfter(final AstNode nextSibling, final Role role, final T... children) { + public final void insertChildrenAfter(final AstNode nextSibling, final Role role, final T... children) { VerifyArgument.notNull(children, "children"); for (final T child : children) { @@ -495,7 +497,7 @@ public final void insertChildrenAfter(final AstNode nextSibl } } - public final void insertChildAfter(final AstNode previousSibling, final T child, final Role role) { + public final void insertChildAfter(final AstNode previousSibling, final T child, final Role role) { insertChildBefore( previousSibling == null || previousSibling.isNull() ? _firstChild : previousSibling._nextSibling, child, 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 b0bee78c..fbbab365 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 @@ -16,7 +16,6 @@ package com.strobel.decompiler.languages.java.ast; -import com.strobel.core.VerifyArgument; import com.strobel.decompiler.languages.EntityType; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.patterns.Match; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Identifier.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Identifier.java index 1b75d5ec..8c0e78f6 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Identifier.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/Identifier.java @@ -87,6 +87,12 @@ public boolean matches(final INode other, final Match match) { matchString(getName(), ((Identifier) other).getName()); } + @Override + public Identifier clone() { + return (Identifier) super.clone(); + } + + // public final static Identifier NULL = new NullIdentifier(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/InstanceOfExpression.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/InstanceOfExpression.java index 04583a0d..ee58bc0b 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/InstanceOfExpression.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/InstanceOfExpression.java @@ -18,12 +18,22 @@ import com.strobel.decompiler.patterns.INode; import com.strobel.decompiler.patterns.Match; +import com.strobel.decompiler.patterns.Role; -public class InstanceOfExpression extends Expression { +import javax.lang.model.element.Modifier; +import java.util.List; + +public class InstanceOfExpression extends PatternExpression { + public final static Role MODIFIER_ROLE = EntityDeclaration.MODIFIER_ROLE; public final static TokenRole INSTANCE_OF_KEYWORD_ROLE = new TokenRole("instanceof", TokenRole.FLAG_KEYWORD | TokenRole.FLAG_OPERATOR); + private boolean _anyModifiers; + + public InstanceOfExpression(final Expression expression, final AstType type) { + this(MYSTERY_OFFSET, expression, type); + } - public InstanceOfExpression( int offset, final Expression expression, final AstType type) { - super( offset); + public InstanceOfExpression(final int offset, final Expression expression, final AstType type) { + super(offset); setExpression(expression); setType(type); } @@ -48,6 +58,45 @@ public final void setExpression(final Expression value) { setChildByRole(Roles.EXPRESSION, value); } + public final Identifier getIdentifier() { + return getChildByRole(Roles.IDENTIFIER); + } + + public final void setIdentifier(final Identifier value) { + setChildByRole(Roles.IDENTIFIER, value); + } + + /** + * Gets the "any" modifiers flag used during pattern matching. + */ + public final boolean isAnyModifiers() { + return _anyModifiers; + } + + /** + * Sets the "any" modifiers flag used during pattern matching. + */ + public final void setAnyModifiers(final boolean value) { + verifyNotFrozen(); + _anyModifiers = value; + } + + public final AstNodeCollection getModifiers() { + return getChildrenByRole(MODIFIER_ROLE); + } + + 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); + } + @Override public R acceptVisitor(final IAstVisitor visitor, final T data) { return visitor.visitInstanceOfExpression(this, data); @@ -60,7 +109,12 @@ public boolean matches(final INode other, final Match match) { return !otherExpression.isNull() && getExpression().matches(otherExpression.getExpression(), match) && - getType().matches(otherExpression.getType(), match); + getType().matches(otherExpression.getType(), match) && + (getIdentifier().isNull() && otherExpression.getIdentifier().isNull() || + getIdentifier().matches(otherExpression.getIdentifier())) && + (isAnyModifiers() || + otherExpression.isAnyModifiers() || + getChildrenByRole(MODIFIER_ROLE).matches(otherExpression.getChildrenByRole(MODIFIER_ROLE), match)); } return false; 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 82878416..e99b1f9e 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 @@ -837,6 +837,13 @@ public Set visitBinaryOperatorExpression(final BinaryOperatorExpression @Override public Set visitInstanceOfExpression(final InstanceOfExpression node, final String name) { + if (_mode == NameResolveMode.EXPRESSION && StringUtilities.equals(node.getIdentifier().getName(), name)) { + final Variable variable = node.getUserData(Keys.VARIABLE); + + if (variable != null) { + return Collections.singleton(variable); + } + } return Collections.emptySet(); } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NameVariables.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NameVariables.java index ec40adfb..cdf71eef 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NameVariables.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/NameVariables.java @@ -29,7 +29,6 @@ import com.strobel.decompiler.ast.Variable; import com.strobel.decompiler.languages.java.JavaOutputVisitor; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -80,15 +79,9 @@ public class NameVariables { METHOD_NAME_MAPPINGS = methodNameMappings; } - private final ArrayList _fieldNamesInCurrentType; private final Map _typeNames = new HashMap<>(); public NameVariables(final DecompilerContext context) { - _fieldNamesInCurrentType = new ArrayList<>(); - - for (final FieldDefinition field : context.getCurrentType().getDeclaredFields()) { - _fieldNamesInCurrentType.add(field.getName()); - } } public final void addExistingName(final String name) { @@ -161,13 +154,6 @@ public static NameVariables assignNamesToVariables( final VariableDefinition originalVariable = v.getOriginalVariable(); if (originalVariable != null) { -/* - if (originalVariable.isFromMetadata() && originalVariable.hasName()) { - v.setName(originalVariable.getName()); - continue; - } -*/ - final String varName = originalVariable.getName(); if (StringUtilities.isNullOrEmpty(varName) || varName.startsWith("V_") || !isValidName(varName)) { @@ -184,7 +170,8 @@ public static NameVariables assignNamesToVariables( } for (final Variable p : parameters) { - if (!p.getOriginalParameter().hasName()) { + final ParameterDefinition op = p.getOriginalParameter(); + if (op != null && !op.hasName()) { p.setName(nv.generateNameForVariable(p, methodBody)); } } @@ -259,7 +246,6 @@ public String getAlternativeName(final String oldVariableName) { } } - @SuppressWarnings("ConstantConditions") private String generateNameForVariable(final Variable variable, final Block methodBody) { String proposedName = null; @@ -359,19 +345,10 @@ private String generateNameForVariable(final Variable variable, final Block meth } if (StringUtilities.isNullOrEmpty(proposedName)) { - proposedName = getNameForType(variable.getType()); + proposedName = getNameForType0(variable.getType()); } return this.getAlternativeName(proposedName); -/* - while (true) { - proposedName = this.getAlternativeName(proposedName); - - if (!_fieldNamesInCurrentType.contains(proposedName)) { - return proposedName; - } - } -*/ } private static String cleanUpVariableName(final String s) { @@ -533,6 +510,11 @@ private static String getNameForArgument(final Expression parent, final int i) { } public String getNameForType(final TypeReference type) { + final String baseName = getNameForType0(type); + return getAlternativeName(baseName); + } + + private String getNameForType0(final TypeReference type) { TypeReference nameSource = type; diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/PatternExpression.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/PatternExpression.java new file mode 100644 index 00000000..43f88e72 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/PatternExpression.java @@ -0,0 +1,11 @@ +package com.strobel.decompiler.languages.java.ast; + +public abstract class PatternExpression extends Expression { + protected PatternExpression(final int offset) { + super(offset); + } + + protected PatternExpression() { + super(MYSTERY_OFFSET); + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ConvertLoopsTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ConvertLoopsTransform.java index 134a8955..c4879dc1 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ConvertLoopsTransform.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ConvertLoopsTransform.java @@ -18,7 +18,7 @@ import com.strobel.annotations.Nullable; import com.strobel.assembler.metadata.BuiltinTypes; -import com.strobel.assembler.metadata.MetadataHelper; +import com.strobel.assembler.metadata.LanguageFeature; import com.strobel.assembler.metadata.TypeReference; import com.strobel.core.CollectionUtilities; import com.strobel.core.Predicate; @@ -34,7 +34,6 @@ import com.strobel.decompiler.languages.java.analysis.ControlFlowNodeType; 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 java.util.*; @@ -43,6 +42,8 @@ import static com.strobel.decompiler.languages.java.analysis.Correlator.areCorrelated; public final class ConvertLoopsTransform extends ContextTrackingVisitor { + private final static Statement[] EMPTY_STATEMENTS = new Statement[0]; + public ConvertLoopsTransform(final DecompilerContext context) { super(context); } @@ -70,7 +71,10 @@ protected AstNode visitChildren(final AstNode node, final Void data) { public AstNode visitExpressionStatement(final ExpressionStatement node, final Void data) { final AstNode n = super.visitExpressionStatement(node, data); - if (!context.getSettings().getDisableForEachTransforms() && n instanceof ExpressionStatement) { + if (context.isSupported(LanguageFeature.FOR_EACH_LOOPS) && + !context.getSettings().getDisableForEachTransforms() && + n instanceof ExpressionStatement) { + final AstNode result = transformForEach((ExpressionStatement) n); if (result != null) { @@ -161,11 +165,8 @@ public boolean test(final ControlFlowNode n) { } final Set incoming = new LinkedHashSet<>(); - final Set visited = new HashSet<>(); - final ArrayDeque agenda = new ArrayDeque<>(); - - agenda.addAll(conditionNode.getIncoming()); - visited.addAll(conditionNode.getIncoming()); + final ArrayDeque agenda = new ArrayDeque<>(conditionNode.getIncoming()); + final Set visited = new HashSet<>(conditionNode.getIncoming()); while (!agenda.isEmpty()) { final ControlFlowEdge edge = agenda.removeFirst(); @@ -215,7 +216,7 @@ public boolean test(final ControlFlowNode n) { return null; } - final Statement[] iteratorSites = incoming.toArray(new Statement[incoming.size()]); + final Statement[] iteratorSites = incoming.toArray(EMPTY_STATEMENTS); final List iterators = new ArrayList<>(); final Set iteratorCopies = new HashSet<>(); @@ -427,7 +428,7 @@ private Statement canInlineInitializerDeclarations(final ForStatement forLoop) { final BlockStatement tempOuter = new BlockStatement(); final BlockStatement temp = new BlockStatement(); - final Statement[] initializers = forLoop.getInitializers().toArray(new Statement[forLoop.getInitializers().size()]); + final Statement[] initializers = forLoop.getInitializers().toArray(EMPTY_STATEMENTS); final Set variableNames = new HashSet<>(); Statement firstInlinableInitializer = null; @@ -1306,7 +1307,7 @@ public final DoWhileStatement transformDoWhile(final WhileStatement loop) { first(m.get("breakStatement")).remove(); } else { - condition = firstOrDefault(m.get("breakCondition")); + condition = first(m.get("breakCondition")); condition.remove(); if (condition instanceof UnaryOperatorExpression && @@ -1323,12 +1324,14 @@ public final DoWhileStatement transformDoWhile(final WhileStatement loop) { doWhile.setCondition(condition); final BlockStatement block = (BlockStatement) loop.getEmbeddedStatement(); + final Statement lastStatement = lastOrDefault(block.getStatements()); - lastOrDefault(block.getStatements()).remove(); - block.remove(); + if (lastStatement != null) { + lastStatement.remove(); + } + block.remove(); doWhile.setEmbeddedStatement(block); - loop.replaceWith(doWhile); // @@ -1341,6 +1344,10 @@ public final DoWhileStatement transformDoWhile(final WhileStatement loop) { final VariableDeclarationStatement declaration = (VariableDeclarationStatement) statement; final VariableInitializer v = firstOrDefault(declaration.getVariables()); + if (v == null) { + continue; + } + for (final AstNode node : condition.getDescendantsAndSelf()) { if (node instanceof IdentifierExpression && StringUtilities.equals(v.getName(), ((IdentifierExpression) node).getIdentifier())) { @@ -1493,7 +1500,6 @@ static Statement canMoveVariableDeclarationIntoStatement( final BlockStatement parent = (BlockStatement) declaration.getParent(); - //noinspection AssertWithSideEffects assert CollectionUtilities.contains(targetStatement.getAncestors(), parent); // 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 6e89127a..03792d8e 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 @@ -40,9 +40,11 @@ public EclipseEnumSwitchRewriterTransform(final DecompilerContext context) { @Override public void run(final AstNode compilationUnit) { - final Visitor visitor = new Visitor(_context); - compilationUnit.acceptVisitor(visitor, null); - visitor.rewrite(); + if (_context.isSupported(LanguageFeature.ENUM_CLASSES)) { + final Visitor visitor = new Visitor(_context); + compilationUnit.acceptVisitor(visitor, null); + visitor.rewrite(); + } } private final static class Visitor extends ContextTrackingVisitor { 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 38c07f2d..66e92da9 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 @@ -29,7 +29,6 @@ import java.util.Map; import static com.strobel.core.CollectionUtilities.first; -import static com.strobel.core.CollectionUtilities.firstOrDefault; public class EnumRewriterTransform implements IAstTransform { private final DecompilerContext _context; @@ -40,7 +39,9 @@ public EnumRewriterTransform(final DecompilerContext context) { @Override public void run(final AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(_context), null); + if (_context.isSupported(LanguageFeature.ENUM_CLASSES)) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } } private final static class Visitor extends ContextTrackingVisitor { @@ -138,7 +139,7 @@ private MemberReference findValuesField(final TypeDeclaration declaration) { final Match match = pattern.match(d); if (match.success()) { - final MemberReferenceExpression reference = firstOrDefault(match.get("valuesField")); + final MemberReferenceExpression reference = first(match.get("valuesField")); return reference.getUserData(Keys.MEMBER_REFERENCE); } } 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 1b515be4..8d1a683a 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 @@ -18,6 +18,7 @@ import com.strobel.assembler.metadata.BuiltinTypes; import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.LanguageFeature; import com.strobel.assembler.metadata.MethodDefinition; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; @@ -36,15 +37,17 @@ import java.util.Map; public class EnumSwitchRewriterTransform implements IAstTransform { - private final DecompilerContext _context; - public EnumSwitchRewriterTransform(final DecompilerContext context) { _context = VerifyArgument.notNull(context, "context"); } + private final DecompilerContext _context; + @Override public void run(final AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(_context), null); + if (_context.isSupported(LanguageFeature.ENUM_CLASSES)) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } } private final static class Visitor extends ContextTrackingVisitor { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroducePatternMatchingTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroducePatternMatchingTransform.java new file mode 100644 index 00000000..5619044b --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/IntroducePatternMatchingTransform.java @@ -0,0 +1,151 @@ +package com.strobel.decompiler.languages.java.ast.transforms; + +import com.strobel.assembler.metadata.LanguageFeature; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.ast.Variable; +import com.strobel.decompiler.languages.java.analysis.Correlator; +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.patterns.*; + +import javax.lang.model.element.Modifier; +import java.util.List; + +import static com.strobel.core.CollectionUtilities.*; + +public class IntroducePatternMatchingTransform extends ContextTrackingVisitor { + + private final IfElseStatement simplePattern; + + public IntroducePatternMatchingTransform(final DecompilerContext context) { + super(context); + + final VariableDeclarationStatement v = new VariableDeclarationStatement( + new BackReference("type").toType(), + Pattern.ANY_STRING, + new CastExpression(new BackReference("type").toType(), new BackReference("expression").toExpression()) + ); + + v.setAnyModifiers(true); + + simplePattern = new IfElseStatement( + new NamedNode("instanceOf", + new InstanceOfExpression(new NamedNode("expression", new AnyNode()).toExpression(), + new NamedNode("type", new AnyNode()).toType())).toExpression(), + new BlockStatement( + new Choice(new NamedNode("variableDeclaration", v), + new SubtreeMatch(new CastExpression(new BackReference("type").toType(), + new BackReference("expression").toExpression()), + "usageViaCast", + true)).toStatement(), + new Repeat(new AnyNode()).toStatement() + ), + new OptionalNode(new AnyNode()).toStatement() + ); + } + + @Override + public void run(final AstNode compilationUnit) { + if (context.isSupported(LanguageFeature.PATTERN_MATCHING)) { + super.run(compilationUnit); + } + } + + @Override + public Void visitIfElseStatement(final IfElseStatement node, final Void data) { + super.visitIfElseStatement(node, data); + + //noinspection IfStatementWithIdenticalBranches + if (trySimplePatternMatch(node)) { + return null; + } + + // TODO: Handle more complex cases, like the `instanceof` test appearing before/after an &&, ||, etc. + + return null; + } + + private boolean trySimplePatternMatch(final IfElseStatement node) { + final Match m = simplePattern.match(node); + + if (!m.success()) { + return false; + } + + final InstanceOfExpression io = first(m.get("instanceOf")); + + final Identifier nameToken; + + if (m.has("variableDeclaration")) { + final VariableDeclarationStatement v = first(m.get("variableDeclaration")); + final VariableInitializer vi = first(v.getVariables()); + nameToken = vi.getNameToken(); + + v.remove(); + nameToken.remove(); + io.setModifiers(v.getModifiers()); + + final Variable variable = vi.getUserData(Keys.VARIABLE); + + if (variable != null) { + io.putUserData(Keys.VARIABLE, variable); + } + } + else if (m.has("usageViaCast")) { + final NameVariables nv; + final MethodDeclaration md = node.getParent(MethodDeclaration.class); + + if (md == null || (nv = md.getUserData(Keys.NAME_VARIABLES)) == null) { + return false; + } + + final AstType astType = first(m.get("type")); + final TypeReference type = astType.toTypeReference(); + final List casts = toList(m.get("usageViaCast")); + + if (type == null) { + return false; + } + + nameToken = Identifier.create(nv.getNameForType(type)); + + if (casts.size() > 1 && io.getExpression() instanceof IdentifierExpression) { + // + // Replace additional casts only if (1) the test expression is a simple identifier; and + // (2) the target identifier is not reassigned anywhere in the block. + // + + final String id = ((IdentifierExpression) io.getExpression()).getIdentifier(); + final Statement parentStatement = casts.get(0).getParent(Statement.class); + + if (parentStatement == null || parentStatement.getParent() == null) { + return false; + } + + final BlockStatement placeholder = new BlockStatement(); + + parentStatement.replaceWith(placeholder); + + final boolean correlated = Correlator.areCorrelated(new IdentifierExpression(id), node.getTrueStatement()); + + placeholder.replaceWith(parentStatement); + + if (!correlated) { + for (int i = 1; i < casts.size(); i++) { + casts.get(i).replaceWith(new IdentifierExpression(casts.get(i).getOffset(), nameToken.clone())); + } + } + } + + casts.get(0).replaceWith(new IdentifierExpression(casts.get(0).getOffset(), nameToken.clone())); + io.addModifier(Modifier.FINAL); + } + else { + return false; + } + + io.setIdentifier(nameToken); + + return true; + } +} 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 65cd7ed1..2d9bb31c 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 @@ -177,7 +177,7 @@ public NewTryWithResourcesTransform(final DecompilerContext context) { @Override public void run(final AstNode compilationUnit) { - if (_tryPattern == null) { + if (_tryPattern == null || !context.isSupported(LanguageFeature.TRY_WITH_RESOURCES)) { return; } 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 edbd0024..1a866511 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 @@ -7,6 +7,7 @@ import com.strobel.assembler.ir.attributes.SourceAttribute; import com.strobel.assembler.metadata.CommonTypeReferences; import com.strobel.assembler.metadata.DynamicCallSite; +import com.strobel.assembler.metadata.LanguageFeature; import com.strobel.assembler.metadata.MetadataHelper; import com.strobel.assembler.metadata.MethodDefinition; import com.strobel.assembler.metadata.TypeDefinition; @@ -111,6 +112,13 @@ public RewriteRecordClassesTransform(final DecompilerContext context) { super(context); } + @Override + public void run(final AstNode compilationUnit) { + if (context.isSupported(LanguageFeature.RECORD_CLASSES)) { + super.run(compilationUnit); + } + } + @Override protected Void visitTypeDeclarationOverride(final TypeDeclaration typeDeclaration, final Void p) { final RecordState oldRecord = _currentRecord; 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 b994cf64..b2b10068 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 @@ -69,6 +69,7 @@ public static IAstTransform[] createPipeline(final DecompilerContext context) { new InsertConstantReferencesTransform(context), new SimplifyArithmeticExpressionsTransform(context), new DeclareLocalClassesTransform(context), + new IntroducePatternMatchingTransform(context), new AddStandardAnnotationsTransform(context), new AddReferenceQualifiersTransform(context), new RemoveHiddenMembersTransform(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 542c7c47..8b4f3321 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.LanguageFeature; import com.strobel.decompiler.DecompilerContext; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.AnyNode; @@ -162,7 +163,7 @@ public TryWithResourcesTransform(final DecompilerContext context) { @Override public void run(final AstNode compilationUnit) { - if (_tryPattern == null) { + if (_tryPattern == null || !context.isSupported(LanguageFeature.TRY_WITH_RESOURCES)) { return; } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/SubtreeMatch.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/SubtreeMatch.java index 8f14080b..d2448ade 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/SubtreeMatch.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/patterns/SubtreeMatch.java @@ -20,18 +20,27 @@ import com.strobel.core.VerifyArgument; import com.strobel.decompiler.utilities.TreeTraversal; -import static com.strobel.core.CollectionUtilities.any; +import static com.strobel.core.CollectionUtilities.*; public final class SubtreeMatch extends Pattern { + private final String _groupName; private final boolean _matchMultiple; private final INode _target; public SubtreeMatch(final INode target) { - this(target, false); + this(target, null, false); } public SubtreeMatch(final INode target, final boolean matchMultiple) { + this(target, null, matchMultiple); + } + public SubtreeMatch(final INode target, final String groupName) { + this(target, groupName, false); + } + + public SubtreeMatch(final INode target, final String groupName, final boolean matchMultiple) { _matchMultiple = matchMultiple; + _groupName = groupName; _target = VerifyArgument.notNull(target, "target"); } @@ -46,6 +55,9 @@ public final boolean matches(final INode other, final Match match) { for (final INode n : TreeTraversal.preOrder(other, INode.CHILD_ITERATOR)) { if (_target.matches(n, match)) { + if (_groupName != null) { + match.add(_groupName, n); + } result = true; } } @@ -53,7 +65,7 @@ public final boolean matches(final INode other, final Match match) { return result; } else { - return any( + final INode n = firstOrDefault( TreeTraversal.preOrder(other, INode.CHILD_ITERATOR), new Predicate() { @Override @@ -62,6 +74,15 @@ public boolean test(final INode n) { } } ); + + if (n == null) + return false; + + if (_groupName != null) { + match.add(_groupName, n); + } + + return true; } } } diff --git a/Procyon.Core/src/main/java/com/strobel/core/StringUtilities.java b/Procyon.Core/src/main/java/com/strobel/core/StringUtilities.java index 45db603d..d1539407 100644 --- a/Procyon.Core/src/main/java/com/strobel/core/StringUtilities.java +++ b/Procyon.Core/src/main/java/com/strobel/core/StringUtilities.java @@ -529,6 +529,7 @@ public static String escape(final char ch) { return escapeCharacter(ch, false); } + @SuppressWarnings("SameParameterValue") private static String escapeCharacter(final char ch, final boolean isUnicodeSupported) { if (ch == '\'') { return "\\'"; @@ -597,7 +598,6 @@ public static String escape(final String value, final boolean quote) { return escape(value, quote, false); } - @SuppressWarnings("ConstantConditions") public static String escape(final String value, final boolean quote, final boolean isUnicodeSupported) { if (value == null) { return null; @@ -752,6 +752,9 @@ private static boolean shouldEscape(final char ch, final boolean quote, final bo } public static String repeat(final char ch, final int length) { + if (length == 0) { + return ""; + } VerifyArgument.isNonNegative(length, "length"); final char[] c = new char[length]; Arrays.fill(c, 0, length, ch); diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/CommandLineOptions.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/CommandLineOptions.java index e339d69e..d2cf1556 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/CommandLineOptions.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/CommandLineOptions.java @@ -17,6 +17,7 @@ package com.strobel.decompiler; import com.beust.jcommander.Parameter; +import com.strobel.assembler.metadata.CompilerTarget; import java.util.ArrayList; import java.util.List; @@ -38,12 +39,6 @@ public class CommandLineOptions { "may be removed or become the standard behavior in future releases.") private boolean _mergeVariables; - @Parameter( - names = { "-ei", "--explicit-imports" }, - description = "[DEPRECATED] Explicit imports are now enabled by default. " + - "This option will be removed in a future release.") - private boolean _forceExplicitImports; - @Parameter( names = { "-ci", "--collapse-imports" }, description = "Collapse multiple imports from the same package into a single wildcard import.") @@ -165,6 +160,15 @@ public class CommandLineOptions { description = "Simplify type-qualified member references in Java output [EXPERIMENTAL].") private boolean _simplifyMemberReferences; + @Parameter( + names = { "--text-block-line-min" }, + description = "Specify the minimum number of line breaks before string literals are rendered as text blocksDefault is 3; set to 0 to disable text blocks.") + private int _textBlockLineMinimum = 3; + + @Parameter( + names = { "--compiler-target" }, + description = "Explicitly specify the language version to decompile for, e.g., 1.7, 1.8, 8, 9, etc. [EXPERIMENTAL, INCOMPLETE]") + private String _compilerTargetOverride; @Parameter( names = { "-fq", "--force-qualified-references" }, @@ -414,4 +418,27 @@ public final boolean getSuppressBanner() { public final void setSuppressBanner(final boolean suppressBanner) { _suppressBanner = suppressBanner; } + + public final int getTextBlockLineMinimum() { + return _textBlockLineMinimum; + } + + public final void setTextBlockLineMinimum(final int textBlockLineMinimum) { + _textBlockLineMinimum = textBlockLineMinimum; + } + + public final CompilerTarget getCompilerTargetOverride() { + if (_compilerTargetOverride != null) { + return CompilerTarget.lookup(_compilerTargetOverride); + } + return null; + } + + public final void setCompilerTargetOverride(final String compilerTargetOverride) { + _compilerTargetOverride = compilerTargetOverride; + } + + public final void setCompilerTargetOverride(final CompilerTarget compilerTargetOverride) { + _compilerTargetOverride = compilerTargetOverride != null ? compilerTargetOverride.name : null; + } } diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java index f9f4d157..55f2a6dc 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java @@ -23,6 +23,7 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -95,6 +96,8 @@ public static void main(final String[] args) { settings.setSimplifyMemberReferences(options.getSimplifyMemberReferences()); settings.setForceFullyQualifiedReferences(options.getForceFullyQualifiedReferences()); settings.setDisableForEachTransforms(options.getDisableForEachTransforms()); + settings.setForcedCompilerTarget(options.getCompilerTargetOverride()); + settings.setTextBlockLineMinimum(options.getTextBlockLineMinimum()); settings.setTypeLoader(new InputTypeLoader()); if (!options.getSuppressBanner()) { @@ -366,7 +369,7 @@ private static Writer createWriter(final TypeDefinition type, final DecompilerSe if (StringUtilities.isNullOrWhitespace(outputDirectory)) { return new OutputStreamWriter( System.out, - settings.isUnicodeOutputEnabled() ? Charset.forName("UTF-8") + settings.isUnicodeOutputEnabled() ? StandardCharsets.UTF_8 : Charset.defaultCharset() ); } @@ -417,7 +420,7 @@ final class FileOutputWriter extends OutputStreamWriter { FileOutputWriter(final File file, final DecompilerSettings settings) throws IOException { super( new FileOutputStream(file), - settings.isUnicodeOutputEnabled() ? Charset.forName("UTF-8") + settings.isUnicodeOutputEnabled() ? StandardCharsets.UTF_8 : Charset.defaultCharset() ); this.file = file; @@ -459,10 +462,6 @@ final class NoRetryMetadataSystem extends MetadataSystem { NoRetryMetadataSystem() { } -// NoRetryMetadataSystem(final String classPath) { -// super(classPath); -// } - NoRetryMetadataSystem(final ITypeLoader typeLoader) { super(typeLoader); }