From b61401f61ddb9dbc63a756da26ca65b6e5e692fd Mon Sep 17 00:00:00 2001 From: nbauma109 Date: Mon, 14 Feb 2022 22:29:31 +0100 Subject: [PATCH 1/5] Create fork-sync.yml --- .github/workflows/fork-sync.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/fork-sync.yml diff --git a/.github/workflows/fork-sync.yml b/.github/workflows/fork-sync.yml new file mode 100644 index 00000000..ed8d4c86 --- /dev/null +++ b/.github/workflows/fork-sync.yml @@ -0,0 +1,18 @@ +name: Sync Fork + +on: + schedule: + - cron: '*/30 * * * *' # every 30 minutes + workflow_dispatch: # on button click + +jobs: + sync: + + runs-on: ubuntu-latest + + steps: + - uses: tgymnich/fork-sync@v1.4 + with: + owner: mstrobel + base: develop + head: develop From d52e076cb1fef1bd64c05caf76faecf97d9330b9 Mon Sep 17 00:00:00 2001 From: nbauma109 Date: Tue, 15 Feb 2022 07:09:16 +0100 Subject: [PATCH 2/5] Update fork-sync.yml --- .github/workflows/fork-sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fork-sync.yml b/.github/workflows/fork-sync.yml index ed8d4c86..c3efb312 100644 --- a/.github/workflows/fork-sync.yml +++ b/.github/workflows/fork-sync.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: tgymnich/fork-sync@v1.4 + - uses: tgymnich/fork-sync@v1.6.3 with: owner: mstrobel base: develop From 8c28d0a2e8d06a539b615cf7e5d71d7fb33dffd5 Mon Sep 17 00:00:00 2001 From: nbauma109 Date: Sat, 19 Mar 2022 12:01:51 +0100 Subject: [PATCH 3/5] remove temp file for line number output and produce String output --- .../strobel/decompiler/DecompilerDriver.java | 53 +++---- .../decompiler/LineNumberFormatter.java | 143 +++++++++--------- .../decompiler/NoRetryMetadataSystem.java | 34 +++++ 3 files changed, 124 insertions(+), 106 deletions(-) create mode 100644 Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java 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 67800d13..12c7d0aa 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java @@ -4,12 +4,20 @@ import com.strobel.Procyon; import com.strobel.annotations.NotNull; import com.strobel.assembler.InputTypeLoader; -import com.strobel.assembler.metadata.*; +import com.strobel.assembler.metadata.CompositeTypeLoader; +import com.strobel.assembler.metadata.DeobfuscationUtilities; +import com.strobel.assembler.metadata.IMetadataResolver; +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.JarTypeLoader; +import com.strobel.assembler.metadata.MetadataParser; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; import com.strobel.core.ExceptionUtilities; import com.strobel.core.StringUtilities; import com.strobel.decompiler.LineNumberFormatter.LineNumberOption; -import com.strobel.decompiler.languages.BytecodeOutputOptions; import com.strobel.decompiler.languages.BytecodeLanguage; +import com.strobel.decompiler.languages.BytecodeOutputOptions; import com.strobel.decompiler.languages.Languages; import com.strobel.decompiler.languages.LineNumberPosition; import com.strobel.decompiler.languages.TypeDecompilationResults; @@ -24,14 +32,13 @@ import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.EnumSet; import java.util.Enumeration; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.ConsoleHandler; @@ -295,7 +302,7 @@ private static void decompileType( final TypeDefinition resolvedType; if (type == null || (resolvedType = type.resolve()) == null) { - System.err.printf("!!! ERROR: Failed to load class %s.\n", typeName); + System.err.printf("!!! ERROR: Failed to load class %s.%n", typeName); return; } @@ -327,7 +334,7 @@ private static void decompileType( } if (writeToFile) { - System.out.printf("Decompiling %s...\n", typeName); + System.out.printf("Decompiling %s...%n", typeName); } final TypeDecompilationResults results = settings.getLanguage().decompileType(resolvedType, output, options); @@ -353,13 +360,17 @@ private static void decompileType( lineNumberOptions.add(LineNumberOption.STRETCHED); } + final File outputFile = ((FileOutputWriter) writer).getFile(); final LineNumberFormatter lineFormatter = new LineNumberFormatter( - ((FileOutputWriter) writer).getFile(), + outputFile, lineNumberPositions, lineNumberOptions ); - lineFormatter.reformatFile(); + final String reformattedFile = lineFormatter.reformatFile(); + final Charset charset = settings.isUnicodeOutputEnabled() ? StandardCharsets.UTF_8 + : Charset.defaultCharset(); + Files.write(outputFile.toPath(), reformattedFile.getBytes(charset)); } } @@ -454,30 +465,4 @@ public String format(@NotNull final LogRecord record) { " [" + record.getLevel() + "] " + loggerName + ": " + record.getMessage() + ' ' + lineSep; } -} - -final class NoRetryMetadataSystem extends MetadataSystem { - private final Set _failedTypes = new HashSet<>(); - - NoRetryMetadataSystem() { - } - - NoRetryMetadataSystem(final ITypeLoader typeLoader) { - super(typeLoader); - } - - @Override - protected TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { - if (_failedTypes.contains(descriptor)) { - return null; - } - - final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); - - if (result == null) { - _failedTypes.add(descriptor); - } - - return result; - } } \ No newline at end of file diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java index a05e386d..5e12ba05 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java @@ -1,11 +1,11 @@ package com.strobel.decompiler; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -19,7 +19,7 @@ * line number information. It can handle either, or both, of the following jobs: * *
    - *
  • Introduce line numbers as leading comments. + *
  • Introduce line numbers as leading comments. *
  • Stretch the file so that the line number comments match the physical lines. *
