diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodDefinition.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodDefinition.java index c4a5d223..74e52a9e 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodDefinition.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodDefinition.java @@ -219,15 +219,17 @@ public int hashCode() { @Override public boolean equals(final Object obj) { - if (obj instanceof MethodDefinition) { - final MethodDefinition other = (MethodDefinition) obj; - - return StringUtilities.equals(getName(), other.getName()) && - StringUtilities.equals(getErasedSignature(), other.getErasedSignature()) && - typeNamesMatch(getDeclaringType(), other.getDeclaringType()); + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; } + final MethodDefinition other = (MethodDefinition) obj; - return false; + return StringUtilities.equals(getName(), other.getName()) && + StringUtilities.equals(getErasedSignature(), other.getErasedSignature()) && + typeNamesMatch(getDeclaringType(), other.getDeclaringType()); } @Override @@ -236,7 +238,7 @@ public void invalidateSignature() { _erasedSignature = null; } - private boolean typeNamesMatch(final TypeReference t1, final TypeReference t2) { + private static boolean typeNamesMatch(final TypeReference t1, final TypeReference t2) { return t1 != null && t2 != null && StringUtilities.equals(t1.getFullName(), t2.getFullName()); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodReference.java b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodReference.java index a3a0caf9..9b4c67b2 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodReference.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/assembler/metadata/MethodReference.java @@ -17,7 +17,6 @@ package com.strobel.assembler.metadata; import com.strobel.core.StringUtilities; -import com.strobel.core.VerifyArgument; import com.strobel.util.ContractUtils; import java.util.Collections; @@ -28,21 +27,24 @@ * Date: 1/6/13 * Time: 2:29 PM */ -public abstract class MethodReference extends MemberReference implements IMethodSignature, - IGenericParameterProvider, - IGenericContext { - protected final static String CONSTRUCTOR_NAME = ""; - protected final static String STATIC_INITIALIZER_NAME = ""; +public abstract class MethodReference extends MemberReference implements IMethodSignature { + protected static final String CONSTRUCTOR_NAME = ""; + protected static final String STATIC_INITIALIZER_NAME = ""; // - public abstract TypeReference getReturnType(); - public boolean hasParameters() { return !getParameters().isEmpty(); } - public abstract List getParameters(); + public boolean hasParameter(String name) { + for (ParameterDefinition parameterDefinition : getParameters()) { + if (parameterDefinition.getName().equals(name)) { + return true; + } + } + return false; + } public List getThrownTypes() { return Collections.emptyList(); @@ -117,11 +119,11 @@ protected StringBuilder appendName(final StringBuilder sb, final boolean fullNam } public boolean isConstructor() { - return MethodDefinition.CONSTRUCTOR_NAME.equals(getName()); + return CONSTRUCTOR_NAME.equals(getName()); } public boolean isTypeInitializer() { - return MethodDefinition.STATIC_INITIALIZER_NAME.equals(getName()); + return STATIC_INITIALIZER_NAME.equals(getName()); } // @@ -171,9 +173,9 @@ public GenericParameter findTypeVariable(final String name) { public MethodDefinition resolve() { final TypeReference declaringType = getDeclaringType(); - if (declaringType == null) + if (declaringType == null) { throw ContractUtils.unsupported(); - + } return declaringType.resolve(this); } 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 90cb2546..78b9a7cc 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 @@ -1654,6 +1654,7 @@ public Void visitParameterDeclaration(final ParameterDeclaration node, final Voi @Override public Void visitFieldDeclaration(final FieldDeclaration node, final Void ignored) { startNode(node); + formatter.resetLineNumberOffsets(OffsetToLineNumberConverter.NOOP_CONVERTER); writeAnnotations(node.getAnnotations(), true); writeModifiers(node.getModifiers()); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/MinMaxLineNumberVisitor.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/MinMaxLineNumberVisitor.java new file mode 100644 index 00000000..38a27412 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/MinMaxLineNumberVisitor.java @@ -0,0 +1,383 @@ +/* + * MinMaxLineNumberVisitor.java + * + * Copyright (c) 2013-2022 Mike Strobel and other contributors + * + * 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; + +import com.strobel.decompiler.languages.java.ast.Annotation; +import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; +import com.strobel.decompiler.languages.java.ast.AssertStatement; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; +import com.strobel.decompiler.languages.java.ast.BlockStatement; +import com.strobel.decompiler.languages.java.ast.BreakStatement; +import com.strobel.decompiler.languages.java.ast.BytecodeConstant; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ClassOfExpression; +import com.strobel.decompiler.languages.java.ast.ConditionalExpression; +import com.strobel.decompiler.languages.java.ast.ContinueStatement; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.DoWhileStatement; +import com.strobel.decompiler.languages.java.ast.EmptyStatement; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.ExpressionStatement; +import com.strobel.decompiler.languages.java.ast.ForEachStatement; +import com.strobel.decompiler.languages.java.ast.ForStatement; +import com.strobel.decompiler.languages.java.ast.GotoStatement; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IfElseStatement; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InlinedBytecodeExpression; +import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.LabelStatement; +import com.strobel.decompiler.languages.java.ast.LabeledStatement; +import com.strobel.decompiler.languages.java.ast.LambdaExpression; +import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; +import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.ReturnStatement; +import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; +import com.strobel.decompiler.languages.java.ast.SwitchExpression; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; +import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ThrowStatement; +import com.strobel.decompiler.languages.java.ast.TryCatchStatement; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; +import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; +import com.strobel.decompiler.languages.java.ast.WhileStatement; + +public class MinMaxLineNumberVisitor extends DepthFirstAstVisitor { + + private final LineNumberTableConverter lineNumberTableConverter; + private int minLineNumber = Integer.MAX_VALUE; + private int maxLineNumber = Integer.MIN_VALUE; + + public MinMaxLineNumberVisitor(final LineNumberTableConverter lineNumberTableConverter) { + this.lineNumberTableConverter = lineNumberTableConverter; + } + + private void updateMinMaxLineNumbers(int offset) { + if (offset != Expression.MYSTERY_OFFSET) { + int lineNumber = lineNumberTableConverter.getLineForOffset(offset); + minLineNumber = Math.min(minLineNumber, lineNumber); + maxLineNumber = Math.max(maxLineNumber, lineNumber); + } + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitInvocationExpression(node, data); + } + + @Override + public Void visitTypeReference(TypeReferenceExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitTypeReference(node, data); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitMemberReferenceExpression(node, data); + } + + @Override + public Void visitNullReferenceExpression(NullReferenceExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitNullReferenceExpression(node, data); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitThisReferenceExpression(node, data); + } + + @Override + public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitSuperReferenceExpression(node, data); + } + + @Override + public Void visitClassOfExpression(ClassOfExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitClassOfExpression(node, data); + } + + @Override + public Void visitBlockStatement(BlockStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitBlockStatement(node, data); + } + + @Override + public Void visitExpressionStatement(ExpressionStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitExpressionStatement(node, data); + } + + @Override + public Void visitBreakStatement(BreakStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitBreakStatement(node, data); + } + + @Override + public Void visitContinueStatement(ContinueStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitContinueStatement(node, data); + } + + @Override + public Void visitDoWhileStatement(DoWhileStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitDoWhileStatement(node, data); + } + + @Override + public Void visitEmptyStatement(EmptyStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitEmptyStatement(node, data); + } + + @Override + public Void visitIfElseStatement(IfElseStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitIfElseStatement(node, data); + } + + @Override + public Void visitLabelStatement(LabelStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitLabelStatement(node, data); + } + + @Override + public Void visitLabeledStatement(LabeledStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitLabeledStatement(node, data); + } + + @Override + public Void visitReturnStatement(ReturnStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitReturnStatement(node, data); + } + + @Override + public Void visitSwitchStatement(SwitchStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitSwitchStatement(node, data); + } + + @Override + public Void visitSwitchExpression(SwitchExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitSwitchExpression(node, data); + } + + @Override + public Void visitThrowStatement(ThrowStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitThrowStatement(node, data); + } + + @Override + public Void visitAnnotation(Annotation node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitAnnotation(node, data); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitVariableDeclaration(node, data); + } + + @Override + public Void visitWhileStatement(WhileStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitWhileStatement(node, data); + } + + @Override + public Void visitPrimitiveExpression(PrimitiveExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitPrimitiveExpression(node, data); + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitCastExpression(node, data); + } + + @Override + public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitBinaryOperatorExpression(node, data); + } + + @Override + public Void visitInstanceOfExpression(InstanceOfExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitInstanceOfExpression(node, data); + } + + @Override + public Void visitIndexerExpression(IndexerExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitIndexerExpression(node, data); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitIdentifierExpression(node, data); + } + + @Override + public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitUnaryOperatorExpression(node, data); + } + + @Override + public Void visitConditionalExpression(ConditionalExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitConditionalExpression(node, data); + } + + @Override + public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitArrayInitializerExpression(node, data); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitObjectCreationExpression(node, data); + } + + @Override + public Void visitArrayCreationExpression(ArrayCreationExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitArrayCreationExpression(node, data); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitAssignmentExpression(node, data); + } + + @Override + public Void visitForStatement(ForStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitForStatement(node, data); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitForEachStatement(node, data); + } + + @Override + public Void visitTryCatchStatement(TryCatchStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitTryCatchStatement(node, data); + } + + @Override + public Void visitGotoStatement(GotoStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitGotoStatement(node, data); + } + + @Override + public Void visitParenthesizedExpression(ParenthesizedExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitParenthesizedExpression(node, data); + } + + @Override + public Void visitSynchronizedStatement(SynchronizedStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitSynchronizedStatement(node, data); + } + + @Override + public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitAnonymousObjectCreationExpression(node, data); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitMethodGroupExpression(node, data); + } + + @Override + public Void visitAssertStatement(AssertStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitAssertStatement(node, data); + } + + @Override + public Void visitLambdaExpression(LambdaExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitLambdaExpression(node, data); + } + + @Override + public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitLocalTypeDeclarationStatement(node, data); + } + + @Override + public Void visitInlinedBytecode(InlinedBytecodeExpression node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitInlinedBytecode(node, data); + } + + @Override + public Void visitBytecodeConstant(BytecodeConstant node, Void data) { + updateMinMaxLineNumbers(node.getOffset()); + return super.visitBytecodeConstant(node, data); + } + + public int getMinLineNumber() { + return minLineNumber; + } + + public int getMaxLineNumber() { + return maxLineNumber; + } +} \ No newline at end of file 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 763f942c..7bf26df6 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 @@ -99,10 +99,16 @@ public void startNode(final AstNode node) { else if (node instanceof Statement) { offset = ((Statement) node).getOffset(); prefix = "/*SL:"; + } else if (node instanceof FieldDeclaration + && node.getLastChild() instanceof VariableInitializer + && node.getLastChild().getLastChild() instanceof Expression) { + offset = ((Expression) node.getLastChild().getLastChild()).getOffset(); + prefix = "/*SL:"; } if (offset != Expression.MYSTERY_OFFSET) { // Convert to a line number. - final int lineNumber = offset2LineNumber.getLineForOffset(offset); + final int lineNumber = node instanceof FieldDeclaration ? ((FieldDeclaration) node).getLineNumber() + : offset2LineNumber.getLineForOffset(offset); if (lineNumber > lastObservedLineNumber) { // Record a data structure mapping original to actual line numbers. final int lineOfComment = output.getRow(); 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 f12a18e6..b4b38c8c 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,12 @@ package com.strobel.decompiler.languages.java.ast; +import com.strobel.assembler.ir.attributes.AttributeNames; +import com.strobel.assembler.ir.attributes.LineNumberTableAttribute; +import com.strobel.assembler.ir.attributes.LineNumberTableEntry; +import com.strobel.assembler.ir.attributes.SourceAttribute; import com.strobel.assembler.metadata.Flags; +import com.strobel.assembler.metadata.MethodDefinition; import com.strobel.decompiler.languages.EntityType; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.patterns.Match; @@ -179,4 +184,22 @@ static boolean removeModifier(final AstNode node, final Flags.Flag modifier) { return false; } + + public int getFirstKnownLineNumber() { + MethodDefinition methodDefinition = getUserData(Keys.METHOD_DEFINITION); + if (methodDefinition != null) { + final LineNumberTableAttribute lineNumberTableAttribute = SourceAttribute.find(AttributeNames.LineNumberTable, methodDefinition.getSourceAttributes()); + if (lineNumberTableAttribute != null) { + List entries = lineNumberTableAttribute.getEntries(); + if (entries != null) { + for (LineNumberTableEntry entry : entries) { + if (entry != null) { + return entry.getLineNumber(); + } + } + } + } + } + return 0; + } } diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/FieldDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/FieldDeclaration.java index 59dec7a9..6cce00bf 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/FieldDeclaration.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/FieldDeclaration.java @@ -21,6 +21,9 @@ import com.strobel.decompiler.patterns.Match; public class FieldDeclaration extends EntityDeclaration { + + private int lineNumber; + public final AstNodeCollection getVariables() { return getChildrenByRole(Roles.VARIABLE); } @@ -48,4 +51,17 @@ public boolean matches(final INode other, final Match match) { return false; } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + @Override + public int getFirstKnownLineNumber() { + return lineNumber; + } } 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 0538cb54..c81ac700 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 @@ -25,10 +25,12 @@ import com.strobel.decompiler.patterns.Role; public class MethodDeclaration extends EntityDeclaration { - public final static Role DEFAULT_VALUE_ROLE = new Role<>("DefaultValue", Expression.class, Expression.NULL); + public static final Role DEFAULT_VALUE_ROLE = new Role<>("DefaultValue", Expression.class, Expression.NULL); - public final static TokenRole DEFAULT_KEYWORD = new TokenRole("default", TokenRole.FLAG_KEYWORD); - public final static TokenRole THROWS_KEYWORD = new TokenRole("throws", TokenRole.FLAG_KEYWORD); + public static final TokenRole DEFAULT_KEYWORD = new TokenRole("default", TokenRole.FLAG_KEYWORD); + public static final TokenRole THROWS_KEYWORD = new TokenRole("throws", TokenRole.FLAG_KEYWORD); + + private int firstKnownLineNumber; public final AstType getPrivateImplementationType() { return getChildByRole(PRIVATE_IMPLEMENTATION_TYPE_ROLE); @@ -111,6 +113,22 @@ public boolean matches(final INode other, final Match match) { return false; } + + public boolean isFirstLineNumberKnown() { + return firstKnownLineNumber > 0; + } + + @Override + public int getFirstKnownLineNumber() { + if (isFirstLineNumberKnown()) { + return firstKnownLineNumber; + } + return super.getFirstKnownLineNumber(); + } + + public void setFirstKnownLineNumber(int firstKnownLineNumber) { + this.firstKnownLineNumber = firstKnownLineNumber; + } // @@ -118,7 +136,7 @@ public static MethodDeclaration forPattern(final Pattern pattern) { return new MethodDeclaration.PatternPlaceholder(VerifyArgument.notNull(pattern, "pattern")); } - private final static class PatternPlaceholder extends MethodDeclaration { + private static final class PatternPlaceholder extends MethodDeclaration { final Pattern child; PatternPlaceholder(final Pattern child) { diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/TypeDeclaration.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/TypeDeclaration.java index f03d604a..829f85ad 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/TypeDeclaration.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/TypeDeclaration.java @@ -123,6 +123,19 @@ public boolean matches(final INode other, final Match match) { return false; } + @Override + public int getFirstKnownLineNumber() { + for (EntityDeclaration entityDeclaration : getMembers()) { + if (entityDeclaration != null) { + int firstKnownLineNumber = entityDeclaration.getFirstKnownLineNumber(); + if (firstKnownLineNumber > 0) { + return firstKnownLineNumber; + } + } + } + return 0; + } + // public final static TypeDeclaration NULL = new NullTypeDeclaration(); diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ReOrderMembersForLineStretchTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ReOrderMembersForLineStretchTransform.java new file mode 100644 index 00000000..c1cbd2bf --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/ReOrderMembersForLineStretchTransform.java @@ -0,0 +1,61 @@ +/* + * ReOrderMembersForLineStretchTransform.java + * + * Copyright (c) 2013-2022 Mike Strobel and other contributors + * + * 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.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.EntityDeclaration; +import com.strobel.decompiler.languages.java.ast.Roles; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class ReOrderMembersForLineStretchTransform extends ContextTrackingVisitor { + + public ReOrderMembersForLineStretchTransform(DecompilerContext context) { + super(context); + } + + @Override + protected Void visitTypeDeclarationOverride(TypeDeclaration typeDeclaration, Void p) { + AstNodeCollection members = typeDeclaration.getChildrenByRole(Roles.TYPE_MEMBER); + List sortedMembers = new ArrayList<>(members); + Collections.sort(sortedMembers, new MemberComparator()); + for (EntityDeclaration member : members) { + member.remove(); + } + for (EntityDeclaration member : sortedMembers) { + typeDeclaration.addChild(member, Roles.TYPE_MEMBER); + } + return p; + } + + private static class MemberComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + @Override + public int compare(EntityDeclaration e1, EntityDeclaration e2) { + return Integer.compare(e1.getFirstKnownLineNumber(), e2.getFirstKnownLineNumber()); + } + } +} diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteInitForLineStretchTransform.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteInitForLineStretchTransform.java new file mode 100644 index 00000000..734171e2 --- /dev/null +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/languages/java/ast/transforms/RewriteInitForLineStretchTransform.java @@ -0,0 +1,320 @@ +/* + * RewriteInitForLineStretchTransform.java + * + * Copyright (c) 2013-2022 Mike Strobel and other contributors + * + * 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.assembler.ir.attributes.AttributeNames; +import com.strobel.assembler.ir.attributes.LineNumberTableAttribute; +import com.strobel.assembler.ir.attributes.LocalVariableTableAttribute; +import com.strobel.assembler.ir.attributes.LocalVariableTableEntry; +import com.strobel.assembler.ir.attributes.SourceAttribute; +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.decompiler.languages.java.LineNumberTableConverter; +import com.strobel.decompiler.languages.java.MinMaxLineNumberVisitor; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AssignmentOperatorType; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.BlockStatement; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.EntityDeclaration; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.ExpressionStatement; +import com.strobel.decompiler.languages.java.ast.FieldDeclaration; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.MethodDeclaration; +import com.strobel.decompiler.languages.java.ast.Roles; +import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; +import com.strobel.decompiler.languages.java.ast.VariableInitializer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class RewriteInitForLineStretchTransform extends DepthFirstAstVisitor implements IAstTransform { + + private ConcurrentHashMap fieldDeclarations = new ConcurrentHashMap<>(); + private ConcurrentHashMap fieldInitLocations = new ConcurrentHashMap<>(); + private ConcurrentHashMap constructionDefinitionToDeclaration = new ConcurrentHashMap<>(); + + @Override + public Void visitBlockStatement(BlockStatement node, Void data) { + if (node.getParent() instanceof MethodDeclaration) { + MethodDeclaration methodDeclaration = (MethodDeclaration) node.getParent(); + MethodDefinition methodDefinition = methodDeclaration.getUserData(Keys.METHOD_DEFINITION); + if (methodDefinition != null && methodDefinition.isTypeInitializer()) { + final LineNumberTableAttribute lineNumberTable = SourceAttribute.find(AttributeNames.LineNumberTable, methodDefinition.getSourceAttributes()); + if (lineNumberTable != null) { + LineNumberTableConverter lineNumberTableConverter = new LineNumberTableConverter(lineNumberTable); + int previousLineNumber = 0; + int pivotLineNumber = 0; + MethodDeclaration newMethodDeclaration = null; + for (AstNode child : node.getChildren()) { + MinMaxLineNumberVisitor minMaxLineNumberVisitor = new MinMaxLineNumberVisitor(lineNumberTableConverter); + child.acceptVisitor(minMaxLineNumberVisitor, null); + int currentLineNumber = minMaxLineNumberVisitor.getMinLineNumber(); + if (!methodDeclaration.isFirstLineNumberKnown()) { + methodDeclaration.setFirstKnownLineNumber(currentLineNumber); + } + if (previousLineNumber > 0 && currentLineNumber > previousLineNumber + 3) { + newMethodDeclaration = (MethodDeclaration) methodDeclaration.clone(); + newMethodDeclaration.setFirstKnownLineNumber(currentLineNumber); + methodDeclaration.getParent().insertChildAfter(methodDeclaration, newMethodDeclaration, Roles.TYPE_MEMBER); + pivotLineNumber = currentLineNumber; + break; + } + previousLineNumber = minMaxLineNumberVisitor.getMaxLineNumber(); + } + if (pivotLineNumber > 0) { + for (AstNode child : node.getChildren()) { + MinMaxLineNumberVisitor minMaxLineNumberVisitor = new MinMaxLineNumberVisitor(lineNumberTableConverter); + child.acceptVisitor(minMaxLineNumberVisitor, null); + int currentLineNumber = minMaxLineNumberVisitor.getMinLineNumber(); + if (currentLineNumber >= pivotLineNumber) { + child.remove(); + } + } + } + if (newMethodDeclaration != null) { + BlockStatement body = newMethodDeclaration.getBody(); + if (body != null) { + for (AstNode child : body.getChildren()) { + MinMaxLineNumberVisitor minMaxLineNumberVisitor = new MinMaxLineNumberVisitor(lineNumberTableConverter); + child.acceptVisitor(minMaxLineNumberVisitor, null); + int currentLineNumber = minMaxLineNumberVisitor.getMinLineNumber(); + if (currentLineNumber < pivotLineNumber) { + child.remove(); + } + } + visitBlockStatement(body, data); + } + } + } + } + } + return super.visitBlockStatement(node, data); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, Void data) { + AstNode parent = node.getParent(); + if (parent instanceof ExpressionStatement + && parent.getParent() instanceof BlockStatement + && parent.getParent().getParent() instanceof EntityDeclaration + && node.getLeft() instanceof MemberReferenceExpression + && node.getOperator() == AssignmentOperatorType.ASSIGN) { + EntityDeclaration entityDeclaration = (EntityDeclaration) parent.getParent().getParent(); + MethodDefinition methodDefinition = entityDeclaration.getUserData(Keys.METHOD_DEFINITION); + if (methodDefinition != null && (methodDefinition.isConstructor() || methodDefinition.isTypeInitializer())) { + LineNumberTableAttribute lineNumberTable = SourceAttribute.find(AttributeNames.LineNumberTable, methodDefinition.getSourceAttributes()); + LineNumberTableConverter lineNumberTableConverter = new LineNumberTableConverter(lineNumberTable); + MemberReferenceExpression memberReferenceExpression = (MemberReferenceExpression) node.getFirstChild(); + MemberReference memberReference = memberReferenceExpression.getUserData(Keys.MEMBER_REFERENCE); + FieldDeclaration fieldDeclaration = fieldDeclarations.get(memberReference.getFullName()); + Expression initializer = node.getRight(); + int offset = initializer.getOffset(); + if (offset != Expression.MYSTERY_OFFSET) { + int lineNumber = lineNumberTableConverter.getLineForOffset(offset); + if (lineNumber > 0 && fieldDeclaration != null && !methodDefinition.hasParameter(memberReference.getName())) { + LocalVariableTableAttribute localVariableTable = SourceAttribute.find(AttributeNames.LocalVariableTable, methodDefinition.getSourceAttributes()); + IdentifierGatherer identifierGatherer = new IdentifierGatherer(); + initializer.acceptVisitor(identifierGatherer, null); + if (localVariableTable == null || identifierGatherer.containsNoneOf(localVariableTable.getEntries())) { + fieldDeclaration.setLineNumber(lineNumber); + FieldLocation fieldLocation = new FieldLocation(memberReference.getFullName(), offset); + fieldInitLocations.putIfAbsent(fieldLocation, new FieldInit(fieldDeclaration)); + fieldInitLocations.get(fieldLocation).init(initializer, (ExpressionStatement) node.getParent(), methodDefinition); + } + } + } + } + } + return super.visitAssignmentExpression(node, data); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, Void data) { + FieldDefinition fieldDefinition = node.getUserData(Keys.FIELD_DEFINITION); + if (fieldDefinition != null) { + fieldDeclarations.put(fieldDefinition.getFullName(), node); + } + return super.visitFieldDeclaration(node, data); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, Void data) { + if (node.getParent() instanceof InvocationExpression) { + MemberReference memberReference = node.getParent().getUserData(Keys.MEMBER_REFERENCE); + if (memberReference instanceof MethodDefinition) { + MethodDefinition methodDefinition = (MethodDefinition) memberReference; + ConstructorDeclaration constructorDeclaration = constructionDefinitionToDeclaration.get(methodDefinition); + if (constructorDeclaration != null) { + visitConstructorDeclaration(constructorDeclaration, data); + } + } + } + return super.visitThisReferenceExpression(node, data); + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new ConstructorGatherer(), null); + compilationUnit.acceptVisitor(this, null); + for (FieldInit fieldInit : fieldInitLocations.values()) { + if (fieldInit.isInAllConstructors() || fieldInit.isInTypeInitializer()) { + fieldInit.removeFieldInitStatements(); + fieldInit.createVariableInitializer(); + } + } + } + + private class FieldInit { + private final FieldDeclaration declaration; + private final List fieldInitStatements = new ArrayList<>(); + private boolean inConstructor = true; + private boolean inTypeInitializer = true; + + public FieldInit(FieldDeclaration declaration) { + this.declaration = declaration; + } + + public void init(Expression initializer, ExpressionStatement expressionStatement, MethodDefinition initMethod) { + inConstructor &= initMethod != null && initMethod.isConstructor(); + inTypeInitializer &= initMethod != null && initMethod.isTypeInitializer(); + fieldInitStatements.add(new FieldInitStatement(initializer, expressionStatement)); + } + + public void removeFieldInitStatements() { + AstNode parent = null; + for (FieldInitStatement fieldInitStatement : fieldInitStatements) { + parent = fieldInitStatement.statement.getParent(); + fieldInitStatement.remove(); + } + if (isInTypeInitializer() && parent != null && !parent.hasChildren() && parent.getParent() instanceof MethodDeclaration) { + parent.getParent().remove(); + } + } + + public boolean isInAllConstructors() { + int constructorCount = countConstructors(); + return inConstructor && constructorCount > 0 && fieldInitStatements.size() == constructorCount; + } + + private int countConstructors() { + int constructorCount = 0; + FieldDefinition fieldDefinition = declaration.getUserData(Keys.FIELD_DEFINITION); + if (fieldDefinition != null) { + for (MethodDefinition methodDefinition : constructionDefinitionToDeclaration.keySet()) { + if (methodDefinition.getDeclaringType() == fieldDefinition.getDeclaringType()) { + constructorCount++; + } + } + } + return constructorCount; + } + + public boolean isInTypeInitializer() { + return inTypeInitializer; + } + + public void createVariableInitializer() { + declaration.getVariables().clear(); + declaration.getVariables().add(new VariableInitializer(declaration.getName(), fieldInitStatements.get(0).initializer)); + } + } + + private static class FieldInitStatement { + private final Expression initializer; + private final ExpressionStatement statement; + + public FieldInitStatement(Expression initializer, ExpressionStatement statement) { + this.initializer = initializer; + this.statement = statement; + } + + public void remove() { + initializer.remove(); + statement.remove(); + } + } + + private static class FieldLocation { + private final String fieldName; + private final int fieldOffset; + + public FieldLocation(String fieldName, int fieldOffset) { + this.fieldName = fieldName; + this.fieldOffset = fieldOffset; + } + + @Override + public int hashCode() { + return Objects.hash(fieldName, fieldOffset); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + FieldLocation other = (FieldLocation) obj; + return Objects.equals(fieldName, other.fieldName) && fieldOffset == other.fieldOffset; + } + } + + private class ConstructorGatherer extends DepthFirstAstVisitor { + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, Void p) { + MethodDefinition methodDefinition = node.getUserData(Keys.METHOD_DEFINITION); + if (methodDefinition != null) { + constructionDefinitionToDeclaration.put(methodDefinition, node); + } + return super.visitConstructorDeclaration(node, p); + } + } + + private static class IdentifierGatherer extends DepthFirstAstVisitor { + + private Set identifiers = new HashSet<>(); + + public boolean containsNoneOf(List entries) { + for (LocalVariableTableEntry entry : entries) { + if (identifiers.contains(entry.getName())) { + return false; + } + } + return true; + } + + @Override + public Void visitIdentifier(Identifier node, Void data) { + identifiers.add(node.getName()); + return super.visitIdentifier(node, data); + } + } +} 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 6a2ff63f..1dd36a86 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 @@ -1,7 +1,7 @@ /* * TransformationPipeline.java * - * Copyright (c) 2013 Mike Strobel + * Copyright (c) 2013-2022 Mike Strobel and other contributors * * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. @@ -76,7 +76,9 @@ public static IAstTransform[] createPipeline(final DecompilerContext context) { new AddStandardAnnotationsTransform(context), new AddReferenceQualifiersTransform(context), new RemoveHiddenMembersTransform(context), - new CollapseImportsTransform(context) + new CollapseImportsTransform(context), + new RewriteInitForLineStretchTransform(), + new ReOrderMembersForLineStretchTransform(context) }; } diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/DecompilerTest.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/DecompilerTest.java index 06ff764e..a52eebd1 100644 --- a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/DecompilerTest.java +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/DecompilerTest.java @@ -113,6 +113,7 @@ protected void verifyOutput(final Class type, final DecompilerSettings settin throw ExceptionUtilities.asRuntimeException(e); } } + protected void verifyOutput(final String internalName, final DecompilerSettings settings, final String expectedOutput) { final PlainTextOutput writer = new PlainTextOutput(); diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EncodingTests.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EncodingTests.java index f0f7d80f..c99b7ea2 100644 --- a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EncodingTests.java +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/EncodingTests.java @@ -61,7 +61,6 @@ public void testUnicodeIdentifierEscaping() { A.class, defaultSettings(), "private static final class A {\n" + - " static String \\ufe4f\\u2167;\n" + " static final transient short x\\u03a7x = 5;\n" + " private static String __\\u0130\\u00dfI(final A x) {\n" + " return A.\\ufe4f\\u2167;\n" + @@ -70,9 +69,7 @@ public void testUnicodeIdentifierEscaping() { " System.out.println(__\\u0130\\u00dfI(null));\n" + " System.out.println(\"\\\"\\u0000\\u000fu\\\\\\\"\\ff'\\rr'\\nn \\u0123\\u1234O\\uffffF\");\n" + " }\n" + - " static {\n" + - " A.\\ufe4f\\u2167 = \"\\ufeff\\ud800\\ud8d8\\udffd\";\n" + - " }\n" + + " static String \\ufe4f\\u2167 = \"\\ufeff\\ud800\\ud8d8\\udffd\";\n" + "}" ); } diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/NameTests.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/NameTests.java index 5c1822f4..2f4e73d2 100644 --- a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/NameTests.java +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/NameTests.java @@ -149,6 +149,9 @@ public void testVariablesHideClasses() throws Throwable { A.class, defaultSettings(), "static final class A {\n" + + " static class x {\n" + + " static int y;\n" + + " }\n" + " static final class Inner {\n" + " void f(final int x, final Object Integer) {\n" + " final x z = new x();\n" + @@ -158,9 +161,6 @@ public void testVariablesHideClasses() throws Throwable { " System.out.println(Integer instanceof Integer);\n" + " }\n" + " }\n" + - " static class x {\n" + - " static int y;\n" + - " }\n" + "}\n" ); } @@ -171,6 +171,8 @@ public void testLocalImportedAndNestedTypeNameCollisions() throws Throwable { B.class, defaultSettings(), "static final class B {\n" + + " static class Integer {\n" + + " }\n" + " void f(final Object o) {\n" + " class Integer {\n" + " }\n" + @@ -184,8 +186,6 @@ public void testLocalImportedAndNestedTypeNameCollisions() throws Throwable { " System.out.println(java.lang.Integer.class);\n" + " }\n" + " }\n" + - " static class Integer {\n" + - " }\n" + "}\n" ); } @@ -196,14 +196,11 @@ public void testFieldHidesImportedTypeName() throws Throwable { C.class, defaultSettings(), "static final class C {\n" + - " static float Float;\n" + + " static float Float = java.lang.Float.NaN;\n" + " void f() {\n" + " System.out.println(C.Float);\n" + " System.out.println(java.lang.Float.NaN);\n" + " }\n" + - " static {\n" + - " C.Float = java.lang.Float.NaN;\n" + - " }\n" + "}\n" ); } @@ -214,13 +211,13 @@ public void testFormalParameterHidesNestedClass() throws Throwable { D.class, defaultSettings(), "static final class D {\n" + + " static class x {\n" + + " static int y;\n" + + " }\n" + " void f(final int x) {\n" + " System.out.println(x * 2);\n" + " System.out.println(D.x.y);\n" + " }\n" + - " static class x {\n" + - " static int y;\n" + - " }\n" + "}\n" ); } @@ -247,14 +244,14 @@ public void testMethodTypeParameterHidesInnerClass() throws Throwable { F.class, defaultSettings(), "static final class F {\n" + + " static class x {\n" + + " static int y;\n" + + " }\n" + " void f(final x z) {\n" + " System.out.println(z);\n" + " System.out.println(F.x.y);\n" + " }\n" + " \n" + - " static class x {\n" + - " static int y;\n" + - " }\n" + "}\n" ); } @@ -265,14 +262,14 @@ public void testClassTypeParameterHidesInnerClass() throws Throwable { G.class, defaultSettings(), "static final class G {\n" + + " static class x {\n" + + " static int y;\n" + + " }\n" + " void f(final x z) {\n" + " System.out.println(z);\n" + " System.out.println(G.x.y);\n" + " }\n" + " \n" + - " static class x {\n" + - " static int y;\n" + - " }\n" + "}\n" ); } @@ -284,12 +281,12 @@ public void testInnerClassHidesBaseInnerClass() throws Throwable { defaultSettings(), "static final class H extends DeclaresX {\n" + " X z;\n" + + " static class X {\n" + + " }\n" + " void f(final DeclaresX.X x) {\n" + " System.out.println(x);\n" + " System.out.println(this.z);\n" + " }\n" + - " static class X {\n" + - " }\n" + "}\n" ); } @@ -316,13 +313,13 @@ public void testInnerClassHidesBaseInnerClassAndInterfaceInnerClass() throws Thr defaultSettings(), "static final class J extends DeclaresX implements IDeclaresX {\n" + " X z;\n" + + " static class X {\n" + + " }\n" + " void f(final DeclaresX.X x, final IDeclaresX.X y) {\n" + " System.out.println(x);\n" + " System.out.println(y);\n" + " System.out.println(this.z);\n" + " }\n" + - " static class X {\n" + - " }\n" + "}\n" ); } diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/OperatorTests.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/OperatorTests.java index 5f45ec31..fd85b9db 100644 --- a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/OperatorTests.java +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/OperatorTests.java @@ -72,11 +72,11 @@ private static class G { private int x; private static int y; private int[] a; - private static int[] b = new int[] { 0 }; + private static int[] b = new int[] { 1 }; private G() { super(); - this.a = new int[] { 0 }; + this.a = new int[] { 1 }; } public int f() { @@ -255,11 +255,10 @@ public void testPostIncrementOptimizations() { "private static class G {\n" + " private int x;\n" + " private static int y;\n" + - " private int[] a;\n" + - " private static int[] b;\n" + + " private static int[] b = { 1 };\n" + " private G() {\n" + - " this.a = new int[] { 0 };\n" + " }\n" + + " private int[] a = { 1 };\n" + " public int f() {\n" + " return this.x++;\n" + " }\n" + @@ -269,9 +268,6 @@ public void testPostIncrementOptimizations() { " public int h(int n) {\n" + " return (++this.x + this.a[n++]) / (this.a[++n] + ++this.a[n]) * ++G.b[++G.y];\n" + " }\n" + - " static {\n" + - " G.b = new int[] { 0 };\n" + - " }\n" + "}\n" ); } diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/PrimitiveTests.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/PrimitiveTests.java index d1e208de..a3c6c5aa 100644 --- a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/PrimitiveTests.java +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/PrimitiveTests.java @@ -38,11 +38,11 @@ public void testFloatingPointPrecision() throws Throwable { defaultSettings(), "private static final class A {\n" + " static final float e = 2.7182817f;\n" + - " static double d;\n" + - " static double w;\n" + - " static double x;\n" + - " static double y;\n" + - " static float z;\n" + + " static double d = 2.7182818459;\n" + + " static double w = Double.POSITIVE_INFINITY;\n" + + " static double x = Double.NEGATIVE_INFINITY;\n" + + " static double y = -4.9E-324;\n" + + " static float z = Float.MIN_VALUE;\n" + " public static strictfp void test() {\n" + " final double t = 9.007199254740992E15;\n" + " final double x = t * t;\n" + @@ -57,13 +57,6 @@ public void testFloatingPointPrecision() throws Throwable { " System.out.println(A.z);\n" + " System.out.println((double)A.z);\n" + " }\n" + - " static {\n" + - " A.d = 2.7182818459;\n" + - " A.w = Double.POSITIVE_INFINITY;\n" + - " A.x = Double.NEGATIVE_INFINITY;\n" + - " A.y = -4.9E-324;\n" + - " A.z = Float.MIN_VALUE;\n" + - " }\n" + "}" ); } diff --git a/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/RealignTest.java b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/RealignTest.java new file mode 100644 index 00000000..ae50ff11 --- /dev/null +++ b/Procyon.CompilerTools/src/test/java/com/strobel/decompiler/RealignTest.java @@ -0,0 +1,353 @@ +package com.strobel.decompiler; + +import org.junit.Test; + +public class RealignTest extends DecompilerTest { + + static class ReOrderMembers { + + public void method1() { + System.out.println("Test.method1"); + } + + public void method2() { + System.out.println("Test.method2"); + } + + class Inner1 { + + public Inner1() { + System.out.println("Inner1 constructor"); + } + + public void method1() { + System.out.println("Inner1.method1"); + } + + public void method2() { + System.out.println("Inner1.method2"); + } + } + + class Inner2 { + + public Inner2() { + System.out.println("Inner2 constructor"); + } + } + + + public void method3() { + System.out.println("Test.method3"); + } + + public void method4() { + System.out.println("Test.method4"); + } + + class Inner3 { + + public Inner3() { + System.out.println("Inner3 constructor"); + } + + public void method1() { + System.out.println("Inner3.method1"); + } + + public void method2() { + System.out.println("Inner3.method2"); + } + } + + class Inner4 { + + public Inner4() { + System.out.println("Inner4 constructor"); + } + + public void method1() { + System.out.println("Inner4.method1"); + } + + public void method2() { + System.out.println("Inner4.method2"); + } + } + + public void method5() { + System.out.println("Test.method5"); + } + + public void method6() { + System.out.println("Test.method6"); + } + } + + static class RewriteInit { + + private RewriteInit top = new RewriteInit(0); + private RewriteInit test; + + static { + System.out.println("clinit1"); + } + + public RewriteInit(int i) { + System.out.println(i); + } + + private RewriteInit middle = new RewriteInit(false); + + public RewriteInit(boolean flag) { + System.out.println(flag); + test = new RewriteInit(0); // must not be moved to declaration + } + + static { + System.out.println("clinit2"); + } + + private RewriteInit bottom = new RewriteInit(true); + + static { + System.out.println("clinit3"); + } + } + + static class RewriteInit2 { + + private RewriteInit2 top = new RewriteInit2(0); + private RewriteInit2 test; + + static { + System.out.println("clinit1"); + } + + public RewriteInit2(int i) { + this(false); + System.out.println(i); + } + + private RewriteInit2 middle = new RewriteInit2(false); + + public RewriteInit2(boolean flag) { + System.out.println(flag); + test = new RewriteInit2(0); // can be moved to declaration (because of this(...) call) + } + + static { + System.out.println("clinit2"); + } + + private RewriteInit2 bottom = new RewriteInit2(true); + + static { + System.out.println("clinit3"); + } + + class Inner { + Inner() { + System.out.println("Inner"); + } + } + } + + static class RewriteInit3 { + + private int _a, _b; + + public RewriteInit3(int a) { + _a = a; + double b = Math.random(); + _b = b < 0.5 ? 1 : 0; + } + } + + + @Test + public void testReOrderMembers() throws Throwable { + verifyOutput( + ReOrderMembers.class, + lineNumberSettings(), + "static class ReOrderMembers {\n" + + " public void method1() {\n" + + " System.out.println(/*EL:10*/\"Test.method1\");\n" + + " }\n" + + " \n" + + " public void method2() {\n" + + " System.out.println(/*EL:14*/\"Test.method2\");\n" + + " }\n" + + " \n" + + " class Inner1 {\n" + + " public Inner1() {\n" + + " System.out.println(/*EL:20*/\"Inner1 constructor\");\n" + + " }\n" + + " \n" + + " public void method1() {\n" + + " System.out.println(/*EL:24*/\"Inner1.method1\");\n" + + " }\n" + + " \n" + + " public void method2() {\n" + + " System.out.println(/*EL:28*/\"Inner1.method2\");\n" + + " }\n" + + " }\n" + + " \n" + + " class Inner2 {\n" + + " public Inner2() {\n" + + " System.out.println(/*EL:35*/\"Inner2 constructor\");\n" + + " }\n" + + " }\n" + + " \n" + + " public void method3() {\n" + + " System.out.println(/*EL:41*/\"Test.method3\");\n" + + " }\n" + + " \n" + + " public void method4() {\n" + + " System.out.println(/*EL:45*/\"Test.method4\");\n" + + " }\n" + + " \n" + + " class Inner3 {\n" + + " public Inner3() {\n" + + " System.out.println(/*EL:51*/\"Inner3 constructor\");\n" + + " }\n" + + " \n" + + " public void method1() {\n" + + " System.out.println(/*EL:55*/\"Inner3.method1\");\n" + + " }\n" + + " \n" + + " public void method2() {\n" + + " System.out.println(/*EL:59*/\"Inner3.method2\");\n" + + " }\n" + + " }\n" + + " \n" + + " class Inner4 {\n" + + " public Inner4() {\n" + + " System.out.println(/*EL:66*/\"Inner4 constructor\");\n" + + " }\n" + + " \n" + + " public void method1() {\n" + + " System.out.println(/*EL:70*/\"Inner4.method1\");\n" + + " }\n" + + " \n" + + " public void method2() {\n" + + " System.out.println(/*EL:74*/\"Inner4.method2\");\n" + + " }\n" + + " }\n" + + " \n" + + " public void method5() {\n" + + " System.out.println(/*EL:79*/\"Test.method5\");\n" + + " }\n" + + " \n" + + " public void method6() {\n" + + " System.out.println(/*EL:83*/\"Test.method6\");\n" + + " }\n" + + "}" + ); + } + + + @Test + public void testRewriteInit() throws Throwable { + verifyOutput( + RewriteInit.class, + lineNumberSettings(), + "static class RewriteInit {\n" + + " /*SL:89*/private RewriteInit top = new RewriteInit(0);\n" + + " static {\n" + + " System.out.println(/*EL:93*/\"clinit1\");\n" + + " }\n" + + " \n" + + " public RewriteInit(final int i) {\n" + + " System.out.println(/*EL:97*/i);\n" + + " }\n" + + " \n" + + " /*SL:100*/private RewriteInit middle = new RewriteInit(false);\n" + + " \n" + + " public RewriteInit(final boolean flag) {\n" + + " System.out.println(/*EL:103*/flag);\n" + + " /*SL:104*/this.test = new RewriteInit(0);\n" + + " }\n" + + " \n" + + " private RewriteInit test;\n" + + " static {\n" + + " System.out.println(/*EL:108*/\"clinit2\");\n" + + " }\n" + + " /*SL:111*/private RewriteInit bottom = new RewriteInit(true);\n" + + " static {\n" + + " System.out.println(/*EL:114*/\"clinit3\");\n" + + " }\n" + + "}" + ); + } + + @Test + public void testRewriteInit2() throws Throwable { + verifyOutput( + RewriteInit2.class, + lineNumberSettings(), + "static class RewriteInit2 {\n" + + " /*SL:120*/private RewriteInit2 top = new RewriteInit2(0);\n" + + " static {\n" + + " System.out.println(/*EL:124*/\"clinit1\");\n" + + " }\n" + + " \n" + + " public RewriteInit2(final int i) {\n" + + " /*SL:128*/this(false);\n" + + " System.out.println(/*EL:129*/i);\n" + + " }\n" + + " \n" + + " /*SL:132*/private RewriteInit2 middle = new RewriteInit2(false);\n" + + " \n" + + " public RewriteInit2(final boolean flag) {\n" + + " System.out.println(/*EL:135*/flag);\n" + + " }\n" + + " /*SL:136*/private RewriteInit2 test = new RewriteInit2(0);\n" + + " static {\n" + + " System.out.println(/*EL:140*/\"clinit2\");\n" + + " }\n" + + " /*SL:143*/private RewriteInit2 bottom = new RewriteInit2(true);\n" + + " static {\n" + + " System.out.println(/*EL:146*/\"clinit3\");\n" + + " }\n" + + " class Inner {\n" + + " Inner() {\n" + + " System.out.println(/*EL:151*/\"Inner\");\n" + + " }\n" + + " }\n" + + "}" + ); + } + + @Test + public void testRewriteInit3() throws Throwable { + verifyOutput( + RewriteInit3.class, + lineNumberSettings(), + "static class RewriteInit3 {\n" + + " private int _a;\n" + + " private int _b;\n" + + " public RewriteInit3(final int a) {\n" + + " /*SL:161*/this._a = a;\n" + + " final double b = /*EL:162*/Math.random();\n" + + " /*SL:163*/this._b = ((b < 0.5) ? 1 : 0);\n" + + " }\n" + + "}" + ); + } + + @Test + public void testRewriteInitThrowable() throws Throwable { + Decompiler.decompile( + "java/lang/Throwable", + new PlainTextOutput(), + lineNumberSettings() + ); + } + + private static DecompilerSettings lineNumberSettings() { + DecompilerSettings lineNumberSettings = defaultSettings(); + lineNumberSettings.setShowDebugLineNumbers(true); + return lineNumberSettings; + } +}