diff --git a/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/src/org/jetbrains/java/decompiler/main/ClassWriter.java index dae656f98c..d28af70997 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassWriter.java +++ b/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -115,18 +115,7 @@ private static boolean invokeProcessors(TextBuffer buffer, ClassNode node) { DecompilerContext.getLogger().writeMessage("Class " + node.simpleName + " couldn't be written.", IFernflowerLogger.Severity.WARN, t); - buffer.append("// $VF: Couldn't be decompiled"); - buffer.appendLineSeparator(); - if (DecompilerContext.getOption(IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR)) { - List lines = new ArrayList<>(); - lines.addAll(ClassWriter.getErrorComment()); - collectErrorLines(t, lines); - for (String line : lines) { - buffer.append("//"); - if (!line.isEmpty()) buffer.append(' ').append(line); - buffer.appendLineSeparator(); - } - } + writeException(buffer, t); return false; } @@ -134,6 +123,21 @@ private static boolean invokeProcessors(TextBuffer buffer, ClassNode node) { return true; } + public static void writeException(TextBuffer buffer, Throwable t) { + buffer.append("// $VF: Couldn't be decompiled"); + buffer.appendLineSeparator(); + if (DecompilerContext.getOption(IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR)) { + List lines = new ArrayList<>(); + lines.addAll(ClassWriter.getErrorComment()); + collectErrorLines(t, lines); + for (String line : lines) { + buffer.append("//"); + if (!line.isEmpty()) buffer.append(' ').append(line); + buffer.appendLineSeparator(); + } + } + } + public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_object, int indent) { ClassWrapper wrapper = node.getWrapper(); if (wrapper == null) { diff --git a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java index 940f569a1a..e7fa05926d 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -398,6 +398,38 @@ else if (cl.superClass == null) { // neither interface nor super class defined return true; } + public void processClass(StructClass cl) throws IOException { + ClassNode root = mapRootClasses.get(cl.qualifiedName); + if (root.type != ClassNode.Type.ROOT) { + return; + } + + boolean packageInfo = cl.isSynthetic() && "package-info".equals(root.simpleName); + boolean moduleInfo = cl.hasModifier(CodeConstants.ACC_MODULE) && cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_MODULE); + + DecompilerContext.getLogger().startProcessingClass(cl.qualifiedName); + ImportCollector importCollector = new ImportCollector(root); + DecompilerContext.startClass(importCollector); + try { + if (!packageInfo && !moduleInfo) { + new LambdaProcessor().processClass(root); + + // add simple class names to implicit import + addClassNameToImport(root, importCollector); + + // build wrappers for all nested classes (that's where actual processing takes place) + initWrappers(root); + + // Java specific last minute processing + new NestedClassProcessor().processClass(root, root); + + new NestedMemberAccess().propagateMemberAccess(root); + } + } finally { + DecompilerContext.getLogger().endProcessingClass(); + } + } + public void writeClass(StructClass cl, TextBuffer buffer) throws IOException { ClassNode root = mapRootClasses.get(cl.qualifiedName); if (root.type != ClassNode.Type.ROOT) { @@ -409,76 +441,25 @@ public void writeClass(StructClass cl, TextBuffer buffer) throws IOException { DecompilerContext.getLogger().startReadingClass(cl.qualifiedName); try { - ImportCollector importCollector = new ImportCollector(root); - DecompilerContext.startClass(importCollector); - if (packageInfo) { ClassWriter.packageInfoToJava(cl, buffer); - importCollector.writeImports(buffer, false); + DecompilerContext.getImportCollector().writeImports(buffer, false); } else if (moduleInfo) { TextBuffer moduleBuffer = new TextBuffer(AVERAGE_CLASS_SIZE); ClassWriter.moduleInfoToJava(cl, moduleBuffer); - importCollector.writeImports(buffer, true); + DecompilerContext.getImportCollector().writeImports(buffer, true); buffer.append(moduleBuffer); } else { - try { - new LambdaProcessor().processClass(root); - } catch (Throwable t) { - DecompilerContext.getLogger().writeMessage("Class " + root.simpleName + " couldn't be written.", - IFernflowerLogger.Severity.WARN, - t); - buffer.append("// $VF: Couldn't be decompiled"); - buffer.appendLineSeparator(); - if (DecompilerContext.getOption(IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR)) { - List lines = new ArrayList<>(); - lines.addAll(ClassWriter.getErrorComment()); - ClassWriter.collectErrorLines(t, lines); - for (String line : lines) { - buffer.append("//"); - if (!line.isEmpty()) buffer.append(' ').append(line); - buffer.appendLineSeparator(); - } - } - return; - } - - // add simple class names to implicit import - addClassNameToImport(root, importCollector); - - // build wrappers for all nested classes (that's where actual processing takes place) - initWrappers(root); - - try { - new NestedClassProcessor().processClass(root, root); - - new NestedMemberAccess().propagateMemberAccess(root); - } catch (Throwable t) { - DecompilerContext.getLogger().writeMessage("Class " + root.simpleName + " couldn't be written.", - IFernflowerLogger.Severity.WARN, - t); - buffer.append("// $VF: Couldn't be decompiled"); - buffer.appendLineSeparator(); - if (DecompilerContext.getOption(IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR)) { - List lines = new ArrayList<>(); - lines.addAll(ClassWriter.getErrorComment()); - ClassWriter.collectErrorLines(t, lines); - for (String line : lines) { - buffer.append("//"); - if (!line.isEmpty()) buffer.append(' ').append(line); - buffer.appendLineSeparator(); - } - } - return; - } - TextBuffer classBuffer = new TextBuffer(AVERAGE_CLASS_SIZE); + new ClassWriter().classToJava(root, classBuffer, 0); classBuffer.reformat(); + classBuffer.getTracers().forEach((classAndMethod, tracer) -> { // get the class by name StructClass clazz = DecompilerContext.getStructContext().getClass(classAndMethod.a); @@ -500,7 +481,7 @@ else if (moduleInfo) { buffer.append("package ").append(packageName).append(';').appendLineSeparator().appendLineSeparator(); } - importCollector.writeImports(buffer, true); + DecompilerContext.getImportCollector().writeImports(buffer, true); int offsetLines = buffer.countLines(); diff --git a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java index b540763723..b63e827bca 100644 --- a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java +++ b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java @@ -64,7 +64,11 @@ public static DecompilerContext getCurrentContext() { } public static void setCurrentContext(DecompilerContext context) { - currentContext.set(context); + if (context == null) { + currentContext.remove(); + } else { + currentContext.set(context); + } } public static void setProperty(String key, Object value) { diff --git a/src/org/jetbrains/java/decompiler/main/Fernflower.java b/src/org/jetbrains/java/decompiler/main/Fernflower.java index 3a89417208..86ea353b7e 100644 --- a/src/org/jetbrains/java/decompiler/main/Fernflower.java +++ b/src/org/jetbrains/java/decompiler/main/Fernflower.java @@ -16,6 +16,7 @@ import org.jetbrains.java.decompiler.util.TextBuffer; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -168,15 +169,20 @@ else if (converter != null) { } } + @Override + public void processClass(final StructClass cl) throws IOException { + classProcessor.processClass(cl); // unhandled exceptions handled later on + } + @Override public String getClassContent(StructClass cl) { + TextBuffer buffer = new TextBuffer(ClassesProcessor.AVERAGE_CLASS_SIZE); try { - TextBuffer buffer = new TextBuffer(ClassesProcessor.AVERAGE_CLASS_SIZE); buffer.append(DecompilerContext.getProperty(IFernflowerPreferences.BANNER).toString()); classProcessor.writeClass(cl, buffer); String res = buffer.convertToStringAndAllowDataDiscard(); if (res == null) { - return "$ FF: Unable to decompile class " + cl.qualifiedName; + return "$ VF: Unable to decompile class " + cl.qualifiedName; } return res; @@ -190,7 +196,7 @@ public String getClassContent(StructClass cl) { lines.addAll(ClassWriter.getErrorComment()); ClassWriter.collectErrorLines(t, lines); lines.add("*/"); - return String.join("\n", lines); + return String.join(DecompilerContext.getNewLineSeparator(), lines); } else { return null; } diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java b/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java index 087631cd81..8930ee1d5f 100644 --- a/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java +++ b/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java @@ -32,6 +32,21 @@ public void writeMessage(String message, Severity severity, Throwable t) { } } + public void startProcessingClass(String className) { + if (accepts(Severity.INFO)) { + writeMessage("Preprocessing class " + className, Severity.INFO); + indent.get().incrementAndGet(); + } + } + + @Override + public void endProcessingClass() { + if (accepts(Severity.INFO)) { + indent.get().decrementAndGet(); + writeMessage("... done", Severity.INFO); + } + } + @Override public void startReadingClass(String className) { if (accepts(Severity.INFO)) { diff --git a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java index c9ece039b0..97a864be4b 100644 --- a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java +++ b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java @@ -31,6 +31,10 @@ public void writeMessage(String message, Throwable t) { writeMessage(message, Severity.ERROR, t); } + public void startProcessingClass(String className) { } + + public void endProcessingClass() { } + public void startReadingClass(String className) { } public void endReadingClass() { } diff --git a/src/org/jetbrains/java/decompiler/struct/ContextUnit.java b/src/org/jetbrains/java/decompiler/struct/ContextUnit.java index 4270c488fa..c863e202ba 100644 --- a/src/org/jetbrains/java/decompiler/struct/ContextUnit.java +++ b/src/org/jetbrains/java/decompiler/struct/ContextUnit.java @@ -1,11 +1,13 @@ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.struct; +import org.jetbrains.java.decompiler.main.ClassWriter; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.extern.IContextSource; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.util.TextBuffer; import java.io.IOException; import java.io.InputStream; @@ -130,41 +132,64 @@ public void save(final Function loader) throws IOException //Whooo threads! final List> futures = new LinkedList<>(); - final ExecutorService decompileExecutor = Executors.newFixedThreadPool(Integer.parseInt((String) DecompilerContext.getProperty(IFernflowerPreferences.THREADS))); + final int threads = Integer.parseInt((String) DecompilerContext.getProperty(IFernflowerPreferences.THREADS)); + final ExecutorService workerExec = Executors.newFixedThreadPool(threads > 0 ? threads : Runtime.getRuntime().availableProcessors()); final DecompilerContext rootContext = DecompilerContext.getCurrentContext(); - final ClassContext[] toDump = new ClassContext[classEntries.size()]; + final List toDump = new ArrayList<>(classEntries.size()); - // classes + // collect classes for (int i = 0; i < classEntries.size(); i++) { StructClass cl = loader.apply(classEntries.get(i)); String entryName = decompiledData.getClassEntryName(cl, classEntries.get(i)); if (entryName != null) { - final int finalI = i; - futures.add(decompileExecutor.submit(() -> { - setContext(rootContext); - String content = decompiledData.getClassContent(cl); - int[] mapping = null; - if (DecompilerContext.getOption(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING)) { - mapping = DecompilerContext.getBytecodeSourceMapper().getOriginalLinesMapping(); - } - toDump[finalI] = new ClassContext(cl.qualifiedName, entryName, content, mapping); - })); + toDump.add(new ClassContext(cl, entryName)); } } - decompileExecutor.shutdown(); + // pre-process + for (final ClassContext classCtx : toDump) { + futures.add(workerExec.submit(() -> { + setContext(rootContext); + classCtx.ctx = DecompilerContext.getCurrentContext(); + try { + decompiledData.processClass(classCtx.cl); + } catch (final Throwable thr) { + classCtx.onError(thr); + } finally { + DecompilerContext.setCurrentContext(null); + } + })); + } - for (Future future : futures) { - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); + waitForAll(futures); + futures.clear(); + + // emit + for (final ClassContext classCtx : toDump) { + if (classCtx.pendingError != null) { + TextBuffer buf = new TextBuffer(); + ClassWriter.writeException(buf, classCtx.pendingError); + classCtx.classContent = buf.convertToStringAndAllowDataDiscard(); + continue; } + + futures.add(workerExec.submit(() -> { + DecompilerContext.setCurrentContext(classCtx.ctx); + classCtx.classContent = decompiledData.getClassContent(classCtx.cl); + if (DecompilerContext.getOption(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING)) { + classCtx.mapping = DecompilerContext.getBytecodeSourceMapper().getOriginalLinesMapping(); + } + })); } + waitForAll(futures); + futures.clear(); + workerExec.shutdown(); + + // write to file for (final ClassContext cls : toDump) { - if (cls != null) { - sink.acceptClass(cls.qualifiedName, cls.entryName, cls.classContent, cls.mapping); + if (cls.classContent != null) { + sink.acceptClass(cls.cl.qualifiedName, cls.entryName, cls.classContent, cls.mapping); } } @@ -186,6 +211,16 @@ public void setContext(DecompilerContext rootContext) { } } + private static void waitForAll(final List> futures) { + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + } + public boolean isOwn() { return own; } @@ -202,16 +237,25 @@ void close() throws Exception { } static final class ClassContext { - private final String qualifiedName; + private final StructClass cl; + DecompilerContext ctx; private final String entryName; - private final String classContent; - private final int /* @Nullable */[] mapping; + String classContent; + int /* @Nullable */[] mapping; + private Throwable pendingError; - ClassContext(final String qualifiedName, final String entryName, final String classContent, final int[] mapping) { - this.qualifiedName = qualifiedName; + ClassContext(final StructClass cl, final String entryName) { + this.cl = cl; this.entryName = entryName; - this.classContent = classContent; - this.mapping = mapping; + } + + void onError(final Throwable thr) { + if (this.pendingError == null) { + this.pendingError = thr; + return; + } + + this.pendingError.addSuppressed(thr); } } } diff --git a/src/org/jetbrains/java/decompiler/struct/IDecompiledData.java b/src/org/jetbrains/java/decompiler/struct/IDecompiledData.java index b3b38dd0c7..929038f209 100644 --- a/src/org/jetbrains/java/decompiler/struct/IDecompiledData.java +++ b/src/org/jetbrains/java/decompiler/struct/IDecompiledData.java @@ -1,9 +1,13 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.struct; +import java.io.IOException; + public interface IDecompiledData { String getClassEntryName(StructClass cl, String entryname); + void processClass(StructClass cl) throws IOException; + String getClassContent(StructClass cl); } diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index 1ab89dd962..23f37fa527 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -633,6 +633,7 @@ private void registerDefault() { // TODO: when in different classes, these two decompile incorrectly to super(outer, s) register(JAVA_8, "TestInnerClassExtend"); + register(JAVA_8, "TestInnerClassReference", "ext/SomeOuterClass", "ext/SomeOuterClass$SomeInner"); register(JAVA_17, "TestInnerClassExtendJ17"); // TODO: include scala stdlibs to avoid "import scala.runtime.ScalaRunTime." diff --git a/testData/results/pkg/TestInnerClassExtend.dec b/testData/results/pkg/TestInnerClassExtend.dec index 1afc47c300..edb645d69c 100644 --- a/testData/results/pkg/TestInnerClassExtend.dec +++ b/testData/results/pkg/TestInnerClassExtend.dec @@ -6,10 +6,9 @@ public class TestInnerClassExtend { }// 7 } - public static class Inner2 extends TestInnerClassExtend.Inner { - public Inner2(TestInnerClassExtend outer, String s) { - outer.getClass();// 12 - super(s); + public class Inner2 extends TestInnerClassExtend.Inner { + public Inner2(String s) { + super(s);// 12 }// 13 } } @@ -22,21 +21,18 @@ class 'pkg/TestInnerClassExtend$Inner' { class 'pkg/TestInnerClassExtend$Inner2' { method ' (Lpkg/TestInnerClassExtend;Ljava/lang/String;)V' { - 1 10 - 3 10 - 4 10 - 5 10 - 7 11 - 8 11 - 9 11 - a 11 - b 12 + 7 10 + 8 10 + 9 10 + a 10 + b 11 } } Lines mapping: 7 <-> 6 12 <-> 11 -13 <-> 13 +13 <-> 12 Not mapped: 5 +11 diff --git a/testData/results/pkg/TestInnerClassReference.dec b/testData/results/pkg/TestInnerClassReference.dec new file mode 100644 index 0000000000..c58534c171 --- /dev/null +++ b/testData/results/pkg/TestInnerClassReference.dec @@ -0,0 +1,26 @@ +package pkg; + +import ext.SomeOuterClass; + +public class TestInnerClassReference extends SomeOuterClass { + public void callInner() { + SomeOuterClass.SomeInner inner = new SomeOuterClass.SomeInner();// 7 + inner.greet();// 9 + }// 10 +} + +class 'pkg/TestInnerClassReference' { + method 'callInner ()V' { + 8 6 + 9 7 + a 7 + b 7 + c 7 + d 8 + } +} + +Lines mapping: +7 <-> 7 +9 <-> 8 +10 <-> 9 diff --git a/testData/src/java8/ext/SomeOuterClass.java b/testData/src/java8/ext/SomeOuterClass.java new file mode 100644 index 0000000000..2c2235e9a3 --- /dev/null +++ b/testData/src/java8/ext/SomeOuterClass.java @@ -0,0 +1,14 @@ +package ext; + +public class SomeOuterClass { + + void hello() { + System.out.println("world"); + } + + public class SomeInner { + public void greet() { + SomeOuterClass.this.hello(); + } + } +} diff --git a/testData/src/java8/pkg/TestInnerClassExtend.java b/testData/src/java8/pkg/TestInnerClassExtend.java index c15265cda7..a77fefc2e2 100644 --- a/testData/src/java8/pkg/TestInnerClassExtend.java +++ b/testData/src/java8/pkg/TestInnerClassExtend.java @@ -7,9 +7,9 @@ public Inner(String s) { } } - public static class Inner2 extends Inner { - public Inner2(TestInnerClassExtend outer, String s) { - outer.super(s); + public class Inner2 extends Inner { + public Inner2(String s) { + super(s); } } } diff --git a/testData/src/java8/pkg/TestInnerClassReference.java b/testData/src/java8/pkg/TestInnerClassReference.java new file mode 100644 index 0000000000..0b901a0975 --- /dev/null +++ b/testData/src/java8/pkg/TestInnerClassReference.java @@ -0,0 +1,11 @@ +package pkg; + +import ext.SomeOuterClass; + +public class TestInnerClassReference extends SomeOuterClass { + public void callInner() { + final SomeInner inner = new SomeInner(); + + inner.greet(); + } +}