From fddee3ec2b0de9501b0d01bac4afeb86146d0c42 Mon Sep 17 00:00:00 2001 From: Duy Date: Sat, 16 Jun 2018 01:13:50 +0700 Subject: [PATCH] Lib java decompiler --- lib-decompiler/build.gradle | 33 + lib-decompiler/src/main/AndroidManifest.xml | 1 + .../java/com/duy/java8/util/DCollection.java | 20 + .../java/com/duy/java8/util/DComparator.java | 16 + .../main/java/com/duy/java8/util/DList.java | 25 + .../main/java/com/duy/java8/util/DMap.java | 44 + .../duy/java8/util/function/BiFunction.java | 50 + .../java8/util/function/BinaryOperator.java | 44 + .../com/duy/java8/util/function/Consumer.java | 47 + .../com/duy/java8/util/function/Function.java | 47 + .../duy/java8/util/function/Predicate.java | 47 + .../java8/util/function/ToIntFunction.java | 46 + .../java8/util/function/UnaryOperator.java | 43 + .../java/decompiler/code/CodeConstants.java | 337 ++++++ .../decompiler/code/ExceptionHandler.java | 23 + .../java/decompiler/code/ExceptionTable.java | 19 + .../code/FullInstructionSequence.java | 24 + .../java/decompiler/code/Instruction.java | 63 + .../decompiler/code/InstructionSequence.java | 145 +++ .../java/decompiler/code/JumpInstruction.java | 22 + .../code/SimpleInstructionSequence.java | 21 + .../decompiler/code/SwitchInstruction.java | 59 + .../java/decompiler/code/cfg/BasicBlock.java | 189 +++ .../decompiler/code/cfg/ControlFlowGraph.java | 819 +++++++++++++ .../code/cfg/ExceptionRangeCFG.java | 57 + .../code/interpreter/InstructionImpact.java | 509 ++++++++ .../java/decompiler/main/AssertProcessor.java | 339 ++++++ .../main/ClassReference14Processor.java | 257 ++++ .../java/decompiler/main/ClassWriter.java | 1074 +++++++++++++++++ .../decompiler/main/ClassesProcessor.java | 421 +++++++ .../decompiler/main/DecompilerContext.java | 122 ++ .../java/decompiler/main/EnumProcessor.java | 56 + .../java/decompiler/main/Fernflower.java | 121 ++ .../decompiler/main/InitializerProcessor.java | 252 ++++ .../collectors/BytecodeMappingTracer.java | 107 ++ .../main/collectors/BytecodeSourceMapper.java | 130 ++ .../main/collectors/CounterContainer.java | 22 + .../main/collectors/ImportCollector.java | 206 ++++ .../main/collectors/VarNamesCollector.java | 34 + .../main/decompiler/BaseDecompiler.java | 46 + .../main/decompiler/ConsoleDecompiler.java | 295 +++++ .../main/decompiler/PrintStreamLogger.java | 96 ++ .../main/extern/IBytecodeProvider.java | 8 + .../main/extern/IFernflowerLogger.java | 57 + .../main/extern/IFernflowerPreferences.java | 48 + .../extern/IFernflowerPreferencesStatic.java | 83 ++ .../main/extern/IIdentifierRenamer.java | 15 + .../decompiler/main/extern/IResultSaver.java | 22 + .../decompiler/main/rels/ClassWrapper.java | 227 ++++ .../decompiler/main/rels/LambdaProcessor.java | 132 ++ .../main/rels/MethodProcessorRunnable.java | 206 ++++ .../decompiler/main/rels/MethodWrapper.java | 45 + .../main/rels/NestedClassProcessor.java | 918 ++++++++++++++ .../main/rels/NestedMemberAccess.java | 471 ++++++++ .../modules/code/DeadCodeHelper.java | 419 +++++++ .../modules/decompiler/ClasspathHelper.java | 71 ++ .../modules/decompiler/ClearStructHelper.java | 26 + .../decompiler/ConcatenationHelper.java | 248 ++++ .../modules/decompiler/DecHelper.java | 197 +++ .../modules/decompiler/DomHelper.java | 704 +++++++++++ .../modules/decompiler/ExitHelper.java | 237 ++++ .../modules/decompiler/ExprProcessor.java | 927 ++++++++++++++ .../modules/decompiler/ExprentStack.java | 25 + .../modules/decompiler/FinallyProcessor.java | 966 +++++++++++++++ .../modules/decompiler/IdeaNotNullHelper.java | 313 +++++ .../modules/decompiler/IfHelper.java | 675 +++++++++++ .../decompiler/InlineSingleBlockHelper.java | 196 +++ .../modules/decompiler/LabelHelper.java | 500 ++++++++ .../modules/decompiler/LoopExtractHelper.java | 185 +++ .../modules/decompiler/MergeHelper.java | 384 ++++++ .../modules/decompiler/PPandMMHelper.java | 138 +++ .../decompiler/PrimitiveExprsList.java | 35 + .../decompiler/SecondaryFunctionsHelper.java | 408 +++++++ .../modules/decompiler/SequenceHelper.java | 307 +++++ .../decompiler/SimplifyExprentsHelper.java | 744 ++++++++++++ .../decompiler/StackVarsProcessor.java | 663 ++++++++++ .../modules/decompiler/StatEdge.java | 80 ++ .../decompiler/StrongConnectivityHelper.java | 117 ++ .../modules/decompiler/SwitchHelper.java | 85 ++ .../decompiler/decompose/DominatorEngine.java | 111 ++ .../DominatorTreeExceptionFilter.java | 163 +++ .../FastExtendedPostdominanceHelper.java | 326 +++++ .../decompose/GenericDominatorEngine.java | 130 ++ .../modules/decompiler/decompose/IGraph.java | 12 + .../decompiler/decompose/IGraphNode.java | 9 + .../deobfuscator/ExceptionDeobfuscator.java | 321 +++++ .../IrreducibleCFGDeobfuscator.java | 242 ++++ settings.gradle | 1 + 88 files changed, 18525 insertions(+) create mode 100644 lib-decompiler/build.gradle create mode 100644 lib-decompiler/src/main/AndroidManifest.xml create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/DCollection.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/DComparator.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/DList.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/DMap.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/BiFunction.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/BinaryOperator.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/Consumer.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/Function.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/Predicate.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/ToIntFunction.java create mode 100644 lib-decompiler/src/main/java/com/duy/java8/util/function/UnaryOperator.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/CodeConstants.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionHandler.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionTable.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/FullInstructionSequence.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/Instruction.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/InstructionSequence.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/JumpInstruction.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SwitchInstruction.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/AssertProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassReference14Processor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassWriter.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassesProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/DecompilerContext.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/EnumProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/Fernflower.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/InitializerProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeMappingTracer.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeSourceMapper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferencesStatic.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/extern/IResultSaver.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/code/DeadCodeHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/ClasspathHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/ClearStructHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/DecHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/DomHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/ExitHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/ExprentStack.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/FinallyProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/IdeaNotNullHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/IfHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/InlineSingleBlockHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/LabelHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/LoopExtractHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/MergeHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/PPandMMHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/PrimitiveExprsList.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/SecondaryFunctionsHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/SequenceHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/SimplifyExprentsHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/StackVarsProcessor.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/StatEdge.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/StrongConnectivityHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/SwitchHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/decompose/DominatorEngine.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/decompose/DominatorTreeExceptionFilter.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/decompose/FastExtendedPostdominanceHelper.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/decompose/GenericDominatorEngine.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/decompose/IGraph.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/decompose/IGraphNode.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/deobfuscator/ExceptionDeobfuscator.java create mode 100644 lib-decompiler/src/main/java/org/jetbrains/java/decompiler/modules/decompiler/deobfuscator/IrreducibleCFGDeobfuscator.java diff --git a/lib-decompiler/build.gradle b/lib-decompiler/build.gradle new file mode 100644 index 000000000..8d5f25c1f --- /dev/null +++ b/lib-decompiler/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + targetCompatibility 1.7 + sourceCompatibility 1.7 + } +} +dependencies { + testImplementation 'junit:junit:4.12' + testImplementation 'org.assertj:assertj-core:3.10.0' + api fileTree(dir: 'libs', include: ['*.jar']) +} + diff --git a/lib-decompiler/src/main/AndroidManifest.xml b/lib-decompiler/src/main/AndroidManifest.xml new file mode 100644 index 000000000..673361f3a --- /dev/null +++ b/lib-decompiler/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/DCollection.java b/lib-decompiler/src/main/java/com/duy/java8/util/DCollection.java new file mode 100644 index 000000000..ed53c32ce --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/DCollection.java @@ -0,0 +1,20 @@ +package com.duy.java8.util; + +import com.duy.java8.util.function.Predicate; + +import java.util.Iterator; + +public class DCollection { + public static boolean removeIf(java.util.Collection collection, + Predicate filter) { + boolean removed = false; + final Iterator each = collection.iterator(); + while (each.hasNext()) { + if (filter.test(each.next())) { + each.remove(); + removed = true; + } + } + return removed; + } +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/DComparator.java b/lib-decompiler/src/main/java/com/duy/java8/util/DComparator.java new file mode 100644 index 000000000..b8c8f4109 --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/DComparator.java @@ -0,0 +1,16 @@ +package com.duy.java8.util; + +import java.util.Comparator; + +public class DComparator { + public static Comparator thenComparing(final Comparator first, + final Comparator second) { + return new Comparator() { + @Override + public int compare(T c1, T c2) { + int res = first.compare(c1, c2); + return (res != 0) ? res : second.compare(c1, c2); + } + }; + } +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/DList.java b/lib-decompiler/src/main/java/com/duy/java8/util/DList.java new file mode 100644 index 000000000..0ef9d27dd --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/DList.java @@ -0,0 +1,25 @@ +package com.duy.java8.util; + +import com.duy.java8.util.function.Consumer; +import com.duy.java8.util.function.Predicate; + +import java.util.ArrayList; +import java.util.List; + +public class DList { + public static void forEach(List list, Consumer action) { + for (E e : list) { + action.accept(e); + } + } + + public static List filter(List input, Predicate predicate) { + List list = new ArrayList<>(); + for (E e : input) { + if (predicate.test(e)) { + list.add(e); + } + } + return list; + } +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/DMap.java b/lib-decompiler/src/main/java/com/duy/java8/util/DMap.java new file mode 100644 index 000000000..7d975ba61 --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/DMap.java @@ -0,0 +1,44 @@ +package com.duy.java8.util; + +import com.duy.java8.util.function.BiFunction; +import com.duy.java8.util.function.Function; + +import java.util.Map; + +public class DMap { + public static V putIfAbsent(Map map, K key, V value) { + V v = map.get(key); + if (v == null) { + v = map.put(key, value); + } + + return v; + } + + public static V computeIfAbsent(Map map, K key, + Function mappingFunction) { + V v; + if ((v = map.get(key)) == null) { + V newValue; + if ((newValue = mappingFunction.apply(key)) != null) { + map.put(key, newValue); + return newValue; + } + } + + return v; + } + + public static V merge(Map map, K key, V value, + BiFunction remappingFunction) { + V oldValue = map.get(key); + V newValue = (oldValue == null) ? value : + remappingFunction.apply(oldValue, value); + if (newValue == null) { + map.remove(key); + } else { + map.put(key, newValue); + } + return newValue; + } +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/BiFunction.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/BiFunction.java new file mode 100644 index 000000000..3bc593703 --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/BiFunction.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + +/** + * Represents a function that accepts two arguments and produces a result. + * This is the two-arity specialization of {@link com.duy.java8.util.function.Function}. + *

+ *

This is a functional interface + * whose functional method is {@link #apply(Object, Object)}. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param the type of the result of the function + * @see com.duy.java8.util.function.Function + */ +public interface BiFunction { + + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @return the function result + */ + R apply(T t, U u); + +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/BinaryOperator.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/BinaryOperator.java new file mode 100644 index 000000000..b63c5743d --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/BinaryOperator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + + +/** + * Represents an operation upon two operands of the same type, producing a result + * of the same type as the operands. This is a specialization of + * {@link com.duy.java8.util.function.BiFunction} for the case where the operands and the result are all of + * the same type. + *

+ *

This is a functional interface + * whose functional method is {@link #apply(Object, Object)}. + * + * @param the type of the operands and result of the operator + * @see com.duy.java8.util.function.BiFunction + * @see UnaryOperator + */ +@FunctionalInterface +public interface BinaryOperator extends BiFunction { + +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/Consumer.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/Consumer.java new file mode 100644 index 000000000..b5491166d --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/Consumer.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + +/** + * Represents an operation that accepts a single input argument and returns no + * result. Unlike most other functional interfaces, {@code Consumer} is expected + * to operate via side-effects. + *

+ *

This is a functional interface + * whose functional method is {@link #accept(Object)}. + * + * @param the type of the input to the operation + */ +public interface Consumer { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void accept(T t); + + +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/Function.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/Function.java new file mode 100644 index 000000000..b525a50f8 --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/Function.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + +/** + * Represents a function that accepts one argument and produces a result. + *

+ *

This is a functional interface + * whose functional method is {@link #apply(Object)}. + * + * @param the type of the input to the function + * @param the type of the result of the function + */ +public interface Function { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t); + + +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/Predicate.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/Predicate.java new file mode 100644 index 000000000..4f0e229fe --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/Predicate.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + +/** + * Represents a predicate (boolean-valued function) of one argument. + *

+ *

This is a functional interface + * whose functional method is {@link #test(Object)}. + * + * @param the type of the input to the predicate + */ +@FunctionalInterface +public interface Predicate { + + /** + * Evaluates this predicate on the given argument. + * + * @param t the input argument + * @return {@code true} if the input argument matches the predicate, + * otherwise {@code false} + */ + boolean test(T t); + +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/ToIntFunction.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/ToIntFunction.java new file mode 100644 index 000000000..f22885128 --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/ToIntFunction.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + +/** + * Represents a function that produces an int-valued result. This is the + * {@code int}-producing primitive specialization for {@link com.duy.java8.util.function.Function}. + *

+ *

This is a functional interface + * whose functional method is {@link #applyAsInt(Object)}. + * + * @param the type of the input to the function + * @see com.duy.java8.util.function.Function + */ +public interface ToIntFunction { + + /** + * Applies this function to the given argument. + * + * @param value the function argument + * @return the function result + */ + int applyAsInt(T value); +} diff --git a/lib-decompiler/src/main/java/com/duy/java8/util/function/UnaryOperator.java b/lib-decompiler/src/main/java/com/duy/java8/util/function/UnaryOperator.java new file mode 100644 index 000000000..29101c92a --- /dev/null +++ b/lib-decompiler/src/main/java/com/duy/java8/util/function/UnaryOperator.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.duy.java8.util.function; + +/** + * Represents an operation on a single operand that produces a result of the + * same type as its operand. This is a specialization of {@code Function} for + * the case where the operand and result are of the same type. + * + *

This is a functional interface + * whose functional method is {@link #apply(Object)}. + * + * @param the type of the operand and result of the operator + * + * @see Function + * @since 1.8 + */ +@FunctionalInterface +public interface UnaryOperator extends Function { + +} diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/CodeConstants.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/CodeConstants.java new file mode 100644 index 000000000..05be907c9 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/CodeConstants.java @@ -0,0 +1,337 @@ +/* + * Copyright 2000-2018 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.code; + +@SuppressWarnings({"unused", "SpellCheckingInspection"}) +public interface CodeConstants { + // ---------------------------------------------------------------------- + // BYTECODE VERSIONS + // ---------------------------------------------------------------------- + + int BYTECODE_JAVA_LE_4 = 1; + int BYTECODE_JAVA_5 = 2; + int BYTECODE_JAVA_6 = 3; + int BYTECODE_JAVA_7 = 4; + int BYTECODE_JAVA_8 = 5; + int BYTECODE_JAVA_9 = 6; + + // ---------------------------------------------------------------------- + // VARIABLE TYPES + // ---------------------------------------------------------------------- + + int TYPE_BYTE = 0; + int TYPE_CHAR = 1; + int TYPE_DOUBLE = 2; + int TYPE_FLOAT = 3; + int TYPE_INT = 4; + int TYPE_LONG = 5; + int TYPE_SHORT = 6; + int TYPE_BOOLEAN = 7; + int TYPE_OBJECT = 8; + int TYPE_ADDRESS = 9; + int TYPE_VOID = 10; + int TYPE_ANY = 11; + int TYPE_GROUP2EMPTY = 12; + int TYPE_NULL = 13; + int TYPE_NOTINITIALIZED = 14; + int TYPE_BYTECHAR = 15; + int TYPE_SHORTCHAR = 16; + int TYPE_UNKNOWN = 17; + int TYPE_GENVAR = 18; + + // ---------------------------------------------------------------------- + // VARIABLE TYPE FAMILIES + // ---------------------------------------------------------------------- + + int TYPE_FAMILY_UNKNOWN = 0; + int TYPE_FAMILY_BOOLEAN = 1; + int TYPE_FAMILY_INTEGER = 2; + int TYPE_FAMILY_FLOAT = 3; + int TYPE_FAMILY_LONG = 4; + int TYPE_FAMILY_DOUBLE = 5; + int TYPE_FAMILY_OBJECT = 6; + + // ---------------------------------------------------------------------- + // ACCESS FLAGS + // ---------------------------------------------------------------------- + + int ACC_PUBLIC = 0x0001; + int ACC_PRIVATE = 0x0002; + int ACC_PROTECTED = 0x0004; + int ACC_STATIC = 0x0008; + int ACC_FINAL = 0x0010; + int ACC_SYNCHRONIZED = 0x0020; + int ACC_NATIVE = 0x0100; + int ACC_ABSTRACT = 0x0400; + int ACC_STRICT = 0x0800; + int ACC_VOLATILE = 0x0040; + int ACC_BRIDGE = 0x0040; + int ACC_TRANSIENT = 0x0080; + int ACC_VARARGS = 0x0080; + int ACC_SYNTHETIC = 0x1000; + int ACC_ANNOTATION = 0x2000; + int ACC_ENUM = 0x4000; + int ACC_MANDATED = 0x8000; + + // ---------------------------------------------------------------------- + // CLASS FLAGS + // ---------------------------------------------------------------------- + + int ACC_SUPER = 0x0020; + int ACC_INTERFACE = 0x0200; + + // ---------------------------------------------------------------------- + // INSTRUCTION GROUPS + // ---------------------------------------------------------------------- + + int GROUP_GENERAL = 1; + int GROUP_JUMP = 2; + int GROUP_SWITCH = 3; + int GROUP_INVOCATION = 4; + int GROUP_FIELDACCESS = 5; + int GROUP_RETURN = 6; + + // ---------------------------------------------------------------------- + // POOL CONSTANTS + // ---------------------------------------------------------------------- + + int CONSTANT_Utf8 = 1; + int CONSTANT_Integer = 3; + int CONSTANT_Float = 4; + int CONSTANT_Long = 5; + int CONSTANT_Double = 6; + int CONSTANT_Class = 7; + int CONSTANT_String = 8; + int CONSTANT_Fieldref = 9; + int CONSTANT_Methodref = 10; + int CONSTANT_InterfaceMethodref = 11; + int CONSTANT_NameAndType = 12; + int CONSTANT_MethodHandle = 15; + int CONSTANT_MethodType = 16; + int CONSTANT_InvokeDynamic = 18; + + // ---------------------------------------------------------------------- + // MethodHandle reference_kind values + // ---------------------------------------------------------------------- + + int CONSTANT_MethodHandle_REF_getField = 1; + int CONSTANT_MethodHandle_REF_getStatic = 2; + int CONSTANT_MethodHandle_REF_putField = 3; + int CONSTANT_MethodHandle_REF_putStatic = 4; + int CONSTANT_MethodHandle_REF_invokeVirtual = 5; + int CONSTANT_MethodHandle_REF_invokeStatic = 6; + int CONSTANT_MethodHandle_REF_invokeSpecial = 7; + int CONSTANT_MethodHandle_REF_newInvokeSpecial = 8; + int CONSTANT_MethodHandle_REF_invokeInterface = 9; + + // ---------------------------------------------------------------------- + // VM OPCODES + // ---------------------------------------------------------------------- + + int opc_nop = 0; + int opc_aconst_null = 1; + int opc_iconst_m1 = 2; + int opc_iconst_0 = 3; + int opc_iconst_1 = 4; + int opc_iconst_2 = 5; + int opc_iconst_3 = 6; + int opc_iconst_4 = 7; + int opc_iconst_5 = 8; + int opc_lconst_0 = 9; + int opc_lconst_1 = 10; + int opc_fconst_0 = 11; + int opc_fconst_1 = 12; + int opc_fconst_2 = 13; + int opc_dconst_0 = 14; + int opc_dconst_1 = 15; + int opc_bipush = 16; + int opc_sipush = 17; + int opc_ldc = 18; + int opc_ldc_w = 19; + int opc_ldc2_w = 20; + int opc_iload = 21; + int opc_lload = 22; + int opc_fload = 23; + int opc_dload = 24; + int opc_aload = 25; + int opc_iload_0 = 26; + int opc_iload_1 = 27; + int opc_iload_2 = 28; + int opc_iload_3 = 29; + int opc_lload_0 = 30; + int opc_lload_1 = 31; + int opc_lload_2 = 32; + int opc_lload_3 = 33; + int opc_fload_0 = 34; + int opc_fload_1 = 35; + int opc_fload_2 = 36; + int opc_fload_3 = 37; + int opc_dload_0 = 38; + int opc_dload_1 = 39; + int opc_dload_2 = 40; + int opc_dload_3 = 41; + int opc_aload_0 = 42; + int opc_aload_1 = 43; + int opc_aload_2 = 44; + int opc_aload_3 = 45; + int opc_iaload = 46; + int opc_laload = 47; + int opc_faload = 48; + int opc_daload = 49; + int opc_aaload = 50; + int opc_baload = 51; + int opc_caload = 52; + int opc_saload = 53; + int opc_istore = 54; + int opc_lstore = 55; + int opc_fstore = 56; + int opc_dstore = 57; + int opc_astore = 58; + int opc_istore_0 = 59; + int opc_istore_1 = 60; + int opc_istore_2 = 61; + int opc_istore_3 = 62; + int opc_lstore_0 = 63; + int opc_lstore_1 = 64; + int opc_lstore_2 = 65; + int opc_lstore_3 = 66; + int opc_fstore_0 = 67; + int opc_fstore_1 = 68; + int opc_fstore_2 = 69; + int opc_fstore_3 = 70; + int opc_dstore_0 = 71; + int opc_dstore_1 = 72; + int opc_dstore_2 = 73; + int opc_dstore_3 = 74; + int opc_astore_0 = 75; + int opc_astore_1 = 76; + int opc_astore_2 = 77; + int opc_astore_3 = 78; + int opc_iastore = 79; + int opc_lastore = 80; + int opc_fastore = 81; + int opc_dastore = 82; + int opc_aastore = 83; + int opc_bastore = 84; + int opc_castore = 85; + int opc_sastore = 86; + int opc_pop = 87; + int opc_pop2 = 88; + int opc_dup = 89; + int opc_dup_x1 = 90; + int opc_dup_x2 = 91; + int opc_dup2 = 92; + int opc_dup2_x1 = 93; + int opc_dup2_x2 = 94; + int opc_swap = 95; + int opc_iadd = 96; + int opc_ladd = 97; + int opc_fadd = 98; + int opc_dadd = 99; + int opc_isub = 100; + int opc_lsub = 101; + int opc_fsub = 102; + int opc_dsub = 103; + int opc_imul = 104; + int opc_lmul = 105; + int opc_fmul = 106; + int opc_dmul = 107; + int opc_idiv = 108; + int opc_ldiv = 109; + int opc_fdiv = 110; + int opc_ddiv = 111; + int opc_irem = 112; + int opc_lrem = 113; + int opc_frem = 114; + int opc_drem = 115; + int opc_ineg = 116; + int opc_lneg = 117; + int opc_fneg = 118; + int opc_dneg = 119; + int opc_ishl = 120; + int opc_lshl = 121; + int opc_ishr = 122; + int opc_lshr = 123; + int opc_iushr = 124; + int opc_lushr = 125; + int opc_iand = 126; + int opc_land = 127; + int opc_ior = 128; + int opc_lor = 129; + int opc_ixor = 130; + int opc_lxor = 131; + int opc_iinc = 132; + int opc_i2l = 133; + int opc_i2f = 134; + int opc_i2d = 135; + int opc_l2i = 136; + int opc_l2f = 137; + int opc_l2d = 138; + int opc_f2i = 139; + int opc_f2l = 140; + int opc_f2d = 141; + int opc_d2i = 142; + int opc_d2l = 143; + int opc_d2f = 144; + int opc_i2b = 145; + int opc_i2c = 146; + int opc_i2s = 147; + int opc_lcmp = 148; + int opc_fcmpl = 149; + int opc_fcmpg = 150; + int opc_dcmpl = 151; + int opc_dcmpg = 152; + int opc_ifeq = 153; + int opc_ifne = 154; + int opc_iflt = 155; + int opc_ifge = 156; + int opc_ifgt = 157; + int opc_ifle = 158; + int opc_if_icmpeq = 159; + int opc_if_icmpne = 160; + int opc_if_icmplt = 161; + int opc_if_icmpge = 162; + int opc_if_icmpgt = 163; + int opc_if_icmple = 164; + int opc_if_acmpeq = 165; + int opc_if_acmpne = 166; + int opc_goto = 167; + int opc_jsr = 168; + int opc_ret = 169; + int opc_tableswitch = 170; + int opc_lookupswitch = 171; + int opc_ireturn = 172; + int opc_lreturn = 173; + int opc_freturn = 174; + int opc_dreturn = 175; + int opc_areturn = 176; + int opc_return = 177; + int opc_getstatic = 178; + int opc_putstatic = 179; + int opc_getfield = 180; + int opc_putfield = 181; + int opc_invokevirtual = 182; + int opc_invokespecial = 183; + int opc_invokestatic = 184; + int opc_invokeinterface = 185; + int opc_invokedynamic = 186; + int opc_new = 187; + int opc_newarray = 188; + int opc_anewarray = 189; + int opc_arraylength = 190; + int opc_athrow = 191; + int opc_checkcast = 192; + int opc_instanceof = 193; + int opc_monitorenter = 194; + int opc_monitorexit = 195; + int opc_wide = 196; + int opc_multianewarray = 197; + int opc_ifnull = 198; + int opc_ifnonnull = 199; + int opc_goto_w = 200; + int opc_jsr_w = 201; + + String CLINIT_NAME = ""; + String INIT_NAME = ""; +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionHandler.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionHandler.java new file mode 100644 index 000000000..12a622951 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionHandler.java @@ -0,0 +1,23 @@ +// 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.code; + +import org.jetbrains.java.decompiler.main.DecompilerContext; + +public class ExceptionHandler { + public int from = 0; + public int to = 0; + public int handler = 0; + + public int from_instr = 0; + public int to_instr = 0; + public int handler_instr = 0; + + public String exceptionClass = null; + + public String toString() { + String new_line_separator = DecompilerContext.getNewLineSeparator(); + return "from: " + from + " to: " + to + " handler: " + handler + new_line_separator + + "from_instr: " + from_instr + " to_instr: " + to_instr + " handler_instr: " + handler_instr + new_line_separator + + "exceptionClass: " + exceptionClass + new_line_separator; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionTable.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionTable.java new file mode 100644 index 000000000..7b752dccb --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/ExceptionTable.java @@ -0,0 +1,19 @@ +// 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.code; + +import java.util.Collections; +import java.util.List; + +public class ExceptionTable { + public static final ExceptionTable EMPTY = new ExceptionTable(Collections.emptyList()); + + private final List handlers; + + public ExceptionTable(List handlers) { + this.handlers = handlers; + } + + public List getHandlers() { + return handlers; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/FullInstructionSequence.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/FullInstructionSequence.java new file mode 100644 index 000000000..ad5370aae --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/FullInstructionSequence.java @@ -0,0 +1,24 @@ +// 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.code; + +import org.jetbrains.java.decompiler.util.VBStyleCollection; + + +public class FullInstructionSequence extends InstructionSequence { + + // ***************************************************************************** + // constructors + // ***************************************************************************** + + public FullInstructionSequence(VBStyleCollection collinstr, ExceptionTable extable) { + super(collinstr); + this.exceptionTable = extable; + + // translate raw exception handlers to instr + for (ExceptionHandler handler : extable.getHandlers()) { + handler.from_instr = this.getPointerByAbsOffset(handler.from); + handler.to_instr = this.getPointerByAbsOffset(handler.to); + handler.handler_instr = this.getPointerByAbsOffset(handler.handler); + } + } +} diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/Instruction.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/Instruction.java new file mode 100644 index 000000000..9087a5e8b --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/Instruction.java @@ -0,0 +1,63 @@ +// 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.code; + +public class Instruction implements CodeConstants { + public final int opcode; + public final int group; + public final boolean wide; + public final int bytecodeVersion; + protected final int[] operands; + + public Instruction(int opcode, int group, boolean wide, int bytecodeVersion, int[] operands) { + this.opcode = opcode; + this.group = group; + this.wide = wide; + this.bytecodeVersion = bytecodeVersion; + this.operands = operands; + } + + public static Instruction create(int opcode, boolean wide, int group, int bytecodeVersion, int[] operands) { + if (opcode >= opc_ifeq && opcode <= opc_if_acmpne || + opcode == opc_ifnull || opcode == opc_ifnonnull || + opcode == opc_jsr || opcode == opc_jsr_w || + opcode == opc_goto || opcode == opc_goto_w) { + return new JumpInstruction(opcode, group, wide, bytecodeVersion, operands); + } else if (opcode == opc_tableswitch || opcode == opc_lookupswitch) { + return new SwitchInstruction(opcode, group, wide, bytecodeVersion, operands); + } else { + return new Instruction(opcode, group, wide, bytecodeVersion, operands); + } + } + + public static boolean equals(Instruction i1, Instruction i2) { + return i1 != null && i2 != null && + (i1 == i2 || + i1.opcode == i2.opcode && + i1.wide == i2.wide && + i1.operandsCount() == i2.operandsCount()); + } + + public void initInstruction(InstructionSequence seq) { + } + + public int operandsCount() { + return operands == null ? 0 : operands.length; + } + + public int operand(int index) { + return operands[index]; + } + + public boolean canFallThrough() { + return opcode != opc_goto && opcode != opc_goto_w && opcode != opc_ret && + !(opcode >= opc_ireturn && opcode <= opc_return) && + opcode != opc_athrow && + opcode != opc_jsr && opcode != opc_tableswitch && opcode != opc_lookupswitch; + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public Instruction clone() { + return create(opcode, wide, group, bytecodeVersion, operands == null ? null : operands.clone()); + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/InstructionSequence.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/InstructionSequence.java new file mode 100644 index 000000000..a4a0306a5 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/InstructionSequence.java @@ -0,0 +1,145 @@ +// 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.code; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.util.TextUtil; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +public abstract class InstructionSequence { + + // ***************************************************************************** + // private fields + // ***************************************************************************** + + protected final VBStyleCollection collinstr; + + protected int pointer = 0; + + protected ExceptionTable exceptionTable = ExceptionTable.EMPTY; + + protected InstructionSequence() { + this(new VBStyleCollection()); + } + + protected InstructionSequence(VBStyleCollection collinstr) { + this.collinstr = collinstr; + } + + // ***************************************************************************** + // public methods + // ***************************************************************************** + + // to nbe overwritten + public InstructionSequence clone() { + return null; + } + + public void clear() { + collinstr.clear(); + pointer = 0; + exceptionTable = ExceptionTable.EMPTY; + } + + public void addInstruction(Instruction inst, int offset) { + collinstr.addWithKey(inst, offset); + } + + public void addInstruction(int index, Instruction inst, int offset) { + collinstr.addWithKeyAndIndex(index, inst, offset); + } + + public void addSequence(InstructionSequence seq) { + for (int i = 0; i < seq.length(); i++) { + addInstruction(seq.getInstr(i), -1); // TODO: any sensible value possible? + } + } + + public void removeInstruction(int index) { + collinstr.remove(index); + } + + public void removeLast() { + if (!collinstr.isEmpty()) { + collinstr.remove(collinstr.size() - 1); + } + } + + public Instruction getInstr(int index) { + return collinstr.get(index); + } + + public Instruction getLastInstr() { + return collinstr.getLast(); + } + + public int getOffset(int index) { + return collinstr.getKey(index); + } + + public int getPointerByAbsOffset(int offset) { + Integer absoffset = offset; + if (collinstr.containsKey(absoffset)) { + return collinstr.getIndexByKey(absoffset); + } else { + return -1; + } + } + + public int getPointerByRelOffset(int offset) { + Integer absoffset = collinstr.getKey(pointer) + offset; + if (collinstr.containsKey(absoffset)) { + return collinstr.getIndexByKey(absoffset); + } else { + return -1; + } + } + + public int length() { + return collinstr.size(); + } + + public boolean isEmpty() { + return collinstr.isEmpty(); + } + + public void addToPointer(int diff) { + this.pointer += diff; + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + StringBuilder buf = new StringBuilder(); + + for (int i = 0; i < collinstr.size(); i++) { + buf.append(TextUtil.getIndentString(indent)); + buf.append(collinstr.getKey(i).intValue()); + buf.append(": "); + buf.append(collinstr.get(i).toString()); + buf.append(new_line_separator); + } + + return buf.toString(); + } + + // ***************************************************************************** + // getter and setter methods + // ***************************************************************************** + + public int getPointer() { + return pointer; + } + + public void setPointer(int pointer) { + this.pointer = pointer; + } + + public ExceptionTable getExceptionTable() { + return exceptionTable; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/JumpInstruction.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/JumpInstruction.java new file mode 100644 index 000000000..a467c7b1c --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/JumpInstruction.java @@ -0,0 +1,22 @@ +// 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.code; + +public class JumpInstruction extends Instruction { + public int destination; + + public JumpInstruction(int opcode, int group, boolean wide, int bytecodeVersion, int[] operands) { + super(opcode, group, wide, bytecodeVersion, operands); + } + + @Override + public void initInstruction(InstructionSequence seq) { + destination = seq.getPointerByRelOffset(this.operand(0)); + } + + @Override + public JumpInstruction clone() { + JumpInstruction copy = (JumpInstruction) super.clone(); + copy.destination = destination; + return copy; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java new file mode 100644 index 000000000..c4545f3d0 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SimpleInstructionSequence.java @@ -0,0 +1,21 @@ +// 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.code; + +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +public class SimpleInstructionSequence extends InstructionSequence { + + public SimpleInstructionSequence() { + } + + public SimpleInstructionSequence(VBStyleCollection collinstr) { + super(collinstr); + } + + public SimpleInstructionSequence clone() { + SimpleInstructionSequence newseq = new SimpleInstructionSequence(collinstr.clone()); + newseq.setPointer(this.getPointer()); + + return newseq; + } +} diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SwitchInstruction.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SwitchInstruction.java new file mode 100644 index 000000000..1ec3d55d4 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/SwitchInstruction.java @@ -0,0 +1,59 @@ +// 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.code; + +public class SwitchInstruction extends Instruction { + private int[] destinations; + private int[] values; + private int defaultDestination; + + public SwitchInstruction(int opcode, int group, boolean wide, int bytecodeVersion, int[] operands) { + super(opcode, group, wide, bytecodeVersion, operands); + } + + @Override + public void initInstruction(InstructionSequence seq) { + defaultDestination = seq.getPointerByRelOffset(operands[0]); + + int prefix = opcode == CodeConstants.opc_tableswitch ? 3 : 2; + int len = operands.length - prefix; + int low = 0; + if (opcode == CodeConstants.opc_lookupswitch) { + len /= 2; + } else { + low = operands[1]; + } + + destinations = new int[len]; + values = new int[len]; + for (int i = 0, k = 0; i < len; i++, k++) { + if (opcode == CodeConstants.opc_lookupswitch) { + values[i] = operands[prefix + k]; + k++; + } else { + values[i] = low + k; + } + destinations[i] = seq.getPointerByRelOffset(operands[prefix + k]); + } + } + + public int[] getDestinations() { + return destinations; + } + + public int[] getValues() { + return values; + } + + public int getDefaultDestination() { + return defaultDestination; + } + + @Override + public SwitchInstruction clone() { + SwitchInstruction copy = (SwitchInstruction) super.clone(); + copy.defaultDestination = defaultDestination; + copy.destinations = destinations.clone(); + copy.values = values.clone(); + return copy; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java new file mode 100644 index 000000000..82848e57c --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java @@ -0,0 +1,189 @@ +// 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.code.cfg; + +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.code.SimpleInstructionSequence; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraphNode; + +import java.util.ArrayList; +import java.util.List; + +public class BasicBlock implements IGraphNode { + + // ***************************************************************************** + // public fields + // ***************************************************************************** + + private final List preds = new ArrayList<>(); + private final List succs = new ArrayList<>(); + + // ***************************************************************************** + // private fields + // ***************************************************************************** + private final List instrOldOffsets = new ArrayList<>(); + private final List predExceptions = new ArrayList<>(); + private final List succExceptions = new ArrayList<>(); + public int id; + public int mark = 0; + private InstructionSequence seq = new SimpleInstructionSequence(); + + public BasicBlock(int id) { + this.id = id; + } + + // ***************************************************************************** + // public methods + // ***************************************************************************** + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public BasicBlock clone() { + BasicBlock block = new BasicBlock(id); + + block.setSeq(seq.clone()); + block.instrOldOffsets.addAll(instrOldOffsets); + + return block; + } + + public Instruction getInstruction(int index) { + return seq.getInstr(index); + } + + public Instruction getLastInstruction() { + if (seq.isEmpty()) { + return null; + } else { + return seq.getLastInstr(); + } + } + + public Integer getOldOffset(int index) { + if (index < instrOldOffsets.size()) { + return instrOldOffsets.get(index); + } else { + return -1; + } + } + + public int size() { + return seq.length(); + } + + public void addPredecessor(BasicBlock block) { + preds.add(block); + } + + public void removePredecessor(BasicBlock block) { + while (preds.remove(block)) /**/ ; + } + + public void addSuccessor(BasicBlock block) { + succs.add(block); + block.addPredecessor(this); + } + + public void removeSuccessor(BasicBlock block) { + while (succs.remove(block)) /**/ ; + block.removePredecessor(this); + } + + // FIXME: unify block comparisons: id or direct equality + public void replaceSuccessor(BasicBlock oldBlock, BasicBlock newBlock) { + for (int i = 0; i < succs.size(); i++) { + if (succs.get(i).id == oldBlock.id) { + succs.set(i, newBlock); + oldBlock.removePredecessor(this); + newBlock.addPredecessor(this); + } + } + + for (int i = 0; i < succExceptions.size(); i++) { + if (succExceptions.get(i).id == oldBlock.id) { + succExceptions.set(i, newBlock); + oldBlock.removePredecessorException(this); + newBlock.addPredecessorException(this); + } + } + } + + public void addPredecessorException(BasicBlock block) { + predExceptions.add(block); + } + + public void removePredecessorException(BasicBlock block) { + while (predExceptions.remove(block)) /**/ ; + } + + public void addSuccessorException(BasicBlock block) { + if (!succExceptions.contains(block)) { + succExceptions.add(block); + block.addPredecessorException(this); + } + } + + public void removeSuccessorException(BasicBlock block) { + while (succExceptions.remove(block)) /**/ ; + block.removePredecessorException(this); + } + + public String toString() { + return toString(0); + } + + public String toString(int indent) { + + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + return id + ":" + new_line_separator + seq.toString(indent); + } + + public boolean isSuccessor(BasicBlock block) { + for (BasicBlock succ : succs) { + if (succ.id == block.id) { + return true; + } + } + return false; + } + + // ***************************************************************************** + // getter and setter methods + // ***************************************************************************** + + public List getInstrOldOffsets() { + return instrOldOffsets; + } + + public List getPredecessors() { + List lst = new ArrayList<>(preds); + lst.addAll(predExceptions); + return lst; + } + + public List getPreds() { + return preds; + } + + public InstructionSequence getSeq() { + return seq; + } + + public void setSeq(InstructionSequence seq) { + this.seq = seq; + } + + public List getSuccs() { + return succs; + } + + public List getSuccExceptions() { + return succExceptions; + } + + public List getPredExceptions() { + return predExceptions; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java new file mode 100644 index 000000000..8f33d5d50 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java @@ -0,0 +1,819 @@ +// 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.code.cfg; + +import com.duy.java8.util.DCollection; +import com.duy.java8.util.function.Predicate; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.ExceptionHandler; +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.code.JumpInstruction; +import org.jetbrains.java.decompiler.code.SwitchInstruction; +import org.jetbrains.java.decompiler.code.interpreter.InstructionImpact; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.modules.code.DeadCodeHelper; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.consts.ConstantPool; +import org.jetbrains.java.decompiler.struct.gen.DataPoint; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.ListStack; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class ControlFlowGraph implements CodeConstants { + + private final Set finallyExits = new HashSet<>(); + + // ***************************************************************************** + // private fields + // ***************************************************************************** + public int last_id = 0; + private VBStyleCollection blocks; + private BasicBlock first; + private BasicBlock last; + private List exceptions; + private Map subroutines; + + // ***************************************************************************** + // constructors + // ***************************************************************************** + + public ControlFlowGraph(InstructionSequence seq) { + buildBlocks(seq); + } + + + // ***************************************************************************** + // public methods + // ***************************************************************************** + + private static short[] findStartInstructions(InstructionSequence seq) { + + int len = seq.length(); + short[] inststates = new short[len]; + + Set excSet = new HashSet<>(); + + for (ExceptionHandler handler : seq.getExceptionTable().getHandlers()) { + excSet.add(handler.from_instr); + excSet.add(handler.to_instr); + excSet.add(handler.handler_instr); + } + + + for (int i = 0; i < len; i++) { + + // exception blocks + if (excSet.contains(i)) { + inststates[i] = 1; + } + + Instruction instr = seq.getInstr(i); + switch (instr.group) { + case GROUP_JUMP: + inststates[((JumpInstruction) instr).destination] = 1; + case GROUP_RETURN: + if (i + 1 < len) { + inststates[i + 1] = 1; + } + break; + case GROUP_SWITCH: + SwitchInstruction swinstr = (SwitchInstruction) instr; + int[] dests = swinstr.getDestinations(); + for (int j = dests.length - 1; j >= 0; j--) { + inststates[dests[j]] = 1; + } + inststates[swinstr.getDefaultDestination()] = 1; + if (i + 1 < len) { + inststates[i + 1] = 1; + } + } + } + + // first instruction + inststates[0] = 1; + + return inststates; + } + + private static void connectBlocks(List lstbb, Map mapInstrBlocks) { + + for (int i = 0; i < lstbb.size(); i++) { + + BasicBlock block = lstbb.get(i); + Instruction instr = block.getLastInstruction(); + + boolean fallthrough = instr.canFallThrough(); + BasicBlock bTemp; + + switch (instr.group) { + case GROUP_JUMP: + int dest = ((JumpInstruction) instr).destination; + bTemp = mapInstrBlocks.get(dest); + block.addSuccessor(bTemp); + + break; + case GROUP_SWITCH: + SwitchInstruction sinstr = (SwitchInstruction) instr; + int[] dests = sinstr.getDestinations(); + + bTemp = mapInstrBlocks.get(((SwitchInstruction) instr).getDefaultDestination()); + block.addSuccessor(bTemp); + for (int dest1 : dests) { + bTemp = mapInstrBlocks.get(dest1); + block.addSuccessor(bTemp); + } + } + + if (fallthrough && i < lstbb.size() - 1) { + BasicBlock defaultBlock = lstbb.get(i + 1); + block.addSuccessor(defaultBlock); + } + } + } + + private static void removeJsrInstructions(ConstantPool pool, BasicBlock block, DataPoint data) { + + ListStack stack = data.getStack(); + + InstructionSequence seq = block.getSeq(); + for (int i = 0; i < seq.length(); i++) { + Instruction instr = seq.getInstr(i); + + VarType var = null; + if (instr.opcode == CodeConstants.opc_astore || instr.opcode == CodeConstants.opc_pop) { + var = stack.getByOffset(-1); + } + + InstructionImpact.stepTypes(data, instr, pool); + + switch (instr.opcode) { + case CodeConstants.opc_jsr: + case CodeConstants.opc_ret: + seq.removeInstruction(i); + i--; + break; + case CodeConstants.opc_astore: + case CodeConstants.opc_pop: + if (var.type == CodeConstants.TYPE_ADDRESS) { + seq.removeInstruction(i); + i--; + } + } + } + + block.mark = 1; + + for (int i = 0; i < block.getSuccs().size(); i++) { + BasicBlock suc = block.getSuccs().get(i); + if (suc.mark != 1) { + removeJsrInstructions(pool, suc, data.copy()); + } + } + + for (int i = 0; i < block.getSuccExceptions().size(); i++) { + BasicBlock suc = block.getSuccExceptions().get(i); + if (suc.mark != 1) { + + DataPoint point = new DataPoint(); + point.setLocalVariables(new ArrayList<>(data.getLocalVariables())); + point.getStack().push(new VarType(CodeConstants.TYPE_OBJECT, 0, null)); + + removeJsrInstructions(pool, suc, point); + } + } + } + + private static void addToReversePostOrderListIterative(BasicBlock root, List lst) { + + LinkedList stackNode = new LinkedList<>(); + LinkedList stackIndex = new LinkedList<>(); + + Set setVisited = new HashSet<>(); + + stackNode.add(root); + stackIndex.add(0); + + while (!stackNode.isEmpty()) { + + BasicBlock node = stackNode.getLast(); + int index = stackIndex.removeLast(); + + setVisited.add(node); + + List lstSuccs = new ArrayList<>(node.getSuccs()); + lstSuccs.addAll(node.getSuccExceptions()); + + for (; index < lstSuccs.size(); index++) { + BasicBlock succ = lstSuccs.get(index); + + if (!setVisited.contains(succ)) { + stackIndex.add(index + 1); + + stackNode.add(succ); + stackIndex.add(0); + + break; + } + } + + if (index == lstSuccs.size()) { + lst.add(0, node); + + stackNode.removeLast(); + } + } + } + + public void removeMarkers() { + for (BasicBlock block : blocks) { + block.mark = 0; + } + } + + // public String getExceptionsUniqueString(BasicBlock handler, BasicBlock block) { + // + // List ranges = getExceptionRange(handler, block); + // + // if(ranges == null) { + // return null; + // } else { + // Set setExceptionStrings = new HashSet(); + // for(ExceptionRangeCFG range : ranges) { + // setExceptionStrings.add(range.getExceptionType()); + // } + // + // String ret = ""; + // for(String exception : setExceptionStrings) { + // ret += exception; + // } + // + // return ret; + // } + // } + + + // ***************************************************************************** + // private methods + // ***************************************************************************** + + public String toString() { + if (blocks == null) return "Empty"; + + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + StringBuilder buf = new StringBuilder(); + + for (BasicBlock block : blocks) { + buf.append("----- Block ").append(block.id).append(" -----").append(new_line_separator); + buf.append(block.toString()); + buf.append("----- Edges -----").append(new_line_separator); + + List suc = block.getSuccs(); + for (BasicBlock aSuc : suc) { + buf.append(">>>>>>>>(regular) Block ").append(aSuc.id).append(new_line_separator); + } + suc = block.getSuccExceptions(); + for (BasicBlock handler : suc) { + ExceptionRangeCFG range = getExceptionRange(handler, block); + + if (range == null) { + buf.append(">>>>>>>>(exception) Block ").append(handler.id).append("\t").append("ERROR: range not found!") + .append(new_line_separator); + } else { + List exceptionTypes = range.getExceptionTypes(); + if (exceptionTypes == null) { + buf.append(">>>>>>>>(exception) Block ").append(handler.id).append("\t").append("NULL").append(new_line_separator); + } else { + for (String exceptionType : exceptionTypes) { + buf.append(">>>>>>>>(exception) Block ").append(handler.id).append("\t").append(exceptionType).append(new_line_separator); + } + } + } + } + buf.append("----- ----- -----").append(new_line_separator); + } + + return buf.toString(); + } + + public void inlineJsr(StructMethod mt) { + processJsr(); + removeJsr(mt); + + removeMarkers(); + + DeadCodeHelper.removeEmptyBlocks(this); + } + + public void removeBlock(final BasicBlock block) { + + while (block.getSuccs().size() > 0) { + block.removeSuccessor(block.getSuccs().get(0)); + } + + while (block.getSuccExceptions().size() > 0) { + block.removeSuccessorException(block.getSuccExceptions().get(0)); + } + + while (block.getPreds().size() > 0) { + block.getPreds().get(0).removeSuccessor(block); + } + + while (block.getPredExceptions().size() > 0) { + block.getPredExceptions().get(0).removeSuccessorException(block); + } + + last.removePredecessor(block); + + blocks.removeWithKey(block.id); + + for (int i = exceptions.size() - 1; i >= 0; i--) { + ExceptionRangeCFG range = exceptions.get(i); + if (range.getHandler() == block) { + exceptions.remove(i); + } else { + List lstRange = range.getProtectedRange(); + lstRange.remove(block); + + if (lstRange.isEmpty()) { + exceptions.remove(i); + } + } + } + + DCollection.removeIf(subroutines.entrySet(), new Predicate>() { + @Override + public boolean test(Entry ent) { + return ent.getKey() == block || ent.getValue() == block; + } + }); + } + + public ExceptionRangeCFG getExceptionRange(BasicBlock handler, BasicBlock block) { + + //List ranges = new ArrayList(); + + for (int i = exceptions.size() - 1; i >= 0; i--) { + ExceptionRangeCFG range = exceptions.get(i); + if (range.getHandler() == handler && range.getProtectedRange().contains(block)) { + return range; + //ranges.add(range); + } + } + + return null; + //return ranges.isEmpty() ? null : ranges; + } + + private void buildBlocks(InstructionSequence instrseq) { + + short[] states = findStartInstructions(instrseq); + + Map mapInstrBlocks = new HashMap<>(); + VBStyleCollection colBlocks = createBasicBlocks(states, instrseq, mapInstrBlocks); + + blocks = colBlocks; + + connectBlocks(colBlocks, mapInstrBlocks); + + setExceptionEdges(instrseq, mapInstrBlocks); + + setSubroutineEdges(); + + setFirstAndLastBlocks(); + } + + private VBStyleCollection createBasicBlocks(short[] startblock, + InstructionSequence instrseq, + Map mapInstrBlocks) { + + VBStyleCollection col = new VBStyleCollection<>(); + + InstructionSequence currseq = null; + List lstOffs = null; + + int len = startblock.length; + short counter = 0; + int blockoffset = 0; + + BasicBlock currentBlock = null; + for (int i = 0; i < len; i++) { + + if (startblock[i] == 1) { + currentBlock = new BasicBlock(++counter); + + currseq = currentBlock.getSeq(); + lstOffs = currentBlock.getInstrOldOffsets(); + + col.addWithKey(currentBlock, currentBlock.id); + + blockoffset = instrseq.getOffset(i); + } + + startblock[i] = counter; + mapInstrBlocks.put(i, currentBlock); + + currseq.addInstruction(instrseq.getInstr(i), instrseq.getOffset(i) - blockoffset); + lstOffs.add(instrseq.getOffset(i)); + } + + last_id = counter; + + return col; + } + + private void setExceptionEdges(InstructionSequence instrseq, Map instrBlocks) { + + exceptions = new ArrayList<>(); + + Map mapRanges = new HashMap<>(); + + for (ExceptionHandler handler : instrseq.getExceptionTable().getHandlers()) { + + BasicBlock from = instrBlocks.get(handler.from_instr); + BasicBlock to = instrBlocks.get(handler.to_instr); + BasicBlock handle = instrBlocks.get(handler.handler_instr); + + String key = from.id + ":" + to.id + ":" + handle.id; + + if (mapRanges.containsKey(key)) { + ExceptionRangeCFG range = mapRanges.get(key); + range.addExceptionType(handler.exceptionClass); + } else { + + List protectedRange = new ArrayList<>(); + for (int j = from.id; j < to.id; j++) { + BasicBlock block = blocks.getWithKey(j); + protectedRange.add(block); + block.addSuccessorException(handle); + } + + ExceptionRangeCFG range = new ExceptionRangeCFG(protectedRange, handle, handler.exceptionClass == null + ? null + : Collections.singletonList(handler.exceptionClass)); + mapRanges.put(key, range); + + exceptions.add(range); + } + } + } + + private void setSubroutineEdges() { + + final Map subroutines = new LinkedHashMap<>(); + + for (BasicBlock block : blocks) { + + if (block.getSeq().getLastInstr().opcode == CodeConstants.opc_jsr) { + + LinkedList stack = new LinkedList<>(); + LinkedList> stackJsrStacks = new LinkedList<>(); + + Set setVisited = new HashSet<>(); + + stack.add(block); + stackJsrStacks.add(new LinkedList()); + + while (!stack.isEmpty()) { + + BasicBlock node = stack.removeFirst(); + LinkedList jsrstack = stackJsrStacks.removeFirst(); + + setVisited.add(node); + + switch (node.getSeq().getLastInstr().opcode) { + case CodeConstants.opc_jsr: + jsrstack.add(node); + break; + case CodeConstants.opc_ret: + BasicBlock enter = jsrstack.getLast(); + BasicBlock exit = blocks.getWithKey(enter.id + 1); // FIXME: find successor in a better way + + if (exit != null) { + if (!node.isSuccessor(exit)) { + node.addSuccessor(exit); + } + jsrstack.removeLast(); + subroutines.put(enter, exit); + } else { + throw new RuntimeException("ERROR: last instruction jsr"); + } + } + + if (!jsrstack.isEmpty()) { + for (BasicBlock succ : node.getSuccs()) { + if (!setVisited.contains(succ)) { + stack.add(succ); + stackJsrStacks.add(new LinkedList<>(jsrstack)); + } + } + } + } + } + } + + this.subroutines = subroutines; + } + + private void processJsr() { + while (true) { + if (processJsrRanges() == 0) break; + } + } + + private int processJsrRanges() { + + List lstJsrAll = new ArrayList<>(); + + // get all jsr ranges + for (Entry ent : subroutines.entrySet()) { + BasicBlock jsr = ent.getKey(); + BasicBlock ret = ent.getValue(); + + lstJsrAll.add(new JsrRecord(jsr, getJsrRange(jsr, ret), ret)); + } + + // sort ranges + // FIXME: better sort order + List lstJsr = new ArrayList<>(); + for (JsrRecord arr : lstJsrAll) { + int i = 0; + for (; i < lstJsr.size(); i++) { + JsrRecord arrJsr = lstJsr.get(i); + if (arrJsr.range.contains(arr.jsr)) { + break; + } + } + lstJsr.add(i, arr); + } + + // find the first intersection + for (int i = 0; i < lstJsr.size(); i++) { + JsrRecord arr = lstJsr.get(i); + Set set = arr.range; + + for (int j = i + 1; j < lstJsr.size(); j++) { + JsrRecord arr1 = lstJsr.get(j); + Set set1 = arr1.range; + + if (!set.contains(arr1.jsr) && !set1.contains(arr.jsr)) { // rang 0 doesn't contain entry 1 and vice versa + Set setc = new HashSet<>(set); + setc.retainAll(set1); + + if (!setc.isEmpty()) { + splitJsrRange(arr.jsr, arr.ret, setc); + return 1; + } + } + } + } + + return 0; + } + + private Set getJsrRange(BasicBlock jsr, BasicBlock ret) { + + Set blocks = new HashSet<>(); + + List lstNodes = new LinkedList<>(); + lstNodes.add(jsr); + + BasicBlock dom = jsr.getSuccs().get(0); + + while (!lstNodes.isEmpty()) { + + BasicBlock node = lstNodes.remove(0); + + for (int j = 0; j < 2; j++) { + List lst; + if (j == 0) { + if (node.getLastInstruction().opcode == CodeConstants.opc_ret) { + if (node.getSuccs().contains(ret)) { + continue; + } + } + lst = node.getSuccs(); + } else { + if (node == jsr) { + continue; + } + lst = node.getSuccExceptions(); + } + + CHILD: + for (int i = lst.size() - 1; i >= 0; i--) { + + BasicBlock child = lst.get(i); + if (!blocks.contains(child)) { + + if (node != jsr) { + for (int k = 0; k < child.getPreds().size(); k++) { + if (!DeadCodeHelper.isDominator(this, child.getPreds().get(k), dom)) { + continue CHILD; + } + } + + for (int k = 0; k < child.getPredExceptions().size(); k++) { + if (!DeadCodeHelper.isDominator(this, child.getPredExceptions().get(k), dom)) { + continue CHILD; + } + } + } + + // last block is a dummy one + if (child != last) { + blocks.add(child); + } + + lstNodes.add(child); + } + } + } + } + + return blocks; + } + + private void splitJsrRange(BasicBlock jsr, BasicBlock ret, Set common_blocks) { + + List lstNodes = new LinkedList<>(); + Map mapNewNodes = new HashMap<>(); + + lstNodes.add(jsr); + mapNewNodes.put(jsr.id, jsr); + + while (!lstNodes.isEmpty()) { + + BasicBlock node = lstNodes.remove(0); + + for (int j = 0; j < 2; j++) { + List lst; + if (j == 0) { + if (node.getLastInstruction().opcode == CodeConstants.opc_ret) { + if (node.getSuccs().contains(ret)) { + continue; + } + } + lst = node.getSuccs(); + } else { + if (node == jsr) { + continue; + } + lst = node.getSuccExceptions(); + } + + + for (int i = lst.size() - 1; i >= 0; i--) { + + BasicBlock child = lst.get(i); + Integer childid = child.id; + + if (mapNewNodes.containsKey(childid)) { + node.replaceSuccessor(child, mapNewNodes.get(childid)); + } else if (common_blocks.contains(child)) { + // make a copy of the current block + BasicBlock copy = child.clone(); + copy.id = ++last_id; + // copy all successors + if (copy.getLastInstruction().opcode == CodeConstants.opc_ret && + child.getSuccs().contains(ret)) { + copy.addSuccessor(ret); + child.removeSuccessor(ret); + } else { + for (int k = 0; k < child.getSuccs().size(); k++) { + copy.addSuccessor(child.getSuccs().get(k)); + } + } + for (int k = 0; k < child.getSuccExceptions().size(); k++) { + copy.addSuccessorException(child.getSuccExceptions().get(k)); + } + + lstNodes.add(copy); + mapNewNodes.put(childid, copy); + + if (last.getPreds().contains(child)) { + last.addPredecessor(copy); + } + + node.replaceSuccessor(child, copy); + blocks.addWithKey(copy, copy.id); + } else { + // stop at the first fixed node + //lstNodes.add(child); + mapNewNodes.put(childid, child); + } + } + } + } + + // note: subroutines won't be copied! + splitJsrExceptionRanges(common_blocks, mapNewNodes); + } + + private void splitJsrExceptionRanges(Set common_blocks, Map mapNewNodes) { + + for (int i = exceptions.size() - 1; i >= 0; i--) { + + ExceptionRangeCFG range = exceptions.get(i); + List lstRange = range.getProtectedRange(); + + HashSet setBoth = new HashSet<>(common_blocks); + setBoth.retainAll(lstRange); + + if (setBoth.size() > 0) { + List lstNewRange; + + if (setBoth.size() == lstRange.size()) { + lstNewRange = new ArrayList<>(); + ExceptionRangeCFG newRange = new ExceptionRangeCFG(lstNewRange, + mapNewNodes.get(range.getHandler().id), range.getExceptionTypes()); + exceptions.add(newRange); + } else { + lstNewRange = lstRange; + } + + for (BasicBlock block : setBoth) { + lstNewRange.add(mapNewNodes.get(block.id)); + } + } + } + } + + private void removeJsr(StructMethod mt) { + removeJsrInstructions(mt.getClassStruct().getPool(), first, DataPoint.getInitialDataPoint(mt)); + } + + private void setFirstAndLastBlocks() { + + first = blocks.get(0); + + last = new BasicBlock(++last_id); + + for (BasicBlock block : blocks) { + if (block.getSuccs().isEmpty()) { + last.addPredecessor(block); + } + } + } + + public List getReversePostOrder() { + + List res = new LinkedList<>(); + addToReversePostOrderListIterative(first, res); + + return res; + } + + public VBStyleCollection getBlocks() { + return blocks; + } + + + // ***************************************************************************** + // getter and setter methods + // ***************************************************************************** + + public BasicBlock getFirst() { + return first; + } + + public void setFirst(BasicBlock first) { + this.first = first; + } + + public List getExceptions() { + return exceptions; + } + + public BasicBlock getLast() { + return last; + } + + public Set getFinallyExits() { + return finallyExits; + } + + private static class JsrRecord { + private final BasicBlock jsr; + private final Set range; + private final BasicBlock ret; + + private JsrRecord(BasicBlock jsr, Set range, BasicBlock ret) { + this.jsr = jsr; + this.range = range; + this.ret = ret; + } + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java new file mode 100644 index 000000000..feb2a3395 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java @@ -0,0 +1,57 @@ +// 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.code.cfg; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class ExceptionRangeCFG { + private final List protectedRange; // FIXME: replace with set + private BasicBlock handler; + private List exceptionTypes; + + public ExceptionRangeCFG(List protectedRange, BasicBlock handler, List exceptionType) { + this.protectedRange = protectedRange; + this.handler = handler; + + if (exceptionType != null) { + this.exceptionTypes = new ArrayList<>(exceptionType); + } + } + + public boolean isCircular() { + return protectedRange.contains(handler); + } + + public BasicBlock getHandler() { + return handler; + } + + public void setHandler(BasicBlock handler) { + this.handler = handler; + } + + public List getProtectedRange() { + return protectedRange; + } + + public List getExceptionTypes() { + return this.exceptionTypes; + } + + public void addExceptionType(String exceptionType) { + if (this.exceptionTypes == null) { + return; + } + + if (exceptionType == null) { + this.exceptionTypes = null; + } else { + this.exceptionTypes.add(exceptionType); + } + } + + public String getUniqueExceptionsString() { + return exceptionTypes != null ? exceptionTypes.stream().distinct().collect(Collectors.joining(":")) : null; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java new file mode 100644 index 000000000..db1efe020 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/code/interpreter/InstructionImpact.java @@ -0,0 +1,509 @@ +// 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.code.interpreter; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.struct.consts.ConstantPool; +import org.jetbrains.java.decompiler.struct.consts.LinkConstant; +import org.jetbrains.java.decompiler.struct.consts.PooledConstant; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; +import org.jetbrains.java.decompiler.struct.gen.DataPoint; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.ListStack; + +public class InstructionImpact { + + // {read, write} + private static final int[][][] stack_impact = { + + {null, null}, // public final static int opc_nop = 0; + null, // public final static int opc_aconst_null = 1; + null, // public final static int opc_iconst_m1 = 2; + null, // public final static int opc_iconst_0 = 3; + null, // public final static int opc_iconst_1 = 4; + null, // public final static int opc_iconst_2 = 5; + null, // public final static int opc_iconst_3 = 6; + null, // public final static int opc_iconst_4 = 7; + null, // public final static int opc_iconst_5 = 8; + {null, {CodeConstants.TYPE_LONG}}, // public final static int opc_lconst_0 = 9; + {null, {CodeConstants.TYPE_LONG}}, // public final static int opc_lconst_1 = 10; + {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fconst_0 = 11; + {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fconst_1 = 12; + {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fconst_2 = 13; + {null, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dconst_0 = 14; + {null, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dconst_1 = 15; + {null, {CodeConstants.TYPE_INT}}, // public final static int opc_bipush = 16; + {null, {CodeConstants.TYPE_INT}}, // public final static int opc_sipush = 17; + null, // public final static int opc_ldc = 18; + null, // public final static int opc_ldc_w = 19; + null, // public final static int opc_ldc2_w = 20; + {null, {CodeConstants.TYPE_INT}}, // public final static int opc_iload = 21; + {null, {CodeConstants.TYPE_LONG}}, // public final static int opc_lload = 22; + {null, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fload = 23; + {null, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dload = 24; + null, // public final static int opc_aload = 25; + null, // public final static int opc_iload_0 = 26; + null, // public final static int opc_iload_1 = 27; + null, // public final static int opc_iload_2 = 28; + null, // public final static int opc_iload_3 = 29; + null, // public final static int opc_lload_0 = 30; + null, // public final static int opc_lload_1 = 31; + null, // public final static int opc_lload_2 = 32; + null, // public final static int opc_lload_3 = 33; + null, // public final static int opc_fload_0 = 34; + null, // public final static int opc_fload_1 = 35; + null, // public final static int opc_fload_2 = 36; + null, // public final static int opc_fload_3 = 37; + null, // public final static int opc_dload_0 = 38; + null, // public final static int opc_dload_1 = 39; + null, // public final static int opc_dload_2 = 40; + null, // public final static int opc_dload_3 = 41; + null, // public final static int opc_aload_0 = 42; + null, // public final static int opc_aload_1 = 43; + null, // public final static int opc_aload_2 = 44; + null, // public final static int opc_aload_3 = 45; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_iaload = 46; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_laload = 47; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_FLOAT}}, + // public final static int opc_faload = 48; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_DOUBLE}}, + // public final static int opc_daload = 49; + null, // public final static int opc_aaload = 50; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_baload = 51; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_caload = 52; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_saload = 53; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_istore = 54; + {{CodeConstants.TYPE_LONG}, null}, // public final static int opc_lstore = 55; + {{CodeConstants.TYPE_FLOAT}, null}, // public final static int opc_fstore = 56; + {{CodeConstants.TYPE_DOUBLE}, null}, // public final static int opc_dstore = 57; + null, // public final static int opc_astore = 58; + null, // public final static int opc_istore_0 = 59; + null, // public final static int opc_istore_1 = 60; + null, // public final static int opc_istore_2 = 61; + null, // public final static int opc_istore_3 = 62; + null, // public final static int opc_lstore_0 = 63; + null, // public final static int opc_lstore_1 = 64; + null, // public final static int opc_lstore_2 = 65; + null, // public final static int opc_lstore_3 = 66; + null, // public final static int opc_fstore_0 = 67; + null, // public final static int opc_fstore_1 = 68; + null, // public final static int opc_fstore_2 = 69; + null, // public final static int opc_fstore_3 = 70; + null, // public final static int opc_dstore_0 = 71; + null, // public final static int opc_dstore_1 = 72; + null, // public final static int opc_dstore_2 = 73; + null, // public final static int opc_dstore_3 = 74; + null, // public final static int opc_astore_0 = 75; + null, // public final static int opc_astore_1 = 76; + null, // public final static int opc_astore_2 = 77; + null, // public final static int opc_astore_3 = 78; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + // public final static int opc_iastore = 79; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_LONG}, null}, + // public final static int opc_lastore = 80; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_FLOAT}, null}, + // public final static int opc_fastore = 81; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_DOUBLE}, null}, + // public final static int opc_dastore = 82; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_OBJECT}, null}, + // public final static int opc_aastore = 83; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + // public final static int opc_bastore = 84; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + // public final static int opc_castore = 85; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, + // public final static int opc_sastore = 86; + {{CodeConstants.TYPE_ANY}, null}, // public final static int opc_pop = 87; + {{CodeConstants.TYPE_ANY, CodeConstants.TYPE_ANY}, null}, // public final static int opc_pop2 = 88; + null, // public final static int opc_dup = 89; + null, // public final static int opc_dup_x1 = 90; + null, // public final static int opc_dup_x2 = 91; + null, // public final static int opc_dup2 = 92; + null, // public final static int opc_dup2_x1 = 93; + null, // public final static int opc_dup2_x2 = 94; + null, // public final static int opc_swap = 95; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_iadd = 96; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_ladd = 97; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + // public final static int opc_fadd = 98; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + // public final static int opc_dadd = 99; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_isub = 100; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lsub = 101; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + // public final static int opc_fsub = 102; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + // public final static int opc_dsub = 103; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_imul = 104; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lmul = 105; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + // public final static int opc_fmul = 106; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + // public final static int opc_dmul = 107; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_idiv = 108; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_ldiv = 109; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + // public final static int opc_fdiv = 110; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + // public final static int opc_ddiv = 111; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_irem = 112; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lrem = 113; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, + // public final static int opc_frem = 114; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, + // public final static int opc_drem = 115; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_ineg = 116; + {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, // public final static int opc_lneg = 117; + {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_fneg = 118; + {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_dneg = 119; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_ishl = 120; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lshl = 121; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_ishr = 122; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lshr = 123; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_iushr = 124; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lushr = 125; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_iand = 126; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_land = 127; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_ior = 128; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lor = 129; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_ixor = 130; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_LONG}}, + // public final static int opc_lxor = 131; + {null, null}, // public final static int opc_iinc = 132; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_LONG}}, // public final static int opc_i2l = 133; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_i2f = 134; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_i2d = 135; + {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_INT}}, // public final static int opc_l2i = 136; + {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_l2f = 137; + {{CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_l2d = 138; + {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_INT}}, // public final static int opc_f2i = 139; + {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_LONG}}, // public final static int opc_f2l = 140; + {{CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_DOUBLE}}, // public final static int opc_f2d = 141; + {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_INT}}, // public final static int opc_d2i = 142; + {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_LONG}}, // public final static int opc_d2l = 143; + {{CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_FLOAT}}, // public final static int opc_d2f = 144; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_i2b = 145; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_i2c = 146; + {{CodeConstants.TYPE_INT}, {CodeConstants.TYPE_INT}}, // public final static int opc_i2s = 147; + {{CodeConstants.TYPE_LONG, CodeConstants.TYPE_LONG}, {CodeConstants.TYPE_INT}}, + // public final static int opc_lcmp = 148; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_fcmpl = 149; + {{CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_FLOAT}, {CodeConstants.TYPE_INT}}, + // public final static int opc_fcmpg = 150; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_INT}}, + // public final static int opc_dcmpl = 151; + {{CodeConstants.TYPE_DOUBLE, CodeConstants.TYPE_DOUBLE}, {CodeConstants.TYPE_INT}}, + // public final static int opc_dcmpg = 152; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifeq = 153; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifne = 154; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_iflt = 155; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifge = 156; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifgt = 157; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ifle = 158; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpeq = 159; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpne = 160; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmplt = 161; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpge = 162; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmpgt = 163; + {{CodeConstants.TYPE_INT, CodeConstants.TYPE_INT}, null}, // public final static int opc_if_icmple = 164; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_OBJECT}, null}, + // public final static int opc_if_acmpeq = 165; + {{CodeConstants.TYPE_OBJECT, CodeConstants.TYPE_OBJECT}, null}, + // public final static int opc_if_acmpne = 166; + {null, null}, // public final static int opc_goto = 167; + {null, {CodeConstants.TYPE_ADDRESS}}, // public final static int opc_jsr = 168; + {null, null}, // public final static int opc_ret = 169; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_tableswitch = 170; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_lookupswitch = 171; + {{CodeConstants.TYPE_INT}, null}, // public final static int opc_ireturn = 172; + {{CodeConstants.TYPE_LONG}, null}, // public final static int opc_lreturn = 173; + {{CodeConstants.TYPE_FLOAT}, null}, // public final static int opc_freturn = 174; + {{CodeConstants.TYPE_DOUBLE}, null}, // public final static int opc_dreturn = 175; + {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_areturn = 176; + {null, null}, // public final static int opc_return = 177; + null, // public final static int opc_getstatic = 178; + null, // public final static int opc_putstatic = 179; + null, // public final static int opc_getfield = 180; + null, // public final static int opc_putfield = 181; + null, // public final static int opc_invokevirtual = 182; + null, // public final static int opc_invokespecial = 183; + null, // public final static int opc_invokestatic = 184; + null, // public final static int opc_invokeinterface = 185; + null, // public final static int opc_xxxunusedxxx = 186; + null, // public final static int opc_new = 187; + null, // public final static int opc_newarray = 188; + null, // public final static int opc_anewarray = 189; + {{CodeConstants.TYPE_OBJECT}, {CodeConstants.TYPE_INT}}, // public final static int opc_arraylength = 190; + null, + // public final static int opc_athrow = 191; + null, + // public final static int opc_checkcast = 192; + null, + // public final static int opc_instanceof = 193; + {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_monitorenter = 194; + {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_monitorexit = 195; + null, + // public final static int opc_wide = 196; + null, + // public final static int opc_multianewarray = 197; + {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_ifnull = 198; + {{CodeConstants.TYPE_OBJECT}, null}, // public final static int opc_ifnonnull = 199; + {null, null}, // public final static int opc_goto_w = 200; + {null, {CodeConstants.TYPE_ADDRESS}}, // public final static int opc_jsr_w = 201; + }; + + private static final int[] arr_type = new int[]{ + CodeConstants.TYPE_BOOLEAN, + CodeConstants.TYPE_CHAR, + CodeConstants.TYPE_FLOAT, + CodeConstants.TYPE_DOUBLE, + CodeConstants.TYPE_BYTE, + CodeConstants.TYPE_SHORT, + CodeConstants.TYPE_INT, + CodeConstants.TYPE_LONG + }; + + + // Sonderbehandlung + // null, // public final static int opc_aconst_null = 1; + // null, // public final static int opc_ldc = 18; + // null, // public final static int opc_ldc_w = 19; + // null, // public final static int opc_ldc2_w = 20; + // null, // public final static int opc_aload = 25; + // null, // public final static int opc_aaload = 50; + // null, // public final static int opc_astore = 58; + // null, // public final static int opc_dup = 89; + // null, // public final static int opc_dup_x1 = 90; + // null, // public final static int opc_dup_x2 = 91; + // null, // public final static int opc_dup2 = 92; + // null, // public final static int opc_dup2_x1 = 93; + // null, // public final static int opc_dup2_x2 = 94; + // null, // public final static int opc_swap = 95; + // null, // public final static int opc_getstatic = 178; + // null, // public final static int opc_putstatic = 179; + // null, // public final static int opc_getfield = 180; + // null, // public final static int opc_putfield = 181; + // null, // public final static int opc_invokevirtual = 182; + // null, // public final static int opc_invokespecial = 183; + // null, // public final static int opc_invokestatic = 184; + // null, // public final static int opc_invokeinterface = 185; + // null, // public final static int opc_new = 187; + // null, // public final static int opc_newarray = 188; + // null, // public final static int opc_anewarray = 189; + // null, // public final static int opc_athrow = 191; + // null, // public final static int opc_checkcast = 192; + // null, // public final static int opc_instanceof = 193; + // null, // public final static int opc_multianewarray = 197; + + + public static void stepTypes(DataPoint data, Instruction instr, ConstantPool pool) { + ListStack stack = data.getStack(); + int[][] arr = stack_impact[instr.opcode]; + + if (arr != null) { + // simple types only + + int[] read = arr[0]; + int[] write = arr[1]; + + if (read != null) { + int depth = 0; + for (int type : read) { + depth++; + if (type == CodeConstants.TYPE_LONG || + type == CodeConstants.TYPE_DOUBLE) { + depth++; + } + } + + stack.removeMultiple(depth); + } + + if (write != null) { + for (int type : write) { + stack.push(new VarType(type)); + if (type == CodeConstants.TYPE_LONG || + type == CodeConstants.TYPE_DOUBLE) { + stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + } + } + } + } else { + // Sonderbehandlung + processSpecialInstructions(data, instr, pool); + } + } + + private static void processSpecialInstructions(DataPoint data, Instruction instr, ConstantPool pool) { + + VarType var1; + PrimitiveConstant cn; + LinkConstant ck; + + ListStack stack = data.getStack(); + + switch (instr.opcode) { + case CodeConstants.opc_aconst_null: + stack.push(new VarType(CodeConstants.TYPE_NULL, 0, null)); + break; + case CodeConstants.opc_ldc: + case CodeConstants.opc_ldc_w: + case CodeConstants.opc_ldc2_w: + PooledConstant constant = pool.getConstant(instr.operand(0)); + switch (constant.type) { + case CodeConstants.CONSTANT_Integer: + stack.push(new VarType(CodeConstants.TYPE_INT)); + break; + case CodeConstants.CONSTANT_Float: + stack.push(new VarType(CodeConstants.TYPE_FLOAT)); + break; + case CodeConstants.CONSTANT_Long: + stack.push(new VarType(CodeConstants.TYPE_LONG)); + stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + break; + case CodeConstants.CONSTANT_Double: + stack.push(new VarType(CodeConstants.TYPE_DOUBLE)); + stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + break; + case CodeConstants.CONSTANT_String: + stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/String")); + break; + case CodeConstants.CONSTANT_Class: + stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/Class")); + break; + case CodeConstants.CONSTANT_MethodHandle: + stack.push(new VarType(((LinkConstant) constant).descriptor)); + break; + } + break; + case CodeConstants.opc_aload: + var1 = data.getVariable(instr.operand(0)); + if (var1 != null) { + stack.push(var1); + } else { + stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, null)); + } + break; + case CodeConstants.opc_aaload: + var1 = stack.pop(2); + stack.push(new VarType(var1.type, var1.arrayDim - 1, var1.value)); + break; + case CodeConstants.opc_astore: + data.setVariable(instr.operand(0), stack.pop()); + break; + case CodeConstants.opc_dup: + case CodeConstants.opc_dup_x1: + case CodeConstants.opc_dup_x2: + int depth1 = 88 - instr.opcode; + stack.insertByOffset(depth1, stack.getByOffset(-1).copy()); + break; + case CodeConstants.opc_dup2: + case CodeConstants.opc_dup2_x1: + case CodeConstants.opc_dup2_x2: + int depth2 = 90 - instr.opcode; + stack.insertByOffset(depth2, stack.getByOffset(-2).copy()); + stack.insertByOffset(depth2, stack.getByOffset(-1).copy()); + break; + case CodeConstants.opc_swap: + var1 = stack.pop(); + stack.insertByOffset(-1, var1); + break; + case CodeConstants.opc_getfield: + stack.pop(); + case CodeConstants.opc_getstatic: + ck = pool.getLinkConstant(instr.operand(0)); + var1 = new VarType(ck.descriptor); + stack.push(var1); + if (var1.stackSize == 2) { + stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + } + break; + case CodeConstants.opc_putfield: + stack.pop(); + case CodeConstants.opc_putstatic: + ck = pool.getLinkConstant(instr.operand(0)); + var1 = new VarType(ck.descriptor); + stack.pop(var1.stackSize); + break; + case CodeConstants.opc_invokevirtual: + case CodeConstants.opc_invokespecial: + case CodeConstants.opc_invokeinterface: + stack.pop(); + case CodeConstants.opc_invokestatic: + case CodeConstants.opc_invokedynamic: + if (instr.opcode != CodeConstants.opc_invokedynamic || instr.bytecodeVersion >= CodeConstants.BYTECODE_JAVA_7) { + ck = pool.getLinkConstant(instr.operand(0)); + MethodDescriptor md = MethodDescriptor.parseDescriptor(ck.descriptor); + for (int i = 0; i < md.params.length; i++) { + stack.pop(md.params[i].stackSize); + } + if (md.ret.type != CodeConstants.TYPE_VOID) { + stack.push(md.ret); + if (md.ret.stackSize == 2) { + stack.push(new VarType(CodeConstants.TYPE_GROUP2EMPTY)); + } + } + } + break; + case CodeConstants.opc_new: + cn = pool.getPrimitiveConstant(instr.operand(0)); + stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, cn.getString())); + break; + case CodeConstants.opc_newarray: + stack.pop(); + stack.push(new VarType(arr_type[instr.operand(0) - 4], 1).resizeArrayDim(1)); + break; + case CodeConstants.opc_athrow: + var1 = stack.pop(); + stack.clear(); + stack.push(var1); + break; + case CodeConstants.opc_checkcast: + case CodeConstants.opc_instanceof: + stack.pop(); + cn = pool.getPrimitiveConstant(instr.operand(0)); + stack.push(new VarType(CodeConstants.TYPE_OBJECT, 0, cn.getString())); + break; + case CodeConstants.opc_anewarray: + case CodeConstants.opc_multianewarray: + int dimensions = (instr.opcode == CodeConstants.opc_anewarray) ? 1 : instr.operand(1); + stack.pop(dimensions); + cn = pool.getPrimitiveConstant(instr.operand(0)); + if (cn.isArray) { + var1 = new VarType(CodeConstants.TYPE_OBJECT, 0, cn.getString()); + var1 = var1.resizeArrayDim(var1.arrayDim + dimensions); + stack.push(var1); + } else { + stack.push(new VarType(CodeConstants.TYPE_OBJECT, dimensions, cn.getString())); + } + } + } +} diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/AssertProcessor.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/AssertProcessor.java new file mode 100644 index 000000000..22428e44b --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/AssertProcessor.java @@ -0,0 +1,339 @@ +// 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.main; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.cfg.BasicBlock; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.SecondaryFunctionsHelper; +import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssertExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExitExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.IfStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.SequenceStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AssertProcessor { + + private static final VarType CLASS_ASSERTION_ERROR = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/AssertionError"); + + public static void buildAssertions(ClassNode node) { + + ClassWrapper wrapper = node.getWrapper(); + + StructField field = findAssertionField(node); + + if (field != null) { + + String key = InterpreterUtil.makeUniqueKey(field.getName(), field.getDescriptor()); + + boolean res = false; + + for (MethodWrapper meth : wrapper.getMethods()) { + RootStatement root = meth.root; + if (root != null) { + res |= replaceAssertions(root, wrapper.getClassStruct().qualifiedName, key); + } + } + + if (res) { + // hide the helper field + wrapper.getHiddenMembers().add(key); + } + } + } + + private static StructField findAssertionField(ClassNode node) { + + ClassWrapper wrapper = node.getWrapper(); + + boolean noSynthFlag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + for (StructField fd : wrapper.getClassStruct().getFields()) { + + String keyField = InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()); + + // initializer exists + if (wrapper.getStaticFieldInitializers().containsKey(keyField)) { + + // access flags set + if (fd.hasModifier(CodeConstants.ACC_STATIC) && fd.hasModifier(CodeConstants.ACC_FINAL) && (noSynthFlag || fd.isSynthetic())) { + + // field type boolean + FieldDescriptor fdescr = FieldDescriptor.parseDescriptor(fd.getDescriptor()); + if (VarType.VARTYPE_BOOLEAN.equals(fdescr.type)) { + + Exprent initializer = wrapper.getStaticFieldInitializers().getWithKey(keyField); + if (initializer.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fexpr = (FunctionExprent) initializer; + + if (fexpr.getFuncType() == FunctionExprent.FUNCTION_BOOL_NOT && + fexpr.getLstOperands().get(0).type == Exprent.EXPRENT_INVOCATION) { + + InvocationExprent invexpr = (InvocationExprent) fexpr.getLstOperands().get(0); + + if (invexpr.getInstance() != null && + invexpr.getInstance().type == Exprent.EXPRENT_CONST && + "desiredAssertionStatus".equals(invexpr.getName()) && + "java/lang/Class".equals(invexpr.getClassname()) && + invexpr.getLstParameters().isEmpty()) { + + ConstExprent cexpr = (ConstExprent) invexpr.getInstance(); + if (VarType.VARTYPE_CLASS.equals(cexpr.getConstType())) { + + ClassNode nd = node; + while (nd != null) { + if (nd.getWrapper().getClassStruct().qualifiedName.equals(cexpr.getValue())) { + break; + } + nd = nd.parent; + } + + if (nd != null) { // found enclosing class with the same name + return fd; + } + } + } + } + } + } + } + } + } + + + return null; + } + + + private static boolean replaceAssertions(Statement statement, String classname, String key) { + + boolean res = false; + + for (Statement st : statement.getStats()) { + res |= replaceAssertions(st, classname, key); + } + + boolean replaced = true; + while (replaced) { + replaced = false; + + for (Statement st : statement.getStats()) { + if (st.type == Statement.TYPE_IF) { + if (replaceAssertion(statement, (IfStatement) st, classname, key)) { + replaced = true; + break; + } + } + } + + res |= replaced; + } + + return res; + } + + private static boolean replaceAssertion(Statement parent, IfStatement stat, String classname, String key) { + + boolean throwInIf = true; + Statement ifstat = stat.getIfstat(); + InvocationExprent throwError = isAssertionError(ifstat); + + if (throwError == null) { + //check else: + Statement elsestat = stat.getElsestat(); + throwError = isAssertionError(elsestat); + if (throwError == null) { + return false; + } else { + throwInIf = false; + } + } + + + Object[] exprres = getAssertionExprent(stat.getHeadexprent().getCondition().copy(), classname, key, throwInIf); + if (!(Boolean) exprres[1]) { + return false; + } + + List lstParams = new ArrayList<>(); + + Exprent ascond = null, retcond = null; + if (throwInIf) { + if (exprres[0] != null) { + ascond = new FunctionExprent(FunctionExprent.FUNCTION_BOOL_NOT, (Exprent) exprres[0], throwError.bytecode); + retcond = SecondaryFunctionsHelper.propagateBoolNot(ascond); + } + } else { + ascond = (Exprent) exprres[0]; + retcond = ascond; + } + + + lstParams.add(retcond == null ? ascond : retcond); + if (!throwError.getLstParameters().isEmpty()) { + lstParams.add(throwError.getLstParameters().get(0)); + } + + AssertExprent asexpr = new AssertExprent(lstParams); + + Statement newstat = new BasicBlockStatement(new BasicBlock( + DecompilerContext.getCounterContainer().getCounterAndIncrement(CounterContainer.STATEMENT_COUNTER))); + newstat.setExprents(Arrays.asList(new Exprent[]{asexpr})); + + Statement first = stat.getFirst(); + + if (stat.iftype == IfStatement.IFTYPE_IFELSE || (first.getExprents() != null && + !first.getExprents().isEmpty())) { + + first.removeSuccessor(stat.getIfEdge()); + first.removeSuccessor(stat.getElseEdge()); + + List lstStatements = new ArrayList<>(); + if (first.getExprents() != null && !first.getExprents().isEmpty()) { + lstStatements.add(first); + } + lstStatements.add(newstat); + if (stat.iftype == IfStatement.IFTYPE_IFELSE) { + if (throwInIf) { + lstStatements.add(stat.getElsestat()); + } else { + lstStatements.add(stat.getIfstat()); + } + } + + SequenceStatement sequence = new SequenceStatement(lstStatements); + sequence.setAllParent(); + + for (int i = 0; i < sequence.getStats().size() - 1; i++) { + sequence.getStats().get(i).addSuccessor(new StatEdge(StatEdge.TYPE_REGULAR, + sequence.getStats().get(i), sequence.getStats().get(i + 1))); + } + + if (stat.iftype == IfStatement.IFTYPE_IFELSE || !throwInIf) { + Statement stmts; + if (throwInIf) { + stmts = stat.getElsestat(); + } else { + stmts = stat.getIfstat(); + } + + List lstSuccs = stmts.getAllSuccessorEdges(); + if (!lstSuccs.isEmpty()) { + StatEdge endedge = lstSuccs.get(0); + if (endedge.closure == stat) { + sequence.addLabeledEdge(endedge); + } + } + } + + newstat = sequence; + } + + newstat.getVarDefinitions().addAll(stat.getVarDefinitions()); + parent.replaceStatement(stat, newstat); + + return true; + } + + private static InvocationExprent isAssertionError(Statement stat) { + + if (stat == null || stat.getExprents() == null || stat.getExprents().size() != 1) { + return null; + } + + Exprent expr = stat.getExprents().get(0); + + if (expr.type == Exprent.EXPRENT_EXIT) { + ExitExprent exexpr = (ExitExprent) expr; + if (exexpr.getExitType() == ExitExprent.EXIT_THROW && exexpr.getValue().type == Exprent.EXPRENT_NEW) { + NewExprent nexpr = (NewExprent) exexpr.getValue(); + if (CLASS_ASSERTION_ERROR.equals(nexpr.getNewType()) && nexpr.getConstructor() != null) { + return nexpr.getConstructor(); + } + } + } + + return null; + } + + private static Object[] getAssertionExprent(Exprent exprent, String classname, String key, boolean throwInIf) { + + if (exprent.type == Exprent.EXPRENT_FUNCTION) { + int desiredOperation = FunctionExprent.FUNCTION_CADD; + if (!throwInIf) { + desiredOperation = FunctionExprent.FUNCTION_COR; + } + + FunctionExprent fexpr = (FunctionExprent) exprent; + if (fexpr.getFuncType() == desiredOperation) { + + for (int i = 0; i < 2; i++) { + Exprent param = fexpr.getLstOperands().get(i); + + if (isAssertionField(param, classname, key, throwInIf)) { + return new Object[]{fexpr.getLstOperands().get(1 - i), true}; + } + } + + for (int i = 0; i < 2; i++) { + Exprent param = fexpr.getLstOperands().get(i); + + Object[] res = getAssertionExprent(param, classname, key, throwInIf); + if ((Boolean) res[1]) { + if (param != res[0]) { + fexpr.getLstOperands().set(i, (Exprent) res[0]); + } + return new Object[]{fexpr, true}; + } + } + } else if (isAssertionField(fexpr, classname, key, throwInIf)) { + // assert false; + return new Object[]{null, true}; + } + } + + return new Object[]{exprent, false}; + } + + private static boolean isAssertionField(Exprent exprent, String classname, String key, boolean throwInIf) { + if (throwInIf) { + if (exprent.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fparam = (FunctionExprent) exprent; + if (fparam.getFuncType() == FunctionExprent.FUNCTION_BOOL_NOT && + fparam.getLstOperands().get(0).type == Exprent.EXPRENT_FIELD) { + FieldExprent fdparam = (FieldExprent) fparam.getLstOperands().get(0); + return classname.equals(fdparam.getClassname()) && + key.equals(InterpreterUtil.makeUniqueKey(fdparam.getName(), fdparam.getDescriptor().descriptorString)); + } + } + return false; + } else { + if (exprent.type == Exprent.EXPRENT_FIELD) { + FieldExprent fdparam = (FieldExprent) exprent; + return classname.equals(fdparam.getClassname()) && + key.equals(InterpreterUtil.makeUniqueKey(fdparam.getName(), fdparam.getDescriptor().descriptorString)); + } + return false; + } + } +} diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassReference14Processor.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassReference14Processor.java new file mode 100644 index 000000000..80093d7d4 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassReference14Processor.java @@ -0,0 +1,257 @@ +// 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.main; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExitExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.CatchStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class ClassReference14Processor { + private static final ExitExprent BODY_EXPR; + private static final ExitExprent HANDLER_EXPR; + + static { + InvocationExprent invFor = new InvocationExprent(); + invFor.setName("forName"); + invFor.setClassname("java/lang/Class"); + invFor.setStringDescriptor("(Ljava/lang/String;)Ljava/lang/Class;"); + invFor.setDescriptor(MethodDescriptor.parseDescriptor("(Ljava/lang/String;)Ljava/lang/Class;")); + invFor.setStatic(true); + invFor.setLstParameters(Collections.singletonList(new VarExprent(0, VarType.VARTYPE_STRING, null))); + BODY_EXPR = new ExitExprent(ExitExprent.EXIT_RETURN, invFor, VarType.VARTYPE_CLASS, null); + + InvocationExprent ctor = new InvocationExprent(); + ctor.setName(CodeConstants.INIT_NAME); + ctor.setClassname("java/lang/NoClassDefFoundError"); + ctor.setStringDescriptor("()V"); + ctor.setFunctype(InvocationExprent.TYP_INIT); + ctor.setDescriptor(MethodDescriptor.parseDescriptor("()V")); + NewExprent newExpr = new NewExprent(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/NoClassDefFoundError"), + new ArrayList(), null); + newExpr.setConstructor(ctor); + InvocationExprent invCause = new InvocationExprent(); + invCause.setName("initCause"); + invCause.setClassname("java/lang/NoClassDefFoundError"); + invCause.setStringDescriptor("(Ljava/lang/Throwable;)Ljava/lang/Throwable;"); + invCause.setDescriptor(MethodDescriptor.parseDescriptor("(Ljava/lang/Throwable;)Ljava/lang/Throwable;")); + invCause.setInstance(newExpr); + invCause.setLstParameters( + Collections.singletonList( + new VarExprent(2, new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/ClassNotFoundException"), + null))); + HANDLER_EXPR = new ExitExprent(ExitExprent.EXIT_THROW, invCause, null, null); + } + + public static void processClassReferences(ClassNode node) { + // find the synthetic method Class class$(String) if present + Map mapClassMeths = new HashMap<>(); + mapClassMethods(node, mapClassMeths); + if (mapClassMeths.isEmpty()) { + return; + } + + Set setFound = new HashSet<>(); + processClassRec(node, mapClassMeths, setFound); + + if (!setFound.isEmpty()) { + for (ClassWrapper wrp : setFound) { + StructMethod mt = mapClassMeths.get(wrp).methodStruct; + wrp.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + } + } + } + + private static void processClassRec(ClassNode node, final Map mapClassMeths, final Set setFound) { + ClassWrapper wrapper = node.getWrapper(); + + // search code + for (MethodWrapper meth : wrapper.getMethods()) { + RootStatement root = meth.root; + if (root != null) { + DirectGraph graph = meth.getOrBuildGraph(); + graph.iterateExprents(new DirectGraph.ExprentIterator() { + @Override + public int processExprent(Exprent exprent) { + for (Entry ent : mapClassMeths.entrySet()) { + if (replaceInvocations(exprent, ent.getKey(), ent.getValue())) { + setFound.add(ent.getKey()); + } + } + return 0; + } + }); + } + } + + // search initializers + for (int j = 0; j < 2; j++) { + VBStyleCollection initializers = + j == 0 ? wrapper.getStaticFieldInitializers() : wrapper.getDynamicFieldInitializers(); + + for (int i = 0; i < initializers.size(); i++) { + for (Entry ent : mapClassMeths.entrySet()) { + Exprent exprent = initializers.get(i); + if (replaceInvocations(exprent, ent.getKey(), ent.getValue())) { + setFound.add(ent.getKey()); + } + + String cl = isClass14Invocation(exprent, ent.getKey(), ent.getValue()); + if (cl != null) { + initializers.set(i, new ConstExprent(VarType.VARTYPE_CLASS, cl.replace('.', '/'), exprent.bytecode)); + setFound.add(ent.getKey()); + } + } + } + } + + // iterate nested classes + for (ClassNode nd : node.nested) { + processClassRec(nd, mapClassMeths, setFound); + } + } + + private static void mapClassMethods(ClassNode node, Map map) { + boolean noSynthFlag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + ClassWrapper wrapper = node.getWrapper(); + + for (MethodWrapper method : wrapper.getMethods()) { + StructMethod mt = method.methodStruct; + + if ((noSynthFlag || mt.isSynthetic()) && + mt.getDescriptor().equals("(Ljava/lang/String;)Ljava/lang/Class;") && + mt.hasModifier(CodeConstants.ACC_STATIC)) { + + RootStatement root = method.root; + if (root != null && root.getFirst().type == Statement.TYPE_TRYCATCH) { + CatchStatement cst = (CatchStatement) root.getFirst(); + if (cst.getStats().size() == 2 && cst.getFirst().type == Statement.TYPE_BASICBLOCK && + cst.getStats().get(1).type == Statement.TYPE_BASICBLOCK && + cst.getVars().get(0).getVarType().equals(new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/ClassNotFoundException"))) { + + BasicBlockStatement body = (BasicBlockStatement) cst.getFirst(); + BasicBlockStatement handler = (BasicBlockStatement) cst.getStats().get(1); + + if (body.getExprents().size() == 1 && handler.getExprents().size() == 1) { + if (BODY_EXPR.equals(body.getExprents().get(0)) && + HANDLER_EXPR.equals(handler.getExprents().get(0))) { + map.put(wrapper, method); + break; + } + } + } + } + } + } + + // iterate nested classes + for (ClassNode nd : node.nested) { + mapClassMethods(nd, map); + } + } + + private static boolean replaceInvocations(Exprent exprent, ClassWrapper wrapper, MethodWrapper meth) { + boolean res = false; + + while (true) { + boolean found = false; + + for (Exprent expr : exprent.getAllExprents()) { + String cl = isClass14Invocation(expr, wrapper, meth); + if (cl != null) { + exprent.replaceExprent(expr, new ConstExprent(VarType.VARTYPE_CLASS, cl.replace('.', '/'), expr.bytecode)); + found = true; + res = true; + break; + } + + res |= replaceInvocations(expr, wrapper, meth); + } + + if (!found) { + break; + } + } + + return res; + } + + private static String isClass14Invocation(Exprent exprent, ClassWrapper wrapper, MethodWrapper meth) { + if (exprent.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fexpr = (FunctionExprent) exprent; + if (fexpr.getFuncType() == FunctionExprent.FUNCTION_IIF) { + if (fexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent headexpr = (FunctionExprent) fexpr.getLstOperands().get(0); + if (headexpr.getFuncType() == FunctionExprent.FUNCTION_EQ) { + if (headexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FIELD && + headexpr.getLstOperands().get(1).type == Exprent.EXPRENT_CONST && + ((ConstExprent) headexpr.getLstOperands().get(1)).getConstType().equals(VarType.VARTYPE_NULL)) { + + FieldExprent field = (FieldExprent) headexpr.getLstOperands().get(0); + ClassNode fieldnode = DecompilerContext.getClassProcessor().getMapRootClasses().get(field.getClassname()); + + if (fieldnode != null && fieldnode.classStruct.qualifiedName.equals(wrapper.getClassStruct().qualifiedName)) { // source class + StructField fd = + wrapper.getClassStruct().getField(field.getName(), field.getDescriptor().descriptorString); // FIXME: can be null! why?? + + if (fd != null && fd.hasModifier(CodeConstants.ACC_STATIC) && + (fd.isSynthetic() || DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET))) { + + if (fexpr.getLstOperands().get(1).type == Exprent.EXPRENT_ASSIGNMENT && fexpr.getLstOperands().get(2).equals(field)) { + AssignmentExprent asexpr = (AssignmentExprent) fexpr.getLstOperands().get(1); + + if (asexpr.getLeft().equals(field) && asexpr.getRight().type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invexpr = (InvocationExprent) asexpr.getRight(); + + if (invexpr.getClassname().equals(wrapper.getClassStruct().qualifiedName) && + invexpr.getName().equals(meth.methodStruct.getName()) && + invexpr.getStringDescriptor().equals(meth.methodStruct.getDescriptor())) { + + if (invexpr.getLstParameters().get(0).type == Exprent.EXPRENT_CONST) { + wrapper.getHiddenMembers() + .add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); // hide synthetic field + return ((ConstExprent) invexpr.getLstParameters().get(0)).getValue().toString(); + } + } + } + } + } + } + } + } + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassWriter.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassWriter.java new file mode 100644 index 000000000..06c4824ba --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -0,0 +1,1074 @@ +// Copyright 2000-2018 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.main; + +import com.duy.java8.util.DList; +import com.duy.java8.util.function.Predicate; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.TypeAnnotation; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; +import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMember; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructAnnDefaultAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructAnnotationAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructAnnotationParameterAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructConstantValueAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructExceptionsAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGenericSignatureAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructLineNumberTableAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructMethodParametersAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructTypeAnnotationAttribute; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; +import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericMain; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.TextBuffer; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ClassWriter { + private static final String[] ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS}; + private static final String[] PARAMETER_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; + private static final String[] TYPE_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS}; + private static final Map MODIFIERS; + private static final int CLASS_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT | + CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_STRICT; + private static final int FIELD_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_STATIC | + CodeConstants.ACC_FINAL | CodeConstants.ACC_TRANSIENT | CodeConstants.ACC_VOLATILE; + private static final int METHOD_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT | + CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_SYNCHRONIZED | CodeConstants.ACC_NATIVE | + CodeConstants.ACC_STRICT; + private static final int CLASS_EXCLUDED = CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_STATIC; + private static final int FIELD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL; + private static final int METHOD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_ABSTRACT; + + static { + MODIFIERS = new LinkedHashMap<>(); + MODIFIERS.put(CodeConstants.ACC_PUBLIC, "public"); + MODIFIERS.put(CodeConstants.ACC_PROTECTED, "protected"); + MODIFIERS.put(CodeConstants.ACC_PRIVATE, "private"); + MODIFIERS.put(CodeConstants.ACC_ABSTRACT, "abstract"); + MODIFIERS.put(CodeConstants.ACC_STATIC, "static"); + MODIFIERS.put(CodeConstants.ACC_FINAL, "final"); + MODIFIERS.put(CodeConstants.ACC_STRICT, "strictfp"); + MODIFIERS.put(CodeConstants.ACC_TRANSIENT, "transient"); + MODIFIERS.put(CodeConstants.ACC_VOLATILE, "volatile"); + MODIFIERS.put(CodeConstants.ACC_SYNCHRONIZED, "synchronized"); + MODIFIERS.put(CodeConstants.ACC_NATIVE, "native"); + } + + private final PoolInterceptor interceptor; + + public ClassWriter() { + interceptor = DecompilerContext.getPoolInterceptor(); + } + + private static void invokeProcessors(ClassNode node) { + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + InitializerProcessor.extractInitializers(wrapper); + + if (node.type == ClassNode.CLASS_ROOT && + !cl.isVersionGE_1_5() && + DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_CLASS_1_4)) { + ClassReference14Processor.processClassReferences(node); + } + + if (cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) { + EnumProcessor.clearEnum(wrapper); + } + + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) { + AssertProcessor.buildAssertions(node); + } + } + + private static void addTracer(StructClass cls, StructMethod method, BytecodeMappingTracer tracer) { + StructLineNumberTableAttribute table = + (StructLineNumberTableAttribute) method.getAttribute(StructGeneralAttribute.ATTRIBUTE_LINE_NUMBER_TABLE); + tracer.setLineNumberTable(table); + String key = InterpreterUtil.makeUniqueKey(method.getName(), method.getDescriptor()); + DecompilerContext.getBytecodeSourceMapper().addTracer(cls.qualifiedName, key, tracer); + } + + private static void methodLambdaToJava(ClassNode lambdaNode, + ClassWrapper classWrapper, + StructMethod mt, + TextBuffer buffer, + int indent, + boolean codeOnly, BytecodeMappingTracer tracer) { + MethodWrapper methodWrapper = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + MethodWrapper outerWrapper = (MethodWrapper) DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); + + try { + String method_name = lambdaNode.lambdaInformation.method_name; + MethodDescriptor md_content = MethodDescriptor.parseDescriptor(lambdaNode.lambdaInformation.content_method_descriptor); + MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(lambdaNode.lambdaInformation.method_descriptor); + + if (!codeOnly) { + buffer.appendIndent(indent); + buffer.append("public "); + buffer.append(method_name); + buffer.append("("); + + boolean firstParameter = true; + int index = lambdaNode.lambdaInformation.is_content_method_static ? 0 : 1; + int start_index = md_content.params.length - md_lambda.params.length; + + for (int i = 0; i < md_content.params.length; i++) { + if (i >= start_index) { + if (!firstParameter) { + buffer.append(", "); + } + + String typeName = ExprProcessor.getCastTypeName(md_content.params[i].copy()); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + buffer.append(typeName); + buffer.append(" "); + + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + + firstParameter = false; + } + + index += md_content.params[i].stackSize; + } + + buffer.append(") {").appendLineSeparator(); + + indent += 1; + } + + RootStatement root = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + if (!methodWrapper.decompiledWithErrors) { + if (root != null) { // check for existence + try { + buffer.append(root.toJava(indent, tracer)); + } catch (Throwable t) { + String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written."; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); + methodWrapper.decompiledWithErrors = true; + } + } + } + + if (methodWrapper.decompiledWithErrors) { + buffer.appendIndent(indent); + buffer.append("// $FF: Couldn't be decompiled"); + buffer.appendLineSeparator(); + } + + if (root != null) { + tracer.addMapping(root.getDummyExit().bytecode); + } + + if (!codeOnly) { + indent -= 1; + buffer.appendIndent(indent).append('}').appendLineSeparator(); + } + } finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + } + } + + private static String toValidJavaIdentifier(String name) { + if (name == null || name.isEmpty()) return name; + + boolean changed = false; + StringBuilder res = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if ((i == 0 && !Character.isJavaIdentifierStart(c)) + || (i > 0 && !Character.isJavaIdentifierPart(c))) { + changed = true; + res.append("_"); + } else { + res.append(c); + } + } + if (!changed) { + return name; + } + return res.append("/* $FF was: ").append(name).append("*/").toString(); + } + + private static boolean hideConstructor(ClassWrapper wrapper, boolean init, boolean throwsExceptions, int paramCount) { + if (!init || throwsExceptions || paramCount > 0 || !DecompilerContext.getOption(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR)) { + return false; + } + + int count = 0; + for (StructMethod mt : wrapper.getClassStruct().getMethods()) { + if (CodeConstants.INIT_NAME.equals(mt.getName())) { + if (++count > 1) { + return false; + } + } + } + + return true; + } + + private static void appendDeprecation(TextBuffer buffer, int indent) { + buffer.appendIndent(indent).append("/** @deprecated */").appendLineSeparator(); + } + + private static void appendRenameComment(TextBuffer buffer, String oldName, MType type, int indent) { + if (oldName == null) return; + + buffer.appendIndent(indent); + buffer.append("// $FF: renamed from: "); + + switch (type) { + case CLASS: + buffer.append(ExprProcessor.buildJavaClassName(oldName)); + break; + + case FIELD: + String[] fParts = oldName.split(" "); + FieldDescriptor fd = FieldDescriptor.parseDescriptor(fParts[2]); + buffer.append(fParts[1]); + buffer.append(' '); + buffer.append(getTypePrintOut(fd.type)); + break; + + default: + String[] mParts = oldName.split(" "); + MethodDescriptor md = MethodDescriptor.parseDescriptor(mParts[2]); + buffer.append(mParts[1]); + buffer.append(" ("); + boolean first = true; + for (VarType paramType : md.params) { + if (!first) { + buffer.append(", "); + } + first = false; + buffer.append(getTypePrintOut(paramType)); + } + buffer.append(") "); + buffer.append(getTypePrintOut(md.ret)); + } + + buffer.appendLineSeparator(); + } + + private static String getTypePrintOut(VarType type) { + String typeText = ExprProcessor.getCastTypeName(type, false); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeText) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeText = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, false); + } + return typeText; + } + + private static void appendComment(TextBuffer buffer, String comment, int indent) { + buffer.appendIndent(indent).append("// $FF: ").append(comment).appendLineSeparator(); + } + + private static void appendAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType) { + Set filter = new HashSet<>(); + + for (String name : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = (StructAnnotationAttribute) mb.getAttribute(name); + if (attribute != null) { + for (AnnotationExprent annotation : attribute.getAnnotations()) { + String text = annotation.toJava(indent, BytecodeMappingTracer.DUMMY).toString(); + filter.add(text); + buffer.append(text).appendLineSeparator(); + } + } + } + + appendTypeAnnotations(buffer, indent, mb, targetType, -1, filter); + } + + private static void appendParameterAnnotations(TextBuffer buffer, StructMethod mt, int param) { + Set filter = new HashSet<>(); + + for (String name : PARAMETER_ANNOTATION_ATTRIBUTES) { + StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute) mt.getAttribute(name); + if (attribute != null) { + List> annotations = attribute.getParamAnnotations(); + if (param < annotations.size()) { + for (AnnotationExprent annotation : annotations.get(param)) { + String text = annotation.toJava(-1, BytecodeMappingTracer.DUMMY).toString(); + filter.add(text); + buffer.append(text).append(' '); + } + } + } + } + + appendTypeAnnotations(buffer, -1, mt, TypeAnnotation.METHOD_PARAMETER, param, filter); + } + + private static void appendTypeAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType, int index, Set filter) { + for (String name : TYPE_ANNOTATION_ATTRIBUTES) { + StructTypeAnnotationAttribute attribute = (StructTypeAnnotationAttribute) mb.getAttribute(name); + if (attribute != null) { + for (TypeAnnotation annotation : attribute.getAnnotations()) { + if (annotation.isTopLevel() && annotation.getTargetType() == targetType && (index < 0 || annotation.getIndex() == index)) { + String text = annotation.getAnnotation().toJava(indent, BytecodeMappingTracer.DUMMY).toString(); + if (!filter.contains(text)) { + buffer.append(text); + if (indent < 0) { + buffer.append(' '); + } else { + buffer.appendLineSeparator(); + } + } + } + } + } + } + } + + private static void appendModifiers(TextBuffer buffer, int flags, int allowed, boolean isInterface, int excluded) { + flags &= allowed; + if (!isInterface) excluded = 0; + for (int modifier : MODIFIERS.keySet()) { + if ((flags & modifier) == modifier && (modifier & excluded) == 0) { + buffer.append(MODIFIERS.get(modifier)).append(' '); + } + } + } + + public static GenericClassDescriptor getGenericClassDescriptor(StructClass cl) { + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { + StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute) cl.getAttribute("Signature"); + if (attr != null) { + return GenericMain.parseClassSignature(attr.getSignature()); + } + } + return null; + } + + public static void appendTypeParameters(TextBuffer buffer, List parameters, List> bounds) { + buffer.append('<'); + + for (int i = 0; i < parameters.size(); i++) { + if (i > 0) { + buffer.append(", "); + } + + buffer.append(parameters.get(i)); + + List parameterBounds = bounds.get(i); + if (parameterBounds.size() > 1 || !"java/lang/Object".equals(parameterBounds.get(0).value)) { + buffer.append(" extends "); + buffer.append(GenericMain.getGenericCastTypeName(parameterBounds.get(0))); + for (int j = 1; j < parameterBounds.size(); j++) { + buffer.append(" & "); + buffer.append(GenericMain.getGenericCastTypeName(parameterBounds.get(j))); + } + } + } + + buffer.append('>'); + } + + public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_object, int indent, BytecodeMappingTracer origTracer) { + ClassWrapper wrapper = node.getWrapper(); + if (wrapper == null) { + return; + } + + boolean lambdaToAnonymous = DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS); + + ClassNode outerNode = (ClassNode) DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); + + BytecodeMappingTracer tracer = new BytecodeMappingTracer(origTracer.getCurrentSourceLine()); + + try { + StructClass cl = wrapper.getClassStruct(); + + DecompilerContext.getLogger().startWriteClass(node.simpleName); + + if (node.lambdaInformation.is_method_reference) { + if (!node.lambdaInformation.is_content_method_static && method_object != null) { + // reference to a virtual method + buffer.append(method_object.toJava(indent, tracer)); + } else { + // reference to a static method + buffer.append(ExprProcessor.getCastTypeName(new VarType(node.lambdaInformation.content_class_name, true))); + } + + buffer.append("::") + .append(CodeConstants.INIT_NAME.equals(node.lambdaInformation.content_method_name) ? "new" : node.lambdaInformation.content_method_name); + } else { + // lambda method + StructMethod mt = cl.getMethod(node.lambdaInformation.content_method_key); + MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + MethodDescriptor md_content = MethodDescriptor.parseDescriptor(node.lambdaInformation.content_method_descriptor); + MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(node.lambdaInformation.method_descriptor); + + if (!lambdaToAnonymous) { + buffer.append('('); + + boolean firstParameter = true; + int index = node.lambdaInformation.is_content_method_static ? 0 : 1; + int start_index = md_content.params.length - md_lambda.params.length; + + for (int i = 0; i < md_content.params.length; i++) { + if (i >= start_index) { + if (!firstParameter) { + buffer.append(", "); + } + + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + + firstParameter = false; + } + + index += md_content.params[i].stackSize; + } + + buffer.append(") ->"); + } + + buffer.append(" {").appendLineSeparator(); + tracer.incrementCurrentSourceLine(); + + methodLambdaToJava(node, wrapper, mt, buffer, indent + 1, !lambdaToAnonymous, tracer); + + buffer.appendIndent(indent).append("}"); + + addTracer(cl, mt, tracer); + } + } finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode); + } + + DecompilerContext.getLogger().endWriteClass(); + } + + public void classToJava(ClassNode node, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) { + ClassNode outerNode = (ClassNode) DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); + + int startLine = tracer != null ? tracer.getCurrentSourceLine() : 0; + BytecodeMappingTracer dummy_tracer = new BytecodeMappingTracer(startLine); + + try { + // last minute processing + invokeProcessors(node); + + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + DecompilerContext.getLogger().startWriteClass(cl.qualifiedName); + + // write class definition + int start_class_def = buffer.length(); + writeClassDefinition(node, buffer, indent); + + boolean hasContent = false; + boolean enumFields = false; + + dummy_tracer.incrementCurrentSourceLine(buffer.countLines(start_class_def)); + + for (StructField fd : cl.getFields()) { + boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + if (hide) continue; + + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + if (isEnum) { + if (enumFields) { + buffer.append(',').appendLineSeparator(); + dummy_tracer.incrementCurrentSourceLine(); + } + enumFields = true; + } else if (enumFields) { + buffer.append(';'); + buffer.appendLineSeparator(); + buffer.appendLineSeparator(); + dummy_tracer.incrementCurrentSourceLine(2); + enumFields = false; + } + + fieldToJava(wrapper, cl, fd, buffer, indent + 1, dummy_tracer); // FIXME: insert real tracer + + hasContent = true; + } + + if (enumFields) { + buffer.append(';').appendLineSeparator(); + dummy_tracer.incrementCurrentSourceLine(); + } + + // FIXME: fields don't matter at the moment + startLine += buffer.countLines(start_class_def); + + // methods + for (StructMethod mt : cl.getMethods()) { + boolean hide = mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + mt.hasModifier(CodeConstants.ACC_BRIDGE) && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE) || + wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + if (hide) continue; + + int position = buffer.length(); + int storedLine = startLine; + if (hasContent) { + buffer.appendLineSeparator(); + startLine++; + } + BytecodeMappingTracer method_tracer = new BytecodeMappingTracer(startLine); + boolean methodSkipped = !methodToJava(node, mt, buffer, indent + 1, method_tracer); + if (!methodSkipped) { + hasContent = true; + addTracer(cl, mt, method_tracer); + startLine = method_tracer.getCurrentSourceLine(); + } else { + buffer.setLength(position); + startLine = storedLine; + } + } + + // member classes + for (ClassNode inner : node.nested) { + if (inner.type == ClassNode.CLASS_MEMBER) { + StructClass innerCl = inner.classStruct; + boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic(); + boolean hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + wrapper.getHiddenMembers().contains(innerCl.qualifiedName); + if (hide) continue; + + if (hasContent) { + buffer.appendLineSeparator(); + startLine++; + } + BytecodeMappingTracer class_tracer = new BytecodeMappingTracer(startLine); + classToJava(inner, buffer, indent + 1, class_tracer); + startLine = buffer.countLines(); + + hasContent = true; + } + } + + buffer.appendIndent(indent).append('}'); + + if (node.type != ClassNode.CLASS_ANONYMOUS) { + buffer.appendLineSeparator(); + } + } finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode); + } + + DecompilerContext.getLogger().endWriteClass(); + } + + private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent) { + if (node.type == ClassNode.CLASS_ANONYMOUS) { + buffer.append(" {").appendLineSeparator(); + return; + } + + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + + int flags = node.type == ClassNode.CLASS_ROOT ? cl.getAccessFlags() : node.access; + boolean isDeprecated = cl.hasAttribute("Deprecated"); + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || cl.hasAttribute("Synthetic"); + boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; + boolean isInterface = (flags & CodeConstants.ACC_INTERFACE) != 0; + boolean isAnnotation = (flags & CodeConstants.ACC_ANNOTATION) != 0; + + if (isDeprecated) { + appendDeprecation(buffer, indent); + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName); + appendRenameComment(buffer, oldName, MType.CLASS, indent); + } + + if (isSynthetic) { + appendComment(buffer, "synthetic class", indent); + } + + appendAnnotations(buffer, indent, cl, -1); + + buffer.appendIndent(indent); + + if (isEnum) { + // remove abstract and final flags (JLS 8.9 Enums) + flags &= ~CodeConstants.ACC_ABSTRACT; + flags &= ~CodeConstants.ACC_FINAL; + } + + appendModifiers(buffer, flags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED); + + if (isEnum) { + buffer.append("enum "); + } else if (isInterface) { + if (isAnnotation) { + buffer.append('@'); + } + buffer.append("interface "); + } else { + buffer.append("class "); + } + + buffer.append(node.simpleName); + + GenericClassDescriptor descriptor = getGenericClassDescriptor(cl); + if (descriptor != null && !descriptor.fparameters.isEmpty()) { + appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds); + } + + buffer.append(' '); + + if (!isEnum && !isInterface && cl.superClass != null) { + VarType supertype = new VarType(cl.superClass.getString(), true); + if (!VarType.VARTYPE_OBJECT.equals(supertype)) { + buffer.append("extends "); + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.superclass)); + } else { + buffer.append(ExprProcessor.getCastTypeName(supertype)); + } + buffer.append(' '); + } + } + + if (!isAnnotation) { + int[] interfaces = cl.getInterfaces(); + if (interfaces.length > 0) { + buffer.append(isInterface ? "extends " : "implements "); + for (int i = 0; i < interfaces.length; i++) { + if (i > 0) { + buffer.append(", "); + } + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.superinterfaces.get(i))); + } else { + buffer.append(ExprProcessor.getCastTypeName(new VarType(cl.getInterface(i), true))); + } + } + buffer.append(' '); + } + } + + buffer.append('{').appendLineSeparator(); + } + + private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) { + int start = buffer.length(); + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); + boolean isDeprecated = fd.hasAttribute("Deprecated"); + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + + if (isDeprecated) { + appendDeprecation(buffer, indent); + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName + " " + fd.getName() + " " + fd.getDescriptor()); + appendRenameComment(buffer, oldName, MType.FIELD, indent); + } + + if (fd.isSynthetic()) { + appendComment(buffer, "synthetic field", indent); + } + + appendAnnotations(buffer, indent, fd, TypeAnnotation.FIELD); + + buffer.appendIndent(indent); + + if (!isEnum) { + appendModifiers(buffer, fd.getAccessFlags(), FIELD_ALLOWED, isInterface, FIELD_EXCLUDED); + } + + VarType fieldType = new VarType(fd.getDescriptor(), false); + + GenericFieldDescriptor descriptor = null; + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { + StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute) fd.getAttribute("Signature"); + if (attr != null) { + descriptor = GenericMain.parseFieldSignature(attr.getSignature()); + } + } + + if (!isEnum) { + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.type)); + } else { + buffer.append(ExprProcessor.getCastTypeName(fieldType)); + } + buffer.append(' '); + } + + buffer.append(fd.getName()); + + tracer.incrementCurrentSourceLine(buffer.countLines(start)); + + Exprent initializer; + if (fd.hasModifier(CodeConstants.ACC_STATIC)) { + initializer = wrapper.getStaticFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } else { + initializer = wrapper.getDynamicFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } + if (initializer != null) { + if (isEnum && initializer.type == Exprent.EXPRENT_NEW) { + NewExprent expr = (NewExprent) initializer; + expr.setEnumConst(true); + buffer.append(expr.toJava(indent, tracer)); + } else { + buffer.append(" = "); + + if (initializer.type == Exprent.EXPRENT_CONST) { + ((ConstExprent) initializer).adjustConstType(fieldType); + } + + // FIXME: special case field initializer. Can map to more than one method (constructor) and bytecode instruction + buffer.append(initializer.toJava(indent, tracer)); + } + } else if (fd.hasModifier(CodeConstants.ACC_FINAL) && fd.hasModifier(CodeConstants.ACC_STATIC)) { + StructConstantValueAttribute attr = + (StructConstantValueAttribute) fd.getAttribute(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE); + if (attr != null) { + PrimitiveConstant constant = cl.getPool().getPrimitiveConstant(attr.getIndex()); + buffer.append(" = "); + buffer.append(new ConstExprent(fieldType, constant.value, null).toJava(indent, tracer)); + } + } + + if (!isEnum) { + buffer.append(";").appendLineSeparator(); + tracer.incrementCurrentSourceLine(); + } + } + + private boolean methodToJava(ClassNode node, StructMethod mt, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) { + ClassWrapper wrapper = node.getWrapper(); + StructClass cl = wrapper.getClassStruct(); + MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + boolean hideMethod = false; + int start_index_method = buffer.length(); + + MethodWrapper outerWrapper = (MethodWrapper) DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); + + try { + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); + boolean isAnnotation = cl.hasModifier(CodeConstants.ACC_ANNOTATION); + boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + boolean isDeprecated = mt.hasAttribute("Deprecated"); + boolean clinit = false, init = false, dinit = false; + + MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); + + int flags = mt.getAccessFlags(); + if ((flags & CodeConstants.ACC_NATIVE) != 0) { + flags &= ~CodeConstants.ACC_STRICT; // compiler bug: a strictfp class sets all methods to strictfp + } + if (CodeConstants.CLINIT_NAME.equals(mt.getName())) { + flags &= CodeConstants.ACC_STATIC; // ignore all modifiers except 'static' in a static initializer + } + + if (isDeprecated) { + appendDeprecation(buffer, indent); + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName + " " + mt.getName() + " " + mt.getDescriptor()); + appendRenameComment(buffer, oldName, MType.METHOD, indent); + } + + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.hasAttribute("Synthetic"); + boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0; + if (isSynthetic) { + appendComment(buffer, "synthetic method", indent); + } + if (isBridge) { + appendComment(buffer, "bridge method", indent); + } + + appendAnnotations(buffer, indent, mt, TypeAnnotation.METHOD_RETURN_TYPE); + + buffer.appendIndent(indent); + + appendModifiers(buffer, flags, METHOD_ALLOWED, isInterface, METHOD_EXCLUDED); + + if (isInterface && !mt.hasModifier(CodeConstants.ACC_STATIC) && mt.containsCode()) { + // 'default' modifier (Java 8) + buffer.append("default "); + } + + String name = mt.getName(); + if (CodeConstants.INIT_NAME.equals(name)) { + if (node.type == ClassNode.CLASS_ANONYMOUS) { + name = ""; + dinit = true; + } else { + name = node.simpleName; + init = true; + } + } else if (CodeConstants.CLINIT_NAME.equals(name)) { + name = ""; + clinit = true; + } + + GenericMethodDescriptor descriptor = null; + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { + StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute) mt.getAttribute("Signature"); + if (attr != null) { + descriptor = GenericMain.parseMethodSignature(attr.getSignature()); + if (descriptor != null) { + long actualParams = md.params.length; + List mask = methodWrapper.synthParameters; + if (mask != null) { + actualParams = DList.filter(mask, new Predicate() { + @Override + public boolean test(VarVersionPair obj) { + return obj == null; + } + }).size(); + } else if (isEnum && init) { + actualParams -= 2; + } + if (actualParams != descriptor.parameterTypes.size()) { + String message = "Inconsistent generic signature in method " + mt.getName() + " " + mt.getDescriptor() + " in " + cl.qualifiedName; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + descriptor = null; + } + } + } + } + + boolean throwsExceptions = false; + int paramCount = 0; + + if (!clinit && !dinit) { + boolean thisVar = !mt.hasModifier(CodeConstants.ACC_STATIC); + + if (descriptor != null && !descriptor.typeParameters.isEmpty()) { + appendTypeParameters(buffer, descriptor.typeParameters, descriptor.typeParameterBounds); + buffer.append(' '); + } + + if (!init) { + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.returnType)); + } else { + buffer.append(ExprProcessor.getCastTypeName(md.ret)); + } + buffer.append(' '); + } + + buffer.append(toValidJavaIdentifier(name)); + buffer.append('('); + + List mask = methodWrapper.synthParameters; + + int lastVisibleParameterIndex = -1; + for (int i = 0; i < md.params.length; i++) { + if (mask == null || mask.get(i) == null) { + lastVisibleParameterIndex = i; + } + } + + List methodParameters = null; + if (DecompilerContext.getOption(IFernflowerPreferences.USE_METHOD_PARAMETERS)) { + StructMethodParametersAttribute attr = + (StructMethodParametersAttribute) mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_METHOD_PARAMETERS); + if (attr != null) { + methodParameters = attr.getEntries(); + } + } + + int index = isEnum && init ? 3 : thisVar ? 1 : 0; + int start = isEnum && init ? 2 : 0; + for (int i = start; i < md.params.length; i++) { + if (mask == null || mask.get(i) == null) { + if (paramCount > 0) { + buffer.append(", "); + } + + appendParameterAnnotations(buffer, mt, paramCount); + + if (methodParameters != null && i < methodParameters.size()) { + appendModifiers(buffer, methodParameters.get(i).myAccessFlags, CodeConstants.ACC_FINAL, isInterface, 0); + } else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarTypeProcessor.VAR_EXPLICIT_FINAL) { + buffer.append("final "); + } + + String typeName; + boolean isVarArg = i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS); + + if (descriptor != null) { + GenericType parameterType = descriptor.parameterTypes.get(paramCount); + isVarArg &= parameterType.arrayDim > 0; + if (isVarArg) { + parameterType = parameterType.decreaseArrayDim(); + } + typeName = GenericMain.getGenericCastTypeName(parameterType); + } else { + VarType parameterType = md.params[i]; + isVarArg &= parameterType.arrayDim > 0; + if (isVarArg) { + parameterType = parameterType.decreaseArrayDim(); + } + typeName = ExprProcessor.getCastTypeName(parameterType); + } + + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + buffer.append(typeName); + if (isVarArg) { + buffer.append("..."); + } + + buffer.append(' '); + + String parameterName; + if (methodParameters != null && i < methodParameters.size()) { + parameterName = methodParameters.get(i).myName; + } else { + parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0)); + } + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + + paramCount++; + } + + index += md.params[i].stackSize; + } + + buffer.append(')'); + + StructExceptionsAttribute attr = (StructExceptionsAttribute) mt.getAttribute("Exceptions"); + if ((descriptor != null && !descriptor.exceptionTypes.isEmpty()) || attr != null) { + throwsExceptions = true; + buffer.append(" throws "); + + for (int i = 0; i < attr.getThrowsExceptions().size(); i++) { + if (i > 0) { + buffer.append(", "); + } + if (descriptor != null && !descriptor.exceptionTypes.isEmpty()) { + GenericType type = descriptor.exceptionTypes.get(i); + buffer.append(GenericMain.getGenericCastTypeName(type)); + } else { + VarType type = new VarType(attr.getExcClassname(i, cl.getPool()), true); + buffer.append(ExprProcessor.getCastTypeName(type)); + } + } + } + } + + tracer.incrementCurrentSourceLine(buffer.countLines(start_index_method)); + + if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { // native or abstract method (explicit or interface) + if (isAnnotation) { + StructAnnDefaultAttribute attr = (StructAnnDefaultAttribute) mt.getAttribute("AnnotationDefault"); + if (attr != null) { + buffer.append(" default "); + buffer.append(attr.getDefaultValue().toJava(0, BytecodeMappingTracer.DUMMY)); + } + } + + buffer.append(';'); + buffer.appendLineSeparator(); + } else { + if (!clinit && !dinit) { + buffer.append(' '); + } + + // We do not have line information for method start, lets have it here for now + buffer.append('{').appendLineSeparator(); + tracer.incrementCurrentSourceLine(); + + RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + + if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence + try { + // to restore in case of an exception + BytecodeMappingTracer codeTracer = new BytecodeMappingTracer(tracer.getCurrentSourceLine()); + TextBuffer code = root.toJava(indent + 1, codeTracer); + + hideMethod = (clinit || dinit || hideConstructor(wrapper, init, throwsExceptions, paramCount)) && code.length() == 0; + + buffer.append(code); + + tracer.setCurrentSourceLine(codeTracer.getCurrentSourceLine()); + tracer.addTracer(codeTracer); + } catch (Throwable t) { + String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written."; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); + methodWrapper.decompiledWithErrors = true; + } + } + + if (methodWrapper.decompiledWithErrors) { + buffer.appendIndent(indent + 1); + buffer.append("// $FF: Couldn't be decompiled"); + buffer.appendLineSeparator(); + tracer.incrementCurrentSourceLine(); + } else if (root != null) { + tracer.addMapping(root.getDummyExit().bytecode); + } + buffer.appendIndent(indent).append('}').appendLineSeparator(); + } + + tracer.incrementCurrentSourceLine(); + } finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + } + + // save total lines + // TODO: optimize + //tracer.setCurrentSourceLine(buffer.countLines(start_index_method)); + + return !hideMethod; + } + + private enum MType {CLASS, FIELD, METHOD} +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassesProcessor.java new file mode 100644 index 000000000..d2fa859f2 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -0,0 +1,421 @@ +/* + * 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.main; + +import com.duy.java8.util.DMap; +import com.duy.java8.util.function.Function; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IIdentifierRenamer; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.LambdaProcessor; +import org.jetbrains.java.decompiler.main.rels.NestedClassProcessor; +import org.jetbrains.java.decompiler.main.rels.NestedMemberAccess; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructInnerClassesAttribute; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.TextBuffer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class ClassesProcessor { + public static final int AVERAGE_CLASS_SIZE = 16 * 1024; + + private final StructContext context; + private final Map mapRootClasses = new HashMap<>(); + + public ClassesProcessor(StructContext context) { + this.context = context; + } + + private static void initWrappers(ClassNode node) { + if (node.type == ClassNode.CLASS_LAMBDA) { + return; + } + + ClassWrapper wrapper = new ClassWrapper(node.classStruct); + wrapper.init(); + + node.wrapper = wrapper; + + for (ClassNode nd : node.nested) { + initWrappers(nd); + } + } + + private static void addClassnameToImport(ClassNode node, ImportCollector imp) { + if (node.simpleName != null && node.simpleName.length() > 0) { + imp.getShortName(node.type == ClassNode.CLASS_ROOT ? node.classStruct.qualifiedName : node.simpleName, false); + } + + for (ClassNode nd : node.nested) { + addClassnameToImport(nd, imp); + } + } + + private static void destroyWrappers(ClassNode node) { + node.wrapper = null; + node.classStruct.releaseResources(); + + for (ClassNode nd : node.nested) { + destroyWrappers(nd); + } + } + + public void loadClasses(IIdentifierRenamer renamer) { + Map mapInnerClasses = new HashMap<>(); + Map> mapNestedClassReferences = new HashMap<>(); + Map> mapEnclosingClassReferences = new HashMap<>(); + Map mapNewSimpleNames = new HashMap<>(); + + boolean bDecompileInner = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_INNER); + + // create class nodes + for (StructClass cl : context.getClasses().values()) { + if (cl.isOwn() && !mapRootClasses.containsKey(cl.qualifiedName)) { + if (bDecompileInner) { + StructInnerClassesAttribute inner = (StructInnerClassesAttribute) cl.getAttribute("InnerClasses"); + + if (inner != null) { + for (StructInnerClassesAttribute.Entry entry : inner.getEntries()) { + String innerName = entry.innerName; + + // original simple name + String simpleName = entry.simpleName; + String savedName = mapNewSimpleNames.get(innerName); + if (savedName != null) { + simpleName = savedName; + } else if (simpleName != null && + renamer != null && + renamer.toBeRenamed(IIdentifierRenamer.Type.ELEMENT_CLASS, simpleName, null, null)) { + simpleName = renamer.getNextClassName(innerName, simpleName); + mapNewSimpleNames.put(innerName, simpleName); + } + + Inner rec = new Inner(); + rec.simpleName = simpleName; + rec.type = entry.simpleNameIdx == 0 ? ClassNode.CLASS_ANONYMOUS : entry.outerNameIdx == 0 ? ClassNode.CLASS_LOCAL : ClassNode.CLASS_MEMBER; + rec.accessFlags = entry.accessFlags; + + // enclosing class + String enclClassName = entry.outerNameIdx != 0 ? entry.enclosingName : cl.qualifiedName; + if (enclClassName == null || innerName.equals(enclClassName)) { + continue; // invalid name or self reference + } + if (rec.type == ClassNode.CLASS_MEMBER && !innerName.equals(enclClassName + '$' + entry.simpleName)) { + continue; // not a real inner class + } + + StructClass enclosingClass = context.getClasses().get(enclClassName); + if (enclosingClass != null && enclosingClass.isOwn()) { // own classes only + Inner existingRec = mapInnerClasses.get(innerName); + if (existingRec == null) { + mapInnerClasses.put(innerName, rec); + } else if (!Inner.equal(existingRec, rec)) { + String message = "Inconsistent inner class entries for " + innerName + "!"; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + } + + // reference to the nested class + DMap.computeIfAbsent(mapNestedClassReferences, enclClassName, new Function>() { + @Override + public Set apply(String k) { + return new HashSet<>(); + } + }).add(innerName); + // reference to the enclosing class + DMap.computeIfAbsent(mapEnclosingClassReferences, innerName, new Function>() { + @Override + public Set apply(String k) { + return new HashSet<>(); + } + }).add(enclClassName); + } + } + } + } + + ClassNode node = new ClassNode(ClassNode.CLASS_ROOT, cl); + node.access = cl.getAccessFlags(); + mapRootClasses.put(cl.qualifiedName, node); + } + } + + if (bDecompileInner) { + // connect nested classes + for (Entry ent : mapRootClasses.entrySet()) { + // root class? + if (!mapInnerClasses.containsKey(ent.getKey())) { + Set setVisited = new HashSet<>(); + LinkedList stack = new LinkedList<>(); + + stack.add(ent.getKey()); + setVisited.add(ent.getKey()); + + while (!stack.isEmpty()) { + String superClass = stack.removeFirst(); + ClassNode superNode = mapRootClasses.get(superClass); + + Set setNestedClasses = mapNestedClassReferences.get(superClass); + if (setNestedClasses != null) { + StructClass scl = superNode.classStruct; + StructInnerClassesAttribute inner = (StructInnerClassesAttribute) scl.getAttribute("InnerClasses"); + + if (inner == null || inner.getEntries().isEmpty()) { + DecompilerContext.getLogger().writeMessage(superClass + " does not contain inner classes!", IFernflowerLogger.Severity.WARN); + continue; + } + + for (StructInnerClassesAttribute.Entry entry : inner.getEntries()) { + String nestedClass = entry.innerName; + if (!setNestedClasses.contains(nestedClass)) { + continue; + } + + if (!setVisited.add(nestedClass)) { + continue; + } + + ClassNode nestedNode = mapRootClasses.get(nestedClass); + if (nestedNode == null) { + DecompilerContext.getLogger().writeMessage("Nested class " + nestedClass + " missing!", IFernflowerLogger.Severity.WARN); + continue; + } + + Inner rec = mapInnerClasses.get(nestedClass); + + //if ((Integer)arr[2] == ClassNode.CLASS_MEMBER) { + // FIXME: check for consistent naming + //} + + nestedNode.simpleName = rec.simpleName; + nestedNode.type = rec.type; + nestedNode.access = rec.accessFlags; + + if (nestedNode.type == ClassNode.CLASS_ANONYMOUS) { + StructClass cl = nestedNode.classStruct; + + // remove static if anonymous class (a common compiler bug) + nestedNode.access &= ~CodeConstants.ACC_STATIC; + + int[] interfaces = cl.getInterfaces(); + + if (interfaces.length > 0) { + if (interfaces.length > 1) { + String message = "Inconsistent anonymous class definition: " + cl.qualifiedName; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + } + nestedNode.anonymousClassType = new VarType(cl.getInterface(0), true); + } else { + nestedNode.anonymousClassType = new VarType(cl.superClass.getString(), true); + } + } else if (nestedNode.type == ClassNode.CLASS_LOCAL) { + // only abstract and final are permitted (a common compiler bug) + nestedNode.access &= (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_FINAL); + } + + superNode.nested.add(nestedNode); + nestedNode.parent = superNode; + + nestedNode.enclosingClasses.addAll(mapEnclosingClassReferences.get(nestedClass)); + + stack.add(nestedClass); + } + } + } + } + } + } + } + + public void writeClass(StructClass cl, TextBuffer buffer) throws IOException { + ClassNode root = mapRootClasses.get(cl.qualifiedName); + if (root.type != ClassNode.CLASS_ROOT) { + return; + } + + DecompilerContext.getLogger().startReadingClass(cl.qualifiedName); + try { + ImportCollector importCollector = new ImportCollector(root); + DecompilerContext.startClass(importCollector); + + 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); + + new NestedClassProcessor().processClass(root, root); + + new NestedMemberAccess().propagateMemberAccess(root); + + TextBuffer classBuffer = new TextBuffer(AVERAGE_CLASS_SIZE); + new ClassWriter().classToJava(root, classBuffer, 0, null); + + int index = cl.qualifiedName.lastIndexOf("/"); + if (index >= 0) { + String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); + + buffer.append("package "); + buffer.append(packageName); + buffer.append(";"); + buffer.appendLineSeparator(); + buffer.appendLineSeparator(); + } + + int import_lines_written = importCollector.writeImports(buffer); + if (import_lines_written > 0) { + buffer.appendLineSeparator(); + } + + int offsetLines = buffer.countLines(); + + buffer.append(classBuffer); + + if (DecompilerContext.getOption(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING)) { + BytecodeSourceMapper mapper = DecompilerContext.getBytecodeSourceMapper(); + mapper.addTotalOffset(offsetLines); + if (DecompilerContext.getOption(IFernflowerPreferences.DUMP_ORIGINAL_LINES)) { + buffer.dumpOriginalLineNumbers(mapper.getOriginalLinesMapping()); + } + if (DecompilerContext.getOption(IFernflowerPreferences.UNIT_TEST_MODE)) { + buffer.appendLineSeparator(); + mapper.dumpMapping(buffer, true); + } + } + } finally { + destroyWrappers(root); + DecompilerContext.getLogger().endReadingClass(); + } + } + + public Map getMapRootClasses() { + return mapRootClasses; + } + + private static class Inner { + private String simpleName; + private int type; + private int accessFlags; + + private static boolean equal(Inner o1, Inner o2) { + return o1.type == o2.type && o1.accessFlags == o2.accessFlags && InterpreterUtil.equalObjects(o1.simpleName, o2.simpleName); + } + } + + public static class ClassNode { + public static final int CLASS_ROOT = 0; + public static final int CLASS_MEMBER = 1; + public static final int CLASS_ANONYMOUS = 2; + public static final int CLASS_LOCAL = 4; + public static final int CLASS_LAMBDA = 8; + public final StructClass classStruct; + public final Map mapFieldsToVars = new HashMap<>(); + public final List nested = new ArrayList<>(); + public final Set enclosingClasses = new HashSet<>(); + public int type; + public int access; + public String simpleName; + public String enclosingMethod; + public InvocationExprent superInvocation; + public VarType anonymousClassType; + public ClassNode parent; + public LambdaInformation lambdaInformation; + private ClassWrapper wrapper; + + public ClassNode(String content_class_name, + String content_method_name, + String content_method_descriptor, + int content_method_invocation_type, + String lambda_class_name, + String lambda_method_name, + String lambda_method_descriptor, + StructClass classStruct) { // lambda class constructor + this.type = CLASS_LAMBDA; + this.classStruct = classStruct; // 'parent' class containing the static function + + lambdaInformation = new LambdaInformation(); + + lambdaInformation.method_name = lambda_method_name; + lambdaInformation.method_descriptor = lambda_method_descriptor; + + lambdaInformation.content_class_name = content_class_name; + lambdaInformation.content_method_name = content_method_name; + lambdaInformation.content_method_descriptor = content_method_descriptor; + lambdaInformation.content_method_invocation_type = content_method_invocation_type; + + lambdaInformation.content_method_key = + InterpreterUtil.makeUniqueKey(lambdaInformation.content_method_name, lambdaInformation.content_method_descriptor); + + anonymousClassType = new VarType(lambda_class_name, true); + + boolean is_method_reference = (content_class_name != classStruct.qualifiedName); + if (!is_method_reference) { // content method in the same class, check synthetic flag + StructMethod mt = classStruct.getMethod(content_method_name, content_method_descriptor); + is_method_reference = !mt.isSynthetic(); // if not synthetic -> method reference + } + + lambdaInformation.is_method_reference = is_method_reference; + lambdaInformation.is_content_method_static = + (lambdaInformation.content_method_invocation_type == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic); // FIXME: redundant? + } + + public ClassNode(int type, StructClass classStruct) { + this.type = type; + this.classStruct = classStruct; + + simpleName = classStruct.qualifiedName.substring(classStruct.qualifiedName.lastIndexOf('/') + 1); + } + + public ClassNode getClassNode(String qualifiedName) { + for (ClassNode node : nested) { + if (qualifiedName.equals(node.classStruct.qualifiedName)) { + return node; + } + } + return null; + } + + public ClassWrapper getWrapper() { + ClassNode node = this; + while (node.type == CLASS_LAMBDA) { + node = node.parent; + } + return node.wrapper; + } + + public static class LambdaInformation { + public String method_name; + public String method_descriptor; + + public String content_class_name; + public String content_method_name; + public String content_method_descriptor; + public int content_method_invocation_type; // values from CONSTANT_MethodHandle_REF_* + public String content_method_key; + + public boolean is_method_reference; + public boolean is_content_method_static; + } + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/DecompilerContext.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/DecompilerContext.java new file mode 100644 index 000000000..5a6e605d9 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/DecompilerContext.java @@ -0,0 +1,122 @@ +// Copyright 2000-2018 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.main; + +import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.struct.StructContext; + +import java.util.Map; + +public class DecompilerContext { + public static final String CURRENT_CLASS = "CURRENT_CLASS"; + public static final String CURRENT_CLASS_WRAPPER = "CURRENT_CLASS_WRAPPER"; + public static final String CURRENT_CLASS_NODE = "CURRENT_CLASS_NODE"; + public static final String CURRENT_METHOD_WRAPPER = "CURRENT_METHOD_WRAPPER"; + private static final ThreadLocal currentContext = new ThreadLocal<>(); + private final Map properties; + private final IFernflowerLogger logger; + private final StructContext structContext; + private final ClassesProcessor classProcessor; + private final PoolInterceptor poolInterceptor; + private ImportCollector importCollector; + private VarProcessor varProcessor; + private CounterContainer counterContainer; + private BytecodeSourceMapper bytecodeSourceMapper; + + // ***************************************************************************** + // context setup and update + // ***************************************************************************** + + public DecompilerContext(Map properties, + IFernflowerLogger logger, + StructContext structContext, + ClassesProcessor classProcessor, + PoolInterceptor interceptor) { + + this.properties = properties; + this.logger = logger; + this.structContext = structContext; + this.classProcessor = classProcessor; + this.poolInterceptor = interceptor; + this.counterContainer = new CounterContainer(); + } + + public static DecompilerContext getCurrentContext() { + return currentContext.get(); + } + + public static void setCurrentContext(DecompilerContext context) { + currentContext.set(context); + } + + public static void setProperty(String key, Object value) { + getCurrentContext().properties.put(key, value); + } + + public static void startClass(ImportCollector importCollector) { + DecompilerContext context = getCurrentContext(); + context.importCollector = importCollector; + context.counterContainer = new CounterContainer(); + context.bytecodeSourceMapper = new BytecodeSourceMapper(); + } + + public static void startMethod(VarProcessor varProcessor) { + DecompilerContext context = getCurrentContext(); + context.varProcessor = varProcessor; + context.counterContainer = new CounterContainer(); + } + + // ***************************************************************************** + // context access + // ***************************************************************************** + + public static Object getProperty(String key) { + return getCurrentContext().properties.get(key); + } + + public static boolean getOption(String key) { + return "1".equals(getProperty(key)); + } + + public static String getNewLineSeparator() { + return getOption(IFernflowerPreferences.NEW_LINE_SEPARATOR) ? + IFernflowerPreferences.LINE_SEPARATOR_UNX : IFernflowerPreferences.LINE_SEPARATOR_WIN; + } + + public static IFernflowerLogger getLogger() { + return getCurrentContext().logger; + } + + public static StructContext getStructContext() { + return getCurrentContext().structContext; + } + + public static ClassesProcessor getClassProcessor() { + return getCurrentContext().classProcessor; + } + + public static PoolInterceptor getPoolInterceptor() { + return getCurrentContext().poolInterceptor; + } + + public static ImportCollector getImportCollector() { + return getCurrentContext().importCollector; + } + + public static VarProcessor getVarProcessor() { + return getCurrentContext().varProcessor; + } + + public static CounterContainer getCounterContainer() { + return getCurrentContext().counterContainer; + } + + public static BytecodeSourceMapper getBytecodeSourceMapper() { + return getCurrentContext().bytecodeSourceMapper; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/EnumProcessor.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/EnumProcessor.java new file mode 100644 index 000000000..61e744e6c --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/EnumProcessor.java @@ -0,0 +1,56 @@ +// 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.main; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statements; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class EnumProcessor { + public static void clearEnum(ClassWrapper wrapper) { + StructClass cl = wrapper.getClassStruct(); + + // hide values/valueOf methods and super() invocations + for (MethodWrapper method : wrapper.getMethods()) { + StructMethod mt = method.methodStruct; + String name = mt.getName(); + String descriptor = mt.getDescriptor(); + + if ("values".equals(name)) { + if (descriptor.equals("()[L" + cl.qualifiedName + ";")) { + wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(name, descriptor)); + } + } else if ("valueOf".equals(name)) { + if (descriptor.equals("(Ljava/lang/String;)L" + cl.qualifiedName + ";")) { + wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(name, descriptor)); + } + } else if (CodeConstants.INIT_NAME.equals(name)) { + Statement firstData = Statements.findFirstData(method.root); + if (firstData != null && !firstData.getExprents().isEmpty()) { + Exprent exprent = firstData.getExprents().get(0); + if (exprent.type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invExpr = (InvocationExprent) exprent; + if (Statements.isInvocationInitConstructor(invExpr, method, wrapper, false)) { + firstData.getExprents().remove(0); + } + } + } + } + } + + // hide synthetic fields of enum and it's constants + for (StructField fd : cl.getFields()) { + String descriptor = fd.getDescriptor(); + if (fd.isSynthetic() && descriptor.equals("[L" + cl.qualifiedName + ";")) { + wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), descriptor)); + } + } + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/Fernflower.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/Fernflower.java new file mode 100644 index 000000000..6cc253c27 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/Fernflower.java @@ -0,0 +1,121 @@ +// Copyright 2000-2018 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.main; + +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IIdentifierRenamer; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.modules.renamer.ConverterHelper; +import org.jetbrains.java.decompiler.modules.renamer.IdentifierConverter; +import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.struct.IDecompiledData; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.lazy.LazyLoader; +import org.jetbrains.java.decompiler.util.TextBuffer; + +import java.io.File; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class Fernflower implements IDecompiledData { + private final StructContext structContext; + private final ClassesProcessor classProcessor; + private final IIdentifierRenamer helper; + private final IdentifierConverter converter; + + public Fernflower(IBytecodeProvider provider, IResultSaver saver, Map customProperties, IFernflowerLogger logger) { + Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); + if (customProperties != null) { + properties.putAll(customProperties); + } + + String level = (String) properties.get(IFernflowerPreferences.LOG_LEVEL); + if (level != null) { + try { + logger.setSeverity(IFernflowerLogger.Severity.valueOf(level.toUpperCase(Locale.US))); + } catch (IllegalArgumentException ignore) { + } + } + + structContext = new StructContext(saver, this, new LazyLoader(provider)); + classProcessor = new ClassesProcessor(structContext); + + PoolInterceptor interceptor = null; + if ("1".equals(properties.get(IFernflowerPreferences.RENAME_ENTITIES))) { + helper = loadHelper((String) properties.get(IFernflowerPreferences.USER_RENAMER_CLASS), logger); + interceptor = new PoolInterceptor(); + converter = new IdentifierConverter(structContext, helper, interceptor); + } else { + helper = null; + converter = null; + } + + DecompilerContext context = new DecompilerContext(properties, logger, structContext, classProcessor, interceptor); + DecompilerContext.setCurrentContext(context); + } + + private static IIdentifierRenamer loadHelper(String className, IFernflowerLogger logger) { + if (className != null) { + try { + Class renamerClass = Fernflower.class.getClassLoader().loadClass(className); + return (IIdentifierRenamer) renamerClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + logger.writeMessage("Cannot load renamer '" + className + "'", IFernflowerLogger.Severity.WARN, e); + } + } + + return new ConverterHelper(); + } + + public void addSource(File source) { + structContext.addSpace(source, true); + } + + public void addLibrary(File library) { + structContext.addSpace(library, false); + } + + public void decompileContext() { + if (converter != null) { + converter.rename(); + } + + classProcessor.loadClasses(helper); + + structContext.saveContext(); + } + + public void clearContext() { + DecompilerContext.setCurrentContext(null); + } + + @Override + public String getClassEntryName(StructClass cl, String entryName) { + ClassNode node = classProcessor.getMapRootClasses().get(cl.qualifiedName); + if (node.type != ClassNode.CLASS_ROOT) { + return null; + } else if (converter != null) { + String simpleClassName = cl.qualifiedName.substring(cl.qualifiedName.lastIndexOf('/') + 1); + return entryName.substring(0, entryName.lastIndexOf('/') + 1) + simpleClassName + ".java"; + } else { + return entryName.substring(0, entryName.lastIndexOf(".class")) + ".java"; + } + } + + @Override + public String getClassContent(StructClass cl) { + try { + TextBuffer buffer = new TextBuffer(ClassesProcessor.AVERAGE_CLASS_SIZE); + buffer.append(DecompilerContext.getProperty(IFernflowerPreferences.BANNER).toString()); + classProcessor.writeClass(cl, buffer); + return buffer.toString(); + } catch (Throwable t) { + DecompilerContext.getLogger().writeMessage("Class " + cl.qualifiedName + " couldn't be fully decompiled.", t); + return null; + } + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/InitializerProcessor.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/InitializerProcessor.java new file mode 100644 index 000000000..12afd4cac --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/InitializerProcessor.java @@ -0,0 +1,252 @@ +// 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.main; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statements; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.util.ArrayList; +import java.util.List; + +public class InitializerProcessor { + public static void extractInitializers(ClassWrapper wrapper) { + MethodWrapper method = wrapper.getMethodWrapper(CodeConstants.CLINIT_NAME, "()V"); + if (method != null && method.root != null) { // successfully decompiled static constructor + extractStaticInitializers(wrapper, method); + } + + extractDynamicInitializers(wrapper); + + // required e.g. if anonymous class is being decompiled as a standard one. + // This can happen if InnerClasses attributes are erased + liftConstructor(wrapper); + + if (DecompilerContext.getOption(IFernflowerPreferences.HIDE_EMPTY_SUPER)) { + hideEmptySuper(wrapper); + } + } + + private static void liftConstructor(ClassWrapper wrapper) { + for (MethodWrapper method : wrapper.getMethods()) { + if (CodeConstants.INIT_NAME.equals(method.methodStruct.getName()) && method.root != null) { + Statement firstData = Statements.findFirstData(method.root); + if (firstData == null) { + return; + } + + int index = 0; + List lstExprents = firstData.getExprents(); + + for (Exprent exprent : lstExprents) { + int action = 0; + + if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent assignExpr = (AssignmentExprent) exprent; + if (assignExpr.getLeft().type == Exprent.EXPRENT_FIELD && assignExpr.getRight().type == Exprent.EXPRENT_VAR) { + FieldExprent fExpr = (FieldExprent) assignExpr.getLeft(); + if (fExpr.getClassname().equals(wrapper.getClassStruct().qualifiedName)) { + StructField structField = wrapper.getClassStruct().getField(fExpr.getName(), fExpr.getDescriptor().descriptorString); + if (structField != null && structField.hasModifier(CodeConstants.ACC_FINAL)) { + action = 1; + } + } + } + } else if (index > 0 && exprent.type == Exprent.EXPRENT_INVOCATION && + Statements.isInvocationInitConstructor((InvocationExprent) exprent, method, wrapper, true)) { + // this() or super() + lstExprents.add(0, lstExprents.remove(index)); + action = 2; + } + + if (action != 1) { + break; + } + + index++; + } + } + } + } + + private static void hideEmptySuper(ClassWrapper wrapper) { + for (MethodWrapper method : wrapper.getMethods()) { + if (CodeConstants.INIT_NAME.equals(method.methodStruct.getName()) && method.root != null) { + Statement firstData = Statements.findFirstData(method.root); + if (firstData == null || firstData.getExprents().isEmpty()) { + return; + } + + Exprent exprent = firstData.getExprents().get(0); + if (exprent.type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invExpr = (InvocationExprent) exprent; + if (Statements.isInvocationInitConstructor(invExpr, method, wrapper, false) && invExpr.getLstParameters().isEmpty()) { + firstData.getExprents().remove(0); + } + } + } + } + } + + private static void extractStaticInitializers(ClassWrapper wrapper, MethodWrapper method) { + RootStatement root = method.root; + StructClass cl = wrapper.getClassStruct(); + Statement firstData = Statements.findFirstData(root); + if (firstData != null) { + boolean inlineInitializers = cl.hasModifier(CodeConstants.ACC_INTERFACE) || cl.hasModifier(CodeConstants.ACC_ENUM); + + while (!firstData.getExprents().isEmpty()) { + Exprent exprent = firstData.getExprents().get(0); + + boolean found = false; + + if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent assignExpr = (AssignmentExprent) exprent; + if (assignExpr.getLeft().type == Exprent.EXPRENT_FIELD) { + FieldExprent fExpr = (FieldExprent) assignExpr.getLeft(); + if (fExpr.isStatic() && fExpr.getClassname().equals(cl.qualifiedName) && + cl.hasField(fExpr.getName(), fExpr.getDescriptor().descriptorString)) { + + // interfaces fields should always be initialized inline + if (inlineInitializers || isExprentIndependent(assignExpr.getRight(), method)) { + String keyField = InterpreterUtil.makeUniqueKey(fExpr.getName(), fExpr.getDescriptor().descriptorString); + if (!wrapper.getStaticFieldInitializers().containsKey(keyField)) { + wrapper.getStaticFieldInitializers().addWithKey(assignExpr.getRight(), keyField); + firstData.getExprents().remove(0); + found = true; + } + } + } + } + } + + if (!found) { + break; + } + } + } + } + + private static void extractDynamicInitializers(ClassWrapper wrapper) { + StructClass cl = wrapper.getClassStruct(); + + boolean isAnonymous = DecompilerContext.getClassProcessor().getMapRootClasses().get(cl.qualifiedName).type == ClassNode.CLASS_ANONYMOUS; + + List> lstFirst = new ArrayList<>(); + List lstMethodWrappers = new ArrayList<>(); + + for (MethodWrapper method : wrapper.getMethods()) { + if (CodeConstants.INIT_NAME.equals(method.methodStruct.getName()) && method.root != null) { // successfully decompiled constructor + Statement firstData = Statements.findFirstData(method.root); + if (firstData == null || firstData.getExprents().isEmpty()) { + return; + } + lstFirst.add(firstData.getExprents()); + lstMethodWrappers.add(method); + + Exprent exprent = firstData.getExprents().get(0); + if (!isAnonymous) { // FIXME: doesn't make sense + if (exprent.type != Exprent.EXPRENT_INVOCATION || + !Statements.isInvocationInitConstructor((InvocationExprent) exprent, method, wrapper, false)) { + return; + } + } + } + } + + if (lstFirst.isEmpty()) { + return; + } + + while (true) { + String fieldWithDescr = null; + Exprent value = null; + + for (int i = 0; i < lstFirst.size(); i++) { + List lst = lstFirst.get(i); + + if (lst.size() < (isAnonymous ? 1 : 2)) { + return; + } + + Exprent exprent = lst.get(isAnonymous ? 0 : 1); + + boolean found = false; + + if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent assignExpr = (AssignmentExprent) exprent; + if (assignExpr.getLeft().type == Exprent.EXPRENT_FIELD) { + FieldExprent fExpr = (FieldExprent) assignExpr.getLeft(); + if (!fExpr.isStatic() && fExpr.getClassname().equals(cl.qualifiedName) && + cl.hasField(fExpr.getName(), fExpr.getDescriptor().descriptorString)) { // check for the physical existence of the field. Could be defined in a superclass. + + if (isExprentIndependent(assignExpr.getRight(), lstMethodWrappers.get(i))) { + String fieldKey = InterpreterUtil.makeUniqueKey(fExpr.getName(), fExpr.getDescriptor().descriptorString); + if (fieldWithDescr == null) { + fieldWithDescr = fieldKey; + value = assignExpr.getRight(); + } else { + if (!fieldWithDescr.equals(fieldKey) || + !value.equals(assignExpr.getRight())) { + return; + } + } + found = true; + } + } + } + } + + if (!found) { + return; + } + } + + if (!wrapper.getDynamicFieldInitializers().containsKey(fieldWithDescr)) { + wrapper.getDynamicFieldInitializers().addWithKey(value, fieldWithDescr); + + for (List lst : lstFirst) { + lst.remove(isAnonymous ? 0 : 1); + } + } else { + return; + } + } + } + + private static boolean isExprentIndependent(Exprent exprent, MethodWrapper method) { + List lst = exprent.getAllExprents(true); + lst.add(exprent); + + for (Exprent expr : lst) { + switch (expr.type) { + case Exprent.EXPRENT_VAR: + VarVersionPair varPair = new VarVersionPair((VarExprent) expr); + if (!method.varproc.getExternalVars().contains(varPair)) { + String varName = method.varproc.getVarName(varPair); + if (!varName.equals("this") && !varName.endsWith(".this")) { // FIXME: remove direct comparison with strings + return false; + } + } + break; + case Exprent.EXPRENT_FIELD: + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeMappingTracer.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeMappingTracer.java new file mode 100644 index 000000000..4ffbe2b62 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeMappingTracer.java @@ -0,0 +1,107 @@ +// Copyright 2000-2018 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.main.collectors; + +import com.duy.java8.util.DMap; + +import org.jetbrains.java.decompiler.struct.attr.StructLineNumberTableAttribute; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class BytecodeMappingTracer { + public static final BytecodeMappingTracer DUMMY = new BytecodeMappingTracer(); + private final Map mapping = new HashMap<>(); // bytecode offset, source line + private final Set unmappedLines = new HashSet<>(); + private int currentSourceLine; + private StructLineNumberTableAttribute lineNumberTable = null; + + public BytecodeMappingTracer() { + } + + public BytecodeMappingTracer(int initial_source_line) { + currentSourceLine = initial_source_line; + } + + public void incrementCurrentSourceLine() { + currentSourceLine++; + } + + public void incrementCurrentSourceLine(int number_lines) { + currentSourceLine += number_lines; + } + + public void addMapping(int bytecode_offset) { + DMap.putIfAbsent(mapping, bytecode_offset, currentSourceLine); + } + + public void addMapping(Set bytecode_offsets) { + if (bytecode_offsets != null) { + for (Integer bytecode_offset : bytecode_offsets) { + addMapping(bytecode_offset); + } + } + } + + public void addTracer(BytecodeMappingTracer tracer) { + if (tracer != null) { + for (Entry entry : tracer.mapping.entrySet()) { + DMap.putIfAbsent(mapping, entry.getKey(), entry.getValue()); + } + } + } + + public Map getMapping() { + return mapping; + } + + public int getCurrentSourceLine() { + return currentSourceLine; + } + + public void setCurrentSourceLine(int currentSourceLine) { + this.currentSourceLine = currentSourceLine; + } + + public void setLineNumberTable(StructLineNumberTableAttribute lineNumberTable) { + this.lineNumberTable = lineNumberTable; + } + + public Set getUnmappedLines() { + return unmappedLines; + } + + public Map getOriginalLinesMapping() { + if (lineNumberTable == null) { + return Collections.emptyMap(); + } + + Map res = new HashMap<>(); + + // first match offsets from line number table + int[] data = lineNumberTable.getRawData(); + for (int i = 0; i < data.length; i += 2) { + int originalOffset = data[i]; + int originalLine = data[i + 1]; + Integer newLine = mapping.get(originalOffset); + if (newLine != null) { + res.put(originalLine, newLine); + } else { + unmappedLines.add(originalLine); + } + } + + // now match offsets from decompiler mapping + for (Entry entry : mapping.entrySet()) { + int originalLine = lineNumberTable.findLineNumber(entry.getKey()); + if (originalLine > -1 && !res.containsKey(originalLine)) { + res.put(originalLine, entry.getValue()); + unmappedLines.remove(originalLine); + } + } + return res; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeSourceMapper.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeSourceMapper.java new file mode 100644 index 000000000..1b9d9f1a3 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/BytecodeSourceMapper.java @@ -0,0 +1,130 @@ +// Copyright 2000-2018 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.main.collectors; + +import com.duy.java8.util.DMap; +import com.duy.java8.util.function.Function; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.util.TextBuffer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +public class BytecodeSourceMapper { + // class, method, bytecode offset, source line + private final Map>> mapping = new LinkedHashMap<>(); + // original line to decompiled line + private final Map linesMapping = new HashMap<>(); + private final Set unmappedLines = new TreeSet<>(); + private int offset_total; + + public void addMapping(String className, String methodName, int bytecodeOffset, int sourceLine) { + Map> class_mapping = DMap.computeIfAbsent(mapping, + className, new Function>>() { + @Override + public Map> apply(String k) { + return new LinkedHashMap<>(); + } + }); // need to preserve order + Map method_mapping = DMap.computeIfAbsent(class_mapping, + methodName, new Function>() { + @Override + public Map apply(String k) { + return new HashMap<>(); + } + }); + + // don't overwrite + DMap.putIfAbsent(method_mapping, bytecodeOffset, sourceLine); + } + + public void addTracer(String className, String methodName, BytecodeMappingTracer tracer) { + for (Entry entry : tracer.getMapping().entrySet()) { + addMapping(className, methodName, entry.getKey(), entry.getValue()); + } + linesMapping.putAll(tracer.getOriginalLinesMapping()); + unmappedLines.addAll(tracer.getUnmappedLines()); + } + + public void dumpMapping(TextBuffer buffer, boolean offsetsToHex) { + if (mapping.isEmpty() && linesMapping.isEmpty()) { + return; + } + + String lineSeparator = DecompilerContext.getNewLineSeparator(); + + for (Entry>> class_entry : mapping.entrySet()) { + Map> class_mapping = class_entry.getValue(); + buffer.append("class '" + class_entry.getKey() + "' {" + lineSeparator); + + boolean is_first_method = true; + for (Entry> method_entry : class_mapping.entrySet()) { + Map method_mapping = method_entry.getValue(); + + if (!is_first_method) { + buffer.appendLineSeparator(); + } + + buffer.appendIndent(1).append("method '" + method_entry.getKey() + "' {" + lineSeparator); + + List lstBytecodeOffsets = new ArrayList<>(method_mapping.keySet()); + Collections.sort(lstBytecodeOffsets); + + for (Integer offset : lstBytecodeOffsets) { + Integer line = method_mapping.get(offset); + + String strOffset = offsetsToHex ? Integer.toHexString(offset) : line.toString(); + buffer.appendIndent(2).append(strOffset).appendIndent(2).append((line + offset_total) + lineSeparator); + } + buffer.appendIndent(1).append("}").appendLineSeparator(); + + is_first_method = false; + } + + buffer.append("}").appendLineSeparator().appendLineSeparator(); + } + + // lines mapping + buffer.append("Lines mapping:").appendLineSeparator(); + Map sorted = new TreeMap<>(linesMapping); + for (Entry entry : sorted.entrySet()) { + buffer.append(entry.getKey()).append(" <-> ").append(entry.getValue() + offset_total + 1).appendLineSeparator(); + } + + if (!unmappedLines.isEmpty()) { + buffer.append("Not mapped:").appendLineSeparator(); + for (Integer line : unmappedLines) { + if (!linesMapping.containsKey(line)) { + buffer.append(line).appendLineSeparator(); + } + } + } + } + + public void addTotalOffset(int offset_total) { + this.offset_total += offset_total; + } + + /** + * Original to decompiled line mapping. + */ + public int[] getOriginalLinesMapping() { + int[] res = new int[linesMapping.size() * 2]; + int i = 0; + for (Entry entry : linesMapping.entrySet()) { + res[i] = entry.getKey(); + unmappedLines.remove(entry.getKey()); + res[i + 1] = entry.getValue() + offset_total + 1; // make it 1 based + i += 2; + } + return res; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java new file mode 100644 index 000000000..c89eea6be --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java @@ -0,0 +1,22 @@ +// 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.main.collectors; + +public class CounterContainer { + public static final int STATEMENT_COUNTER = 0; + public static final int EXPRESSION_COUNTER = 1; + public static final int VAR_COUNTER = 2; + + private final int[] values = new int[]{1, 1, 1}; + + public void setCounter(int counter, int value) { + values[counter] = value; + } + + public int getCounter(int counter) { + return values[counter]; + } + + public int getCounterAndIncrement(int counter) { + return values[counter]++; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java new file mode 100644 index 000000000..c51f8ff7c --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java @@ -0,0 +1,206 @@ +/* + * 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.main.collectors; + +import com.duy.java8.util.DComparator; +import com.duy.java8.util.function.Function; +import com.duy.java8.util.function.Predicate; + +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.util.TextBuffer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ImportCollector { + private static final String JAVA_LANG_PACKAGE = "java.lang"; + + private final Map mapSimpleNames = new HashMap<>(); + private final Set setNotImportedNames = new HashSet<>(); + // set of field names in this class and all its predecessors. + private final Set setFieldNames = new HashSet<>(); + private final String currentPackageSlash; + private final String currentPackagePoint; + + public ImportCollector(ClassNode root) { + String clName = root.classStruct.qualifiedName; + int index = clName.lastIndexOf('/'); + if (index >= 0) { + String packageName = clName.substring(0, index); + currentPackageSlash = packageName + '/'; + currentPackagePoint = packageName.replace('/', '.'); + } else { + currentPackageSlash = ""; + currentPackagePoint = ""; + } + + Map classes = DecompilerContext.getStructContext().getClasses(); + StructClass currentClass = root.classStruct; + while (currentClass != null) { + // all field names for the current class .. + for (StructField f : currentClass.getFields()) { + setFieldNames.add(f.getName()); + } + + // .. and traverse through parent. + currentClass = currentClass.superClass != null ? classes.get(currentClass.superClass.getString()) : null; + } + } + + /** + * Check whether the package-less name ClassName is shaded by variable in a context of + * the decompiled class + * + * @param classToName - pkg.name.ClassName - class to find shortname for + * @return ClassName if the name is not shaded by local field, pkg.name.ClassName otherwise + */ + public String getShortNameInClassContext(String classToName) { + String shortName = getShortName(classToName); + if (setFieldNames.contains(shortName)) { + return classToName; + } else { + return shortName; + } + } + + public String getShortName(String fullName) { + return getShortName(fullName, true); + } + + public String getShortName(String fullName, boolean imported) { + ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(fullName.replace('.', '/')); //todo[r.sh] anonymous classes? + + String result = null; + if (node != null && node.classStruct.isOwn()) { + result = node.simpleName; + + while (node.parent != null && node.type == ClassNode.CLASS_MEMBER) { + //noinspection StringConcatenationInLoop + result = node.parent.simpleName + '.' + result; + node = node.parent; + } + + if (node.type == ClassNode.CLASS_ROOT) { + fullName = node.classStruct.qualifiedName; + fullName = fullName.replace('/', '.'); + } else { + return result; + } + } else { + fullName = fullName.replace('$', '.'); + } + + String shortName = fullName; + String packageName = ""; + + int lastDot = fullName.lastIndexOf('.'); + if (lastDot >= 0) { + shortName = fullName.substring(lastDot + 1); + packageName = fullName.substring(0, lastDot); + } + + StructContext context = DecompilerContext.getStructContext(); + + // check for another class which could 'shadow' this one. Two cases: + // 1) class with the same short name in the current package + // 2) class with the same short name in the default package + boolean existsDefaultClass = + (context.getClass(currentPackageSlash + shortName) != null && !packageName.equals(currentPackagePoint)) || // current package + (context.getClass(shortName) != null && !currentPackagePoint.isEmpty()); // default package + + if (existsDefaultClass || + (mapSimpleNames.containsKey(shortName) && !packageName.equals(mapSimpleNames.get(shortName)))) { + // don't return full name because if the class is a inner class, full name refers to the parent full name, not the child full name + return result == null ? fullName : (packageName + "." + result); + } else if (!mapSimpleNames.containsKey(shortName)) { + mapSimpleNames.put(shortName, packageName); + if (!imported) { + setNotImportedNames.add(shortName); + } + } + + return result == null ? shortName : result; + } + + public int writeImports(TextBuffer buffer) { + int importLinesWritten = 0; + + List imports = packImports(); + + for (String s : imports) { + buffer.append("import "); + buffer.append(s); + buffer.append(';'); + buffer.appendLineSeparator(); + + importLinesWritten++; + } + + return importLinesWritten; + } + + private List packImports() { + Predicate> predicate = new Predicate>() { + @Override + public boolean test(Map.Entry ent) { + return !setNotImportedNames.contains(ent.getKey()) && + !ent.getValue().isEmpty() && + !JAVA_LANG_PACKAGE.equals(ent.getValue()) && + !ent.getValue().equals(currentPackagePoint); + } + }; + Function, String> mapper = new Function, String>() { + @Override + public String apply(Map.Entry ent) { + return ent.getValue() + "." + ent.getKey(); + } + }; + Comparator> comparingByValue = new Comparator>() { + @Override + public int compare(Map.Entry c1, Map.Entry c2) { + return c1.getValue().compareTo(c2.getValue()); + } + }; + Comparator> comparingByKey = new Comparator>() { + @Override + public int compare(Map.Entry c1, Map.Entry c2) { + return c1.getKey().compareTo(c2.getKey()); + } + }; + Comparator> comparator = DComparator.thenComparing(comparingByValue, comparingByKey); + +// return mapSimpleNames.entrySet().stream() +// .filter(predicate) +// .sorted(comparator) +// .map(mapper) +// .collect(list); + List> entries = new ArrayList<>(); + + //filter + for (Map.Entry entry : mapSimpleNames.entrySet()) { + if (predicate.test(entry)) { + entries.add(entry); + } + } + + //sort + Collections.sort(entries, comparator); + + List result = new ArrayList<>(); + for (Map.Entry entry : entries) { + result.add(mapper.apply(entry)); + } + return result; + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java new file mode 100644 index 000000000..8bda1812f --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java @@ -0,0 +1,34 @@ +// 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.main.collectors; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class VarNamesCollector { + + private final Set usedNames = new HashSet<>(); + + public VarNamesCollector() { + } + + public VarNamesCollector(Collection setNames) { + usedNames.addAll(setNames); + } + + public void addName(String value) { + usedNames.add(value); + } + + public String getFreeName(int index) { + return getFreeName("var" + index); + } + + public String getFreeName(String proposition) { + while (usedNames.contains(proposition)) { + proposition += "x"; + } + usedNames.add(proposition); + return proposition; + } +} diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java new file mode 100644 index 000000000..eb4455acd --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java @@ -0,0 +1,46 @@ +// Copyright 2000-2018 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.main.decompiler; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.io.File; +import java.util.Map; + +@SuppressWarnings("unused") +public class BaseDecompiler { + private final Fernflower engine; + + public BaseDecompiler(IBytecodeProvider provider, IResultSaver saver, Map options, IFernflowerLogger logger) { + engine = new Fernflower(provider, saver, options, logger); + } + + public void addSource(File source) { + engine.addSource(source); + } + + public void addLibrary(File library) { + engine.addLibrary(library); + } + + /** + * @deprecated use {@link #addSource(File)} / {@link #addLibrary(File)} instead + */ + public void addSpace(File file, boolean own) { + if (own) { + addSource(file); + } else { + addLibrary(file); + } + } + + public void decompileContext() { + try { + engine.decompileContext(); + } finally { + engine.clearContext(); + } + } +} \ No newline at end of file diff --git a/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java new file mode 100644 index 000000000..7563e88e7 --- /dev/null +++ b/lib-decompiler/src/main/java/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -0,0 +1,295 @@ +// Copyright 2000-2018 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.main.decompiler; + +import com.duy.java8.util.DMap; +import com.duy.java8.util.function.Function; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver { + private final File root; + private final Fernflower engine; + + // ******************************************************************* + // Implementation + // ******************************************************************* + private final Map mapArchiveStreams = new HashMap<>(); + private final Map> mapArchiveEntries = new HashMap<>(); + + protected ConsoleDecompiler(File destination, Map options, IFernflowerLogger logger) { + root = destination; + engine = new Fernflower(this, this, options, logger); + } + + @SuppressWarnings("UseOfSystemOutOrSystemErr") + public static void main(String[] args) { + if (args.length < 2) { + System.out.println( + "Usage: java -jar fernflower.jar [-