*/ @@ -27,17 +27,17 @@ public class LineNumberFormatter { private final List _positions; private final File _file; private final EnumSet _options; - + public enum LineNumberOption { LEADING_COMMENTS, STRETCHED, } - + /** * Constructs an instance. * - * @param file the file whose line numbers should be fixed + * @param file the file whose line numbers should be fixed * @param lineNumberPositions a recipe for how to fix the line numbers in 'file'. * @param options controls how 'this' represents line numbers in the resulting file */ @@ -46,115 +46,118 @@ public LineNumberFormatter(File file, EnumSet options) { _file = file; _positions = lineNumberPositions; - _options = (options == null ? EnumSet.noneOf( LineNumberOption.class) : options); + _options = (options == null ? EnumSet.noneOf(LineNumberOption.class) : options); } /** - * Rewrites the file passed to 'this' constructor so that the actual line numbers match - * the recipe passed to 'this' constructor. + * Rewrites the file passed to 'this' constructor so that the actual line + * numbers match the recipe passed to 'this' constructor. + * + * @return The formatted source code */ - public void reformatFile() throws IOException { - List lineBrokenPositions = new ArrayList(); - List brokenLines = breakLines( lineBrokenPositions); - emitFormatted( brokenLines, lineBrokenPositions); + public String reformatFile() throws IOException { + List lineBrokenPositions = new ArrayList<>(); + List brokenLines = breakLines(lineBrokenPositions); + try (StringWriter sw = new StringWriter()) { + emitFormatted(brokenLines, lineBrokenPositions, sw); + return sw.toString(); + } } - + /** * Processes {@link #_file}, breaking apart any lines on which multiple line-number markers * appear in different columns. * * @return the list of broken lines */ - private List breakLines( List o_LineBrokenPositions) throws IOException { + private List breakLines(List o_LineBrokenPositions) throws IOException { int numLinesRead = 0; int lineOffset = 0; List brokenLines = new ArrayList<>(); - try( BufferedReader r = new BufferedReader( new FileReader( _file))) { - for ( int posIndex=0; posIndex<_positions.size(); posIndex++) { - LineNumberPosition pos = _positions.get( posIndex); + try (BufferedReader r = new BufferedReader(new FileReader(_file))) { + for (int posIndex = 0; posIndex < _positions.size(); posIndex++) { + LineNumberPosition pos = _positions.get(posIndex); o_LineBrokenPositions.add( new LineNumberPosition( pos.getOriginalLine(), pos.getEmittedLine()+lineOffset, pos.getEmittedColumn())); - + // Copy the input file up to but not including the emitted line # in "pos". - while ( numLinesRead < pos.getEmittedLine()-1) { - brokenLines.add( r.readLine()); + while (numLinesRead < pos.getEmittedLine() - 1) { + brokenLines.add(r.readLine()); numLinesRead++; } - + // Read the line that contains the next line number annotations, but don't write it yet. String line = r.readLine(); numLinesRead++; - + // See if there are two original line annotations on the same emitted line. LineNumberPosition nextPos; int prevPartLen = 0; char[] indent = {}; do { - nextPos = (posIndex < _positions.size()-1) ? _positions.get( posIndex+1) : null; + nextPos = (posIndex < _positions.size() - 1) ? _positions.get(posIndex + 1) : null; if ( nextPos != null && nextPos.getEmittedLine() == pos.getEmittedLine() - && nextPos.getOriginalLine() > pos.getOriginalLine()) { + && nextPos.getOriginalLine() > pos.getOriginalLine()) { // Two different source line numbers on the same emitted line! posIndex++; lineOffset++; - String firstPart = line.substring( 0, nextPos.getEmittedColumn() - prevPartLen - 1); - brokenLines.add( new String(indent) + firstPart); + String firstPart = line.substring(0, nextPos.getEmittedColumn() - prevPartLen - 1); + brokenLines.add(new String(indent) + firstPart); prevPartLen += firstPart.length(); indent = new char[prevPartLen]; - Arrays.fill( indent, ' '); - line = line.substring( firstPart.length(), line.length()); - + Arrays.fill(indent, ' '); + line = line.substring(firstPart.length(), line.length()); + // Alter the position while adding it. o_LineBrokenPositions.add( new LineNumberPosition( nextPos.getOriginalLine(), nextPos.getEmittedLine()+lineOffset, nextPos.getEmittedColumn())); } else { nextPos = null; } - } while ( nextPos != null); - + } while (nextPos != null); + // Nothing special here-- just emit the line. - brokenLines.add( new String(indent) + line); + brokenLines.add(new String(indent) + line); } - + // Copy out the remainder of the file. String line; - while ( (line = r.readLine()) != null) { - brokenLines.add( line); + while ((line = r.readLine()) != null) { + brokenLines.add(line); } } return brokenLines; } - - private void emitFormatted( List brokenLines, List lineBrokenPositions) throws IOException { - File tempFile = new File( _file.getAbsolutePath() + ".fixed"); + + private void emitFormatted(List brokenLines, List lineBrokenPositions, Writer writer) { int globalOffset = 0; int numLinesRead = 0; Iterator lines = brokenLines.iterator(); - - int maxLineNo = LineNumberPosition.computeMaxLineNumber( lineBrokenPositions); - try( LineNumberPrintWriter w = new LineNumberPrintWriter( - maxLineNo, new BufferedWriter( new FileWriter( tempFile)))) { - + + int maxLineNo = LineNumberPosition.computeMaxLineNumber(lineBrokenPositions); + try (LineNumberPrintWriter w = new LineNumberPrintWriter(maxLineNo, writer)) { + // Suppress all line numbers if we weren't asked to show them. - if ( ! _options.contains( LineNumberOption.LEADING_COMMENTS)) { + if (!_options.contains(LineNumberOption.LEADING_COMMENTS)) { w.suppressLineNumbers(); } - + // Suppress stretching if we weren't asked to do it. - boolean doStretching = (_options.contains( LineNumberOption.STRETCHED)); - - for ( LineNumberPosition pos : lineBrokenPositions) { + boolean doStretching = (_options.contains(LineNumberOption.STRETCHED)); + + for (LineNumberPosition pos : lineBrokenPositions) { int nextTarget = pos.getOriginalLine(); int nextActual = pos.getEmittedLine(); int requiredAdjustment = (nextTarget - nextActual - globalOffset); - + if (doStretching && requiredAdjustment < 0) { // We currently need to remove newlines to squeeze things together. - // prefer to remove empty lines, + // prefer to remove empty lines, // 1. read all lines before nextActual and remove empty lines as needed List stripped = new ArrayList<>(); - while( numLinesRead < nextActual - 1) { + while (numLinesRead < nextActual - 1) { String line = lines.next(); numLinesRead++; if ((requiredAdjustment < 0) && line.trim().isEmpty()) { @@ -169,59 +172,55 @@ maxLineNo, new BufferedWriter( new FileWriter( tempFile)))) { ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER; for (String line : stripped) { if (requiredAdjustment < 0) { - w.print( lineNoToPrint, line); - w.print( " "); + w.print(lineNoToPrint, line); + w.print(" "); requiredAdjustment++; globalOffset--; } else { - w.println( lineNoToPrint, line); + w.println(lineNoToPrint, line); } } // 3. read and print next actual String line = lines.next(); numLinesRead++; if (requiredAdjustment < 0) { - w.print( nextTarget, line); - w.print( " "); + w.print(nextTarget, line); + w.print(" "); globalOffset--; } else { - w.println( nextTarget, line); + w.println(nextTarget, line); } } else { - while( numLinesRead < nextActual) { + while (numLinesRead < nextActual) { String line = lines.next(); numLinesRead++; boolean isLast = (numLinesRead >= nextActual); int lineNoToPrint = isLast ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER; - - if ( requiredAdjustment > 0 && doStretching) { + + if (requiredAdjustment > 0 && doStretching) { // We currently need to inject newlines to space things out. do { - w.println( ""); + w.println(""); requiredAdjustment--; globalOffset++; - } while ( isLast && requiredAdjustment > 0); - w.println( lineNoToPrint, line); + } while (isLast && requiredAdjustment > 0); + w.println(lineNoToPrint, line); } else { // No tweaks needed-- we are on the ball. - w.println( lineNoToPrint, line); + w.println(lineNoToPrint, line); } } } } - + // Finish out the file. String line; - while ( lines.hasNext()) { + while (lines.hasNext()) { line = lines.next(); - w.println( line); + w.println(line); } } - - // Delete the original file and rename the formatted temp file over the original. - _file.delete(); - tempFile.renameTo( _file); } } diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java new file mode 100644 index 00000000..770d1bce --- /dev/null +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java @@ -0,0 +1,34 @@ +package com.strobel.decompiler; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; + +import java.util.HashSet; +import java.util.Set; + +public final class NoRetryMetadataSystem extends MetadataSystem { + private final Set _failedTypes = new HashSet<>(); + + NoRetryMetadataSystem() { + } + + NoRetryMetadataSystem(final ITypeLoader typeLoader) { + super(typeLoader); + } + + @Override + protected TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { + if (_failedTypes.contains(descriptor)) { + return null; + } + + final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); + + if (result == null) { + _failedTypes.add(descriptor); + } + + return result; + } +} \ No newline at end of file From 4d55f8f5ba691a6fefae664a2664ab19fedc6eb4 Mon Sep 17 00:00:00 2001 From: nbauma109 Date: Wed, 13 Apr 2022 21:03:13 +0200 Subject: [PATCH 4/5] return the TypeDecompilationResults in Decompiler.decompile(...) --- .../java/com/strobel/decompiler/Decompiler.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/Decompiler.java b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/Decompiler.java index 7b348f4d..8d801830 100644 --- a/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/Decompiler.java +++ b/Procyon.CompilerTools/src/main/java/com/strobel/decompiler/Decompiler.java @@ -19,14 +19,21 @@ import com.strobel.assembler.InputTypeLoader; import com.strobel.assembler.metadata.*; import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.languages.TypeDecompilationResults; import com.strobel.decompiler.languages.java.JavaFormattingOptions; +import java.util.Collections; + public final class Decompiler { - public static void decompile(final String internalName, final ITextOutput output) { - decompile(internalName, output, new DecompilerSettings()); + + private Decompiler() { + } + + public static TypeDecompilationResults decompile(final String internalName, final ITextOutput output) { + return decompile(internalName, output, new DecompilerSettings()); } - public static void decompile(final String internalName, final ITextOutput output, final DecompilerSettings settings) { + public static TypeDecompilationResults decompile(final String internalName, final ITextOutput output, final DecompilerSettings settings) { VerifyArgument.notNull(internalName, "internalName"); VerifyArgument.notNull(settings, "settings"); @@ -53,7 +60,7 @@ public static void decompile(final String internalName, final ITextOutput output if (type == null || (resolvedType = type.resolve()) == null) { output.writeLine("!!! ERROR: Failed to load class %s.", internalName); - return; + return new TypeDecompilationResults(null); } DeobfuscationUtilities.processType(resolvedType); @@ -67,6 +74,6 @@ public static void decompile(final String internalName, final ITextOutput output settings.setJavaFormattingOptions(JavaFormattingOptions.createDefault()); } - settings.getLanguage().decompileType(resolvedType, output, options); + return settings.getLanguage().decompileType(resolvedType, output, options); } } From b75f0da90eab1d6248777a4ed0f2c3d97693f86d Mon Sep 17 00:00:00 2001 From: nbauma109 Date: Sun, 5 Jun 2022 11:54:04 +0200 Subject: [PATCH 5/5] Created InMemoryLineNumberFormatter to avoid temp file creation --- .../AbstractLineNumberFormatter.java | 225 ++++++++++++++++++ .../strobel/decompiler/DecompilerDriver.java | 17 +- .../InMemoryLineNumberFormatter.java | 58 +++++ .../decompiler/LineNumberFormatter.java | 217 ++--------------- .../strobel/decompiler/LineNumberOption.java | 23 ++ .../decompiler/LineNumberPrintWriter.java | 16 ++ .../decompiler/NoRetryMetadataSystem.java | 16 ++ 7 files changed, 379 insertions(+), 193 deletions(-) create mode 100644 Procyon.Decompiler/src/main/java/com/strobel/decompiler/AbstractLineNumberFormatter.java create mode 100644 Procyon.Decompiler/src/main/java/com/strobel/decompiler/InMemoryLineNumberFormatter.java create mode 100644 Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberOption.java diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/AbstractLineNumberFormatter.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/AbstractLineNumberFormatter.java new file mode 100644 index 00000000..bd3121b4 --- /dev/null +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/AbstractLineNumberFormatter.java @@ -0,0 +1,225 @@ +/* + * AbstractLineNumberFormatter.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; + +import com.strobel.decompiler.languages.LineNumberPosition; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; + +public abstract class AbstractLineNumberFormatter { + + private final List _positions; + private final EnumSet _options; + + /** + * Constructs an instance. + * + * @param lineNumberPositions a recipe for how to fix the line numbers in 'file'. + * @param options controls how 'this' represents line numbers in the resulting file + */ + protected AbstractLineNumberFormatter( + List lineNumberPositions, + EnumSet options) { + _positions = lineNumberPositions; + _options = (options == null ? EnumSet.noneOf(LineNumberOption.class) : options); + } + + /** + * Rewrites the file passed to 'this' constructor so that the actual line + * numbers match the recipe passed to 'this' constructor. + * + * @return The formatted source code + */ + public String reformatFile() throws IOException { + List lineBrokenPositions = new ArrayList<>(); + List brokenLines = breakLines(lineBrokenPositions); + try (StringWriter sw = new StringWriter()) { + emitFormatted(brokenLines, lineBrokenPositions, sw); + return sw.toString(); + } + } + + /** + * Processes {@link #_file}, breaking apart any lines on which multiple line-number markers + * appear in different columns. + * + * @return the list of broken lines + */ + private List breakLines(List o_LineBrokenPositions) throws IOException { + int numLinesRead = 0; + int lineOffset = 0; + List brokenLines = new ArrayList<>(); + + try (BufferedReader r = createReader()) { + for (int posIndex = 0; posIndex < _positions.size(); posIndex++) { + LineNumberPosition pos = _positions.get(posIndex); + o_LineBrokenPositions.add( new LineNumberPosition( + pos.getOriginalLine(), pos.getEmittedLine()+lineOffset, pos.getEmittedColumn())); + + // Copy the input file up to but not including the emitted line # in "pos". + while (numLinesRead < pos.getEmittedLine() - 1) { + brokenLines.add(r.readLine()); + numLinesRead++; + } + + // Read the line that contains the next line number annotations, but don't write it yet. + String line = r.readLine(); + numLinesRead++; + + // See if there are two original line annotations on the same emitted line. + LineNumberPosition nextPos; + int prevPartLen = 0; + char[] indent = {}; + do { + nextPos = (posIndex < _positions.size() - 1) ? _positions.get(posIndex + 1) : null; + if ( nextPos != null + && nextPos.getEmittedLine() == pos.getEmittedLine() + && nextPos.getOriginalLine() > pos.getOriginalLine()) { + // Two different source line numbers on the same emitted line! + posIndex++; + lineOffset++; + String firstPart = line.substring(0, nextPos.getEmittedColumn() - prevPartLen - 1); + brokenLines.add(new String(indent) + firstPart); + prevPartLen += firstPart.length(); + indent = new char[prevPartLen]; + Arrays.fill(indent, ' '); + line = line.substring(firstPart.length(), line.length()); + + // Alter the position while adding it. + o_LineBrokenPositions.add( new LineNumberPosition( + nextPos.getOriginalLine(), nextPos.getEmittedLine()+lineOffset, nextPos.getEmittedColumn())); + } else { + nextPos = null; + } + } while (nextPos != null); + + // Nothing special here-- just emit the line. + brokenLines.add(new String(indent) + line); + } + + // Copy out the remainder of the file. + String line; + while ((line = r.readLine()) != null) { + brokenLines.add(line); + } + } + return brokenLines; + } + + protected abstract BufferedReader createReader() throws IOException; + + private void emitFormatted(List brokenLines, List lineBrokenPositions, Writer writer) { + int globalOffset = 0; + int numLinesRead = 0; + Iterator lines = brokenLines.iterator(); + + int maxLineNo = LineNumberPosition.computeMaxLineNumber(lineBrokenPositions); + try (LineNumberPrintWriter w = new LineNumberPrintWriter(maxLineNo, writer)) { + + // Suppress all line numbers if we weren't asked to show them. + if (!_options.contains(LineNumberOption.LEADING_COMMENTS)) { + w.suppressLineNumbers(); + } + + // Suppress stretching if we weren't asked to do it. + boolean doStretching = (_options.contains(LineNumberOption.STRETCHED)); + + for (LineNumberPosition pos : lineBrokenPositions) { + int nextTarget = pos.getOriginalLine(); + int nextActual = pos.getEmittedLine(); + int requiredAdjustment = (nextTarget - nextActual - globalOffset); + + if (doStretching && requiredAdjustment < 0) { + // We currently need to remove newlines to squeeze things together. + // prefer to remove empty lines, + // 1. read all lines before nextActual and remove empty lines as needed + List stripped = new ArrayList<>(); + while (numLinesRead < nextActual - 1) { + String line = lines.next(); + numLinesRead++; + if ((requiredAdjustment < 0) && line.trim().isEmpty()) { + requiredAdjustment++; + globalOffset--; + } else { + stripped.add(line); + } + } + // 2. print non empty lines while stripping further as needed + int lineNoToPrint = (stripped.size() + requiredAdjustment <= 0) + ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER; + for (String line : stripped) { + if (requiredAdjustment < 0) { + w.print(lineNoToPrint, line); + w.print(" "); + requiredAdjustment++; + globalOffset--; + } else { + w.println(lineNoToPrint, line); + } + } + // 3. read and print next actual + String line = lines.next(); + numLinesRead++; + if (requiredAdjustment < 0) { + w.print(nextTarget, line); + w.print(" "); + globalOffset--; + } else { + w.println(nextTarget, line); + } + + } else { + while (numLinesRead < nextActual) { + String line = lines.next(); + numLinesRead++; + boolean isLast = (numLinesRead >= nextActual); + int lineNoToPrint = isLast ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER; + + if (requiredAdjustment > 0 && doStretching) { + // We currently need to inject newlines to space things out. + do { + w.println(""); + requiredAdjustment--; + globalOffset++; + } while (isLast && requiredAdjustment > 0); + w.println(lineNoToPrint, line); + } else { + // No tweaks needed-- we are on the ball. + w.println(lineNoToPrint, line); + } + } + } + } + + // Finish out the file. + String line; + while (lines.hasNext()) { + line = lines.next(); + w.println(line); + } + } + } + +} 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 12c7d0aa..84d1b55f 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/DecompilerDriver.java @@ -1,3 +1,19 @@ +/* + * DecompilerDriver.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; import com.beust.jcommander.JCommander; @@ -15,7 +31,6 @@ import com.strobel.assembler.metadata.TypeReference; import com.strobel.core.ExceptionUtilities; import com.strobel.core.StringUtilities; -import com.strobel.decompiler.LineNumberFormatter.LineNumberOption; import com.strobel.decompiler.languages.BytecodeLanguage; import com.strobel.decompiler.languages.BytecodeOutputOptions; import com.strobel.decompiler.languages.Languages; diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/InMemoryLineNumberFormatter.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/InMemoryLineNumberFormatter.java new file mode 100644 index 00000000..78d44a56 --- /dev/null +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/InMemoryLineNumberFormatter.java @@ -0,0 +1,58 @@ +/* + * InMemoryLineNumberFormatter.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; + +import com.strobel.decompiler.languages.LineNumberPosition; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.EnumSet; +import java.util.List; + +/** + * An InMemoryLineNumberFormatter is used to rewrite an existing .java source, introducing + * line number information. It can handle either, or both, of the following jobs: + * + *
    + *
  • Introduce line numbers as leading comments. + *
  • Stretch the source so that the line number comments match the physical lines. + *
+ */ +public class InMemoryLineNumberFormatter extends AbstractLineNumberFormatter { + + private final String _source; + + /** + * Constructs an instance. + * + * @param source the source whose line numbers should be fixed + * @param lineNumberPositions a recipe for how to fix the line numbers in source. + * @param options controls how 'this' represents line numbers in the resulting source + */ + public InMemoryLineNumberFormatter(String source, + List lineNumberPositions, + EnumSet options) { + super(lineNumberPositions, options); + _source = source; + } + + @Override + protected BufferedReader createReader() throws IOException { + return new BufferedReader(new StringReader(_source)); + } +} diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java index 5e12ba05..3b16f7e2 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberFormatter.java @@ -1,19 +1,30 @@ +/* + * LineNumberFormatter.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; +import com.strobel.decompiler.languages.LineNumberPosition; + import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Arrays; +import java.nio.file.Files; import java.util.EnumSet; -import java.util.Iterator; import java.util.List; -import com.strobel.decompiler.languages.LineNumberPosition; - /** * A LineNumberFormatter is used to rewrite an existing .java file, introducing * line number information. It can handle either, or both, of the following jobs: @@ -23,16 +34,9 @@ *
  • Stretch the file so that the line number comments match the physical lines. * */ -public class LineNumberFormatter { - private final List _positions; - private final File _file; - private final EnumSet _options; +public class LineNumberFormatter extends AbstractLineNumberFormatter { - public enum LineNumberOption - { - LEADING_COMMENTS, - STRETCHED, - } + private final File _file; /** * Constructs an instance. @@ -44,183 +48,12 @@ public enum LineNumberOption public LineNumberFormatter(File file, List lineNumberPositions, EnumSet options) { + super(lineNumberPositions, options); _file = file; - _positions = lineNumberPositions; - _options = (options == null ? EnumSet.noneOf(LineNumberOption.class) : options); } - /** - * Rewrites the file passed to 'this' constructor so that the actual line - * numbers match the recipe passed to 'this' constructor. - * - * @return The formatted source code - */ - public String reformatFile() throws IOException { - List lineBrokenPositions = new ArrayList<>(); - List brokenLines = breakLines(lineBrokenPositions); - try (StringWriter sw = new StringWriter()) { - emitFormatted(brokenLines, lineBrokenPositions, sw); - return sw.toString(); - } + @Override + protected BufferedReader createReader() throws IOException { + return Files.newBufferedReader(_file.toPath()); } - - /** - * Processes {@link #_file}, breaking apart any lines on which multiple line-number markers - * appear in different columns. - * - * @return the list of broken lines - */ - private List breakLines(List o_LineBrokenPositions) throws IOException { - int numLinesRead = 0; - int lineOffset = 0; - List brokenLines = new ArrayList<>(); - - try (BufferedReader r = new BufferedReader(new FileReader(_file))) { - for (int posIndex = 0; posIndex < _positions.size(); posIndex++) { - LineNumberPosition pos = _positions.get(posIndex); - o_LineBrokenPositions.add( new LineNumberPosition( - pos.getOriginalLine(), pos.getEmittedLine()+lineOffset, pos.getEmittedColumn())); - - // Copy the input file up to but not including the emitted line # in "pos". - while (numLinesRead < pos.getEmittedLine() - 1) { - brokenLines.add(r.readLine()); - numLinesRead++; - } - - // Read the line that contains the next line number annotations, but don't write it yet. - String line = r.readLine(); - numLinesRead++; - - // See if there are two original line annotations on the same emitted line. - LineNumberPosition nextPos; - int prevPartLen = 0; - char[] indent = {}; - do { - nextPos = (posIndex < _positions.size() - 1) ? _positions.get(posIndex + 1) : null; - if ( nextPos != null - && nextPos.getEmittedLine() == pos.getEmittedLine() - && nextPos.getOriginalLine() > pos.getOriginalLine()) { - // Two different source line numbers on the same emitted line! - posIndex++; - lineOffset++; - String firstPart = line.substring(0, nextPos.getEmittedColumn() - prevPartLen - 1); - brokenLines.add(new String(indent) + firstPart); - prevPartLen += firstPart.length(); - indent = new char[prevPartLen]; - Arrays.fill(indent, ' '); - line = line.substring(firstPart.length(), line.length()); - - // Alter the position while adding it. - o_LineBrokenPositions.add( new LineNumberPosition( - nextPos.getOriginalLine(), nextPos.getEmittedLine()+lineOffset, nextPos.getEmittedColumn())); - } else { - nextPos = null; - } - } while (nextPos != null); - - // Nothing special here-- just emit the line. - brokenLines.add(new String(indent) + line); - } - - // Copy out the remainder of the file. - String line; - while ((line = r.readLine()) != null) { - brokenLines.add(line); - } - } - return brokenLines; - } - - private void emitFormatted(List brokenLines, List lineBrokenPositions, Writer writer) { - int globalOffset = 0; - int numLinesRead = 0; - Iterator lines = brokenLines.iterator(); - - int maxLineNo = LineNumberPosition.computeMaxLineNumber(lineBrokenPositions); - try (LineNumberPrintWriter w = new LineNumberPrintWriter(maxLineNo, writer)) { - - // Suppress all line numbers if we weren't asked to show them. - if (!_options.contains(LineNumberOption.LEADING_COMMENTS)) { - w.suppressLineNumbers(); - } - - // Suppress stretching if we weren't asked to do it. - boolean doStretching = (_options.contains(LineNumberOption.STRETCHED)); - - for (LineNumberPosition pos : lineBrokenPositions) { - int nextTarget = pos.getOriginalLine(); - int nextActual = pos.getEmittedLine(); - int requiredAdjustment = (nextTarget - nextActual - globalOffset); - - if (doStretching && requiredAdjustment < 0) { - // We currently need to remove newlines to squeeze things together. - // prefer to remove empty lines, - // 1. read all lines before nextActual and remove empty lines as needed - List stripped = new ArrayList<>(); - while (numLinesRead < nextActual - 1) { - String line = lines.next(); - numLinesRead++; - if ((requiredAdjustment < 0) && line.trim().isEmpty()) { - requiredAdjustment++; - globalOffset--; - } else { - stripped.add(line); - } - } - // 2. print non empty lines while stripping further as needed - int lineNoToPrint = (stripped.size() + requiredAdjustment <= 0) - ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER; - for (String line : stripped) { - if (requiredAdjustment < 0) { - w.print(lineNoToPrint, line); - w.print(" "); - requiredAdjustment++; - globalOffset--; - } else { - w.println(lineNoToPrint, line); - } - } - // 3. read and print next actual - String line = lines.next(); - numLinesRead++; - if (requiredAdjustment < 0) { - w.print(nextTarget, line); - w.print(" "); - globalOffset--; - } else { - w.println(nextTarget, line); - } - - } else { - while (numLinesRead < nextActual) { - String line = lines.next(); - numLinesRead++; - boolean isLast = (numLinesRead >= nextActual); - int lineNoToPrint = isLast ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER; - - if (requiredAdjustment > 0 && doStretching) { - // We currently need to inject newlines to space things out. - do { - w.println(""); - requiredAdjustment--; - globalOffset++; - } while (isLast && requiredAdjustment > 0); - w.println(lineNoToPrint, line); - } else { - // No tweaks needed-- we are on the ball. - w.println(lineNoToPrint, line); - } - } - } - } - - // Finish out the file. - String line; - while (lines.hasNext()) { - line = lines.next(); - w.println(line); - } - } - } - } diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberOption.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberOption.java new file mode 100644 index 00000000..e8e6f016 --- /dev/null +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberOption.java @@ -0,0 +1,23 @@ +/* + * LineNumberOption.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; + +public enum LineNumberOption +{ + LEADING_COMMENTS, + STRETCHED, +} \ No newline at end of file diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberPrintWriter.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberPrintWriter.java index bdc2baa3..0b00fdc4 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberPrintWriter.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/LineNumberPrintWriter.java @@ -1,3 +1,19 @@ +/* + * LineNumberPrintWriter.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; import java.io.PrintWriter; diff --git a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java index 770d1bce..a17e1e7f 100644 --- a/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java +++ b/Procyon.Decompiler/src/main/java/com/strobel/decompiler/NoRetryMetadataSystem.java @@ -1,3 +1,19 @@ +/* + * NoRetryMetadataSystem.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; import com.strobel.assembler.metadata.ITypeLoader;