From a5a48b3727def5167928c402e8c0f65adb491872 Mon Sep 17 00:00:00 2001 From: hoenicke Date: Thu, 8 Jun 2006 17:58:19 +0000 Subject: [PATCH] Initial attempt for generics support, does not work yet git-svn-id: https://svn.code.sf.net/p/jode/code/branches/generics@1397 379699f6-c40d-0410-875b-85095c16579e --- jode/.cvsignore | 9 + jode/AUTHORS | 1 + jode/COPYING | 340 +++ jode/COPYING.LESSER | 504 +++++ jode/COPYING.LGPL | 504 +++++ jode/ChangeLog | 563 +++++ jode/INSTALL | 23 + jode/MANIFEST.MF | 3 + jode/NEWS | 36 + jode/README | 90 + jode/THANKS | 5 + jode/TODO | 64 + jode/bin/.cvsignore | 2 + jode/bin/Makefile.am | 5 + jode/bin/jode.bat.in | 19 + jode/bin/jode.in | 14 + jode/build.xml | 380 ++++ jode/config.props | 28 + jode/create.sh | 58 + jode/doc/.cvsignore | 3 + jode/doc/applet.htp | 25 + jode/doc/bluesky.htp | 69 + jode/doc/dasm_to_java.perl | 711 +++++++ jode/doc/download.htp | 38 + jode/doc/faq.htp | 90 + jode/doc/favicon.xpm | 106 + jode/doc/feedback.htp | 11 + jode/doc/footer.inc | 13 + jode/doc/gimp/arrow.gif | Bin 0 -> 126 bytes jode/doc/gimp/bytecode.gif | Bin 0 -> 1222 bytes jode/doc/gimp/bytecode.txt | 9 + jode/doc/gimp/flow.gif | Bin 0 -> 192 bytes jode/doc/gimp/jode-logo.xcf | Bin 0 -> 80236 bytes jode/doc/gimp/statement.gif | Bin 0 -> 1226 bytes jode/doc/gimp/statement.txt | 9 + jode/doc/gimp/vects.fig | 42 + jode/doc/header.inc | 63 + jode/doc/history.htp | 20 + jode/doc/htp.def | 52 + jode/doc/index.htp | 74 + jode/doc/jode-logo.png | Bin 0 -> 5068 bytes jode/doc/jode.htt | 67 + jode/doc/jode.texi | 11 + jode/doc/license.htp | 21 + jode/doc/links.htp | 75 + jode/doc/menu.inc | 47 + jode/doc/myproject.jos | 84 + jode/doc/pattern.txt | 65 + jode/doc/poweredbyhtp.png | Bin 0 -> 1313 bytes jode/doc/technical.texi | 279 +++ jode/doc/usage.htp | 250 +++ jode/doc/w3c_ab.png | Bin 0 -> 1035 bytes jode/jode/jode.jos | 28 + jode/lib/java-getopt-1.0.8.jar | Bin 0 -> 49643 bytes jode/makesnapshot | 40 + jode/prj.el | 84 + jode/project-ext.dtd | 34 + jode/project.dtd | 273 +++ .../sf/jode/decompiler/OptionNames.properties | 96 + .../net/sf/jode/swingui/Resources.properties | 28 + .../sf/jode/swingui/Resources_de.properties | 28 + jode/scripts/addHeader.pl | 94 + jode/scripts/createStackDelta.pl | 134 ++ jode/scripts/javaDependencies.pl | 184 ++ jode/scripts/jcpp.pl | 234 ++ jode/scripts/php2html.pl | 15 + jode/src/net/sf/jode/GlobalOptions.java | 107 + .../sf/jode/bytecode/BasicBlockReader.java | 1076 ++++++++++ .../sf/jode/bytecode/BasicBlockWriter.java | 1011 +++++++++ .../src/net/sf/jode/bytecode/BasicBlocks.java | 409 ++++ jode/src/net/sf/jode/bytecode/BinaryInfo.java | 405 ++++ jode/src/net/sf/jode/bytecode/Block.java | 296 +++ .../jode/bytecode/ClassFormatException.java | 43 + jode/src/net/sf/jode/bytecode/ClassInfo.java | 1599 ++++++++++++++ jode/src/net/sf/jode/bytecode/ClassPath.java | 1040 +++++++++ .../sf/jode/bytecode/ConstantInstruction.java | 53 + .../net/sf/jode/bytecode/ConstantPool.java | 280 +++ jode/src/net/sf/jode/bytecode/FieldInfo.java | 340 +++ .../jode/bytecode/GrowableConstantPool.java | 348 +++ jode/src/net/sf/jode/bytecode/Handler.java | 94 + .../net/sf/jode/bytecode/IncInstruction.java | 60 + .../src/net/sf/jode/bytecode/Instruction.java | 503 +++++ .../sf/jode/bytecode/LocalVariableInfo.java | 135 ++ jode/src/net/sf/jode/bytecode/MethodInfo.java | 339 +++ jode/src/net/sf/jode/bytecode/Opcodes.java | 280 +++ jode/src/net/sf/jode/bytecode/Reference.java | 79 + .../jode/bytecode/ReferenceInstruction.java | 86 + .../net/sf/jode/bytecode/SlotInstruction.java | 72 + .../sf/jode/bytecode/SwitchInstruction.java | 58 + .../bytecode/TypeDimensionInstruction.java | 72 + .../net/sf/jode/bytecode/TypeInstruction.java | 50 + .../net/sf/jode/bytecode/TypeSignature.java | 710 +++++++ jode/src/net/sf/jode/bytecode/package.html | 106 + jode/src/net/sf/jode/decompiler/Analyzer.java | 26 + jode/src/net/sf/jode/decompiler/Applet.java | 124 ++ .../net/sf/jode/decompiler/ClassAnalyzer.java | 825 +++++++ .../net/sf/jode/decompiler/ClassDeclarer.java | 42 + .../net/sf/jode/decompiler/Declarable.java | 38 + .../net/sf/jode/decompiler/Decompiler.java | 233 ++ .../net/sf/jode/decompiler/FieldAnalyzer.java | 206 ++ .../net/sf/jode/decompiler/ImportHandler.java | 405 ++++ .../src/net/sf/jode/decompiler/LocalInfo.java | 414 ++++ jode/src/net/sf/jode/decompiler/Main.java | 429 ++++ .../sf/jode/decompiler/MethodAnalyzer.java | 1149 ++++++++++ jode/src/net/sf/jode/decompiler/Opcodes.java | 443 ++++ jode/src/net/sf/jode/decompiler/Options.java | 59 + .../jode/decompiler/OuterValueListener.java | 37 + .../net/sf/jode/decompiler/OuterValues.java | 392 ++++ .../sf/jode/decompiler/ProgressListener.java | 37 + jode/src/net/sf/jode/decompiler/Scope.java | 60 + .../sf/jode/decompiler/TabbedPrintWriter.java | 825 +++++++ jode/src/net/sf/jode/decompiler/Window.java | 307 +++ .../net/sf/jode/expr/ArrayLengthOperator.java | 47 + .../net/sf/jode/expr/ArrayLoadOperator.java | 58 + .../net/sf/jode/expr/ArrayStoreOperator.java | 60 + jode/src/net/sf/jode/expr/BinaryOperator.java | 94 + .../net/sf/jode/expr/CheckCastOperator.java | 74 + .../net/sf/jode/expr/CheckNullOperator.java | 96 + .../net/sf/jode/expr/ClassFieldOperator.java | 42 + .../net/sf/jode/expr/CombineableOperator.java | 36 + .../sf/jode/expr/CompareBinaryOperator.java | 94 + .../sf/jode/expr/CompareToIntOperator.java | 64 + .../sf/jode/expr/CompareUnaryOperator.java | 116 + jode/src/net/sf/jode/expr/ConstOperator.java | 204 ++ .../sf/jode/expr/ConstantArrayOperator.java | 121 ++ .../src/net/sf/jode/expr/ConvertOperator.java | 55 + jode/src/net/sf/jode/expr/Expression.java | 318 +++ jode/src/net/sf/jode/expr/FieldOperator.java | 316 +++ .../net/sf/jode/expr/GetFieldOperator.java | 57 + jode/src/net/sf/jode/expr/IIncOperator.java | 87 + .../net/sf/jode/expr/IfThenElseOperator.java | 138 ++ .../net/sf/jode/expr/InstanceOfOperator.java | 67 + jode/src/net/sf/jode/expr/InvokeOperator.java | 1286 +++++++++++ .../net/sf/jode/expr/LValueExpression.java | 26 + .../net/sf/jode/expr/LocalLoadOperator.java | 64 + .../net/sf/jode/expr/LocalStoreOperator.java | 48 + .../net/sf/jode/expr/LocalVarOperator.java | 83 + .../net/sf/jode/expr/MatchableOperator.java | 27 + .../sf/jode/expr/MonitorEnterOperator.java | 46 + .../net/sf/jode/expr/MonitorExitOperator.java | 46 + .../net/sf/jode/expr/NewArrayOperator.java | 67 + jode/src/net/sf/jode/expr/NewOperator.java | 38 + jode/src/net/sf/jode/expr/NoArgOperator.java | 39 + jode/src/net/sf/jode/expr/NopOperator.java | 73 + jode/src/net/sf/jode/expr/Operator.java | 331 +++ .../net/sf/jode/expr/OuterLocalOperator.java | 72 + jode/src/net/sf/jode/expr/PopOperator.java | 57 + .../net/sf/jode/expr/PrePostFixOperator.java | 65 + .../net/sf/jode/expr/PutFieldOperator.java | 42 + jode/src/net/sf/jode/expr/ShiftOperator.java | 40 + jode/src/net/sf/jode/expr/SimpleOperator.java | 30 + .../net/sf/jode/expr/StoreInstruction.java | 123 ++ .../net/sf/jode/expr/StringAddOperator.java | 57 + jode/src/net/sf/jode/expr/ThisOperator.java | 66 + jode/src/net/sf/jode/expr/UnaryOperator.java | 63 + jode/src/net/sf/jode/flow/BreakBlock.java | 104 + jode/src/net/sf/jode/flow/BreakableBlock.java | 45 + jode/src/net/sf/jode/flow/CaseBlock.java | 207 ++ jode/src/net/sf/jode/flow/CatchBlock.java | 240 +++ .../jode/flow/CombineIfGotoExpressions.java | 104 + .../sf/jode/flow/CompleteSynchronized.java | 96 + .../net/sf/jode/flow/ConditionalBlock.java | 140 ++ jode/src/net/sf/jode/flow/ContinueBlock.java | 102 + .../sf/jode/flow/CreateAssignExpression.java | 216 ++ .../src/net/sf/jode/flow/CreateCheckNull.java | 123 ++ .../net/sf/jode/flow/CreateClassField.java | 89 + .../net/sf/jode/flow/CreateConstantArray.java | 116 + .../net/sf/jode/flow/CreateExpression.java | 119 ++ .../sf/jode/flow/CreateForInitializer.java | 55 + .../jode/flow/CreateIfThenElseOperator.java | 236 ++ .../sf/jode/flow/CreateNewConstructor.java | 241 +++ .../jode/flow/CreatePrePostIncExpression.java | 183 ++ .../net/sf/jode/flow/DescriptionBlock.java | 49 + jode/src/net/sf/jode/flow/EmptyBlock.java | 79 + jode/src/net/sf/jode/flow/FinallyBlock.java | 108 + jode/src/net/sf/jode/flow/FlowBlock.java | 1889 +++++++++++++++++ .../src/net/sf/jode/flow/IfThenElseBlock.java | 235 ++ .../net/sf/jode/flow/InstructionBlock.java | 162 ++ .../sf/jode/flow/InstructionContainer.java | 133 ++ jode/src/net/sf/jode/flow/JsrBlock.java | 124 ++ jode/src/net/sf/jode/flow/Jump.java | 74 + jode/src/net/sf/jode/flow/LoopBlock.java | 547 +++++ jode/src/net/sf/jode/flow/RetBlock.java | 75 + jode/src/net/sf/jode/flow/ReturnBlock.java | 92 + .../src/net/sf/jode/flow/SequentialBlock.java | 249 +++ jode/src/net/sf/jode/flow/SlotSet.java | 217 ++ jode/src/net/sf/jode/flow/SpecialBlock.java | 209 ++ .../src/net/sf/jode/flow/StructuredBlock.java | 660 ++++++ jode/src/net/sf/jode/flow/SwitchBlock.java | 283 +++ .../net/sf/jode/flow/SynchronizedBlock.java | 127 ++ jode/src/net/sf/jode/flow/ThrowBlock.java | 39 + .../sf/jode/flow/TransformConstructors.java | 1111 ++++++++++ .../jode/flow/TransformExceptionHandlers.java | 998 +++++++++ jode/src/net/sf/jode/flow/TryBlock.java | 226 ++ jode/src/net/sf/jode/flow/VariableSet.java | 257 +++ jode/src/net/sf/jode/flow/VariableStack.java | 176 ++ jode/src/net/sf/jode/jvm/CodeVerifier.java | 1526 +++++++++++++ jode/src/net/sf/jode/jvm/Interpreter.java | 767 +++++++ .../net/sf/jode/jvm/InterpreterException.java | 35 + jode/src/net/sf/jode/jvm/NewObject.java | 54 + .../net/sf/jode/jvm/RuntimeEnvironment.java | 123 ++ .../sf/jode/jvm/SimpleRuntimeEnvironment.java | 235 ++ .../net/sf/jode/jvm/SyntheticAnalyzer.java | 413 ++++ jode/src/net/sf/jode/jvm/Value.java | 98 + jode/src/net/sf/jode/jvm/VerifyException.java | 36 + .../net/sf/jode/obfuscator/ClassBundle.java | 497 +++++ .../sf/jode/obfuscator/ClassIdentifier.java | 785 +++++++ .../net/sf/jode/obfuscator/CodeAnalyzer.java | 25 + .../sf/jode/obfuscator/CodeTransformer.java | 25 + .../ConstantRuntimeEnvironment.java | 294 +++ .../sf/jode/obfuscator/FieldIdentifier.java | 156 ++ .../net/sf/jode/obfuscator/Identifier.java | 264 +++ .../sf/jode/obfuscator/IdentifierMatcher.java | 44 + .../sf/jode/obfuscator/LocalIdentifier.java | 64 + jode/src/net/sf/jode/obfuscator/Main.java | 183 ++ .../sf/jode/obfuscator/MethodIdentifier.java | 252 +++ .../net/sf/jode/obfuscator/OptionHandler.java | 29 + .../sf/jode/obfuscator/PackageIdentifier.java | 504 +++++ .../sf/jode/obfuscator/ParseException.java | 26 + jode/src/net/sf/jode/obfuscator/Renamer.java | 34 + .../net/sf/jode/obfuscator/ScriptParser.java | 282 +++ .../sf/jode/obfuscator/TranslationTable.java | 80 + .../obfuscator/modules/ConstantAnalyzer.java | 1781 ++++++++++++++++ .../obfuscator/modules/IdentityRenamer.java | 50 + .../obfuscator/modules/KeywordRenamer.java | 84 + .../obfuscator/modules/LocalOptimizer.java | 789 +++++++ .../modules/LocalizeFieldTransformer.java | 33 + .../obfuscator/modules/ModifierMatcher.java | 353 +++ .../modules/MultiIdentifierMatcher.java | 112 + .../jode/obfuscator/modules/NameSwapper.java | 103 + .../obfuscator/modules/RemovePopAnalyzer.java | 423 ++++ .../modules/SerializePreserver.java | 91 + .../obfuscator/modules/SimpleAnalyzer.java | 228 ++ .../obfuscator/modules/StrongRenamer.java | 171 ++ .../obfuscator/modules/UniqueRenamer.java | 46 + .../sf/jode/obfuscator/modules/WildCard.java | 111 + jode/src/net/sf/jode/overview.html | 50 + .../net/sf/jode/swingui/ClassPathDialog.java | 274 +++ .../sf/jode/swingui/HierarchyTreeModel.java | 285 +++ jode/src/net/sf/jode/swingui/Main.java | 463 ++++ .../sf/jode/swingui/PackagesTreeModel.java | 217 ++ jode/src/net/sf/jode/type/ArrayType.java | 208 ++ jode/src/net/sf/jode/type/ClassInfoType.java | 136 ++ jode/src/net/sf/jode/type/ClassType.java | 406 ++++ .../sf/jode/type/GenericParameterType.java | 68 + jode/src/net/sf/jode/type/IntegerType.java | 289 +++ jode/src/net/sf/jode/type/MethodType.java | 89 + jode/src/net/sf/jode/type/MultiClassType.java | 315 +++ jode/src/net/sf/jode/type/NullType.java | 85 + jode/src/net/sf/jode/type/ParameterType.java | 88 + jode/src/net/sf/jode/type/RangeType.java | 235 ++ jode/src/net/sf/jode/type/ReferenceType.java | 210 ++ .../src/net/sf/jode/type/SystemClassType.java | 72 + jode/src/net/sf/jode/type/Type.java | 694 ++++++ jode/src/net/sf/jode/util/ArrayEnum.java | 38 + jode/src/net/sf/jode/util/SimpleMap.java | 98 + jode/src/net/sf/jode/util/SimpleSet.java | 92 + jode/src/net/sf/jode/util/StringQuoter.java | 101 + jode/src/net/sf/jode/util/UnifyHash.java | 293 +++ jode/test/.cvsignore | 2 + jode/test/AnonymousClass.java | 204 ++ jode/test/AnonymousJavac.java | 181 ++ jode/test/ArrayCloneTest.java | 28 + jode/test/ArrayTest.java | 92 + jode/test/AssignOp.java | 90 + jode/test/Base.j | 12 + jode/test/Child.j | 29 + jode/test/ClassOpTest.java | 36 + jode/test/ConstantTypes.java | 71 + jode/test/CountOpcodes.java | 99 + jode/test/EvilTypes.j | 124 ++ jode/test/Expressions.java | 91 + jode/test/Flow.java | 218 ++ jode/test/For.java | 35 + jode/test/HintTypeTest.java | 50 + jode/test/IfCombine.java | 33 + jode/test/InlineTest.java | 34 + jode/test/InlinedAnon.java | 21 + jode/test/InnerClass.java | 99 + jode/test/InnerCompat.java | 69 + jode/test/JavacBug.java | 33 + jode/test/JsrTest.j | 63 + jode/test/LocalTypes.java | 183 ++ jode/test/Makefile.am | 45 + jode/test/MethodScopedClass.java | 124 ++ jode/test/NestedAnon.java | 21 + jode/test/ObfuscateStrings.j | 166 ++ jode/test/OptimizeTest.java | 79 + jode/test/OptimizerTest.java | 58 + jode/test/PrivateHideTest.java | 89 + jode/test/RemovePopExample.j | 70 + jode/test/ResolveConflicts.java | 144 ++ jode/test/StackOps.j | 41 + jode/test/TriadicExpr.java | 38 + jode/test/TryCatch.java | 159 ++ jode/test/Unreach.java | 32 + jode/test/innerclasses/CheckPrivate.java | 40 + jode/test/innerclasses/CheckSuperRemove.java | 28 + jode/test/removepop.jodescript | 7 + jode/test/simpletests.sh | 71 + .../jode/bytecode/BasicBlockWriterTest.java | 226 ++ .../net/sf/jode/bytecode/BasicBlocksTest.java | 47 + .../src/net/sf/jode/bytecode/BlockTest.java | 44 + jode/test/src/net/sf/jode/flow/TrExcTest.java | 358 ++++ .../jode/obfuscator/modules/ConstAnaTest.java | 186 ++ 305 files changed, 57306 insertions(+) create mode 100644 jode/.cvsignore create mode 100644 jode/AUTHORS create mode 100644 jode/COPYING create mode 100644 jode/COPYING.LESSER create mode 100644 jode/COPYING.LGPL create mode 100644 jode/ChangeLog create mode 100644 jode/INSTALL create mode 100644 jode/MANIFEST.MF create mode 100644 jode/NEWS create mode 100644 jode/README create mode 100644 jode/THANKS create mode 100644 jode/TODO create mode 100644 jode/bin/.cvsignore create mode 100644 jode/bin/Makefile.am create mode 100644 jode/bin/jode.bat.in create mode 100644 jode/bin/jode.in create mode 100644 jode/build.xml create mode 100644 jode/config.props create mode 100755 jode/create.sh create mode 100644 jode/doc/.cvsignore create mode 100644 jode/doc/applet.htp create mode 100644 jode/doc/bluesky.htp create mode 100755 jode/doc/dasm_to_java.perl create mode 100644 jode/doc/download.htp create mode 100644 jode/doc/faq.htp create mode 100644 jode/doc/favicon.xpm create mode 100644 jode/doc/feedback.htp create mode 100644 jode/doc/footer.inc create mode 100644 jode/doc/gimp/arrow.gif create mode 100644 jode/doc/gimp/bytecode.gif create mode 100644 jode/doc/gimp/bytecode.txt create mode 100644 jode/doc/gimp/flow.gif create mode 100644 jode/doc/gimp/jode-logo.xcf create mode 100644 jode/doc/gimp/statement.gif create mode 100644 jode/doc/gimp/statement.txt create mode 100644 jode/doc/gimp/vects.fig create mode 100644 jode/doc/header.inc create mode 100644 jode/doc/history.htp create mode 100644 jode/doc/htp.def create mode 100644 jode/doc/index.htp create mode 100644 jode/doc/jode-logo.png create mode 100644 jode/doc/jode.htt create mode 100644 jode/doc/jode.texi create mode 100644 jode/doc/license.htp create mode 100644 jode/doc/links.htp create mode 100644 jode/doc/menu.inc create mode 100644 jode/doc/myproject.jos create mode 100644 jode/doc/pattern.txt create mode 100644 jode/doc/poweredbyhtp.png create mode 100644 jode/doc/technical.texi create mode 100644 jode/doc/usage.htp create mode 100644 jode/doc/w3c_ab.png create mode 100644 jode/jode/jode.jos create mode 100644 jode/lib/java-getopt-1.0.8.jar create mode 100755 jode/makesnapshot create mode 100644 jode/prj.el create mode 100644 jode/project-ext.dtd create mode 100644 jode/project.dtd create mode 100644 jode/props/net/sf/jode/decompiler/OptionNames.properties create mode 100644 jode/props/net/sf/jode/swingui/Resources.properties create mode 100644 jode/props/net/sf/jode/swingui/Resources_de.properties create mode 100755 jode/scripts/addHeader.pl create mode 100644 jode/scripts/createStackDelta.pl create mode 100755 jode/scripts/javaDependencies.pl create mode 100755 jode/scripts/jcpp.pl create mode 100644 jode/scripts/php2html.pl create mode 100644 jode/src/net/sf/jode/GlobalOptions.java create mode 100644 jode/src/net/sf/jode/bytecode/BasicBlockReader.java create mode 100644 jode/src/net/sf/jode/bytecode/BasicBlockWriter.java create mode 100644 jode/src/net/sf/jode/bytecode/BasicBlocks.java create mode 100644 jode/src/net/sf/jode/bytecode/BinaryInfo.java create mode 100644 jode/src/net/sf/jode/bytecode/Block.java create mode 100644 jode/src/net/sf/jode/bytecode/ClassFormatException.java create mode 100644 jode/src/net/sf/jode/bytecode/ClassInfo.java create mode 100644 jode/src/net/sf/jode/bytecode/ClassPath.java create mode 100644 jode/src/net/sf/jode/bytecode/ConstantInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/ConstantPool.java create mode 100644 jode/src/net/sf/jode/bytecode/FieldInfo.java create mode 100644 jode/src/net/sf/jode/bytecode/GrowableConstantPool.java create mode 100644 jode/src/net/sf/jode/bytecode/Handler.java create mode 100644 jode/src/net/sf/jode/bytecode/IncInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/Instruction.java create mode 100644 jode/src/net/sf/jode/bytecode/LocalVariableInfo.java create mode 100644 jode/src/net/sf/jode/bytecode/MethodInfo.java create mode 100644 jode/src/net/sf/jode/bytecode/Opcodes.java create mode 100644 jode/src/net/sf/jode/bytecode/Reference.java create mode 100644 jode/src/net/sf/jode/bytecode/ReferenceInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/SlotInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/SwitchInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/TypeDimensionInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/TypeInstruction.java create mode 100644 jode/src/net/sf/jode/bytecode/TypeSignature.java create mode 100644 jode/src/net/sf/jode/bytecode/package.html create mode 100644 jode/src/net/sf/jode/decompiler/Analyzer.java create mode 100644 jode/src/net/sf/jode/decompiler/Applet.java create mode 100644 jode/src/net/sf/jode/decompiler/ClassAnalyzer.java create mode 100644 jode/src/net/sf/jode/decompiler/ClassDeclarer.java create mode 100644 jode/src/net/sf/jode/decompiler/Declarable.java create mode 100644 jode/src/net/sf/jode/decompiler/Decompiler.java create mode 100644 jode/src/net/sf/jode/decompiler/FieldAnalyzer.java create mode 100644 jode/src/net/sf/jode/decompiler/ImportHandler.java create mode 100644 jode/src/net/sf/jode/decompiler/LocalInfo.java create mode 100644 jode/src/net/sf/jode/decompiler/Main.java create mode 100644 jode/src/net/sf/jode/decompiler/MethodAnalyzer.java create mode 100644 jode/src/net/sf/jode/decompiler/Opcodes.java create mode 100644 jode/src/net/sf/jode/decompiler/Options.java create mode 100644 jode/src/net/sf/jode/decompiler/OuterValueListener.java create mode 100644 jode/src/net/sf/jode/decompiler/OuterValues.java create mode 100644 jode/src/net/sf/jode/decompiler/ProgressListener.java create mode 100644 jode/src/net/sf/jode/decompiler/Scope.java create mode 100644 jode/src/net/sf/jode/decompiler/TabbedPrintWriter.java create mode 100644 jode/src/net/sf/jode/decompiler/Window.java create mode 100644 jode/src/net/sf/jode/expr/ArrayLengthOperator.java create mode 100644 jode/src/net/sf/jode/expr/ArrayLoadOperator.java create mode 100644 jode/src/net/sf/jode/expr/ArrayStoreOperator.java create mode 100644 jode/src/net/sf/jode/expr/BinaryOperator.java create mode 100644 jode/src/net/sf/jode/expr/CheckCastOperator.java create mode 100644 jode/src/net/sf/jode/expr/CheckNullOperator.java create mode 100644 jode/src/net/sf/jode/expr/ClassFieldOperator.java create mode 100644 jode/src/net/sf/jode/expr/CombineableOperator.java create mode 100644 jode/src/net/sf/jode/expr/CompareBinaryOperator.java create mode 100644 jode/src/net/sf/jode/expr/CompareToIntOperator.java create mode 100644 jode/src/net/sf/jode/expr/CompareUnaryOperator.java create mode 100644 jode/src/net/sf/jode/expr/ConstOperator.java create mode 100644 jode/src/net/sf/jode/expr/ConstantArrayOperator.java create mode 100644 jode/src/net/sf/jode/expr/ConvertOperator.java create mode 100644 jode/src/net/sf/jode/expr/Expression.java create mode 100644 jode/src/net/sf/jode/expr/FieldOperator.java create mode 100644 jode/src/net/sf/jode/expr/GetFieldOperator.java create mode 100644 jode/src/net/sf/jode/expr/IIncOperator.java create mode 100644 jode/src/net/sf/jode/expr/IfThenElseOperator.java create mode 100644 jode/src/net/sf/jode/expr/InstanceOfOperator.java create mode 100644 jode/src/net/sf/jode/expr/InvokeOperator.java create mode 100644 jode/src/net/sf/jode/expr/LValueExpression.java create mode 100644 jode/src/net/sf/jode/expr/LocalLoadOperator.java create mode 100644 jode/src/net/sf/jode/expr/LocalStoreOperator.java create mode 100644 jode/src/net/sf/jode/expr/LocalVarOperator.java create mode 100644 jode/src/net/sf/jode/expr/MatchableOperator.java create mode 100644 jode/src/net/sf/jode/expr/MonitorEnterOperator.java create mode 100644 jode/src/net/sf/jode/expr/MonitorExitOperator.java create mode 100644 jode/src/net/sf/jode/expr/NewArrayOperator.java create mode 100644 jode/src/net/sf/jode/expr/NewOperator.java create mode 100644 jode/src/net/sf/jode/expr/NoArgOperator.java create mode 100644 jode/src/net/sf/jode/expr/NopOperator.java create mode 100644 jode/src/net/sf/jode/expr/Operator.java create mode 100644 jode/src/net/sf/jode/expr/OuterLocalOperator.java create mode 100644 jode/src/net/sf/jode/expr/PopOperator.java create mode 100644 jode/src/net/sf/jode/expr/PrePostFixOperator.java create mode 100644 jode/src/net/sf/jode/expr/PutFieldOperator.java create mode 100644 jode/src/net/sf/jode/expr/ShiftOperator.java create mode 100644 jode/src/net/sf/jode/expr/SimpleOperator.java create mode 100644 jode/src/net/sf/jode/expr/StoreInstruction.java create mode 100644 jode/src/net/sf/jode/expr/StringAddOperator.java create mode 100644 jode/src/net/sf/jode/expr/ThisOperator.java create mode 100644 jode/src/net/sf/jode/expr/UnaryOperator.java create mode 100644 jode/src/net/sf/jode/flow/BreakBlock.java create mode 100644 jode/src/net/sf/jode/flow/BreakableBlock.java create mode 100644 jode/src/net/sf/jode/flow/CaseBlock.java create mode 100644 jode/src/net/sf/jode/flow/CatchBlock.java create mode 100644 jode/src/net/sf/jode/flow/CombineIfGotoExpressions.java create mode 100644 jode/src/net/sf/jode/flow/CompleteSynchronized.java create mode 100644 jode/src/net/sf/jode/flow/ConditionalBlock.java create mode 100644 jode/src/net/sf/jode/flow/ContinueBlock.java create mode 100644 jode/src/net/sf/jode/flow/CreateAssignExpression.java create mode 100644 jode/src/net/sf/jode/flow/CreateCheckNull.java create mode 100644 jode/src/net/sf/jode/flow/CreateClassField.java create mode 100644 jode/src/net/sf/jode/flow/CreateConstantArray.java create mode 100644 jode/src/net/sf/jode/flow/CreateExpression.java create mode 100644 jode/src/net/sf/jode/flow/CreateForInitializer.java create mode 100644 jode/src/net/sf/jode/flow/CreateIfThenElseOperator.java create mode 100644 jode/src/net/sf/jode/flow/CreateNewConstructor.java create mode 100644 jode/src/net/sf/jode/flow/CreatePrePostIncExpression.java create mode 100644 jode/src/net/sf/jode/flow/DescriptionBlock.java create mode 100644 jode/src/net/sf/jode/flow/EmptyBlock.java create mode 100644 jode/src/net/sf/jode/flow/FinallyBlock.java create mode 100644 jode/src/net/sf/jode/flow/FlowBlock.java create mode 100644 jode/src/net/sf/jode/flow/IfThenElseBlock.java create mode 100644 jode/src/net/sf/jode/flow/InstructionBlock.java create mode 100644 jode/src/net/sf/jode/flow/InstructionContainer.java create mode 100644 jode/src/net/sf/jode/flow/JsrBlock.java create mode 100644 jode/src/net/sf/jode/flow/Jump.java create mode 100644 jode/src/net/sf/jode/flow/LoopBlock.java create mode 100644 jode/src/net/sf/jode/flow/RetBlock.java create mode 100644 jode/src/net/sf/jode/flow/ReturnBlock.java create mode 100644 jode/src/net/sf/jode/flow/SequentialBlock.java create mode 100644 jode/src/net/sf/jode/flow/SlotSet.java create mode 100644 jode/src/net/sf/jode/flow/SpecialBlock.java create mode 100644 jode/src/net/sf/jode/flow/StructuredBlock.java create mode 100644 jode/src/net/sf/jode/flow/SwitchBlock.java create mode 100644 jode/src/net/sf/jode/flow/SynchronizedBlock.java create mode 100644 jode/src/net/sf/jode/flow/ThrowBlock.java create mode 100644 jode/src/net/sf/jode/flow/TransformConstructors.java create mode 100644 jode/src/net/sf/jode/flow/TransformExceptionHandlers.java create mode 100644 jode/src/net/sf/jode/flow/TryBlock.java create mode 100644 jode/src/net/sf/jode/flow/VariableSet.java create mode 100644 jode/src/net/sf/jode/flow/VariableStack.java create mode 100644 jode/src/net/sf/jode/jvm/CodeVerifier.java create mode 100644 jode/src/net/sf/jode/jvm/Interpreter.java create mode 100644 jode/src/net/sf/jode/jvm/InterpreterException.java create mode 100644 jode/src/net/sf/jode/jvm/NewObject.java create mode 100644 jode/src/net/sf/jode/jvm/RuntimeEnvironment.java create mode 100644 jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java create mode 100644 jode/src/net/sf/jode/jvm/SyntheticAnalyzer.java create mode 100644 jode/src/net/sf/jode/jvm/Value.java create mode 100644 jode/src/net/sf/jode/jvm/VerifyException.java create mode 100644 jode/src/net/sf/jode/obfuscator/ClassBundle.java create mode 100644 jode/src/net/sf/jode/obfuscator/ClassIdentifier.java create mode 100644 jode/src/net/sf/jode/obfuscator/CodeAnalyzer.java create mode 100644 jode/src/net/sf/jode/obfuscator/CodeTransformer.java create mode 100644 jode/src/net/sf/jode/obfuscator/ConstantRuntimeEnvironment.java create mode 100644 jode/src/net/sf/jode/obfuscator/FieldIdentifier.java create mode 100644 jode/src/net/sf/jode/obfuscator/Identifier.java create mode 100644 jode/src/net/sf/jode/obfuscator/IdentifierMatcher.java create mode 100644 jode/src/net/sf/jode/obfuscator/LocalIdentifier.java create mode 100644 jode/src/net/sf/jode/obfuscator/Main.java create mode 100644 jode/src/net/sf/jode/obfuscator/MethodIdentifier.java create mode 100644 jode/src/net/sf/jode/obfuscator/OptionHandler.java create mode 100644 jode/src/net/sf/jode/obfuscator/PackageIdentifier.java create mode 100644 jode/src/net/sf/jode/obfuscator/ParseException.java create mode 100644 jode/src/net/sf/jode/obfuscator/Renamer.java create mode 100644 jode/src/net/sf/jode/obfuscator/ScriptParser.java create mode 100644 jode/src/net/sf/jode/obfuscator/TranslationTable.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/ConstantAnalyzer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/IdentityRenamer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/KeywordRenamer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/LocalOptimizer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/LocalizeFieldTransformer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/ModifierMatcher.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/MultiIdentifierMatcher.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/NameSwapper.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/SerializePreserver.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/SimpleAnalyzer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/StrongRenamer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/UniqueRenamer.java create mode 100644 jode/src/net/sf/jode/obfuscator/modules/WildCard.java create mode 100644 jode/src/net/sf/jode/overview.html create mode 100644 jode/src/net/sf/jode/swingui/ClassPathDialog.java create mode 100644 jode/src/net/sf/jode/swingui/HierarchyTreeModel.java create mode 100644 jode/src/net/sf/jode/swingui/Main.java create mode 100644 jode/src/net/sf/jode/swingui/PackagesTreeModel.java create mode 100644 jode/src/net/sf/jode/type/ArrayType.java create mode 100644 jode/src/net/sf/jode/type/ClassInfoType.java create mode 100644 jode/src/net/sf/jode/type/ClassType.java create mode 100644 jode/src/net/sf/jode/type/GenericParameterType.java create mode 100644 jode/src/net/sf/jode/type/IntegerType.java create mode 100644 jode/src/net/sf/jode/type/MethodType.java create mode 100644 jode/src/net/sf/jode/type/MultiClassType.java create mode 100644 jode/src/net/sf/jode/type/NullType.java create mode 100644 jode/src/net/sf/jode/type/ParameterType.java create mode 100644 jode/src/net/sf/jode/type/RangeType.java create mode 100644 jode/src/net/sf/jode/type/ReferenceType.java create mode 100644 jode/src/net/sf/jode/type/SystemClassType.java create mode 100644 jode/src/net/sf/jode/type/Type.java create mode 100644 jode/src/net/sf/jode/util/ArrayEnum.java create mode 100644 jode/src/net/sf/jode/util/SimpleMap.java create mode 100644 jode/src/net/sf/jode/util/SimpleSet.java create mode 100644 jode/src/net/sf/jode/util/StringQuoter.java create mode 100644 jode/src/net/sf/jode/util/UnifyHash.java create mode 100644 jode/test/.cvsignore create mode 100644 jode/test/AnonymousClass.java create mode 100644 jode/test/AnonymousJavac.java create mode 100644 jode/test/ArrayCloneTest.java create mode 100644 jode/test/ArrayTest.java create mode 100644 jode/test/AssignOp.java create mode 100644 jode/test/Base.j create mode 100644 jode/test/Child.j create mode 100644 jode/test/ClassOpTest.java create mode 100644 jode/test/ConstantTypes.java create mode 100644 jode/test/CountOpcodes.java create mode 100644 jode/test/EvilTypes.j create mode 100644 jode/test/Expressions.java create mode 100644 jode/test/Flow.java create mode 100644 jode/test/For.java create mode 100644 jode/test/HintTypeTest.java create mode 100644 jode/test/IfCombine.java create mode 100644 jode/test/InlineTest.java create mode 100644 jode/test/InlinedAnon.java create mode 100644 jode/test/InnerClass.java create mode 100644 jode/test/InnerCompat.java create mode 100644 jode/test/JavacBug.java create mode 100644 jode/test/JsrTest.j create mode 100644 jode/test/LocalTypes.java create mode 100644 jode/test/Makefile.am create mode 100644 jode/test/MethodScopedClass.java create mode 100644 jode/test/NestedAnon.java create mode 100644 jode/test/ObfuscateStrings.j create mode 100644 jode/test/OptimizeTest.java create mode 100644 jode/test/OptimizerTest.java create mode 100644 jode/test/PrivateHideTest.java create mode 100644 jode/test/RemovePopExample.j create mode 100644 jode/test/ResolveConflicts.java create mode 100644 jode/test/StackOps.j create mode 100644 jode/test/TriadicExpr.java create mode 100644 jode/test/TryCatch.java create mode 100644 jode/test/Unreach.java create mode 100644 jode/test/innerclasses/CheckPrivate.java create mode 100644 jode/test/innerclasses/CheckSuperRemove.java create mode 100644 jode/test/removepop.jodescript create mode 100755 jode/test/simpletests.sh create mode 100644 jode/test/src/net/sf/jode/bytecode/BasicBlockWriterTest.java create mode 100644 jode/test/src/net/sf/jode/bytecode/BasicBlocksTest.java create mode 100644 jode/test/src/net/sf/jode/bytecode/BlockTest.java create mode 100644 jode/test/src/net/sf/jode/flow/TrExcTest.java create mode 100644 jode/test/src/net/sf/jode/obfuscator/modules/ConstAnaTest.java diff --git a/jode/.cvsignore b/jode/.cvsignore new file mode 100644 index 0000000..92379e3 --- /dev/null +++ b/jode/.cvsignore @@ -0,0 +1,9 @@ +Makefile +Makefile.in +configure +config.log +config.cache +config.status +stamp-h +libtool +aclocal.m4 diff --git a/jode/AUTHORS b/jode/AUTHORS new file mode 100644 index 0000000..0f30348 --- /dev/null +++ b/jode/AUTHORS @@ -0,0 +1 @@ +Jochen Hoenicke diff --git a/jode/COPYING b/jode/COPYING new file mode 100644 index 0000000..eeb586b --- /dev/null +++ b/jode/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/jode/COPYING.LESSER b/jode/COPYING.LESSER new file mode 100644 index 0000000..b1e3f5a --- /dev/null +++ b/jode/COPYING.LESSER @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/jode/COPYING.LGPL b/jode/COPYING.LGPL new file mode 100644 index 0000000..b1e3f5a --- /dev/null +++ b/jode/COPYING.LGPL @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/jode/ChangeLog b/jode/ChangeLog new file mode 100644 index 0000000..9b1f1cb --- /dev/null +++ b/jode/ChangeLog @@ -0,0 +1,563 @@ +2005-10-14 Jochen Hoenicke + + * src/net/sf/jode/flow/TransformConstructor.java: + (lookForConstructorCall) Check for isStatic before setting + outer $this reference + (reported by Andreas Salathé, bug #1306688) + +2005-09-13 Jochen Hoenicke + + Check for NullPointer in SyntheticAnalyzer. Based on + patch suggessted by Peter Klauser (klp at users.sf.net). + + * src/net/sf/jode/jvm/SyntheticAnalyzer.java: + (checkStaticAccess): Check refField for null pointer. + (checkAccess): Likewise. + +2004-08-06 Jochen Hoenicke + + * src/net/sf/jode/bytecode/BinaryInfo.java (ACC_*): added + constants describing modifier attributes. + * src/net/sf/jode/bytecode/BasicBlockReader.java + (convertHandlers): remove empty handlers. + (readCode): merge adjacent try-blocks (splitted by javac-1.4 + return rule). + * src/net/sf/jode/bytecode/FieldInfo.java (syntheticFlag): + removed, use modifier and ACC_SYNTHETIC (new in java 5) instead. + Changed all usages. When writing it currently writes out both + old and new synthetic format. + (getSignature): New method to return full generic signature. + * src/net/sf/jode/bytecode/MethodInfo.java + (syntheticFlag, getSignature): likewise. + * src/net/sf/jode/bytecode/ClassInfo.java (getSignature): + New method to return full generic signature. + * src/net/sf/jode/decompiler/MethodAnalyzer.java (skipWriting): + Skip java 5 bridge methods. + * src/net/sf/jode/expr/InvokeOperator.java (getClassAnalyzer): + Check for null callee. + * src/net/sf/jode/expr/FlowBlock.java (analyze): New order for + T1,T2 analysis: Do not do T1 analysis when the block has more + than one real successor and the next block can be easily merged. + See comment for more information. + +2004-08-05 Jochen Hoenicke + + * build.xml: replace execon with apply. + * src/net/sf/jode/bytecode/ClassInfo.java (readAttributes): + read in signature attribute (not yet published, though). + * src/net/sf/jode/bytecode/MethodInfo.java (readAttributes): + likewise. + * src/net/sf/jode/bytecode/FieldInfo.java (readAttributes): + likewise. + * src/net/sf/jode/bytecode/ClassInfo.java (mergeModifiers): + only check the traditional modifiers for equality. + * src/net/sf/jode/bytecode/ConstantPool.java (getConstant): + Support for CLASS constants (jdk1.5) added. + * src/net/sf/jode/bytecode/BasicBlockReader.java (readCode): + opc_ldc, opc_ldc_w: Support for CLASS constants added. + * src/net/sf/jode/decompiler/Opcodes.java (addOpcode): + likewise. + * src/net/sf/jode/expr/InvokeOperator.java + (simplifyStringBuffer, simplifyString): + Also handle StringBuilder (jdk1.5). + * src/net/sf/jode/type/Type.java (tStringBuilder): new field. + * src/net/sf/jode/swingui/Main.java (main): handle debug + options. + + +2004-01-31 Jochen Hoenicke + + * src/net/sf/jode/jvm/SyntheticAnalyzer.java (checkGetClass): + Handle jdk1.4 class$ methods. + + * src/net/sf/jode/jvm/RuntimeEnvironment.java: Fixed some javadocs. + * src/net/sf/jode/flow/CompleteSynchronized.java: likewise. + * src/net/sf/jode/flow/CreateExpression.java: likewise. + * src/net/sf/jode/flow/CreateIfThenElseOperator.java: likewise. + + Added changes (except obfuscator changes) from jode-1.1 tree up to + 2001-07-08 + + * src/net/sf/jode/bytecode/ClassInfo.java (deprecatedFlag): Added + flag for deprecated classes. Stuart Ballard noticed that this was + missing. + (readAttribute): Read deprecated attribute. + (prepareWriting): Prepare deprecated attribute. + (writeKnownAttributes): Write deprecated attribute. + (isDeprected): New function. + (setDeprecated): Likewise. + + * src/net/sf/jode/bytecode/BasicBlockReader.java (readCode): Fix + the exception handlers that javac 1.4 produces: I simply shorten + the start/end interval, so that the catcher is not in the end + interval. + + * src/net/sf/jode/flow/CreateAssignExpression.java + (createAssignOp): Bug fix: Check whether store is already a + op-assign and break out. + * src/net/sf/jode/expr/StoreInstruction.java (isOpAssign): New + function to check whether this is an op-assign. + + * src/net/sf/jode/flow/CatchBlock.java (combineLocal): Added more + checks if LocalStoreOperator is of the right form. + + * net/sf/jode/flow/TransformConstructors.java (Constructor): Ignore + OuterValues for static constructor. + + * src/net/sf/jode/expr/CompareToIntOperator.java (dumpExpression): + Added a missing breakOp. + +2004-01-22 Jochen Hoenicke + + * net/sf/jode/jvm/CodeVerifier.java (modelEffect): Allow assigning + fields in an uninitialized class as some synthetic code does this. + +2003-06-11 Mark Morschhäuser + * net/sf/jode/decompiler/Main.java: New MenuItem to save a decompiled file. + + * net/sf/jode/decompiler/Main.java: Main-window will be centered on startup + + * build.xml: + (release): Added MANIFEST.MF to target and enabled compressed jar-file + + * MANIFEST.MF: Added this file to be able to create an executable jar-file + +2002-06-11 Jochen Hoenicke + + * net/sf/jode/decompiler/Main.java: New option keep-alive. With + this option jode won't stop after an error but will continue with + the next class. + Patch suggested by Francis Devereux, francis at hc.eclipse.co.uk + +2002-02-25 Jochen Hoenicke + + * jode/bytecode/ClassInfo.java.in (read): Don't check for a + maximum version anymore. Sun changes it with every release without + changing the bytecode format. + +2002-02-15 Jochen Hoenicke + + * net/sf/jode/bytecode/BasicBlockReader.java: handle empty loops. + (IS_NULL): new constant to tag empty blocks. + (markReachableBlocks): check for empty loops. + (convertBlock): Handle empty blocks. + (convert): Handle IS_NULL. + + * net/sf/jode/decompiler/MethodAnalyzer.java: + (analyzeCode): handle empty blocks. + +2001-08-14 Jochen Hoenicke + + * build.xml: test is default. + (release-javadoc): New target. + (release-src): Get from dir test only source files. + (doc-javadoc): More parameters for nicer docu. + +2001-08-12 Jochen Hoenicke + + * net/sf/jode/bytecode/TypeSignature.java: + (getArgumentSize): Renamed to ... + (getParameterSize): ... this. Changed all callers. + (skipType): Made private. + + * net/sf/jode/jvm/CodeVerifier.java: + (initInfo): Use TypeSignature.getParameterTypes instead of skipType. + + * net/sf/jode/jvm/SyntheticAnalyzer.java: + (checkGetClass): Be more lenient with the types, they are already + checked by the CodeVerifier. This is to support jdk-1.4. + + * net/sf/jode/expr/InvokeOperator.java + (dumpExpression): Fixed the check for null outerExpr. + + * net/sf/jode/flow/FlowBlock.java: + (checkConsistent): Allow lastModified in a finally block. + * net/sf/jode/flow/TransformExceptionHandlers.java: Reworked exception + handlers again. This time checked with javac 1.3, javac 1.1 and + jikes. + (checkTryCatchOrder): New method that was previously part of + analyze. + (analyze): Use checkTryCatchOrder. Don't merge try and catch flow + blocks anymore, leave it to the analyzeXXX methods. + (mergeTryCatch): New method. + (analyzeCatchBlock): Get catchFlow as parameter. Call + mergeTryCatch. + (transformSubroutine): Handle POP-only subroutines. + (removeJSR): Don't do special case for catchBlock any more. This + is because catchFlow isn't yet merged when this method is called. + (checkAndRemoveJSR): Likewise. + (checkAndRemoveMonitorExit): Likewise. Merge subroutine only if + we are the only predecessor. + (analyzeSynchronized): Get catchFlow as parameter. Call + mergeTryCatch. + (mergeFinallyBlocks): New method, calls mergeTryCatch and does the + common part of mergeFinally and mergeSpecialFinally. + (analyzeFinally): Simplified, after checking and removing JSR, it + does immediately analyze and transform subroutine to get the + finallyBlock. Then it throws away the catchFlow and calls + mergeFinallyBlocks. + (analyzeSpecialFinally): Simplified, after checking it only handles + the jumps in the try part and then call mergeFinallyBlocks. + +2001-08-08 Jochen Hoenicke + More Documentation updates. + * build.xml: Release rules. + * scripts/jcpp.pl: Don't make backups of original. + * net/sf/jode/bytecode/BasicBlocks.java (setBlocks): Check that + successors are inside method. + * net/sf/jode/bytecode/Block.java (getStackHeight): New Method. + * net/sf/jode/bytecode/ClassPath.java (Location): public class to + model a component of the class path. Previously it was Path. + (ClassPath): New constructors added that take Location objects. + * net/sf/jode/bytecode/ConstantPool.java (getClassName): Cache + constants. + * net/sf/jode/bytecode/GrowableConstantPool.java: Made public. + (grow): Check that not too many constants are added. + (reserveLongConstants): Removed (not used). + (copyConstant): Removed (not used). + * net/sf/jode/jvm/NewObject.java: Made package protected. + * net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java: + Big updates (almost rewrote from scratch). Still doesn't compile. + +2001-08-05 Jochen Hoenicke + + Documentation updates (INSTALL, javadoc). + Added JUnit Test cases. + * build.xml: Big update. + * net/sf/jode/bytecode/BasicBlock.java: + (updateMaxStackLocals): new method to calculate maxStack and + maxLocals. + (setBlocks): fixed calculation of handlers, call updateMaxLocals. + * net/sf/jode/bytecode/BasicBlockReader.java: + (maxLocals, maxStack): new fields. + (readCode): read maxStack/Locals into private fields. + (convert): check that maxStack/Locals match what we calculate. + * net/sf/jode/bytecode/BinaryInfo.java: + (getKnownAttributeCount): renamed to... + (getAttributeCount): ... this, and also count internal attributes. + Made it protected. + (readAttribute): made protected. + (drop): made protected. + (prepareAttributes): made protected. + (writeKnownAttributes): removed. + (writeAttributes): made protected, use getAttributeCount. + Changed policy: it doesn't call writeKnownAttribute, but instead + it expects sub classes to override this method. + (getAttributeSize): made protected, subclasses should override it. + Changed all subclasses to new policy. + * net/sf/jode/bytecode/Block.java: + (lineNr): Removed, it wasn't used. + (pop,push): Removed, replaced by ... + (maxpop,maxpush,delta): ... these, with slightly changed semantics. + (stackHeight): New variable. + (Block): Default Constructor doesn't initialize fields now. + (getCatchers): Renamed to ... + (getHandlers): ... this, changed all callers. + (initCode): Calculate maxpop, maxpush, delta correctly. + (getStackPopPush): Changed accordingly to new fields. + (setCode): Removed debugging output for illegal contents. + * net/sf/jode/bytecode/Classes.java: Reworked handling of inner + classes. + (innerClasses): Field mustn't be null anymore when loaded. + (setName): Update class in classpath. + * net/sf/jode/bytecode/ClassPath.java: + (renameClassInfo): new function, should only used by ClassInfo. + * net/sf/jode/bytecode/ConstantPool.java: made public. + (getUTF8,getRef,getClassType,getClassName): Don't allow the 0 index. + (iterateClassNames): New method. + * net/sf/jode/decompiler/Main.java: + (decompileClass): Catch ClassFormatExceptions and decompile + remaining classes. + * net/sf/jode/obfuscator/ClassIdentifier.java: + Updated handling of inner/extra classes to new ClassInfo behaviour. + (initSuperClasses): Load DECLARATION of super classes. + * net/sf/jode/obfuscator/PackageIdentifier.java: + Replace deprecated methods of ClassInfo with corresponding classpath + calls. + (loadMatchingClasses): Initialize packages loaded on demand if we + are initialize. + * net/sf/jode/obfuscator/modules/ConstantAnalyzer.java: + Now extends SimpleAnalyzer. + (canonizeIfaceRef): Removed; it is now inherited. + (canonizeRef): likewise. + Big updates to handle jsr correctly. + (handleOpcode): Moved method to BlockInfo. + * net/sf/jode/obfuscator/modules/SimpleAnalyzer.java: + (canonizeIfaceRef): New method, copied from ConstantAnalyzer. + (canonizeRef): call canonizeIfaceRef for interfaces. + * net/sf/jode/util/UnifyHash.java + (iterateHashCode): iterator now supports remove(). + (remove): New method. + +2001-07-30 Jochen Hoenicke + + Changed compilation procedure to ant. + +2001-07-30 Jochen Hoenicke + + * jode/bytecode/BasicBlockReader.java: Fixed import of non + collection java.util classes. + * jode/bytecode/BasicBlockWriter.java: likewise. + +2001-07-28 Jochen Hoenicke + + * jode/AssertError.java: removed, all uses are now replaced + by java.lang.InternalError. + * jode/Makefile.am: removed AssertError.java + * jode/bytecode/ClassInfo.java: reworked handling of inner + classes. + (extraClasses): removed, they are calculated automatically. + (hasInnerClassesAttr): new variable. + (readInnerClassesAttribute): Mark all classes in the constant + pool as having OUTERCLASS info filled. Don't handle extraClasses + specially. + (prepareWriting): Change for automatically generating outer + class info. + (getKnownAttributes): dito. + (writeKnownAttributes): dito. + (getExtraClasses): removed. + (setExtraClasses): removed. + + * jode/bytecode/ClassAnalyzer.java (conflicts): load or guess + declarations of info before getting inner classes. + * jode/decompiler/TabbedPrintWriter.java (BreakPoint.endOp): + Set options correctly. + * jode/expr/InvokeOperator.java (getMethodInfo): load or guess + declarations before accessing methods. + * jode/flow/FlowBlock.java (resolveSomeJumps): When creating a + if-then-else move the jump from the then branch to the if, before + restarting analysis. + (doT1): handle the case when lastModified.jump is null. Throw + statements have no jump now. + * jode/jvm/SyntheticAnalyzer (checkAccess): Fix the detection for + PUTDUPSTATIC/FIELD. + * jode/type/ClassType.java (getCastHelper): More checks when + cast is not needed: interfaces and null pointer. + +2001-07-15 Jochen Hoenicke + * jode/decompiler/Decompiler.java (decompile): removed + setClassPath call. ClassInfo.forName() is no longer used. + * jode/decompiler/Main.java (decompile): likewise. + +2001-07-15 Jochen Hoenicke + Applied patches from 2001-05-26 of Jode 1.1 tree: + * configure.in: Set version to 1.1. + + * jode/swingui/Main.java (main): Also use bootclasspath if no + classpath given. + + * jode/decompiler/MethodAnalyzer.java (skipWriting): Don't skip + empty constructor that have a throws clause. + + * configure.in: Determine whether jdk1.1 resp. jdk1.2. Call jcpp + in config.status. + + * jode/expr/Expression.java (makeInitializer): Now takes the + type of the initialization. Changed all callers. + * jode/expr/ConstantArrayOperator.java (makeInitializer): Check + that type is our array type, otherwise we can't omit new Array[]. + + * jode/decompiler/LocalInfo.java (markFinal): Don't check that + only one write is present. If two writes are in an then and an + else branch of an if, the local can still be final. + + * jode/type/ArrayType.java (getSubType): Handle array of integer + types correctly: byte[] is something completely different than + int[]. + (getSuperType): Likewise. + + * jode/expr/FieldOperator.java (getFieldInfo): New function. + (needsCast): A cast is also needed if the field is private or + package scope and the current type can't access the field. + + * jode/expr/InvokeOperator.java (getMethodInfo): New function. + (needsCast): A cast is also needed if the method is private or + package scope and the current type can't access the method. + + * jode/expr/ArrayStoreOperator.java (dumpExpression): Check if a + cast of the array expression is needed. + + * jode/expr/TransformConstructors.java + (transformFieldInitializers): Don't allow moving method invocations + that throw a checked exception. + + * jode/bytecode/MethodInfo.java (readAttribute): Read Exceptions + attribute even when not all attributes should be read. They are + needed by TransformConstructors, see above. + + * jode/decompiler/TabbedPrintWriter.java (saveOps): Don't allow + line breaks in not completed expressions since implicit parentheses + would destroy the syntax. No need to put line break option on stack. + (restoreOps): Adapted Stack format. + + * jode/decompiler/ClassAnalyzer.java (dumpDeclaration): Moved + Code from dumpSource here. Don't put a line break after closing + brace. + (dumpSource): call dumpDeclaration and add a line break. + (dumpBlock): Moved dropInfo(ATTRIBS) here. + + * jode/decompiler/ClassAnalyzer.java (STRICTFP): New Constant. + (isStrictFP): New function. + (initialize): Set strictfp modifier if a constructor has it set. + (dumpSource): Handle strictfp modifier. + + * jode/decompiler/MethodAnalyzer.java (STRICTFP): New Constant. + (isStrictFP): New function. + (dumpSource): Handle strictfp modifier. + + * jode/jvm/SyntheticAnalyzer.java (checkAccess): Check for a + special putfield access, where the set value is returned. Allow + the modifier of field/method to be protected and the class to be + a superclass. + (checkStaticAccess): Likewise. + (ACCESSDUPPUTFIELD): New Constant. + (ACCESSDUPPUTSTATIC): New Constant. + + * jode/expr/InvokeOperator.java (simplifyAccess): Handle new + synthetics. + + * jode/flow/SpecialBlock.java (removePop): Remove pop also for + non void store instructions. + + * jode/decompiler/MethodAnalyzer.java (skipWriting): Also skip + the new synthetics. + + * jode/decompiler/Main.java (main): Call System.exit() after + everything was compiled. + + * jode/flow/TransformExceptionHandlers.java (removeJSR): + Renamed back from removeBadJSR (see patch from 2001-02-04). The + checkAndRemove* functions mustn't change the successors while they + iterate over them. Instead of removing good jsr they mark them as + good and removeJSR will finally remove them. + (checkAndRemoveJSR): See above. + (checkAndRemoveMonitorExit): See above. + + * jode/flow/JsrBlock.java (good): New variable, see above. + (setGood): New method. + (isGood): New method. + +2001-07-15 Jochen Hoenicke + Applied patch from 2001-05-08 of Jode 1.1 tree: + * jode/jvm/CodeVerifier.java (doVerify): Don't check for + uninitialized objects in local or stack slots on backwards jump or + exception blocks. Sun's jdk also doesn't check it, and I never + understood why it is necessary. But see JVM Spec 4.9.4. + +2001-07-15 Jochen Hoenicke + Applied patch from 2001-05-02 of Jode 1.1 tree: + * jode/obfuscator/modules/ConstantAnalyzer.java (handleOpcode): + Added divide by zero checks for opc_irem and opc_lrem. + +2001-07-15 Jochen Hoenicke + Applied patches from 2001-02-27 of Jode 1.1 tree: + + * acinclude.m4 (JODE_CHECK_CLASS): Changed "test -e" to "-f" since + -e is not supported on all architectures (Solaris) and -f is more + correct anyway. + Reported by Erik Modén. + + * jode/swingui/Main.java (AreaWriter): Convert all kinds of + line breaks (CR+LF, CR, LF) to a LF character, which a JTextArea + understands. + +2001-07-15 Jochen Hoenicke + Applied patch from 2001-02-04 of Jode 1.1 tree: + + * jode/expr/IfThenElseOperator.java (simplify): Allow in the class$ + simplification the then and else part to be swapped. + * jode/type/ClassType.java (keywords): Added the package + and import keywords. + + * jode/flow/TransformExceptionHandlers.java: + (getPredecessor): New function. + (getMonitorExitSlot): New function. + (skipFinExitChain): New function. + (removeJSR): Replaced by ... + (removeBadJSR): ... this. + (checkAndRemoveJSR): Use the new functions. Much simpler and + handles nested synchronized blocks. It now traces the whole JSR + and monitorexit chain before a jump to the first entry via + skipFinExitChain, then checks and remove the first JSR + resp. monitorexit. JSR jumps are simply ignored now. + (checkAndRemoveMonitorExit): likewise. + * jode/flow/StructuredBlock.java (prependBlock): New function. + * jode/flow/CatchBlock.java (makeDeclaration): Generate name + of dummyLocal, since nobody else will generate it. + + * jode/bytecode/BasicBlockReader.java (readCode): Remove bogus + exceptionHandlers, whose catchers just throw the exception again. + This kind of entries are inserted by an obfuscator and would break + JODE. + * jode/util/UnifyHash.java (iterateHashCode): Call cleanUp, + to clean unneeded references. + * jode/flow/TransformConstructors.java (transformOneField): + Changed to private. Take field number as parameter. Check that + expression doesn't contain a FieldOperator for a later field of + the same class or a PutFieldOperator. Changed all callers. + +2001-07-15 Jochen Hoenicke + + Applied patch from 2001-02-01 of Jode 1.1 tree: + * jode/jvm/CodeVerifier.java (Type.mergeType): If array elem + types can't be merged, return tObject as common super type. + * jode/type/ArrayType.java (getGeneralizedType): If array elem + type can't be intersected, return tObject as common super type. + +2001-07-15 Jochen Hoenicke + Applied patch from Jode 1.1 tree: + + * jode/expr/Expression.java (updateParentTypes): Call setType, + instead of merging the types. Other childs want to know about the + type change as well. + * jode/decompiler/LocalInfo.java (combineWith): Reorganized a bit, + but no changes. + * jode/expr/InvokeOperator.java (dumpExpression): Always print + the ThisOperator if a field is from a parent class of an outer + class is used. And always qualify the this operator if not + innermost. + +2001-07-14 Jochen Hoenicke + Applied patches from the Jode 1.1 tree: + + * jode/decompiler/TabbedPrintWriter.java: Better gnu style handling: + (openBraceClass) (closeBraceClass) + (openBraceNoIndent) (closeBraceNoIndent): new functions. + (closeBraceNoSpace): Removed. + * jode/decompiler/TabbedPrintWriter.java (GNU_SPACING): new constant. + (printOptionalSpace): Print space for GNU_SPACING. + * jode/decompiler/Options.java (setOptions): changed gnu style + to include GNU_SPACING. + * jode/decompiler/ClassAnalyzer.java (dumpSource): Use + open/closeBraceClass. + * jode/decompiler/MethodAnalyzer.java (dumpSource): Use + open/closeBraceNoIndent. Call printOptionalSpace. + * jode/decompiler/InvokeOperator.java (dumpExpression): + Call printOptionalSpace, use open/closeBraceClass for inner + classes. + * jode/decompiler/UnaryOperator.java (dumpExpression): Call + printOptionalSpace. + + Added pascal style from Rolf Howarth + * jode/decompiler/Decompiler.java (setOption): detect pascal option. + * jode/decompiler/TabbedPrintWriter.java (BRACE_FLUSH_LEFT): + new constant. + (openBrace, openBraceContinue, closeBrace, closeBraceNoSpace, + closeBraceContinue): handle flush left. + + * jode/type/NullType.java (intersection): Removed, since the + version in ReferenceType is more correct. Before + tNull.isOfType(tRange(X,tNull)) returned false, which lead to + incorrect behaviour in InvokeOperator.needsCast. + * jode/decompiler/FieldAnalyzer.java (dumpSource): Removed the + "= null" hack for final fields; it was not correct, since the + field could be initialized in a constructor. + * jode/decompiler/TabbedPrintWriter.java (BreakPoint.endOp): + Simplified the code, copy options always from child. + * jode/expr/InvokeOperator.java (isGetClass): Allow the method to + be declared inside an outer class: We simply check if we can get + the method analyzer. + (simplify): handle unifyParam. + * jode/expr/PopOperator.java (getBreakPenalty): return penalty of + inner expression. (dumpExpression): Call dumpExpression of + subexpression immediately without priority. diff --git a/jode/INSTALL b/jode/INSTALL new file mode 100644 index 0000000..dfce9a9 --- /dev/null +++ b/jode/INSTALL @@ -0,0 +1,23 @@ +Before installing, make sure you have at least version 1.1 of the java +developement kit installed. If you want to run this program you only +need the java runtime environment. Version 1.1 is quite old, I +recommend using Java 2 (jdk1.2 or above). You need perl if you want +to compile a 1.1 version. + +This package was designed to use the ANT from the jakarta.apache.org +tools. I assume you have installed it correctly. + +Take some time to edit config.props. There are a few options you need +to take care of. (Unfortunately ant can't test for executables). + +Now you are ready to invoke ant. There are many possible targets, here +are the most useful ones: + +all builds class files and documentation. +build builds class files only (autodetects java version). +build-1.1 builds JDK1.1 class files. +doc builds documentation. +dist creates all release files. +test does some self tests. You need to have junit installed for this. +clean cleans everything that doesn't belong to the source distribution. +cvsclean cleans everything that doesn't belong into the cvs repository. diff --git a/jode/MANIFEST.MF b/jode/MANIFEST.MF new file mode 100644 index 0000000..f5d4544 --- /dev/null +++ b/jode/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: net.sf.jode.swingui.Main +Created-By: SeeksTheMoon diff --git a/jode/NEWS b/jode/NEWS new file mode 100644 index 0000000..b9f98b0 --- /dev/null +++ b/jode/NEWS @@ -0,0 +1,36 @@ +New in 1.2 +* New bytecode interface +* Faster and better flow analyzation + +New in 1.1 +* break long lines +* handle most of javac v8 constructs (jdk 1.3) +* bug fixes + +New in 1.0.93 +* anonymous and inner class decompilation reworked. +* replaced a bash specific construct in acinclude.m4 +* fixed a funny bug: string += "1" was decompiled as string++ +* main class of decompiler is now jode.decompiler.Main +* fixed a bug in ConstantAnalyzer (obfuscator) (produced wrong code) +* fixed more bugs in obfuscator +* better memory usage in decompiler +* fixed a bug in decompiler (couldn't handle nop instruction) +* web pages updated. + +New in 1.0.92 +* option --pretty works again +* web pages updated +* swingui can show class hierarchie +* KeywordRenamer added to obfuscator. + +New in 1.0.91: +* first version using configure. Jode can now be almost automatically + build, see INSTALL for instructions. +* the decompiler can handler inner and anoymous classes. +* you now need the gnu getopt package. +* you need JDK 1.2 or alternatively the swing and collection packages + for 1.1 + + + diff --git a/jode/README b/jode/README new file mode 100644 index 0000000..8ebabf2 --- /dev/null +++ b/jode/README @@ -0,0 +1,90 @@ +JODE (Java Optimize and Decompile Environment) + +JODE is a java package containing a decompiler and an optimizer for +java. This package is freely available under the GNU General Public +License. + +The decompiler reads in class files and produces something similar to +the original java file. Of course this can't be perfect: There is no +way to produce the comments or the names of local variables (except +when the java files were compiled with `-g') and there are often more +ways to write the same thing. However, jode does its job quite well. + +The optimizer transforms class files in various ways with +can be controlled by a script file. + +Please note that most software licenses forbid to decompile class +files. Use this decompiler only, if you have legal rights to +decompile the class (e.g. on your own code). + +The features of the decompilers are: + + * Systematic flow analysis, that can decompile every java code + without the need of goto (which doesn't exists in java). + * Type deduction, that can guess the type of local variables, even if + it is an interface type that doesn't occur in the bytecode. + * Handling of inner and anonymous classes. + * Different indentation styles available. + * It can decompile itself (194 classes) without a single error. + * It can decrypt strings on the fly, that were encrypted by an obfuscator. + +Known bugs of the decompiler: + + - Some jdk1.3 synthetic access functions aren't understood. The + produced source contains access$xxx functions, but it still compiles. + + - There may be other bugs, that cause Exceptions or invalid code. + If you have such a problems don't hesitate to issue a bug report. + Please include the class file if possible. + +Limitations: + + - If not all dependent classes can be found, the verifier (which is + run before decompilation starts) may exit with a type error. You + can decompile it with --verify=off, but take the warning serious, + that the types may be incorrect. There's sometimes no way to guess + the right type, if you don't have access the full class hierarchie. + + But if you don't have the dependent classes, you can't compile the + code again, anyway, so why do you want to decompile it? + + - There may be situations, where jode doesn't understand complex + expressions. In this case many ugly temporary variables are used, + but the code should still be compileable. This does especially + happen when you compile with `-O' flag and javac has inlined some + methods. + + +The features of the obfuscator are: + * Modular design, you can plug the obfuscation transformation you need + together via the script file. + * Strong analysis, that optimizes away fields, expressions that are + known to be constant (and reverts the flow obfuscation of Zelix + Klassmaster) + * Can also be used as Optimizer, without any obfuscation at all, it + will preserve the local variable table and line number table. + * Many different renaming options, you can also simply add your own. + + +PRELIMINARIES: + +See INSTALL for installation instructions. + +USAGE: + +First set the classpath. It should contain the jar file jode-1.1.jar, +the gnu getopt package and the sun collection package for 1.1. For +the swingui program you also need swing in you classpath. + +You can then decompile a class file with: + java jode.decompiler.Main --classpath program.jar,libfoo.jar org.package.Class +or a complete package with + java jode.decompiler.Main --classpath libfoo.jar program.jar + +For a graphical user interface based on swing try: + java jode.swingui.Main --classpath jarfile1.jar + +The obfuscator/deobfuscator can be run with a script: + java jode.obfuscator.Main obfuscation.jos + +See the web documents for more information about the script syntax. diff --git a/jode/THANKS b/jode/THANKS new file mode 100644 index 0000000..b95fa57 --- /dev/null +++ b/jode/THANKS @@ -0,0 +1,5 @@ +Joe Bronkema +Rolf Howarth for pascal indentaton style. +Erik Modén +Martin Schmitz for finding many bugs in the obfuscator. +zzzeek diff --git a/jode/TODO b/jode/TODO new file mode 100644 index 0000000..d768a38 --- /dev/null +++ b/jode/TODO @@ -0,0 +1,64 @@ +This is a list of features, that would be nice to have: + +Decompiler: + - BUG: public final static null fields aren't initialized (leads to compile error) + - outline inlined methods. + - remove string decrypt method. + - remove synthetic methods if and only if all calls to them are resolved. + - rename keywords to safe names. + ~ handle try catch more thouroughly/safely. + ~ decompile jode.jvm.Interpreter (hand optimized bytecode) + +Obfuscator: + - Detect Class.forName() calls with constant parameters and rename + these constants. Detect class$ methods with constant parameters. + Warn about all other occurences of Class.forName() + - work around Class.forName, by creating a new version using a hash + table that maps md5 sums of old names to obfuscated names. + + This should be put into the constant analyzer. The simple + analyzer should only do the warnings. + - Transforming the class hierarchy, e.g. combining two totally + unrelated classes together into one class or make some class + to implement some interfaces, that it previously didn't. + - Doing flow obfuscation, i.e. do some tests, that one knows to + succeed always, and jump to funny position if the test fails. + The tests should use undecidable properties, so that a + deobfuscator cannot remove them again. + +DeObfuscator: + - Deobfuscator should detect inner/anonymous classes and mark them + as such. It should be possible with the renaming table to mark + inner classes as well. Inner classes are easy to detect; there + constructor has a special form. And the information is very + useful for the decompiler. + + This should be done with some generalize interface similar to (or + instead of) Transformer + + - Deobfuscator should generate nicer names. This should be a + special Renamer. The renamer should analyze short methods and + call them getXXX, isXXX, setXXX if apropriate, detect synthetic + methods and similar. Class names should be derived from super + class or interface (e.g. Enumeration), fields should be derived + from their type, maybe also from their assignments. + + One can build more renamer, each handles some special cases and + calls the next one, if it can't handle an identifier. + +User Interface: + - make a nice user interface: + ~ list classnames: toggable between class hierarchie/package hierarchie. + - list fields/method of selected class. + - show decompilation of selected method. + - show usage of method/fields. + - syntax highlighting, hyper links etc. + (look at java.swing.JEditorPane or at Java Insight) + - as a first approximation use HTML code and a JHTMLPane + - visual obfuscation/deobfuscation (like klassmaster?, better?) + +Internal: + - clean up package hierarchy, esp. expr, flow and decompiler. + - move to net.sf.jode package. + - make the class names more precise, e.g. StructuredBlock is Statement, + FlowBlock is BasicBlock. diff --git a/jode/bin/.cvsignore b/jode/bin/.cvsignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/jode/bin/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/jode/bin/Makefile.am b/jode/bin/Makefile.am new file mode 100644 index 0000000..72799e5 --- /dev/null +++ b/jode/bin/Makefile.am @@ -0,0 +1,5 @@ +## Input file for automake to generate the Makefile.in used by configure + +bin_SCRIPTS = jode +EXTRA_DIST = jode.bat.in jode.in + diff --git a/jode/bin/jode.bat.in b/jode/bin/jode.bat.in new file mode 100644 index 0000000..bcdfe50 --- /dev/null +++ b/jode/bin/jode.bat.in @@ -0,0 +1,19 @@ +; Use this batch file to make invoking the decompiler easier. +; Please edit this file and insert correct directories where needed. +; +; Usage: jode dec [decompiler options] +; jode swi [swingui options] +; jode obf [obfuscator options] +; Since the decompiler is the most important program you can omit `dec': +; jode [decompiler options] + +set CLASSPATH=jode-@VERSION@-1.2.jar;%CLASSPATH% +set PROGGY=default + +if %1 == swi set PROGGY=swingui +if %1 == obf set PROGGY=obfuscator +if %1 == dec set PROGGY=decompiler +if NOT %PROGGY% == default shift +if %PROGGY% == default set PROGGY=decompiler + +java jode.%PROGGY%.Main %1 %2 %3 %4 %5 %6 %7 %8 %9 diff --git a/jode/bin/jode.in b/jode/bin/jode.in new file mode 100644 index 0000000..1d4d31d --- /dev/null +++ b/jode/bin/jode.in @@ -0,0 +1,14 @@ +#!@SHELL@ +prefix=@prefix@ + +case $1 in + [Ss]wi*) CLAZZ=jode.swingui.Main; shift ;; + [Dd]ec*) CLAZZ=jode.decompiler.Main; shift ;; + [Oo]bf*) CLAZZ=jode.obfuscator.Main; shift ;; + *) CLAZZ=jode.decompiler.Main ;; +esac + + +CP=`echo $CLASSPATH | sed s/:/,/` +CLASSPATH=@datadir@/jode-@VERSION@.jar:@CLASSPATH@ \ +@JAVA@ $CLAZZ --classpath $CP $* diff --git a/jode/build.xml b/jode/build.xml new file mode 100644 index 0000000..525fac4 --- /dev/null +++ b/jode/build.xml @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jode/config.props b/jode/config.props new file mode 100644 index 0000000..e9bdf86 --- /dev/null +++ b/jode/config.props @@ -0,0 +1,28 @@ +# Do you have online access for generating javadoc? +# If not, where are your local files. +javadoc.offline=false +javadoc.packagelistLoc= +javadoc.href=http://java.sun.com/products/jdk/1.2/docs/api/ +#javadoc.href=file:/usr/doc/inet/java/jdk1.2/docs/api + +#javadoc.offline=true +#javadoc.packagelistLoc=/usr/doc/inet/java/jdk1.2/docs/api + +# Is Perl installed on your system? +# +# perl is needed to reconfigure Jode for JDK-1.1. If you haven't +# installed it you can only configure for JDK-1.2 and you should +# comment out the next line. +# +# perl is also used for things that are relevant for the maintainer. +# +# Remove the next line if perl is not installed. +perl.present=true + +# Is HTP installed on your system? +# +# htp is needed to generate html files from htp files. +# see http://htp.sourceforge.net/ +# +# Remove the next line if either htp is not installed. +htp.present=true diff --git a/jode/create.sh b/jode/create.sh new file mode 100755 index 0000000..814c947 --- /dev/null +++ b/jode/create.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +create_jar() { +jar -xvf $HOME/java/jars/getopt.jar +rm -rf META-INF +jar -cvf ../jode-1.0.92-$1.jar AUTHORS COPYING README INSTALL NEWS doc/*.{html,jos,perl,gif} `find jode -name \*.class` gnu +rm -rf gnu +} + +create_first() { +# first 1.1 version. +tar -xvzf jode-1.0.92.tar.gz +cd jode-1.0.92 +CLASSPATH=$HOME/java/jars/getopt.jar:/usr/local/1.1collections/lib/collections.jar:/usr/local/swing-1.1/swingall.jar \ + ./configure --with-java=/usr/lib/java --with-jikes=/home/jochen/bin +make +create_jar 1.1 +cd .. +rm -rf jode-1.0.92 +} + +create_second() { +# now 1.2 version. +tar -xvzf jode-1.0.92.tar.gz +cd jode-1.0.92 +find -name \*.java -o -name \*.java.in | xargs jcpp -DJDK12 +CLASSPATH=$HOME/java/jars/getopt.jar \ + ./configure --with-java=/usr/local/jdk1.2 --with-jikes=/home/jochen/bin +make +create_jar 1.2 +cd .. +rm -rf jode-1.0.92 +} + +create_applet() { +cat <jode-applet.jos +# JODE Optimizer Script +strip = "unreach","source","lnt","lvt","inner" +load = new WildCard { value = "jode" }, + new WildCard { value = "gnu" } +preserve = new WildCard { value = "jode.JodeApplet..()V" } +renamer = new StrongRenamer { + charsetStart = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + charsetPart = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$" + charsetPackage = "abcdefghijklmnopqrstuvwxyz" + charsetClass = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +} +analyzer = new ConstantAnalyzer +post = new LocalOptimizer, new RemovePopAnalyzer +EOF + +CLASSPATH=jode-1.0.92-1.1.jar:$CLASSPATH java jode.obfuscator.Main \ + --cp jode-1.0.92-1.1.jar --dest jode-applet.jar jode-applet.jos +} + +#create_first +#create_second +create_applet diff --git a/jode/doc/.cvsignore b/jode/doc/.cvsignore new file mode 100644 index 0000000..2f083ef --- /dev/null +++ b/jode/doc/.cvsignore @@ -0,0 +1,3 @@ +Makefile +Makefile.in +*.html diff --git a/jode/doc/applet.htp b/jode/doc/applet.htp new file mode 100644 index 0000000..f1605f4 --- /dev/null +++ b/jode/doc/applet.htp @@ -0,0 +1,25 @@ +
+ +

Please be patience, loading the applet may take some time.

+ +
+ + + + +

Sorry you need a java enabled browser to test a java applet ;-)

+

Don't read the rest, it only contains information about the applet.

+
+

+ +

Press the start button to decompile Michael's +Plasma applet (and give the decompiler some time to download the +jar file).

+ +

You may change the classpath to point to a zip or jar file of your +choice. Unfortunately, your browser will most likely forbid URL's that +aren't located on jode.sourceforge.net. +Save probably doesn't work, because it is forbidden by your browser.

+ +
diff --git a/jode/doc/bluesky.htp b/jode/doc/bluesky.htp new file mode 100644 index 0000000..b87fc87 --- /dev/null +++ b/jode/doc/bluesky.htp @@ -0,0 +1,69 @@ +
+ +

This section contains features that I think would be great to have, +but are also very hard to implement.

+ +

Currently this are all my own ideas. But if you send me an idea +for an interesting feature, I will add it to this list.

+ +

Outline inlined methods

+ +

If java gets called with `-O' switch, it inlines methods, +that are private, final, or static and contain no loops. When +decompiling this it sometimes produces really ugly code. The right +way to solve this would be to outline the code again. This is +possible but requires to find the inlined method.

+ +

The name `outline' was suggested by Michael. +

+ +

Better names of local variables

+ +

The local variable naming is very stupid. Even with pretty it just +names the variable after the type with a unifying number appended. A +method containing very much objects of the same type looks very +ugly.

+ +

My plan is looking at the assignments. If we have locals in +assignments

+
+int l_1 = array.length
+String l_2 = object.getName()
+
+

we could name them "length" and "name". If we +have assignments:

+
+MenuItem local_1 = new MenuItem("Open");
+MenuItem local_2 = new MenuItem("Save");
+
+

good names would be miOpen and miSave.

+ +

It is currently possible to assign a (hint name,type) pair +to a local. If the type matches, the local will be named after +hint name. This could be extended by giving them several +weighted hints and constructing the name in an intelligent way.

+ +

Better deobfuscation features

+

First there should be a good Renamer: Methods that simply +return a field value should be renamed to getFieldName. +Fields should be named after their type, maybe also by assignments +(see section about local variable names).

+ +

The deobfuscator should detect inner and anonymous variables, +synthetic methods and so on, and rename them accordingly.

+ +

Handling of Class.forName in obfuscator

+

The obfuscator should detect Class.forName constructs (and +similarly for methods and fields) and if their parameters are constant +it should change the parameter according to the rename function.

+ +

Merging javadoc comments

+

It would be nice if the decompiler could merge the javadoc comments +into the class file. More and more people use javadoc to comment the +public api of their java classes. It shouldn't be too difficult to +copy them back into the java code.

+ +

This doesn't need to be built into the decompiler. A script that takes +the javadoc pages and the decompiled code can easily merge them.

+
diff --git a/jode/doc/dasm_to_java.perl b/jode/doc/dasm_to_java.perl new file mode 100755 index 0000000..f5d5840 --- /dev/null +++ b/jode/doc/dasm_to_java.perl @@ -0,0 +1,711 @@ +use strict; + +my (@tstack, @vstack); + +my $incindent = 4; + +my $instr_addr; +my (%instr, %next_instr, %prev_instr); + +@tstack = (); +@vstack = (); + +sub print_stack { + my ($type, $value); + while (@tstack and @vstack) { + $type = shift @tstack; + $value = shift @vstack; + print STDERR "($type) $value, "; + } + if (@tstack) { + print STDERR "TSTACK to big : @tstack "; + } elsif (@vstack) { + print STDERR "VSTACK to big : @vstack "; + } + @tstack = (); + @vstack = (); +} + +sub print_code { + my ($indent, $code, $addr) = @_; + #print " "x$indent, $code, (defined addr)?"/* $addr */":"", "\n"; + print " "x$indent, $code, "\n"; +} + +sub dump_program { + my $addr; + foreach $addr (sort { $a <=> $b } keys %instr) { + print_code (0, "$addr $instr{$addr}"); + } + return 1 +} + +sub convert_value($$$) { + my ($value, $oldtype, $newtype) = @_; + return "$value" if ($oldtype eq $newtype); + return "$value" if ($oldtype =~ /\*/ or $newtype =~ /\*/); + return "$value" if ($oldtype eq "boolean" && $newtype eq "int"); + if ($oldtype eq "int" && $newtype eq "boolean") { + $value =~ s/1/true/g; + $value =~ s/0/false/g; + $value =~ s/\&/\&\&/g; + $value =~ s/\|/\|\|/g; + return $value; + } + return $value; # "/*warn: conv: $oldtype => $newtype*/ $value"; +} + +sub get_type ($) { + my $type; + $_ = $_[0]; + SWITCH: { + /^b$/ && ($type = "byte",last); + /^c$/ && ($type = "char",last); + /^s$/ && ($type = "short",last); + /^i$/ && ($type = "int",last); + /^l$/ && ($type = "long",last); + /^f$/ && ($type = "float",last); + /^d$/ && ($type = "double",last); + /^a$/ && ($type = "*",last); + die "internal error in get_type"; + } + return $type; +} + +sub pop_value_type ($) { + if (not @tstack || not @vstack) { + die "Stack is empty??"; + } + my $result = ""; + my $want_type = $_[0]; + my $act_type = pop @tstack; + my $value = pop @vstack; + warn "want_type not defined" + if (not defined $want_type); + warn "act_type not defined" + if (not defined $act_type); + $result = convert_value($value, $act_type, $want_type); + return ($result, $act_type); +} + +sub pop_value($) { + @_ = pop_value_type($_[0]); + return $_[0]; +} + +sub parse_type($) { + $_[0] =~ /^\#\d+\s+ \s*$/x or die "Wrong field parameter `$_[0]'"; + return $1; +} + +sub parse_field($) { + $_[0] =~ /^\#\d+\s+ ]+) # name + ((?:\[\])*) # [][]... belongs to type + >\s*$/x or die "Wrong field parameter `$_[0]'"; + return $1.$3, $2; +} + +sub parse_special($) { + $_[0] =~ /\#\d+\s+\s*$/x or die "Wrong method parameter `$_[0]'"; + my ($method, $params) = ($1,$2); + my @params = split /,\s*/, $params; + return $method, @params; +} + +sub parse_method($) { + $_[0] =~ /\#\d+\s+\s*$/x or die "Wrong method parameter `$_[0]'"; + my ($type, $method, $params) = ($1, $2,$3); + my @params = split /,\s*/, $params; + return $type, $method, @params; +} + +sub classify($$) { + my $class = $_[0] . "."; + $class = "" if $class eq "this."; + return $class.$_[1]; +} + +sub new_instr($) { + if (defined ($instr{$instr_addr})) { + $instr{$instr_addr} .= "\n/*warn: multiple*/\n".$_[0]; + } else { + $instr{$instr_addr} = $_[0]; + } + # print STDERR "$instr_addr: $instr{$instr_addr}\n"; +} + +sub new_assign($$) { + my ($var, $value) = @_; + if (@vstack) { + if (("$value" eq "($var + 1)") && + ($vstack[-1] eq "$var")) { + $vstack[-1] = "$var++"; + } else { + warn("`$var = $value' in expression, while vstack"); + } + } else { + new_instr("$var = $value;"); + } +} + +sub combine_if_block { + my ($addr, $end) = @_; + + $instr{$addr} =~ /if (\(.*\)) goto (\d+);/ or return; + my ($cond, $dest) = ($1, $2); + + COMBINE: + while (1) { + my $if; + + + # First combine ifs with the same dest addr, that is ors. + my @conds = ($cond); + for ($if = $next_instr{$addr}; $if < $end; $if = $next_instr{$addr}) { + + $instr{$if} =~ /if (\(.*\)) goto ($dest);/ or last; + + push @conds, $1; + #remove unnecessary ifs (hope there are no goto's) + $next_instr{$addr} = $next_instr{$if}; + $prev_instr{$next_instr{$if}}= $addr; + delete $instr{$if}; + delete $prev_instr{$if}; + delete $next_instr{$if}; + } + if (@conds > 1) { + # combine conditions with or and reset the list + $cond = join " || ", @conds; + $cond = "($cond)"; + } + + last COMBINE if ($if >= $end || $instr{$if} !~ /^if/); + + # Now try if we can combine all further ifs until the destination (that is and) + combine_if_block($if, $dest) + unless ($next_instr{$if} == $dest); + + last COMBINE if ($next_instr{$if} != $dest); + + # This is an and + $instr{$if} =~ /if (\(.*\)) goto (\d+);/; + $cond = "(!$cond && $1)"; + $dest = $2; + $next_instr{$addr} = $next_instr{$if}; + $prev_instr{$next_instr{$if}}= $addr; + delete $instr{$if}; + delete $prev_instr{$if}; + delete $next_instr{$if}; + } + + # Okay, we are stuck here, build the combined if. + $instr{$addr} = "if $cond goto $dest;"; +} + +sub simplify_instructions { + my ($addr, $end) = @_; + ADDR: + for (; $addr < $end; $addr = $next_instr{$addr}) { + combine_if_block($addr, $end) + if $instr{$addr} =~ /^if /; + + while ( $instr{$addr} =~ + /^(.*)new (java\.lang\.)?StringBuffer\((\).append\(.*)+\).toString\(\)(.*)$/ ) { + my ($first, $middle, $last) = ($1,$3,$4); + $middle =~ s/\).append\(/\+/g; + $middle =~ s/^\+//; + $instr{$addr} = $first.$middle.$last; + } + + while ( $instr{$addr} =~ s/([A-Za-z_\$][A-Za-z_\$0-9]*) = \(\1 \+ 1\)/$1++/) { + } + } +} + +# The parameters: +# start first instruction to decode +# end last instruction to decode + 1 +# next instruction where control flows after this block +# (usually end but may be bigger) +# break instruction where a break would bring us to +# indent The indentation of this block + +sub print_stmtlist ($$$$$) { + my ($start, $end, $next, $break, $indent) = @_; + my $addr; + $addr = $start; + ADDR: + while ($addr < $end) { + (dump_program && die "Addresses out of range: $addr") if (not defined $next_instr{$addr}); + + $_ = $instr{$addr}; + /^goto (\d+);$/ && do { + my $dest = $1; + if ($dest == $break) { + print_code($indent, "break $dest;", $addr); + $addr = $next_instr{$addr}; + next ADDR; + } + my $begin = $next_instr{$addr}; + if ($instr{$dest} =~ /^if\s\((.*)\)\sgoto\s$begin/) { + # This is a while-loop + print_code($indent, "while ($1) {", $addr); + print_stmtlist($begin, $dest, $dest, $dest, $indent+$incindent); + print_code($indent, "}"); + $addr = $next_instr{$dest}; + next ADDR; + } + }; + /^if \((.*)\) goto (\d+);/ && do { + my $cond = $1; + my $next_after_if = $2; + if ($next_after_if > $addr && + ($next_after_if <= $end || $next_after_if == $next)) { + # This seems to be an if. + print_code($indent, "if (!($cond)) {", $addr); + + # endthen is the last instruction in then block + 1 + my $endthen = ($next_after_if > $end) ? $end : $next_after_if; + my $prev = $prev_instr{$endthen}; + if ($instr{$prev} =~ /^goto\s(.*);/ && + $1 > $endthen && ($1 <= $end || $1 == $next)) { + $next_after_if = $1; + my $endelse = $1; + if ($endelse > $end) { + $endelse = $end; + } + # there is an else part + print_stmtlist ($next_instr{$addr}, $prev, + $next_after_if, $break, $indent+$incindent); + print_code($indent, "} else {"); + print_stmtlist ($endthen, $endelse, + $next_after_if, $break, $indent+$incindent); + $addr = $endelse; + } else { + # no else-part + print_stmtlist ($next_instr{$addr}, $endthen, + $next_after_if, $break, $indent+$incindent); + $addr = $endthen; + } + print_code($indent, "}"); + next ADDR; + } + if ($next_after_if == $break) { + # This is an if () break; + print_code($indent, "if ($cond) break;", $addr); + $addr = $next_instr{$addr}; + next ADDR; + } + }; + /^case ((.|\n)*)$/ && do { + my $default; + my $cond = "NONE"; + my @lines = split "\n", $1; + $_ = shift @lines; + /^\((.*)\)$/ and $cond = $1; + (shift @lines) =~ /^default: goto (\d+);/ and $default = $1; + my $next_after_switch = $default; + if ($instr{$prev_instr{$default}} =~ /^goto\s(\d+);/ and + $1 > $default) { + $next_after_switch = $1; + } + print_code ($indent, "switch ($cond) {", $addr); + my %cases = ($default => "default"); + foreach (@lines) { + (/^(\d+): goto (\d+);$/ and $cases{$2} = "case $1") + or warn ("ILLEGAL case : `$_'"); + my $casepos = $1; + if ($casepos > $next_after_switch) { + if ($instr{$prev_instr{$1}} =~ /^goto\s(\d+);/ and + $1 > $casepos) { + $next_after_switch = $1; + } else { + $next_after_switch = $casepos; + } + } + } + $next_after_switch = $end + if ($next_after_switch > $end && $next_after_switch != $next); + my $endswitch = ($next_after_switch > $end) ? $end : $next_after_switch; + #print STDERR "Addr: $addr, labels: `", + # (join ":", keys %cases ), "', default: $default, end: $next_after_switch\n"; + $addr = $next_instr{$addr}; + foreach $_ (sort { $a <=> $b } keys %cases) { + my $next_case = $_; + if ($instr{$prev_instr{$next_case}} eq + "goto $next_after_switch;") { + print_stmtlist($addr, $prev_instr{$next_case}, + $next_after_switch, $next_after_switch, + $indent+$incindent); + print_code($indent+$incindent, "break;"); + } else { + print_stmtlist($addr, $next_case, + $next_case, $next_after_switch, + $indent+$incindent); + } + print_code($indent, $cases{$next_case}.":"); + $addr = $next_case; + } + print_stmtlist($addr, $endswitch, + $endswitch, $next_after_switch, + $indent+$incindent); + print_code($indent, "}"); + $addr = $endswitch; + next ADDR; + }; + print_code($indent, $_, $addr); + $addr = $next_instr{$addr}; + } +} + + + +my %locals = (); +my $addr; + +LINE: while (<>) { + + chomp; + (/^\s*(\d+)\s+(.*)$/ and $addr = $1, $_ = $2) or do { + warn "Line `$_' ist not formatted correctly\n"; + next LINE; + }; + + if (not @vstack) { + if (defined ($instr_addr)) { + new_instr("/*warn: missing instruction!*/") + if (not defined $instr{$instr_addr}); + $next_instr{$instr_addr} = $addr; + $prev_instr{$addr} = $instr_addr; + } else { + $prev_instr{$addr} = -1; + } + $instr_addr = $addr; + } + + INSTR: + { + /^([ilfda])load[\s_]+(\d+)\s*$/ && do { + push @tstack, get_type($1); + my $local; + if ($2 == 0) { + $local = "this"; + } else { + $local = "local_$2"; + } + push @vstack, $local; + last INSTR; + }; + /^([bcsilfda])aload\s*$/ && do { + my $warn = ""; + my $index = pop_value("int"); + my ($array, $atype) = pop_value_type(get_type($1)."[]"); + my $type = $atype; + ($atype =~ /(.*)\[\]/ and $type = $1) or + $warn = "/*warn: `$atype' not an array*/ "; + push @tstack, $type; + push @vstack, "$warn$array"."[$index]"; + last INSTR; + }; + (/^[bs](i)push\s+(-?\d+)\s*$/ || + /^([ilfda])const[\s_]+([m\-]?[\d.Ee\+\-]+|null)\s*$/) && do { + push @tstack, get_type($1); + push @vstack, ($2 eq "m1") ? -1 : $2; + last INSTR; + }; + /ldc[12]?_?w?\s+\#\d+\s+\<(\S+)\s+([^\>]+)\>/ && do { + push @tstack, $1; + push @vstack, $2; + last INSTR; + }; + /^([ilfda])store[\s_]+(\d+)\s*$/ && do { + my $local; + my ($value, $type) = pop_value_type(get_type($1)); + if ($2 == 0) { + $local = "this"; + } else { + $local = "local_$2"; + if (not defined $locals{$2}) { + $locals{$2} = $type; + $local = "$type $local"; + } else { + $local = convert_value($local, $type, $locals{$2}); + } + } + new_assign($local, $value); + last INSTR; + }; + /^([bcsilfda])astore\s*$/ && do { + my ($value, $type) = pop_value_type(get_type($1)); + my $index = pop_value("int"); + my ($array, $atype) = pop_value_type(get_type($1)."[]"); + ($atype =~ /(.*)\[\]/ and $atype = $1) or + $atype = "`$atype' not an array*/\n\t"; + new_assign("$array"."[$index]", + convert_value("$value", $type, $atype)); + + last INSTR; + }; + /^new\s+(.*)\s*/ && do { + my ($type) = parse_type($1); + push @tstack, $type; + push @vstack, "new $type"; + last INSTR; + }; + /^newarray\s+(\S+)\s*$/ && do { + my $arrtype = $1; + my $value = pop_value("int"); + push @tstack, $arrtype."[]"; + push @vstack, "new ".$arrtype."[$value]"; + last INSTR; + }; + /^getfield\s+(.*)$/ && do { + my ($type, $field) = parse_field($1); + my $class = pop_value("*") . "."; + $class = "" if $class eq "this."; + push @tstack, $type; + push @vstack, "$class$field"; + last INSTR; + }; + /^getstatic\s+(.*)$/ && do { + my ($type, $field) = parse_field($1); + push @tstack, $type; + my $class="FIXME."; + push @vstack, "$class$field"; + last INSTR; + }; + /^putfield\s+(.*)$/ && do { + my ($dtype, $field) = parse_field($1); + my $value = pop_value($dtype); + $field = classify(pop_value("*"), $field); + new_assign($field, $value); + last INSTR; + }; + /^goto\s+(\d+)\s*$/ && do { + new_instr("goto $1;"); + last INSTR; + }; + /^tableswitch\s+(\d+)\s+to\s+(\d+): default=(\d+)\s*$/ && do { + my $from = $1; + my $to = $2; + my $default = $3; + my $num; + my $casestmt = "case (" . pop_value("int") . ")\n"; + $casestmt .= "default: goto $default;\n"; + for $num ($from .. $to) { + $_ = <>; + if ( $_ =~ /\s+$num:\s*(\d+)/ ) { + $casestmt .= "$num: goto $1;\n"; + } else { + warn "unknown case: `$_' at $."; + } + } + new_instr($casestmt); + last INSTR; + }; + /^lookupswitch\s+(\d+):\s+default=(\d+)\s*$/ && do { + my $anz = $1; + my $default = $2; + my $num; + my $casestmt = "case (" . pop_value("int") . ")\n"; + $casestmt .= "default: goto $default;\n"; + for $num (1 .. $anz) { + $_ = <>; + if ( $_ =~ /\s+(\d+):\s*(\d+)/ ) { + $casestmt .= "$1: goto $2;\n"; + } else { + $casestmt .= "error in case"; + } + } + new_instr($casestmt); + last INSTR; + }; + + /^invokespecial\s+(.*)$/ && do { + my ($method, @paramtypes) = parse_special ($1); + my @params=(); + # Constructoraufruf! Wenn alles glatt laeuft... + while (@paramtypes) { + my $ptype = pop @paramtypes; + my $value = pop_value($ptype); + unshift @params, $value; + } + my ($new_class, $class_type) = pop_value_type ("*"); + $method = $new_class; + my $call = "$method(" . join (", ", @params) . ")"; + if ($vstack[-1] eq $new_class) { + $vstack[-1] = "$call"; + } else { + new_instr("$call;"); + } + last INSTR; + }; + /^invoke(virtual|static)\s+(.*)$/ && do { + my ($type, $method, @paramtypes) = parse_method ($2); + my @params=(); + while (@paramtypes) { + my $ptype = pop @paramtypes; + my $value = pop_value($ptype); + unshift @params, $value; + } + my ($class, $class_type) = ($1 eq "virtual")? pop_value_type ("*") : "FIXME"; + $method = classify($class, $method); + my $call = "$method(" . join (", ", @params) . ")"; + if ($type eq "void") { + new_instr("$call;"); + } else { + push @tstack, $type; + push @vstack, $call; + } + last INSTR; + }; + /^return\s*$/ && do { + new_instr("return;"); + last INSTR; + }; + /^pop\s*$/ && do { + unless (@vstack) { + print STDERR "pop: Stack is empty at $addr"; + } + new_instr(pop(@vstack).";"); + pop @tstack; + last INSTR; + }; + /^dup\s*$/ && do { + push @tstack, $tstack[-1]; + push @vstack, $vstack[-1]; + last INSTR; + }; + /^dup2\s*$/ && do { + push @tstack, $tstack[-2]; + push @vstack, $vstack[-2]; + push @tstack, $tstack[-2]; + push @vstack, $vstack[-2]; + last INSTR; + }; + /^dup_x([12])\s*$/ && do { + splice @tstack, -1-$1, 0, $tstack[-1]; + splice @vstack, -1-$1, 0, $vstack[-1]; + last INSTR; + }; + /^([ilfd])neg\s*$/ && do { + my $type = get_type($1); + my $op1 = pop_value($type); + push @tstack, $type; + push @vstack, "-$op1"; + last INSTR; + }; + /^([ilfd])(add|sub|mul|div|rem|and|or|xor|shl|shr)\s*$/ && do { + my $type = get_type($1); + my $op2 = pop_value($type); + my $op1 = pop_value($type); + my $op; + for ($2) { + /add/ && ($op="+", last); + /sub/ && ($op="-", last); + /mul/ && ($op="*", last); + /div/ && ($op="/", last); + /rem/ && ($op="%", last); + /and/ && ($op="&", last); + /or/ && ($op="|", last); + /xor/ && ($op="^", last); + /shl/ && ($op="<<", last); + /shr/ && ($op=">>", last); + } + push @tstack, $type; + push @vstack, "($op1 $op $op2)"; + last INSTR; + }; + /^iinc\s+(\d+)\s+(-?\d+)\s*$/ && do { + my $value = $2; + my $local; + if ($1 == 0) { + $local = "this"; + } else { + $local = "local_$1"; + } + new_instr(convert_value("$local", "int", $locals{$1}). + (($2 == 1)? "++;" : " += $2;")); + last INSTR; + }; + /^([bcifld])2([bcifld])\s*$/ && do { + my $value = pop_value(get_type($1)); + my $type = get_type($2); + push @tstack, $type; + push @vstack, "($type) $value"; + last INSTR; + }; + /^([lfd])cmp([lg]?)\s*$/ && do { + my $type = get_type($1); + my $op2 = pop_value($type); + my $op1 = pop_value($type); + push @tstack, "int"; + push @vstack, "($op1 <=>$2 $op2)"; + last INSTR; + }; + /^if(eq|lt|le|ne|gt|ge)\s+(\d+)\s*$/ && do { + my $op; + my $dest = $2; + for ($1) { + /eq/ && ($op="==", last); + /lt/ && ($op="<", last); + /le/ && ($op="<=", last); + /ne/ && ($op="!=", last); + /gt/ && ($op=">", last); + /ge/ && ($op=">=", last); + } + my $op1 = pop_value("int"); + new_instr("if ($op1 $op 0) goto $dest;"); + last INSTR; + }; + /^if_icmp(eq|lt|le|ne|gt|ge)\s+(\d+)\s*$/ && do { + my $op; + my $dest = $2; + for ($1) { + /eq/ && ($op="==", last); + /lt/ && ($op="<", last); + /le/ && ($op="<=", last); + /ne/ && ($op="!=", last); + /gt/ && ($op=">", last); + /ge/ && ($op=">=", last); + } + my $op2 = pop_value("int"); + my $op1 = pop_value("int"); + new_instr("if ($op1 $op $op2) goto $dest;"); + last INSTR; + }; + /^if(null|nonnull)\s+(\d+)\s*$/ && do { + my $dest = $2; + my $op; + for ($1) { + /notnull/ && ($op="!=", last); + /null/ && ($op="==", last); + } + my $op1 = pop_value("*"); + new_instr("if ($op1 $op null) goto $dest;"); + last INSTR; + }; + + do { + print STDERR "Stack: "; + &print_stack; + print STDERR "\nUnknown Instruction: `$_'\n\t"; + }; + } +} +$addr++; +$next_instr{$instr_addr} = $addr; + +simplify_instructions (0, $addr); +print_stmtlist(0, $addr, $addr, $addr, 2*$incindent); diff --git a/jode/doc/download.htp b/jode/doc/download.htp new file mode 100644 index 0000000..968d2b8 --- /dev/null +++ b/jode/doc/download.htp @@ -0,0 +1,38 @@ +
+ +

Jode is available in the download area in source or binary +form. For compiling the source code, you need several other packages, +check the links page. You need a unix like +environment for compilation.

+ +

The simplest way to get it, especially for non unix users, is in +precompiled form, though. There are two jar archives in the download +area:

+ +
  • jode-1.1-JDK1.1.jar is for JDK 1.1. If you want to use +the swing interface, you have to download swing separately, all other +packages are already included in the archive.
  • + +
  • jode-1.1.jar is for JDK 1.2 or better. It should run +without any other package.
+
+ +
+

You can get the latest sources from the CVS +repository. Follow the instruction on that page; use +jode as modulename. If you want to checkout a +specific version you can use the -r option:

+ +
    +
  • -r jode_1_0_93: checks out the version 1.0.93
  • +
  • -r branch_1_1: checks out the latest version in the +1.1 series.
+ +

To build the sources from CVS change to the main directory where +the configure.in file resides and run + +

aclocal && automake -a && autoconf
+ +

Afterwards follow the instruction in the INSTALL file.

+
\ No newline at end of file diff --git a/jode/doc/faq.htp b/jode/doc/faq.htp new file mode 100644 index 0000000..0de7f09 --- /dev/null +++ b/jode/doc/faq.htp @@ -0,0 +1,90 @@ +
+This is a list of some questions that pop up from time to time. +
+ +
+ +

Does Jode support Java 5?

+ +

It does not support generics/vararg method or the new for loop at +the moment. It produces readable code and I think it may even compile +again. But it is not compatible as the generics and varargs +information is not included.

+ +

Jode crashes with ExceptionHandler order failed

+ +

Try jode-1.1.2pre1 or the latest CVS version. If it still does not +work rewrite jode.flow.TransformExceptionHandlers and +send me the fix :)

+ +

Since Java 1.4 the format for finally and synchronized blocks +changed again. It was always a very difficult task to reconstruct +finally blocks correctly and the code is huge and very +hard to maintain. With Java 5 it gets even worse.

+ +

The decompiler crashes with a VerifyException, what can I do?

+ +

The class isn't verifiable, probably because there is not enough +information about used classes. See the question about the +classpath.

+ +

This could also be caused by malicious bytecode, or because there +is a bug in Jode's verifier, or because Sun decided to change the +definition of correct bytecode, again.

+ +

What should be included in the classpath?

+ +

Jode needs to know the full class hierarchie to guess the types. +This includes not only the classes in the program, but also the +libraries used by the java program, even the Java runtime library. +You should set the classpath to include all these classes.

+ +

If you don't specify the classpath on the command line, Jode uses +the same as your Java Virtual Machine.

+ +

As last resort, if Jode can't find a class in the classpath it uses +reflection to ask the Virtual Machine. This works quite well, but +loading classes can have side effects, e.g. when AWT classes are +loaded, an AWT thread is created, even though Jode doesn't need +it.

+ +

Why doesn't Jode decompile my inner class +MyClass$Inner.class?

+ +

You should decompile the outermost class (MyClass in +this case). The produced code contains the inner class.

+ +
+ +
+ +

What should be included in the classpath?

+ +

The program, all libraries, the Java runtime library. Don't omit a +library even when you don't want to obfuscate it.

+ +

What should I preserve

+ +

The most common mistake is to preserve a class. In most cases this +is not what you want. This only makes sure the class won't be +renamed, it doesn't prevent it from being stripped. Instead you +should preserve methods and constructors. The constructor is just a +method with the special name <init>.

+ +

Another common mistake is to omit the type +signature, e.g. to preserve Class.main instead of +Class.main.([Ljava/lang/String;)V. That doesn't work. If +you don't want to care about the format of the type signature use a +wildcard as in Class.main.*.

+ +

What is a type signature

+ +

The type signature is a machine readable representation of a java +type that is used all over in java bytecode. The JDK ships a command +named javap. With java -s you can lists the fields +and methods of a class with their type signatures.

+ +

If you are interested in the format of type signatures read the +Java Virtual Machine Specification, Chapter 4.3 Descriptors

+ +
\ No newline at end of file diff --git a/jode/doc/favicon.xpm b/jode/doc/favicon.xpm new file mode 100644 index 0000000..16b822a --- /dev/null +++ b/jode/doc/favicon.xpm @@ -0,0 +1,106 @@ +/* XPM */ +static char * favicon_xpm[] = { +"16 16 87 1", +" c None", +". c #C2C2C2", +"+ c #A1A1A1", +"@ c #BBBBBB", +"# c #D9D9D9", +"$ c #BABABA", +"% c #C1C1C1", +"& c #ECECEC", +"* c #A7A7A7", +"= c #636363", +"- c #989898", +"; c #C3C3C3", +"> c #C5C5C5", +", c #A6A6A6", +"' c #747474", +") c #646464", +"! c #6D6D6D", +"~ c #8C8C8C", +"{ c #ABABAB", +"] c #A5A5A5", +"^ c #787878", +"/ c #A8A8A8", +"( c #606060", +"_ c #FFFFFF", +": c #626262", +"< c #7A7A7A", +"[ c #FEFEFE", +"} c #949494", +"| c #535353", +"1 c #919191", +"2 c #F0F0F0", +"3 c #5B5B5B", +"4 c #B3B3B3", +"5 c #5A5A5A", +"6 c #3E3E3E", +"7 c #4C4C4C", +"8 c #666666", +"9 c #616161", +"0 c #939393", +"a c #F8F8F8", +"b c #1C1C1C", +"c c #999999", +"d c #DFDFDF", +"e c #0D0D0D", +"f c #B1B1B1", +"g c #343434", +"h c #5D5D5D", +"i c #676767", +"j c #6F6F6F", +"k c #9E9E9E", +"l c #4F4F4F", +"m c #F7F7F7", +"n c #1B1B1B", +"o c #E7E7E7", +"p c #1D1D1D", +"q c #7C7C7C", +"r c #9B9B9B", +"s c #525252", +"t c #EFEFEF", +"u c #9C9C9C", +"v c #434343", +"w c #414141", +"x c #3D3D3D", +"y c #3F3F3F", +"z c #BFBFBF", +"A c #3A3A3A", +"B c #686868", +"C c #6B6B6B", +"D c #C4C4C4", +"E c #F4F4F4", +"F c #FAFAFA", +"G c #D7D7D7", +"H c #AFAFAF", +"I c #828282", +"J c #737373", +"K c #818181", +"L c #DEDEDE", +"M c #E9E9E9", +"N c #696969", +"O c #9F9F9F", +"P c #A2A2A2", +"Q c #717171", +"R c #B4B4B4", +"S c #E8E8E8", +"T c #898989", +"U c #767676", +"V c #DBDBDB", +" ", +" ", +" ", +" ", +" .++@ #$%& ", +"*=-;>,')!~{]^)/ ", +"(_:<[}|123:42567", +"8_,90=abc|defg*h", +"i_jklfmnj1opq(r=", +"9sgtugvuwxyzwAB:", +"}CDEFGHIJK{L[MNO", +" #PQCKRd S@TiUV ", +" ", +" ", +" ", +" "}; diff --git a/jode/doc/feedback.htp b/jode/doc/feedback.htp new file mode 100644 index 0000000..d260c0f --- /dev/null +++ b/jode/doc/feedback.htp @@ -0,0 +1,11 @@ +
+ +

You can report bugs to the bug forum.

+ +

You can contact me by email via hoenicke at +users.sourceforge.net. Please mention jode in the +subject.

+ +

There is a mailing list. Check this page for subscription informations.

+
\ No newline at end of file diff --git a/jode/doc/footer.inc b/jode/doc/footer.inc new file mode 100644 index 0000000..c95004a --- /dev/null +++ b/jode/doc/footer.inc @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/jode/doc/gimp/arrow.gif b/jode/doc/gimp/arrow.gif new file mode 100644 index 0000000000000000000000000000000000000000..877659a41b6d5227d6ec7bbce9422b96d9649823 GIT binary patch literal 126 zcmZ?wbh9u|RAW$M_`m=H|NsBj0ns242Bz4a{*|ZS@-LnvbfLT9Xn1~HWr9XufA}n( zS*tj2?_!hD{?fbH@7#S~{*<*%NBoz0Y*CziJaxm%CwmuN(!9ES&!M&`+3ndS4^sm4 cE4SIoU&<33oknJ@aEA6pRA{L=~n)s5nRfm5=p+0nh>h^qgoAA7~j9D1`@?2P^{! zkXLqfQnLCnC7~p!%)=^yX9yW(l8Z4lqXN62QW`~gMM8*xCW3W{jAx8crtz>ZU{VQ2 zKs~&{VeLiWxg93J1*qJZNLQd1@CH0>Php&Nk0x|pA(}DCgQeKO83EqS!(``NGIIuj zVpBAcVyBowMyH?IxMmByc?bjrNsDH2fM)@O3iArR83rC&0wKke&$*OP`N)?{wtSaS zsp5SELgsBS8}oR+gb-n06eKGc4@%IizIywL)eA}iqyUv>^zu;8r#Q_ItUObuDB3tU z0ky2aWUF==XH+~B2<-+W35-GZuQeJAu^q2~T1Et#&=v1iPH9{z@VOlA zQ%H{Qx7~F9F~^i@Oaws^Ia!^&bN_Fxal^U)0>~mfNkCF}>kr3?Z|F{YciZ(7FRps) z%OtCD(9QU6b^ZCxb>0aF{mhZS3|6gNa<?l@ z7Ne4e^AlRu9|{di-oN{fDWqt8wg07u!;QMs`EJN~|J+DPu9vCSWRaqQ*IF9hgYr7K^(cfzuw@%Yl^ zvzo-JVC}Bs*TQ~uq?DZL8?9^$ys)BB;LXkoi!~`gj-Ki?}JNt8hf+r%ZH~Q&5NZ+CvdgyM$l2llHUK>jw zmYem0xzKL-Sqdb@XC6)+d#l@8>lY3^=H)B%bURl^Q^coo_IKw;2&kx4I4Bqh^oZA_ uXGv*S5)~&^wz-KUBj;k|W~oHiVMf9#qiO@2^J!XZdyAW^yUV*<002AonN|b< literal 0 HcmV?d00001 diff --git a/jode/doc/gimp/jode-logo.xcf b/jode/doc/gimp/jode-logo.xcf new file mode 100644 index 0000000000000000000000000000000000000000..60455954bfee381d7b5e7ff5074c252b2d64301b GIT binary patch literal 80236 zcmeHw34B~tz5ls)CYfX=pDb>As|ED7I-63WA8DM)CE5A{2@uP^gHArYe72 z9u;>Ktoz3$_(z`4=TqNPi+`>1G*xlqA-F`TYz11{bdtF<_y7I=&N+9PnUn%8MPJh1 zxxf26zq9?$Ilt|kJ9F_RZ@gm0H5XhoV8gbRTZnCB()7ja1 z#u;avd+tBK{`Hq$cG;!dwr#uay6djL{`wnlyz#4Fy=|o66$)N!YwN^`6OpHZ`B}ht zTvgp4P>L7+SYQ%qi*TQyVddhEO!fbUdjeMluEwi2U%L5%ZI@hr*=x34{+cV!-?nA; zGN2{1wCrraBt|6;YjnB~Dz#w-Af?>y`vAYxz;^@wvcOLFF2LV1_|F0UiGkk-_&tW^ zp99$KJ~IM$2C&n;5U}o$+g$}%m+x-)1v-lE;m_Hy3-DZl-57ia@aqNdYzSdD27d-L zuQfCw?6QsBXe<8Q4Ic;m1%Vx@_d5pv1n@u9a5rwb}>&D**pN zV5j?~fd6Xn*8zUmz#jlSXlT9|(7WAV2Mk{0&+Yy}06X1#0@#hhJ@vrr@|_6m_Fx2U zy*<+bo4R`10PD87J&OUGHuo$CjB&-E)3Y{!-59(vz&kx5?8e}?fo88MCxqReD7<}p zNE5J^jX^TF_if|f!&^40e>|De@b9S@Ha&8TMYaO(9bY5A48b}D!1pO z+tr3Y$%EVTVZgftc6vS(&b=Otk(tA8&-+31T0`?MfY%G`_PiJHQUkvm^cr@0u8-g` z?g7mn6N&5NxJdmMi*?QLt{bg%!$-5W0Y;3K4= zoowxu?q8hqtql)XX{wc4{}}_r4q+Ymb9$BmeyPE~0yNtV z+zb3Y0y{ld1HQuGVQ;NpZ{QCCZ|aKKaJRtDYcVvp!p^&1U~Ly74S#OWEx_wGxIMH5 ze`sjl2l!q?Lp${e18)cZK|=%ktXsFu?Ex>14w3J>+Fp^RD zB;eOeUZ*F7-57i>Xm%Nz5O#Z_@K(^g$IyhZ+Y^O3bGI8BTI?Ez^&a5GBj~O%G&gS# zXVWJD!#l;F+w)1lA2&219L4LI6?iV7*Kic?_Pi7LKbUetIEr_B&QsQdb0TmQ@Ah

GTl(YXrYn!}QopUMB*(G58$Nm^OEZup5PC=I)X* z^(@w~8^LSO9r^L6J$emC@o+$#R;1TojA+WDOWt_?#hVvheB~tdTQ1!^ zSb&cEQ2%PyYLVe10&E#;5~x9ClmU9t_0zhKK|w)&dOFS_We&D(+oGIuFT zW8zuK5A!#!U#YINQfHp2)VWvv=ii{zkAA7t!9OdfcByh2?@>-W@b)i=P0 zykhe;+h&}3{#!O*$@EHGNnAdzDg%=YB3?mLYYS z%BaFh)ug;R%a(UI9qzK+@)fJ*5xWt*E38#7AcTC+a$*Ni9P39@8 za2lIBm)Twzht{b!mG79ylqskl$#_P12jDV->zA;RFKi~fbRuB1y4}#en8>cRLa>4G z+Es*Ct|q*sjd1fSz~~+Fy&U;8b>YPXg%~^pxM~gwR>K0y7}x)|%=L7$ zDh&D){K5{(l@@=ZU+5*&3kd6{pGmt_t19f)M6IehLQ!a;^w`RkvxDh_c+B_ve3qR3 z5FB2=Pei&OMP!&4tli-|{tRR!q6IP>)wx9U`)L*eM5mtsqQ)=u^Jx%I(}|Pr~v?VtA(m?5?Gr* zWgW^InYUsI#2~0Qazr3X8ZL@tqxFR|*ryGgFWqQ{vu?KX=BoAV-I@+4 z2XDE4fIP=TkLpq<^R^W8BM;jf)ZMCs59?0i!+OYNJao+Aj9jm^RVze!4+?}f1h}-5 z1nbulhAshoQU_~az$)iVB)E#iL6h=5>(JG)kr^AAu{HBF*G%<*8tLrq?;nKh9U1KJ z@9m^CYuVcG*ZcYYUatJz5aX39-@Dsy^7DgRVI3y@q{?p{^e5rD7wZ!OGvT``-#O?{ zrC^3^+X{@QTKfGNplPAd>IWv@JLu06M7>%_9Rw-Y=>yt9$<&Jq3cLFqDwPI`Zzz?1 zK;GbvPV6qT1+~ls^dOoCfe&aaah()*Eq1j_D*n^X-IbRz`z5@(6C7Mkr8HC=h_tJZU<)>8sc zT+_umI%q13oNT92JaL^`M^hh4>Xfw@V}Q^O>sL|RtOfV;9m}b1P9aZ4`>=wMr5znh z+c}4ATNI%cWd%avN8g*%#07ly(eYGWRhB_6t#Qf;h62dNrntNe;ntt!Z` z=qAO6b<`Wnml1}V13XuB2>JoVAzFqiB!B80q25R=^;&-2dV+HZu3nBlkFj}-&Hq5N z89@@r%t44(r&mno>Atrcf)!$;7ed(^+}c91(eJlW0z!uE_S+!@JFybxQ%*u+_WAj+WS%; zSA{dxb*iv}@shRtIF*lc2rc7B4?j**eeSnaVJYF0`O!t9c0z0Tv5_BVP>N1cea__& zQGaLUViLA+pl0AUOHCnTXY#f~-+JXXTTSK+n#EhczV+%`3vP}Nt>N#cLh*OVXRmy= zkS&8`P(MF<`O%4w3H@^w?23N4UAS%4w_dr;Q+)_Q#;$ayD)`8i@K07LfAv#bYhViX zIlENfbDOPp7uV-m?DpE#T(r$pimPcRP-9N;G1=D`Am5|lm)EO_KqhgYfU6Q~Ex%U5 z+^R*al(;m^3VW}^b1j~Ery*3MytRnYHUgRUC#j6Nr$NBybmY0(pDY=xeW=t)sAn9? zcc@Fc%oH)0;;zemO>Sc$kSJfIBqB4jlmO*H61yz;3E%jS#Od z1!05OnaOINn6FwVm`qjjBsmC94sw_$s(x52gQy9%m9%U(EfdxR1Pg?|6H}$y$F@q# zATeUR^nwJAkQfhxbfnll;~qkF!Q|PE5G0KX8clR%L9w*zU=hx65H;AuVN)#+*_ zI+9mC@Ox9BHKc3dji>yTDAVddY~w+aCctB;txAa1aUgJUrw%0SpiP7u&_f4z&POWX zf`SRE6Tk`kP>+$MwzT&;JaeMgLCF!H#w@7Dy$1I*?$t2aGHNahr~+axs;B~D4w{ex zQ5)D_0baL4izek>iMH0MSt!gwlV%_aKNZ@s$?tO~jE(x(sE@7n|0QePrPdRbCR=tU z=xNoQ_{b(G3!K?bgp3ny+YeC-O$|v32?T}3lL^$8gH+vS=MXs2sZzScWQ9(&YOhxD z$RTl8MgX8cOcETYFk_1j7{IR*sFNA#y6aeo& z(5a&bAQvKuMDNyN6yU@pV6Xyoh-e5`WY<(;z>K*wZVt^SyrDK{TNpzZG=nE%1A#*G z0jfF=O^Zlp(Aitt z3w_<$o9soO`%+i1-&FVra3GM|B?|vcs{VE$^BYfxvtL-ETu~=g(A)V9OK^;b%B2cc zSH2q?5#|Zz$0;4?dP0;vGOugL_-V>d zCq%usqa(z(qrp_#;oTAC-HwJ+sn5}VbB#FDbf}l9nXsE@nQJBti_&M}`rM|mVHz8z zvDHLV_?WCFml7Wl;!x8u_}xS`C)!FP-@8dcMFj0)!rnlLG#AxpLe@6WEUJ&n1j0Ae zd(;pkyL)^6L6)X7_$(t9p5EMHH*}#i9Y(X8M#UQ#@hg z@YRGF_bG0D~Xm*s#LGHqhEHG`}LLsN}*p2Pq-h2 zEoS3In?nEfYDam#=ttRbm`62^#t%~e!NUdE!VMCDGBUHp|CSAusdgtFcF%68#Z>Ps z)d**IJEHqa9bUR@8IE7B>{yaq0`+$ty-I|tCFb8n=!L||o`5`dBf5O$3CY<=D+Rl* zWQ^Q;^{oXr?C}iZ%+h#K<%#)fTt7wxH)t}6Gc4e(A2D6rdiAYSZj1Ep%R=6^!pFs} zmpAa|YpSo}J+TYNXTORI;b-}KB|fJacmeJ_>x(~oHdkIWOTT=}`4?XP=F&sPeV{z1 zhm56e{b3Io^T{4EW?km5>zf(~8j3^4T{uO5=3HDWaIKH0uOElE9A@FIfww8ugIDfP z`J7T4Psa5kT$_|Stw*WTuT|mvu}$#fy9tgB?;@r@Z#I4ViZ^_*m)bYVg^dP=Enuhc;P2SB z0}Zu*jBSSnu`!259zSANGZwMZ);X-|v4~X|v07R@ZAy;%NH`Dvz?gIQzs`FVBVO$0(W+(vG@8f|J%U*kL7VLKAXG$?$5mY!jo~JI@hrIdj%YDY*k*> zjXC&K2wb22sNal@&HU6o{$#|4^Cv&@`mq&4rU2#>zlHUZf4rG*{JaL}!bQfLUMZEUI;+2cNSb$GYXbVe$v>3;^*6tM`)yzT&)$f_2yp|@W#xD_tOuZhocZ`r1F*T`cE0T z!ae=hbfwVuM<>1Vicfy)o(Da@@Z{bf{`=0izIL92=-+RPI=o#S!sz|x+aG!J%aFbT zyI~^-zx!5vImbekYrpi4VRkq7ovUjpubfpk?FDmlg`Zwshwgi=@Av~6;r;5OYEGSr zm;X03RrfB^85ZU zuQHLW`!nGW3Y_{D;KD~Ks*|@9d@DicbCmIgvnjPdMQ}u3 z*%WZ!RH67F(E~|=KMJ^TJ>~M}DEad%DdXL*Qr!PsB==7U{u9Bbhp@Kt57rRw$We-4 zN11jVv5#elb$>_f6~vxR?4OeacM*9up@oFLN%8j`$eX-3fyj+SZUvNgeovwJbEaQI zR^q{By=BV@{5T42BEhR+D_BL&BqUnlLCD%81sUK#Z)A?|GKaL`>x9lB?kj{|L+G=F&L#ADLcN4;Cv++y z$t#*?fVert?IncsUl{u|!F@z_5c(0Je>&@2Vzv3&A~5O;FO@ z^veHI7D)YG!VmHdwfypfgoiICd{%+0>=uHr%n`hq;EQE>epZYsLFfOgic9s>!yw83 zmWLMe^Zo|d%U|S6;eC%h{BYjq$%oSq%6s|wd4GgKiC6V%Zo|oZyc)jn4^aE_l$U(h zFy4Y(puEa$Lnv*L@*3Xu5UN?^xj;3K*WD^_Ted~{&C_q3K_T#&lMj9HMnlKa9 z_LyJNmwuVr?<{`dmg{c%;=d34{11P9>@d!a96bD|AK(7b>n=EB*`x%-+pv4mrJvc8 zh4p&S+dJ@$Ti^Nmvpc8bIIJ3}+4#=?^ziD>Na4|6edlxN&`9QG@BL{W8ewGFt8e_b zM{(kPhkqdZ%dfxh%@>{ef+cO!$K&|CI_QjBaLTz?-}L1>?>zw1csT#dkDL!(Jw+X? zXbUta=#n|gYkbF};OT6n zz4#r!0k>zUTie`Z5<<6zVVE ze?c1Cg>8Or&j)&ws9>g_d-&Vuwm^dnzw_2_4D9tV+#^ri`O$w~0%i2K#H3|sTyWh- zZ~yV1hVU&mj|~od|F(}_^Xe6NAJLgr)OR>q{_w${e|Foclfa|2Km71_Z+hK|YD~yF zKbya&|LT{_a^yvq;RCxr{)V;nn0%9ArTqCDH=MH|De7%Ft8-6O?LqDaG(X4N zm1R zJ148*NvCf6?9a1!NqBmgn_o#`bMpX9kh=zUvdx|HSDD<8;*@cp+7;sj{2fZZf(K)$ zTF%V!+-&i+LKPfH72N7ce2~&>2BE(aT1x2Ggiay!LqZo460!R)gg!{zw+X$Tk}GwX zU=Jl1rE!qkvCJ^okGF3JWtXGlayx3dx(BFLFem~cRBIV-bYP+ zE9Kf7DD8$OK{^%Qi-}x#Gi8wbS%CSwL~- zDWyK+VDmM9?xTcnPhyGXCw`Avzv^NW*GpWV;{#+L9FMubA&h1LZn_orOn(2FFu=U@ z4S7D?JsUI1t9tt|9fiUNX|{UlcV;1%^7~)NhuU`!W3A;sA+F2nPt%>r|FsjZ)_PU1 z`#Im{&VRZF6EWwUeaj)p!Te8G(cE*^-Zp}j={>ZG_Sdk}ddd4>JT}6|cvlS&Q<#k= z8jQBWhsPnuMAqH}@!15o<3rmv8i4QaFvb^Q3k1lmtm=EEJ}0u17j+g?xs_N5@Z ze)*1Gda$lPeCQ|tdhv!dq{`4SkDYWdtbol1iG@VJ;EMQiUvNeIF^Zk2gDgz{owrQi zqp_3o5FdFH+V&>IMc!mK)NT5C?^o)4e96@XHz~E*QR<>^DE0boT*&jrE0wzJeM()9 zn95efSGGx9Cg%CN`;^z*bbChr`a=v#zSZ+P0Y^|rePk^|mdw{6{&*pxq~3)5kw zxrB((q^VLZyjJ7DVJ!Y4hmUT)!1F(05op@qJcrtB6_jIk%wgqs|W|Lhf1i57}Z~_;NMwmwz%!jrj*Ys+= zJSsMIs4OQE=UK(T5-t;h1MK;tV9uvu_1XDf1Knume=V#-E+5QH>}K=J)J#HyKP+>& z8OLM{!Fb;t$Z~3PIZm>K*r&)1xC3}MB{wjDR3^gm(PhGr(AZ7Rrj&m;X9?;eZ8op>i-TOhT<1(VUxY)9ch>!&(NO3|XUyB3&!YJ+q!+gJBqmby;yeg1zi6 z`ZZOL6#B%}+JRxiCIsX-aJ9OxeO31sjqrL^p_5jsiXanICNouTs*Lb+MmPm32dX&~ zs6xi6Ssk*I13YSsj#5J`sRg~0T&}Lu)%7Gc4YSe6i-<*TAfY8MXoyX+qd4tGi3?~G zCJ1h8e8-MShtA_f7=|#AnG!C7=0k>MD1R0Nr_3N}cUqEUQs-dlN0Y_{j83pRL`7JC zWM&G3OcT>qRmgmT=|FCz4ad_r7jb;Ms?tet7MllBVW9{cG2@ zJX#_h9v@Bw1bX!1%)w2_a^7&-j|(UHv`875Cluz&kP1!P0VAoP+1Ve_eond>7vaHZUW<&s-Ri>Gh&{zGdq9HpqmJZVt#AfW{ z1>vh7(KnegrhdB)@p1r{MzCIIz&5n28Ue)sZb4Yjh=3^w>}fST8KFH5@kKu2H-e<1 zp__4}Ue-)>Xz&_?Bu-SWT}#1;8cBu#J4i4ynv@m7kx`(G#85AUl!d^S#ehMh*{utn zPhJbAS}?R6j5w~=`f8CeRq&_{32nJlnF4Hb`Da>UpudfliDaYxL z-9*(mG&eM~(eQ>4_J-*6BhYDmZOF78V66fgJwbWJCJVHG#9WBzdnkWeDaI}cj06@| zYDxs=#&cK}d;C-p%4pw0Gp)4!F;hs7KtLphP_&?OOk!h}izTQ{r*g2EXHScf#-J-Z zkTPMdA!;TDTWM_G(3I6SuNbD!Ds5qCW9y{_Yh2sk!J}S0d^Bc#uu6^PUb0Ayq_Jxr zPOTO$77+>oS#DaS1kQ(jM2HM6p?9O3f@fLurO&25I5UR8)CU=*Ju;gmBD6~KSm8Y1 zl!6U#t4!w_R3@TH+XFJAM3b8kHIVwndaW3kZEJ7Be!LNujR>G>qVb6L1!l2W0wP|y z%NpqO8iWZ${aKChq1AX*16;|_N>!VRqfLG32AwoXTOG|(g!XYKY3*RV-;8Ks(b(eV z;)`x_u=b4`9J8a0IUr`AQSmE$oO?8JI#@(&t= zY!l5C(^NDFz1Id`I%;VKN<7DOMvqyl)v@$m!2@Xh!!%8$#AON^pGEhnY%B`bSM{W2 z+2XL{RRas6_F^?$myqLW#QwskdIV;Jp&1W%9}hHbc+NsI|EU!@gC!R7NK)QJGlAd3+-# zUPUWj$&j{$wN6LutOzxk-^#>+52>0ErY=I&BS}1E577v}GrKGhONv8Bc*Y?llXWIeH(Gc$ zM2v;oaGuD*_{z{wCgRryLTR}GM^*yAZ80{nA~IyDN`x-v@=(`&>sUnSYa*u`85TcL z@%dYHOrD=_x?96G{?7e^Hyyz5YMh1(Uxxbm*TmDW#`(p!{!XdB*DLi-yc2fArAplh znj0@y>O)mZedM)Def%z^K6yIw;s+@}8GRSSeG3i*X6h65E3wa+X>9N4Omr?&LmE8m zyo9%PP^l%agVEzD96nQXCKNOKDO*7%2!s&j@#nnbrST|J5|=PPCtOEr>wFLP+vNl;qZ%t|b+Uy6H|3F z^E#Pp4(35=u6AzrJyI?U@S|tQbO`%}Uqk!QI@E!!Q51C1tpl-U=?bLgJ$uX0+j49- zBJ`%9c=?vzFAVM7gKn47lOMNUWZM&=mv(k!j@YrM&57fmVKiRXm*e$(^=N(PmYv0{ zUJyY?IK4pa3#bbLpBF~4hMV}(@d#F!zjI5c!3JZFA+gx7)f^~b8&Sfe;XDY34+S^c zCYwfJkfPN|i!r35HDXF!P&+3*rJ!C4ITK1+$mzxEKp-VW^16Ex^?59tHi$M6pP_7Z z_a-_$thV~3P)t^jS&C4;xpzTnsSCO3S}VGtvn%%GL5p77LequELCDI4PGmT~01x&7 z#*R~_QgZ0h(?bVHYf15?Jt_~|0y6^>a3_Y08FUrty$8|4FWrE;Df;n>b_b^$O%con zlAH3=dxlW?4d-={4DV!SFmpXle@rw|o5IsOvtZYGT{y5dPF7HbrZQ!ww+wSaBXz2z znlkD-bE0|X2(nJ*d0*8{MN1ODG$tu{$pbc2b$t#?OLbOvjvz0PHh>H)SXp3pESC>y^)+~wkg4E6tN!?Sm=YSq=^nupUmTFB< zrCFNVGa|`*tM(qS8hgtYGO=0{RLdZx_KryEP!-1srUWwW+{rOXOM11LA|sNB`INQR zge&Q3Nl)u3G$Lu3$8I4fQya?@lP1H|Oo8Zpeh5=7fw_+fnIygjQ@K(wG7o7XaW2-7 zAVC6RD)(kl4kkVqNt#J%Syay65sh~Wm3y+*RFjpF5dt(H-E;OF)@eg|OrgW6KM`7* z#7r}V?`v$2S)oj8wvI(@=Obqoo2eui7j-w%TOPc&j5$lXnj$|Wz4L)DVG@i&U0Ld* z)b1(xm`T4HHBZRRU|jds?!`};6n~-NOHr$r4Ti;{;(g*A^T- z!75S;E2E@=x@X01 z)t2;@zXfZgd<)BXSrE4_{1Neo%e2ugJQU-()uvo^6>u=h=5vQ07V%lKK*o>aIETvK zC=9HDNu{&e-SdQ20U_1&qLtzX%0w%sw&;p;w+w|j5lYjoPg*60y)noQZ4~yU?vD!$ ze0>r0Zi9%LxzyARMsMQOI+Rh}mMn{=g2oT5K-SgmJPcU}6!a|)xU$uea4`YvwCyC;f>;65l!LRp4%~0CHg3=rO~uL1 zTIi1hv~08y!ZKQvOC6FK+DDDz>qIdqkT^xTMs+-l+QqukW|1{GCMj)oR_}R;T0~P| zO&QY#MKmi{tAW(czhSc8Z;0ZJ!1)0!{!?3q5Q{KWj=w+B9I~%T8OuA>`PZ`~-k_rd z(rjxlq0&ZWtoVxh!Nzu)z9#6v4(jF^UXNGW6-cQ;211Odz*aqB|h$j4?wfApN54iF0Bml!|S!W~gIFENqEXBLSI`?w4tZ8MjdzV#jT? zhJ+n9QzvYQ@GKZGGkpw4rYCS{^)KSyM7wL)e*=RGv(;aUI!o(aW{)`kt))CaBu7?fc(TG=3`=Ve!}%`F@;w22hSFR3hw-jDiiTEK-jJgAhbj7DeJR zv7kzEWanuHRU`oOESlSh&8TQ#$IiQ0U?-RxF>ElSqgXrZo+mM?u;*!vW*F78 zAu_1C1?u5&kTuh^2{BJS6eI}WbT|Q8Z?5`VbP|g4qMD=TY*%|D59Rxt2KB+3bL*Sc6Gonk zlG6n`*p!IWI~}&R+c`-+ZXw)M|c=I$BoxRhIK9h|c*jPcOu(cftJUBx$ zi@KmI%%da|TbN}dn9;iYLIsS**08Ywr|`=Pg?6>Sh%G6`H89;w$kj4I0)LpH49&`Z z6E~Am3?*P|VaOHr!RZ?~^AW+^SM?=HtP$f}2e;BX6jjriWrUy}{)CMRNDL~YV@zosW^iwZUfmq>c&+O&tQM6@ES3yGPLS3nzALdL9anSqc$fz0nlQi+ z7{ns|i7 zXLZI1CSYzF0>~PZ3x_6%#K}-+UD3SydN}D8GQ1>3GrY+O)k@b>*Cz4E6yvMh#jAH@ zB2J37Cd<}vZb&?nlgSOICJv|DlAO*bgHDpw64vC@a_m$MCUj-cBB%4QAd|3>F$Rfr za%MV_8Ej^{Q~z+ZVUFoa3#W7rqR*IiF+ww9pr(xk_Of#rPU405(O@fFTNF>MyEhW&XY$Skvh^Invw6I=S6 z6}k_0FdwrW3}=+nImA_iC52Vs)IZ*+P}ZBPt^BCUrR(ut*h(7kD5a5OtDvB#KvXr1K3zWA<&F zPRrt=B@fBVzL^mUc6mg?Zi%fmr?f8a5Q!2O-d1fH5ut%qUbIY|J&(nw6vxnP0#)rC zp{Uq{qav}@Ld9h!L@@u&eo*zE93>fo$iZR^^{lfe%eq*m-WJl^K7n8;Qwe9Xrb-VD z!`##7dPJQTSAaDm8$=mwM0zDs%23Oy%3w1nS12n5DI_Y9T!S6fvSQXWwn}durHA&1 z+Ob3=3vG%V*ZWA-kZ+<<4+IOPB#++4)f-CHuyHV#%<3weN$fmuQ^a)DPQ1mR2$zb} zS(GPb>CCoK;Ues7MwDQ`%}L+IN-0|%J(?JKR1*wX6azNe{;$hg;tQyZGH zC=3u3YES6M0greCth-KZ=o#9860G-ltV*BFAv!!zBg8!7!;?kYH;hKq%AD}AF$NrI ziqe}7niEP-^F&a{Q*`Mc>8MlLjxp%Tg&GE`$UJ?{YG$}FoX&lbaHZu6j>?8>>p>$L zBEe2w3|dtTI?|j` zGHKg#zxL9Dm{m%-+T$w`Pu5%uY|;b_ONON_D^u8fIAUE!G4Om0V%<8HU8NEw#!GLm?4Cq6QwCF&t@NF2%CE@gm7h(5*w@7EzC8HZ1aZC;aCEq2w(ThMB1D8Y zq)O|-`-BRwx8MUIC8<8ziZC@@+UygP$e(m8;UK;~L*Z+f%HSP*A6OZ5d2%wTOzB}q zBRoP0%J1Xwdl-JNfG?x{dCl;b(8juUgj5WFakIm$&t;+vP;pc2@R#Cc_)F1x_>1vo_)F0sw@bwGC>;K= z)nB^6VsoQd+&!Dbvg^|he{n_Q@k^Y?clG02XYr$&$Ns9uS0C?!V#(u+cB{22sfUKa zS7wN!F>t6*sCO_$Q>39rz^&S^JmM3JZaq5El0sWC;y~i(*m;XT;ceP1-v6w~<{;7t z2~Bxy1N32itSr1I%&$Hkz4&-#M|ukQ;ADlFSk@kyNx@u?4>B_XhvJ!1oXjK*49^RvcOD_%#5Jo#UK4szz4@0 zo0;V&KA1V`qKKzOXD01PEOj^gf@6M3xgN?r#@BIcFL`l<^Ea_AU$gz2gsq*#Zxp9s zxg|;r*kl%8>OkEM^NKeYzf_E81d)p@T|iQEr~i9QZaWiDedk zM2c)1A(NjwVl!rru3VTmdNV4r(#WGDep)4)0XhnX3FonYhaZ(C^U>InF> z77HW9RAeXlb+I{JnXM{v$Ct#?M}O+8_&(wn2`?gItOLNc16M8SjV5Bs6PAjEXGq6J zVE~`9ynrqgF-0{76mbz#>;{z;F=dI0MGQRSxO-eGVn!pe^E4)6o-<-IxT8poqGnWh zM-nkb#80baGe9vBQ&gTIDJx>)W8`{8A|{^fct*sOqA?Z_F~xWzVoK3==t`mkju(?Z z&sokgp#vAem!cjeJcyX0`3eye!&G<>F)>V~2N4s)EIG<2BVxv^7_sCqX~wLWQH`lF zD<%w^pD05mBG!tHv$EFK^Tdkbk98c6NY%3`v#;WPh;f0TfkRv=5wA#CswhguM;u9p zF;AN}8s|#+Zgz4E6Dbr`#69r%(`+3oOZio7%rkmo!)ArWNIY(4_*|4+QcJu&&)=sw zZ)WM(UZr0RcNzi6W^H#fI+=B>%`H#!i7E$WvV9c@bV64~Lv2Ojv@(yF1TeQ~tVDxu zm;@ds0ZS_zu>)h7RyNdzt}zXhfVXG##CBp5*kUA(h)H0di;_!fIa2GMPib-9U=px{ zm;`Vu5IDsoz_UbHJ;z!_jJJC81AGyb@njN=NdoZk`MdVxEjvzo$Zjz!Fj0;%zFLl!Vc#Xz6Ocz0N(s7hMnvP z4_amMCBE7h0L({A>tqwk8}-qae(djkY4piMpKv)i1QdRvPl7|A5byHXC)lr1*zrbq zddOp+#I()oM@#E?=o8a*)}lvS`mw)DpZ?LtN#@^r^|I_jen+^<&dx!vnM`>mB9I?#l9PSXtdCeLP!C!m+keuo zhOnZ9I;=^7+tkI?zrQV+1;t9cQ~F1SXHA0K*~_8R0b2 zgf&ux@HF7$a+bxy&E!}P!89Uc4<5ARTngr(r{^HtBhEo@t#+d@2k9<39{6rC^h~{! zenNIFFqG#K?Sjy?NfN&f^4IEuh9NshAmolWghzSyI0s20#n%u@;=Dup-RCVrue2`f5L<{>o^c zVVx(^geuD?;ha6D$vJ2{5U^A_FJ+o|zv&@+6A(!gO`AhTOX~xgOjxuu6>|{na)l{C z1awlNE*)sc!x({1dE;!Cgus+-DZsD@MaC8?tdSywrvWFI>mnUnvI5gB{VK^*59 zlY?V&@I=eO591dO#~p`@v%4YtMzuVfk!vaJ{EX6{i?jH`TReq&O`!1xEs|f+fIb$TX28t8fBvfj#2I=hlLWw@}K!{+G@w6!D>}zturibU?-%Hw0+VBE5qwycs&d+E(~n&x!A+-;we-a z0*yCl5vrmwjWQBh-e^*d@)Th#logQZO(roJ$Z1JBa>ht1m_hYrlqR59gLHc_yriK{ zm?4S_=RV;|sA^(GPc~CI8!S~U!`+_LB8pJwg@C8LcOtY>ZB)5ocr_K3fm@c+6nns^ zB{3MHwCoXv7ne|`yoarRr;alJOSPKltk1t`__C zYKdPLyO#R3vRk>#uTi{~lz)F*i`~|L7ts$V( zBLp$`O6WDd$e9wyN!+p^*xHocZ=EOOMx}Li`VDl(w!$E&2|fND#aB&JO~XeEvSzfa z97I;>W=iB4yvImQDISkl`rHc3f> zHtAuMA&3TZki%$IRQ7(xfL%~l!D%iwpkNJp_x(zZCE6u|PQ z8m5($=%^{SH^pV3?jUV4Thd}FMa-_WPg3QI@X1k;2V@)N$rJ*DwJdyocD!037Y)Q3 zMy}ym(ii125?K6Y;=Tq-Cd5`Py z)SQh6x0@o77Vd9IOYl)cVm*p1Nzf)e4EF>PZw`_~Nla)ZI#N@Z+3~Q>40aq5h7jv4 zs4||H0sd%_SZC-{7}*KxmI7G*RMX#*60y!~Z;H!6OMPtPtvv(;S<(b z_<(GqJefj3u$G0-_pr|7l66)rKOtyip~EqKrZvM;?-v@;I;PLY^jWDsJ080p&$i6I zitR9J@QZY?9rWh8u_~gm_nN>467xbnYE4|8nJbtsW>c6R&7bCm0{)53CR+qzF4)li z46+L547O+@TOJdL6*}y<$nK5o2{h8iFbT<^4W`i*XiOoi@bY&Vs<3V4$_P`BP)NAP zQQA7Yry)&-y(dkUhXm}d*@8%mjYrvPF&WvO0q^FHN^pp*GaD$n7`y~e=CqnhY|Y2} zZg(1v=;+vv$?SidIF>fxS5tqJ)va|P4I5D3$w`P1C1z&|k{Xhdks z!0sB%pfZDk#_msKH)R5`qA0PIBO61q-OxxyX%dn_8%(1s(3pb9nNx-;Y+Jc9!qg)a z5^kTAw$5&sNRwf~NR#Cu0ZU@GATV>J^<<95VCE2`yt$(i9Fi>fz<--bjytrPN^H%? z`))Vw^j&7on9Rv&XH5jpn9LbPmX*q!r?Wj|x0ObFA&%Qm_Oe@3+@|8S$ZwES3Jd)4 zYQEp7=K2#@$^H3N)6wwR369T^Y~iD{FC?{y#%iD%Bh&D zob=Cd-G}Q@To7FGoDTDdjaHS~=nnPfUZUWmf3D_qg7{Z`&b9RcNnQoEH#{*Ty zY;mj;4`v(sdNIdDd_u+6+bVz)9Sb|nF@3Z%$W3Nyaa zL-%mkmNM9;7cSN$j})PX=-FJ@j%GQ6ZIT#S!#Ze(HEb%e<(gL)xVXSJTFg=g^S9wj zQ;0N&0NBPR@I34!V4swh*pFrpY{NtN47S+^W@DB%bg3R|UIbPK2s#@LT;}|j20|aU zr7&X01N7Kzdxo=ZyVyCjb&mD*l;5$;w>6?y(RakPT3v~?DGJ`nwrfo zBi4!%w9O<{hZaq_S{eG$mRlqXZM^otlq<5(0w`t%29Fpu zj)z&IjUH_-7*hwcL<9@vX57f242_sm_OO1f7&f$9n1wQs)|583Aa_h15xYiCWfrH5 zRw*r;8q^RoY64qI*my-&X~6XuUN@PC zRLLmae`_d*cQZ@0xj!aLM8X7XcTAR;ceFW6j)xiiY|CWKUccrCNh#7bknP%v)B+Sz zw=yEVb&{6ml*rU_V)4=xrNwJ(ZxQL{$-G5S@yv|zh_PPI9xo}p)FL;UQMXS5Wf`m2 z*lNMl%H{&y^vO_Z)J58W4*C{-{{ft_@dFl6lw>0fpkirs3}`}VbKJ%GiX$+JWY)D{ zBaF);Dy$M2%Q`HP)N&j$$+p;1{ur-FH%t7R$*9TzHXH4VAS*eHnzbE)47gz6AcA1E4a!3P10%7`k_ zNm{i~Jy7L}(xV$dZ^QUKqF>CDc{8HonK?qzy3u{1lEO>9A)}^s`($e%W0fphEtp!_ z)nJ?iQ&vH!r?qfLW3VWY(>tb~f&!=+jDM zEbA;rQpz+O*>L5R_!X-&&wtD2E4kTlqIOhfv2yuyYVR??Ik>+S zR}#A@h&VKU1J^EGKZ~V1bt{xpcM7h1a6PD;`s;Ari0hNMzN(yY_=1q}^~m=TTwh?` zSnz~_l-d=l(52QYr)#ZpyI!CYT_>w#*BVvPb&{&=>Qt#N{7TrdCGuIFkLr9>=c76w z)%mE-M|D1`iw1b4jo4r~avN8mjmMPe$ZbS*#ceDKL>159<*KTyRZZ!dty;Qfsi|Ev zpYkHzl&+P?w+e%>S_XrI!olGnaj-aO;7nh2TKz**fbYBF#zKn^7WrV&Q(UB*;)6v# zSmc96m_VFJoKT!toM4=21z%Wng{tjZDpy_C5>?-|ST%GlQscT7%AL=|TN-`u%dA%? zAfJzR;;Qq}P9N>`(M})j#GTK?Gd;Z6P8V}!xvJ?}rqW#BDi`;z-GFpE{A zKUYog+f}pQrY1o!OqLzTSr85s3^UX;xu&b>{xmg1E*`gNk)szpelZKjFnAPWwm(_5 z$^`@7=XIoQi-~HE&ufAl=irf!xqg$HS90O^hjHX1yvABR#_RYjiOvqfeF<7Rt5lW0 z0>Wn*gbL;N63FsJkn0Q8cz>SE^Qr!H%-zYz(EzP=|KCwQB}%sveXH{qsEJsw)36Tb z;k(BBGPj@~$2)z!7yxMq_*(F^(Vqi;vJF$Q{wLrVR~0tOe~WgUD7sUGFB2fwnjzOF zL#A0nhq7!s#+`DE$HpkXxSlAtD6{BeQ7&nDG(qGMWe<;@@rr(74v(ervd7elzg58= zXS4Uwj=tHWam9Cgysj@X)}OKQ87Fh?Ihk)o9#*k@J(}lYO>VWk)%w;lu$F?g9B4(* zqM(Igtqocpv_fc&aKnZc3M~~{EOQ|JXuZr+O^}4+A>GG`l&7@EOT(f8R>NiKbGNIb zX?HQrSU<+b{Nd~JjRux86C{G??D$IBYgECXm)B5u6}AGYA9+eu9Ftb z`5M&sAZ(ZuMYjvz7qm>iN{;KB>z8Yl>yy0WTBH@W62Ftkb=X(^{Gzvd_LHm2?H_=&`g4)n+Pe_1PM1D++HepyO?C)_glbYnfDt!krNV`F4z-f^9E&dG>)tb+YuqdALVYth!H zeL>rQuBqJ2sx2zt4wx+gI~P zXtnu>MNC1wA&tllVhm~oI{z5qGv9$Gq$Z`NrFTLvhI=lIh0Me{n*iHlHX>*zif+4T zWNLfL7s?CD2Fd~()nfdGdyIW*7uxy!q94tcc)IwgtrE3ktc^pv$J#+0P1;GcnK-Ik z%{BsNqhGYWqLDA|>M-&}yNyvV+Hs6}(XQjpT{P;&BP1C}*AzIO9o1SI>tQhm#>Vi( K&C+KQLHqyWxY6za literal 0 HcmV?d00001 diff --git a/jode/doc/gimp/statement.gif b/jode/doc/gimp/statement.gif new file mode 100644 index 0000000000000000000000000000000000000000..8781380de71eaf5c369e424415b1ad18a288f47a GIT binary patch literal 1226 zcmc(e|5H>|7{;FqYaDJn3^Thn-4;<)RunB9pk0xPTOv?xjX~m?!Jx%}15~5XM1r9^G{V%ffW3F6C6FIz0%XRG)7}1w-k;u?bLRa#&-moz!3HelRb>-7!_H)nzu?=wi#jsf+JY7#016&Wx+m}2YA58 z4;-Fs1s)E-26zCq7l>C?CztBhzE$ z)k1(s$gBg6R!I=vCPaKE3bGwc041o@b^h34Uj|+T$;OdJvz7bY1P z1+BJ~Ydmh9ni0jGqZ(FstfR+eg)yqvWk)qmX;7?Fh-Nb^7U=a5Md{OgIbCL2MhT5* z^9h~=VW<|NNrz6Wum-r_fF2+SoW){bMzH_H`BDsQgVYQb3MxiSt6bN>f|M3f>R6X7 zGsY;TMoHxw2h&1w{v?k(shZ~ERxq3dtlDhUSY(}^rMJX-4O4132$U$H&OvteB@ek4 zrWmiIiu2J`kR?tDTX}8&M{V>%y?+5@3BDvKsjasEL!#O6`aoOVm1BWxUO1Cvw_Ys^ zkq6=CHvwUawrN3K-a>AX{f-U7{%d>6URyubr6wnHL*6`wJj(pvd zu9bazwmBxW_`y{BW4{-U?RW1k4sSnSQE-UQ`XcSd`mm9_uF%lb_X1xI3n>hG?pSTn zil-au4u>Si-^uH{J-_$3dDB9g`M%w_y11!7wep0iMG`nxtzqR?Wu)J!Y-rP`_)zIYrGPh&Oy`AH* F^0) { + if (a[i] > max) + max = a[i]; + if (a[i] == 0) + break; + i++; + } + diff --git a/jode/doc/gimp/vects.fig b/jode/doc/gimp/vects.fig new file mode 100644 index 0000000..e345b20 --- /dev/null +++ b/jode/doc/gimp/vects.fig @@ -0,0 +1,42 @@ +#FIG 3.2 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 1800 4725 2700 4725 2700 5400 1800 5400 1800 4725 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 2475 4275 3375 4275 3375 3600 2475 3600 2475 4275 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 2475 2475 3375 2475 3375 3150 2475 3150 2475 2475 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 1 + 2475 3825 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 2475 5850 3375 5850 3375 6525 2475 6525 2475 5850 +2 1 0 1 0 7 100 0 -1 0.000 0 0 7 1 0 3 + 1 1 2.00 60.00 120.00 + 3600 5400 2925 5625 2925 5850 +2 1 0 1 0 7 100 0 -1 0.000 0 0 7 1 0 3 + 1 1 2.00 60.00 120.00 + 2925 4275 2250 4500 2250 4725 +2 2 0 1 0 7 100 0 -1 0.000 0 0 7 0 0 5 + 3150 4725 4050 4725 4050 5400 3150 5400 3150 4725 +2 1 0 1 0 7 100 0 -1 0.000 0 0 7 1 0 2 + 1 1 2.00 60.00 120.00 + 2925 3150 2925 3600 +2 1 0 1 0 7 100 0 -1 0.000 0 0 7 1 0 5 + 1 1 2.00 60.00 120.00 + 2250 5400 2250 5625 1350 5625 1350 3825 2475 3825 +2 1 0 1 0 7 100 0 -1 0.000 0 0 7 1 0 3 + 1 1 2.00 60.00 120.00 + 2925 4275 3600 4500 3600 4725 +2 1 0 1 0 7 100 0 -1 0.000 0 0 7 1 0 5 + 1 1 2.00 60.00 120.00 + 3600 5400 3600 5625 4275 5625 4275 3825 3375 3825 +2 1 0 1 0 0 100 0 20 0.000 0 0 7 0 0 8 + 10800 2250 9675 3375 9675 2700 8325 2700 8325 1800 9675 1800 + 9675 1125 10800 2250 diff --git a/jode/doc/header.inc b/jode/doc/header.inc new file mode 100644 index 0000000..e8f8805 --- /dev/null +++ b/jode/doc/header.inc @@ -0,0 +1,63 @@ +"; +} + +function sflink($link) { + echo ""; +} +?> + + +Java Optimize and Decompile Environment (JODE) + + + + + + + + + +
JODEPowered by SourceForge
Best viewed with Any
+      Browser
+ + + diff --git a/jode/doc/history.htp b/jode/doc/history.htp new file mode 100644 index 0000000..f12e608 --- /dev/null +++ b/jode/doc/history.htp @@ -0,0 +1,20 @@ +

+

Someday I found guavad, a disassembler for java byte +code (it does similar things like javap -c). I used +it on a class file, and found that it was possible to reconstruct the +original java code. First I did it by hand on some small routines, +but I soon realized that it was a rather stupid task. So I wrote a +small perl script that +did this process automatically. At the end of the next day I had my +first working decompiler.

+ +

Now while the perl script is working, it is not easy +to use. You have to decompile the code first with a disassembler, cut +out the code of a single method, and run the perl script on it. I +decided to get the bytecode directly out of the class files and do +this all automatically. I decided to write it in java +now, because it suited best.

+ +

Just for the records: the java code is now more than 50 times +bigger than the original perl script and is still growing.

+
\ No newline at end of file diff --git a/jode/doc/htp.def b/jode/doc/htp.def new file mode 100644 index 0000000..b53a900 --- /dev/null +++ b/jode/doc/htp.def @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + +
+
diff --git a/jode/doc/index.htp b/jode/doc/index.htp new file mode 100644 index 0000000..8b6494a --- /dev/null +++ b/jode/doc/index.htp @@ -0,0 +1,74 @@ +
+ +

JODE is a java package containing a decompiler and an +optimizer for java. This package is freely +available under the GNU GPL. The bytecode package and the core +decompiler is now under GNU Lesser General Public License, so you can +integrate it in your project.

+ +

The decompiler reads in class files and produces something +similar to the original java file. Of course this can't be +perfect: There is no way to produce the comments or the names of local +variables (except when compiled with debuging) and there are often +more ways to write the same thing. However, JODE does its job quite +well, so you should give it a try and start the +applet.

+ +

The optimizer transforms class files in various ways with +can be controlled by a script file. It supports the following +operations:

+
    +
  • Renaming class, method, field and local names to shorter, +obfuscated, or unique names or according to a given translation +table
  • +
  • Removing debugging information
  • +
  • Removing dead code (classes, fields, methods) and constant +fields
  • +
  • Optimizing local variable allocation
  • +
+ +
+ +
+ +
    +
  • JODE 1.1.1 is out. With support for javac v8 (jdk 1.3).
  • +
  • The license changed to LGPL for the bytecode interface and decompiler.
  • +
+
+ +
+ +

The current version has problems try/catch/finally code produced + by java 1.4 compiler. You may try the latest CVS version or pre-release + instead.

+ +

Some jdk1.3 synthetic access functions aren't understood. The + produced source contains access$xxx functions, but it still compiles.

+ +

There may be other bugs, that cause Exceptions or invalid code. + If you have such a problems don't hesitate to issue a bug report. + Please include the class file if possible.

+ +
+ +
+ +

If not all dependent classes can be found, the verifier (which is + run before decompilation starts) may exit with a type error. You + can decompile it with --verify=off, but take the warning + serious, that types may be incorrect. There's sometimes no way to + guess the right type, if you don't have access the full class + hierarchie.
+ + This is not a bug in the verifier: java will complain the same way, + if it is run with bytecode verification turned on. And if you don't + have the dependent classes, you can't compile the code again.

+ +

There may be situations, where the code doesn't understand complex +expressions. In this case many ugly temporary variables are used, but +the code should still be compileable. This does especially happen +when you compile with `-O' flag and javac has inlined some +methods.

+ +
\ No newline at end of file diff --git a/jode/doc/jode-logo.png b/jode/doc/jode-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..866a784d4ce29df0afef3f16def3f161c61dbf84 GIT binary patch literal 5068 zcmV;-6Ep0IP) zfqsL9f{BZWjgO3yl#!R2mYbcKpP`mCX&B@Tx z&(qb?+1%LP;N9cp;ppn;?C$IF^YHfg_4)hw{{Q@Sztr;p0004WQchCHK!vHX9iJ z9L}7}%#h3d%CG#&ul&mYqwKcZ!!Lzw#pvHl$djvTQ`I-WNHUOZuZUQY8vR9*L(KYZ z<)-M~9I05Ty$+4Ew`MmTJU`y%{o5=RZmUY8H(mcgtY!*vhnGWeVH z4v!lWr!SH`+W#o5t6LN)RdpHFdfdoIwU%l_@f)lbqq`z?evaO*>nL6%MbpDgH%3>H zL|Q+Hu-$66BFAd}5Uo{3$awP5=*^bL%UZ9D{!2}~r6c!~$Zi})528nsCO=`!xLAkef>^uJH#+|`jyCfe&|WIv-WIODK#z;1@_A?J zUkdBIAaM5ESA*C7?H=%DOxu&)yx*45zb&1MVBJ@RADV{_;Dv6KunvtrZAw1$=etCY z=Y3-^U%wxl?pFHX^)5BE{GZYC?cn$0^RQe0ovNWLZePNszfRWCvN|Rspm{6w80JTo z3dnv^7kXl)r|~-6+FEw*lbg@ZbZdisVLij_$<9)lC#h0@YBZl~m2hjR4`Myhks==n zb++TSwhw(JL2sqTjNcRc{gpn{YsOp$S(~eey&Wx8bk`cJKJ7U97>Kczl&KYS zs7;w_p=Qce%i8}LTDFMnjZwRPSCo*HFFw}WgW1Lwmcy_S*NvOOPT)YF^^583C~KcR z0iSbZJLD%Nq^hHZym@0J`5S#@b^N3oMQ7uao`s574(lXLd+( zlozW1^NhH@(LGbZ0dzBI3#K$GJNt{}FzwdvJs(^u-M71oed0s3DY9lZV5tbO81eiZ z_z7dID`U(4COPbPAE_HtWDA+35h(-n7}GV&>_rO4lJ;r;P*JHtB8#jGdB7KuE_^Qg z3Yg%}ci-)wE3f3GXy*sAB%+phH_Yj4{Z1nNPxa0I+47R9nQakAt0y*(`?uv{ZWs%5 zZ~q@iGecvrA}bxA_J3O}GW({V2bRc7UmxuMx*P}xsd!%Mg}6m?`&USp{;U9uNN=`( zu{uH|qpq`wWee}WR9zu#<0w5?B4s@NWqOo3k;+e>Bp^J3uj#gFv|2ZQO0U&2%zuz9 zQD91)j8V@Sxo&u2ZI|csj#}YgZW$4$fNPJ=9cdXxoP#ZvFaqxEhVbjp&%RWWr|Y<# zg|FG*RcLScNyg|oFx@D9H^jg~P4O&ADdl|WzxV{R#9)m?ogLjo<@t@2aBfE2fUhOQ z6%65*XAfUpoIOb%b8&kv1D5qB%>~*bb&YN!DCm?+@4o_u2U}8Ql(NU0Y7gu7uZbjlq{I;i8X8bVAkUq<8|MOOeZK>ZMo)Q+$DX3q_S3D_!gi3wd@iJ3cEhzeW$-)vO0UL;O zziZsihK7)u^Xq}B=!fQ7ILt?Qqp2%61uWAZk>2( z!ZK_Le6Yyi{p`l*I3yYq#(0`h?btsIf5KBP9W z9}HL&Jm)8ukTM0H#s>C*6C{pUg*}q-z>Yq68HapeMI0H%#hzUN?9l6$95NElXOloh zM8I0uNG;Acv1!~b#w$W66DzYmY5>-V$g^PYhZ)!b$H)Z0B7YR1qX2XqU>6pGo<|PZ zr?lw;$i&j_7XO~2h`=*2u_gk%B^Rt7l`O$7um$=W20uqr!NDoxsQK1(ok(B$r}mBW zO)TYZMf*}Qr_!?z8_H023m7QRspc$b! z?nk)tB&AFgkO_ZeL2dz}JuSLNv}V-;GzOUQLhkbz4|c@NPYZrtF559+5n2m^9nx2L z^38+Gwe#tc-Po&F!(c&o3&CI+d*&lBTc8(;+~6@DtUE-N025ojQ{pt%8KT58TRM4- z2r~*ibi10)El|KRBJFM=%H6W?sS(%2YCf9%3TPtHE*fkFgM~6M<%*(k!O9+3T`-T! zK9k4~drn~gq^l_63xBiEa<^#BI)Q~NAjjiJJ#7 za{lT>1W)LW(@UHJ>7;WC-fhs`LPUhXarch7A0{h^qD*WoSos9D3*DIVqe7Aelh!(O zuX*JPPVL|%HlBxBOTZQLxVCN3-C|K9a1dpp@}>{g?7|tZFjyHh8O+av@Sv;Jveja8`4$vfB!jUef$`mLz&)u@9To*!=jSoJOnT)armU+Sp zWj~_AU}Gl!n4lt86_PCI#%c?GnUhM)ATP@mTud13M=D1a#}!XD>~0|%EaA-3YRwof zRCc7o>Rnv0yn~8Lf>{09H{5vL>9&UR&YZ49>j<-@54< z7wm!rOKWU8urqkYTw-@i3MG_X01p7zvKy(gU`>u!Mgza{x!x%xSsAnhnzdpgmSb)! zROUinDr{{bqAFrt8f;v43xB|xSP!fMfh`AMZ49={0lNp9m&^AgSQ&vStRa(ui76w# z^HNJ`!Qv6M!Ev`ll$S|!%RbmU7;JgNg57hHdi$2%~5-h#s^i+sNOdgl80zqkp zpgjyH?a+B(W4r>3t|0J>iDBQ+(=28T_Q9}V;i0Qrw9ur9Ex>`lrtRp5DDT(1kp+Q` z*4TG^lq6sa8DzJ1rLfu{Z}4VJ;8A7!G*~7RTOyn-6dfMO1q4R3t|?{fU_M#Gt3up4 zKJ?&)ZAvL!Em4onZt*-4=<<-KS?oOeqU*;lNwDmEITiZ^Hi`@nsuJQD0=F(BG)+@L zY&?CdlCFZ#70YhLmS<}LSjY%UrGB6GV?(nA1F$9oHcy*axxky`dJItYP^yI}&?b2x ziM2D2Nx%*xYhLsmBmW7~>Fkz@Ajbyz@e#Y}u{#k4hqR^~o@a>5kS1tij~SRso;xes zQ&<%Y59r*I5MO(muKxWuvPz4XbCOW1o9Kt1zRb~d&O@QRK1a3|8A}Xz+xv`O*yY%9$QRAIFnUs9@9k# znxE={-E-G;RBA}qhs_c^vl;~x?2-7gg`=udVU$=a5sk&usmCd2!gM(9mc=|kLbLht zB<3W0@E)CkliY|mbW`AjAz(5vJSho){k%H;da~D9;96P1igCA2GvMM&NE%-CyBFnC zQUk92W9z***QSQSuETR+A|`9Z3NdN$J?$nt^JtC5Gh+A;B%a2bcnVv^!`i2{RzzI) z{{&V~u+T;q0Q-{8+oD2*2AG5iIVVwEA+Ta(TYU_61XdMzQtuOBb)Xo}w>-KhJ3d!p ztya?gWU(hJ?Vtd`VzXO(t~irCq?Uf0l9(CrM=pQfteA0@)jk1M2d9+sgz>^4>EPd9 zrr&T&jn-HM)wl2i*(4^Oujw`Qa`Z0s`4;{Bi?2JCRe$oL2X>KYEn@}D#!^D4MDkst zun#XCphUMASXm`z^e-$LtYVw|tx-#LLR%e2>jMcIkjkkx=7U`)TgzCBo2+4d5}P~q z&e@e)jauh*%4~!6d2hy+_joxy3hx4;gRkFAwYx*H>gg zUm|WM@)Hq^C*Ne_Acd@Kr02{kT&1bf1teDkfbBIl{gzyzTat73XkiUEMKJAp?6CR+LLh2i??_d z?ybKj9^4S-!=RpB7SG6^r%i*^1i&udkIR|auC6r^(FODs3Uj&hR8}%0Y%Enq@Yknk zzGd~z>mpbb$l3B}$^|=Ry#i0xv0w^!#*(*AoFS_VfnCK#1}Nn|5VXn`54K7YKDRntWXTgDgx}tFgAOyyFOT&>_iAGICi~LOWCEguM{P- zmv*hOZuq!uYpJvyA7Tz6ijdJ_^leGF>sT1dbJiF6*> zNWBLixk&i&gDK5Wd2T8QTf21&w?1#NOA=7@20hOBw?5*D*&tc?a3I*!GNMDpGxq78 zQwHVp{0_#F2+fcMfhGf+5!VCrBHfqV8PXw-RQ)2b78hi?!(}5gCjM^Z&JsFU*?tPx z?>bzB?Af=xHM4zf$J>7s>}QWn#J!x$HTOo9=%n#~5$uM|XGp!=`7+P+{hR5w?c>Ve z`QfgBJD9J&46*BrE+zSFUQuh%WIO-H#={@;KfD)D78G^*meo5OK0;Oi-EVW*$bvrf z%lA{YCeLxtk#yG@&bQ~D=(8U>kEG@@6Gmri-~0xTw~p`&NAq%Lyh^Trn~*y8uMZv$9MW-e>lGi8m3=+{Y_#Vf zveo4b{1dT(?Q;sfHCnpFdz5pzXZ9?dyZ^BS-zOV|xNu*4>5cT&mh*Z$%N%uz8M;d` z1KYfF1+{uGbC;1e+r8!@Tnp$uoTXOzJLOp&&XyZz!1?^xt{1amr(U>_Hwq4Ht0sH_ zMLl;h3C}^f?=Sote|XcoPX+YjgdEtmWA6JNue97HWdfeI;E41+Sgz|`gY?!ndhe;6 zeP0~2m%FSi^8b*s6$Tumw;Ffk3_B{l<}NSH3&y|k5mr|*1-tpMbth$UqC@UWr1>vU zeG;uUy-ii6a2D40+*irw_M54;lJ4ZbUk+ovYIw-%<-Sxj!A?r|Prcz}xms^`_`YwJ zv!VK|+@&*uRW)^GH1WVDPjg=`!>d@PuhY1q`*x*|dM~wmQJ11no1H&6iwkG0NIwa7z!t-Q|G*>yf{V3V4w-1o^0a;6K);H*CF-xkxsYeOzO zkA!r6Q&pO;1O4qX0|-2@N%xwO`)*nGI`XxLH-B5}_80ryPm|-o=HtDRpC*6*rOq7= ihup9H%CG!i$o~L2noH)HfT@-M0000 + + +Java Optimize and Decompile Environment (JODE) + + + + + + + + + + +
JODEPowered by SourceForge
HTML coding Powered by htp
Best viewed with Any Browser
+ + + +
+ + + + + + + + + + + + + + + + + diff --git a/jode/doc/jode.texi b/jode/doc/jode.texi new file mode 100644 index 0000000..02d6d5e --- /dev/null +++ b/jode/doc/jode.texi @@ -0,0 +1,11 @@ +\input texinfo @c -*- texinfo -*- + +@setfilename jode.info +@settitle Jode, a Java Decompiler +@direntry +* Jode: (jode). A Java Decompiler +@end direntry + + +@include technical.texi +@bye diff --git a/jode/doc/license.htp b/jode/doc/license.htp new file mode 100644 index 0000000..6890496 --- /dev/null +++ b/jode/doc/license.htp @@ -0,0 +1,21 @@ +
+

JODE is Copyright © 1998-2004 by Jochen Hoenicke.

+ +

This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version.

+ +

You can redistribute some of the packages under the +terms of the of the GNU Lesser General +Public License as published by the Free Software Foundation. See +the copyright headers in the source code.

+ +

This program 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 for more details.

+ +
diff --git a/jode/doc/links.htp b/jode/doc/links.htp new file mode 100644 index 0000000..a73726c --- /dev/null +++ b/jode/doc/links.htp @@ -0,0 +1,75 @@ +
+

Other decompilers

+ +

Other obfuscators

+
    +
  • The Open Directory list
  • +
  • Hashjava is another free obfuscator. It is no longer maintained, though, since its successor was commercialized.
  • +
  • Zelix +Klassmaster does a very good flow optimization and also decrypts +strings. But JODE's deobfuscator can undo both.
  • +
  • Christian S. Collberg has some really interesting papers about non reversible obfuscations.
  • +
+

Graphical User Interface

+ +

Software Directories

+
    +
  • Get everything and anything for Linux at the + Linux Directory. +
  • +
  • A great place for developing free software is + SourceForge +
  • +
+

Miscellanous packages needed to run JODE

+
+
CYGWIN (unix tools for win95/NT)
+
+ http://sourceware.cygnus.com/cygwin/ +
+
JDK 1.1:
+
+ http://java.sun.com/products/jdk/1.1/ +
+
Swing for JDK 1.1:
+
+ http://java.sun.com/products/jfc/index.html#download-swing +
+
JDK 1.2:
+
+ http://java.sun.com/products/jdk/1.2/ +
+
Getopt:
+
+ http://www.urbanophile.com/arenn/hacking/download.html#getopt +
+
Collection Classes:
+
I have written a small script that puts the collection classes +from the GNU Classpath Project +into its own package (gnu.java.util.collections). This +script is now part of GNU classpath. For your convenience I have put a +precompiled jar +file on this server. +
+
+
\ No newline at end of file diff --git a/jode/doc/menu.inc b/jode/doc/menu.inc new file mode 100644 index 0000000..a741623 --- /dev/null +++ b/jode/doc/menu.inc @@ -0,0 +1,47 @@ +Home
" , "selflink", "index", + "Project page" , "sflink", "project/", + "Applet" , "selflink", "applet", + "Download" , "selflink", "download", + "FAQ" , "selflink", "faq", + "Feedback" , "selflink", "feedback", + "Documentation", "selflink", "usage", + "License" , "selflink", "license", + "History" , "selflink", "history", + "Links" , "selflink", "links", + "Blue Sky" , "selflink", "bluesky"); +?> + + + +
diff --git a/jode/doc/myproject.jos b/jode/doc/myproject.jos new file mode 100644 index 0000000..c32f852 --- /dev/null +++ b/jode/doc/myproject.jos @@ -0,0 +1,84 @@ +# This is a sample script file to obfuscate my project + +# The class path should include everything that is needed to run the +# project. Don't forget the java base classes (rt.jar or classes.zip). +classpath = "c:\\jdk1.2\\jre\\lib\\rt.jar","d:\\project\\java" + +# The jar, zip file or directory in which the obfuscated class files +# should be written. +dest = "obfuscated.zip" + +# Write the reverse translation table to translat.tbl. With the help of +# this table you can later undo the renaming. +revtable = "translat.tbl" + +strip = "unreach","lvt","inner" + +# this variable will tell, which classes and packages should be included +# in the obfuscated.jar package. +load = new WildCard { value = "org.myorg.myproject" }, + new WildCard { value = "org.myorg.mylib*" }, + new WildCard { value = "org.otherorg.shortlib" } + +# this variable will tell, which classes and packages must not be +# renamed. +preserve = new WildCard { value = "org.myorg.ApplicationClass.main.*" }, + new WildCard { value = "org.myorg.AppletClass..()V" }, + new WildCard { value = "org.resources.BundleClass*..()V" }, + new MultiIdentifierMatcher { + and = new WildCard { value = "org.myorg.publiclib.*" }, + new ModifierMatcher { access = "PUBLIC" } + } + +# There are different renamers currently. This is just an example that +# produces very good obfuscated code, that is still valid bytecode. +renamer = new StrongRenamer { + charsetStart = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ" + charsetPart = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789_$" + charsetPackage = "abcdefghijklmnopqrstuvwxyz" + charsetClass = "abcdefghijklmnopqrstuvwxyz" +} + +# The constant analyzer does a great job to remove constant fields and +# deadcode. E.g. if you obfuscate the decompiler applet it will +# remove the whole debugging code, since the applet doesn't need it. +analyzer = new ConstantAnalyzer + +# The LocalOptimizer will reorder local variables to use fewer slots. +# It may still have some bugs, so remove it if your applet doesn't +# work (and send me the class). +# The RemovePopAnalyzer will remove instructions that were optimized +# away by the ConstantAnalyzer and LocalOptimizer. +post = new LocalOptimizer, new RemovePopAnalyzer + +################################################################ +# The syntax for load and preserve is as follows +################################################################ +# +# preserve ::= +# // preserves everything that is matched by +# // at least one identifier matcher. +# +# IdentifierMatcher ::= +# MultiIdentifierMatcher { and = } +# | +# MultiIdentifierMatcher { or = } +# | +# WildCard { value = "" } +# | +# ModifierMatcher { access = "" +# [access = "" ...] +# modifier = "" +# [modifier = "" ...] +# // identifier must fulfill all constraints +# } +# | +# SerializedPreserver +# +# AccessSpec ::= +# "> (PUBLIC|PROTECTED|PACKAGE|PRIVATE) +# +# ModifierSpec ::= +# (ABSTRACT|FINAL|INTERFACE|NATIVE|STATIC +# |STRICT|SYNCHRONIZED|TRANSIENT|VOLATILE) +# \ No newline at end of file diff --git a/jode/doc/pattern.txt b/jode/doc/pattern.txt new file mode 100644 index 0000000..17d478b --- /dev/null +++ b/jode/doc/pattern.txt @@ -0,0 +1,65 @@ +inner classes: +access methods: +Method int access$0(jode.test.InnerClass) + 0 aload_0 + 1 getfield #13 + 4 ireturn + +Method void access$1(jode.test.InnerClass, int) + 0 aload_0 + 1 iload_1 + 2 putfield #13 + 5 return + +inner class: + private final jode.test.InnerClass this$0; +Constructor +Method jode.test.InnerClass. Inner(jode.test.InnerClass) + 0 aload_0 + 1 invokespecial #6 + 4 aload_0 + 5 aload_1 + 6 putfield #11 + 9 aload_0 + 10 aload_1 + 11 putfield #11 + 14 aload_0 + + + + + +.class operator: + +usage: + 0 getstatic #23 + 3 ifnull 12 + 6 getstatic #23 + 9 goto 21 + 12 ldc #25 + 14 invokestatic #21 + 17 dup + 18 putstatic #23 + +or: + 0 getstatic #13 + 3 ifnonnull 14 + 6 ldc #1 + 8 invokestatic #12 + 11 putstatic #13 + + +Method java.lang.Class class$(java.lang.String) + 0 aload_0 + 1 invokestatic #39 + 4 areturn + 5 astore_1 + 6 new #41 + 9 dup + 10 aload_1 + 11 invokevirtual #47 + 14 invokespecial #51 + 17 athrow +Exception table: + from to target type + 0 5 5 diff --git a/jode/doc/poweredbyhtp.png b/jode/doc/poweredbyhtp.png new file mode 100644 index 0000000000000000000000000000000000000000..d38a52c1db9b25fb4d25df007cd7284da1891cc7 GIT binary patch literal 1313 zcmV++1>X9JP)({l8}#slCjL3Afuq3 z!KW#;sb#FDmbI&Xv8#`=v9Id70_VsU-oaYe#BuS|0LI6}(aW&W&cNW)uhZ1g=HSWn z@@x0pF2XskIMF-Rf2?#ei$r6cH000B@NklsI106oqNRWTHkc;i5GN zkqVds4Umoj3hDd5)ts~lI6nkkYc{2!g?`;@C+Cz(<-aMvPVOKXGP`#dbv5|o0*&<#JHueC9-{$r!-%e9^1V_T~ z!m&|OnG#$=0SlVVO0oyb&`E4apz=J(Rp4kuf+)9$Qy85k0S`}(8K9+7ifb2Qp+c#| ztiW!zo6UB+ozG?iPxj!jQbC#t6F7$)M}{(KkW*L}Baikpa%W)9tRt9MLviF-xHtVd zAOX7DZFj5r`)uYX!GoFd)|~|~mKt*%__IQ~Vyx^P*GI<@w+J%Em^guuwwP&K8+j^l zce^ZGEEmfGWaKX;KB~Y724Lneb;ySVw}UZqy(V{H*)jsko@`)_aQU|bv1MR$;;X$D~H z!QdzVcYWud#^XAc)L|?g2G7bT&(>Cqa2H<`i>2Fn@e&LK$Oc}!%3rGRr1O;BT+F+4p z*&*h0$5(JA3q#Hsa4Dgb5STw4AW(n^t^)aE^0JGLeJLNw>-ZKIWRT|)Y^=v3OlY+? zZJv=YId+$&r6BPlrN?Eme*Gzi!zW$_3aCE`3iwJ^Z4s3CQX);fS zzc7=$a7+otRfGuCeKVQfB~4tfcRHOc%dc=Opv-fJHHFH{H`DpF*MmHSC@e!3n1-AV zlV)#v7nA#^r>9QiA-%>iSH8?yNc=KPS7|(tr|rjECdEBuOsEjuUbC4DdvWctUN68m z`f|q3kq8RH@rv@x8;)ltj$a`U!Xr+pkYT%(BzH-#_1vvL9Ka&YFRcpb9}Gbok;EMe zD7DZO<53(xQV}UBh6!YIH*5_iW8t+`KHZ{YA&6s5&0ytJNx=#Br;4%Ll{P zmQR5{_um$amj=vS=jP`3AiZK87qm)v>J_lx4^2FsCBs@jNm_BQ{Vbz#nx(b@Zb9|& zm6i9=gHBz_I-f^h$k(_(?8~vzX}Zs{rRV;xd&9fydd0CRou10+Dqcit7VGCoS!2?& z3(I;aCac|cyO~7Ve*5uXYG#bFa|=}KtjB`q3?=}mph?;2e*(5%eqVeBC_I)*=w`is zA4&65$LEK3xmditEhdFVBiIhlyq|#ov2XNJo0GJ$?i>C13$pXM?ETB{{DJ#_|26*t Xt>n~Y13#|d00000NkvXXu0mjfaJ6#s literal 0 HcmV?d00001 diff --git a/jode/doc/technical.texi b/jode/doc/technical.texi new file mode 100644 index 0000000..0d6c800 --- /dev/null +++ b/jode/doc/technical.texi @@ -0,0 +1,279 @@ +@node Technical Info, Top, Top, Top +@chapter Technical Information + +This chapter contains information, how the decompiler works. + +@menu +* Types:: +* Expression analysis:: +* Flow analysis:: +* Solving unknown stack-ops:: +* Highlevel analysis:: +@end menu + +@node Types, Expression analysis, Technical Info, Technical Info +@section Type checking and guessing +@cindex Types, Conversions + +The class jode.Type is the base class of all types (except MethodType). +A type under jode is really a set of types, since it sometimes cannot +know the exact type. A special type is Type.tError which represents the +empty set and means, that something has gone wrong. + +A type has the following operators: + +@table @asis +@item getSubType +Get the set of types, that are implicitly castable to one of the types +in this type set. + +@item getSuperType +Get the set of types, to which the types in this type set can be casted +without a bytecode cast. + +@item intersection +Get the intersection of the type sets. + +(getCastHelper?) +(getImplicitCast?) +@end table + +There are simple types, that can only casted to themself (like long, +float, double, void), 32 bit integer types (boolean, byte, char, short, +int) and reference types (classes, interfaces, arrays and null type). +There is also a type range to represent sets of reference types. + +createRangeType +getSpecializedType +getGeneralizedType + +The meaning is +ref1.intersection(ref2) = + ref1.getSpecializedType(ref2).createRangeType(ref1.getGeneralizedType(ref2)) + + + + + + +{IBCS}[] intersect {CSZ}[] = {CS}[] + intersect + --> + +The byte code distinguishes five different types: + +@enumerate +@item 16 bit integral types (boolean, byte, short, char and int) +@item long +@item float +@item double +@item reference types (objects, interfaces, arrays, null type) +@end enumerate + +It sometimes makes a difference between byte, short, char and int, but +not always. + + +16bit integral types: + +We differ seven different 16 bit integral types: +@table @asis +@item I +@code{int} type +@item C +@code{char} type +@item S +@code{short} type +@item B +@code{byte} type +@item Z +@code{boolean} type +@item cS +An @code{int} constant whose value is in @code{short} range. +@item cB +An @code{int} constant whose value is in @code{byte} range. +@end table + +Each of this types has a super range and a sub range: +@multitable {type} {(I,C,S,B,Z,cS,cB)} {(I,C,S,B,Z,cS,cB)} +@item type @tab sub types @tab super types +@item I @tab (I,C,S,B,cS,cB) @tab (I) +@item C @tab (C) @tab (I,C) +@item S @tab (S,B,cS,cB) @tab (I,S) +@item B @tab (B,cB) @tab (I,S,B) +@item Z @tab (Z) @tab (Z) +@item cS @tab (cS,cB) @tab (I,S,cS) +@item cB @tab (cB) @tab (I,S,B,cS,cB) +@end multitable + +getTop() getBottom() give the type directly. + +createRangeType(Type bottom) does the following: +If top == tUnknown , union all supertypes +If top is 16bit type, + intersect (union of subtypes of top) (union of supertypes) +Return tError otherwise. + +Type.createRangeType(Type top) does the following: +if Type == tUnknown + if top is IntegerType + new IntegerType(union of subtypes of top) + + + + + +Hints. We distinguish strong and weak Hints: + +strong Hints: + assignment: + lhs.strongHint = mergeHint(lhs.strongHint, rhs.strongHint) + lhs.weakHint = mergeHint(lhs.weakHint, rhs.weakHint) + rhs.strongHint = lhs.strongHint + + + binary op: + left.weakHint = mergeHints(left.weakHint, right.strongHint?strongHint: weakHint) + + binary op +types that may occur directly in bytecode: + (I,Z) + (I) + (Z) + (I,C,S,B,Z) + (I,cS,cB) + (I,cS) + (I,C,cS,cB) + (I,C,cS) + (I,C) + (C) + (S) + (B) + (B,Z) + +now the sub (>) and super (<) operators + + >(I,Z) = (I,C,S,B,Z,cS,cB) New! + >(I) = (I,C,S,B,cS,cB) New! + >(Z) = (Z) + >(I,C,S,B,Z) = (I,C,S,B,Z,cS,cB) + >(I,cS,cB) = (I,C,S,B,cS,cB) + >(I,cS) = (I,C,S,B,cS,cB) + >(I,C,cS,cB) = (I,C,S,B,cS,cB) + >(I,C,cS) = (I,C,S,B,cS,cB) + >(I,C) = (I,C,S,B,cS,cB) + >(C) = (C) + >(S) = (S,B,cS,cB) New! + >(B) = (B,cB) New! + >(B,Z) = (B,Z,cB) New! + + <(I,Z) = (I,Z) + <(I) = (I) + <(Z) = (Z) + <(I,C,S,B,Z) = (I,C,S,B,Z) + <(I,cS,cB) = (I,S,B,cS,cB) New! + <(I,cS) = (I,S,cS) New! + <(I,C,cS,cB) = (I,C,S,B,cS,cB) + <(I,C,cS) = (I,C,S,cS) New! + <(I,C) = (I,C) + <(C) = (I,C) + <(S) = (I,S) New! + <(B) = (I,S,B) New! + <(B,Z) = (I,S,B,Z) New! + + >(I,C,S,B,Z,cS,cB) = (I,C,S,B,Z,cS,cB) + >(I,C,S,B,cS,cB) = (I,C,S,B,cS,cB) + >(B,Z,cB) = (B,Z,cB) + >(I,C,S,cS) = (I,C,S,B,cS,cB) + >(I,S,B,Z) = (I,C,S,B,Z,cS,cB) + >(I,S,B,cS,cB) = (I,C,S,B,cS,cB) + + <(I,C,S,B,Z,cS,cB) = (I,C,S,B,Z,cS,cB) + <(I,C,S,B,cS,cB) = (I,C,S,B,cS,cB) + <(B,Z,cB) = (I,S,B,Z,cS,cB) + <(I,C,S,cS) = (I,C,S,cS) + <(I,S,B,Z) = (I,S,B,Z) + <(I,S,B,cS,cB) = (I,S,B,cS,cB) + + +Zu betrachtende 32bit Typen: + + (I,Z) = (I,Z) + (I) = (I) + (Z) = (Z) + (I,C,S,B,Z) + (I,cS,cB) + (I,cS) + (I,C,cS,cB) + (I,C,cS) + (I,C) + (B,Z) + (I,C,S,B,Z,cS,cB) + (I,C,S,B,cS,cB) + (B,Z,cB) + (I,C,S,cS) + (I,S,B,Z) + (I,S,B,cS,cB) + +@node Highlevel analysis, Technical Info, Solving unknown stack-ops, Technical Info +@section Highlevel analysis +@cindex passes + +@section The passes + +JODE works in three passes: + +@subsection Pass 1: Initialize + +In the initialize pass the methods, fields and inner classes are read in +and the inner classes are recursively initialized. In this pass the +complexity of the class is calculated. Anonymous and method scoped +classes aren't even considered yet. + +@subsection Pass 2: Analyze + +The analyze pass is the real decompilation pass: The code of the methods +is transformed into flow blocks and merged to one flow block as +described in a previous section. The in/out analysis for the local +variables is completed, and the locals are merged as necessary. The +parameter 0 for non static method is marked as ThisOperator in this +pass. + +The constructors are analyzed first. If they initialize synthetic +fields, this is taken as clear sign that this are outer value +parameters. So afterwards, these synthetic fields know their value. + +Then the methods are analyzed. Each method remembers the anonymous +classes it creates for Pass 3, but these classes are not yet +initialized. Inner classes aren't analyzed yet, either. + +@subsection Pass 3: Analyze Inner + +As the name of this pass makes clear the inner classes are initialized +in this pass, i.e. first Pass 2 and 3 are invoked for the inner classes. + +After that the method scoped classes are analyzed: For each constructor +it is first check if one surrounding method knows about it. If not, a +new class analyzer is created for the method scoped class and Pass 1--3 +are invoked. Every surrounding method is then told about this new class +analyzer. + +After this pass, every anonymous constructor is analyzed, so we know +which constructor parameters can be outer values. The constructor +transformation may force some other outer values, though. It is also +known, in which method a method scoped class must be declared. + +@subsection Pass 4: Make Declarations + +The last pass begins with transforming the constructors of a class. Now +the outer values are fixed and the constructor parameters and synthetic +fields are told their values. + +After that every method determines where to declare local variables and +method scoped classes. Local variables are declared as final if a +method scoped class uses it as outer value. The name of local +variables is guessed now. + +This pass is done recursively for inner and method scoped classes. + + diff --git a/jode/doc/usage.htp b/jode/doc/usage.htp new file mode 100644 index 0000000..8d63d24 --- /dev/null +++ b/jode/doc/usage.htp @@ -0,0 +1,250 @@ + +

On this page:
+Decompiler
+   Command Line
+   AWT Interface
+   Swing Interface
+   Java Interface
+Obfuscator
+

+
+ +
+

After you have downloaded the jar archive +put it into your CLASSPATH. The package +swingall.jar is also needed if you are using JDK 1.1.

+ +
  • Under Windows you have to start a MSDOS session and type +something like: +
    +set CLASSPATH=C:\download\jode-.jar;C:\swing\swingall.jar
    +
    + +
  • Under Unix you start a shell and type (for bourne shell): +
    export CLASSPATH=/tmp/jode-.jar:/usr/local/swing/swingall.jar
    +or for csh: +
    setenv CLASSPATH /tmp/jode-.jar:/usr/local/swing/swingall.jar
    +
+
+There is also a batch file for windows and a script file for unix, +that you can use. You can extract it with the following command: +
+  jar -xvf jode-".jar bin/jode.bat resp. bin/jode
+
+Edit the file to adapt it to your paths and put it to a convenient location. +
+ +
+ +The most powerful way to start JODE's decompiler is the command +line interface. Some people don't like long command lines; they +should go to the next section.
+ +Start the class jode.decompiler.Main with the options. The +following command will give a complete list of the available commands: + +
java jode.decompiler.Main --help
+ +If you want to decompile a jar package you can do it this way: + +
java jode.decompiler.Main --dest srcdir program.jar
+ +If you have installed the batch file/script, you can use it like this: +
jode --dest srcdir program.jar
+ +

AWT Interface

+ +The AWT Interface looks exactly like the +applet. In fact the applet uses the AWT Interface. You start it +after setting the CLASSPATH (see above), with + +
java jode.decompiler.Window
+ +In the classpath line you can enter a number of jar files, zip files +and directories separated by comma(,). Then enter the +dot(.) separated name of the class you want to decompile. +Press the start button and the decompiled class should +appear. You can save it via the save button. + +

Swing Interface

+ +For the swing interface you need java version 1.2 or the separately +available swing package (see link +page. You can invoke it with the following command (JDK1.2 only): +
+java -jar jode-.jar classes.jar
+
+or if you have set the classpath (see above) +
+java jode.swingui.Main classes.jar
+resp. jode swi classes.jar
+
+ +

The swing interface will show the package hierarchie of all classes +in the classpath on the left side. You can now select a class and the +decompiled code will appear on the right side. Via the menu, you may +change the classpath or switch between package hierarchie tree and +class inheritence tree.

+ +

The swing interface is very useful to browse through class files if +you don't have the source code. You can also use it to trace bugs in +library code. It is not meant to generate java files and so +you won't find a save option there.

+ +

Java Interface

+ +

If you want to integrate JODE into your own java program, +you can use the jode.decompiler.Decompiler +class. Note that the LGPL allows dynamic linking as long as you don't change +Jode itself. Please tell me if you use JODE in this way.

+ +

You should ship jode-1.1-embedded.jar with your program. This jar file is +available in the download area. +It works only under JDK 1.2 and above.

+
+ +
+

To use the obfuscator you should first create a script file, say myproject.jos. Then you can invoke the +obfuscator with:

+
+java jode.obfuscator.Main myproject.jos
+
+ +

The script file should contain the following options:

+ +

First select the classpath. You should include everything in the +classpath that you need to run your application. This also includes +the system class files (Sun puts them into classes.zip or +rt.jar))

+
+classpath = "c:\\jdk1.2\\jre\\lib\\rt.jar","d:\\project\\java",
+     "ftp://www.myorg.org/pub/classlib.jar"
+
+ +

Specify where you want the obfuscated classes to go. I recommend +to write them directly into a zip file, but you can also give a +directory.

+
+dest = "obfuscated.zip"
+
+ +

You can make JODE write its translation table. This table +can be used later to undo the name obfuscation, or you can look there +to decrypt exceptions you may get.

+
+revtable = "translat.tbl"
+
+ +

Select what you want to strip. There are several +possibilities, which can be separated by comma(,):

+
+
unreach
+
strip unreachable methods and classes.
+
source
+
remove the name of the java file (exceptions will get unreadable).
+
lnt
+
remove the line number table (exceptions will get unreadable).
+
lvt
+
remove the local variable table (debugging doesn't work).
+
inner
+
strip inner class info (reflection doesn't work correctly).
+
+
+strip = "unreach","lvt","inner"
+
+ +

Select the packages and classes you want to obfuscate. You should +only include libraries, that you don't ship separately. If you give a +package, all classes and subpackages are loaded. You can also use +* as wild card, that matches everything (including dots). +

+
+load = new WildCard { value = "org.myorg.myproject" },
+       new WildCard { value = "org.myorg.mylib*" },
+       new WildCard { value = "org.otherorg.shortlib" }
+
+ +

Select the methods and classes you want to preserve. This is +the main method for applications and the default constructor +<init>.()V for applets, resource bundles and other classes +that you load manually at runtime.
+You have to give the method +name and the type signature to identify your method. javap +-s will show you the type signatures for your classes, but you +may also use *, to select all methods with that name. +If you have serializable classes and want to preserve their serialized +form you can use the SerializePreserver.

+
+preserve = new SerializePreserver,
+           new WildCard { value = "org.myorg.ApplicationClass.main.*" },
+           new WildCard { value = "org.myorg.AppletClass.<init>.()V" },
+           new WildCard { value = "org.resources.Bundle*.<init>.()V" },
+
+ +

If you want to obfuscate (or just shorten) the identifier you can +specify a renamer. There are currently following renamer +available

+
StrongRenamer
+
Renames to the shortest possible name. You can give a charset +that should be used. It uses the same name as much as possible.
+
UniqueRenamer
+
Renames to unique identifier of the form xxx123. Useful +to reduce name conflicts, before you decompile an obfuscated package.
+
NameSwapper
+
This renamer just swaps the names. This is a funny obfuscation +option that is not very strong, but very confusing.
+
KeywordRenamer
+
Renames identifiers to keyword. You can give your own list of +keywords as parameters. Resulting code is not decompilable directly, +but it is not legal bytecode either. Some paranoid +web browsers refuse to run applets containing keywords as identifiers +(and they are completely within the Java VM spec).
+
+
+renamer = new StrongRenamer
+
+ +

You can also create a renaming table with the same format as the +table written by revtable. The entries in the table get precedence +over renamer. Entries not in the table will get renamed by the +renamer.

+
+table = "translat.tbl"
+
+ +

Now you can select the analyzer. The purpose of the +analyzer is to mark all reachable methods, find out which methods +needs to get the same name (overloading), and which method names +mustn't change (overload of library methods, e.g. nextElement +for Enumerations). There are currently two analyzers. +

+
SimpleAnalyzer
+
Straight forward analyzer. It is fast and will remove dead code +on method basis.
+ +
ConstantAnalyzer
+
Strong analyzer that will determine, which fields and instructions +have constant values. It will remove dead code on instruction basis +and replace constant instruction with a load of the constant, or +remove them completely.
This analyzer is especially useful to +revert the flow obfuscation of some other obfuscators.
+
+
+analyzer = new ConstantAnalyzer
+
+ +

Pre- and Post transformers transform the bytecode before +resp. after the Analyzer runs. Using this default should be okay. +You may remove the LocalOptimizer, though, if you have problems.

+

In the future I may add some new post transformers, that do string +encryption, flow obfuscation and similar things. If you want to write +your own Transformers please contact me, since the next version will +change the bytecode interface.

+
+post = new LocalOptimizer, new RemovePopAnalyzer
+
+
\ No newline at end of file diff --git a/jode/doc/w3c_ab.png b/jode/doc/w3c_ab.png new file mode 100644 index 0000000000000000000000000000000000000000..5ac097c40a1682a2143fc04242964f8438ba4d5a GIT binary patch literal 1035 zcmeAS@N?&q;$mQ6;PUiv2?Ej)KrGL}3?xIueq91m2LgOTTyArg54v1CRhZjUjF3nKNhppGo@<1|Sg} z;QJtbeP{#D3)SX@MJp56>T9n)9D8Z;`^`1)f4{gkLVuKOMlulOpEQ+5f_0 zmQJeZ*|tLl?HsBOLCmcy4t~!}lKVM0s{DReUoWwO6Et#A=n!b#RIo{~^E;ELq4D_aa8$s^W;j+1_0(TsF%^otdW#s=i8a zyJ=>U>cyCtp>~?@-rH{mp-n-r6KYR>U|ss=<>tEQoUc;iXZU?}^?dB}p-v}#^M=`5 z=2zZ(+jFL1&H9se+ds^VGrY6zi^g*%uASj;%KZIRjQ1?yjuXg8(E84Lc#4s_4U5^j ziMn$>J$)`#7G8DKVCfmo?;pw*U#YmgcgDR{Nxv4JmOO4%e)q<;riAe4*=;KgZ6C!_SL*97qYSkPr#@g&OPZA@?g^X@yv^#`R( z`zzL)ZPW6yeY7~}Hs6*{*4Gai>R)VKx#h0t_2myYN|iG|a#_jA|J%)?KzEOUX-4n% zGz&Sl?+N8M-tSr0e7nqdX7mnQ>3N0|-|+3u{np){A@)AaYNNaLN44nk2l^eo+Dpx@ zoD84xRpkAOZtl|Z3u(vxa`l+E1|8*!s_=|mbL@08+qShIMQ3)LTo~^)S%i!ti=d#Wz Gp$PyiRqnL_ literal 0 HcmV?d00001 diff --git a/jode/jode/jode.jos b/jode/jode/jode.jos new file mode 100644 index 0000000..e170834 --- /dev/null +++ b/jode/jode/jode.jos @@ -0,0 +1,28 @@ +# This is a sample script file to obfuscate the JODE project. + +# First we select what we want to strip. There are several possibilities: +# unreach - strip unreachable methods and classes. +# source - strip source file attribute. +# lnt - strip line number table. +# lvt - strip local variable table. +# inner - strip inner class info +strip = "unreach" + +load = new WildCard { value = "jode" } + +preserve = new WildCard { value = "jode.Decompiler.main.*" }, + new WildCard { value = "jode.JodeApplet..()V" }, + new WildCard { value = "jode.JodeWindow.main.*" }, + new WildCard { value = "jode.obfuscator.Main.main.*" }, + new WildCard { value = "jode.swingui.Main.main.*" }, + new WildCard { value = "jode.obfuscator.modules.*..()V" }, + new WildCard { value = "jode.obfuscator.modules.*.setOption.*" } + +# value = "jode.Decompiler.main.*", +# "jode.JodeApplet..()V", +# "jode.JodeWindow.main.*", +# "jode.obfuscator.Main.main.*", +# "jode.swingui.Main.main.*" + +analyzer = new SimpleAnalyzer +post = new LocalOptimizer, new RemovePopAnalyzer diff --git a/jode/lib/java-getopt-1.0.8.jar b/jode/lib/java-getopt-1.0.8.jar new file mode 100644 index 0000000000000000000000000000000000000000..1dfedf71d5c267533b471dfd05a64f4104427b8b GIT binary patch literal 49643 zcmbrlWo+e8v!E6usmoF8*- z*|KHXvMfKVYQ0sjvK$yVItcW?RaB0S2IPNR(ElAPNT`c2$S6uODTpY_NJ^-wGb%_v zC`?Vt%P}y{A;>Y%&P@GjQe|Fb+ui>GqAUjm?Jz_Yg7fc2tbd#G|MAgO|E^j(xHDM- z+#H?Um=pl6t|pcMS5bEdb31^MgB_!ji=z|3#mySvTBE+9h{uBN_eX0=FJJZtOeJk} z;%{X}IuX<()fB43%iJyZi`5Dy5t{SfXYTshH08Tlf87ns5w=mYo9=f?#=*ef5yNLjGUBqR#5C+n#);N z>ccL~yx6H96D>;z{c=*|$?6ZCm+0MbMtu1O9R0_#cAzWWB`#L+IBd8{I$g${o8&qQ zxZF>++CqL{!w146e>9FzbbrSlLD(-zqG0kmz3MSu!Xhn4O*ULSZ;!A9MEWV#0S}$0 z%&fD#q#UjF+e^;KMZELqKjDGt!0v6C5@srJDG6l;CqQwB4ArmDGjN%}h%9q<*|+31 z6s-3Na3tM1uvC5eRC9|~K=_lbW-rX(+UX;ZWL(B4Rnfxk$H`f7ASdC0AG2tP32SC? z*+P|h*q$3Ze(WFKzVt%+XmCd>_XZx0YYRm~>7#SVsh50$Ds>*(22#P>*u!WJSo7?< zHX;jhw24y}vn-K6aRMQ}v(cio182tEa_?9J4Z3f!SPVP z$%i?HR&=#F2mQ2hdK~5Y_sU+*+3EHiG8&^K53v42O2_HGz|s#85Lw9odr~Z1{u?O@ zjvL&_-S1jmSsMeIrchhU3Z3Pq;GT`R(6f{uV`QL3ZpJ)UVE{$8{ z5{OG8$Wjl4C(|o)RXa~^@rHYu)ff(_)XRM&8${0I5UqWegE)ag9(}~*C)Kw9#E-d)P1C)-jy@#NT@!Y_aO&B_JxA ztvFDPN>h;FeFdYa7@y_MyRiH^*M%5-uxHGCili97W6Xl|dgJID&zlp1@2Nw)Os6Al zuz}Pzy0cwyeyxYJb$zv~*9Om+5Fs7ich5b?ecG}S}pZ^@Gp=CdJE*u!B);ii|Rr2{I-J;5|W$Oda z`GIjwd;sz&*MhvHgxvZ$$a#N}^LeAcq^W*?Qm(u4ZGePhB_=cO%+``9btDw9WK>vr z2G$$Z_Ge2&J}PcUYAMhq!GY!ytJbJ(;T7~h`0>vb3(9E0! z+yylzG&L==`wJi4-Uhtf?vRAI0YM1&MSXTz)nEZUsn_$pPP+uErgHue<1gdkHc8-@U{ik)c#!wlLN|)GC1w_K=dQgTx4V~wea{^U^5kiwJFtT zhZv#Bfm@;mGSRmrWx=~*n$o1r=|r?2t8<~IEPU38@=jr z-Y?0EiVd`q28y}h+i)1TCR(S+YW|akH-hG`YkH3~GiifHrkCuOaGAB6Zgp5T(PQdy zbqsx-`%UqK$uINRdahvwZv2SuyXNVo025Xu-GvA_MlD%Z4!tJP_iO^YMrY1TLsrU5 zajiM82}LKuIA6;ml$m$2Fzm2F=fxcTa4~qwL6JZ=O(;oq=px`A7@E?=!a>H!VYs>j zS2zt(hgk{jh^SKp{j^N2PFY{}!PA`@!&(?CFGDw*4c9j3rX&+;1@zXJG45Z+b1jv~ z8gZEBJB>}faqEXt@;4(~o@dLYdtVi z>}4axx?creU^CYa9mazVZ1M;bm9J=%B8lB|7K>T(4%|rtj1PV75 zXOuBrvj(3KW`1UVjH@Uo>t#K#QofNp7XHqf6U=fsL%JPi3Sa+3SrQ8M__S1;U^=jAgX}JXKWO4b5Z8rg z?6?#$aEwnS75r9We6$_9bmLQYldxB9)jf#wOON*qVudDtcvNtl!57>(2GbVpgi0tT zs0zt1E&+H7EE!5v2WuKo=_-j5?(qRa=YVdG-2Y1Xp1`zk3;&by$u^J}dP%wr;zF=0 z9^^5xCBgvQR5mPTAB>rHJ#-sVqMdkh>G$UpPB;l0F7(X@omodJeVEg8Adzoo^!^~&+rDCQ+R!>>?}Q_PT(n{9pxem`aVL*g7q8iXz5-3hFd&r7u+}hUfNH1b_tjp2JQl=d? zB+stC=nl7|>Ppd5oU3Gm}%q3#QwAx`ITz94;J0kFxEr$I*30pS36E z{COa~z%2=UnR>=QlGT4EJ3l`@{?+kx%Hv@ufxu&o)ed(26#=cuw8cR8r(uo|$C4vO z?jly-ODYjj^hl}zK4awb{M?&%yQ!)=mE&Mq3jHuWykbsWXCfmgKHhokjfWY z<04sd&7mi+xv?rT`U;EoV?y*PHT!gA;d-)BQJDj>Ag8=%(+o8K@kB}ZkEdxu2-fY9 zJR*yrDA_7gEzTH1JYU4l=jf7Z=)pU4!h~91t~-cdC`<*-7rV>ShAd@ZC%qmZ4HG^D zU4mk=n~DR)s5k{NLr{I=sOs$Wgd7$FwuglwU3%nflDV)Ni-kPDNUa@8e7+fW#wPA zwpZUCMzceLbBMX-U3edPtFuOixIW95J`V|F0(xI6hZszej47L{Jy;RwMmCN)Zy!a^ zC*q;MG~zBKNAhqm3VmeydbKq9!K+%z4~wY2=8b;ZoqyF@D!~R2ewUT=cjIups6KG# zfP^Z;ZM*F6#7K3bKY@j=TebB3SLSvErug>ok0a;*kt5RoE;0O58yNqW+Q2C9=wPYj z_W^Z?;v*EJAiR@q7V_L*NlzWbpWs@wY3u51Y2(V{ zdBpcxqVC_>bwXySHVKffmz#(o=y&NVbC^@QN z32MX{ybqJf#n$`w#dCLCeJ9sQq^D#58*2lK-z>-~hP-Y1cr)7prB3>c3aKR(+`_46 zw?gj)nIiBO_nSG@FKb(Z-1&$r$kZ|t(nli{})-0ywM7vK^OE3_(z}S8jnJA&LNWc&_bu-ijF|GEvD4Cks0@RX#Xa_*am& z6>-|~AyODpj5vt{I}dhFXe1nSN?(fU%Hu%join@f7{tCfXx7jhPkj?E4l;gAe}_Mf zsr}d_1)8(Ac!5bKH8^JsQ-^qh?5N8!$aKp{+t0mNrH%3o=YIcc;;b6tc;@jzZ-d*UUjsJ9k45ABZdyn?mta`3QNF`UAutDX5(3 z146weHUs93KTZ4jL|AvoF%A_4oe{+flBeQ4Q!X0wQ$kC^^4uO>!aMd0}P{OmWGl=Nwx) z!bi@q$pmzY8glfB7k3Gmh&KPl7m7hKwEQySdrky-`=BuTc$ga>zP;T2@Csxf0mws! zTkcOXPO(9Dw*QKd5p$8{DJ~XO1(hkrVe#)@tcC&AsbFU&XXa>%KJ&x6q$)cYOrE<79+hIPUZeK(9Qu|KMWO1~{!9MWnxOv+96-TMSN9eV(8gM6`djI#2C+OT zmKsirHHjxDOw;FaO!VDWJVjza)7H~6t*fd~qZ#BTO-T(Q($DW`*@TwRC0IxZqRh8( z%f|9O1bWQ9PI7r-)pKKr@E%>nsx#BvZQw0;4!f>auc3#!;@rx57RfqlF1VnR--Gc@ z6?Zqaq@uHOzz>nyVldB3t3~f*IhkiUenTsGZL>MxH_@&NAq=EzB-D$ylp=xK#&`!1ldHYPW}$8Ri%$e&^CXI>C_HOn!Du z&eN?ADMOpY7mt@X8(n8Q{Rv*UbKSeeelHC-qy?@e6~&rSMm!(@J*xo9vhICPfEZS4 z$ELdB(A-B=YYM$d*_8+9Ag!SzDI4@hO{6lmEFKexf;vxAVQM$*fZ^P@|(Scf> z#^y~q57b`0TDGYmd!fU#Vrodiy(7~f2VO%qGK%6wZ?Y$7 z6)$`aWkVjK7^GBwnO!f*?Es;=@?=r5lh1!S$W`nvU(xJX<^uUnTEBqmdkHR{pkusm zgkHaVperUXfsxu$mH~DCvaI9QTN)jjKYJ99wXa7`a+sDA4(HyCq)a_dd}R^mET0p%BNzNP*kJ+G~v>vrt=F7RRPru zd3T?AdOhd6=jI#^gyMBpUKK-&G>ZGTQSbZt@wiq(p3v`)jTox)$Vp3~O02q`@4L$+ zTzNz!qn$uEua}p5mYsH?Zcne5L%&;Q-d7t|o|O%(J5?phTW5_rwvUp7FvrR=-W7oU zh4{3``OV!sH_dV7*wQ4WR@t=bR&Q1Omy6%`#ld@^W&)wnKxFTS&YZTpuH~Ca@nR8u zEp5wG)|u+caA~e!iP7A60l$k2HQG$HIaIG{>Zt9f@hv8yFQ1o}3-;i!LdDr~?HV7cC(fy^0D1dAw3??RznGOEbfXpJbL_}UTXZOcWHt5Th8rm{bl zfh!NVhHBrDLf7)UMmwF`>@pDx+2nu4TC6Q}c(q(J@6DeQiG+Bv*QktXc$H`=9Z&SKEn8%5Ib!Y*>#e%y&y!*W@hc>f|6DYWRY{k0=vtiF)m|&y zDE4Y{i?q;Y(ieEQgkar@_%UiqdER{S4KzeuxTRS`+y+U@lexF{odHF)XqiC1d@%lL z5;{%bPK+i2!45T(VEDDmN2Iy7KD1TQ(%maZGkExt0&5_oOmIse_(~9L52~Ti1ATrX z7-2ds3Ktmq;MHA`M1Zb`*&i_pAnMxvx+_(rBDZd}$1KvYq$7pljt{C$g{#bjZ8ZE^ z=f7$8eB!aIy!{_Y>r57-A-#z#<0M)4p(Hai>-Jf52SssyMd_vtXZGJk!7Ox12JK41 z(1m_Kh}Oi+lMrFDEU=DYGZVn@r)J{g@aI69=_2pr8!0^gU}WXo_hgoFz=6i<$PXW< zt7^Z$N!XYSP0jL^?y7{8#<32|u8e=+=}|6axrZ*q$#7B|)H7=_D0xT)rOW-pp;AGD zNHqn*k-N~8MhqzmMjWhs{%t+43YGtZ8|9~bG8oW8fdN_}iE4|Dcp_B?bD-KyCnP1< zqe7Tq4mk^T4eeNfX@VoD*OlJ%mar<+5y@$e1J*TlSs}K5t}Ce?stzTc*k}M1v?{9h z7{DtAR@h`}OFbGR=i29-nJzI<&YL%=8|0sODiDOrO=jW}Uaqvynnaa^^#+O+_2|uM z1GYO&2*j~G2jXmtYT(RcQ8K7~Zd29Iu3&M8l`3mAiOj2`2uR2cYJvPV-l|4}xo95B zp-r6nnAO(!9q)65a{Vlf@KZ17X^zkjCH^kk&tZfoEJyVsCBn%y09DKZPBp9_n&c@5tvzmlQcGq z;nA_>P?Z+rLJ}o(HG|5##;8Ocln4_2?+>}qHRhY+6PZ7p&|7ZZ3>P!QgJ6c`7Kk`+ ztIikH6o=>VV}qgRQi{EVW)`dXKaJrL2c#h_rdH=*_yB#I$zWw3Wx`XI3YukEQwm`h zI_NlKL^VGQgZ92)yNZQ|Z2g>=qbwglVTjAIcggx-qp&xXlQS}>pa|l?c`+p9QdH;5 z_{lbVvrqqDM@b}%<=wSVnGjI3#lQCVRs$F ziAXuXl0dbmjU5QK{ovf0g^-)ht+m?)cSV9!r!zl*pOZVGR9uGU0uWAkm+zWfH=9Ej zrIpXdbmt*VFBN^(U8q8513^e|q|T>QCJrptbDHj!5U~>PDiM`Qt1;ud35U{z;e&Tp zxv#irG-jB)X&>tvk{*f|;x)>{{pn+_`4e2ZOg{x>AXZ$-Dfj*UeIHd3Fs#!k$N(zo zjD=JTXgAnK>lNN~dB_)OT`gX{uvI}wYjTv#a+x2i#8eA!8lUvoM9^kXmz>Wf6TnI2VoC@ zj7wmNQ#F40h&BdGryqzT>{!wOe!g5HBC60it|VT5^ItBAfxY0aQR39UcEkr52lwzJ z^b`@gCC{s%f+A2N=)PlwQYwRFj;r?khKR1*-?$=?LGHXZJJP^cQhYk9Y`SwX6$B_Q z`0zh!tQIMAdco8Y7YR7T*JF=wS9W_{L$=#ZGmSp5tSF4tWl}_Ol*V zlc=}^_Pj@c!C~i#K6CnwBsSoovEUo;hC$&xg1(?~ua- z#L_fTgB=cNV_j*c5b!TBculQg`oOhISc2C4e-Rl>QL*^kXhQjN(jHsJH2@ak#k@7! zP^;{^SkzNbMKN*6wAcg(lwI>SoQ_uiw*K_moWS!^g5J0=2k)`Ly|247P{1~k2+{7B zg=KY%ZKj^}71mCoNGcCMQb+(?@tU;$h{Y6W8*$onMeQ%NZ^T9;0ly_KG$wonC{T6p>9@#L^yVs zTH$B{M_Wp+X-E1QV1lqnoI{X!zxYQJ_f!z-!_JEL{L`?rA^xlx*KQiIHux@`heLk? z_%~)(6EIsQd5bweWKfJbD49$iUc?`uFQRQVq(~a>en<_)BYTnk!#25 zvJYP0527zGEm)$bKf;NQ5 z_6z+ngnoR7w94+lU|6z*>;R~$gC+A@x`kz-T5fC|OMYb7p;ytR2!ZE_(%bFt{_AAZ zcl6~I3ThbM?OiE?`G@7_GGvChn&Y#`^2|r52{Wif+&Rl;b#PJ_gi5>(Mxw9sAgGm}{ zral~`d==?VEFVJnhhM#N(=#UJotreu&pP1wj}^O{Jfb+k*=S@Sq>%2VO0(@t2?KoQ z$-`klY>8JnW+wYIllnH7JAWb&mcsJ{f(MZ@I9NAhgNd> zO)%82Fa(7Rmg)jNx)5PL3hJo+EJdY5e$LaBxP!c`Q*`$42V2y6+61}(QBWx%YTVMh z6NF0f3^Q)p)9M@TbOi}mWOXc23#nEZT}lB2wd#RrnT2e&J05!2&*3-K4UkJQJ{MdD zPxULvCcsmUE#Nbn6x3<3?jAO_FVj1QaYQ2@WWpiG(zF(?Fu!B7kggpE#bYAP35vHb za%W9~5B9j*O!L;s@jwW2s<0zApdEkhiMd5jA{MzJ;VCaImLuX&WYVouhNd&Dz% zO_E#T*|AZo{!K!tzblJOp3yhDiHeF>cKDW<#2414?84M>gZ zSHF%PO2g^4d&Mpl6@sUN?oG|dSO(wFwvHo@y@mhKBn6?mBReBhJ4+PNE-1Ep3wVr> z!0lE-qZd_6#oTHgFO=^un-)eYoEWUc(2e%k+76|k5{s1nIR~YOKuxcAW*7R3evmmM zqc-l7@ovKKg3j9zvPh(Mds&6yj)|T9nqJT{->HxIM6DTNeLIFU!?+HLheRF?prpn1 z$DPOR^UGF?uTS~05OrQ3PzViGs%VFPL%-c#;y=dap6ynII>tg;W@Up-CNl{&qLUCZ znVqnshtcmfC}`^%p_0ySu4WuVYFBXu{!T}(|MfS@jHss)UxX!P(0ZtYkrp`BOvA~` zG(T85?(2LnnovXtk7H})WP)t6vXIObr>haq>9b+x^Vp^&nd396FhP)GGn&wyO;}H5 zd1rQ^R{u}Fk~GCxL{23`d62J}KZ4cAWgn1G3o*gT@Pi=91N3Yb#K-t`+8zlb7zVN_ z59UBx^LsHkVD{y&oQ0vXkPR-0R8F@QigOS(HvM2r@A5D~+{PL_ zbk=XPdC?8WM}c{@Sf`72QD`3KGrh1b)eL64r^^P{l(eZ7Ch_0GIrhemRWBcANdaMq zq2v#?Jx7?{Y?nJRwp+`qZdiB@kINZWuy-}{q9+1M z{UfI9anP;8fm=tO2wCEUPb;OSH%yL!o$*>QGKHU?j4O}p+|xk@6!uV*w??D~{jpni z;9^I8V)4*=;j?mJfiULq$-lJ)sV)dtPx}NmCwvg91$~WAvE86xtd&VtM-cQeMrkO2 zc1&LRLzv=o3*IY)Qy@LZVsmY@>dSb@%CY7vEfoF&;7$u)?RB5Dz8z$_RvYu3~9<;Y->uF&*Y9~ncqhQ>Y%bb$P zqdsVru;Z9Q9Hbz{Rbd|KT-@EiBoR~>Wx{j`lI|BCNrdt`M)b;sFeVQeh;A=E}@ zJ;ToX;k&pzxA-qWl;>wudT|}sC%Ts5Kg7ji4U&LnVmbJddbFShsV+5?%^!q(fr8|Y zy2IZ7bn2)5SPZkiWDoDcj+29wZa%a1MzRWnE^6(|HFeMgA02Q^M*<_`>zx&AC4 zRXe&i)W-Nt7Zacrj3Af9G!GzC{0SE9EM`t96~V)HOJ?8ZHh7((HCuZ^ zipM2Pl&*D;M)^60A_Yy2K_}-n4uUCQlvH=f=btiS=Z^E26AmW%>m$M<2!_^Ll$Gnc zx4@7LBfPaNbKLby!=|0(;KT1(fqT(OIF{?0D~4Vfsw&htwrB>MF~4Ub<%b`$HfSh~ zW3zA0-11$d+g5dV1Fy$QkcXo=vh#sQ%{a@_V1gEMPaFd__l(PVW_U%Cm-rTM-Cs~< z!7*}P#^GhJaP-bZ>tV>8i}%j<2D!Bv#+4O`D^btSv|QWYrR$%9I)eE85Q7(!m>Gu4 zK~O7PA}R?>$S9s8(SF*~rSAQH;1}2mCYgCo&3mWk1=r=lXY4H!lYcJyVbiv)yYGJ& zK_^I!t`MKYyX9zh7MjFuO&}!ynkF&!0~>?7+vbO~(V!(tPV4MfZpGSAVK4_BxY|9K zb-rO+JD2aCq!}B1va6m7ULP<}K8Js_0un8T+D3ZSDDdUwpXsV=Zwcq?9(_Uz?Jcv2 z4{MOY`&|?1FYvrjWYjI8u0-e}Yf=24&8FNVS-K z$+du_S7%)WxBF0i`d)_lZ_3xz<(x(S#m$BrqT)ToE|E9fD6Wc`k?}REqwFf49u4vF zV<=CES<(WhX<>ikF%kcxaP-T2$O%txAR+W|!SPw)IzkE;y;rPY0888Tzwc$K81toDA#I2Z~!H{SMbico!m&6Ny?M`URGL z5r+CRygD-OQ_!AGH0AU|Zcn{ca!|`$WVanEk5pgS40)Pj#)Fj1K?{i_>&wKD)p#+- zap$CGfK5pf3~`l5u679djH?_c_~9wx>KC z0(=a~`>fiBuqA~Zak7+U%1uQWSbkr2#H&qc>|!sjd`PT10z)rpHWB@aI0!0;%RkV+_qUfPb< zyYyldQYy^hSGw#hN;kb>%?|oQowltWmNs)D;7#1DTmLtl^EA*mh(~pM zX2zcTK$_DdlOaK!IgR~A-j__3Fys=AE#<6>3|ZbcDX(OIE54LwnEnZ(u3>(Di`SMK zF3umyj21`uP}{i&Gv+JXGy><62hw)d)ZH1rI`w3S#%SPxySOM3;=)0LVxz)xoV15w&1R- zfbB0;;;?%$b>A-zr3#iVLWUx$?TX<{vEZcW{i}9VU(Z!8{8iOB!gr``z&V|5 z5<&9`_$`lh`c3usyuVxM6D5q+p!OW<`8(F)vVS+pvT4T4k6&TmFoyKX+5>lb$Q&}3 zwZaLjLuh)%0$Asbs6mbWcZVMLi+CK`Qx#%#$!w|T{vqqS1;w~eDDKA`3p-f*%Av_1 z&IIG4gKsi?RH}KIiZoLxPpb%pGl5cIoE3HzBiHB0aZw_hM^sAy|6kMcI0C>^-qZnt zBb<@Ahp*?UA@`DE+B;X+O2b`&AV$AKP#|onUWJG9y=1{-6+$b70Pewc-a769PT7Ev zs7?y+tGepj8R9@!r*Xp#vk!@cwGaxELr@JAq}ToYoxqYvukE;xGA=~a7u8}@aj!*;R0;y9c1U+;Rotk zBk;-rjzM)ACN`tn?rx4nm=ah7nYo; z==1yRv<}d|!hXMs#@r`^fD;dq-_6u<%MN$lB?(g>{}aZhE2!kRx1Ab<;>2eW-9Y99 z4~>Q5u_>FL*I%|mAYGR>(Poh^r0&ZkN?tI`%vy40`_Su;JoOP48qsd!tCZTR>|QA0 z=S>Fqo!HCQz>!d}(${S5ic|$TI%}Jn#UTFq;ES^<6*uZ<;RRT5#_RwSM8tAk&=#G} z%ytib2lM=`Bj;y32u-oQB-m8E#AYTWi@6hmCMqyUUjp%sadI`TGzkWHFPjLoq9a1V!!k4h-m}1WikvXDvNp1%L;V%SP zsZ96cgBWP2(WZBC@sa1VNHsS+5&K9+&omju?t998ix#6$YDk^P9RSe0k*%1=kas?P(jK)m^)~C>db}Y{x6U0lz@!98aNvaUPrb|TZ#uGb3GgpxchTO# zlD>Rj;~%C?br3| z1cH_C44kT~zeac}S25cT)rRjD>v$C{k2mi>)&`;OBx~R57`nuc$O3w4eus>p5*BV@ z5R!9ph2WrU|F+)PlaZ64T?3T`6_9ZHjm8i?Rr*VI|4Oylo9C6i< ze|+*^X*1?7g`ic4Y;B)%YU)L5^~q`GgpzT(->7wgVrq>P##LxkUZ(CRlSeM6|B1%G zV$EfB!zgmr(2e@j?^W@7K1%%;Vs7+RA^257uq{{Y7~7o0u535Dv2nb1Hw1| z0x!8fd;NChO_f2aIxNiKBwY->76+%m{*XCa*1IQYQvO2{#u8M*Mm4Iazu_$@d*%PE z7QF$e#$lm885d}1)`q1?NOUO?&H7|o`9d7z7k0kjcoY1n?ptA2@vOXC|7L{@e8`O< zjVWXfv8+MLN^nf|>ot}Vn8$vj(l~*qKcj`6flh4@L~F^*S2HndGnVCUCVzBa)LX*( zcyr8iczaw+cz^R^eNkko%wBmBVAmC#$n8H)Gy#g2$}@`CS0$NlpGGGYe(Z%=(esL4BB zID-Q!-uxMdt$|wrR|0R0F1~3I*v#8HGGpm8>D61 zkiWyv-TCY3^5g0uW>ksMSopq*KP!CMY(HmhiSVt<*nj;lvRdjyk?=mD-)N2pAj^c= z=c1Kfpm}u)mqm)*-AmlEgYFF?)2Yw&>DU{9ah<_#^bGQo2-9o!Qe1Hgdk*K|m|40P zu~I+Ac_vFFJeo70X)!uGuqS`$$COXdZ>yRWHXc7nO%+!zp%!GdP8R2_nHMh0ou@e- zdcr`Hg5yFtB?+IpXW5`fl}96^b`Nl|Hefnl$N*ZO9ybpCioRdp5*+@zWj(b zJTNbn8fXZnC{*YW>L^g!oo4pjcyZNswMyvs;4mtv;@G`bBF~4-IvNQDk?{x2wLXH+ zqv=K*mav3Ep`S+no`cS-iA?sAU}G?0V<&gv{Yu*4YSJhi-N4f(nx~_trB{HPvAk$= z3o$})H|Gtp=a$NadFK?bj{F5~b7;4-4qFj#+9N)yF+S+h(nPzcb{@88W`uKFe~&sE zzwKXW@ZxF%Hj(bn7+xECCh4Yg^#)cLy_5t*-edfX{7{Jf{;!tbkVuwQazYT0dFTJB zx9vTWfnq-#a68`lmd^^8_hMb^f*9# z&o6%>ia+Ff=+Hpn!m-iO=hLz|$^oPW$BzN_$aI>=6sdxZKY??^JX0BO!)I8 z^c~n)IYSA*D~%ZcRW;b;S#$rNi}9CGG8W>ZU5l>Q-~#$v>sS>6k(5j_+aOJUyCfDY zMW4(qqR9SOPtN?QTce3HaW(*ysIcoE5>)b^(YNXmv7Un5bN+gy%Y#m)wtL`*lag44 zp2k?405x31aS#WNlUWK#sOc848$szsM{nDUDXl|O#U1_p9%Sbm-)GKZY@Q?+ zT*0wzup}R-kUW!6Wn{Tt;?=s90#yu8Fj{^NmYmuX9`QreId^VMpVZ`L|8v@kDXAq9 zsZ8D`MFcf;1!g_J2Zs1!bwhC#uS%q855w|B7VLSc#*s8L|`NJF1A z6}jU`NYuN@zm-!4qU~@7YbM$WB?E&!|32)g=&`6rb!I#z5Nvprq6Jao^%<23kgj|; zOeR~dGHM>Lhd;;_rsfxyk)lA;i)4&m7KUo{`o2KLdom$ZIRbHLBT)dQ;0>N`CR%2l zsK?b55nWd%U-eIT*#2r#NF=amjJ6f4$uEfd`&}Dj1vlQSF08_tl}`P4h)E5M<|-g4 zIdHqXwb+G5)<%rVw=rY6Q0G=CI%`ka%$B^lD2KpbTLTYp_>3LJ#D%L;Ye#}e^XcR) z^RtxD@mfW5$Ey@6eYoXmEmb5X%id;U28>8zG^|hC+%Ct0NfF`vtmYSE3h24(AcKdv zyP)9>M>}*eh$5yYw>AHQV(QL(8`Nhf-@@By(RIJ|e0PYcs224W)XA(#5{{-c*14t} z595a9CoXZ8=^xX9Ml|A!;Sl{Ar&i*VMsr%s;Yu!RDaZ;BORWGdrnn}ol(D}ydudZ&}C&lUcdR3J0g%8FYy$ zwKiTG0^S;*>%D8_(?OI-j4SBC{}@*Wh+KUVJ~%)ORf@+cE#Qi+^|R#h#GX z%29vI>wlTzPD=c11i(Q+RG|N-v4{M>k3Ik0KKj2mNUJM2E(jxcpJ;XI!OCt@VuYw_ z4AiWf&XuYNmb-}J`I^?PUPgRMqtkxzHOa^aM^r-jHF$b%XFXe3_`kgM3d9Yrv{Rgj z%UtuHimEi$TZk;nxmdM1SIAKq{Kq@W4g1eKntsC^n}CjG(a_ph&5wAmb()>sHRu-S zTakk9>IeKi)ISk$^b9o**bh;3VGD#Ph=JbuE*`**+W-ff$AucWzYwceE0`_>Ru+o4A?5^5{tsZXeJlhSx#o zUT*eJamHM1V{^IKgc}@W{xj;#BB@chZqLZ@g&4y)k7_hG3{$KM*A=GkDZeJvnH$aSR|BXj1Aw&?{7;~qE`2Bt=c2+!`IS7YMM~Ao0-v<|@=}gLGZXh9 z+2)k>LuyO0Y~dQ8sC(Ee{r)V_7ZEQG$giYBdaR}+i?+EoIG6C;*)P`gI{P|jH!{8s z4vzQ|UJpo5eyV-ID&E=-ffZBadYz#GXz~w~qbOnjR!SChGZHsB(@RWys;WhL1`R!y zVJS)5saOpO-0?QGAgX@HuWeNZd)@x$C&g^kTb%n?Il47U@}WuDj9O=Sl3M=yqTdl* zc~|qpq@C4im1~HlV_rC}WdKJK92Qb+LcNAVEMHGKGbK4A))cn!PXE8U*?z-5<+}WPp@M(g|Jfb>AMg18<_`aV`?YOMJWPhQ zOdS99YkyuFIZs9|5#sY$v)D->8!M(Y6Qc4p@2`nnh%_`7a!uxxPWL@b}3>of4DJu*;3d7Gx z+nQN@9EO0m9BitY_;ae1o3--tO)E-cY69s6u1Ws{*Es6pZ%2xl-F80sG2*Z zC&`rdkZTN1=;M8`2FqkS6E|Fi4Z<^DADA?igmb1!x>G;)yj4+#P4oBTTuY};@51Lq zv=0&>#q%qHXD?ouTbee0A??7u*gM+Er4H2PkWAvJ6Z1rIN2>NvsO@lJPUsjjG0(y=OLwE{I!KX+Y&chnd}y2N zD{bPjbK%+9SPh0v^pBzp#Yu_~1a@=0hIHxo-L9yqqCd~ej`AOD-zSl>U5Lq{ha2_e zvee|@Sj zc_2U#eMU2=yFF(3ec-RQMOU>3rk;tpRsj=>&JI(XWO3rmTV1J6+*(ci;lK^zaXV7f zsVJd_%Ex0TFpNoSxdLI>`P9LTy;F_AkNtmt26u@WFZ5qgqfL{7-Lj72jpa@Z#W2DM?CG=(K z0ZY#|9QdKk>jm*ahv1<<2=A|Pk3%^WpW)6dQ*qV%x)f&_*~?|!?NQj^^?np$1nH$1 z^7X$A%P;4QiGFv-h`6xMn#-g@SYUFaH*=d~8q9}TTOz^vc5%^rtNr|c_6!q z6(-@4ud8pz?cfcTo-XL<7~>`{VZ95Iugf7tBLbdj+8%i$Py28N18rTlw+or? z3a0tA+8qmTGDGg|3i+-)VJFbzVq+h!5P_DPNtrGP=x++SN_I55;~8l~Tp*Q^O-gmW zu`qSQX+bqgZO83enBD};IO{b%RQ4|GNfD#th21snXFE_}O;)ny4NCN-T}$BHpCEP8 ze5GAvP1zx_-GrZl)z1|$2v3s@+StcA&U7}UU}QK_9wewdZ27wSu@=3Mu+@7^F6|o$f0}GxTZHC|m=EpfHJd#YBWkTkKE0-bC zOO#t2IaH9fb`<0d98_hoty|EjigYZ;v_Cb^7CB$pS(voU7s=_jc(bBTMvHdguamNrAy{IYswuV#-biKLUIJOwM@kB9#9=NpYS(u;RqmkalseuPY zvZ0qXsYv9+%eQqsy;9)4GJ@YAfNt4xOP1=F>CqBhVV*?Bx>=88U~Rh(xQ9)2LVUvM zG66m+u+suw3}>LoHc_b8JZ#H!v*3zB5!bE|)R=UUnyGH(SZ`~d4Z|!N+v`R}&$58fuKS1_#zlDyf_^42d>U~rWFJMA@QT5k za(kQPUPWvT)%)q=;qRh>rQ9o)rp?F&{%)y+EP29sRlOkiFC&l=lX3OyUya=X{XeNH z=KsH{8d)1SIe9BLwJg6>n)#w`q)Pf?$umUs$$4 z9)rk#T8gOq?I+*;cF4)C>0rF*>YH?sgUX~7n0-~j5@NPc<`3_A4 zt$@+n1XOHNXku(KIoe83LSdvjoE1RGjRsb$<{xE_1iwllnhFB`fmX`-S0AH&=R5ZT#X?rhH}(iXM{tRGLt_FbwXH$WS+bjL?HFCgWM2^AvHP^ zWgo@`0I-U30oXBkQSa9;PDyK$YA=HaoIcveuD$Uxz_&SCOLHKf?NhV7#wpng1qh@d zpPOIq)aPVtc7c=$_7CK)Lnkgc4w4+`W!au`>C&$ksqmX-S|J2K;FH<5Yp*5qcrD$N z&)X|-I9sKWGwNy|AiKn3Ow|v`SoveIuSwm1Q}U4WpVoFB4czYmLbkO#<2tgE|rYC3Vh%T?M66idGM=$0{HZ16@@6@Vay7?S1`kt zd&F~pZ~~;eWx-PNam+uXV%A`vGUHPBbCF;^1Pa)M)A!(6zrtQrXYpfIXVGse{&H=f z*AU(dT>`SmxT44t5TTOAC6_HgEur>^>fD3qdC86Cp{|tr-@%mJLM(VC5E#M+q&M?B zSrACtwcq85#1(5-B{Y#Yq2xb4u1w)$9?-^UIFEm=KR1&%*28sX4Bny?o$T$>5U^`4*b4x3MqxsS|?8< zmcf)`_TFD{(3N-y37Z;j$xNh=xiT&+sVyu%p9ZE6$pr6+IYbmgm|ezQagluiZNd1j zFt1?HZ=82qvNKs5f5}y;VHuu6Hi?)8;Gs4~2u5HoG;fMc!;f+|Oc`5A0puPR^Ewra~Xqo}iSoPe^W=?O+ewAOpdutujZ;A^$jk zZ%jR&e^x@85rZs%LGU#u;x!6;rHJV~*3$cS<>*e!?cDt4fnFAF!~wE^Tt@I-nQu-` zmV=IBBk=d+jM=~A#4>w;maVp6l*7huL`A3=*NfOB{6%)j<>4;p1FWO=R}JQr&z+2< z;joZ-#8p2TP@i)%f+}zo6GZ@adrpUo4~YPT>8P0HPryOq$8d$nSH}otH=N1DxWtBV zm#5GWo_TT*Sf?IT=%GF@zM=hg9o#I-Ea*R05N)@od&m}W(l2y#L4hwYEsFL^yQ!=p zGsZ>^Q)TNd>JCN}{5ELrbIZF3+^UO{WxfKQ&=WfFk;MoY%Ea#NApc=%<^2;_wO9Z% zq~jYfbaUaxi;rXp1Mv(0k1UT{?4RngUP|uJzZF3=}PsL1J* zGwp#$PUUUP-36NKW2}@z#q6xgZBUZQuYCa$EDD6ucenu?t^yOUoBKcWUb=_yYd5aU zx)hMkeHK6_W3DVVPo3JlY ztOQYy(h8_VwK>XiR0)0q?i2_ZIqGU?!#Yp+^e9`Vl-!UXig<{Md<$z8A!Wkk<=r*} z6A$wb8y?12&CMv+Bad5z3s%%g9o?G|T)PU6lv~JXPqNgg%Um?-E^(C z-JJW-{Y?VAllU4Qfu9Hd3r@ZNt~M7Lz7c->8zGc%|4GUJRl)xEe*C{i$p47B>V~|M zEAG#y2j3!$x~SMJo^qkS721uM5cnW9DjLMT$@T2FIb!3oMe) z+SgZ)*u>W-8g%Z{zzn0XeH!gvO=;_WK+^fsZr^)#M_LZ6|zpMy%>y@ zPR6d<&epRbItJb6eKZE$Cj%UW_gWo4bX;Uq|0>Q)5tJCH=Te{%urL^nmRWp^MQC6T zuv4_%LMZ!kh?%`c#Hmx1dY?V^-4U=5@K(xRGGOtqQ9}COFd&T2(fv*W?2yk{@KNYz zGGI91V{kW6fWS7g!uD_)fh*HsuNRz;yDrF!Yr!N{d{llBaY1pOWtpuO*YqszrB(%>jjX&=ftI<^%xp-2 z)dBxNXI%zYGEy0FEFYbWj9ZlQU2D6l@)Tu}Mm#FVQx+E1eAA-jbfc9$cnwq|l{#6} z?@CyS6685^=8T-ba=ardvTUBCRr2zVAy3}zDI921xEQd8<`*jqRriIqW7sDEjXBj6 z<|-U!GBxMu=L4M9^j-Eu3e#Eoid-}2kao0xDiM}v5~rG-moRaSh9Pufe`w8~xn*@y zST3f>#jC&#C9B#LWQ)mL#~n~?T(-KL##?drP@k^)K;%Boe3aEETTOP9mZhAi*)dj2 zMt;JTYNzmrOh9^0496m8jOi9lMKk??`Asuql=4D60jH3)EPymvE$naLQh(7SYa&;= zSu05VeH zc@!yAZ6;*{iA0+SrDkeCfgyN7y9wzlmWt?Otio^tQPC!2-%(Zu(ieSArI3tUQyi@p zbzxf+amI`?r+boi420A_blu=uS->4KN{iZW5mX)GrDmB8bh3>7-%G3`G9TXl$qVPC$T)F z;yjq}H0y%DcZO#Cu{Vbw^jqOL58`&o3^pHtv<{?K`P;&lOKC2(`^$RlFz&nD9$9j*a{U|!anoB#zu4w;S`SY1~Bi4E*ErsseCE$zC zK)R6iBJ(LAZxL=vzq0Q^_ZtP3oW7;zfZb(6Q%G~laxNdz>(AU`tWsDaNJg%yPzVu*Z6G#GsA>*`*Sn`x&DpERK%$D^}nL^d>>ng5gYew=ntSXgL;+x-CRTR zQTyBZ585nM7Owo5f6W9x&S;6oqSCTgef#@+{dd_DF~hmKS)zr|^6KG}BbZ%Tq`gVX zc71cRB96UEkPTHPm?$)OV=9`QL67Tw>%n#+zEB8)Cwgs#byrR$4-TJBzIQY3bxbIm z2RDJw=gv&-hE{Rc9#cCsdvCP(q9KYmn6-G2ZCz+LUdY*jep0n#R$=a3FnTR$Y%Pj_ zA%>4Ijt|i%&X~`L?WaG&XSm#FAm3yxf@&xdEMb4MK0#t#Zt(0-%pTB1pO8zAeU>qX zL)i5xW#$l5@g5k<+`x5yYFA|9rFTC8^3N~1Iq&BNd8Xd$1KJPdlhQncXDEkfyuBNy zexF{lXBzP}qkdn5sIO?~5f5#bMWf6DEHylv*5gwW-YD+#jBfjg8+7O4OD$JddSP9x z@DcQClC*lgfQ$d(zQ~B<=SG180qsHmr=k9Dcl-Yi_5b#SSKd}m8>%V0u3r&Ws#9UO ziH(Pgw(^CP7DEoWvRw6QDI1C7#hZlCQs#+3kU(S=o}W7}yf=IpKve5CW*%m#$>pP@ z_5>$i?I-;2sWZ=Oo{tRI8k!gt9-pa)Fpy2fpVL!WUb^Om2_IXPp*+Qn)I*LEi@|*Sx2?vhRX5@>-uV?)Ft{cv)G7vx=0Q7o&kql z9(8lg_IGY)XH9yVAdqiRqC4KqB!D|4(!>I@Mr-QD7;$F*$^vVRVrnn)ZRR<5HWw~D zyU>koz)+nu%WKyk4hxOGo-Lzn*-%<3+>+eI*5G+y{M|a>%8zNf1ny3*ZJ^NFt@H^; z?Dux{zU68I$<<4nX7yIU`LYVP9zzI1&Y;0|md^L%{!V;QN`)8%$w2csY0ZuP)9dq${J&T?SWT9X@AK#7J;rL#$KB=q=UIi?^#}&W z^@D>$Q0A2kMx zYxZWL-@;kI(sK7kmn_}Zbc?2RyzfQ?zf}MxAo`erRVDK z+UAiM-=7u}2v$`LQK*eDzgL6P?~#>jW^lLS>m%ayV%tOADu!?znS!n^6=v^L?V@XHz1Z}Z*q3LZrO`4<(%}nkt*Bg# z1><=97T;D$8p0)ucRuUd2me&E-})LmHlDR|J1Ymd3*`a9pXx~yU@N!oEzkv5TokmR zKV+GtJ4hC0@yckwwy1$aK+A#l8o=)M&^2=AIRWlXy2bAB#vn5wL%`R;`qYK6`z?Tg z9@I&GdE&0)bPCgJU^xAzhYEHtkDf6#1Pb8^bVnrkUv2c8o+F_eqQ=2f-^?g zP+`Bwv(YSuM8~XSJ+muo^c7NTVKfey>0TT}k*J*t1-vX3&{*N1(s@|GW51@(wLrPVn5-GmF%)A90OI=uL$F@RU(OAB^3ZmweX{EW`a$Ji7qBGsX(8Ak4$MM*}hkY z)>-l~Fx1gVtAJKpI;)^chRi(a6H{+g*9r?(I4ZgM?fOxP@(W+13;h91g)pXXaNq+R zd93DlxSzOe1(mt->j+ z48Az?*>=!v9hr@gMI&58NE(P_V#_R|U7s)O?;ww~_Q`piNYB0wli%7(d*x3scPh}l zO55b!;1f~$z4ghzg*?H;;sI0{N;~pOygdBxSbY!N^e{5Ulh1$m` z;@g|h4(fO!M(09S5bv7Nt=IQcP7lB+B$FKmGGI*|qaBdwrS{A=yHP3G^8on6W9gp3 zt=%H5={FJ_zNRUBJ$NXXRfwe_v~c_jKQ%Q{xj~A9U4Wpc#~m%#oM7J6}JSeB^gV*M{79`rr!g zcmT-fOn+PZX<#ykX(PYt0wy~PdtetBR#dux9aSkpyrC+~jni#|RoGW#Z>Pho+V3(v zdp(d;ufl;nXoWe1kb-Fx@JWUd&`)?Y_a+EksuBV*>l^nxZJRF+NYuAWVLV`h zOH8&8T9a^O0DFyIKQ-%cM3a96pt#%qIpq**sH5Now}~((6;GRMsR($a2jughl>(Yn zTp-TF311V{X`r#+b|uPx`#&);t`hP`=)ObLPWgtnc^%3571rUzgI3JRRRdn|uqYkzc<%cUL|>cozLoW9J;>X|IK0=?&Wp1y}o z_o05D|HGWRn)0LAQ*SqQCs%PC=l)@^TGvGvloZN0wX%wdRrh#US}s|kgV-GK+_`*l zsPe1z>QaZ>bZvYjSsCzshR6lC-f7w!s2O+=LMMfNwRI_ZE^jfp$pr082V1;DNSdp4 z03XjTyc7C9{_%YneMsOK8ESvI=VR(Lv1WSK=;{je$F7~2wRTjfJhBlbDX8N zlrljk{msJxG=kHfAGupsbTNi0QO71j9x@C?+Rsr~Rm&rX&R7k?sxp0#6+BP_*;97{ zru3V(piOG2%G$xA)W5Mhj5W(dC)~ilx>ob3@u3l&1g_8n#^-4gbWrOgryfIt4B^#A zacG*K>pi~0uW$LH@{mkSu-L#)x{@YP^?m_N2e}3;YF*}4!w9PO&c6UF@-K3M5S=9I zoH+;vY>4x?pT*m1rgdi3z)DLn7>fSmI9yMk_f2CE_v95VxjkFY65yV8u+H;{w(u3K zPAM!o@IpL8%orob4PN$xZy-nvaCZk%`$})4-ul;AEfUN~%mlg;p#NbfkxDkCa)?5{%lH~%mzhDAAlB6bWR!B8`|Z6 z5)l;qN&6_?+yognPA5 zzamgfBY~;Xfk20GRlgCLu{~(KLU}VTtB?+Mk=Q7#c8eg`yUQE?Q2sszY{2FhR=Z$C zXQA#bsR5u@!&gWi&W+ayGak|*T4by0w^IR@zyjXmAOj?bvrR7Os;lLGXRYSFaOcjP zD6r6UMfOK_l+vx%r%S}yA6XFNglSlWsjwYjyI*#Djr%lE{_%QoO1yPk+QLMcnw{`7 ze{&j;ut! zjX06oWdNwk=1o9&90}bPxhon?d5U^m9Eyp4gK{)*ilC(`YSi}ImZ;_re0=Hd4wW#A%^aj|KPoH1$I-C*9)FBo^dohaasZaV5D7=DhP?AX7s`p;{pq} z7H#wjH*px+-%?KO_ifU>W(&4Tlj4rL%A`tE0}{NEX!I=oZ-?<$1ce}xzr;xs%tWKg z;4DN|z4HFEY+e-MMC#h|epStiGiEP>sVlDIViqw>YB6@eokEGAnQm<@RPP{AsD-nf zq|l(^fNVqd>2XnHOG8HYaLgiROufW?a~S(@;Rg`e*@r^}jH2(}6B23BJafVHJ8GN`PEBn?8wilCnBLeB+7H zted+0@@xOP3bT(dVk=;x^SGtA->VgIctd3yfmKYQDy5C40|dYYIX*%gq#0v;x)srG z))7_)oZd&@V&rY`M9K(2WI2)D(~=ouXJ*4~!cfZPN+gt|w^4LD56wG%df?+_5Vx(pSDY zFOwKG&`)U;{k|C$)!H#;sxB|+?!t;8@xTna;L(?W8cc4AqVU9e!94lC&*%#mEo$9! zfzK7ca%LEuN8A)wLV9X^FiBcVMNOnYsVO+uZ2>lYhGU%KP)#IE zBjYsp$mmWRFNqTWY%UyM34DI2&u7*1qdrhb?9^FBZ5`xKL=X?v{$~|Q%aRyUaf9mT zPf#uOG8-Jr#5l?m!Q%RJkNvZekra$K(NAS;!7%Ln;4q_c;VR~>leL_@#UWAM>HUn| z1W0@V#;!EzKdMxMvbH8iq_sMzarA?OC^=qpZ$FTdN`<|`-`8m8!Q|+kVya^D#>@fi z{9F0!x69h=|ioR%G4#P5; zdAUD?0TdoB$v6*cyy38C*Y(=a(>Csl_76RH_m(foF~Lr82`J+Nz~#g9T=B(UY=4nIwb56XtrDehILM zVP$5SKZ;;HJW_gTOTD*F9U1rt$1VNPqj3tj3OwZL zBH1@0NpWV^ScB&2;L0IjYn7!{Pt#Hh(}LEbMM-4U-#a5T7yfR{W?2;y*C;ku2_giN z$qg~W5yPU{9o_^HiweShlFvSs#&ntq;#LZR7-V6S+ijmi(lJAw2^-HZ!Eg+l>-D8gq-{{viEG1o%UIOY zYKbmKL$uZBm7;z(6hTv0g8MB=ydJ3t;`YZWHF#sXG!Tu!-q4TKK|d(@ z*}nULy9I(vZi~eEKUT%?h*c7H(!yWv+ET$EJp5GFGIDr5WHUc5? zYk@&`1)DvX5%x2?){&co=au!-P(+G!hT-7_t>c?`xmdn$7k5vzq$};U#dviCh*&*i zA8>rGaFt>Q&F}?2(hfXcGw^>AC#};rU4g`xrRvZzW-HUuHa7Y_E3M3yUTpn)@4$l9 zFr~T)oj~jU1Cc~k(8(uBA5iY9_%nunNHP6xnyTTeN#92!R_7IP?Un$*D524#g{cO=1kPkc3Q0b0V+pR*}$QwU(H5W4Qa(Xi z;5YzKpjD);2dzmRKt&cHHBxliV~yinRbkO*h#@hla?Ad z5+`}=2joM&KbyB_A?tHGEixwEe(a});G7MB(V}0v&#=;;+;&q1lQ_gWa;Nl z1AL|_Y~y9UaFRsNnS?3HA5C^kN`~M@ky*nAqb|O zW;>gF?vICiBKWab&=Y=r6#+j5`-u&0^F(KzsNBRZGVw+1O@V4+G;LuRfop(WW;U7Af3Y;llPXP#%GRNCXqB`R3+lVW z^5l2EMrdVED$*v$S*n8h2ifRnL~SVBpQVVljr}YdjVKckhKB8=)o z&D}HLf9}GKs-gv zq`#gbd+;WE3VAiZw9M=nnjkLU2Ni!NCYzw4RY%?BHQFiKr=Izi>g*P^pguL$3bWK^6C0{RrMZj&!eX|L#j{-_h0l4Lo%;HdUq0MF9%7DZk)cX3gN~h=J>(F3Rq`2qx z#_P(cmDUwAs?v`|VpdaJ-*P_EC?Y3r9=sW&K4F)8_y4b7Lv zGui}{G!<^ea4q$#X7bG%vY;)4nK;Z0%M4RIhckv&K7=JT5+xk=D*=EE8!EY<))O99 zk5@}kKH2o0Dg{myxwSh4Rw~h$D8YLfe9+$B5!=xv#-!4V)}$wKsBUgc56g%SDjU`` zt=eKp#oqnd?|ui#?tUH(K^CD}7M)InTUN~jubXD85n(S!iz2s^s`)LxCIHltp~1}3 z1mDb-SOOZ$tQWZJy%AT#tD`&=2W}MiP+4U-Hl5WFY~M~%yBbf6gyS8O4&;*HPa>2r z!Crf>G-7(3YNXWZ$WYw1f-Qm(8S9v+YTA2W@hEZuKb5CqRgW>J^z7|{3M<6bhS8Sv znVuVZU1OmUR$gx6AM$R%Y5(PPTh*KRR-v#1uN|7BJ7A7Et+Ia2(@dtc5}&gx5X21kz$pr*mVUCogUenvP0252?Lr_(Dgss$&k9K>=^n^A~V&?`yNL4gB zt|&pCT;i93;%aYg-Ad7DUnUgSExmaU_3*Z`MvC%fAl>aHX`1b6)R%K>D_7$W;Uc0B zjcHL^;YO0If_Kma!(GVO;DGwdQ+-u}>q&P4a$qDC&D|hJkIcFEO2JvIb5L{-kJlEH zM$W&73-I*SuUbaQkTr zvop#iLSHiQyAjpWFq3Dmgt=;YVi8gL=77j=4IWCj?B+IMAfg!IPb6)>ghT4cRY)`| zyZ~8*l5Cp`SgDN!n8t|FADLMNnj@MGOakVAUtgI1oZeLqw`6(8qk%Igr{6+TC4PQQ zl$q-^3#i8!K)6vt9?{hy29Yv-OqVEo1bz?<;NlF7kbQ)vgIl}XsM=WbC)B3y zO0WtZiAmqp;fn*$apt$b-Ma38E(c5QedqkhwA94#=fcIGcR3`mjtj(5|3#yH_Xtf$ ztzqocU>;+?R-J*$wrEaUVPM`n)$)9t*|XpH%i#Nx4>di`{+T&Gu<{M7T$}97fH)n< z;n0qeuU`fcg8_T>zt3zx9Kj{#8~DUqTIJFU>Xidd(n&jnHfb7rcXmTcC@Pg{x5zn; zX(4dR91?%kU39$Rw<5M!Lwx4RCbjhC!6X;TP-QdgRkE<0wxMb$Eel>y+o-og#qfr=3MiOB78;`?9834hH0WY6avNbZY^=r{CDTaiW68DO-CN+O=6(mM z&E}U5$=u|HB~rz-Q3NZ)d*BaW@>yHF_2Hwl?v;MsA_$!4&MDGUHLic$7IDjKjh_{! zlXr32z_QA!Zh9pt8CyaOt~JMv5(>YM5NBJ@5ogZ`b6G#y6cXSGFx$$tth#G+&^V-* zTbz0`aTlQzKAy2-xE2_F4F7yRW^u@Rgt9Cz4iHd=2lT18)emNpDrX(vgyd~|?r8OX z2!(oBH4b2!4AER!7}9uFZ7EE1MspudHnc|UX4FGY`d~z(^sJ5hjg;BFQha)58_n)4 z>NQL$A3lhoXOv4k?Jh=N|1`YcjGW)`N)iWsS#L}BNCWe?Vf_Lj$xr#NfNy84$35GB ziDMjS-WU~zWM6(MkZgnFl%k-x))y3k2+C>Ax$qYXQ4!c*mP!W#H>T$P6b=tG`FS6K2A&v$lk3rsoqKc2Kw%FEK-CC{K)$;qnB=Cw}(U z+5`<%#GaWDa*xLdV#0;9PBlYW9x(y^+dLW0 zrgX6BVQsAW_^IYx*`cl2Y>%G%VR z9y{(|)K1!5@%N^$S7krIMv#iEyr!)Aw zAN&1qSm^@2(Kc$b2ekwXo9LU;OSTtRSJG$C_-haUQpJk*JoeKN(H?NsQ`Gh>`E3|rUtp{T`jYZBwK}>xo^0sg#Oy!(d>gh@_(2e8 z-|Ze7EOKXGuvIJTw#%fVSQv+jnRuB;%QekHW9H}bsGOVa*dX_~<#SilhKn(#X513! zA$dAjCA1!SMIkePR>U>MUcWw|6PP&{!I%tu+3e{qpqmbw@AH2zm5-Lu6&s|>nI&BTB)|*)W?RtBZ4r;K!!CBv3!lk4t$df(Y&D}wd5w7sc-!fS zz;P&EJ)jZXE?<&j{?+uW_JoZsqX6>Mt}4n?bRtER>7GajQTg^lwqK2ov*xY0{PTcJ zWrc)CyK`2F3@%IayC|7;zmL>k^X|A28XI%f1K-9@3+bASkBh0;jw?LdzKiteqr*xK zU|9$1*FQIf#q+?a67KiPZ4u+M=|)+Q zou;9i$CieLJh()x>utii4MYfLUUHb2r5qwj!C~OxtIy)~js7)3L_1XYt}^>LYtn`b%+CaiL;0?79rN2rF3C zUU+uRYI>i<0B^MiPT#Je8|5Kok{|!7o+))w6ob^(Yv}VEP|6n64XW$xpIy3i z`gE}bH$_|-OlOu{qf~(|w$*tD-1J1}{H^Ct5ppAOWXu>lh;yrKiKK&?8+|d*-C8sL zhVD2xN(z?~%JqSh+wR@3v#sf>(y7fck%2u7kHuE1EtPtJMAKzopVOym1HM zvq%kSnTqXAtWr8-)|{M&3{2^vT;ItK$SBwb$bkDqyr862zTQD#t8AxkRSWYFl`O=f{9E(LS7CN-sG%#S%H z=&Ui6o?!aaon(l`k)w&$h*^+Ys+e~Fb~D%@PM~4tdQ#h0++#|2VL1NUh9>Be;5#K) z@R7*2vm{hQkH}}NI?W+$8I9D(nAcJA3W`!|m8o?=)--qF8e)_@}2079%^~8m1XZw1dYpZ&?CUFhdNpJxc)}*%<$SKCA zv4C0kd#`*;ObC~y@-QgFBWfRV7^NL!whnNbsPpM)4d27qW{#0%AlFjP7}!~e1RDIH_Z}A_Tjg@O77!4 z-oZY3lF|pEr>a(>p6jsxeZ22cs;5EnbKulK# z?m#*MmA-^odzMu7<3B}8%?yT}Vd<7DZ<${xEoj?u(JdI;AeKCu34v?y;k-39@v;gF z4Gm1;wW!x%O%e*yx0=Y**|3;@EH=9XrDewsRsx?wy!-vviE$Rex4{y)qXcf9#U=w@ z;Ke@qZ<3`wKPzxa?#zJx_>h^EgS1OOrm-^ z>SLc-)E1dW{l=p{~8I_f#jYO-3aZKtZ!9s&1O%{O-(CD;1^ge z=Jh*8-6YypeJ2S;v_0O{@xJr-$$WInEn9myuHrdj(}FxKQ9YLMSRAbkbsRx6WLFK^ z>nqX>{ZBbM9HOuOniMTl$tc{8NS(Vj#dVh3UFG9wES&jvE+l({vy5(GDdWg&6*%q3yBT3I z_J)7GL*MHQj8fVxyyyN{e4;{UKYCyjDC~hD15+Ne9}Q2TQY8%xDuXx6$VUC6F6GzA zHo9OAJC*^i=)9$KdmYg33zP?=qe+_#Htf9b;-#9e|Ju6oz#z8h+2M*Wzf`H+OxSvF zT@7n5ngymty-*P@IFIo7BAMJnU=D`?eF(m6-}ZVT$2*3qQn**G^2xA{yHHM;>UB-1 zE~ZE_@XBPkreuaHU^>WiPLCod`DNP_>j&@2>UR!hSLk1&Vf3F#Y)rbY=YkPf>0q-1 zKZmnp2w+SmL27fwZZ31pm%8eSChWMq+Bbk(eQyp!4FknRIN02vs}L&WvR%gl*MibG z=@dH@v{>SX5P6xf={1vkvlrfy@CH${#MQAb8#WGBb>!14@T<(@Lf?&wd6LScLpcNc zaMJLh_8`h>LR}9_p$64rLa)B&XB{`!YEDYri4u(2GqqvPipryDu3_=biYbdfDqI6T z8pNVULN;Y<5OmZIZH&#z7X}?+kU>&73%Al89sVqJ4K-Ci7jC3lLtUsjq99uK|LSDC z5AOGhZqN;A1%REEuN#U>xp{L@p|wuc)5y&JCAEtcx3h4hmO=+TR-xM-twSap?O&1o9?1)gPf5YtthrFhX_4w+z#D5k5uA*D?@# z;I@G2fuhe8sTz)q-nqRy8S#OQr||xXtbehWi++D^>S`L6yR(@3`+*V$Z!vGHMMue< znTb;E;cWVN-1Piw@c5`}F3UDTVuSadUV)19 zh@`$4XRPg|@y_s0SOx|!gf4b8MaJzZG`%~kUE|yF$iLbeeE{nFnaG$;@|h2)Of`qz z!_#Ai_C)}#pvl$%eCXDF*S=|w*ECnuuPt*a2*xtgzwFqNhY`Hza z68m+@LgX?_2kMjxgfB;_dg*{zadI33h@-6hgHEucO(Gh=?0eGY+6 zZuCzyx9n9D9e;pK9xq_ceUa*K<#w#cb*(3LtQ&Ohz>0Mc|Fb_!61yjXO)2ch!0pVe zVg4BDolvibtYl zGU+S~;~&9lV5eC+p-Qg6PMg=PaMtK{|HOO=4bU}Tja2Vt z>%iB?qM#g*3;l4I09V1ebtXf%Cp|y5Mh9izzu-8&&=EBbL9F`xK7;mQ2(tO?I8tZW9L@aJ(&92sdu~Zu^(maUB~3hVa1pxh>U_57 z=y84Mxbnu}n`8T;NNV?KcMs2^A&RpKZ7bSG<)E&m#Ho~kZrv*W zAbSy^ECh8g8Vi~$)o_u6AywnLQ<0dOiD5S0h%!x+OLyGqs6X$K zA3i-{8OQ$e3YeUmNfnr_uehC)mxeks%VeCN8G#}Dvi>FazNTJmw_^ugQPq*q*ut77 z!X?%f@fNbxNG5)mOh9>Y#DG~^XoLS&t2Jvx8sC+$ZMtg0@W6uBDO#umi9yopvtq%0=2$ATJsX{CscNte{M!oVf ztRC6i*!U3muSV)}e||SRBM^{f*MDjr{J(H3|M$hP|E`jCsl)zBT59^9=K9D@j{+SO z^ozSihf4%Iqyz3J2?{+j0r@|Doda|w>$dOHaXMDVwr$(CZ6_Vuwr$(Cla6iM9s6b9 z``$ad_dWM~YphzM))@6$vu1rYN7br1=N}(8NQ^$fcT_S(k~ZXrqN(pJK562Bh@Wmg z6FEM_Hc*3k19_M{mEz5YcD7aR!h?3jy649;NoqiD+htohg~dhZ6!)v=!^&rB$KA%B zNKQ+(vATDDJIw_dfc@GxMxqZ%%?-7=%(>4g0_RsBRn6FFLahQ(X9y%~>$8ARr z?fShItnT_fDUQv$y+@~vhP?zV@0vX;j!nBf4r7iPXorkrzgCFjPzuD9G3Pw6?~F`S zp-8`$+Eq>t06RO_zm=+%xP~*@*a61bMIxH1?h4Xqo*7aEN*_`=)@6v?hBb)RE>g|( zlrJQil_@HWGhLh2@cSpKFISh+Lj$4%Gm$RoCW7KYlQV*I%pfPGsaG0wNn=#b>PCVt zovkmLyR)>&My4~h2>GvRxxl$=xd1=5*1+;O?#OYT_jy^`?$A{@>;Rx^nhyLxNvl~4 z^nWUZ43<6X(>u4@v9omEfmlE7i*FeXBv8HTv#FT~G^t$(WNBE3u$CI-m>b|=qLH8(g%{DuA=)3{(y6j6%dqlCc-?4cL zg!CSkVO&1%TVju}k)>!cz-+jlh1=Hc9dMw-|36wlI!L7gdH*GO0X{G`;Gu7m~cVi3bY)Cl1 z6w+|iCY%}|eYmex2F*6)5v>WQ#Q~^92OyTczi(I#30&9MUiE$>Lv`7*2KED;%66Vv zQG4$yrG?#BKskYQF;PiEb``kg6!KCQjbgnA3I1git@m(tpJ2BUzl$aO2&ofT;DS;$ z+6(b|kx3hY5crca5Lla#;Hq~zb5%Jd1$M&r7`#0P9TZilItqICx3f2KD zq@;m@Cp#Ehx6j<%Be``V2>UQuB`?2TYI9znLYe0p=fS}yFT~OUBDvPY+3rEsbOCKU zwe}^Iwpr?K-FH^is&&LN-C^ddGV$o_g+vylSq`O&AEXCZOY}M=_?oETpsWZ@ZNIL_ zjGm2pWmAKfATad{;4;3?DRhi;CVto&=4L+sq;%5$^^o97@cTU5z#>-)BAnS%bKq`W zwq_mUdv)l!AiYs@tRFa`a7b@f9llN}jp}Hu4yI9{IK_B}ukGM8YlohP`j4rjM|TOX z4^&~@#^I|yRd%%qIFvz@Ss^LQVLgUSDrF&^dwMGfdDl*IxOik>HYo+HU zq#s}c9cYU)jo3Gg6txAn(OS4fL2TpfC#EwO<6Ovr#%vdwqBaW|M(5O%QX%#elcNV& z*GR#g9`m6W#VQ!e4cA(GI>}9S7UX@}4|5ZSft^ZJz0!?oM78e|yWuXm?t@m_j{}TZ z6Iaj)LCFq0Ppy=Jy*}CQ{m8lb27Q;&%$g)pbloxSDUl~yc{K_Mps^QksVnU`Akv??wsfc&RuZnV8V?TwaP$@lagO(#U;C z2zdTdzj^UZVYDeZTwhX{haT6qo%)cPg(~{T0dHR5rD+0dn%Tq zyqB2Jyak#Z$X0vRNpmXn+NHZ3sZ}xn>*sJ~f@4ks$heI7MVSkeca{H=8p%FkfHkrs z8Y3W;xki~Op*F1R5xXds>mu%IJ2qN0djU@bzdO6^(!Jsd*SRqt5_&lR5t&jkDhVyz zu0$}qA(jcdQzw8r8}gwh)zSglL}<{&kqLhY7Edy{xfT!Ryt02O>GBwM3*&@Af&U)3fpFXx3N&9^XR`eQ^= zZcW~PbGpKq@wz`RseLdVVh+Br=1MLvQ76Enejsz~1=81co^;oo(4^i(UZ7SpQjj-t$lQ_5TG^l`aH;t#y~!BEeZi2i zR1;HfRI_I|uTk0i==aJqFmp)i3wo3Sn>kXe5y6nXDVtanaRhoc@tF(MbsUFB2fERG zeu~P<=uizY)PhIiVgL@4o-h+^ybFeZ+04XhikWnBZGnWF*$^bB^6K@Op6n#30O48v z6nEXLrsI_PFNq@bUwT6W=9b>Rp5rc}CVf1Vf zi`wD^3GDBH(<{PxLR*gG5l9#f8N;nABN!=~)iK9eec%@k@eXW{RRoEZQJPze3J;gl zWNGLn+a>S9fg9=2!4jb)>e2&I*NLT>Ik<@hO(a9^Q;frrJpng<45iEw+~K}3>KXU) z=VzvdPfjHe8Qayd3rz-`)@+_Oe-pVBl5v*kWPMqDJF6v)8?I>?JRsH2aO%iW*qme{ z@p8hl3|)c76cqG%tWS&0gTInlQY~lPi31TIW-I5*o@QOJUl?pu?dfELm8S~OqT=^c zhe~b(MJcz4E&DzrLUoWX8__!!ZdQgQvGD!!BXRxRt3uugyd1Yo5&TRE#H;VlSSc{O z_Ko`9;9U94uDcCwszl>DW7WsNoIu;)MXC|PrhLLh$Ew%FoT5u{%~sfISj{oLrZh9p zzfGW^RI6`-RbnAz=NCrdCH&rkHJ!>={4lR3ReB7yzo&rLZwq_2Sn$^`H-~ek?+U)L z9&%q0*!3EMEejs(KZUoGy+903Ia11JErJQ#JGHZu=OW`)78yWuRLn4X;kd70 zoWHG$HcxtAfTUOGBn4yO;_sxaU!btBV<$c;Q8DUbr#>p*8XY{PQY(-iLq1K=tlUge zI@NN`f8pJ%;NB(YLCFMwDE0V!1~S6fUF4bwPMNea7y;e5oj5j(`j&fD<03FcwE^$$ zim4huZWY;}oTM#HcUrTsimnx&P@+%8p2oEt5aD)bbKn$xx|lCup)=HQd7L!KWKhcX zC=J=~7MS5)sx)96h8Yz~r(gVosFL5wff~8N?$KuN0v)*|d~~6q+*4K1V%jl7DO^aw zcS|k)SKE-WpYH_&or%6A@q%$-k!rF2b4vOKsTCuqf2Cv%;YcjbRerhu(@gs924D759G_0&^Q4>wX)YeRT6V;0_vau^@ zkq#O6dfPUrgVt-W%^0d6iP=5;giZ?hiM=tjoO*ZRg;#79K|MvN`&6oYaSrc zw5ufXZT(}qPK-ZMg0qk(ypCaIkEhFWvd!3=CF{q3%n4 zX6N(Rt76T~IuOu(!fj=)K1V&ehCE{*G`zDmUX(TsC$!|?A+)#PA-Fqbf}2Ivq8Knh zj}K3$Shk7OhrS=P;RDI6RiX5EiKw@H5GbWg8)MpOoEtLsrYWU=?%`#y9Eg4<%p*%! zJ*$rM zmZxuG@JeCdb3Bbwn8(6<#hqBkj$ZI&uOXoK`=wcsU$|$%$XImQ}3{De? zP{^oknQONzJqGV?>emuY9NN#Q#>qD~K*6Y$Vf@Ceg*Q2^pUS&5!rdUKEgfjj>AH_Z z9;aZ|+L7d8k6f9cq)5!7AiF7aP?w}U`btKKqx%AXcvZacTdhqR+r-1~Mk7K-E|GKM ziA$Y;r{L`1s6pAQ_fDlj`XhdkLX3rsV_V~p7)D8G!5nL&(7LdF0cMB;?Mh!$;IX8=RC$5HE5zta;`JWtLTQTL z+W1Xlh-S|7ROuy=ee_A|rST;MeLmY1M{HFF>jc2jD1#r&3;?T0(k*UiPJz+)#&F0r zqId?r%`a+}b13|nMNxDrE;}n9mq{T+JJ<1NMANL@sF$-nXL%MBKNr^;jiWksKTEH`?G2U zU#U<-Jo=pBT}+3w-4Q|&$>_8lL5%!C^yJX}CG-dpkwJ6I#)RmP=$Vnh&>v`eKUvZu zk@kd!Xc)W_Z|w4e?fOKSq!~U&j*UF@@94UiU(i1pUocD}`Iv)u4s|Zkny`h7D!XHi zBv^u0>mp*;ceey2THpr{I0RC}FRV6rt>WsJoUWfo(RbWL$KDv2{`YwPHGO4s$8 zo=GTTu#+MV7yy|$O0wGI7=Y`nh3=XjcxPTwlH-1%}uTr8{#x{7HN9YW}8 zbR!EEhFoRY|G^l$(Qc?UaMKw&>#O{P!$}hAY}I>)|k(p3`X6tjDY3t7zxJ1Js^G+5un z)VK~E2lH?@nkIfUbv)4HwP%kpk9gqCH@7k~GxO0K{@$NV`e{dX!EtMueZECJi<{e3 zsx{kHbn92uaD~<`CdE=d=w$Z6bh3IjqEvg$7D{(FruMT!a)$L)P_x^5&#lY*=?(jb zQQUxIfq1dcV-Nv4N$f~&kk@9sCq7|) z=^5NI8G!a2$37g%jPH z6B4K<&A&rU09DI@uawY%n5Si!2xrlUeG1Xs7s#sN-=M6EY7v|_(u!iSur?Ou&Mhob z8>R;-Iu+%i7t%5#g6UWAlLLXj=x)8w8j{m*g+0M3i{Xo)iAOuKvT5eA>vgdt! zo4W=Fl4uSTx}3g7InL9TD;$ID8$TQ>IaNIbLT|Id%uee~n}|P8%PJ+5q?j?z9rTg9 zgizAzI3qp@-~JCn+CWCb#?izPQQ0=ZrLa$Cx1ym%%Ozlql1F39`yuvhI%?UrkSSWr zwnS|MxHdQ_RzhT9ijH!HK|V44JZEqq|0c7-tx^06UW??MDoGYUJiJ=)@&!Qsjnwux z-s>PPbB@?jhNNcH{$^+!=M%jdDhab~6W&gM`9NpSNhvbPcUX)jqpcrvJF#0LvtIgb zQJbL-LV|ON-MzONRtmgYS0uKFJEouaBaHKf0xq;4lxw5WnOHh^vwTT??1HA!6F>s1 zJwjsA9qUKUbgobe>7?!J`mG!u5&%Lyn2~JP7T`t0Rlb?J#Jo8^Oqz_kO-_$20r37n z1C#>o$!p_F0hw&6k>>|ifTY)v20lbq`Q!sDNTg5UTfPWBV#}=4YxJ>rj2ds5IMKsy|AmF=`0Ny@*dw1sI1l7}k z8{G2AZ;ez}4&$!=)orowgYVfc#&;jR}b78QG}z(t^@P3{YG>_9X4C ze&FL-b@nAFXg|tI`k1w(J*X+;%fSgs&z7gj#o8H|JCi39#uWETbNtz;rH7FXj<%K* z&B@vL=>5ikhe-4d502)>zLJ>^;e(CV$@#60m#cL|XGK#xyH2HsyO(RP&MHfz!$XyN zQ?%K|d3qT|eM6xNA_o-;fx+inc&Br^)y}z9EOmyb@l{)g<3n+_m*jbQnc29!SfXp90E@^8EF(l(}5H6DQ~vTE|}@r zw6jTBRlVJz;?iiS7}KwmNt`8cb7cMErb9zSDDirTz+Um0Ey^FJwNiz%%g!E@H!}$t z8$uvBGbUU=c2+*_{Eb1Ev!eZMGJDa4+p3#a7RfNNNM(=iQfw)Kr%^1(7L$0bZW__B zo*H&osRT%Sx7U`JDc$a*wEzv>KEe=eV)Pk0nq4+YJuBbcGYiE)3B@N-O0K;<+(>aj z@Fnm%I|Ud2Z4coM7{Q|Xrz135k!!*gM8S%sg*w}WtH2)4k(dK6q471U|DY7230UA6bEsz8AWjwD34Z?GbN zX#n~qyrz?nCv$)^vfc!F*?JeyUWIz^p5=2@VV%LybrboE8Pue$73Ryi#6?A{k*ll_ zjEP^rsxxu8NR(_G0tglELIbWsuODAGsAHL_4-pK4p>&$3+ND)2I6(kme%3Tm{qXh-(r)�ENPTm(kslQ#;~RdMx7gVwXv?*zp)?>7!8? zsQ0;5v!nzUml~B4KuZt*zv}6>tv_7(-ZVck@#IV&IaU z5lnz}S7T;2NqnB;Qc;)pXz|pCB{K}^Ur3jQnN;pFe*CS7our;_tJbCuv&wY9#(EC4 zMxcbj{3T5_DnxaI<(NN)v}ygj6|liRuuDi{7!3nB)%(^1po3!QU;Rb46Zegh;>(=QKg!wC zMN>(6DOr6x@gCCN3NLyzpV{opt~LNCq6ZS@t6B_#|3j(9YR zH-`H5BY^zO6eR$Bao)AZJvF2%Z0j-7hm`f}!oD3LO9E}M?pHS$CveCaoq#rV7&Xv) zOdYD@v@&HG4V2nZ`!{^1VAh&p#k{g;v$=e5S#b{%mU_rdM?#kgsB83L{H>^!4q{z|{PDI`s3oBc4+!jV{}NH#xd?yXS?cyYIEF-}gU5l{qTCU@ zbA&wr&f#33GrsuQE3)u$fJq7=0@!V{xFUVcVfh#Y!c~Ny#PZE`Kf8fz>{`U~sL9sk z@2?6AaJ&j^sz;oepjn9~zFY*ac$&}#*`$vsa`v&J-;D_YXGddQdKx1-3T-<~v z!EqikbkJMvUNjvmA$sR~CXar>Y76y;qf6RtBZMT9rG}X+Id<$hswC0BXK~(;Wa@Kb`JV$e5|v(o!`C)L`^^F*9@1@Gw( z8Z^R=ioIT-JyxSm`PmQQPg^Ysc0+jA%L$ILl!*PS&MzDQ!DOt=gPHfdflrH1(%#Ss zga|zfCNMIjzc2m{T6Qnvsqq(q0xmoxTlplA;v54ET&dk)@@5;SRfXWHfGDMlQO`hr z`Sfv2cf>gznsV67sH5%3erytKue>O^ayT8Hi2qFNinsd~*ST z4}BU@)JouGiO+{9OQE7`G}(bf@mE1VmqnLveEOm%>!RjvHv~X_43FYvM&<6wCpN zd%Ilwot)G{pW6BLIQ4Al;rHRo{hgPagXdSB->>7ni{g{}wSlgz&5fy|YV2!7o~Mt) z`@4xd`v{tvMH~_Bqrk44Fu{;PGCx89%Wo<#2MmPtdMxtC2=>?-1>8V9{Dy*nTD&$P zfpJm@xS5~X{uM%0iPhtx1C z@6P=O%>8i1VnYnNGmH$wP<5FfA}nW81C$;pN6v-{3t6Z0Vobv#<4t6$m(WX%%pl*s zJqh$Pk2nI)kU`KAG>|!MNbaet{ z5q-%JwXWPUTVCiF4uDww?%keA4Tp@)DS zXhPd5iF~qGT~-=*CHYN$UoBBGns_%B+7-ClZlsFr8(yt;sa{B=&hk`@(-;&L7YNpX z`L2?1`v{)Ax_MW^xmR)(9Cl_m0RdmoS2uY=sBZ|O$M&$~NJa=#qxq*(MsRNKKjs{m z+j@esHtyL57?@-`JoG99Y%Aes&ItGFQWHMU!QzK+l z2D_gkm?Jmv7y@WzFNFA=@+@59$&O1#K74a~kna%+<0IfCQ^S|wWyYs&p~Bg>#G^DV z2*LLuDMoR%ee_ZyY1?I)VQ=m8=6yor2rJz3J!X+%SO>NNM~{4;uPVnSMkQ^Hn=H~Z z40P-67~2q?K{0t1Q?cLNr?&{U%efu!$sk}+K|<$7)}(c_^a`c+BK(V?tYM$Uf59r- z5eGWJxsF2+tx=T79i5YKPl@d``MCMfTx;-N!+Q?E?qfjyC>dtA8I0*4@R|oUot|y$ zzto=wedsB+>(f;`K;P`X4%eB7!y1^1Hsv_kIwBYi1?t{$ScO5DveT7 zsVANFAP8AD+16g2nzYjM5H0Z|9%9b4)5qpazRa+!~nGl!4vNj(>vxOF5V)r0|%Rj5v}1JEpGYrNzZ1xuB}QgqnnUoSisuF0v_o;ZyY(y^*;CdvT{7x#%Zxp!)T%r?`2A1hc73W|;N{$yc;sf^ z0L;|yDwN;kW`S^N3x}AVDu@s7l43;zGjH_FLF|Uj1sf3og(&t})o<=I(a$io2hQcx%Q&f}YvImG z>1R?Cy61Cq)bvWo0@nkYB69la5wZv<0Q8b|N)6*?Y(PF25wz5>gwcbu7XKuy)es{Ou^C}udZ0E}*Yb-Crr;X)b*@z4HiWUR#I=4Po58Ko}3GBURU+eXaCp`0QRTBrM96qECA6`RP9c#Ns&67HNK>fu zw)py6rLZdkvTIxO z7Nwyr)Ze~O_hjvadX6^+^Dp-b_z?BSI?hGa!r(3@JGm-hlzRT&K1~e*4O8H+*wWOa zXKNAdzCBHlC@USAzM=42fN=oXWUlw;)3U!rTw+9T4h3jkaOW6eGt8by@&H7c^gx$N zByK`VdlZ6z3?k$!&BU;Kag3W!;r+;8Y8#QUB||)!@0~X_<@+wKL)(u+sD%?$*sR3B z3ENoHi?$R0CE)!0=o8#&c0lI=UrY|xGz-Os%SGVns6a2%RWHbIE1$v>qc&g3lE+OS zImIn8nW2Z+wXGjZDS=?U&+PJfLi7sGs@vk@Eki>r2a7~{z-Wx|t8Iy{gpF5uKCh>z z)l5ngtY|pJsHdG#y86sv2$6Q;_@dX@5a9L}OhSqF^m!7{IBAa?)=H?J4qwdS^idAFj_@aT5|*}O{g z@4>xOy)VEbk4;9}!3exEnE@XNpLk4ezfeBs_t@+7&rOATgRnWNsWQHr3|+&IE_xP> zUt$Qo()0ImEwv)0DN(-3jSH&df;?YLV=`IHu}BF{af*3x8_I+1PU z6Y>^Z0fgvvJ5G7@H)NpTXL0Pe;a>x+jj2r@NdGthdn^!&lo$2s{&7ocn?JwnalC6_ zum(*1y{Mb-y3gV^nn~TA8tnDJidQ>dQ!99&YG#Z*%d{(X%Ae9qP8L-<>*W!qtV#|z zSf-yb38BbZ)Z`-low06|t)CCB@Da3&!slR8!+x~RSenurGoii!#y0Qj6VvCJhYO48 zio(lW;GtrZ0v!+<-1tFnEFAJ$XU*RuTSGAn*qWiZKVyj7#9mu9$|(wj1JW#LIX3aC z{uwVuN3ro|fE}5O|97dAtXx29m)z*v#vz*yhF9_5&%nMluEG~9e!^wUW|d9YtIbh20ViMLenc380@cL9 z&N^e0(1mUH9!FbkQZ`zn`ZGT@dS1gjpy&bUN;j)t38{IKTh%*elthF7_J_KKq_aqI z_r_kik)0RN%d0q-70)N#Om71CDLXly&R1UY^yG=cDiY}TI>&kj{-~198a(BiH1$>*y*1X>Z1EMltXq39N8qDhQSfc8@?6AQYX{kUWgo5GUIyBbQt&Ksu!O zm`gWU&DI%lI3?D}Jw5*NqNk_e&U4R`PORC42h1TB>eNIe^zPk6ooW5d{dmOz-;RTR z^XL1oXNcmM%tU7$J~s0Wnp#_)Ta~BbElblobVnYg^^zJ(&Le#S9^SoLtLs)qv2D={ z%jqdQLG_0_yOiHZp%|&$&gH?tG3`Q%Yt0D)GOP|DwipK*glS<+fPs;{^aV4_9lm6Q zSM%*8WN&%SReaAiSBwdFA;n;)xlGoV_Q|B_PEDdXopy=9u4v6=W6ZW*9fUFJ4%i62 z3qhJ9k)+Ut9IL;rXYd>43OGvQ_Z?P#DmND4ra>>U%?fET&7io7LD?2nPiGRy_BdlI z*_QB+Ne{U!hzGYE8srhS!&#>2>iW@B1DywRel;()Z9xP-ITC$c?g6-$ax2AB9s}UT zU)e1i&ti9TAfpOGSnppwfBwiWm&O+`qTRlgd9`gL=$g~rwkQq}_I#s78oolfl+%nMSlGh3&DoASrE;jK(8 zSlrNV_P(_@t%%PNqiZen4wUognZ*krm1zDponTO-KRP4V!6eLooruBC)ngdd#*(+i z5FidVJ~)+=juy>liD94pMj?;oOJ^Cq(V=0-v8jj9Ny8NY`ua}UP*X zsh7BcWuO-?nl8Yz=ZWH+_g?zf2iA{CUVc!y>R{)NcSbxxjI=?X?&4mmUUap(B34A+`9 z`$UrxOb7+vnCUDl`(_aA*yTVqLrVGmO;vdrE1$TkVl(3o84aP?s3j`QWF zL>73*=qAUfop#;pej4h}Yuejwj|$nqvYd3Y;&tJf^!(V?L{29CI6s{|c@6@#KEl^E zufn0XU1q9W+fIm1dF0}r$5Kyhi-UJ!b?;XQG&O>c+DqA4LvR!2*h5#FnUzr6gpwz* z$7Umtyu#2ajP$Zyhljv37V`i!sXkB}7xyha>3KV^W-q|P2PDLaUZk5?!)E|8V5tt+ zOP7tmx+!Fyv)A~=_r_Bp4uS9?k!_#Q5LaDep6NW@?^!ScCyQo}F?HB(GOHKZf-#{G z7%tvqS8U@Efz*`~3!)jSRs@e))VN)eIjpK(I{r{)JcQovC~DtA9aiZ)6WEH)W{u zx~3BGF+l>YzO3ajkc}(V=YmDp28oGSsPsHLj|DlipaUA|v}a z9vb|QseL>*n$fTrL#yLPT$}ga`D*z(oNkdFq$uX^Td0wRSake0kr1_Lae? zTCs`fTu8HJLD>yqF*%#(^${?;AHbGZ`uUh(hR05`V@y^oF0f}GS&cLW`roW>*|ucqnLq{(`7^lt-&XiD z);4Wr{scimFxc%m>kE&Z%r@8A^Fm|k2SJNtetfik;~+5%*yZh7G1%2M(zbzj`PdVc z#%g#TGBY?)7;LJhRNKzOj@eYeOZ*7BEp;=L_N@txde}i}4bc0na4i^n7dJqVyWDrF zgT6cf{}gu8?fVVwEP8`?QWYe&($98at2FV*d?!&7s%X(eFRiKOVDml#)hK@DJ8i&G zRq(0vL8fq9P5%Nnc={xvrtBm}lv$&Fy zNyK`8D)#J)?f{KtNj${eZEQofzKj9Fs9TsZ#&1I2z!VlimEx@nnp9aqw0<&gd|~nM zo}cMexP^|hTvQuf``XH@SZf&P)8)Nr&plG~E?+v-$4pXHIF|U^r6?1KVI?LLu+;!! z@0URsSWhLy$prg~hFTZ1c^ji@BcgKzsLLuyjSlU*<#=V&c2$wUc*9UYT0t@wZHMSE zl>$kmOUOATxz6}}s3GK2+MvJj0@2*Lyr7H{!x-(=`U3;Ah&T61MnsxtVrR|gm4v)C z+X2}iMjf}GJjl#X3YcOakqbSPhlNxM44rS?W~|VgcJwa~&1NuuqQK(l%nwBJ%r2ww zEI7krd-|p$s|M}5#!6(tpbvYbAH`16t)@wDFnJ?1E*Y7{l||vR1?StuB^(qncoRf9 zWr*p6!*?(rGEB#i=)jO1xr?!)(tsifFQxR{8O)Tcix`e@v`m@S45MSc67%^RTv&|9|TY%TVHLI(Z5QbW=V9G~vvf-?>zM$d3FDbKGDXxGAqA6QuLK+G-)2mH2Im?@Pk zpDY0?ACQ+&HBkgDg1TofAU++c<5n8ewH9;>>w$R_TtOcA=~jwt#^wyTVmyHlWI8hG zWy_|RvoN=WK|mxg-5Qi1WDdH35{7q({@s}zrja;F{xSqGHwA>M%%glLpIG5=0F>KD z5}ngIcvJk%d=5LsM3|UHxNE(}`|bp+Q_K8wSq2eHj9(+EJ0KJarfDfK-_yZ+2TbD8 zV@ZYbNr>x?h`DW;7|cXO51CDemh;^zu`Ea>e~T{W|6C(QV?(Vn?|=o=u2K|R@U6~7uj2mXpJLAGo? z_(d%PPyHC1rG+3`1x&vS!xbFa1XSd9J5ZM2XfXbcXkV_U;{|D};xKND-){oj2T7MI z=bYoB=>R(JGnb#c_M7s=fMayl)tzYg!jljw#}NuhsoSI7y``@9AyV4E^o{#9ZQrbv zRn>j|nZ`4N8M?$|B&^F#>c<3u=VwU9?ttO zsRYrT`{{_~k^W^&yV(5JT@df|_8fV>GJ96n-$moH?;CJgTFP3IY8z$}F}XyyDFh?5 z8D>qu3E3(iodj7NlRnTJrH}#LIuQp&wRAegU5tp73jwm`%lWEp&WCR-nY+3^2OiF+ z2wu(Y*}PiPmtURZZ6*5dCbuH4SG!WBh#w9kJ3UykIx`7fteJZemsp0P2OkLqfu~QZ!zXyro)sl+lHM1FI@;En|*_pZFkq_u{BeqJVAsdunZEA7;EbP-+XY7xBf;&C~V% zI_=8OMTkIiYklsxf!6K(BZ$r5PK}2>B==@Z#(1FX?DeMnWFetw-~isH=y8b=q~4k` zL5Ev2(&X?#&&l0^h0hucvw$yTtNvtTi>i|M`6W8o4)yB+lR*cLj?Qmx-!N00TFuv! z!*BNx>W>R&-xPXELOr=$8CpBCy>d1RNzBsPgC7wI-n!aNK((}1&PIu5!kaIN>tW|4 z4*MivL^%Bx8>=JpRYg`F!<6+@^gU=``oS2`7fhvzeaCIYYG!U3+{oOBCuE+GUvjtJWC(>>9^Rkg*C%d9LtLgCc78|YD#A&)?_hSfeS_8BF(D2I z#|uKWoH@IBy;&R8A_V4a+$4!xZeJTcK=RJn61y|4t`6aJa1{{0iv~N#_ssQQrn4du z_Q4!WQY*nVyjUFvpQtoPYr5^oewF<=UzosxTsymYU#1_t+fqSvTm416G(e+Vd~A`6EJ%s{pRje@6d?SHpaLM?}xraCMYy=j-d%^Bs6v}uFAC7hxPS9;i!z70iJejPpFd=JC+p(W|L zm#1d}=a!naeD9$WG~;KSZv0V571~2wrQbD=5n#vTGBq7bz+P1zbg~-QZ15B)z@zZd zJ3`tsAoZ&MkLmG4kK(360nMB=KXji9B|CysZ=DG23PuWLE&RChv)1iukNP;-bk z3a=Z~+a{UV@|m^Zw~Hl*v;ZLWtzmr9vMG|$+99Sbwzba1Gu%&XvorKR4xTp%O34Lu zXS%y6X9w@nr7@ZOEI`YJ#v+=`0Kv5%nkYffw5EU=Y&p>qS5Y-3X@u*VL=Evmji z)>n9bZ(%6BULsqLyZXMLdqkKdA)!l<$gR|>5n65i6giB(+4Q0Ev*S&e;To<71FG38 z-u3?b09*i!;iVC)Eh8M0?-tMT=&o)36$GB^?xe64!EA>C13 zM7Xu*>nQ(l0bCyT$&ePqGCMpSg2Su2-!(Fm$fIOdg38!BJuq$&H}s5F7R9Vb>CcLG zEmnLos#gLa>V0Gdy6y$GM;RvfIZS7Yx}(V8T|*eYI{B173n2$@F7=ZvS0)}~1xFVW z1-_zSQNH$7eAzD)-{Ne(76GBb-c&w(XM}wk1@JSoc)ofq6;bXd`})@4+5M^0ZRDY5 z_L-MSHgc18LtOfZk4Ss?ra$PWl`oEa9lU{QFzXgm%=S(pDUwZ>iufc|M1QyMOB6i= z1GvS#T1t6~jjEY;o?ne=qHj5X5j;y@Axg_V5;J$s$H%#VjnPL32NZ*AE3x;G68*B%o7v3VN%ADjO<4gZ6`+P*(z*?*C8zut&{5Qn(_Ba{mGNe_sAeVD_Jozl+xXr4aqEF(veGA^%ku{7=f?bu0f8 zxc%2q7W;$p-v{Xb%iaEyBl$O}w6AIOw~;FKzj6MpL4U7&{wo*oU&C7A-+=y0XYHTH z{5>Q5PyZhK|2xLNHRSK9Vvej{*~k?D*^H~Pyd=% PhycM~$&%6?f35u=B5tYG literal 0 HcmV?d00001 diff --git a/jode/makesnapshot b/jode/makesnapshot new file mode 100755 index 0000000..d67d611 --- /dev/null +++ b/jode/makesnapshot @@ -0,0 +1,40 @@ +#!/bin/sh + +OLDDIR=`pwd` +TIME=`date +"%Y%m%d %H:%M"` +if [ "${1#-D}" != "$1" ] ; then + TIME=`date +"%Y%m%d %H:%M" --date="${1#-D}"` + shift; +fi +DATE=`echo $TIME | cut -c0-8` +echo $TIME +echo $DATE + +TEMP=`mktemp -d $HOME/tmp.XXXXXX` + +trap "cd $OLDDIR; rm -rf $TEMP" EXIT +cd $TEMP + +CLASSPATH=$TEMP:/usr/local/swing-1.1/swing.jar +export CLASSPATH + +cvs export -D"$TIME" jode +cd jode +perl -i -pe's/(snapshot )[0-9]+/${1}'"$TIME"'/ + if /public final static String version/;' GlobalOptions.java + +COMPILER=${1:-jikes} +if [ -z "$1" ]; then + FLAGS="-g" +else + shift + FLAGS="$*" +fi + +# jasmin -d .. jvm/Interpreter.j + +eval $COMPILER $FLAGS -d .. \ + Decompiler.java obfuscator/Main.java JodeApplet.java swingui/Main.java + +cd .. +zip -r $HOME/jode-$DATE.zip jode diff --git a/jode/prj.el b/jode/prj.el new file mode 100644 index 0000000..d768113 --- /dev/null +++ b/jode/prj.el @@ -0,0 +1,84 @@ + +(jde-set-project-name "jode") +(jde-set-variables + '(jde-run-option-properties nil) + '(jde-run-option-stack-size (quote ((128 . "kilobytes") (400 . "kilobytes")))) + '(jde-gen-buffer-templates (quote (("Class" . jde-gen-class) ("Console" . jde-gen-console) ("Swing App" . jde-gen-jfc-app)))) + '(jde-compile-option-command-line-args "") + '(jde-gen-action-listener-template (quote ("'& (P \"Component name: \")" "\".addActionListener(new ActionListener() {\" 'n>" "\"public void actionPerformed(ActionEvent e) {\" 'n>" "\"}});\" 'n>"))) + '(jde-compile-option-depend nil) + '(jde-compile-option-optimize nil) + '(jde-run-option-verify (quote (nil t))) + '(jde-gen-inner-class-template (quote ("'& \"class \" (P \"Class name: \" class)" "(P \"Superclass: \" super t)" "(let ((parent (jde-gen-lookup-named 'super)))" "(if (not (string= parent \"\"))" "(concat \" extends \" parent))) \" {\" 'n>" "\"public \" (s class) \"() {\" 'n> \"}\" 'n> \"}\" 'n>"))) + '(jde-run-read-vm-args nil) + '(jde-entering-java-buffer-hooks (quote (jde-reload-project-file))) + '(jde-run-applet-viewer "appletviewer") + '(jde-compile-option-debug t t) + '(jde-project-file-name "prj.el") + '(jde-run-option-verbose (quote (nil nil nil))) + '(jde-run-application-class "") + '(jde-db-option-vm-args nil) + '(jde-run-option-heap-size (quote ((1 . "megabytes") (16 . "megabytes")))) + '(jde-db-read-vm-args nil) + '(jde-db-option-heap-profile (quote (nil "./java.hprof" 5 20 "Allocation objects"))) + '(jde-db-mode-hook nil) + '(jde-run-option-garbage-collection (quote (t t))) + '(jde-compile-option-vm-args nil) + '(jde-run-applet-doc "index.html") + '(jde-db-option-java-profile (quote (nil . "./java.prof"))) + '(jde-gen-get-set-var-template (quote ("'n>" "(P \"Variable type: \" type) \" \"" "(P \"Variable name: \" name) \";\" 'n> 'n>" "\"/**\" 'n>" "\"* Get the value of \" (s name) \".\" 'n>" "\"* @return Value of \" (s name) \".\" 'n>" "\"*/\" 'n>" "\"public \" (s type) \" get\" (jde-gen-init-cap (jde-gen-lookup-named 'name))" "\"() {return \" (s name) \";}\" 'n> 'n>" "\"/**\" 'n>" "\"* Set the value of \" (s name) \".\" 'n>" "\"* @param v Value to assign to \" (s name) \".\" 'n>" "\"*/\" 'n>" "\"public void set\" (jde-gen-init-cap (jde-gen-lookup-named 'name))" "\"(\" (s type) \" v) {this.\" (s name) \" = v;}\" 'n>"))) + '(jde-db-option-verify (quote (nil t))) + '(jde-run-mode-hook nil) + '(jde-db-option-classpath nil) + '(jde-compile-option-deprecation nil) + '(jde-db-startup-commands nil) + '(jde-gen-boilerplate-function (quote jde-gen-create-buffer-boilerplate)) + '(jde-compile-option-nodebug nil) + '(jde-compile-option-classpath nil) + '(jde-build-use-make nil) + '(jde-quote-classpath t) + '(jde-gen-to-string-method-template (quote ("'&" "\"public String toString() {\" 'n>" "\"return super.toString();\" 'n>" "\"}\" 'n>"))) + '(jde-run-read-app-args nil) + '(jde-db-source-directories (quote ("d:/jdk1.2/src/"))) + '(jde-db-option-properties nil) + '(jde-db-option-stack-size (quote ((128 . "kilobytes") (400 . "kilobytes")))) + '(jde-db-set-initial-breakpoint t) + '(jde-run-option-application-args (quote ("-v" "--debug=analyze,inout" "--classpath=/home/jochen/output/share/jode-1.0.90.jar" "jode.Decompiler")) t) + '(jde-gen-mouse-listener-template (quote ("'& (P \"Component name: \")" "\".addMouseListener(new MouseAdapter() {\" 'n>" "\"public void mouseClicked(MouseEvent e) {}\" 'n>" "\"public void mouseEntered(MouseEvent e) {}\" 'n>" "\"public void mouseExited(MouseEvent e) {}\" 'n>" "\"public void mousePressed(MouseEvent e) {}\" 'n>" "\"public void mouseReleased(MouseEvent e) {}});\" 'n>"))) + '(jde-gen-console-buffer-template (quote ("(funcall jde-gen-boilerplate-function) 'n" "\"/**\" 'n" "\" * \"" "(file-name-nondirectory buffer-file-name) 'n" "\" *\" 'n" "\" *\" 'n" "\" * Created: \" (current-time-string) 'n" "\" *\" 'n" "\" * @author \" (user-full-name) 'n" "\" * @version\" 'n" "\" */\" 'n>" "'n>" "\"public class \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\" {\" 'n> 'n>" "\"public \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\"() {\" 'n>" "'n>" "\"}\" 'n>" "'n>" "\"public static void main(String[] args) {\" 'n>" "'p 'n>" "\"}\" 'n> 'n>" "\"} // \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "'n>"))) + '(jde-compile-option-directory "/home/jochen/java/unstable/build" t) + '(jde-run-option-vm-args nil) + '(jde-make-program "make") + '(jde-use-font-lock t) + '(jde-db-option-garbage-collection (quote (t t))) + '(jde-gen-class-buffer-template (quote ("(funcall jde-gen-boilerplate-function)" "\"package jode;\" 'n" "'n" "\"/**\" 'n" "\" * \" 'n" "\" * @author \" (user-full-name) 'n" "\" */\" 'n" "\"public class \" (file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\" \" (jde-gen-get-super-class) \" {\" 'n" "> 'n" "> \"public \" (file-name-sans-extension (file-name-nondirectory buffer-file-name)) \"() {\" 'n" "> 'p 'n" "> \"}\" 'n" "> 'n" "> \"}\" 'n"))) + '(jde-compiler "jikes +E") + '(jde-jdk-doc-url "file:/usr/doc/packages/jdk115/docs/index.html") + '(jde-db-debugger (quote ("jdb" . "Executable"))) + '(jde-compile-option-optimize-interclass nil) + '(jde-run-option-classpath nil) + '(jde-key-bindings (quote (("" . jde-compile) ("" . jde-run) ("" . jde-db) ("" . jde-build) ("" . jde-run-menu-run-applet) ("" . jde-browse-jdk-doc) ("" . jde-save-project) (" " . jde-gen-println)))) + '(jde-gen-mouse-motion-listener-template (quote ("'& (P \"Component name: \")" "\".addMouseMotionListener(new MouseMotionAdapter() {\" 'n>" "\"public void mouseDragged(MouseEvent e) {}\" 'n>" "\"public void mouseMoved(MouseEvent e) {}});\" 'n>"))) + '(jde-db-marker-regexp "^Breakpoint hit: .*(\\([^$]*\\).*:\\([0-9]*\\))") + '(jde-run-working-directory "") + '(jde-gen-window-listener-template (quote ("'& (P \"Window name: \")" "\".addWindowListener(new WindowAdapter() {\" 'n>" "\"public void windowActivated(WindowEvent e) {}\" 'n>" "\"public void windowClosed(WindowEvent e) {}\" 'n>" "\"public void windowClosing(WindowEvent e) {System.exit(0);}\" 'n>" "\"public void windowDeactivated(WindowEvent e) {}\" 'n>" "\"public void windowDeiconified(WindowEvent e) {}\" 'n>" "\"public void windowIconified(WindowEvent e) {}\" 'n>" "\"public void windowOpened(WindowEvent e) {}});\" 'n>"))) + '(jde-global-classpath (quote ("/usr/local/swing-1.1/swing.jar" "/usr/local/1.1collections/lib/collections.jar" "/home/jochen/java/jars/getopt.zip" "/home/jochen/java/unstable/jode" "/home/jochen/java/unstable/build" "/usr/lib/java/lib/classes.zip")) t) + '(jde-enable-abbrev-mode nil) + '(jde-gen-println (quote ("'&" "\"System.out.println(\" (P \"Print out: \") \");\" 'n>"))) + '(jde-run-option-heap-profile (quote (nil "./java.hprof" 5 20 "Allocation objects"))) + '(jde-db-read-app-args nil) + '(jde-db-option-verbose (quote (nil nil nil))) + '(jde-run-java-vm "java") + '(jde-read-compile-args nil) + '(jde-run-option-java-profile (quote (nil . "./java.prof"))) + '(jde-compile-option-encoding nil) + '(jde-run-java-vm-w "javaw") + '(jde-compile-option-nowarn nil) + '(jde-gen-jfc-app-buffer-template (quote ("(funcall jde-gen-boilerplate-function) 'n" "\"import java.awt.*;\" 'n" "\"import java.awt.event.*;\" 'n" "\"import com.sun.java.swing.*;\" 'n 'n" "\"/**\" 'n" "\" * \"" "(file-name-nondirectory buffer-file-name) 'n" "\" *\" 'n" "\" *\" 'n" "\" * Created: \" (current-time-string) 'n" "\" *\" 'n" "\" * @author \" (user-full-name) 'n" "\" * @version\" 'n" "\" */\" 'n>" "'n>" "\"public class \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\" extends JFrame {\" 'n> 'n>" "\"public \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\"() {\" 'n>" "\"super(\\\"\" (P \"Enter app title: \") \"\\\");\" 'n>" "\"setSize(600, 400);\" 'n>" "\"addWindowListener(new WindowAdapter() {\" 'n>" "\"public void windowClosing(WindowEvent e) {System.exit(0);}\" 'n>" "\"public void windowOpened(WindowEvent e) {}});\" 'n>" "\"}\" 'n>" "'n>" "\"public static void main(String[] args) {\" 'n>" "'n>" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\" f = new \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "\"();\" 'n>" "\"f.show();\" 'n>" "'p 'n>" "\"}\" 'n> 'n>" "\"} // \"" "(file-name-sans-extension (file-name-nondirectory buffer-file-name))" "'n>"))) + '(jde-db-option-application-args nil) + '(jde-gen-buffer-boilerplate (quote ("/* " (file-name-nondirectory buffer-file-name) " Copyright (C) 1997-1998 Jochen Hoenicke." (quote n) " *" (quote n) " * This program is free software; you can redistribute it and/or modify" (quote n) " * it under the terms of the GNU General Public License as published by" (quote n) " * the Free Software Foundation; either version 2, or (at your option)" (quote n) " * any later version." (quote n) " *" (quote n) " * This program is distributed in the hope that it will be useful," (quote n) " * but WITHOUT ANY WARRANTY; without even the implied warranty of" (quote n) " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" (quote n) " * GNU General Public License for more details." (quote n) " *" (quote n) " * You should have received a copy of the GNU General Public License" (quote n) " * along with this program; see the file COPYING. If not, write to" (quote n) " * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA." (quote n) " *" (quote n) " * $" "Id$" (quote n) " */" (quote n)))) + '(jde-db-option-heap-size (quote ((1 . "megabytes") (16 . "megabytes")))) + '(jde-compile-option-verbose nil) + '(jde-mode-abbreviations (quote (("ab" . "abstract") ("bo" . "boolean") ("br" . "break") ("by" . "byte") ("byv" . "byvalue") ("cas" . "cast") ("ca" . "catch") ("ch" . "char") ("cl" . "class") ("co" . "const") ("con" . "continue") ("de" . "default") ("dou" . "double") ("el" . "else") ("ex" . "extends") ("fa" . "false") ("fi" . "final") ("fin" . "finally") ("fl" . "float") ("fo" . "for") ("fu" . "future") ("ge" . "generic") ("go" . "goto") ("impl" . "implements") ("impo" . "import") ("ins" . "instanceof") ("in" . "int") ("inte" . "interface") ("lo" . "long") ("na" . "native") ("ne" . "new") ("nu" . "null") ("pa" . "package") ("pri" . "private") ("pro" . "protected") ("pu" . "public") ("re" . "return") ("sh" . "short") ("st" . "static") ("su" . "super") ("sw" . "switch") ("sy" . "synchronized") ("th" . "this") ("thr" . "throw") ("throw" . "throws") ("tra" . "transient") ("tr" . "true") ("vo" . "void") ("vol" . "volatile") ("wh" . "while")))) + '(jde-make-args "") + '(jde-gen-code-templates (quote (("Get Set Pair" . jde-gen-get-set) ("toString method" . jde-gen-to-string-method) ("Action Listener" . jde-gen-action-listener) ("Window Listener" . jde-gen-window-listener) ("Mouse Listener" . jde-gen-mouse-listener) ("Mouse Motion Listener" . jde-gen-mouse-motion-listener) ("Inner Class" . jde-gen-inner-class) ("println" . jde-gen-println))))) diff --git a/jode/project-ext.dtd b/jode/project-ext.dtd new file mode 100644 index 0000000..0924399 --- /dev/null +++ b/jode/project-ext.dtd @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/jode/project.dtd b/jode/project.dtd new file mode 100644 index 0000000..7320ac2 --- /dev/null +++ b/jode/project.dtd @@ -0,0 +1,273 @@ + + + + + + + + +%ext-file; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jode/props/net/sf/jode/decompiler/OptionNames.properties b/jode/props/net/sf/jode/decompiler/OptionNames.properties new file mode 100644 index 0000000..8b9e7d6 --- /dev/null +++ b/jode/props/net/sf/jode/decompiler/OptionNames.properties @@ -0,0 +1,96 @@ +options = tabwidth,indent,style,linewidth,import,verbose,lvt,inner,anonymous,push,pretty,decrypt,onetime,immediate,verify,contrafo,debug + +tabwidth.0= +tabwidth.1=Set tab width to n. +tabwidth.2=This means that Jode uses tabs to replace n spaces. \ +Don't confound this with the indent option. Use 0 if you don't want \ +tabs. Default is 8. + +indent.0= +indent.1=Indent blocks by n spaces. + +style.0={sun|gnu} +style.1=Specify indentation style. + +linewidth.0= +linewidth.1=Set maximum line width to n. +linewidth.2=Jode breaks lines that are longer than this. It tries it's best \ +to make all lines fit in this limit, but sometimes this won't succeed. + +import.0=, +import.1=import classes if they occur more than clslimit times and packages \ +with more than pkglimit used classes. Default is 0,1 which means that all \ +used classes are imported, but never a whole package. + +verbose.0= +verbose.1=Be verbose (higher n means more verbose). +verbose.2=This prints some information about the currently decompiled \ +class or method to the console. + +debug.0=,... +debug.1=Enable debugging options. Useful to track errors in the decompiler. +debug.2=Possible flags are: \ +"bytecode", to print raw bytecode. \ +"lvt", dump LocalVariableTable. \ +"verifier", to trace bytecode verification. \ +"check", do time consuming sanity checks; useful to spot serious errors. \ +"types", to see the type intersections. \ +"flow", for a very verbose trace of the decompile process. \ +"analyze", briefly inform about "T1/T2" analyzation. \ +"inout", to view the in/out local variable analyzation. \ +"locals", to see how local variable merging is done. \ +"constructors", to trace constructor transformation. \ +"interpreter", to follow the execution of the interpreter \ +(the interpreter is used for string decryption). + +inner.0={yes|no} +inner.1=(Don't) decompiler inner classes. + +anonymous.0={yes|no} +anonymous.1=(Don't) decompiler method scoped classes. + +contrafo.0={yes|no} +contrafo.1=(Don't) transform constructors. + +lvt.0={yes|no} +lvt.1=(Don't) use the local variable table. +lvt.2=Turning it off is useful if an obfuscator filled it with bogus values. + +pretty.0={yes|no} +pretty.1=(Don't) use `pretty' names for local variables. +pretty.2=The non pretty names have the advantage, that their names are \ +unique. This make search & replace possible. + +push.0={yes|no} +push.1=Allow PUSH pseudo instructions in output. +push.2=Sometimes, when methods were inlined, Jode can't reconstruct \ +the original expression. It has to split a complex expression into \ +several ones, using temporary variables. If this option is on, it won't \ +use the temporary variables, but uses pseudo PUSH/POP instructions instead, \ +as they are in the bytecode. + +decrypt.0={yes|no} +decrypt.1=(Don't) decrypt encrypted strings. +decrypt.2=Some obfuscators encrypt all strings. To decrypt them at runtime \ +they add a small decryption routine to the code. If Jode detects such a \ +decryption routine it interprets it to decrypt the strings at decompile time. + +onetime.0={yes|no} +onetime.1=(Don't) remove locals that written and then immediately read. +onetime.2=When javac inlines a method it uses temporary local variables for \ +the parameters. Often these local variables can be removed, which makes \ +the code much better to read. + +immediate.0={yes|no} +immediate.1=Output the source immediately as it gets decompiled. +immediate.2=This leads to more instant output, but has many disadvantages.\ +For one the import statements can't be correct. But it also may lead to \ +buggy code. The advantage is, that you can get partial output even if an +exception is thrown. + +verify.0={yes|no} +verify.1=(Don't) verify code before decompiling it. +verify.2=Jode assumes at many places that your byte code is legal. To \ +be sure it verifies it before decompiling. If verification fails it \ +rejects the code. Since verification can fail for legal code if the \ +type hierarchy is not known, you can turn this option off. diff --git a/jode/props/net/sf/jode/swingui/Resources.properties b/jode/props/net/sf/jode/swingui/Resources.properties new file mode 100644 index 0000000..b57889a --- /dev/null +++ b/jode/props/net/sf/jode/swingui/Resources.properties @@ -0,0 +1,28 @@ +usage.count=3 +usage.0 = usage: java jode.swingui.Main [CLASSPATH] +usage.1 = The directories in CLASSPATH should be separated by ','. +usage.2 = If no CLASSPATH is given the virtual machine classpath is used. + +browse.filter.description = *.jar, *.zip Archives +browse.title = Browse +cpdialog.title = Set Class Path + +button.okay=Okay +button.apply=Apply +button.cancel=Cancel +button.select=Select +button.browse=Browse... +button.add=Add +button.remove=Remove + +main.decompiling=decompiling +main.exception = \nException while decompiling: + +menu.file = File +menu.file.save = Save... +menu.file.save.ex = An Exception occured on filewriting! +menu.file.gc = Collect Garbage +menu.file.exit = Exit +menu.opt = Options +menu.opt.hier = Class Hierarchy +menu.opt.cp = Set Class Path... diff --git a/jode/props/net/sf/jode/swingui/Resources_de.properties b/jode/props/net/sf/jode/swingui/Resources_de.properties new file mode 100644 index 0000000..8e39b9f --- /dev/null +++ b/jode/props/net/sf/jode/swingui/Resources_de.properties @@ -0,0 +1,28 @@ +usage.count=3 +usage.0 = Syntax: java jode.swingui.Main [CLASSPATH] +usage.1 = Die Verzeichnisse in CLASSPATH werden durch Kommas abgetrennt. +usage.2 = Wird kein CLASSPATH angegeben, so wird Java's standard classpath verwendet. + +browse.filter.description = *.jar, *.zip Archive +browse.title = Durchsuchen +cpdialog.title = Setze Classpath + +button.okay = Ok +button.apply = Anwenden +button.cancel = Abbruch +button.select = Selektieren +button.browse = Durchsuchen... +button.add = Hinzufügen +button.remove = Entfernen + +main.decompiling = dekompiliere +main.exception = \nBeim Dekompilieren ist ein Fehler aufgetreten: + +menu.file = Datei +menu.file.save = Speichern... +menu.file.save.ex = Beim Schreiben der Datei trat eine Ausnahme auf! +menu.file.gc = Speicher freiräumen +menu.file.exit = Beenden +menu.opt = Optionen +menu.opt.hier = Klassen-Hierarchie +menu.opt.cp = Setze Classpath... diff --git a/jode/scripts/addHeader.pl b/jode/scripts/addHeader.pl new file mode 100755 index 0000000..417c054 --- /dev/null +++ b/jode/scripts/addHeader.pl @@ -0,0 +1,94 @@ +#!/usr/bin/perl +# addHeader.pl Copyright (C) 1999 Jochen Hoenicke. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +# +# $Id$ + +# This perl script just adds the copyright header to all given java files, +# removing a possible previous header. + + +for (@ARGV) { + my $file = $_; + $file =~ m=([^/]*)\.java(\.in)?$= or do { + print STDERR "$file is not a java file"; + next; + }; + my $class = $1; + my $curyear = `date +%Y`; + chomp $curyear; +# my $firstcheckin = `rlog $file 2>/dev/null |grep date| tail -1`; +# my $firstyear = +# ($firstcheckin =~ m=date: ([0-9]+)/[0-9]+/[0-9]+=) ? $1 : $curyear; +# my $lastcheckin = `rlog $file 2>/dev/null |grep date| head -1`; +# my $lastyear = +# ($firstcheckin =~ m=date: ([0-9]+)/[0-9]+/[0-9]+=) ? $1 : $curyear; + open FILE, "<$file"; + while () { + $firstyear = $1 if /Copyright \(C\) (\d{4})/; + last if /^package/; + } + + $firstyear = $curyear if ! $firstyear; + my $lastyear = $curyear; + my $years = ($firstyear == $lastyear) + ? $firstyear : "$firstyear-$lastyear"; + my $lesser = ""; + my $dotlesser = ""; + + if ($file =~ m!jode/(util|bytecode|jvm|flow|expr|decompiler + |GlobalOptions|AssertError)!x) { + $lesser = " Lesser"; + $dotlesser = ".LESSER"; + } + + rename "$file", "$file.orig" or do { + print STDERR "Can't open file $file\n"; + next; + }; + open OLD, "<$file.orig"; + open NEW, ">$file"; + print NEW <<"EOF"; +/* $class Copyright (C) $years Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU$lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU$lesser General Public License + * along with this program; see the file COPYING$dotlesser. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * \$Id\$ + */ + +EOF + + while () { + /^package/ and last; + } + print NEW $_; + while () { + print NEW $_; + } +} + diff --git a/jode/scripts/createStackDelta.pl b/jode/scripts/createStackDelta.pl new file mode 100644 index 0000000..6fe0386 --- /dev/null +++ b/jode/scripts/createStackDelta.pl @@ -0,0 +1,134 @@ +#!/usr/bin/perl +# createStackDelta.pl Copyright (C) 1999 Jochen Hoenicke. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +# +# $Id$ + +# This perl script creates the stackDelta string needed by +# jode.bytecode.Instruction. + +my $OPCODEFILE="jode/bytecode/Opcodes.java"; +my @delta; +my $lastOpcode = 0; +my $nr; + +open OPCODES, "<$OPCODEFILE"; +while () { + next unless /opc_([a-z0-9_]+)\s*=\s*(\d+)/; + + $_ = $1; + $nr = $2; + if (/^(nop|iinc)$/) { + # no pop, no push + $delta[$nr] = "000"; + } elsif (/^([aif](const|load).*|[sb]ipush|ldc(_w)?)$/) { + # no pop, one push + $delta[$nr] = "010"; + } elsif (/^([ld](const|load).*|ldc2_w)$/) { + # no pop, two push + $delta[$nr] = "020"; + } elsif (/^([aif]store.*|pop)$/) { + # one pop, no push + $delta[$nr] = "001"; + } elsif (/^([ld]store.*|pop2)$/) { + # two pop, no push + $delta[$nr] = "002"; + } elsif (/^[aifbcs]aload$/) { + # two pop, one push + $delta[$nr] = "012"; + } elsif (/^[dl]aload$/) { + # two pop, two push + $delta[$nr] = "022"; + } elsif (/^[aifbcs]astore$/) { + # three pop, no push + $delta[$nr] = "003"; + } elsif (/^[dl]astore$/) { + # four pop, no push + $delta[$nr] = "004"; + } elsif (/^dup(2)?(_x([12]))?$/) { + $count = $1 ? 2 : 1; + $depth = $2 ? $3 : 0; + $pop = $count + $depth; + $push = $pop + $count; + $delta[$nr] = "0".$push.$pop; + } elsif (/^swap$/) { + # two pop, two push + $delta[$nr] = "022"; + } elsif (/^[if](add|sub|mul|div|rem|u?sh[lr]|and|or|xor)$/) { + # two pop, one push + $delta[$nr] = "012"; + } elsif (/^[ld](add|sub|mul|div|rem|and|or|xor)$/) { + # four pop, two push + $delta[$nr] = "024"; + } elsif (/^[if]neg$/) { + # one pop, one push + $delta[$nr] = "011"; + } elsif (/^[ld]neg$/) { + # two pop, two push + $delta[$nr] = "022"; + } elsif (/^lu?sh[lr]$/) { + # 3 pop, two push + $delta[$nr] = "023"; + } elsif (/^[if]2[ifbcs]$/) { + # one pop, one push + $delta[$nr] = "011"; + } elsif (/^[if]2[ld]$/) { + # one pop, two push + $delta[$nr] = "021"; + } elsif (/^[ld]2[if]$/) { + # two pop, one push + $delta[$nr] = "012"; + } elsif (/^[ld]2[ld]$/) { + # two pop, two push + $delta[$nr] = "022"; + } elsif (/^fcmp[lg]$/) { + $delta[$nr] = "012"; + } elsif (/^[ld]cmp[lg]?$/) { + $delta[$nr] = "014"; + } elsif (/^if_[ia]cmp(eq|ne|lt|ge|le|gt)$/) { + $delta[$nr] = "002"; + } elsif (/^(if(eq|ne|lt|ge|le|gt|(non)?null)|tableswitch|lookupswitch)$/) { + # order does matter + $delta[$nr] = "001"; + } elsif (/^(goto(_w)?|jsr(_w)?|ret|return)$/) { + $delta[$nr] = "000"; + } elsif (/^([ifa]return)$/) { + $delta[$nr] = "001"; + } elsif (/^([ld]return)$/) { + $delta[$nr] = "002"; + } elsif (/^(new)$/) { + $delta[$nr] = "010"; + } elsif (/^(multianewarray|(get|put|invoke).*)$/) { + # unknown + $delta[$nr] = "100"; + } elsif (/^(athrow|monitor(enter|exit))$/) { + $delta[$nr] = "001"; + } elsif (/^(a?newarray|arraylength|checkcast|instanceof)$/) { + $delta[$nr] = "011"; + } else { + # illegal + next; + } + $lastOpcode = $nr + if ($nr > $lastOpcode); +} + +print " private final static String stackDelta = \n\t\""; +for ($nr = 0; $nr <= $lastOpcode; $nr ++) { + defined $delta[$nr] or $delta[$nr] = "177"; + print "\\$delta[$nr]"; +} +print "\";\n"; diff --git a/jode/scripts/javaDependencies.pl b/jode/scripts/javaDependencies.pl new file mode 100755 index 0000000..e92f592 --- /dev/null +++ b/jode/scripts/javaDependencies.pl @@ -0,0 +1,184 @@ +#!/usr/bin/perl -s -w +# +# javaDependencies Copyright (C) 1999 Jochen Hoenicke. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +# +# $Id$ + +# This scripts create Makefile dependencies out of class files. It +# simply scans the constant pool of the class files, finding all +# references to other classes and adding a dependency to that class. +# +# It doesn't do a perfect job, since it can't handle dependencies to +# constant values in different classes: The compiler inlines the +# constant and thus doesn't include a reference to the class. +# +# Usage: +# javaDependencies.pl -classpath [-dependdir [-subdir ]] +# [-depfile ] +# +# +# cp: colon separated paths to the java files we should depend on. +# depdir: if set, use this path as path to the java files when printing +# dependencies, not the path where the java files were found. +# useful, if you want to make use of VPATH settings in Makefile. +# subdir: if set, this is the path from depdir to the current directory. +# Use it to remove unneccessary ../../$subdir/ +# depfile: the name of the dependency file, default is "Makefile.dep". +# class: The class files (not inner classes) for which the dependencies +# should be generated. We will also look for inner and anon +# classes. + +my $buff; + +sub readInBuff ($) { + my $count = $_[0]; + my $offset = 0; + while ($count > 0) { + my $result; + $result = read FILE, $buff, $count, $offset or return 0; + $offset += $result; + $count -= $result; + } + $offset; +} + +sub readUTF () { + readInBuff 2 or die "Can't read UTF8 length"; + my $ulength = unpack("n", $buff) & 0xffff; + return "" if $ulength == 0; + readInBuff $ulength or die "Can't read UTF8 string $ulength"; + unpack("a$ulength", $buff); +} + +$depfile = "Makefile.dep" if (!defined($depfile)); +open DEPFILE, ">$depfile"; +print DEPFILE <) { + $currsubdir = $2; + my $firstcomp = $1; + if ($clzz =~ m<$firstcomp/(.*)>) { + my $remain = $1; + if ($path =~ m<^(|.*/)\.\./+$>) { + $path = $1; + $clzz = $remain; + } + } + } + } + } + push @deplist, "$path$clzz.java"; + next clazz; + } + } + } + if (@deplist) { + print DEPFILE "$clazz: " . join (" ", @deplist) . "\n"; + } + } +} +close DEPFILE; diff --git a/jode/scripts/jcpp.pl b/jode/scripts/jcpp.pl new file mode 100755 index 0000000..91e7b7b --- /dev/null +++ b/jode/scripts/jcpp.pl @@ -0,0 +1,234 @@ +#!/usr/bin/perl -w +# +# jcpp Copyright (C) 1999-2001 Jochen Hoenicke. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +# +# $Id$ + +# This is a program to allow conditional compiled code in java files. +# The key idea is, not to run the file always through the +# preprocessor, but to modify the java files directly and make use of +# comments. +# +# The comments all have the form /// and start at the beginning of the +# line to distinguish them from normal comments. You must not use +# such comments for other purposes. +# +# Usage is simple: jcpp -Ddefine1 -Ddefine2 first.java second.java +# The files should contain comments of the form +# +# ///#ifdef JDK12 +# jdk1.2 code +# ///#else +# jdk1.1 code +# ///#endif +# +# After running jcpp the false branch is commented out. If the true +# branch was commented out it will get commented in. +# +# jcpp can also change definitions, useful for package renaming. The +# java file should look like this: +# +# ///#def COLLECTIONS java.util +# import java.util.Vector +# import java.util.ArrayList +# ///#enddef +# +# If jcpp is then called with -DCOLLECTIONS=gnu.java.util.collections +# it will replace every occurence of java.util (the string in the #def +# line) with the new value: +# +# ///#def COLLECTIONS gnu.java.util.collections +# import gnu.java.util.collections.Vector +# import gnu.java.util.collections.ArrayList +# ///#enddef + +my @files; +my %defs; + +for (@ARGV) { + if ($_ =~ /^-D([^=]*)$/) { + $defs{$1} = 1; + } elsif ($_ =~ /^-D([^=]*)=([^=]*)$/) { + $defs{$1} = $2; + } else { + push @files, $_; + } +} + +for (@files) { + # Number of nested #if directives. Initially 0, will be increased + # on every #if directive and decreased on every #endif directive. + my $level = 0; + + # The number of the outermost level, whose #if directive was + # false. This is 0, if there wasn't an false #if directive, yet. + # As long as it is != 0, we comment every line, and ignore + # directives except for increasing/decreasing $level. + my $falselevel = 0; + + # Tells if an error occured and the transformation shouldn't + # be done. + my $error = 0; + my $changes = 0; + + # The list of #def replacements, @replold is the previous value, + # @replnew the new one. + my @replold = (); + my @replnew = (); + + my $file = $_; + open OLD, "<$file" or do { + print STDERR "Can't open file $file\n"; + next; + }; + open NEW, ">$file.tmp" or do { + print STDERR "Can't open tmp file $file.tmp\n"; + next; + }; + my $linenr = 0; + LINE: + while () { + $linenr++; + if (m'^///#') { + # This is a directive. First we print it out. + if (m'^///#\s*if') { + $level++; + if (m'^///#\s*ifdef\s+(\w+)\s*$') { + # If there was an outer false #if directive, we ignore the + # condition. + next LINE if ($falselevel); + + my $label=$1; + + # An ifdef directive, look if -D is defined. + $falselevel = $level + unless (defined $defs{$label}); + } elsif (m'^///#\s*ifndef\s+(\w+)\s*$') { + # If there was an outer false #if directive, we ignore the + # condition. + next LINE if ($falselevel); + + my $label=$1; + # An ifndef directive, look if -D is defined + $falselevel = $level + if (defined $defs{$label}); + } elsif (m'^///#\s*if\s+(\w+)\s*(==|!=)\s*(\S+)\s*$') { + # If there was an outer false #if directive, we ignore the + # condition. + next LINE if ($falselevel); + + my $label=$1; + my $value=$3; + + # An ifdef directive, look if -D is defined. + $falselevel = $level + unless ($2 eq "==" ? $defs{$label} eq $value + : $defs{$label} ne $value); + } elsif (m'^///#\s*if\s+(\w+)\s*(>=|<=|>|<)\s*(\S+)\s*$') { + # If there was an outer false #if directive, we ignore the + # condition. + next LINE if ($falselevel); + + my $label=$1; + my $value=$3; + + # An ifdef directive, look if -D is defined. + $falselevel = $level + unless ($2 eq ">=" ? $defs{$label} >= $value + : $2 eq "<=" ? $defs{$label} <= $value + : $2 eq ">" ? $defs{$label} > $value + : $defs{$label} < $value); + } + } elsif (m'^///#\s*else\s*$') { + # An else directive. We switch from true to false and + # if level is falselevel we switch from false to true + if ($level == 0) { + # An else outside of any directives; warn. + print STDERR "$file: $linenr: unmatched $_"; + $error = 1; + } elsif ($falselevel == $level) { + $falselevel = 0; + } elsif ($falselevel == 0) { + $falselevel = $level; + } + } elsif (m'^///#\s*endif\s*$') { + # set $falselevel to 0, if the false branch is over now. + $falselevel = 0 if ($falselevel == $level); + # decrease level. + if ($level == 0) { + print STDERR "$file: $linenr: unmatched $_"; + $error = 1; + } else { + $level--; + } + } elsif (m'^///#\s*def\s+(\w+)\s+(\S*)$') { + my $label = $1; + my $old = $2; + my $new = $defs{$label}; + if (defined $new && $new ne $old) { + push @replold, "$old"; + push @replnew, "$new"; + $changes = 1; + } else { + push @replnew, ""; + push @replold, ""; + } + } elsif (m'^///#\s*enddef\s*$') { + pop @replold; + pop @replnew; + } else { + print STDERR "$file: $linenr: ignoring unknown directive $_"; + $error = 1; + } + } elsif (m'^///(.*)') { + $line = $1; + if ($falselevel == 0 && $level > 0) { + # remove comments in true branch, but not in outermost level. + $_ = "$line\n"; + $changes = 1; + } + } else { + if ($falselevel != 0) { + # add comments in false branch + $_ = "///$_"; + $changes = 1; + } + } + for ($i = 0; $i < @replold; $i++) { + $_ =~ s/\Q$replold[$i]\E/$replnew[$i]/ if ($replold[$i] ne ""); + } + print NEW $_; + } + + if ($level != 0 || $falselevel != 0) { + # something got wrong + print STDERR "$file: unmatched directives: level $level, ". + "falselevel $falselevel\n"; + $error = 1; + } + + if ($error == 0) { + if ($changes == 0) { + unlink "$file.tmp"; + } else { + (unlink "$file" and rename "$file.tmp", "$file") + or print STDERR "$file: Couldn't rename files.\n"; + } + } else { + print STDERR "$file: errors occured, file not transformed.\n"; + } +} diff --git a/jode/scripts/php2html.pl b/jode/scripts/php2html.pl new file mode 100644 index 0000000..10a670e --- /dev/null +++ b/jode/scripts/php2html.pl @@ -0,0 +1,15 @@ +#!/usr/bin/perl -w + +my $num = 0; +for (@ARGV) { + next if $_ !~ /\.php$/; + $html = $php = $_; + $html =~ s/\.php$/.html/; + + $ENV{extension}="html"; + if (! -e "$html" || (-M "$php" <= -M "$html")) { + $num++; + system "php -f $php >$html"; + } +} +print $num . " html files updated.\n"; diff --git a/jode/src/net/sf/jode/GlobalOptions.java b/jode/src/net/sf/jode/GlobalOptions.java new file mode 100644 index 0000000..39a0e93 --- /dev/null +++ b/jode/src/net/sf/jode/GlobalOptions.java @@ -0,0 +1,107 @@ +/* GlobalOptions Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode; +import java.io.PrintWriter; +import java.util.StringTokenizer; + +public class GlobalOptions { + public final static String version = "@VERSION@"; + public final static String email = "jochen@gnu.org"; + public final static String copyright = + "Jode (c) 1998-2004 Jochen Hoenicke <"+email+">"; + public final static String URL = "http://jode.sourceforge.net/"; + + public static PrintWriter err = new PrintWriter(System.err, true); + public static int verboseLevel = 0; + public static int debuggingFlags = 0; + + public static final int DEBUG_BYTECODE = 0x001; + public static final int DEBUG_VERIFIER = 0x002; + public static final int DEBUG_TYPES = 0x004; + public static final int DEBUG_FLOW = 0x008; + public static final int DEBUG_INOUT = 0x010; + public static final int DEBUG_ANALYZE = 0x020; + public static final int DEBUG_LVT = 0x040; + public static final int DEBUG_CHECK = 0x080; + public static final int DEBUG_LOCALS = 0x100; + public static final int DEBUG_CONSTRS = 0x200; + public static final int DEBUG_INTERPRT = 0x400; + + public static final String[] debuggingNames = { + "bytecode", "verifier", "types", "flow", + "inout", "analyze", "lvt", "check", "locals", + "constructors", "interpreter" + }; + + public static void usageDebugging() { + err.println("Debugging option: --debug=flag1,flag2,..."); + err.println("possible flags:"); + err.println(" bytecode " + + "show bytecode, as it is read from class file."); + err.println(" verifier " + + "show result of bytecode verification."); + err.println(" types " + + "show type intersections"); + err.println(" flow " + + "show flow block merging."); + err.println(" analyze " + + "show T1/T2 analyzation of flow blocks."); + err.println(" inout " + + "show in/out set analysis."); + err.println(" lvt " + + "dump LocalVariableTable."); + err.println(" check " + + "do time consuming sanity checks."); + err.println(" locals " + + "dump local merging information."); + err.println(" constructors " + + "dump constructor simplification."); + err.println(" interpreter " + + "debug execution of interpreter."); + System.exit(0); + } + + /** + * Parse the argument given to the debugging flag. + * @exception IllegalArgumentException + * if a problem occured while parsing the argument. + */ + public static boolean setDebugging(String debuggingString) { + if (debuggingString.length() == 0 || debuggingString.equals("help")) { + usageDebugging(); + throw new IllegalArgumentException(); + } + + StringTokenizer st = new StringTokenizer(debuggingString, ","); + next_token: + while (st.hasMoreTokens()) { + String token = st.nextToken().intern(); + for (int i=0; i info.addr) { + InstrInfo catcher = infos[handlers[i].catcher]; + if ((catcher.flags & IS_REACHABLE) == 0) { + catcher.flags |= IS_REACHABLE; + catcher.stack = 1; + todo.push(catcher); + } + } + } + + InstrInfo prevInfo = null; + // Search the end of the block and calculate next stack depth + while (true) { + info.instr.getStackPopPush(poppush); + stack += poppush[1] - poppush[0]; + if (stack < 0) { + throw new ClassFormatException + ("Pop from empty stack: " + bb); + } + + if (!info.instr.doesAlwaysJump() + && info.succs == null + && (infos[info.nextAddr].flags & IS_BORDER) == 0) { + prevInfo = info; + try { + info = infos[info.nextAddr]; + } catch (ArrayIndexOutOfBoundsException ex) { + throw new ClassFormatException + ("Flow falls out of method " + bb); + } + } else + break; + } + + if (info.instr.getOpcode() == opc_goto + || (info.instr.getOpcode() == opc_return + && stack == 0)) { + /* If list is a goto or return, we step an instruction + * back. We don't need to modify stack, since goto and + * return are neutral. + */ + info = prevInfo; + } + } + + /* mark successors as reachable */ + int[] succs = info.succs; + if (succs != null) { + for (int i=0; i < succs.length; i++) { + InstrInfo succ = infos[succs[i]]; + if ((succ.flags & IS_REACHABLE) == 0) { + succ.flags |= IS_REACHABLE; + int succstack = stack; + if (info.instr.getOpcode() == opc_jsr) + succstack++; + if (succ.stack < 0) + succ.stack = succstack; + else if (succ.stack != succstack) + throw new ClassFormatException + ("Stack height varies: "+bb+":"+succs[i]); + todo.push(succ); + } + } + } + if (info.nextAddr < infos.length) + infos[info.nextAddr].flags |= IS_BORDER; + + if (!info.instr.doesAlwaysJump()) { + InstrInfo succ = infos[info.nextAddr]; + if ((succ.flags & IS_REACHABLE) == 0) { + succ.flags |= IS_REACHABLE; + if (succ.stack < 0) + succ.stack = stack; + else if (succ.stack != stack) + throw new ClassFormatException + ("Stack height varies: "+bb+":"+info.nextAddr); + todo.push(succ); + } + } + } + } + + private int getSuccBlockNr(int succAddr) { + InstrInfo succ = infos[succAddr]; + while ((succ.flags & IS_FORWARD) != 0) { + switch (succ.instr.getOpcode()) { + case opc_goto: + succ = infos[succ.succs[0]]; + break; + case opc_return: + return -1; + default: + throw new IllegalStateException(); + } + } + return succ.blockNr; + } + + private Block getSuccBlock(int succAddr) { + int nr = getSuccBlockNr(succAddr); + return nr == -1 ? null : blocks[nr]; + } + + + private Handler[] convertHandlers() { + int newCount = 0; + for (int i=0; i < handlers.length; i++) { + while (handlers[i].start < handlers[i].end + && infos[handlers[i].start].blockNr == -1) + handlers[i].start = infos[handlers[i].start].nextAddr; + if (handlers[i].start == handlers[i].end) + continue; + + while (handlers[i].end < infos.length + && infos[handlers[i].end].blockNr == -1) + handlers[i].end = infos[handlers[i].end].nextAddr; + if ((infos[handlers[i].catcher].flags & IS_REACHABLE) != 0) + newCount++; + } + Handler[] newHandlers = new Handler[newCount]; + int ptr = 0; + for (int i=0; i 0) + info = infos[info.nextAddr]; + instrs[i] = info.instr; + } + + int[] lastSuccs = info.succs; + int succLength = lastSuccs != null ? lastSuccs.length : 0; + boolean alwaysJump = info.instr.doesAlwaysJump(); + + Block[] succs = new Block[succLength + (alwaysJump ? 0 : 1)]; + for (int i=0; i < succLength; i++) + succs[i] = getSuccBlock(lastSuccs[i]); + if (!alwaysJump) + succs[succLength] = getSuccBlock(info.nextAddr); + + blocks[blockNr].setCode(instrs, succs); + } + + void convert() throws ClassFormatException { + markReachableBlocks(); + + int blockCount = 0; + /* Count the blocks */ + for (int i=0; i< infos.length; i = infos[i].nextAddr) { + if ((infos[i].flags & (IS_REACHABLE | IS_FORWARD)) == IS_REACHABLE) + infos[i].blockNr = blockCount++; + } + + blocks = new Block[blockCount]; + for (int i=0; i< blocks.length; i++) + blocks[i] = new Block(); + + int start = -1; + int count = 0; + for (int i = 0; i < infos.length; i = infos[i].nextAddr) { + InstrInfo info = infos[i]; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) { + if ((info.flags & IS_BORDER) != 0) + GlobalOptions.err.println + (""+info.addr+": "+info.flags+","+info.blockNr+";"+info.stack); + } + if ((info.flags & IS_BORDER) != 0) { + if (start != -1) + convertBlock(start, count); + start = -1; + } + if ((info.flags & (IS_REACHABLE | IS_FORWARD)) == IS_REACHABLE) { + if ((info.flags & IS_NULL) != 0) { + convertBlock(i, 0); + } else { + start = i; + count = 0; + } + } + if (start != -1) + count++; + } + if (start != -1) + convertBlock(start, count); + bb.setBlocks(blocks, getSuccBlock(0), convertHandlers()); + if (bb.maxStack > maxStack) + throw new ClassFormatException("Only allocated "+maxStack + +" stack slots for method, needs " + +bb.maxStack); + if (bb.maxLocals > maxLocals) + throw new ClassFormatException("Only allocated "+maxLocals + +" local slots for method, needs " + +bb.maxLocals); + } + + public void readCode(ConstantPool cp, + DataInputStream input) throws IOException { + maxStack = input.readUnsignedShort(); + maxLocals = input.readUnsignedShort(); + + int codeLength = input.readInt(); + infos = new InstrInfo[codeLength]; + { + int addr = 0; + while (addr < codeLength) { + Instruction instr; + int length; + + infos[addr] = new InstrInfo(); + int opcode = input.readUnsignedByte(); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(addr+": "+opcodeString[opcode]); + + switch (opcode) { + case opc_wide: { + int wideopcode = input.readUnsignedByte(); + switch (wideopcode) { + case opc_iload: case opc_fload: case opc_aload: + case opc_istore: case opc_fstore: case opc_astore: { + int slot = input.readUnsignedShort(); + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction(wideopcode, lvi); + length = 4; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print + (" " + opcodeString[wideopcode] + " " + slot); + break; + } + case opc_lload: case opc_dload: + case opc_lstore: case opc_dstore: { + int slot = input.readUnsignedShort(); + if (slot >= maxLocals-1) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction(wideopcode, lvi); + length = 4; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print + (" " + opcodeString[wideopcode] + " " + slot); + break; + } + case opc_ret: { + int slot = input.readUnsignedShort(); + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction(wideopcode, lvi); + length = 4; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" ret "+slot); + break; + } + case opc_iinc: { + int slot = input.readUnsignedShort(); + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + int incr = input.readShort(); + instr = new IncInstruction(wideopcode, lvi, incr); + length = 6; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print + (" iinc " + slot + " " + instr.getIncrement()); + break; + } + default: + throw new ClassFormatException("Invalid wide opcode " + +wideopcode); + } + break; + } + case opc_iload_0: case opc_iload_1: + case opc_iload_2: case opc_iload_3: + case opc_lload_0: case opc_lload_1: + case opc_lload_2: case opc_lload_3: + case opc_fload_0: case opc_fload_1: + case opc_fload_2: case opc_fload_3: + case opc_dload_0: case opc_dload_1: + case opc_dload_2: case opc_dload_3: + case opc_aload_0: case opc_aload_1: + case opc_aload_2: case opc_aload_3: { + int slot = (opcode-opc_lload_0) & 3; + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction + (opc_iload + (opcode-opc_iload_0)/4, lvi); + length = 1; + break; + } + case opc_istore_0: case opc_istore_1: + case opc_istore_2: case opc_istore_3: + case opc_fstore_0: case opc_fstore_1: + case opc_fstore_2: case opc_fstore_3: + case opc_astore_0: case opc_astore_1: + case opc_astore_2: case opc_astore_3: { + int slot = (opcode-opc_istore_0) & 3; + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction + (opc_istore + (opcode-opc_istore_0)/4, lvi); + length = 1; + break; + } + case opc_lstore_0: case opc_lstore_1: + case opc_lstore_2: case opc_lstore_3: + case opc_dstore_0: case opc_dstore_1: + case opc_dstore_2: case opc_dstore_3: { + int slot = (opcode-opc_lstore_0) & 3; + if (slot >= maxLocals-1) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction + (opc_lstore + (opcode-opc_lstore_0)/4, lvi); + length = 1; + break; + } + case opc_iload: case opc_fload: case opc_aload: + case opc_istore: case opc_fstore: case opc_astore: { + int slot = input.readUnsignedByte(); + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction(opcode, lvi); + length = 2; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+slot); + break; + } + case opc_lstore: case opc_dstore: + case opc_lload: case opc_dload: { + int slot = input.readUnsignedByte(); + if (slot >= maxLocals - 1) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction(opcode, lvi); + length = 2; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+slot); + break; + } + case opc_ret: { + int slot = input.readUnsignedByte(); + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + instr = new SlotInstruction(opcode, lvi); + length = 2; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+slot); + break; + } + case opc_aconst_null: + case opc_iconst_m1: + case opc_iconst_0: case opc_iconst_1: case opc_iconst_2: + case opc_iconst_3: case opc_iconst_4: case opc_iconst_5: + case opc_fconst_0: case opc_fconst_1: case opc_fconst_2: + instr = new ConstantInstruction + (opc_ldc, constants[opcode - opc_aconst_null]); + length = 1; + break; + case opc_lconst_0: case opc_lconst_1: + case opc_dconst_0: case opc_dconst_1: + instr = new ConstantInstruction + (opc_ldc2_w, constants[opcode - opc_aconst_null]); + length = 1; + break; + case opc_bipush: + instr = new ConstantInstruction + (opc_ldc, new Integer(input.readByte())); + length = 2; + break; + case opc_sipush: + instr = new ConstantInstruction + (opc_ldc, new Integer(input.readShort())); + length = 3; + break; + case opc_ldc: { + int index = input.readUnsignedByte(); + int tag = cp.getTag(index); + if (tag != cp.STRING && tag != cp.CLASS + && tag != cp.INTEGER && tag != cp.FLOAT) + throw new ClassFormatException + ("wrong constant tag: "+tag); + instr = new ConstantInstruction + (opc_ldc, cp.getConstant(index)); + length = 2; + break; + } + case opc_ldc_w: { + int index = input.readUnsignedShort(); + int tag = cp.getTag(index); + if (tag != cp.STRING && tag != cp.CLASS + && tag != cp.INTEGER && tag != cp.FLOAT) + throw new ClassFormatException + ("wrong constant tag: "+tag); + instr = new ConstantInstruction + (opc_ldc, cp.getConstant(index)); + length = 3; + break; + } + case opc_ldc2_w: { + int index = input.readUnsignedShort(); + int tag = cp.getTag(index); + if (tag != cp.LONG && tag != cp.DOUBLE) + throw new ClassFormatException + ("wrong constant tag: "+tag); + instr = new ConstantInstruction + (opc_ldc2_w, cp.getConstant(index)); + length = 3; + break; + } + case opc_iinc: { + int slot = input.readUnsignedByte(); + if (slot >= maxLocals) + throw new ClassFormatException + ("Invalid local slot "+slot); + LocalVariableInfo lvi + = LocalVariableInfo.getInfo(slot); + int incr = input.readByte(); + instr = new IncInstruction(opcode, lvi, incr); + length = 3; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print + (" " + slot + " " + instr.getIncrement()); + break; + } + case opc_goto: + case opc_jsr: + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: + instr = new Instruction(opcode); + length = 3; + infos[addr].succs = new int[] { addr+input.readShort() }; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+infos[addr].succs[0]); + break; + + case opc_goto_w: + case opc_jsr_w: + instr = new Instruction(opcode - (opc_goto_w - opc_goto)); + length = 5; + infos[addr].succs = new int[] { addr+input.readInt() }; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+infos[addr].succs[0]); + break; + + case opc_tableswitch: { + length = 3 - (addr % 4); + input.readFully(new byte[length]); + int def = input.readInt(); + int low = input.readInt(); + int high = input.readInt(); + int[] dests = new int[high-low+1]; + int npairs = 0; + for (int i=0; i < dests.length; i++) { + dests[i] = input.readInt(); + if (dests[i] != def) + npairs++; + } + infos[addr].succs = new int[npairs + 1]; + int[] values = new int[npairs]; + int pos = 0; + for (int i=0; i < dests.length; i++) { + if (dests[i] != def) { + values[pos] = i+low; + infos[addr].succs[pos] = addr + dests[i]; + pos++; + } + } + infos[addr].succs[npairs] = addr + def; + instr = new SwitchInstruction(opc_lookupswitch, values); + length += 13 + 4 * (high-low+1); + break; + } + case opc_lookupswitch: { + length = 3 - (addr % 4); + input.readFully(new byte[length]); + int def = input.readInt(); + int npairs = input.readInt(); + infos[addr].succs = new int[npairs + 1]; + int[] values = new int[npairs]; + for (int i=0; i < npairs; i++) { + values[i] = input.readInt(); + if (i > 0 && values[i-1] >= values[i]) + throw new ClassFormatException + ("lookupswitch not sorted"); + infos[addr].succs[i] = addr + input.readInt(); + } + infos[addr].succs[npairs] = addr + def; + instr = new SwitchInstruction(opc_lookupswitch, values); + length += 9 + 8 * npairs; + break; + } + + case opc_getstatic: + case opc_getfield: + case opc_putstatic: + case opc_putfield: + case opc_invokespecial: + case opc_invokestatic: + case opc_invokevirtual: { + int index = input.readUnsignedShort(); + int tag = cp.getTag(index); + if (opcode < opc_invokevirtual) { + if (tag != cp.FIELDREF) + throw new ClassFormatException + ("field tag mismatch: "+tag); + } else { + if (tag != cp.METHODREF) + throw new ClassFormatException + ("method tag mismatch: "+tag); + } + Reference ref = cp.getRef(index); + if (ref.getName().charAt(0) == '<' + && (!ref.getName().equals("") + || opcode != opc_invokespecial)) + throw new ClassFormatException + ("Illegal call of special method/field "+ref); + instr = new ReferenceInstruction(opcode, ref); + length = 3; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+ref); + break; + } + case opc_invokeinterface: { + int index = input.readUnsignedShort(); + int tag = cp.getTag(index); + if (tag != cp.INTERFACEMETHODREF) + throw new ClassFormatException + ("interface tag mismatch: "+tag); + Reference ref = cp.getRef(index); + if (ref.getName().charAt(0) == '<') + throw new ClassFormatException + ("Illegal call of special method "+ref); + int nargs = input.readUnsignedByte(); + if (TypeSignature.getParameterSize(ref.getType()) + != nargs - 1) + throw new ClassFormatException + ("Interface nargs mismatch: "+ref+" vs. "+nargs); + if (input.readUnsignedByte() != 0) + throw new ClassFormatException + ("Interface reserved param not zero"); + + instr = new ReferenceInstruction(opcode, ref); + length = 5; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+ref); + break; + } + + case opc_new: + case opc_checkcast: + case opc_instanceof: { + String type = cp.getClassType(input.readUnsignedShort()); + if (opcode == opc_new && type.charAt(0) == '[') + throw new ClassFormatException + ("Can't create array with opc_new"); + instr = new TypeInstruction(opcode, type); + length = 3; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+type); + break; + } + case opc_multianewarray: { + String type = cp.getClassType(input.readUnsignedShort()); + int dims = input.readUnsignedByte(); + if (dims == 0) + throw new ClassFormatException + ("multianewarray dimension is 0."); + for (int i=0; i < dims; i++) { + /* Note that since type is a valid type + * signature, there must be a non bracket + * character, before the string is over. + * So there is no StringIndexOutOfBoundsException. + */ + if (type.charAt(i) != '[') + throw new ClassFormatException + ("multianewarray called for non array:"+ type); + } + instr = new TypeDimensionInstruction(opcode, type, dims); + length = 4; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" " + type + " " + dims); + break; + } + case opc_anewarray: { + String type + = "["+cp.getClassType(input.readUnsignedShort()); + instr = new TypeDimensionInstruction + (opc_multianewarray, type.intern(), 1); + length = 3; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+type); + break; + } + case opc_newarray: { + char sig = newArrayTypes.charAt + (input.readUnsignedByte()-4); + String type = new String (new char[] { '[', sig }); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.print(" "+type); + instr = new TypeDimensionInstruction + (opc_multianewarray, type.intern(), 1); + length = 2; + break; + } + + case opc_nop: + case opc_iaload: case opc_laload: case opc_faload: + case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iastore: case opc_lastore: case opc_fastore: + case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: + case opc_pop: case opc_pop2: + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + case opc_swap: + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: + case opc_iand: case opc_land: + case opc_ior: case opc_lor: + case opc_ixor: case opc_lxor: + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_lcmp: case opc_fcmpl: case opc_fcmpg: + case opc_dcmpl: case opc_dcmpg: + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: + case opc_return: + case opc_athrow: + case opc_arraylength: + case opc_monitorenter: case opc_monitorexit: + instr = new Instruction(opcode); + length = 1; + break; + default: + throw new ClassFormatException("Invalid opcode "+opcode); + } + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.println(); + + infos[addr].instr = instr; + infos[addr].addr = addr; + infos[addr].nextAddr = addr + length; + if (addr + length == codeLength && !instr.doesAlwaysJump()) + throw new ClassFormatException + ("Flow falls out of method " + bb); + addr += length; + } + if (addr != codeLength) + throw new ClassFormatException("last instruction too long"); + } + + int handlersLength = input.readUnsignedShort(); + handlers = new HandlerEntry[handlersLength]; + for (int i=0; i< handlersLength; i ++) { + handlers[i] = new HandlerEntry(); + handlers[i].start = input.readUnsignedShort(); + handlers[i].end = input.readUnsignedShort(); + handlers[i].catcher = input.readUnsignedShort(); + int index = input.readUnsignedShort(); + handlers[i].type = (index == 0) ? null + : cp.getClassName(index); + + if (i > 0 && handlers[i].start == handlers[i-1].end + && handlers[i].catcher == handlers[i-1].catcher + && handlers[i].type == handlers[i-1].type) { + /* Javac 1.4 splits handlers at return instruction + * (see below). We merge them together here. + */ + handlers[i-1].end = handlers[i].end; + handlersLength--; + i--; + } + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.println("Handler "+handlers[i].start + +"-"+handlers[i].end + +" @"+handlers[i].catcher + + ": "+handlers[i].type); + + if (infos[handlers[i].catcher].instr.getOpcode() == opc_athrow) { + /* There is an obfuscator, which inserts bogus + * exception entries jumping directly to a throw + * instruction. Remove those handlers. + */ + handlersLength--; + i--; + continue; + } + + if (handlers[i].start <= handlers[i].catcher + && handlers[i].end > handlers[i].catcher) + { + /* Javac 1.4 is a bit paranoid with finally and + * synchronize blocks and even breaks the JLS. + * We fix it here. Hopefully this won't produce + * any other problems. + */ + if (handlers[i].start == handlers[i].catcher) { + handlersLength--; + i--; + continue; + } else { + handlers[i].end = handlers[i].catcher; + } + } + + if (infos[handlers[i].end].instr.getOpcode() >= opc_ireturn + && infos[handlers[i].end].instr.getOpcode() <= opc_return) { + /* JDK 1.4 sometimes doesn't put return instruction into try + * block, which breaks the decompiler later. The return + * instruction can't throw exceptions so it doesn't really + * matter. + * + * FIXME: This may break other things if the return + * instruction is reachable from outside the try block. + */ + handlers[i].end++; + } + } + if (handlersLength < handlers.length) { + HandlerEntry[] newHandlers = new HandlerEntry[handlersLength]; + System.arraycopy(handlers, 0, newHandlers, 0, + handlersLength); + handlers = newHandlers; + } + + for (int i=0; i< infos.length; i++) { + if (infos[i] != null && infos[i].succs != null) { + int[] succs = infos[i].succs; + for (int j=0; j < succs.length; j++) { + try { + infos[succs[j]].flags |= IS_BORDER; + } catch (RuntimeException ex) { + throw new ClassFormatException + ("Illegal successor: " + bb+":"+i); + } + } + } + } + + for (int i=0; i< handlersLength; i ++) { + /* Mark the instructions as border instructions. + * reachable. + */ + infos[handlers[i].start].flags |= IS_BORDER; + if (handlers[i].end < infos.length) + infos[handlers[i].end].flags |= IS_BORDER; + infos[handlers[i].catcher].flags |= IS_BORDER | IS_CATCHER; + } + } + + public void readLVT(int length, ConstantPool cp, + DataInputStream input) throws IOException { + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_LVT) != 0) + GlobalOptions.err.println("LocalVariableTable of "+bb); + int count = input.readUnsignedShort(); + if (length != 2 + count * 10) { + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_LVT) != 0) + GlobalOptions.err.println("Illegal LVT length, ignoring it"); + return; + } + Vector[] lvt = new Vector[maxLocals]; + for (int i=0; i < count; i++) { + LVTEntry lve = new LVTEntry(); + lve.start = input.readUnsignedShort(); + lve.end = lve.start + input.readUnsignedShort(); + int nameIndex = input.readUnsignedShort(); + int typeIndex = input.readUnsignedShort(); + int slot = input.readUnsignedShort(); + if (nameIndex == 0 || cp.getTag(nameIndex) != cp.UTF8 + || typeIndex == 0 || cp.getTag(typeIndex) != cp.UTF8 + || slot >= maxLocals) { + + // This is probably an evil lvt as created by HashJava + // simply ignore it. + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_LVT) != 0) + GlobalOptions.err.println + ("Illegal entry, ignoring LVT"); + lvt = null; + return; + } + lve.name = cp.getUTF8(nameIndex); + lve.type = cp.getUTF8(typeIndex); + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_LVT) != 0) + GlobalOptions.err.println("\t" + lve.name + ": " + + lve.type + +" range "+lve.start + +" - "+lve.end + +" slot "+slot); + if (lvt[slot] == null) + lvt[slot] = new Vector(); + lvt[slot].addElement(lve); + } + for (int i = 0; i< infos.length; i = infos[i].nextAddr) { + Instruction instr = infos[i].instr; + if (instr.hasLocal()) { + LocalVariableInfo lvi = instr.getLocalInfo(); + int slot = lvi.getSlot(); + if (lvt[slot] == null) + continue; + int addr = i; + if (instr.getOpcode() >= opc_istore + && instr.getOpcode() <= opc_astore) + addr = infos[i].nextAddr; + + Enumeration enumeration = lvt[slot].elements(); + LVTEntry match = null; + while (enumeration.hasMoreElements()) { + LVTEntry lve = (LVTEntry) enumeration.nextElement(); + if (lve.start <= addr && lve.end > addr) { + if (match != null + && (!match.name.equals(lve.name) + || !match.type.equals(lve.type))) { + /* Multiple matches..., give no info */ + match = null; + break; + } + match = lve; + } + } + if (match != null) + instr.setLocalInfo(LocalVariableInfo + .getInfo(slot, match.name, match.type)); + } + } + + int paramCount = bb.getParamCount(); + for (int slot=0; slot< paramCount; slot++) { + if (lvt[slot] == null) + continue; + Enumeration enumeration = lvt[slot].elements(); + LVTEntry match = null; + while (enumeration.hasMoreElements()) { + LVTEntry lve = (LVTEntry) enumeration.nextElement(); + if (lve.start == 0) { + if (match != null + && (!match.name.equals(lve.name) + || !match.type.equals(lve.type))) { + /* Multiple matches..., give no info */ + match = null; + break; + } + match = lve; + } + } + if (match != null) { + bb.setParamInfo(LocalVariableInfo + .getInfo(slot, match.name, match.type)); + } + } + } + + public void readLNT(int length, ConstantPool cp, + DataInputStream input) throws IOException { + int count = input.readUnsignedShort(); + if (length != 2 + count * 4) { + GlobalOptions.err.println + ("Illegal LineNumberTable, ignoring it"); + return; + } + for (int i = 0; i < count; i++) { + int start = input.readUnsignedShort(); + infos[start].instr.setLineNr(input.readUnsignedShort()); + } + + int lastLine = -1; + for (int i = 0; i< infos.length; i = infos[i].nextAddr) { + Instruction instr = infos[i].instr; + if (instr.hasLineNr()) + lastLine = instr.getLineNr(); + else + instr.setLineNr(lastLine); + } + } +} + diff --git a/jode/src/net/sf/jode/bytecode/BasicBlockWriter.java b/jode/src/net/sf/jode/bytecode/BasicBlockWriter.java new file mode 100644 index 0000000..08de109 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/BasicBlockWriter.java @@ -0,0 +1,1011 @@ +/* BasicBlockWriter Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.BitSet; +import java.util.Stack; +///#def COLLECTIONS java.util +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +///#enddef + +/** + * This is a helper class, that contains the method to write basic + * blocks to a class file. + */ +class BasicBlockWriter implements Opcodes { + + private class LVTEntry { + int startAddr, endAddr; + Instruction start, end; + LocalVariableInfo lvi; + } + + BasicBlocks bb; + int[] blockAddr; + int[][] instrLength; + + int lntCount; + short[] lnt; + LVTEntry[] lvt; + + boolean retAtEnd; + int lastRetAddr; + + BitSet isRet; + BitSet isWide; + BitSet isWideCond; + + public BasicBlockWriter(BasicBlocks bb, GrowableConstantPool gcp) { + this.bb = bb; + init(gcp); + prepare(gcp); + } + + public void buildNewLVT() { + Block startBlock = bb.getStartBlock(); + Block[] blocks = bb.getBlocks(); + if (startBlock == null) + return; + + /* We begin with the first Instruction and follow program flow. + * We remember which locals are life at start of each block + * in atStart. + */ + LocalVariableInfo[][] atStart = + new LocalVariableInfo[blocks.length][]; + int startBlockNr = startBlock.getBlockNr(); + atStart[startBlockNr] = new LocalVariableInfo[bb.getMaxLocals()]; + for (int i=0; i < bb.getParamCount(); i++) { + LocalVariableInfo lvi = bb.getParamInfo(i); + atStart[startBlockNr][i] = lvi.getName() != null ? lvi : null; + } + + /* We currently ignore the jsr/ret issue. Should be okay, + * though, since it can only generate a bit too much local + * information. */ + Stack todo = new Stack(); + todo.push(startBlock); + while (!todo.isEmpty()) { + Block block = (Block) todo.pop(); + int blockNr = block.getBlockNr(); + LocalVariableInfo[] life + = (LocalVariableInfo[]) atStart[blockNr].clone(); + Instruction[] instrs = block.getInstructions(); + for (int i = 0; i < instrs.length; i++) { + if (instrs[i].hasLocal()) { + LocalVariableInfo lvi = instrs[i].getLocalInfo(); + int slot = lvi.getSlot(); + life[slot] = lvi.getName() != null ? lvi : null; + } + } + Block[] succs = block.getSuccs(); + for (int j = 0; j < succs.length; j++) { + if (succs[j] == null) + continue; + int succNr = succs[j].getBlockNr(); + if (atStart[succNr] == null) { + atStart[succNr] = (LocalVariableInfo[]) life.clone(); + todo.push(succs[j]); + } else { + boolean changed = false; + for (int k = 0; k < life.length; k++) { + if (atStart[succNr][k] != life[k] + && atStart[succNr][k] != null) { + atStart[succNr][k] = null; + changed = true; + } + } + if (changed && !todo.contains(succs[j])) + todo.push(succs[j]); + } + } + Handler[] handlers = block.getHandlers(); + for (int j = 0; j < handlers.length; j++) { + int succNr = handlers[j].getCatcher().getBlockNr(); + if (atStart[succNr] == null) { + atStart[succNr] = (LocalVariableInfo[]) life.clone(); + todo.push(handlers[j].getCatcher()); + } else { + boolean changed = false; + for (int k = 0; k < life.length; k++) { + if (atStart[succNr][k] != life[k] + && atStart[succNr][k] != null) { + atStart[succNr][k] = null; + changed = true; + } + } + if (changed && !todo.contains(handlers[j].getCatcher())) + todo.push(handlers[j].getCatcher()); + } + } + } + + ArrayList lvtEntries = new ArrayList(); + + LVTEntry[] current = new LVTEntry[bb.getMaxLocals()]; + for (int slot=0; slot < bb.getParamCount(); slot++) { + LocalVariableInfo lvi = bb.getParamInfo(slot); + if (lvi.getName() != null) { + current[slot] = new LVTEntry(); + current[slot].startAddr = 0; + current[slot].lvi = lvi; + System.err.println("lvi at init,"+slot+": "+lvi); + } + } + + for (int i=0; i < blocks.length; i++) { + if (atStart[i] == null) + // ignore unreachable blocks: + continue; + + Block block = blocks[i]; + int addr = blockAddr[i]; + for (int slot = 0; slot < current.length; slot++) { + if (current[slot] != null + && current[slot].lvi != atStart[i][slot]) { + current[slot].endAddr = addr; + lvtEntries.add(current[slot]); + current[slot] = null; + } + if (current[slot] == null && atStart[i][slot] != null) { + current[slot] = new LVTEntry(); + current[slot].startAddr = addr; + current[slot].lvi = atStart[i][slot]; + System.err.println("lvi at "+i+","+slot+": "+current[slot].lvi); + } + } + + Instruction[] instrs = block.getInstructions(); + for (int k = 0; k < instrs.length; k++) { + Instruction instr = instrs[k]; + if (instr.hasLocal()) { + LocalVariableInfo lvi = instr.getLocalInfo(); + int slot = lvi.getSlot(); + if (current[slot] != null + && current[slot].lvi != lvi) { + current[slot].endAddr = addr; + lvtEntries.add(current[slot]); + current[slot] = null; + } + if (current[slot] == null + && lvi.getName() != null) { + current[slot] = new LVTEntry(); + current[slot].startAddr = addr; + current[slot].lvi = lvi; + System.err.println("lvi at "+i+","+k+","+slot+": "+current[slot].lvi); + } + } + addr += instrLength[i][k]; + } + } + + for (int slot = 0; slot < current.length; slot++) { + if (current[slot] != null) { + current[slot].endAddr = blockAddr[blockAddr.length - 1]; + lvtEntries.add(current[slot]); + current[slot] = null; + } + } + if (lvtEntries.size() > 0) + lvt = (LVTEntry[]) lvtEntries.toArray + (new LVTEntry[lvtEntries.size()]); + } + + public void init(GrowableConstantPool gcp) { + Block[] blocks = bb.getBlocks(); + blockAddr = new int[blocks.length + 1]; + instrLength = new int[blocks.length + 1][]; + + int[] gotos = new int[blocks.length + 1]; + int[] conds = new int[blocks.length + 1]; + + boolean needRet = false; + boolean hasRet = false; + isRet = new BitSet(); + BitSet isJsr = new BitSet(); + + int addr = 0; + Block startBlock = bb.getStartBlock(); + if (startBlock == null) { + addr++; + isRet.set(0); + hasRet = true; + gotos[0] = -1; + } else if (startBlock != blocks[0]) { + /* reserve 3 byte for a goto at the beginning */ + addr += 3; + gotos[0] = startBlock.getBlockNr(); + } + + for (int i = 0; i < blocks.length; i++) { + boolean hasDefaultSucc = true; + blockAddr[i] = addr; + Instruction[] instrs = blocks[i].getInstructions(); + instrLength[i] = new int[instrs.length]; + Block[] succs = blocks[i].getSuccs(); + for (int j = 0; j < instrs.length; j++) { + Instruction instr = instrs[j]; + if (instr.hasLineNr()) + lntCount++; + + conds[i+1] = -2; + int opcode = instr.getOpcode(); + int length; + switch_opc: + switch (opcode) { + case opc_ldc: + case opc_ldc2_w: { + Object constant = instr.getConstant(); + if (constant == null) { + length = 1; + break switch_opc; + } + for (int k = 1; k < constants.length; k++) { + if (constant.equals(constants[k])) { + length = 1; + break switch_opc; + } + } + if (opcode == opc_ldc2_w) { + gcp.putLongConstant(constant); + length = 3; + break switch_opc; + } + if (constant instanceof Integer) { + int value = ((Integer) constant).intValue(); + if (value >= Byte.MIN_VALUE + && value <= Byte.MAX_VALUE) { + length = 2; + break switch_opc; + } else if (value >= Short.MIN_VALUE + && value <= Short.MAX_VALUE) { + length = 3; + break switch_opc; + } + } + if (gcp.putConstant(constant) < 256) { + length = 2; + } else { + length = 3; + } + break; + } + case opc_iinc: { + int slot = instr.getLocalSlot(); + int increment = instr.getIncrement(); + if (slot < 256 + && increment >= Byte.MIN_VALUE + && increment <= Byte.MAX_VALUE) + length = 3; + else + length = 6; + break; + } + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: + case opc_istore: case opc_lstore: + case opc_fstore: case opc_dstore: case opc_astore: + if (instr.getLocalSlot() < 4) { + length = 1; + break; + } + if (instr.getLocalSlot() < 256) + length = 2; + else + length = 4; + break; + case opc_ret: { + if (instr.getLocalSlot() < 256) + length = 2; + else + length = 4; + hasDefaultSucc = false; + break; + } + case opc_lookupswitch: { + length = (~addr) & 3; /* padding */ + int[] values = instr.getValues(); + int npairs = values.length; + for (int k=0; k< succs.length; k++) { + if (succs[k] == null) + needRet = true; + } + if (npairs > 0 + && 4 + 4 * (values[npairs-1] - values[0] + 1) + <= 8 * npairs) { + // Use a table switch + length += 13 + 4 * (values[npairs-1] - values[0] + 1); + } else { + // Use a lookup switch + length += 9 + 8 * npairs; + } + hasDefaultSucc = false; + break; + } + case opc_jsr: + conds[i+1] = succs[0].getBlockNr(); + length = 3; + isJsr.set(i+1); + break; + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: + if (succs[0] == null) { + needRet = true; + conds[i+1] = -1; + } else + conds[i+1] = succs[0].getBlockNr(); + length = 3; + break; + case opc_multianewarray: { + if (instr.getDimensions() == 1) { + String clazz = instr.getClazzType().substring(1); + if (newArrayTypes.indexOf(clazz.charAt(0)) != -1) { + length = 2; + } else { + gcp.putClassType(clazz); + length = 3; + } + } else { + gcp.putClassType(instr.getClazzType()); + length = 4; + } + break; + } + case opc_getstatic: + case opc_getfield: + case opc_putstatic: + case opc_putfield: + gcp.putRef(gcp.FIELDREF, instr.getReference()); + length = 3; + break; + case opc_invokespecial: + case opc_invokestatic: + case opc_invokevirtual: + gcp.putRef(gcp.METHODREF, instr.getReference()); + length = 3; + break; + case opc_invokeinterface: + gcp.putRef(gcp.INTERFACEMETHODREF, instr.getReference()); + length = 5; + break; + case opc_new: + case opc_checkcast: + case opc_instanceof: + gcp.putClassType(instr.getClazzType()); + length = 3; + break; + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: + case opc_return: + case opc_athrow: + length = 1; + hasDefaultSucc = false; + break; + case opc_nop: + case opc_iaload: case opc_laload: case opc_faload: + case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iastore: case opc_lastore: case opc_fastore: + case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: + case opc_pop: case opc_pop2: + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + case opc_swap: + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: + case opc_iand: case opc_land: + case opc_ior: case opc_lor: + case opc_ixor: case opc_lxor: + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_lcmp: case opc_fcmpl: case opc_fcmpg: + case opc_dcmpl: case opc_dcmpg: + case opc_arraylength: + case opc_monitorenter: case opc_monitorexit: + length = 1; + break; + default: + throw new IllegalStateException("Invalid opcode "+opcode); + } + instrLength[i][j] = length; + addr += length; + } + if (hasDefaultSucc) { + Block defaultSucc = succs[succs.length-1]; + if (defaultSucc == null) { + // This is a return + gotos[i+1] = -1; + isRet.set(i+1); + lastRetAddr = addr; + hasRet = true; + addr++; + } else if (defaultSucc.getBlockNr() == i + 1) { + // no need for any jump + gotos[i+1] = succs[succs.length-1].getBlockNr(); + } else { + // Reserve space for a normal goto. + gotos[i+1] = succs[succs.length-1].getBlockNr(); + addr += 3; + } + } else { + // No goto needed for this block + gotos[i+1] = -2; + } + } + if (needRet && !hasRet) { + retAtEnd = true; + lastRetAddr = addr; + addr++; + } + blockAddr[blocks.length] = addr; + + isWide = new BitSet(); + isWideCond = new BitSet(); + // Now check for wide goto/jsr/if, but only if method is big enough + boolean changed = addr > Short.MAX_VALUE; + while (changed) { + changed = false; + for (int i = 0; i < gotos.length; i++) { + int gotoNr = gotos[i]; + int condNr = conds[i]; + if (!isWideCond.get(i) && condNr != -2) { + int from = blockAddr[i] - 3; + if (gotoNr != i + 1) + from -= isRet.get(i) ? 1 : isWide.get(i) ? 5 : 3; + int dist = Integer.MAX_VALUE; + if (condNr == -1) { + if (retAtEnd) { + dist = blockAddr[blockAddr.length-1] - 1 - from; + } else { + for (int j = 0; j < gotos.length; j++) { + if (isRet.get(j)) { + dist = blockAddr[j] - 1 - from; + if (dist >= Short.MIN_VALUE + && dist <= Short.MAX_VALUE) + break; + } + } + if (dist == Integer.MAX_VALUE) + throw new InternalError(); + } + } else { + dist = blockAddr[condNr] - from; + } + + if (dist < Short.MIN_VALUE || dist > Short.MAX_VALUE) { + /* We must do the a wide cond: + * if_!xxx L + * goto_w condNr + * L:goto gotoNr + */ + isWideCond.set(i); + int diff = isJsr.get(i) ? 2 : condNr == -1 ? 1 : 5; + instrLength[i][instrLength[i].length-1] += diff; + for (int j = i; j < blockAddr.length; j++) + blockAddr[j] += diff; + changed = true; + } + } + if (!isWide.get(i) && gotoNr >= 0) { + int dist = blockAddr[gotoNr] - blockAddr[i] + 3; + if (dist < Short.MIN_VALUE || dist > Short.MAX_VALUE) { + /* wide goto, correct addresses */ + isWide.set(i); + for (int j = i; j < blockAddr.length; j++) + blockAddr[j] += 2; + changed = true; + } + } + } + } + buildNewLVT(); + } + + public int getSize() { + /* maxStack: 2 + * maxLocals: 2 + * code: 4 + codeLength + * exc count: 2 + * exceptions: n * 8 + * attributes: + * lvt_name: 2 + * lvt_length: 4 + * lvt_count: 2 + * lvt_entries: n * 10 + * attributes: + * lnt_name: 2 + * lnt_length: 4 + * lnt_count: 2 + * lnt_entries: n * 4 + */ + int attrsize = 0; + if (lvt != null) + attrsize += 8 + lvt.length * 10; + if (lntCount > 0) + attrsize += 8 + lntCount * 4; + return 10 + + blockAddr[blockAddr.length - 1] + + bb.getExceptionHandlers().length * 8 + + attrsize; + } + + protected int getAttributeCount() { + int count = 0; + if (lvt != null) + count++; + if (lntCount > 0) + count++; + return count; + } + + public void prepare(GrowableConstantPool gcp) { + Handler[] handlers = bb.getExceptionHandlers(); + for (int i = 0; i< handlers.length; i++) { + if (handlers[i].type != null) + gcp.putClassName(handlers[i].type); + } + if (lvt != null) { + gcp.putUTF8("LocalVariableTable"); + int count = lvt.length; + for (int i=0; i < count; i++) { + System.err.println("lvt: "+lvt[i].lvi); + gcp.putUTF8(lvt[i].lvi.getName()); + gcp.putUTF8(lvt[i].lvi.getType()); + } + } + if (lntCount > 0) + gcp.putUTF8("LineNumberTable"); + } + + public void writeAttributes(GrowableConstantPool gcp, + DataOutputStream output) + throws IOException { + if (lvt != null) { + output.writeShort(gcp.putUTF8("LocalVariableTable")); + int count = lvt.length; + int length = 2 + 10 * count; + output.writeInt(length); + output.writeShort(count); + for (int i=0; i < count; i++) { + output.writeShort(lvt[i].startAddr); + output.writeShort(lvt[i].endAddr); + output.writeShort(gcp.putUTF8(lvt[i].lvi.getName())); + output.writeShort(gcp.putUTF8(lvt[i].lvi.getType())); + output.writeShort(lvt[i].lvi.getSlot()); + } + } + if (lntCount > 0) { + output.writeShort(gcp.putUTF8("LineNumberTable")); + int length = 2 + 4 * lntCount; + output.writeInt(length); + output.writeShort(lntCount); + for (int i = 0; i < lntCount; i++) { + output.writeShort(lnt[2*i]); + output.writeShort(lnt[2*i+1]); + } + } + } + + public void write(GrowableConstantPool gcp, + DataOutputStream output) throws IOException { + output.writeShort(bb.getMaxStack()); + output.writeShort(bb.getMaxLocals()); + Block[] blocks = bb.getBlocks(); + if (blockAddr[blockAddr.length - 1] > 65535) + throw new ClassFormatError("Method too long"); + output.writeInt(blockAddr[blockAddr.length-1]); + lnt = new short[lntCount*2]; + + int addr = 0; + Block startBlock = bb.getStartBlock(); + if (isRet.get(0)) { + output.writeByte(opc_return); + addr ++; + } else if (isWide.get(0)) { + output.writeByte(opc_goto_w); + output.writeInt(blockAddr[startBlock.getBlockNr()]); + addr += 5; + } else if (startBlock != blocks[0]) { + output.writeByte(opc_goto); + output.writeShort(blockAddr[startBlock.getBlockNr()]); + addr += 3; + } + int lntPtr = 0; + + for (int i = 0; i< blocks.length; i++) { + boolean hasDefaultSucc = true; + Block[] succs = blocks[i].getSuccs(); + if (addr != blockAddr[i]) + throw new InternalError("Address calculation broken for "+i+": "+blockAddr[i]+"!="+addr+"!"); + Instruction[] instructions = blocks[i].getInstructions(); + int size = instructions.length; + for (int j = 0; j < size; j++) { + Instruction instr = instructions[j]; + if (instr.hasLineNr()) { + lnt[lntPtr++] = (short) addr; + lnt[lntPtr++] = (short) instr.getLineNr(); + } + int opcode = instr.getOpcode(); + switch_opc: + switch (opcode) { + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: + case opc_istore: case opc_lstore: + case opc_fstore: case opc_dstore: case opc_astore: { + int slot = instr.getLocalSlot(); + if (slot < 4) { + if (opcode < opc_istore) + output.writeByte(opc_iload_0 + + 4*(opcode-opc_iload) + + slot); + else + output.writeByte(opc_istore_0 + + 4*(opcode-opc_istore) + + slot); + } else if (slot < 256) { + output.writeByte(opcode); + output.writeByte(slot); + } else { + output.writeByte(opc_wide); + output.writeByte(opcode); + output.writeShort(slot); + } + break; + } + case opc_ret: { + int slot = instr.getLocalSlot(); + if (slot < 256) { + output.writeByte(opcode); + output.writeByte(slot); + } else { + output.writeByte(opc_wide); + output.writeByte(opcode); + output.writeShort(slot); + } + hasDefaultSucc = false; + break; + } + case opc_ldc: + case opc_ldc2_w: { + Object constant = instr.getConstant(); + if (constant == null) { + output.writeByte(opc_aconst_null); + break switch_opc; + } + for (int k = 1; k < constants.length; k++) { + if (constant.equals(constants[k])) { + output.writeByte(opc_aconst_null + k); + break switch_opc; + } + } + if (opcode == opc_ldc2_w) { + output.writeByte(opcode); + output.writeShort(gcp.putLongConstant(constant)); + } else { + if (constant instanceof Integer) { + int value = ((Integer) constant).intValue(); + if (value >= Byte.MIN_VALUE + && value <= Byte.MAX_VALUE) { + + output.writeByte(opc_bipush); + output.writeByte(((Integer)constant) + .intValue()); + break switch_opc; + } else if (value >= Short.MIN_VALUE + && value <= Short.MAX_VALUE) { + output.writeByte(opc_sipush); + output.writeShort(((Integer)constant) + .intValue()); + break switch_opc; + } + } + if (instrLength[i][j] == 2) { + output.writeByte(opc_ldc); + output.writeByte(gcp.putConstant(constant)); + } else { + output.writeByte(opc_ldc_w); + output.writeShort(gcp.putConstant(constant)); + } + } + break; + } + case opc_iinc: { + int slot = instr.getLocalSlot(); + int incr = instr.getIncrement(); + if (instrLength[i][j] == 3) { + output.writeByte(opcode); + output.writeByte(slot); + output.writeByte(incr); + } else { + output.writeByte(opc_wide); + output.writeByte(opcode); + output.writeShort(slot); + output.writeShort(incr); + } + break; + } + case opc_jsr: { + int dist = blockAddr[succs[0].getBlockNr()] - addr; + if (isWideCond.get(i+1)) { + /* wide jsr */ + output.writeByte(opc_jsr_w); + output.writeInt(dist); + } else { + /* wide jsr */ + output.writeByte(opc_jsr); + output.writeShort(dist); + } + break; + } + + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: { + Block dest = succs[0]; + if (isWideCond.get(i+1)) { + /* swap condition */ + if (opcode >= opc_ifnull) + opcode = opcode ^ 1; + else + opcode = 1 + ((opcode - 1) ^ 1); + output.writeByte(opcode); + if (dest == null) { + output.writeShort(4); + output.writeByte(opc_ret); + } else { + output.writeShort(8); + output.writeByte(opc_goto_w); + int dist = blockAddr[dest.getBlockNr()] - addr; + output.writeInt(dist); + } + } else { + int dist; + if (dest == null) { + if (retAtEnd) { + dist = blockAddr[blocks.length] - 1 - addr; + } else { + for (int k = 0; ; k++) { + if (isRet.get(k)) { + dist = blockAddr[k] - 1 - addr; + if (dist >= Short.MIN_VALUE + && dist <= Short.MAX_VALUE) + break; + } + if (k == blocks.length) + throw new InternalError(); + } + } + } else { + dist = blockAddr[dest.getBlockNr()] - addr; + } + output.writeByte(opcode); + output.writeShort(dist); + } + break; + } + + case opc_lookupswitch: { + int align = 3-(addr % 4); + int[] values = instr.getValues(); + int npairs = values.length; + Block defBlock = succs[npairs]; + int defAddr = defBlock == null ? lastRetAddr + : blockAddr[defBlock.getBlockNr()]; + + if (npairs > 0) { + int tablesize = values[npairs-1] - values[0] + 1; + if (4 + tablesize * 4 <= 8 * npairs) { + // Use a table switch + output.writeByte(opc_tableswitch); + output.write(new byte[align]); + /* def */ + output.writeInt(defAddr - addr); + /* low */ + output.writeInt(values[0]); + /* high */ + output.writeInt(values[npairs-1]); + int pos = values[0]; + for (int k = 0; k < npairs; k++) { + while (pos++ < values[k]) + output.writeInt(defAddr - addr); + int dest = succs[k] == null ? lastRetAddr + : blockAddr[succs[k].getBlockNr()]; + output.writeInt(dest - addr); + } + hasDefaultSucc = false; + break; + } + } + // Use a lookup switch + output.writeByte(opc_lookupswitch); + output.write(new byte[align]); + /* def */ + output.writeInt(defAddr - addr); + output.writeInt(npairs); + for (int k = 0; k < npairs; k++) { + output.writeInt(values[k]); + int dest = succs[k] == null ? lastRetAddr + : blockAddr[succs[k].getBlockNr()]; + output.writeInt(dest - addr); + } + hasDefaultSucc = false; + break; + } + + case opc_getstatic: + case opc_getfield: + case opc_putstatic: + case opc_putfield: + output.writeByte(opcode); + output.writeShort(gcp.putRef(gcp.FIELDREF, + instr.getReference())); + break; + + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: { + Reference ref = instr.getReference(); + output.writeByte(opcode); + if (opcode == opc_invokeinterface) { + output.writeShort + (gcp.putRef(gcp.INTERFACEMETHODREF, ref)); + output.writeByte + (TypeSignature + .getParameterSize(ref.getType()) + 1); + output.writeByte(0); + } else + output.writeShort(gcp.putRef(gcp.METHODREF, ref)); + break; + } + case opc_new: + case opc_checkcast: + case opc_instanceof: + output.writeByte(opcode); + output.writeShort(gcp.putClassType(instr.getClazzType())); + break; + case opc_multianewarray: + if (instr.getDimensions() == 1) { + String clazz = instr.getClazzType().substring(1); + int index = newArrayTypes.indexOf(clazz.charAt(0)); + if (index != -1) { + output.writeByte(opc_newarray); + output.writeByte(index + 4); + } else { + output.writeByte(opc_anewarray); + output.writeShort(gcp.putClassType(clazz)); + } + } else { + output.writeByte(opcode); + output.writeShort + (gcp.putClassType(instr.getClazzType())); + output.writeByte(instr.getDimensions()); + } + break; + + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: + case opc_athrow: case opc_return: + output.writeByte(opcode); + hasDefaultSucc = false; + break; + + case opc_nop: + case opc_iaload: case opc_laload: case opc_faload: + case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iastore: case opc_lastore: case opc_fastore: + case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: + case opc_pop: case opc_pop2: + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + case opc_swap: + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: + case opc_iand: case opc_land: + case opc_ior: case opc_lor: + case opc_ixor: case opc_lxor: + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_lcmp: case opc_fcmpl: case opc_fcmpg: + case opc_dcmpl: case opc_dcmpg: + case opc_arraylength: + case opc_monitorenter: case opc_monitorexit: + output.writeByte(opcode); + break; + default: + throw new ClassFormatException("Invalid opcode "+opcode); + } + addr += instrLength[i][j]; + } + if (hasDefaultSucc) { + // Check which type of goto we should use at end of this block. + Block defaultSucc = succs[succs.length - 1]; + if (isRet.get(i+1)) { + output.writeByte(opc_return); + addr++; + } else if (isWide.get(i+1)) { + output.writeByte(opc_goto_w); + output.writeInt(blockAddr[defaultSucc.getBlockNr()] + - addr); + addr+=5; + } else if (defaultSucc.getBlockNr() != i+1) { + output.writeByte(opc_goto); + output.writeShort(blockAddr[defaultSucc.getBlockNr()] + - addr); + addr+=3; + } + } + } + if (retAtEnd) { + output.writeByte(opc_return); + addr++; + } + if (addr != blockAddr[blocks.length]) + throw new InternalError("Address calculation broken!"); + + Handler[] handlers = bb.getExceptionHandlers(); + output.writeShort(handlers.length); + for (int i = 0; i< handlers.length; i++) { + output.writeShort(blockAddr[handlers[i].start.getBlockNr()]); + output.writeShort(blockAddr[handlers[i].end.getBlockNr()+1]); + output.writeShort(blockAddr[handlers[i].catcher.getBlockNr()]); + output.writeShort((handlers[i].type == null) ? 0 + : gcp.putClassName(handlers[i].type)); + } + } +} diff --git a/jode/src/net/sf/jode/bytecode/BasicBlocks.java b/jode/src/net/sf/jode/bytecode/BasicBlocks.java new file mode 100644 index 0000000..dddb1ae --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/BasicBlocks.java @@ -0,0 +1,409 @@ +/* BasicBlocks Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +import net.sf.jode.GlobalOptions; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.BitSet; +import java.util.Stack; +///#def COLLECTIONS java.util +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +/** + *

Represents the byte code of a method in form of basic blocks. A + * basic block is a bunch of instructions, that must always execute in + * sequential order. Every basic block is represented by an Block + * object.

+ * + *

All jump instructions must be at the end of the block, and the + * jump instructions doesn't have to remember where they jump to. + * Instead this information is stored inside the blocks. See + * Block for details.

+ * + *

Exception Handlers are represented by the Handler class. Their + * start/end range must span over some consecutive BasicBlocks and + * there handler must be another basic block.

+ * + * + * + *

When the code is written to a class file, the blocks are written + * in the given order. Goto and return instructions are inserted as + * necessary, you don't need to care about that.

+ * + *

Creating new BasicBlocks

+ * + *

If you want to create a new BasicBlocks object, first create the + * Block objects, then initialize them (you need to have all successor + * blocks created for this). Afterwards create a new BasicBlock and + * fill its sub blocks:

+ * + *
+ *   MethodInfo myMethod = new MethodInfo("foo", "()V", PUBLIC);
+ *   Block blocks = new Block[10];
+ *   for (int i = 0; i < 10; i++) blocks[i] = new Block();
+ *   blocks[0].setCode(new Instruction[] {...}, 
+ *                     new Block[] {blocks[3], blocks[1]});
+ *   ...
+ *   Handler[] excHandlers = new Handler[1];
+ *   excHandlers[0] = new Handler(blocks[2], blocks[5], blocks[6],
+ *                                "java.lang.NullPointerException");
+ *   BasicBlocks bb = new BasicBlocks(myMethod);
+ *   bb.setCode(blocks, blocks[0], excHandlers);
+ *   classInfo.setMethods(new MethodInfo[] { myMethod });
+ * 
+ * + * @see net.sf.jode.bytecode.Block + * @see net.sf.jode.bytecode.Instruction + */ +public class BasicBlocks extends BinaryInfo implements Opcodes { + + /** + * The method info which contains the basic blocks. + */ + private MethodInfo methodInfo; + /** + * The maximal number of stack entries, that may be used in this + * method. + */ + int maxStack; + /** + * The maximal number of local slots, that may be used in this + * method. + */ + int maxLocals; + + /** + * This is an array of blocks, which are arrays + * of Instructions. + */ + private Block[] blocks; + + /** + * The start block. Normally the first block, but differs if method start + * with a goto, e.g a while. This may be null, if this method is empty. + */ + private Block startBlock; + + /** + * The local variable infos for the method parameters. + */ + private LocalVariableInfo[] paramInfos; + + /** + * The array of exception handlers. + */ + private Handler[] exceptionHandlers; + + public BasicBlocks(MethodInfo mi) { + methodInfo = mi; + int paramSize = (mi.isStatic() ? 0 : 1) + + TypeSignature.getParameterSize(mi.getType()); + paramInfos = new LocalVariableInfo[paramSize]; + for (int i=0; i< paramSize; i++) + paramInfos[i] = LocalVariableInfo.getInfo(i); + } + + public int getMaxStack() { + return maxStack; + } + + public int getMaxLocals() { + return maxLocals; + } + + public MethodInfo getMethodInfo() { + return methodInfo; + } + + public Block getStartBlock() { + return startBlock; + } + + public Block[] getBlocks() { + return blocks; + } + + /** + * @return the exception handlers, or null if the method has no + * exception handlers. + */ + public Handler[] getExceptionHandlers() { + return exceptionHandlers; + } + + public LocalVariableInfo getParamInfo(int i) { + return paramInfos[i]; + } + + public int getParamCount() { + return paramInfos.length; + } + + /** + * Updates the maxStack and maxLocals according to the current code. + * Call this every time you change the code. + */ + public void updateMaxStackLocals() { + maxLocals = getParamCount(); + maxStack = 0; + + if (startBlock == null) + return; + + BitSet visited = new BitSet(); + Stack todo = new Stack(); + int[] poppush = new int[2]; + + startBlock.stackHeight = 0; + todo.push(startBlock); + while (!todo.isEmpty()) { + Block block = (Block) todo.pop(); + int stackHeight = block.stackHeight; + if (stackHeight + block.maxpush > maxStack) + maxStack = stackHeight + block.maxpush; + stackHeight += block.delta; + + Block[] succs = block.getSuccs(); + Instruction[] instr = block.getInstructions(); + for (int i = 0; i < instr.length; i++) { + if (instr[i].hasLocal()) { + int slotlimit = instr[i].getLocalSlot() + 1; + int opcode = instr[i].getOpcode(); + if (opcode == opc_lstore || opcode == opc_dstore + || opcode == opc_lload || opcode == opc_dload) + slotlimit++; + if (slotlimit > maxLocals) + maxLocals = slotlimit; + } + } + if (instr.length > 0 + && instr[instr.length-1].getOpcode() == opc_jsr) { + if (!visited.get(succs[0].blockNr)) { + succs[0].stackHeight = stackHeight + 1; + todo.push(succs[0]); + visited.set(succs[0].blockNr); + } else if (succs[0].stackHeight != stackHeight + 1) + throw new IllegalArgumentException + ("Block has two different stack heights."); + + if (succs[1] != null && !visited.get(succs[1].blockNr)) { + succs[1].stackHeight = stackHeight; + todo.push(succs[1]); + visited.set(succs[1].blockNr); + } else if ((succs[1] == null ? 0 : succs[1].stackHeight) + != stackHeight) + throw new IllegalArgumentException + ("Block has two different stack heights."); + } else { + for (int i = 0; i < succs.length; i++) { + if (succs[i] != null && !visited.get(succs[i].blockNr)) { + succs[i].stackHeight = stackHeight; + todo.push(succs[i]); + visited.set(succs[i].blockNr); + } else if ((succs[i] == null ? 0 : succs[i].stackHeight) + != stackHeight) + throw new IllegalArgumentException + ("Block has two different stack heights."); + } + } + Handler[] handler = block.getHandlers(); + for (int i = 0; i < handler.length; i++) { + if (!visited.get(handler[i].getCatcher().blockNr)) { + handler[i].getCatcher().stackHeight = 1; + todo.push(handler[i].getCatcher()); + visited.set(handler[i].getCatcher().blockNr); + } else if (handler[i].getCatcher().stackHeight != 1) + throw new IllegalArgumentException + ("Block has two different stack heights."); + } + } + } + + public void setBlocks(Block[] blocks, Block startBlock, + Handler[] handlers) { + this.blocks = blocks; + this.startBlock = startBlock; + + exceptionHandlers = handlers.length == 0 ? Handler.EMPTY : handlers; + ArrayList activeHandlers = new ArrayList(); + for (int i = 0; i < blocks.length; i++) { + blocks[i].blockNr = i; + for (int j = 0; j < handlers.length; j++) { + if (handlers[j].getStart() == blocks[i]) + activeHandlers.add(handlers[j]); + } + if (activeHandlers.size() == 0) + blocks[i].catchers = Handler.EMPTY; + else + blocks[i].catchers = + (Handler[]) activeHandlers.toArray(Handler.EMPTY); + for (int j = 0; j < handlers.length; j++) { + if (handlers[j].getEnd() == blocks[i]) + activeHandlers.remove(handlers[j]); + } + } + /* Check if all successor blocks are in this basic block */ + for (int i = 0; i < blocks.length; i++) { + Block[] succs = blocks[i].getSuccs(); + for (int j = 0; j < succs.length; j++) { + if (succs[j] != null + && succs[j] != blocks[succs[j].blockNr]) + throw new IllegalArgumentException + ("Succ " + j + " of block " + i + + " not in basicblocks"); + } + } + updateMaxStackLocals(); +// TransformSubroutine.createSubroutineInfo(this); + } + + /** + * Sets the name and type of a method parameter. This overwrites + * any previously set parameter info for this slot. + * @param info a local variable info mapping a slot nr to a name + * and a type. + */ + public void setParamInfo(LocalVariableInfo info) { + paramInfos[info.getSlot()] = info; + } + + private BasicBlockReader reader; + void read(ConstantPool cp, + DataInputStream input, + int howMuch) throws IOException { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + GlobalOptions.err.println("Reading "+methodInfo); + reader = new BasicBlockReader(this); + reader.readCode(cp, input); + readAttributes(cp, input, howMuch); + reader.convert(); + reader = null; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_BYTECODE) != 0) + dumpCode(GlobalOptions.err); + } + + protected void readAttribute(String name, int length, ConstantPool cp, + DataInputStream input, + int howMuch) throws IOException { + if (howMuch >= ClassInfo.ALMOSTALL + && name.equals("LocalVariableTable")) { + reader.readLVT(length, cp, input); + } else if (howMuch >= ClassInfo.ALMOSTALL + && name.equals("LineNumberTable")) { + reader.readLNT(length, cp, input); + } else + super.readAttribute(name, length, cp, input, howMuch); + } + + + void reserveSmallConstants(GrowableConstantPool gcp) { + for (int i=0; i < blocks.length; i++) { + next_instr: + for (Iterator iter + = Arrays.asList(blocks[i].getInstructions()).iterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + if (instr.getOpcode() == Opcodes.opc_ldc) { + Object constant = instr.getConstant(); + if (constant == null) + continue next_instr; + for (int j=1; j < Opcodes.constants.length; j++) { + if (constant.equals(Opcodes.constants[j])) + continue next_instr; + } + if (constant instanceof Integer) { + int value = ((Integer) constant).intValue(); + if (value >= Short.MIN_VALUE + && value <= Short.MAX_VALUE) + continue next_instr; + } + gcp.reserveConstant(constant); + } + } + } + } + + BasicBlockWriter bbw; + void prepareWriting(GrowableConstantPool gcp) { + bbw = new BasicBlockWriter(this, gcp); + prepareAttributes(gcp); + } + + protected int getAttributeCount() { + return super.getAttributeCount() + bbw.getAttributeCount(); + } + + protected void writeAttributes(GrowableConstantPool gcp, + DataOutputStream output) + throws IOException { + super.writeAttributes(gcp, output); + bbw.writeAttributes(gcp, output); + } + + void write(GrowableConstantPool gcp, + DataOutputStream output) throws IOException { + output.writeInt(bbw.getSize() + getAttributeSize()); + bbw.write(gcp, output); + writeAttributes(gcp, output); + bbw = null; + } + + public void dumpCode(PrintWriter output) { + output.println(methodInfo.getName()+methodInfo.getType()+":"); + if (startBlock == null) + output.println("\treturn"); + else if (startBlock != blocks[0]) + output.println("\tgoto "+startBlock); + + for (int i=0; i< blocks.length; i++) { + blocks[i].dumpCode(output); + } + for (int i=0; i< exceptionHandlers.length; i++) { + output.println("catch " + exceptionHandlers[i].type + + " from " + exceptionHandlers[i].start + + " to " + exceptionHandlers[i].end + + " catcher " + exceptionHandlers[i].catcher); + } + } + + public String toString() { + return "BasicBlocks["+methodInfo+"]"; + } +} diff --git a/jode/src/net/sf/jode/bytecode/BinaryInfo.java b/jode/src/net/sf/jode/bytecode/BinaryInfo.java new file mode 100644 index 0000000..a9fa738 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/BinaryInfo.java @@ -0,0 +1,405 @@ +/* BinaryInfo Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import net.sf.jode.util.SimpleMap; + +///#def COLLECTIONS java.util +import java.util.Map; +import java.util.Collections; +import java.util.Iterator; +///#enddef + + +/** + *

Represents a container for user specified attributes.

+ * + *

Java bytecode is extensible: Classes, Methods and Fields may + * have any number of attributes. Every attribute has a name and some + * unformatted data.

+ * + *

There are some predefined attributes, even the Code of a Method + * is an attribute. These predefined attributes are all handled by + * this package as appropriate. These methods are only useful for non + * standard attributes.

+ * + *

You can provide new attributes by overriding the protected + * methods of this class. This makes it possible to use constant pool + * entries in the attributes.

+ * + *

Another possibility is to add the attributes with the public + * method. This way you don't need to extend the classes, but you + * can't use a constant pool for the contents of the attributes. One + * possible application of this are installation classes. These + * classes have a special attribute containing a zip archive of the + * files that should be installed. There are other possible uses, + * e.g. putting native machine code for some architectures into the + * class.

+ * + * @author Jochen Hoenicke + */ +public class BinaryInfo { + /** + * The bit mask representing public modifier. + */ + public static int ACC_PUBLIC = 0x0001; + /** + * The bit mask representing private modifier. + */ + public static int ACC_PRIVATE = 0x0002; + /** + * The bit mask representing protected modifier. + */ + public static int ACC_PROTECTED = 0x0004; + /** + * The bit mask representing static modifier. + */ + public static int ACC_STATIC = 0x0008; + /** + * The bit mask representing final modifier. + */ + public static int ACC_FINAL = 0x0010; + /** + * The bit mask representing the ACC_SUPER modifier for classes. + * This is a special modifier that only has historic meaning. Every + * class should have this set. + */ + public static int ACC_SUPER = 0x0020; + /** + * The bit mask representing volatile modifier for fields. + */ + public static int ACC_VOLATILE = 0x0040; + /** + * The bit mask representing synthetic bridge method. This is + * used when a non-generic method overrides a generic method of + * super class/interface. + */ + public static int ACC_BRIDGE = 0x0040; + /** + * The bit mask representing transient fields. + */ + public static int ACC_TRANSIENT = 0x0080; + /** + * The bit mask representing varargs methods. + */ + public static int ACC_VARARGS = 0x0080; + /** + * The bit mask representing enumeration fields. + */ + public static int ACC_ENUM = 0x0100; + /** + * The bit mask representing native methods. + */ + public static int ACC_NATIVE = 0x0100; + /** + * The bit mask representing interfaces. + */ + public static int ACC_INTERFACE = 0x0200; + /** + * The bit mask representing abstract modifier. + */ + public static int ACC_ABSTRACT = 0x0400; + /** + * The bit mask representing annotation classes. + */ + public static int ACC_ANNOTATION = 0x0800; + /** + * The bit mask representing strictfp modifier. + */ + public static int ACC_STRICT = 0x0800; + /** + * The bit mask representing synthetic fields/methods and classes. + */ + public static int ACC_SYNTHETIC = 0x1000; + + private Map unknownAttributes = null; + + void skipAttributes(DataInputStream input) throws IOException { + int count = input.readUnsignedShort(); + for (int i=0; i< count; i++) { + input.readUnsignedShort(); // the name index + long length = input.readInt(); + while (length > 0) { + long skipped = input.skip(length); + if (skipped == 0) + throw new EOFException("Can't skip. EOF?"); + length -= skipped; + } + } + } + + /** + * Reads in an attributes of this class. Overwrite this method if + * you want to handle your own attributes. If you don't know how + * to handle an attribute call this method for the super class. + * @param name the attribute name. + * @param length the length of the attribute. + * @param constantPool the constant pool of the class. + * @param input a data input stream where you can read the attribute + * from. It will protect you to read more over the attribute boundary. + * @param howMuch the constant that was given to the {@link + * ClassInfo#load} function when loading this class. + */ + protected void readAttribute(String name, int length, + ConstantPool constantPool, + DataInputStream input, + int howMuch) throws IOException { + byte[] data = new byte[length]; + input.readFully(data); + if (howMuch >= ClassInfo.ALL) { + if (unknownAttributes == null) + unknownAttributes = new SimpleMap(); + unknownAttributes.put(name, data); + } + } + + static class ConstrainedInputStream extends FilterInputStream { + int length; + + public ConstrainedInputStream(int attrLength, InputStream input) { + super(input); + length = attrLength; + } + + public int read() throws IOException { + if (length > 0) { + int data = super.read(); + length--; + return data; + } + throw new EOFException(); + } + + public int read(byte[] b, int off, int len) throws IOException { + if (length < len) { + len = length; + } + if (len == 0) + return -1; + int count = super.read(b, off, len); + length -= count; + return count; + } + + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + public long skip(long count) throws IOException { + if (length < count) { + count = length; + } + count = super.skip(count); + length -= (int) count; + return count; + } + + public void skipRemaining() throws IOException { + while (length > 0) { + int skipped = (int) skip(length); + if (skipped == 0) + throw new EOFException(); + length -= skipped; + } + } + } + + void readAttributes(ConstantPool constantPool, + DataInputStream input, + int howMuch) throws IOException { + int count = input.readUnsignedShort(); + unknownAttributes = null; + for (int i=0; i< count; i++) { + String attrName = + constantPool.getUTF8(input.readUnsignedShort()); + final int attrLength = input.readInt(); + ConstrainedInputStream constrInput = + new ConstrainedInputStream(attrLength, input); + readAttribute(attrName, attrLength, + constantPool, new DataInputStream(constrInput), + howMuch); + constrInput.skipRemaining(); + } + } + + /** + * Drops information from this info. Override this to drop your + * own info and don't forget to call the method of the super class. + * @param keep the constant representing how much information we + * should keep (see {@link ClassInfo#load}). + */ + protected void drop(int keep) { + if (keep < ClassInfo.ALL) + unknownAttributes = null; + } + + /** + * Returns the number of attributes of this class. Overwrite this + * method if you want to add your own attributes by providing a + * writeAttributes method. You should call this method for the + * super class and add the number of your own attributes to the + * returned value. + * @return the number of attributes of this class. + */ + protected int getAttributeCount() { + return unknownAttributes != null ? unknownAttributes.size() : 0; + } + + /** + * Prepare writing your attributes. Overwrite this method if you + * want to add your own attributes, which need constants on the + * class pool. Add the necessary constants to the constant pool + * and call this method for the super class. + * @param gcp The growable constant pool. + */ + protected void prepareAttributes(GrowableConstantPool gcp) { + if (unknownAttributes == null) + return; + Iterator i = unknownAttributes.keySet().iterator(); + while (i.hasNext()) + gcp.putUTF8((String) i.next()); + } + + /** + *

Writes the attributes to the output stream. + * Overwrite this method if you want to add your own attributes. + * All constants you need from the growable constant pool must + * have been previously registered by the {@link #prepareAttributes} + * method. This method must not add new constants to the pool

+ * + * First call the method of the super class. Afterwrites write + * each of your own attributes including the attribute header + * (name and length entry). + * + * @param constantPool The growable constant pool, which is not + * growable anymore (see above). + * @param output the data output stream. You must write exactly + * as many bytes to it as you have told with the {@link + * #getAttributeSize} method. + */ + protected void writeAttributes + (GrowableConstantPool constantPool, + DataOutputStream output) throws IOException { + int count = getAttributeCount(); + output.writeShort(count); + if (unknownAttributes != null) { + Iterator i = unknownAttributes.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = (Map.Entry) i.next(); + String name = (String) e.getKey(); + byte[] data = (byte[]) e.getValue(); + output.writeShort(constantPool.putUTF8(name)); + output.writeInt(data.length); + output.write(data); + } + } + } + + /** + * Gets the total length of all attributes in this binary info. + * Overwrite this method if you want to add your own attributes + * and add the size of your attributes to the value returned by + * the super class.
+ * + * Currently you only need to write this if you extend + * BasicBlocks. + * + * @return the total length of all attributes, including their + * headers and the "number of attributes" field. + */ + protected int getAttributeSize() { + int size = 2; /* attribute count */ + if (unknownAttributes != null) { + Iterator i = unknownAttributes.values().iterator(); + while (i.hasNext()) + size += 2 + 4 + ((byte[]) i.next()).length; + } + return size; + } + + /** + * Finds a non standard attribute with the given name. You don't + * have access to the constant pool. If you need the pool don't + * use this method but extend this class and override + * readAttribute method. + * @param name the name of the attribute. + * @return the contents of the attribute, null if not found. + * @see #readAttribute + */ + public byte[] findAttribute(String name) { + if (unknownAttributes != null) + return (byte[]) unknownAttributes.get(name); + return null; + } + + /** + * Gets all non standard attributes. + * @return an iterator for all attributes. The values returned by + * the next() method of the iterator are of Map.Entry type. The + * key of the entry is the name of the attribute, while the values + * are the byte[] contents. + * @see #findAttribute + */ + public Iterator getAttributes() { + if (unknownAttributes != null) + return unknownAttributes.entrySet().iterator(); + return Collections.EMPTY_SET.iterator(); + } + + /** + * Adds a new non standard attribute or replaces an old one with + * the same name. If it already exists, it will be overwritten. + * Note that there's now way to correlate the contents with a + * constant pool. If you need that extend this class and override + * the methods {@link #getAttributeCount}, {@link + * #prepareAttributes}, {@link #writeAttributes}, and {@link + * #getAttributeSize}. + * @param name the name of the attribute. + * @param contents the new contens. + */ + public void addAttribute(String name, byte[] contents) { + if (unknownAttributes == null) + unknownAttributes = new SimpleMap(); + unknownAttributes.put(name, contents); + } + + /** + * Removes a non standard attributes. + * @param name the name of the attribute. + * @return the old contents of the attribute. + */ + public byte[] removeAttribute(String name) { + if (unknownAttributes != null) + return (byte[]) unknownAttributes.remove(name); + return null; + } + + /** + * Removes all non standard attributes. + */ + public void removeAllAttributes() { + unknownAttributes = null; + } +} diff --git a/jode/src/net/sf/jode/bytecode/Block.java b/jode/src/net/sf/jode/bytecode/Block.java new file mode 100644 index 0000000..8ad9426 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/Block.java @@ -0,0 +1,296 @@ +/* Block Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +import java.io.PrintWriter; +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Arrays; +import java.util.List; +import java.util.Iterator; +///#enddef + +/** + *

Represents a single basic block. It contains a list of + * instructions and the successor blocks.

+ * + *

All jump instructions must be at the end of the block. These + * jump instructions are opc_lookupswitch, + * opc_ifxxx, opc_jsr, opc_ret, + * opc_xreturn and opc_return. + * An opc_goto is implicit if the basic block doesn't end + * with a jump instructions, or if it ends with an conditional jump or + * jsr.

+ * + *

The jump instructions don't remember their destinations, instead + * the Block does it. This are the successor block. There are + * several cases:

+ * + *
    + *
  • Block ends with opc_lookupswitch with + * n values. Then there must be n+1 + * successors where the first n successors correspond to + * the values and the last successor is the default successor.
  • + *
  • Block ends with opc_ifxxx, then there must be two + * successors: The first one is the successor if the condition evaluates + * to true, the second one is for the false branch.
  • + *
  • Block ends with opc_jsr, then there must be two + * successors: The first one is the subroutine, the second is the next + * block after the subroutine.
  • + *
  • Block ends with opc_xreturn or + * opc_ret, then there must no successor at all.
  • + *
  • In any other case there must be exactly one successor.
  • + *
+ * + *

If any successor is null it represents end of + * method, i.e. a return instruction. You can also use + * null successors for conditional jumps and switch + * instruction. You normally shouldn't use opc_return + * instructions. They are only necessary, if you want to return with + * a non-empty stack.

+ * + * @author Jochen Hoenicke + * @see net.sf.jode.bytecode.BasicBlocks + * @see net.sf.jode.bytecode.Instruction + */ +public final class Block { + /** + * The opcodes of the instructions in this block. + */ + private Instruction[] instrs; + + /** + * The blockNr of successor blocks + */ + private Block[] succs; + + /** + * The catching blocks. Set by BasicBlocks. + */ + Handler[] catchers; + + /** + * The blockNr of this block. Set by BasicBlocks. + */ + int blockNr; + + /** + * The number of items this block takes from the stack with + * respect to the stack items at the beginning of the block. + */ + int maxpop; + /** + * The maximum number of items the stack may grow. + */ + int maxpush; + /** + * The difference stack items after the block minus stack items + * before block. + */ + int delta; + /** + * The stack height at the beginning of this block. + * Only valid after the block was inserted in a BasicBlocks and + * the updateMaxStackLocals() of BasicBlocks was called. + */ + int stackHeight; + + /** + * Creates a new block uninitialized block. You mustn't really + * use it (except as successor for other blocks) until you have + * set the code. + */ + public Block() { + } + + /** + * Gets the list of instructions. The returned list should not be + * modified, except that the instructions (but not their opcodes) + * may be modified. + */ + public Instruction[] getInstructions() { + return instrs; + } + + /** + * Gets the successor array. The last successor is the next basic + * block that is jumped to via goto or the default part of a + * switch. For conditional jumps and jsrs the second successor gives + * the destination. + */ + public Block[] getSuccs() { + return succs; + } + + /** + * Gets the exception handlers whose try region contains this + * block. You can't set them since they are calculated + * automatically. + * @return the exception handlers. + * @see BasicBlocks#setBlocks + */ + public Handler[] getHandlers() { + return catchers; + } + + /** + * Gets the block number. The block numbers are consecutive number + * from 0 to the number of blocks in a method. The equation + *
 BasicBlocks.getBlock()[i].getBlockNr() == i 
+ * always holds (as long as you don't do something dirty, like adding + * the same block to different BasicBlocks, or to the same but more + * than once). + * @return the block number. + */ + public int getBlockNr() { + return blockNr; + } + + private void initCode() { + int size = instrs.length; + maxpop = maxpush = 0; + int depth = 0; + int poppush[] = new int[2]; + boolean needGoto = true; + for (int i = 0; i < size; i++) { + instrs[i].getStackPopPush(poppush); + depth -= poppush[0]; + if (maxpop < -depth) + maxpop = -depth; + depth += poppush[1]; + if (maxpush < depth) + maxpush = depth; + + int opcode = instrs[i].getOpcode(); + switch (opcode) { + case Opcodes.opc_goto: + throw new IllegalArgumentException("goto in block"); + + case Opcodes.opc_lookupswitch: + if (succs.length != instrs[i].getValues().length + 1) + throw new IllegalArgumentException + ("number of successors for switch doesn't match"); + if (i != size - 1) + throw new IllegalArgumentException + ("switch in the middle!"); + needGoto = false; + break; + + case Opcodes.opc_ret: case Opcodes.opc_athrow: + case Opcodes.opc_ireturn: case Opcodes.opc_lreturn: + case Opcodes.opc_freturn: case Opcodes.opc_dreturn: + case Opcodes.opc_areturn: case Opcodes.opc_return: + if (succs.length != 0) + throw new IllegalArgumentException + ("throw or return with successor."); + if (i != size - 1) + throw new IllegalArgumentException + ("return in the middle!"); + needGoto = false; + break; + + case Opcodes.opc_ifeq: case Opcodes.opc_ifne: + case Opcodes.opc_iflt: case Opcodes.opc_ifge: + case Opcodes.opc_ifgt: case Opcodes.opc_ifle: + case Opcodes.opc_if_icmpeq: case Opcodes.opc_if_icmpne: + case Opcodes.opc_if_icmplt: case Opcodes.opc_if_icmpge: + case Opcodes.opc_if_icmpgt: case Opcodes.opc_if_icmple: + case Opcodes.opc_if_acmpeq: case Opcodes.opc_if_acmpne: + case Opcodes.opc_ifnull: case Opcodes.opc_ifnonnull: + case Opcodes.opc_jsr: + if (succs.length != 2) + throw new IllegalArgumentException + ("successors inappropriate for if/jsr"); + if (succs[0] == null && opcode == Opcodes.opc_jsr) + throw new IllegalArgumentException + ("null successors inappropriate for jsr"); + if (i != size - 1) + throw new IllegalArgumentException + ("if/jsr in the middle!"); + needGoto = false; + } + } + delta = depth; + if (needGoto && succs.length != 1) + throw new IllegalArgumentException("no single successor block"); + } + + /** + * Returns the stack height at the beginning of the block. This + * is automatically calculated, when the block is inserted in a + * basic block. + */ + public int getStackHeight () { + return stackHeight; + } + + public void getStackPopPush (int[] poppush) { + poppush[0] = maxpop; + poppush[1] = delta + maxpop; + return; + } + + /** + * Set the code, i.e. instructions and successor blocks. + * The instructions must be valid and match the successors. + */ + public void setCode(Instruction[] instrs, Block[] succs) { + this.instrs = instrs; + this.succs = succs; + initCode(); + } + + public void dumpCode(PrintWriter output) { + output.println(" "+this+":"); + for (int i = 0; i < instrs.length; i++) { + Instruction instr = instrs[i]; + if (i == instrs.length - 1 && succs != null) { + int opcode = instr.getOpcode(); + if (opcode == Opcodes.opc_lookupswitch) { + // Special case for switch: + output.println("\tswitch"); + int[] values = instr.getValues(); + for (int j = 0; j < values.length; j++) + output.println("\t case"+values[j] + +": goto "+succs[j]); + output.println("\t default: goto"+ + succs[values.length]); + return; + } else if (succs.length > 1) { + output.println("\t"+instr.getDescription() + +" "+succs[0]); + break; + } + } + output.println("\t"+instr.getDescription()); + + } + if (succs != null && succs.length > 0) { + if (succs[succs.length-1] == null) + output.println("\treturn"); + else + output.println("\tgoto "+succs[succs.length-1]); + } + } + + public String toString() { + return "Block_"+blockNr; + } +} diff --git a/jode/src/net/sf/jode/bytecode/ClassFormatException.java b/jode/src/net/sf/jode/bytecode/ClassFormatException.java new file mode 100644 index 0000000..8b1d5ef --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/ClassFormatException.java @@ -0,0 +1,43 @@ +/* ClassFormatException Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * Thrown when a class file with an unknown or illegal format is loaded. + * + * @author Jochen Hoenicke + */ +public class ClassFormatException extends java.io.IOException{ + /** + * Constructs a new class format exception with the given detail + * message. + * @param detail the detail message. + */ + public ClassFormatException(String detail) { + super(detail); + } + + /** + * Constructs a new class format exception. + */ + public ClassFormatException() { + super(); + } +} diff --git a/jode/src/net/sf/jode/bytecode/ClassInfo.java b/jode/src/net/sf/jode/bytecode/ClassInfo.java new file mode 100644 index 0000000..b3e802f --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/ClassInfo.java @@ -0,0 +1,1599 @@ +/* ClassInfo Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import net.sf.jode.GlobalOptions; +import net.sf.jode.util.UnifyHash; + +import java.io.DataInputStream; +import java.io.BufferedInputStream; +import java.io.DataOutputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.util.Enumeration; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +///#def COLLECTIONS java.util +import java.util.Arrays; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.List; +import java.util.ArrayList; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.Comparable; +///#enddef + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Represents a class or interface. It can't be used for primitive + * or array types. Every class/interface is associated with a class + * path, which is used to load the class and its dependent classes. + * + *

ClassInfo and ClassPath

+ * + * Every ClassInfo instance belongs to a {@link ClassPath}. This + * class path is used to find the class and its dependent classes, + * e.g. the super class. Even if you want to create a class info from + * the scratch you have to associate it with a class path, in which + * the dependent classes are searched. + * + * For every class path and every class name there exists at most one + * class info object with this class name. The only exception is when + * you overwrite a loaded class, e.g. by calling setName(). + * + *

Creating a Class

+ * As you can see, there is no public constructor. Instead you create + * a new ClassInfo, by calling {@link ClassPath#getClassInfo}. + * Multiple calls of this method with the same class name result in + * the same object. The resulting ClassInfo is initially empty and + * you now have three different means to fill it with informations: + * You can {@link #load load} the class from its classpath (from which + * it was created), you can {@link #guess guess} the information + * (useful if the class can't be loaded), or you build it from scratch + * by setting its contents with the various setSomething + * methods. + * + *

Changing a Class

+ * Whether or not the classinfo was already filled with information, + * you can change it. You can, for example, provide another array of + * methods, change the modifiers, or rename the class. Use the + * various setSomething methods. + * + *

The Components of a Class

+ * A class consists of several components: + *
+ *
name
+ * The name of the class. The name is already set, when you create + * a new ClassInfo with getClassInfo. If you change this name this + * has some consequences, read the description of the {@link + * #setName} method. + *
+ *
class name
+ * The short java name of this class, i.e. the name that appears + * behind the "class" keyword in the java source file. While + * getClassName() also works for package scope classes, + * setClassName() must only be called on inner classes and will not + * change the bytecode name.
+ * + * E.g.: The ClassName of java.util.Map$Entry is + * Entry. If you change its ClassName to + * Yrtne and save it, it will still be in a file called + * Map$Entry.class, but a debugger would call it + * java.util.Map.Yrtne. Note that you should also save + * Map, because it also has a reference to the + * ClassName. + *
+ *
modifiers
+ * There is a set of access modifiers (AKA access flags) attached to + * each class. They are represented as integers (bitboard) and can + * be conveniently accessed via {@link java.lang.reflect.Modifier}. + *
+ * + * Inner classes can have more modifiers than normal classes, as + * they can be private, protected or static. These extended modifiers + * are supported, too.
+ * + * TODO: Check that reflection returns the extended modifiers! + *
+ *
superclass
+ * Every class except java.lang.Object has a super + * class. The super class is created in the same classpath as the + * current class. Interfaces always have + * java.lang.Object as their super class. + *
+ *
interfaces
+ * Every class (resp. interfaces) can implement (resp. extend) + * zero or more interfaces. + *
+ *
signature
The classes super class and interfaces with + * template information.
+ * + *
fields
+ * Fields are represented as {@link FieldInfo} objects. + *
+ *
methods
+ * Methods are represented as {@link MethodInfo} objects. + *
+ *
method scoped
+ * A boolean value; true if this class is an anonymous or method + * scoped class. + *
+ *
outer class
+ * the class in which this class or interface was declared. It + * returns null for package scoped and method scoped classes. + *
+ *
classes
+ * the inner classes declared in this class. This doesn't include + * method scoped classes. + *
+ *
source file
+ * The name of source file. The JVM uses this field when a stack + * trace is produced. It may be null if the class was compiled + * without debugging information. + *
+ *
+ * + *

Inner Classes

+ * Inner classes are supported as far as the information is present in + * the bytecode. However, you can always ignore this inner + * information, and access inner classes by their bytecode name, + * e.g. java.util.Map$Entry. There are four different + * types of classes: + *
+ *
normal package scoped classes
+ * A class is package scoped if, and only if + * {@link #getOuterClass()} returns null and + * {@link #isMethodScoped()} returns false. + *
+ *
class scoped classes (inner classes)
+ * A class is class scoped if, and only if + * {@link #getOuterClass()} returns not null. + * + * The bytecode name ({@link #getName()}) of an inner class is + * in normally of the form Package.Outer$Inner. However, + * ClassInfo also supports differently named classes, as long as the + * InnerClass attribute is present. The method + * {@link #getClassName()} returns the name of the inner class + * (Inner in the above example). + * + * You can get all inner classes of a class with the + * method {@link #getClasses}. + *
+ *
named method scoped classes
+ * A class is a named method scoped class if, and only if + * {@link #isMethodScoped()} returns true and + * {@link #getClassName()} returns not null. In + * that case {@link #getOuterClass()} returns null, + * too.

+ * + * The bytecode name ({@link #getName()}) of a method scoped class is + * normally of the form Package.Outer$Number$Inner. However, + * ClassInfo also supports differently named classes, as long as the + * InnerClass attribute is present.

+ * + * There's no way to get the method scoped classes of a method, except + * by analyzing its instructions. And even that is error prone, since + * it could just be a method scoped class of an outer method. + *
+ *
anonymous classes
+ * A class is an anonymous class if, and only if + * {@link #isMethodScoped()} returns true and + * {@link #getClassName()} returns null. In that + * case {@link #getOuterClass()} returns null, + * too.

+ * + * The bytecode name ({@link #getName()}) of a method scoped class + * is normally of the form Package.Outer$Number. + * However, ClassInfo also supports differently named classes, as + * long as the InnerClass attribute is present.

+ * + * There's no way to get the anonymous classes of a method, except + * by analyzing its instructions. And even that is error prone, since + * it could just be an anonymous class of an outer method. + *
+ *
+ * + *
+ *

Open Question

+ * + * I represent most types as {@link String} objects (type + * signatures); this is convenient since java bytecode does the same. + * On the other hand a class type should be represented as + * {@link ClassInfo} object. There is a method in {@link TypeSignature} + * to convert between them, which needs a class path. This is a + * bit difficult to use.
+ * + * However the alternative would be to represents types as ClassInfo + * and create ClassInfo objects for primitive and array types. But + * this contradicts the purpose of this class, which is to read and + * write class files. I think the current solution is okay.
+ * + * @author Jochen Hoenicke */ +public final class ClassInfo extends BinaryInfo implements Comparable { + + private static ClassPath defaultClasspath; + + private int status = 0; + + private boolean modified = false; + private boolean isGuessed = false; + private ClassPath classpath; + + private int modifiers = -1; + private boolean deprecatedFlag; + private String name; + private String className; + private boolean methodScoped; + private ClassInfo superclass; + private ClassInfo outerClass; + private ClassInfo[] interfaces; + private ClassInfo[] innerClasses; + private FieldInfo[] fields; + private MethodInfo[] methods; + private String sourceFile; + private boolean hasInnerClassesAttr; + + /** + * The type signature that also contains template information. + */ + private String signature; + + private final static ClassInfo[] EMPTY_INNER = new ClassInfo[0]; + + /** + * This constant can be used as parameter to drop. It specifies + * that no information at all should be kept for the current class. + * + * @see #load + */ + public static final int NONE = 0; + /** + * This constant can be used as parameter to load. It specifies + * that at least the outer class information should be loaded, + * i.e. the outer class and the java class name. It is the only + * information that is loaded recursively: It is also + * automatically loaded for all classes that are accessed by this + * class. The reason for the recursive load is simple: In java + * bytecode a class contains the outer class information for all + * classes that it accesses, so we can create this information + * without the need to read the outer class. We also need this + * information when writing a class. + * + * @see #load + */ + public static final int OUTERCLASS = 5; + /** + * This constant can be used as parameter to load. It specifies + * that at least the hierarchy information, i.e. the + * superclass/interfaces fields and the modifiers + * of this class should be loaded. + * + * @see #load + */ + public static final int HIERARCHY = 10; + /** + * This constant can be used as parameter to load. It specifies + * that all public fields, methods and inner class declarations + * should be loaded. It doesn't load method bodies. + * + * @see #load + */ + public static final int PUBLICDECLARATIONS = 20; + /** + * This constant can be used as parameter to load. It specifies + * that all the fields, methods and inner class declaration + * should be loaded. It doesn't load method bodies. + * + * @see #load + */ + public static final int DECLARATIONS = 30; + /** + * This constant can be used as parameter to load. It specifies + * that everything in the class except debugging information and + * non-standard attributes should be loaded. + * + * @see #load + */ + public static final int NODEBUG = 80; + /** + * This constant can be used as parameter to load. It specifies + * that everything in the class except non-standard attributes + * should be loaded. + * + * @see #load + */ + public static final int ALMOSTALL = 90; + /** + * This constant can be used as parameter to load. It specifies + * that everything in the class should be loaded. + * + * @see #load + */ + public static final int ALL = 100; + + /** + * @deprecated + */ + public static void setClassPath(String path) { + setClassPath(new ClassPath(path)); + } + + /** + * @deprecated + */ + public static void setClassPath(ClassPath path) { + defaultClasspath= path; + } + + /** + * @deprecated + */ + public static boolean exists(String name) { + return defaultClasspath.existsClass(name); + } + + /** + * @deprecated + */ + public static boolean isPackage(String name) { + return defaultClasspath.isDirectory(name.replace('.', '/')); + } + + /** + * @deprecated + */ + public static Enumeration getClassesAndPackages(String packageName) { + return defaultClasspath.listClassesAndPackages(packageName); + } + + /** + * @deprecated + */ + public static ClassInfo forName(String name) { + return defaultClasspath.getClassInfo(name); + } + + /** + * Disable the default constructor. + * @exception InternalError always. + */ + private ClassInfo() throws InternalError { + throw new InternalError(); + } + + ClassInfo(String name, ClassPath classpath) { + /* Name may be null when reading class with unknown name from + * stream. + */ + if (name != null) + this.name = name.intern(); + this.classpath = classpath; + } + + /** + * Returns the classpath in which this class was created. + */ + public ClassPath getClassPath() { + return classpath; + } + + /****** READING CLASS FILES ***************************************/ + + private static int javaModifiersToBytecode(int javaModifiers) + { + int modifiers = javaModifiers & (Modifier.FINAL + | 0x20 /*ACC_SUPER*/ + | Modifier.INTERFACE + | Modifier.ABSTRACT); + + if ((javaModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) + return Modifier.PUBLIC | modifiers; + else + return modifiers; + } + + private void mergeModifiers(int newModifiers) + throws ClassFormatException + { + if (modifiers == -1) { + modifiers = newModifiers; + return; + } + if (((modifiers ^ newModifiers) & ~0x20) == 0) { + modifiers |= newModifiers; + return; + } + + int oldSimple = javaModifiersToBytecode(modifiers); + if (((oldSimple ^ newModifiers) & 0xfdf) == 0) { + modifiers |= newModifiers & 0x20; + return; + } + + int newSimple = javaModifiersToBytecode(newModifiers); + if (((newSimple ^ modifiers) & 0xfdf) == 0) { + modifiers = newModifiers | (modifiers & 0x20); + return; + } + + throw new ClassFormatException + ("modifiers in InnerClass info doesn't match: " + + modifiers + "<->" + newModifiers); + } + + private void mergeOuterInfo(String className, ClassInfo outer, + int realModifiers, boolean ms) + throws ClassFormatException + { + if (status >= OUTERCLASS) { + if ((className == null + ? this.className != null : !className.equals(this.className)) + || this.outerClass != outer) { + /* Ignore errors when merging, some obfuscator may have + * stripped InnerClasses attributes + */ + if (this.className == null && this.outerClass == null + && (className != null || outer != null)) { + this.outerClass = outer; + this.className = className; + this.methodScoped = ms; + } else if (className != null || outer != null) { + GlobalOptions.err.println + ("WARNING: Outer information mismatch " + +name+": "+className+","+outer+","+ms+"<->" + +this.className +","+this.outerClass+","+this.methodScoped); + } + } + if (realModifiers != -1) + mergeModifiers(realModifiers); + } else { + if (realModifiers != -1) + mergeModifiers(realModifiers); + this.className = className; + this.outerClass = outer; + this.methodScoped = ms; + this.status = OUTERCLASS; + } + } + + private void readInnerClassesAttribute(int length, ConstantPool cp, + DataInputStream input) + throws IOException + { + /* The InnerClasses attribute is transformed in a special way + * so we want to taker a closer look. According to the 2nd + * edition of the vm specification (InnerClasses attribute), + * + * http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#79996 + * + * there is a InnerClass record for each non package scope + * class referenced in this class. We are only interested in + * out own entry and in the entries for our inner classes. + * The latter can easily be recognized, since this class must + * be mentioned in the outer_class_info_index field. + */ + + hasInnerClassesAttr = true; + + int count = input.readUnsignedShort(); + if (length != 2 + 8 * count) + throw new ClassFormatException + ("InnerClasses attribute has wrong length"); + + int innerCount = 0; + /** + * The first part will contain the inner classes, the last + * part the extra classes. + */ + ClassInfo[] innerCIs = new ClassInfo[count]; + + for (int i = 0; i < count; i++) { + int innerIndex = input.readUnsignedShort(); + int outerIndex = input.readUnsignedShort(); + int nameIndex = input.readUnsignedShort(); + String inner = cp.getClassName(innerIndex); + String outer = outerIndex != 0 + ? cp.getClassName(outerIndex) : null; + String innername = nameIndex != 0 ? cp.getUTF8(nameIndex) : null; + int access = input.readUnsignedShort(); + if (innername != null && innername.length() == 0) + innername = null; + + /* Some compilers give method scope and anonymous classes + * a valid outer field, but we mustn't handle them as + * inner classes. + */ + if (innername == null) + outer = null; + + /* The best way to distinguish method scope classes is by thier + * class name. + */ + if (outer != null + && inner.length() > outer.length() + 2 + innername.length() + && inner.startsWith(outer+"$") + && inner.endsWith("$"+innername) + && Character.isDigit(inner.charAt(outer.length() + 1))) + outer = null; + + ClassInfo innerCI = classpath.getClassInfo(inner); + ClassInfo outerCI = outer != null + ? classpath.getClassInfo(outer) : null; + + innerCI.mergeOuterInfo(innername, outerCI, + access, outerCI == null); + if (outerCI == this) + innerCIs[innerCount++] = innerCI; + } + + /* Now inner classes are at the front of the array in correct + * order. The extra classes are in reverse order at the end + * of the array. + */ + if (innerCount > 0) { + innerClasses = new ClassInfo[innerCount]; + System.arraycopy(innerCIs, 0, innerClasses, 0, innerCount); + } else + innerClasses = EMPTY_INNER; + } + + protected void readAttribute(String name, int length, + ConstantPool cp, + DataInputStream input, + int howMuch) throws IOException { + if (howMuch >= ClassInfo.ALMOSTALL && name.equals("SourceFile")) { + if (length != 2) + throw new ClassFormatException("SourceFile attribute" + + " has wrong length"); + sourceFile = cp.getUTF8(input.readUnsignedShort()); + } else if (howMuch >= ClassInfo.OUTERCLASS + && name.equals("InnerClasses")) { + readInnerClassesAttribute(length, cp, input); + } else if (name.equals("Signature")) { + signature = cp.getUTF8(input.readUnsignedShort()); + } else if (name.equals("Deprecated")) { + deprecatedFlag = true; + if (length != 0) + throw new ClassFormatException + ("Deprecated attribute has wrong length"); + } else + super.readAttribute(name, length, cp, input, howMuch); + } + + void loadFromReflection(Class clazz, int howMuch) + throws SecurityException, ClassFormatException { + if (howMuch >= OUTERCLASS) { + Class declarer = clazz.getDeclaringClass(); + if (declarer != null) { + /* We have to guess the className, since reflection doesn't + * tell it :-( + */ + int dollar = name.lastIndexOf('$'); + className = name.substring(dollar+1); + outerClass = classpath.getClassInfo(declarer.getName()); + /* As mentioned above OUTERCLASS is recursive */ + if (outerClass.status < OUTERCLASS) + outerClass.loadFromReflection(declarer, OUTERCLASS); + } else { + /* Check if class name ends with $[numeric]$name or + * $[numeric], in which case it is a method scoped + * resp. anonymous class. + */ + int dollar = name.lastIndexOf('$'); + if (dollar >= 0 && Character.isDigit(name.charAt(dollar+1))) { + /* anonymous class */ + className = null; + outerClass = null; + methodScoped = true; + } else { + int dollar2 = name.lastIndexOf('$', dollar); + if (dollar2 >= 0 + && Character.isDigit(name.charAt(dollar2+1))) { + className = name.substring(dollar+1); + outerClass = null; + methodScoped = true; + } + } + } + + } + if (howMuch >= HIERARCHY) { + modifiers = clazz.getModifiers(); + if (clazz.getSuperclass() == null) + superclass = clazz == Object.class + ? null : classpath.getClassInfo("java.lang.Object"); + else + superclass = classpath.getClassInfo + (clazz.getSuperclass().getName()); + Class[] ifaces = clazz.getInterfaces(); + interfaces = new ClassInfo[ifaces.length]; + for (int i = 0; i= PUBLICDECLARATIONS) { + Field[] fs; + Method[] ms; + Constructor[] cs; + Class[] is; + if (howMuch == PUBLICDECLARATIONS) { + fs = clazz.getFields(); + ms = clazz.getMethods(); + cs = clazz.getConstructors(); + is = clazz.getClasses(); + } else { + fs = clazz.getDeclaredFields(); + ms = clazz.getDeclaredMethods(); + cs = clazz.getDeclaredConstructors(); + is = clazz.getDeclaredClasses(); + } + + int len = 0; + for (int i = fs.length; --i >= 0; ) { + if (fs[i].getDeclaringClass() == clazz) + len++; + } + int fieldPtr = len; + fields = new FieldInfo[len]; + for (int i = fs.length; --i >= 0; ) { + if (fs[i].getDeclaringClass() == clazz) { + String type = TypeSignature.getSignature(fs[i].getType()); + fields[--fieldPtr] = new FieldInfo + (fs[i].getName(), type, fs[i].getModifiers()); + } + } + + len = cs.length; + for (int i = ms.length; --i >= 0; ) { + if (ms[i].getDeclaringClass() == clazz) + len++; + } + methods = new MethodInfo[len]; + int methodPtr = len; + for (int i = ms.length; --i >= 0; ) { + if (ms[i].getDeclaringClass() == clazz) { + String type = TypeSignature.getSignature + (ms[i].getParameterTypes(), ms[i].getReturnType()); + methods[--methodPtr] = new MethodInfo + (ms[i].getName(), type, ms[i].getModifiers()); + } + } + for (int i = cs.length; --i >= 0; ) { + String type = TypeSignature.getSignature + (cs[i].getParameterTypes(), void.class); + methods[--methodPtr] = new MethodInfo + ("", type, cs[i].getModifiers()); + } + if (is.length > 0) { + innerClasses = new ClassInfo[is.length]; + for (int i = is.length; --i >= 0; ) { + innerClasses[i] = classpath.getClassInfo(is[i].getName()); + /* As mentioned above OUTERCLASS is loaded recursive */ + if (innerClasses[i].status < OUTERCLASS) + innerClasses[i].loadFromReflection(is[i], OUTERCLASS); + } + } else + innerClasses = EMPTY_INNER; + } + status = howMuch; + } + + /** + * Reads a class file from a data input stream. Normally you should + * load a class from its classpath instead. This may + * be useful for special kinds of input streams, that ClassPath + * doesn't handle. + * + * @param input The input stream, containing the class in standard + * bytecode format. + * @param howMuch The amount of information that should be read in, one + * of HIERARCHY, PUBLICDECLARATIONS, DECLARATIONS or ALL. + * @exception ClassFormatException if the file doesn't denote a valid + * class. + * @exception IOException if input throws an exception. + * @exception IllegalStateException if this ClassInfo was modified. + * @see #load + */ + public void read(DataInputStream input, int howMuch) + throws IOException + { + if (modified) + throw new IllegalStateException(name); + if (status >= howMuch) + return; + + /* Since we have to read the whole class anyway, we load all + * info, that we may need later and that does not take much memory. + */ + if (howMuch <= DECLARATIONS) + howMuch = DECLARATIONS; + + /* header */ + if (input.readInt() != 0xcafebabe) + throw new ClassFormatException("Wrong magic"); + int version = input.readUnsignedShort(); + version |= input.readUnsignedShort() << 16; + if (version < (45 << 16 | 0)) + throw new ClassFormatException("Wrong class version"); + + /* constant pool */ + ConstantPool cpool = new ConstantPool(); + cpool.read(input); + + /* modifiers */ + modifiers = input.readUnsignedShort(); + /* name */ + String className = cpool.getClassName(input.readUnsignedShort()); + if (name == null) + name = className; + else if (!name.equals(className)) + throw new ClassFormatException("wrong name " + className); + + /* superclass */ + int superID = input.readUnsignedShort(); + superclass = superID == 0 ? null + : classpath.getClassInfo(cpool.getClassName(superID)); + + /* interfaces */ + int count = input.readUnsignedShort(); + interfaces = new ClassInfo[count]; + for (int i = 0; i < count; i++) { + interfaces[i] = classpath.getClassInfo + (cpool.getClassName(input.readUnsignedShort())); + } + + /* fields */ + count = input.readUnsignedShort(); + fields = new FieldInfo[count]; + for (int i = 0; i < count; i++) { + fields[i] = new FieldInfo(); + fields[i].read(cpool, input, howMuch); + } + + /* methods */ + count = input.readUnsignedShort(); + methods = new MethodInfo[count]; + for (int i = 0; i < count; i++) { + methods[i] = new MethodInfo(); + methods[i].read(cpool, input, howMuch); + } + + /* initialize inner classes to empty array, in case there + * is no InnerClasses attribute. + */ + innerClasses = EMPTY_INNER; + + /* attributes */ + readAttributes(cpool, input, howMuch); + + /* All classes that are mentioned in the constant pool must + * have an empty outer class info. This is specified in the + * 2nd edition of the JVM specification. + */ + Iterator iter = cpool.iterateClassNames(); + while (iter.hasNext()) { + ClassInfo ci = classpath.getClassInfo((String) iter.next()); + if (ci.status < OUTERCLASS) + ci.mergeOuterInfo(null, null, -1, false); + } + + /* Set status */ + status = howMuch; + } + + /****** WRITING CLASS FILES ***************************************/ + + /** + * Reserves constant pool entries for String, Integer and Float + * constants needed by the bytecode. These constants should have + * small constant pool indices so that a ldc instead of a ldc_w + * bytecode can be used. + */ + private void reserveSmallConstants(GrowableConstantPool gcp) { + for (int i = 0; i < fields.length; i++) + fields[i].reserveSmallConstants(gcp); + + for (int i = 0; i < methods.length; i++) + methods[i].reserveSmallConstants(gcp); + } + + /** + * Reserves all constant pool entries needed by this class. This + * is necessary, because the constant pool is the first thing + * written to the class file. + */ + private void prepareWriting(GrowableConstantPool gcp) { + gcp.putClassName(name); + gcp.putClassName(superclass.name); + for (int i = 0; i < interfaces.length; i++) + gcp.putClassName(interfaces[i].name); + + for (int i = 0; i < fields.length; i++) + fields[i].prepareWriting(gcp); + + for (int i = 0; i < methods.length; i++) + methods[i].prepareWriting(gcp); + + for (int i = 0; i < innerClasses.length; i++) + gcp.putClassName(innerClasses[i].name); + + if (sourceFile != null) { + gcp.putUTF8("SourceFile"); + gcp.putUTF8(sourceFile); + } + + /* All classes mentioned in the constant pool must have an + * outer class info. This is clearly specified in the 2nd + * edition of the JVM specification. + */ + hasInnerClassesAttr = false; + Iterator iter = gcp.iterateClassNames(); + while (iter.hasNext()) { + ClassInfo ci = classpath.getClassInfo((String) iter.next()); + if (ci.status < OUTERCLASS) { + GlobalOptions.err.println + ("WARNING: " + ci.name + "'s outer class isn't known."); + } else { + if ((ci.outerClass != null || ci.methodScoped) + && ! hasInnerClassesAttr) { + gcp.putUTF8("InnerClasses"); + hasInnerClassesAttr = true; + } + if (ci.outerClass != null) + gcp.putClassName(ci.outerClass.name); + if (ci.className != null) + gcp.putUTF8(ci.className); + } + } + if (deprecatedFlag) + gcp.putUTF8("Deprecated"); + if (signature != null) { + gcp.putUTF8("Signature"); + gcp.putUTF8(signature); + } + prepareAttributes(gcp); + } + + /** + * Count the attributes needed by the class. + */ + protected int getAttributeCount() { + int count = super.getAttributeCount(); + if (sourceFile != null) + count++; + if (hasInnerClassesAttr) + count++; + return count; + } + + /** + * Write the attributes needed by the class, namely SourceFile + * and InnerClasses attributes. + */ + protected void writeAttributes(GrowableConstantPool gcp, + DataOutputStream output) + throws IOException { + super.writeAttributes(gcp, output); + if (sourceFile != null) { + output.writeShort(gcp.putUTF8("SourceFile")); + output.writeInt(2); + output.writeShort(gcp.putUTF8(sourceFile)); + } + + List outers = new ArrayList(); + Iterator iter = gcp.iterateClassNames(); + while (iter.hasNext()) { + ClassInfo ci = classpath.getClassInfo((String) iter.next()); + while (ci != null + && ci.status >= OUTERCLASS + && (ci.outerClass != null || ci.methodScoped)) { + /* Order is important so remove ci if it + * already exists and add it to the end. This + * way the outermost classes go to the end. + */ + outers.remove(ci); + outers.add(ci); + ci = ci.outerClass; + } + } + if (hasInnerClassesAttr) { + int count = outers.size(); + output.writeShort(gcp.putUTF8("InnerClasses")); + output.writeInt(2 + count * 8); + output.writeShort(count); + + ListIterator listiter = outers.listIterator(count); + while (listiter.hasPrevious()) { + ClassInfo ci = (ClassInfo) listiter.previous(); + + output.writeShort(gcp.putClassName(ci.name)); + output.writeShort(ci.outerClass == null ? 0 : + gcp.putClassName(ci.outerClass.name)); + output.writeShort(ci.className == null ? 0 : + gcp.putUTF8(ci.className)); + output.writeShort(ci.modifiers); + } + } + if (deprecatedFlag) { + output.writeShort(gcp.putUTF8("Deprecated")); + output.writeInt(0); + } + if (signature != null) { + output.writeShort(gcp.putUTF8("Signature")); + output.writeInt(2); + output.writeShort(gcp.putUTF8(signature)); + } + } + + + /** + * Writes a class to the given DataOutputStream. Of course this only + * works if ALL information for this class is loaded/set. If this + * class has an outer class, inner classes or extra classes, their + * status must contain at least the OUTERCLASS information. + * @param out the output stream. + * @exception IOException if out throws io exception. + * @exception IllegalStateException if not enough information is set. + */ + public void write(DataOutputStream out) throws IOException { + if (status < ALL) + throw new IllegalStateException("state is "+status); + + GrowableConstantPool gcp = new GrowableConstantPool(); + reserveSmallConstants(gcp); + prepareWriting(gcp); + + out.writeInt(0xcafebabe); + out.writeShort(3); + out.writeShort(45); + gcp.write(out); + + out.writeShort(javaModifiersToBytecode(modifiers)); + out.writeShort(gcp.putClassName(name)); + out.writeShort(gcp.putClassName(superclass.getName())); + out.writeShort(interfaces.length); + for (int i = 0; i < interfaces.length; i++) + out.writeShort(gcp.putClassName(interfaces[i].getName())); + + out.writeShort(fields.length); + for (int i = 0; i < fields.length; i++) + fields[i].write(gcp, out); + + out.writeShort(methods.length); + for (int i = 0; i < methods.length; i++) + methods[i].write(gcp, out); + + writeAttributes(gcp, out); + } + + /** + * Loads the contents of a class from its class path. + * @param howMuch The amount of information that should be loaded + * at least, one of {@link #OUTERCLASS}, {@link #HIERARCHY}, {@link + * #PUBLICDECLARATIONS}, {@link #DECLARATIONS}, {@link #NODEBUG}, + * {@link #ALMOSTALL} or {@link #ALL}. Note that more information + * than requested can be loaded if this is convenient. + * @exception ClassFormatException if the file doesn't denote a + * valid class. + * @exception FileNotFoundException if class wasn't found in classpath. + * @exception IOException if an io exception occured while reading + * the class. + * @exception SecurityException if a security manager prohibits loading + * the class. + * @exception IllegalStateException if this ClassInfo was modified by + * calling one of the setSomething methods. + */ + public void load(int howMuch) + throws IOException + { + if (modified) + throw new IllegalStateException(name); + if (status >= howMuch) + return; + if (classpath.loadClass(this, howMuch)) { + if (status < howMuch) + throw new IllegalStateException("state = "+status); + return; + } + throw new FileNotFoundException(name); + } + + /** + * Guess the contents of a class. This is a last resort if the + * file can't be read by the class path. It generates outer class + * information based on the class name, assumes that the class + * extends java.lang.Object, implements no interfaces and has no + * fields, methods or inner classes. + * + * @param howMuch The amount of information that should be read, + * e.g. {@link #HIERARCHY}. + * @see #OUTERCLASS + * @see #HIERARCHY + * @see #PUBLICDECLARATIONS + * @see #DECLARATIONS + * @see #ALMOSTALL + * @see #ALL + */ + public void guess(int howMuch) + { + if (howMuch <= status) + throw new IllegalStateException("status = "+status); + isGuessed = true; + if (howMuch >= OUTERCLASS) { + modifiers = Modifier.PUBLIC | 0x20; + int dollar = name.lastIndexOf('$'); + if (dollar == -1) { + /* normal class */ + } else if (Character.isDigit(name.charAt(dollar+1))) { + /* anonymous class */ + methodScoped = true; + } else { + className = name.substring(dollar+1); + int prevDollar = name.lastIndexOf('$', dollar); + if (prevDollar >= 0 + && Character.isDigit(name.charAt(prevDollar))) { + /* probably method scoped class, (or inner class + * of anoymous class) */ + methodScoped = true; + outerClass = classpath.getClassInfo + (name.substring(0, prevDollar)); + } else { + /* inner class, we assume it is static, so we don't + * get an exception when we search for the this$0 + * parameter in an constructor invocation. + */ + modifiers |= Modifier.STATIC; + outerClass = classpath.getClassInfo + (name.substring(0, dollar)); + } + } + } + if (howMuch >= HIERARCHY) { + if (name.equals("java.lang.Object")) + superclass = null; + else + superclass = classpath.getClassInfo("java.lang.Object"); + interfaces = new ClassInfo[0]; + } + if (howMuch >= PUBLICDECLARATIONS) { + methods = new MethodInfo[0]; + fields = new FieldInfo[0]; + innerClasses = EMPTY_INNER; + } + status = howMuch; + } + + /** + * This is the counter part to load and guess. It will drop all + * informations bigger than "keep" and clean up the memory. Note + * that drop should be used with care if more than one thread + * accesses this ClassInfo. + * @param keep tells how much info we should keep, can be + * {@link #NONE} or anything that load accepts. + * @see #load + */ + public void drop(int keep) { + if (status <= keep) + return; + if (modified) { + System.err.println("Dropping info between " + keep + " and " + + status + " in modified class " + this + "."); + Thread.dumpStack(); + return; + } + if (keep < HIERARCHY) { + superclass = null; + interfaces = null; + } + + if (keep < OUTERCLASS) { + methodScoped = false; + outerClass = null; + innerClasses = null; + } + + if (keep < PUBLICDECLARATIONS) { + fields = null; + methods = null; + status = keep; + } else { + if (status >= DECLARATIONS) + /* We don't drop non-public declarations, since this + * is not worth it. + */ + keep = DECLARATIONS; + + for (int i = 0; i < fields.length; i++) + fields[i].drop(keep); + for (int i = 0; i < methods.length; i++) + methods[i].drop(keep); + } + + if (keep < ALMOSTALL) + sourceFile = null; + super.drop(keep); + status = keep; + } + + /** + * Returns the full qualified name of this class. + * @return the full qualified name of this class, an interned string. + */ + public String getName() { + return name; + } + + /** + * Tells whether the information in this class was guessed by a call + * to {@link #guess}. + * @return true if the information was guessed. + */ + public boolean isGuessed() { + return isGuessed; + } + + /** + * Returns the java class name of a class, without package or + * outer classes. This is null for an anonymous class. For other + * classes it is the name that occured after the + * class keyword (provided it was compiled from + * java). + * This need OUTERCLASS information loaded to work properly. + * + * @return the short name of this class. Returns null for + * anonymous classes. + * + * @exception IllegalStateException if OUTERCLASS information wasn't + * loaded yet. */ + public String getClassName() { + if (status < OUTERCLASS) + throw new IllegalStateException("status is "+status); + if (className != null || isMethodScoped()) + return className; + + int dot = name.lastIndexOf('.'); + return name.substring(dot+1); + } + + /** + * Returns the ClassInfo object for the super class. + * @return the short name of this class. + * @exception IllegalStateException if HIERARCHY information wasn't + * loaded yet. + */ + public ClassInfo getSuperclass() { + if (status < HIERARCHY) + throw new IllegalStateException("status is "+status); + return superclass; + } + + /** + * Returns the ClassInfo object for the super class. + * @return the short name of this class. + * @exception IllegalStateException if HIERARCHY information wasn't + * loaded yet. + */ + public ClassInfo[] getInterfaces() { + if (status < HIERARCHY) + throw new IllegalStateException("status is "+status); + return interfaces; + } + + /** + * Gets the type signature including template information of the class. + * WARNING: This field may disappear and merged into getType later. + * The type signature of a class consists of the signature for the + * superclass followed by the signatures of the interfaces. + * @return the type signature, empty string for java.lang.Object. + * @see TypeSignature + */ + public String getSignature() { + if (status < HIERARCHY) + throw new IllegalStateException("status is "+status); + if (signature != null) + return signature; + if (superclass == null) + return ""; + StringBuffer sb = new StringBuffer(); + sb.append('L').append(superclass.getName().replace('.','/')) + .append(";"); + for (int i = 0; i < interfaces.length; i++) { + sb.append('L').append(interfaces[i].getName().replace('.','/')) + .append(";"); + } + return sb.toString(); + } + + /** + * Gets the modifiers of this class, e.g. public or abstract. The + * information is only available if at least {@link #HIERARCHY} is + * loaded. + * @return a bitboard of the modifiers. + * @see Class#getModifiers + * @see BinaryInfo#ACC_PUBLIC ACC_* fields in BinaryInfo + */ + public int getModifiers() { + if (modifiers == -1) + throw new IllegalStateException("status is "+status); + return modifiers; + } + + /** + * Checks whether this class info represents an interface. The + * information is only available if at least {@link #HIERARCHY} is + * loaded. + * @return true if this class info represents an interface. + */ + public boolean isInterface() { + return Modifier.isInterface(getModifiers()); + } + + /** + * Checks whether this class was declared as deprecated. In bytecode + * this is represented by a special attribute. + * @return true if this class info represents a deprecated class. + */ + public boolean isDeprecated() { + return deprecatedFlag; + } + + /** + * Searches for a field with given name and type signature. + * @param name the name of the field. + * @param typeSig the {@link TypeSignature type signature} of the + * field. + * @return the field info for the field. + */ + public FieldInfo findField(String name, String typeSig) { + if (status < PUBLICDECLARATIONS) + throw new IllegalStateException("status is "+status); + for (int i = 0; i < fields.length; i++) + if (fields[i].getName().equals(name) + && fields[i].getType().equals(typeSig)) + return fields[i]; + return null; + } + + /** + * Searches for a method with given name and type signature. + * @param name the name of the method. + * @param typeSig the {@link TypeSignature type signature} of the + * method. + * @return the method info for the method. + */ + public MethodInfo findMethod(String name, String typeSig) { + if (status < PUBLICDECLARATIONS) + throw new IllegalStateException("status is "+status); + for (int i = 0; i < methods.length; i++) + if (methods[i].getName().equals(name) + && methods[i].getType().equals(typeSig)) + return methods[i]; + return null; + } + + /** + * Gets the methods of this class. + */ + public MethodInfo[] getMethods() { + if (status < PUBLICDECLARATIONS) + throw new IllegalStateException("status is "+status); + return methods; + } + + /** + * Gets the fields (class and member variables) of this class. + */ + public FieldInfo[] getFields() { + if (status < PUBLICDECLARATIONS) + throw new IllegalStateException("status is "+status); + return fields; + } + + /** + * Returns the outer class of this class if it is an inner class. + * This needs the OUTERCLASS information loaded. + * @return The class that declared this class, null if the class + * isn't declared in a class scope + * + * @exception IllegalStateException if OUTERCLASS information + * wasn't loaded yet. + */ + public ClassInfo getOuterClass() { + if (status < OUTERCLASS) + throw new IllegalStateException("status is "+status); + return outerClass; + } + + /** + * Tells whether the class was declared inside a method. + * This needs the OUTERCLASS information loaded. + * @return true if this is a method scoped or an anonymous class, + * false otherwise. + * + * @exception IllegalStateException if OUTERCLASS information + * wasn't loaded yet. + */ + public boolean isMethodScoped() { + if (status < OUTERCLASS) + throw new IllegalStateException("status is "+status); + return methodScoped; + } + + /** + * Gets the inner classes declared in this class. + * This needs at least PUBLICDECLARATION information loaded. + * @return an array containing the inner classes, guaranteed != null. + * @exception IllegalStateException if PUBLICDECLARATIONS information + * wasn't loaded yet. + */ + public ClassInfo[] getClasses() { + if (status < PUBLICDECLARATIONS) + throw new IllegalStateException("status is "+status); + return innerClasses; + } + + public String getSourceFile() { + return sourceFile; + } + + /** + * Sets the name of this class info. Note that by changing the + * name you may overwrite an already loaded class. This can have + * ugly effects, as references to that overwritten class may still + * exist. + */ + public void setName(String newName) { + /* The class name is used as index in the hash table. We have + * to update the class path and tell it about the name change. + */ + classpath.renameClassInfo(this, newName); + name = newName.intern(); + status = ALL; + modified = true; + } + + public void setSuperclass(ClassInfo newSuper) { + superclass = newSuper; + status = ALL; + modified = true; + } + + public void setInterfaces(ClassInfo[] newIfaces) { + interfaces = newIfaces; + status = ALL; + modified = true; + } + + public void setModifiers(int newModifiers) { + modifiers = newModifiers; + status = ALL; + modified = true; + } + + public void setDeprecated(boolean flag) { + deprecatedFlag = flag; + } + + public void setMethods(MethodInfo[] mi) { + methods = mi; + status = ALL; + modified = true; + } + + public void setFields(FieldInfo[] fi) { + fields = fi; + status = ALL; + modified = true; + } + + public void setOuterClass(ClassInfo oc) { + outerClass = oc; + status = ALL; + modified = true; + } + + public void setMethodScoped(boolean ms) { + methodScoped = ms; + status = ALL; + modified = true; + } + + public void setClasses(ClassInfo[] ic) { + innerClasses = ic.length == 0 ? EMPTY_INNER : ic; + status = ALL; + modified = true; + } + + public void setSourceFile(String newSource) { + sourceFile = newSource; + status = ALL; + modified = true; + } + + /** + * Gets the serial version UID of this class. If a final static + * long serialVersionUID field is present, its constant value + * is returned. Otherwise the UID is calculated with the algorithm + * in the serial version spec. + * @return the serial version UID of this class. + * @exception IllegalStateException if DECLARATIONS aren't loaded. + * @exception NoSuchAlgorithmException if SHA-1 message digest is not + * supported (needed for calculation of UID. + */ + public long getSerialVersionUID() throws NoSuchAlgorithmException { + if (status < DECLARATIONS) + throw new IllegalStateException("status is "+status); + FieldInfo fi = findField("serialVersionUID", "J"); + if (fi != null + && ((fi.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) + == (Modifier.STATIC | Modifier.FINAL)) + && fi.getConstant() != null) + return ((Long) fi.getConstant()).longValue(); + + final MessageDigest md = MessageDigest.getInstance("SHA"); + OutputStream digest = new OutputStream() { + + public void write(int b) { + md.update((byte) b); + } + + public void write(byte[] data, int offset, int length) { + md.update(data, offset, length); + } + }; + DataOutputStream out = new DataOutputStream(digest); + try { + out.writeUTF(this.name); + + // just look at interesting bits of modifiers + int modifs = javaModifiersToBytecode(this.modifiers) + & (Modifier.ABSTRACT | Modifier.FINAL + | Modifier.INTERFACE | Modifier.PUBLIC); + out.writeInt(modifs); + + ClassInfo[] interfaces = (ClassInfo[]) this.interfaces.clone(); + Arrays.sort(interfaces); + for (int i = 0; i < interfaces.length; i++) + out.writeUTF(interfaces[i].name); + + FieldInfo[] fields = (FieldInfo[]) this.fields.clone(); + Arrays.sort(fields); + for (int i = 0; i < fields.length; i++) { + modifs = fields[i].getModifiers(); + if ((modifs & Modifier.PRIVATE) != 0 + && (modifs & (Modifier.STATIC + | Modifier.TRANSIENT)) != 0) + continue; + + out.writeUTF(fields[i].getName()); + out.writeInt(modifs); + out.writeUTF(fields[i].getType()); + } + + MethodInfo[] methods = (MethodInfo[]) this.methods.clone(); + Arrays.sort(methods); + + for (int i = 0; i < methods.length; i++) { + modifs = methods[i].getModifiers(); + /* The modifiers of should be just static, + * but jikes also marks it final. + */ + if (methods[i].getName().equals("")) + modifs = Modifier.STATIC; + if ((modifs & Modifier.PRIVATE) != 0) + continue; + + out.writeUTF(methods[i].getName()); + out.writeInt(modifs); + + // the replacement of '/' with '.' was needed to make + // computed SUID's agree with those computed by JDK. + out.writeUTF(methods[i].getType().replace('/', '.')); + } + + out.close(); + + byte[] sha = md.digest(); + long result = 0; + for (int i = 0; i < 8; i++) { + result += (long)(sha[i] & 0xFF) << (8 * i); + } + return result; + } catch (IOException ex) { + /* Can't happen, since our OutputStream can't throw an + * IOException. + */ + throw new InternalError(); + } + } + + /** + * Compares two ClassInfo objects for name order. + * @return a positive number if this name lexicographically + * follows than other's name, a negative number if it preceeds the + * other, 0 if they are equal. + * @exception ClassCastException if other is not a ClassInfo. + */ + public int compareTo(Object other) { + return name.compareTo(((ClassInfo) other).name); + } + + /** + * Checks if this class is a super class of child. This loads the + * complete hierarchy of child on demand and can throw an IOException + * if some classes are not found or broken. + * + * It doesn't check for cycles in class hierarchy, so it may get + * into an eternal loop. + * + * @param child the class that should be a child class of us. + * @return true if this is as super class of child, false otherwise + * @exception IOException if hierarchy of child could not be loaded. + */ + public boolean superClassOf(ClassInfo child) throws IOException { + while (child != this && child != null) { + if (child.status < HIERARCHY) + child.load(HIERARCHY); + child = child.getSuperclass(); + } + return child == this; + } + + /** + * Checks if this interface is implemented by clazz. This loads the + * complete hierarchy of clazz on demand and can throw an IOException + * if some classes are not found or broken. If this class is not an + * interface it returns false, but you should check it yourself for + * better performance.
+ * + * It doesn't check for cycles in class hierarchy, so it may get + * into an eternal loop. + * @param clazz the class to be checked. + * @return true if this is a interface and is implemented by clazz, + * false otherwise + * @exception IOException if hierarchy of clazz could not be loaded. + */ + public boolean implementedBy(ClassInfo clazz) throws IOException { + while (clazz != this && clazz != null) { + if (clazz.status < HIERARCHY) + clazz.load(HIERARCHY); + ClassInfo[] ifaces = clazz.getInterfaces(); + for (int i = 0; i < ifaces.length; i++) { + if (implementedBy(ifaces[i])) + return true; + } + clazz = clazz.getSuperclass(); + } + return clazz == this; + } + + /** + * Returns a string representation of the class. This is just the + * full qualified class name. + */ + public String toString() { + return name; + } +} diff --git a/jode/src/net/sf/jode/bytecode/ClassPath.java b/jode/src/net/sf/jode/bytecode/ClassPath.java new file mode 100644 index 0000000..cd37b56 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/ClassPath.java @@ -0,0 +1,1040 @@ +/* ClassPath Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +import java.io.ByteArrayInputStream; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Hashtable; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +///#def COLLECTIONS java.util +import java.util.Iterator; +///#enddef + +import net.sf.jode.GlobalOptions; +import net.sf.jode.util.UnifyHash; + +/** + * A path in which class files are searched for. + * + * Class files can be loaded from several different locations, these + * locations can be: + *
    + *
  • A local directory.
  • + *
  • A local jar or zip file
  • + *
  • A URL (unified resource location), pointing to a directory
  • + *
  • A URL pointing to a jar or zip file.
  • + *
  • A Jar URL (see {@link java.net.JarURLConnection}), useful if + * the jar file is not packed correctly.
  • + *
  • The reflection URL reflection:/. This is a + * special location, which fills the ClassInfo with the information + * from the java reflection API. Obviously it can't load any files + * nor the full bytecode. It only loads declarations of classes. If a + * security manager is present, it can only load public + * declarations.
  • + *
+ * + * We use standard java means to find a class file: package correspond + * to directories and the class file must have the .class + * extension. For example if the class path points to + * /home/java, the class java.lang.Object is + * loaded from /home/java/java/lang/Object.class. Of course + * you can write your own {@link Location}s that break this rule. + * + * A class path can have another ClassPath as fallback. If + * ClassInfo.loadInfo is called and the class isn't found the fallback + * ClassPath is searched instead. This repeats until there is no + * further fallback. The fallback is not used when listing classes or + * files. + * + * The main method for creating classes is {@link #getClassInfo}. The + * other available methods are useful to find other files in the + * ClassPath and to get a listing of all available files and classes. + * + * A ClassPath handles some IOExceptions and + * SecurityExceptions skipping the path that produced + * them. + * + * @author Jochen Hoenicke + * @version 1.1 + */ +public class ClassPath { + + /** + * We need a different pathSeparatorChar, since ':' (used for UNIX + * systems) is also used as protocol separator in URLs.
+ * + * We currently allow both pathSeparatorChar and + * altPathSeparatorChar and decide if it is a protocol separator + * by context. This doesn't always work, so use + * altPathSeparator, or better yet the + * ClassPath(String[]) or ClassPath(Location[]) constructors. + */ + public static final char altPathSeparatorChar = ','; + + /** + * A location is a single component of the ClassPath. It provides + * methods to find files, list all files and reading them.
+ * + * Files and directories are always separated by "/" in this class, + * even under Windows where the default is a "\". This behaviour + * is consistent with that of {@link ZipFile}.
+ * + * You can extend this class to provide your own custom locations. + */ + public static class Location { + /** + * Tells whether there exists a file or directory with + * the given name at this location.
+ * The default implementation returns false. + * @param file the name of the file, directories are always + * separated by "/". + * @return true if a file exists at this location. + */ + protected boolean exists(String file) { + return false; + } + + /** + * Tells whether there exists a directory (or package) with + * the given name at this location.
+ * The default implementation returns false. + * @param file the name of the directory, subdirectories are always + * separated by "/". + * @return true if a file exists at this location. + */ + protected boolean isDirectory(String file) { + return false; + } + + /** + * Returns an input stream that reads the given file. It is only + * called for files for which exists returns true.
+ * The default implementation returns null. + * @param file the name of the file, subdirectories are always + * separated by "/". + * @return an input stream for the given file, or null if file + * was not found. + * @exception IOException if an io exception occured while opening + * the file. + */ + protected InputStream getFile(String file) throws IOException { + return null; + } + + /** + * Lists the files and subdirectory in a directory. This is + * only called for directories for which isDirectory returns + * true.
+ * + * The objects returned by the nextElement() + * method of the Enumeration should be of type String and + * contain the file resp directory name without any parent + * directory names.
+ * + * Note that this method is also used by + * {@link ClassPath#listClassesAndPackages}.
+ * + * The default implementation returns null, which is equivalent + * to an empty enumeration. + * + * @param directory the name of the directory, subdirectories + * are always separated by "/". + * @return an enumeration, listing the file names. It may + * return null instead of an empty enumeration. + */ + protected Enumeration listFiles(String directory) { + return null; + } + + /** + * Loads a class from this location and fills it with the given + * information.
+ * The default implementation will get the corresponding ".class" + * file via getFile() and fill the information from the stream. + * So normally there is no need to override this method. + *
+ * + * If you want to build classes on the fly, for example if you + * wrote a parser for java files and want to build class files + * from them, you can override this method. + * + * @param clazz the dot separated full qualified class name. + * @param howMuch the amount of information to load + * @return true, if loading the class was successful, false + * if it was not found. + * @exception ClassFormatException if class format is illegal + * @exception IOException if an io exception occured while reading + * the class. + * @see ClassInfo#read + */ + protected boolean loadClass(ClassInfo clazz, int howMuch) + throws IOException, ClassFormatException + { + String file = clazz.getName().replace('.', '/') + ".class"; + if (!exists(file)) + return false; + DataInputStream input = new DataInputStream + (new BufferedInputStream(getFile(file))); + clazz.read(input, howMuch); + return true; + } + } + + private static class ReflectionLocation extends Location { + protected boolean loadClass(ClassInfo classinfo, int howMuch) + throws IOException, ClassFormatException + { + if (howMuch > ClassInfo.DECLARATIONS) + return false; + + Class clazz = null; + try { + clazz = Class.forName(classinfo.getName()); + } catch (ClassNotFoundException ex) { + return false; + } catch (NoClassDefFoundError ex) { + return false; + } + try { + classinfo.loadFromReflection(clazz, howMuch); + return true; + } catch (SecurityException ex) { + return false; + } + } + + public String toString() { + return "reflection:"; + } + } + + private static class LocalLocation extends Location { + private File dir; + + protected LocalLocation(File path) { + dir = path; + } + + protected boolean exists(String filename) { + if (java.io.File.separatorChar != '/') + filename = filename + .replace('/', java.io.File.separatorChar); + try { + return new File(dir, filename).exists(); + } catch (SecurityException ex) { + return false; + } + } + + protected boolean isDirectory(String filename) { + if (java.io.File.separatorChar != '/') + filename = filename + .replace('/', java.io.File.separatorChar); + return new File(dir, filename).isDirectory(); + } + + protected InputStream getFile(String filename) throws IOException { + if (java.io.File.separatorChar != '/') + filename = filename + .replace('/', java.io.File.separatorChar); + File f = new File(dir, filename); + return new FileInputStream(f); + } + + protected Enumeration listFiles(String directory) { + if (File.separatorChar != '/') + directory = directory + .replace('/', File.separatorChar); + File f = new File(dir, directory); + final String[] files = f.list(); + if (files == null) + return null; + + if (!directory.endsWith(File.separator)) + directory += File.separator; + final String prefix = directory; + return new Enumeration() { + int i = 0; + public boolean hasMoreElements() { + return i < files.length; + } + public Object nextElement() { + try { + return files[i++]; + } catch (ArrayIndexOutOfBoundsException ex) { + return new NoSuchElementException(); + } + } + }; + } + + public String toString() { + return dir.getName(); + } + } + + private static class ZipLocation extends Location { + private Hashtable entries = new Hashtable(); + private ZipFile file; + private byte[] contents; + private String prefix; + + private void addEntry(ZipEntry ze) { + String name = ze.getName(); + if (prefix != null) { + if (!name.startsWith(prefix)) + return; + name = name.substring(prefix.length()); + } + + if (ze.isDirectory() + /* || !name.endsWith(".class")*/) + return; + + do { + String dir = ""; + int pathsep = name.lastIndexOf("/"); + if (pathsep != -1) { + dir = name.substring(0, pathsep); + name = name.substring(pathsep+1); + } + + Vector dirContent = (Vector) entries.get(dir); + if (dirContent != null) { + dirContent.addElement(name); + return; + } + + dirContent = new Vector(); + dirContent.addElement(name); + entries.put(dir, dirContent); + name = dir; + } while (name.length() > 0); + } + + ZipLocation(ZipFile zipfile, String prefix) { + this.file = zipfile; + this.prefix = prefix; + + Enumeration zipEnum = file.entries(); + entries = new Hashtable(); + while (zipEnum.hasMoreElements()) { + addEntry((ZipEntry) zipEnum.nextElement()); + } + } + + ZipLocation(byte[] zipcontents, String prefix) + throws IOException + { + this.contents = zipcontents; + this.prefix = prefix; + + // fill entries into hash table + ZipInputStream zis = new ZipInputStream + (new ByteArrayInputStream(zipcontents)); + entries = new Hashtable(); + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + addEntry(ze); + zis.closeEntry(); + } + zis.close(); + } + + protected boolean exists(String filename) { + if (entries.containsKey(filename)) + return true; + + String dir = ""; + String name = filename; + int index = filename.lastIndexOf('/'); + if (index >= 0) { + dir = filename.substring(0, index); + name = filename.substring(index+1); + } + Vector directory = (Vector)entries.get(dir); + if (directory != null && directory.contains(name)) + return true; + return false; + } + + protected boolean isDirectory(String filename) { + return entries.containsKey(filename); + } + + protected InputStream getFile(String filename) throws IOException { + String fullname = prefix != null ? prefix + filename : filename; + if (contents != null) { + ZipInputStream zis = new ZipInputStream + (new ByteArrayInputStream(contents)); + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + if (ze.getName().equals(fullname)) { +///#ifdef JDK11 +/// // The skip method in jdk1.1.7 ZipInputStream +/// // is buggy. We return a wrapper that fixes +/// // this. +/// return new FilterInputStream(zis) { +/// private byte[] tmpbuf = new byte[512]; +/// public long skip(long n) throws IOException { +/// long skipped = 0; +/// while (n > 0) { +/// int count = read(tmpbuf, 0, +/// (int)Math.min(n, 512L)); +/// if (count == -1) +/// return skipped; +/// skipped += count; +/// n -= count; +/// } +/// return skipped; +/// } +/// }; +///#else + return zis; +///#endif + } + zis.closeEntry(); + } + zis.close(); + } else { + ZipEntry ze = file.getEntry(fullname); + if (ze != null) + return file.getInputStream(ze); + } + return null; + } + + protected Enumeration listFiles(String directory) { + Vector direntries = (Vector) entries.get(directory); + if (direntries != null) + return direntries.elements(); + return null; + } + + public String toString() { + return file.getName(); + } + } + + private static class URLLocation extends Location { + private URL base; + + public URLLocation(URL base) { + this.base = base; + } + + protected boolean exists(String filename) { + try { + URL url = new URL(base, filename); + URLConnection conn = url.openConnection(); + conn.connect(); + conn.getInputStream().close(); + return true; + } catch (IOException ex) { + return false; + } + } + + protected InputStream getFile(String filename) throws IOException { + try { + URL url = new URL(base, filename); + URLConnection conn = url.openConnection(); + conn.setAllowUserInteraction(true); + return conn.getInputStream(); + } catch (IOException ex) { + return null; + } + } + + protected boolean loadClass(ClassInfo clazz, int howMuch) + throws IOException, ClassFormatException + { + /* We override this method to avoid the costs of the + * exists call, and to optimize howMuch. + */ + String file = clazz.getName().replace('.', '/') + ".class"; + InputStream is = getFile(file); + if (is == null) + return false; + + DataInputStream input = new DataInputStream + (new BufferedInputStream(is)); + + /* Reading an URL may be expensive. Therefore we ignore + * howMuch and read everything to avoid reading it again. + */ + clazz.read(input, ClassInfo.ALL); + return true; + } + + public String toString() { + return base.toString(); + } + } + + private Location[] paths; + private UnifyHash classes = new UnifyHash(); + + ClassPath fallback = null; + + /** + * Creates a new class path for the given path. See the class + * description for more information, which kind of paths are + * supported. When a class or a file is not found in the class + * path the fallback is used. + * @param paths An array of paths. + * @param fallback The fallback classpath. + */ + public ClassPath(String[] paths, ClassPath fallback) { + this.fallback = fallback; + initPath(paths); + } + + /** + * Creates a new class path for the given path. See the class + * description for more information, which kind of paths are + * supported. + * @param paths An array of paths. + */ + public ClassPath(String[] paths) { + initPath(paths); + } + + /** + * Creates a new class path for the given path. When a class + * or a file is not found in the class path the fallback is used. + * @param locs An array of locations. + * @param fallback The fallback classpath. + */ + public ClassPath(Location[] locs, ClassPath fallback) { + this.fallback = fallback; + paths = locs; + } + + /** + * Creates a new class path for the given path. + * @param locs An array of locations. + */ + public ClassPath(Location[] locs) { + paths = locs; + } + + /** + * Creates a new class path for the given path. See the class + * description for more information, which kind of paths are + * supported. + * @param path One or more paths. They should be separated by the + * altPathSeparatorChar or pathSeparatorChar, but the latter is + * deprecated since it may give problems for UNIX machines. + * @see #ClassPath(String[] paths) + */ + public ClassPath(String path, ClassPath fallback) { + this.fallback = fallback; + initPath(tokenizeClassPath(path)); + } + + /** + * Creates a new class path for the given path. See the class + * description for more information, which kind of paths are + * supported. + * @param path One or more paths. They should be separated by the + * altPathSeparatorChar or pathSeparatorChar, but the latter is + * deprecated since it may give problems for UNIX machines. + * @see #ClassPath(String[] paths) + */ + public ClassPath(String path) { + initPath(tokenizeClassPath(path)); + } + + /** + * Creates a location for a given path component. See the + * class comment which path components are supported. + * @param path the path component. + * @return a location corresponding to the class. + * @exception NullPointerException if path is null. + * @exception IOException if an io exception occured while accessing the + * path component. + * @exception SecurityException if a security exception occured + * while accessing the path component. + */ + public static Location createLocation(String path) + throws IOException, SecurityException + { + String zipPrefix = null; + // The special reflection URL + if (path.startsWith("reflection:")) + return new ReflectionLocation(); + + // We handle jar URL's ourself, this makes them work even with + // java 1.1 + if (path.startsWith("jar:")) { + int index = 0; + do { + index = path.indexOf('!', index); + } while (index != -1 && index != path.length()-1 + && path.charAt(index+1) != '/'); + + if (index == -1 || index == path.length() - 1) + throw new MalformedURLException(path); + zipPrefix = path.substring(index+2); + if (!zipPrefix.endsWith("/")) + zipPrefix += "/"; + path = path.substring(4, index); + } + + int index = path.indexOf(':'); + /* Grrr, we need to distinguish c:\foo from URLs. */ + if (index > 1) { + // This looks like an URL. + URL base = new URL(path); + URLConnection connection = base.openConnection(); + if (zipPrefix != null + || path.endsWith(".zip") || path.endsWith(".jar") + || connection.getContentType().endsWith("/zip")) { + // This is a zip file. Read it into memory. + byte[] contents = readURLZip(connection); + return new ZipLocation(contents, zipPrefix); + } else + return new URLLocation(base); + } else { + File dir = new File(path); + if (zipPrefix != null || !dir.isDirectory()) { + return new ZipLocation(new ZipFile(dir), zipPrefix); + } else + return new LocalLocation(dir); + } + } + + private static String[] tokenizeClassPath(String path) { + // Calculate a good approximation (rounded upwards) of the tokens + // in this path. + int length = 1; + for (int index=path.indexOf(File.pathSeparatorChar); + index != -1; length++) + index = path.indexOf(File.pathSeparatorChar, index+1); + if (File.pathSeparatorChar != altPathSeparatorChar) { + for (int index=path.indexOf(altPathSeparatorChar); + index != -1; length++) + index = path.indexOf(altPathSeparatorChar, index+1); + } + + + String[] tokens = new String[length]; + int i = 0; + for (int ptr=0; ptr < path.length(); ptr++, i++) { + int next = ptr; + while (next < path.length() + && path.charAt(next) != File.pathSeparatorChar + && path.charAt(next) != altPathSeparatorChar) + next++; + + int index = ptr; + colon_separator: + while (next > ptr + && next < path.length() + && path.charAt(next) == ':') { + // Check if this is a URL instead of a pathSeparator + // Since this is a while loop it allows nested urls like + // jar:ftp://ftp.foo.org/pub/foo.jar!/ + + while (index < next) { + char c = path.charAt(index); + // According to RFC 1738 letters, digits, '+', '-' + // and '.' are allowed SCHEMA characters. We + // disallow '.' because it is a good marker that + // the user has specified a filename instead of a + // URL. + if ((c < 'A' || c > 'Z') + && (c < 'a' || c > 'z') + && (c < '0' || c > '9') + && "+-".indexOf(c) == -1) { + break colon_separator; + } + index++; + } + next++; + index++; + while (next < path.length() + && path.charAt(next) != File.pathSeparatorChar + && path.charAt(next) != altPathSeparatorChar) + next++; + } + tokens[i] = path.substring(ptr, next); + ptr = next; + } + return tokens; + } + + private static byte[] readURLZip(URLConnection conn) throws IOException { + int length = conn.getContentLength(); + if (length <= 0) + // Give a approximation if length is unknown + length = 10240; + else + // Increase the length by one, so we hopefully don't need + // to grow the array later (we need one byte overshot to + // know when the end is reached). + length++; + + byte[] contents = new byte[length]; + + InputStream is = conn.getInputStream(); + int pos = 0; + for (;;) { + // This is ugly, is.available() may return zero even + // if there are more bytes. + int avail = Math.max(is.available(), 1); + if (pos + avail > contents.length) { + // grow the byte array. + byte[] newarr = new byte + [Math.max(2*contents.length, pos + avail)]; + System.arraycopy(contents, 0, newarr, 0, pos); + contents = newarr; + } + int count = is.read(contents, pos, contents.length-pos); + if (count == -1) + break; + pos += count; + } + if (pos < contents.length) { + // shrink the byte array again. + byte[] newarr = new byte[pos]; + System.arraycopy(contents, 0, newarr, 0, pos); + contents = newarr; + } + return contents; + } + + private void initPath(String[] tokens) { + int length = tokens.length; + paths = new Location[length]; + + for (int i = 0; i < length; i++) { + if (tokens[i] == null) + continue; + try { + paths[i] = createLocation(tokens[i]); + } catch (MalformedURLException ex) { + GlobalOptions.err.println + ("Warning: Malformed URL "+ tokens[i] + "."); + } catch (IOException ex) { + GlobalOptions.err.println + ("Warning: IO exception while accessing " + +tokens[i]+"."); + } catch (SecurityException ex) { + GlobalOptions.err.println + ("Warning: Security exception while accessing " + +tokens[i]+"."); + } + } + } + + + /** + * Creates a new class info for a class residing in this search + * path. This doesn't load the class immediately, this is done by + * ClassInfo.loadInfo. It is no error if class doesn't exists.
+ * + * ClassInfos are guaranteed to be unique, i.e. if you have two + * ClsssInfo objects loaded from the same class path with the same + * classname they will always be identical. The only exception is + * if you use setName() or getClassInfoFromStream() and explicitly + * overwrite a previous class.
+ * + * @param classname the dot-separated full qualified name of the class. + * For inner classes you must use the bytecode name with $, + * e.g. java.util.Map$Entry. + * @exception IllegalArgumentException if class name isn't valid. + */ + public ClassInfo getClassInfo(String classname) + { + checkClassName(classname); + int hash = classname.hashCode(); + Iterator iter = classes.iterateHashCode(hash); + while (iter.hasNext()) { + ClassInfo clazz = (ClassInfo) iter.next(); + if (clazz.getName().equals(classname)) + return clazz; + } + ClassInfo clazz = new ClassInfo(classname, this); + classes.put(hash, clazz); + return clazz; + } + + /** + * Creates a new class info from an input stream containing the + * bytecode. This method is useful if you don't know the class + * name or if you have the class in an unusual location. The + * class is fully loaded ({@link ClassInfo#ALL}) when you use this + * method.
+ * + * If a class with the same name was already created with + * getClassInfo() it will effectively be removed from the class + * path, although references to it may still exists elsewhere. + * + * @param stream the input stream containing the bytecode. + * @return the ClassInfo representing this class. + * @exception IOException if an io exception occurs. + * @exception ClassFormatException if bytecode isn't valid. + */ + public ClassInfo getClassInfoFromStream(InputStream stream) + throws IOException, ClassFormatException + { + ClassInfo classInfo = new ClassInfo(null, this); + classInfo.read(new DataInputStream(new BufferedInputStream(stream)), + ClassInfo.ALL); + String classname = classInfo.getName(); + /* Remove the classinfo with the same name from this path if + * it exists. + */ + Iterator iter = classes.iterateHashCode(classname.hashCode()); + while (iter.hasNext()) { + ClassInfo clazz = (ClassInfo) iter.next(); + if (clazz.getName().equals(classname)) { + iter.remove(); + break; + } + } + classes.put(classname.hashCode(), classInfo); + return classInfo; + } + + /** + * Updates the classes unify hash for a class renaming. This + * should be only called by {@link ClassInfo#setName}. + */ + void renameClassInfo(ClassInfo classInfo, String classname) { + classes.remove(classInfo.getName().hashCode(), classInfo); + /* Now remove any class already loaded with that name, just + * in case we're overwriting one. + */ + Iterator iter = classes.iterateHashCode(classname.hashCode()); + while (iter.hasNext()) { + ClassInfo clazz = (ClassInfo) iter.next(); + if (clazz.getName().equals(classname)) { + iter.remove(); + break; + } + } + classes.put(classname.hashCode(), classInfo); + } + + /** + * Checks, if a class with the given name exists somewhere in this + * path. + * @param classname the class name. + * @exception IllegalArgumentException if class name isn't valid. + */ + public boolean existsClass(String classname) { + checkClassName(classname); + return existsFile(classname.replace('.', '/') + ".class"); + } + + /** + * Checks, if a file with the given name exists somewhere in this + * path. + * @param filename the file name. + * @see #existsClass + */ + public boolean existsFile(String filename) { + for (int i=0; i 0 ? dir + "/" : dir; + return new Enumeration() { + String next = getNext(); + + private String getNext() { + while (enumeration.hasMoreElements()) { + String name = (String) enumeration.nextElement(); + if (name.indexOf('.') == -1 + && isDirectory(prefix + name)) + // This is a package + return name; + if (name.endsWith(".class")) + // This is a class + return name.substring(0, name.length()-6); + } + return null; + } + + public boolean hasMoreElements() { + return next != null; + } + public Object nextElement() { + if (next == null) + throw new NoSuchElementException(); + String result = next; + next = getNext(); + return result; + } + }; + } + + /** + * Loads the contents of a class. This is only called by ClassInfo. + */ + boolean loadClass(ClassInfo clazz, int howMuch) + throws IOException, ClassFormatException + { + for (int i = 0; i < paths.length; i++) { + if (paths[i] != null && paths[i].loadClass(clazz, howMuch)) + return true; + } + if (fallback != null) + return fallback.loadClass(clazz, howMuch); + return false; + } + + /** + * Returns a string representation of this classpath. + * @return a string useful for debugging purposes. + */ + public String toString() { + StringBuffer sb = new StringBuffer("ClassPath["); + for (int i = 0; i < paths.length; i++) { + if (paths[i] != null) + sb.append(paths[i]).append(','); + } + sb.append("fallback=").append(fallback).append(']'); + return sb.toString(); + } +} diff --git a/jode/src/net/sf/jode/bytecode/ConstantInstruction.java b/jode/src/net/sf/jode/bytecode/ConstantInstruction.java new file mode 100644 index 0000000..2ffddc7 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/ConstantInstruction.java @@ -0,0 +1,53 @@ +/* ConstantInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import net.sf.jode.util.StringQuoter; + +/** + * This class represents an instruction in the byte code. + * + */ +class ConstantInstruction extends Instruction { + /** + * The typesignature of the class/array. + */ + private Object constant; + + ConstantInstruction(int opcode, Object constant) { + super(opcode); + this.constant = constant; + } + + public final Object getConstant() + { + return constant; + } + + public final void setConstant(Object constant) + { + this.constant = constant; + } + + public String toString() { + return super.toString() + ' ' + + (constant instanceof String + ? StringQuoter.quote((String) constant) : constant); + } +} diff --git a/jode/src/net/sf/jode/bytecode/ConstantPool.java b/jode/src/net/sf/jode/bytecode/ConstantPool.java new file mode 100644 index 0000000..0560cfb --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/ConstantPool.java @@ -0,0 +1,280 @@ +/* ConstantPool Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import java.io.DataInputStream; +import java.io.IOException; + +import java.util.NoSuchElementException; +///#def COLLECTIONS java.util +import java.util.Iterator; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +/** + * This class represent the constant pool. Normally you wont need to + * touch this class, as ClassInfo already does all the hard work. You + * will only need it if you want to add your own custom attributes + * that use the constant pool. + * + * @author Jochen Hoenicke + */ +public class ConstantPool { + public final static int CLASS = 7; + public final static int FIELDREF = 9; + public final static int METHODREF = 10; + public final static int INTERFACEMETHODREF = 11; + public final static int STRING = 8; + public final static int INTEGER = 3; + public final static int FLOAT = 4; + public final static int LONG = 5; + public final static int DOUBLE = 6; + public final static int NAMEANDTYPE = 12; + public final static int UTF8 = 1; + + int count; + int[] tags; + int[] indices1, indices2; + + Object[] constants; + + public ConstantPool () { + } + + public void read(DataInputStream stream) + throws IOException { + count = stream.readUnsignedShort(); + tags = new int[count]; + indices1 = new int[count]; + indices2 = new int[count]; + constants = new Object[count]; + + for (int i=1; i< count; i++) { + int tag = stream.readUnsignedByte(); + tags[i] = tag; + switch (tag) { + case CLASS: + indices1[i] = stream.readUnsignedShort(); + break; + case FIELDREF: + case METHODREF: + case INTERFACEMETHODREF: + indices1[i] = stream.readUnsignedShort(); + indices2[i] = stream.readUnsignedShort(); + break; + case STRING: + indices1[i] = stream.readUnsignedShort(); + break; + case INTEGER: + constants[i] = new Integer(stream.readInt()); + break; + case FLOAT: + constants[i] = new Float(stream.readFloat()); + break; + case LONG: + constants[i] = new Long(stream.readLong()); + tags[++i] = -LONG; + break; + case DOUBLE: + constants[i] = new Double(stream.readDouble()); + tags[++i] = -DOUBLE; + break; + case NAMEANDTYPE: + indices1[i] = stream.readUnsignedShort(); + indices2[i] = stream.readUnsignedShort(); + break; + case UTF8: + constants[i] = stream.readUTF().intern(); + break; + default: + throw new ClassFormatException("unknown constant tag"); + } + } + } + + public int getTag(int i) throws ClassFormatException { + if (i == 0) + throw new ClassFormatException("null tag"); + return tags[i]; + } + + public String getUTF8(int i) throws ClassFormatException { + if (tags[i] != UTF8) + throw new ClassFormatException("Tag mismatch"); + return (String)constants[i]; + } + + public Reference getRef(int i) throws ClassFormatException { + if (tags[i] != FIELDREF + && tags[i] != METHODREF && tags[i] != INTERFACEMETHODREF) + throw new ClassFormatException("Tag mismatch"); + if (constants[i] == null) { + int classIndex = indices1[i]; + int nameTypeIndex = indices2[i]; + if (tags[nameTypeIndex] != NAMEANDTYPE) + throw new ClassFormatException("Tag mismatch"); + String type = getUTF8(indices2[nameTypeIndex]); + try { + if (tags[i] == FIELDREF) + TypeSignature.checkTypeSig(type); + else + TypeSignature.checkMethodTypeSig(type); + } catch (IllegalArgumentException ex) { + throw new ClassFormatException(ex.getMessage()); + } + String clName = getClassType(classIndex); + constants[i] = Reference.getReference + (clName, getUTF8(indices1[nameTypeIndex]), type); + } + return (Reference) constants[i]; + } + + public Object getConstant(int i) throws ClassFormatException { + if (i == 0) + throw new ClassFormatException("null constant"); + switch (tags[i]) { + case INTEGER: + case FLOAT: + case LONG: + case DOUBLE: + return constants[i]; + case CLASS: + return Reference.getReference(getClassType(i), + "class", "Ljava/lang/Class;"); + case STRING: + return getUTF8(indices1[i]); + } + throw new ClassFormatException("Tag mismatch: "+tags[i]); + } + + public String getClassType(int i) throws ClassFormatException { + if (tags[i] != CLASS) + throw new ClassFormatException("Tag mismatch"); + String clName = getUTF8(indices1[i]); + if (clName.charAt(0) != '[') { + clName = ("L"+clName+';').intern(); + } + try { + TypeSignature.checkTypeSig(clName); + } catch (IllegalArgumentException ex) { + throw new ClassFormatException(ex.getMessage()); + } + return clName; + } + + public String getClassName(int i) throws ClassFormatException { + if (tags[i] != CLASS) + throw new ClassFormatException("Tag mismatch"); + if (constants[i] == null) { + String clName = getUTF8(indices1[i]); + try { + TypeSignature.checkTypeSig("L"+clName+";"); + } catch (IllegalArgumentException ex) { + throw new ClassFormatException(ex.getMessage()); + } + constants[i] = clName.replace('/','.').intern(); + } + return (String) constants[i]; + } + + /** + * Iterates through all class entries in the class pool and returns + * their (dot seperated) class name. + */ + public Iterator iterateClassNames() { + return new Iterator() + { + int entry = 1; + public boolean hasNext() { + try { + while (entry < count + && (tags[entry] != CLASS + || getUTF8(indices1[entry]) + .charAt(0) == '[')) + entry++; + } catch (ClassFormatException ex) { + throw new InternalError(ex.getMessage()); + } + return entry < count; + } + + public Object next() { + if (!hasNext()) + throw new NoSuchElementException(); + try { + return getClassName(entry++); + } catch (ClassFormatException ex) { + throw new InternalError(ex.getMessage()); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public String toString(int i) { + switch (tags[i]) { + case CLASS: + return "Class "+toString(indices1[i]); + case STRING: + return "String \""+toString(indices1[i])+"\""; + case INTEGER: + return "Int "+constants[i].toString(); + case FLOAT: + return "Float "+constants[i].toString(); + case LONG: + return "Long "+constants[i].toString(); + case DOUBLE: + return "Double "+constants[i].toString(); + case UTF8: + return constants[i].toString(); + case FIELDREF: + return "Fieldref: "+toString(indices1[i])+"; " + + toString(indices2[i]); + case METHODREF: + return "Methodref: "+toString(indices1[i])+"; " + + toString(indices2[i]); + case INTERFACEMETHODREF: + return "Interfaceref: "+toString(indices1[i])+"; " + + toString(indices2[i]); + case NAMEANDTYPE: + return "Name "+toString(indices1[i]) + +"; Type "+toString(indices2[i]); + default: + return "unknown tag: "+tags[i]; + } + } + + public int size() { + return count; + } + + public String toString() { + StringBuffer result = new StringBuffer("ConstantPool[ null"); + for (int i=1; i< count; i++) { + result.append(", ").append(i).append(" = ").append(toString(i)); + } + result.append(" ]"); + return result.toString(); + } +} diff --git a/jode/src/net/sf/jode/bytecode/FieldInfo.java b/jode/src/net/sf/jode/bytecode/FieldInfo.java new file mode 100644 index 0000000..00c789c --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/FieldInfo.java @@ -0,0 +1,340 @@ +/* FieldInfo Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; +///#def COLLECTIONEXTRA java.lang +import java.lang.Comparable; +///#enddef + +/** + * Represents a java bytecode field (class variable). A field + * consists of the following parts: + * + *
+ * + *
name
The field's name
+ * + *
type
The field's {@link TypeSignature type signature} + * in bytecode format.
+ * + *
signature
The field's {@link TypeSignature type signature} + * in bytecode format including template information.
+ * + *
modifiers
The modifiers of the field like private, public etc. + * These are created by or-ing the constants {@link Modifier#PUBLIC}, + * {@link Modifier#PRIVATE}, {@link Modifier#PROTECTED}, + * {@link Modifier#STATIC}, {@link Modifier#FINAL}, + * {@link Modifier#VOLATILE}, {@link Modifier#TRANSIENT}, + * {@link Modifier#STRICT} + * of class {@link java.lang.reflect.Modifier}. + * + *
synthetic
true if this field is synthetic.
+ * + *
deprecated
true if this field is deprecated.
+ * + *
constant
Final static fields may have a constant + * value. This is either of type String, Integer, Long, Float or + * Double. + * + *
+ * + * @author Jochen Hoenicke + * @see net.sf.jode.bytecode.TypeSignature + * @see net.sf.jode.bytecode.BasicBlocks + */ +public final class FieldInfo extends BinaryInfo implements Comparable { + int modifier; + String name; + String typeSig; + + Object constant; + boolean deprecatedFlag; + /** + * The type signature that also contains template information. + */ + private String signature; + + /** + * Creates a new empty field info. + */ + public FieldInfo() { + } + + /** + * Creates a new field with given name, type and modifiers. + * @param name the name of the field. + * @param typeSig the typeSig the type signature. + * @param modifier the modifier + * @see TypeSignature + * @see Modifier + */ + public FieldInfo(String name, String typeSig, int modifier) { + this.name = name; + this.typeSig = typeSig; + this.modifier = modifier; + } + + protected void readAttribute(String name, int length, + ConstantPool cp, + DataInputStream input, + int howMuch) throws IOException { + if (howMuch >= ClassInfo.DECLARATIONS + && name.equals("ConstantValue")) { + if (length != 2) + throw new ClassFormatException + ("ConstantValue attribute has wrong length"); + int index = input.readUnsignedShort(); + constant = cp.getConstant(index); + } else if (name.equals("Synthetic")) { + modifier |= ACC_SYNTHETIC; + if (length != 0) + throw new ClassFormatException + ("Synthetic attribute has wrong length"); + } else if (name.equals("Deprecated")) { + deprecatedFlag = true; + if (length != 0) + throw new ClassFormatException + ("Deprecated attribute has wrong length"); + } else if (name.equals("Signature")) { + signature = cp.getUTF8(input.readUnsignedShort()); + } else + super.readAttribute(name, length, cp, input, howMuch); + } + + void read(ConstantPool constantPool, + DataInputStream input, int howMuch) throws IOException { + modifier = input.readUnsignedShort(); + name = constantPool.getUTF8(input.readUnsignedShort()); + typeSig = constantPool.getUTF8(input.readUnsignedShort()); + readAttributes(constantPool, input, howMuch); + } + + void reserveSmallConstants(GrowableConstantPool gcp) { + } + + void prepareWriting(GrowableConstantPool gcp) { + gcp.putUTF8(name); + gcp.putUTF8(typeSig); + if (constant != null) { + gcp.putUTF8("ConstantValue"); + if (typeSig.charAt(0) == 'J' || typeSig.charAt(0) == 'D') + gcp.putLongConstant(constant); + else + gcp.putConstant(constant); + } + if (isSynthetic()) + gcp.putUTF8("Synthetic"); + if (deprecatedFlag) + gcp.putUTF8("Deprecated"); + if (signature != null) { + gcp.putUTF8("Signature"); + gcp.putUTF8(signature); + } + prepareAttributes(gcp); + } + + protected int getAttributeCount() { + int count = super.getAttributeCount(); + if (constant != null) + count++; + if (isSynthetic()) + count++; + if (deprecatedFlag) + count++; + return count; + } + + protected void writeAttributes(GrowableConstantPool gcp, + DataOutputStream output) + throws IOException { + super.writeAttributes(gcp, output); + if (constant != null) { + output.writeShort(gcp.putUTF8("ConstantValue")); + output.writeInt(2); + int index; + if (typeSig.charAt(0) == 'J' + || typeSig.charAt(0) == 'D') + index = gcp.putLongConstant(constant); + else + index = gcp.putConstant(constant); + output.writeShort(index); + } + if (isSynthetic()) { + output.writeShort(gcp.putUTF8("Synthetic")); + output.writeInt(0); + } + if (deprecatedFlag) { + output.writeShort(gcp.putUTF8("Deprecated")); + output.writeInt(0); + } + if (signature != null) { + output.writeShort(gcp.putUTF8("Signature")); + output.writeInt(2); + output.writeShort(gcp.putUTF8(signature)); + } + } + + void write(GrowableConstantPool constantPool, + DataOutputStream output) throws IOException { + output.writeShort(modifier); + output.writeShort(constantPool.putUTF8(name)); + output.writeShort(constantPool.putUTF8(typeSig)); + writeAttributes(constantPool, output); + } + + protected void drop(int keep) { + if (keep < ClassInfo.DECLARATIONS) + constant = null; + super.drop(keep); + } + + /** + * Gets the name of the field. + * @return the name. + */ + public String getName() { + return name; + } + + /** + * Gets the type signature of the field. + * @return the type signature. + * @see TypeSignature + */ + public String getType() { + return typeSig; + } + + /** + * Gets the type signature including template information of the field. + * WARNING: This field may disappear and merged into getType later. + * @return the type signature. + * @see TypeSignature + */ + public String getSignature() { + return signature != null ? signature : typeSig; + } + + /** + * Gets the modifier of the field. + * @return the modifiers. + * @see Modifier + */ + public int getModifiers() { + return modifier; + } + + /** + * Tells whether this field is synthetic. + * @return true if the field is synthetic. + */ + public boolean isSynthetic() { + return (modifier & ACC_SYNTHETIC) != 0; + } + + /** + * Tells whether this field is deprecated. + * @return true if the field is deprecated. + */ + public boolean isDeprecated() { + return deprecatedFlag; + } + + /** + * Gets the constant value of the field. For static final fields + * that have a simple String, int, float, double or long constant, + * this returns the corresponding constant as String, Integer, Float + * Double or long. For other fields it returns null. + * @return The constant, or null. + */ + public Object getConstant() { + return constant; + } + + /** + * Sets the name of the field. + * @param newName the name. + */ + public void setName(String newName) { + name = newName; + } + + /** + * Sets the type signature of the field. + * @param newType the type signature. + * @see TypeSignature + */ + public void setType(String newType) { + typeSig = newType; + } + + /** + * Sets the modifier of the field. + * @param newModifier the modifiers. + * @see Modifier + */ + public void setModifiers(int newModifier) { + modifier = newModifier; + } + + public void setSynthetic(boolean flag) { + if (flag) + modifier |= ACC_SYNTHETIC; + else + modifier &= ~ACC_SYNTHETIC; + } + + public void setDeprecated(boolean flag) { + deprecatedFlag = flag; + } + + public void setConstant(Object newConstant) { + constant = newConstant; + } + + /** + * Compares two FieldInfo objects for field order. The field + * order is as follows: First the static class intializer followed + * by constructor with type signature sorted lexicographic. Then + * all other fields sorted lexicographically by name. If two + * fields have the same name, they are sorted by type signature, + * though that can only happen for obfuscated code. + * + * @return a positive number if this field follows the other in + * field order, a negative number if it preceeds the + * other, and 0 if they are equal. + * @exception ClassCastException if other is not a ClassInfo. */ + public int compareTo(Object other) { + FieldInfo fi = (FieldInfo) other; + int result = name.compareTo(fi.name); + if (result == 0) + result = typeSig.compareTo(fi.typeSig); + return result; + } + + public String toString() { + return "Field "+Modifier.toString(modifier)+" "+ + getSignature()+" "+name; + } +} + diff --git a/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java b/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java new file mode 100644 index 0000000..96db4ee --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/GrowableConstantPool.java @@ -0,0 +1,348 @@ +/* GrowableConstantPool Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Hashtable; + +/** + * This class represent a constant pool, where new constants can be + * added to. Normally you wont need to touch this class, as ClassInfo + * already does all the hard work. You will only need it if you want + * to add your own custom attributes that use the constant pool. + * + * @author Jochen Hoenicke + */ +public class GrowableConstantPool extends ConstantPool { + Hashtable entryToIndex = new Hashtable(); + boolean written; + + /** + * This class is used as key to the entryToIndex hashtable + */ + private class Key { + int tag; + Object objData; + int intData; + + public Key(int tag, Object objData, int intData) { + this.tag = tag; + this.objData = objData; + this.intData = intData; + } + + public int hashCode() { + return tag ^ objData.hashCode() ^ intData; + } + + public boolean equals(Object o) { + if (o instanceof Key) { + Key k = (Key) o; + return tag == k.tag && intData == k.intData + && objData.equals(k.objData); + } + return false; + } + } + + /** + * Create a new growable constant pool + */ + public GrowableConstantPool () { + count = 1; + tags = new int[128]; + indices1 = new int[128]; + indices2 = new int[128]; + constants = new Object[128]; + written = false; + } + + private final void grow(int wantedSize) { + if (written) + throw new IllegalStateException("adding to written ConstantPool"); + if (wantedSize > 65535) + throw new IllegalArgumentException("Too many constants added"); + if (tags.length < wantedSize) { + int newSize = Math.min(65535, Math.max(tags.length*2, wantedSize)); + int[] tmpints = new int[newSize]; + System.arraycopy(tags, 0, tmpints, 0, count); + tags = tmpints; + tmpints = new int[newSize]; + System.arraycopy(indices1, 0, tmpints, 0, count); + indices1 = tmpints; + tmpints = new int[newSize]; + System.arraycopy(indices2, 0, tmpints, 0, count); + indices2 = tmpints; + Object[] tmpobjs = new Object[newSize]; + System.arraycopy(constants, 0, tmpobjs, 0, count); + constants = tmpobjs; + } + } + + private int putConstant(int tag, Object constant) { + Key key = new Key(tag, constant, 0); + Integer index = (Integer) entryToIndex.get(key); + if (index != null) + return index.intValue(); + int newIndex = count; + grow(count + 1); + tags[newIndex] = tag; + constants[newIndex] = constant; + entryToIndex.put(key, new Integer(newIndex)); + count++; + return newIndex; + } + + private int putLongConstant(int tag, Object constant) { + Key key = new Key(tag, constant, 0); + Integer index = (Integer) entryToIndex.get(key); + if (index != null) + return index.intValue(); + int newIndex = count; + grow(count + 2); + tags[newIndex] = tag; + tags[newIndex+1] = -tag; + constants[newIndex] = constant; + entryToIndex.put(key, new Integer(newIndex)); + count += 2; + return newIndex; + } + + private int putIndexed(int tag, Object obj1, int index1, int index2) { + Key key = new Key(tag, obj1, index2); + Integer indexObj = (Integer) entryToIndex.get(key); + if (indexObj != null) { + /* Maybe this was a reserved, but not filled entry */ + int index = indexObj.intValue(); + indices1[index] = index1; + indices2[index] = index2; + return index; + } + grow(count+1); + tags[count] = tag; + indices1[count] = index1; + indices2[count] = index2; + entryToIndex.put(key, new Integer(count)); + return count++; + } + + /** + * Adds a new UTF8 entry to the constant pool and returns the index. + * If it already exists it will reuse the previous entry. + * @param utf the UTF8 string. + * @return the index of the pool entry. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ + public final int putUTF8(String utf) { + return putConstant(UTF8, utf); + } + + /** + * Adds a new class name entry to the constant pool and returns + * the index. If it already exists it will reuse the previous + * entry. + * @param name the dot separated full qualified class name. + * @return the index of the pool entry. + * @exception IllegalArgumentException if the class name is illegal. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ + public int putClassName(String name) { + name = name.replace('.','/'); + TypeSignature.checkTypeSig("L"+name+";"); + return putIndexed(CLASS, name, putUTF8(name), 0); + } + + /** + * Adds a new class entry to the constant pool and returns + * the index. If it already exists it will reuse the previous + * entry. This is the same as putClassName, except for the format + * of the parameter and that it can also handle arrays. + * @param name the type signature of the class to add. + * @return the index of the pool entry. + * @exception IllegalArgumentException if the class name is illegal. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ + public int putClassType(String name) { + TypeSignature.checkTypeSig(name); + if (name.charAt(0) == 'L') + name = name.substring(1, name.length()-1); + else if (name.charAt(0) != '[') + throw new IllegalArgumentException("wrong class type: "+name); + return putIndexed(CLASS, name, putUTF8(name), 0); + } + + /** + * Adds a new field/method or interface reference to the constant + * pool and returns the index. If it already exists it will reuse + * the previous entry. + * @param tag the tag of the reference, one of FIELDREF, METHODREF + * or INTERFACEMETHODREF. + * @param ref the reference. + * @return the index of the pool entry. + * @exception IllegalArgumentException if the reference type or + * class name is illegal. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ + public int putRef(int tag, Reference ref) { + String className = ref.getClazz(); + String typeSig = ref.getType(); + if (tag == FIELDREF) + TypeSignature.checkTypeSig(typeSig); + else + TypeSignature.checkMethodTypeSig(typeSig); + + + int classIndex = putClassType(className); + int nameIndex = putUTF8(ref.getName()); + int typeIndex = putUTF8(typeSig); + int nameTypeIndex = putIndexed(NAMEANDTYPE, + ref.getName(), nameIndex, typeIndex); + return putIndexed(tag, className, classIndex, nameTypeIndex); + } + + /** + * Puts a "one slot" constant into this constant pool. If it + * already exists it will reuse the previous entry. + * @param c the constant; must be of type + * Integer, Float or String + * @return the index of the pool entry. + * @exception IllegalArgumentException if the constant is of wrong type. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ + public int putConstant(Object c) { + if (c instanceof String) { + return putIndexed(STRING, c, putUTF8((String) c), 0); + } else if (c instanceof Reference) { + return putClassType(((Reference) c).getClazz()); + } else { + int tag; + if (c instanceof Integer) + tag = INTEGER; + else if (c instanceof Float) + tag = FLOAT; + else + throw new IllegalArgumentException + ("illegal constant " + c + " of type: " + c.getClass()); + return putConstant(tag, c); + } + } + + /** + * Puts a "double slot" constant into this constant pool. If it + * already exists it will reuse the previous entry. + * @param c the constant; must be of type Long or Double + * @return the index of the pool entry. + * @exception IllegalArgumentException if the constant is of wrong type. + * @exception IllegalStateException if the pool was already written, + * but the entry was not added before. + */ + public int putLongConstant(Object c) { + int tag; + if (c instanceof Long) + tag = LONG; + else if (c instanceof Double) + tag = DOUBLE; + else + throw new IllegalArgumentException + ("illegal long constant " + c + " of type: " + c.getClass()); + return putLongConstant(tag, c); + } + + /** + * Reserves an entry in this constant pool for a constant (for ldc). + * The constant must still be put into the pool, before the pool may + * be written. + * @param c the constant, must be of type + * Integer, Float or String + * @return the reserved index into the pool of this constant. + */ + public int reserveConstant(Object c) { + if (c instanceof String) { + return putIndexed(STRING, c, -1, 0); + } else if (c instanceof Reference) { + String name = ((Reference) c).getClazz(); + if (name.charAt(0) == 'L') + name = name.substring(1, name.length()-1); + else if (name.charAt(0) != '[') + throw new IllegalArgumentException("wrong class type: "+name); + return putIndexed(CLASS, name, -1, 0); + } else { + return putConstant(c); + } + } + + /** + * Writes the constant pool to the stream. This is normally called + * by {@link ClassInfo#write}. + * @param stream the stream to write to. + * @exception IOException if it occured while writing. + */ + public void write(DataOutputStream stream) + throws IOException { + written = true; + stream.writeShort(count); + for (int i=1; i< count; i++) { + int tag = tags[i]; + stream.writeByte(tag); + switch (tag) { + case CLASS: + stream.writeShort(indices1[i]); + break; + case FIELDREF: + case METHODREF: + case INTERFACEMETHODREF: + stream.writeShort(indices1[i]); + stream.writeShort(indices2[i]); + break; + case STRING: + stream.writeShort(indices1[i]); + break; + case INTEGER: + stream.writeInt(((Integer)constants[i]).intValue()); + break; + case FLOAT: + stream.writeFloat(((Float)constants[i]).floatValue()); + break; + case LONG: + stream.writeLong(((Long)constants[i]).longValue()); + i++; + break; + case DOUBLE: + stream.writeDouble(((Double)constants[i]).doubleValue()); + i++; + break; + case NAMEANDTYPE: + stream.writeShort(indices1[i]); + stream.writeShort(indices2[i]); + break; + case UTF8: + stream.writeUTF((String)constants[i]); + break; + default: + throw new ClassFormatException("unknown constant tag"); + } + } + } +} diff --git a/jode/src/net/sf/jode/bytecode/Handler.java b/jode/src/net/sf/jode/bytecode/Handler.java new file mode 100644 index 0000000..a4341ab --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/Handler.java @@ -0,0 +1,94 @@ +/* Handler Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * A simple class containing the info about one try-catch block. + * + * @author Jochen Hoenicke + */ +public class Handler { + Block start, end, catcher; + String type; + + /** + * The empty handler array. Since handlers are often empty, we don't + * want to create a new object each time. + */ + final static Handler[] EMPTY = new Handler[0]; + + /** + * Creates a new handler. + */ + Handler(Block s, Block e, Block c, String t) { + start = s; + end = e; + catcher = c; + type = t; + } + + /** + * Gets the first basic block of the try. + */ + public Block getStart() { + return start; + } + + /** + * Gets the last basic block of the try. + */ + public Block getEnd() { + return end; + } + + /** + * Gets the first basic block of the exception handler. + */ + public Block getCatcher() { + return catcher; + } + + /** + * Gets the class name of the exception type. + */ + public String getType() { + return type; + } + + public void setStart(Block start) { + this.start = start; + } + + public void setEnd(Block end) { + this.end = end; + } + + public void setCatcher(Block catcher) { + this.catcher = catcher; + } + + /** + * Sets the class name of the exception type. + */ + public void setType(String type) { + this.type = type; + } +} + diff --git a/jode/src/net/sf/jode/bytecode/IncInstruction.java b/jode/src/net/sf/jode/bytecode/IncInstruction.java new file mode 100644 index 0000000..fa4b2be --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/IncInstruction.java @@ -0,0 +1,60 @@ +/* IncInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * This class represents an instruction in the byte code. + * + */ +class IncInstruction extends SlotInstruction { + /** + * The amount of increment. + */ + private int increment; + + /** + * Creates a simple opcode, without any parameters. + */ + IncInstruction(int opcode, LocalVariableInfo lvi, int increment) { + super(opcode, lvi); + this.increment = increment; + } + + /** + * Get the increment for an opc_iinc instruction. + */ + public final int getIncrement() + { + return increment; + } + + /** + * Set the increment for an opc_iinc instruction. + */ + public final void setIncrement(int incr) + { + this.increment = incr; + } + + + public String toString() { + return super.toString()+' '+increment; + } +} diff --git a/jode/src/net/sf/jode/bytecode/Instruction.java b/jode/src/net/sf/jode/bytecode/Instruction.java new file mode 100644 index 0000000..54c7b0e --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/Instruction.java @@ -0,0 +1,503 @@ +/* Instruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + *

This class represents an instruction in the byte code. + * Instructions can be created with the static {@link #forOpcode} + * methods.

+ * + *

We only allow a subset of opcodes. Other opcodes are mapped to + * their simpler version. Don't worry about this, when writing the + * bytecode the shortest possible bytecode is produced.

+ * + * The opcodes we map are: + *
+ * [iflda]load_x           -> [iflda]load
+ * [iflda]store_x          -> [iflda]store
+ * [ifa]const_xx, ldc_w    -> ldc
+ * [dl]const_xx            -> ldc2_w
+ * wide opcode             -> opcode
+ * tableswitch             -> lookupswitch
+ * [a]newarray             -> multianewarray
+ * 
+ */ +public class Instruction implements Opcodes{ + /** + * The opcode and lineNr of the instruction. + * opcode is (lineAndOpcode & 0xff), while + * lineNr is (lineAndOpcode >> 8). + * If line number is not known or unset, it is -1. + */ + private int lineAndOpcode; + + /** + * Creates a new simple Instruction with no parameters. We map + * some opcodes, so you must always use the mapped opcode. + * @param opcode the opcode of this instruction. + * @exception IllegalArgumentException if opcode is not in our subset + * or if opcode needs a parameter. */ + public static Instruction forOpcode(int opcode) { + switch (opcode) { + case opc_nop: + case opc_iaload: case opc_laload: case opc_faload: + case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iastore: case opc_lastore: case opc_fastore: + case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: + case opc_pop: case opc_pop2: + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + case opc_swap: + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: + case opc_iand: case opc_land: + case opc_ior: case opc_lor: + case opc_ixor: case opc_lxor: + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_lcmp: case opc_fcmpl: case opc_fcmpg: + case opc_dcmpl: case opc_dcmpg: + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: + case opc_return: + case opc_athrow: + case opc_arraylength: + case opc_monitorenter: case opc_monitorexit: + case opc_goto: + case opc_jsr: + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: + return new Instruction(opcode); + default: + throw new IllegalArgumentException("Instruction has a parameter"); + } + } + + + /** + * Creates a new ldc Instruction. + * @param opcode the opcode of this instruction. + * @param constant the constant parameter. + * @exception IllegalArgumentException if opcode is not opc_ldc or + * opc_ldc2_w. + */ + public static Instruction forOpcode(int opcode, Object constant) { + if (opcode == opc_ldc || opcode == opc_ldc2_w) + return new ConstantInstruction(opcode, constant); + throw new IllegalArgumentException("Instruction has no constant"); + } + + /** + * Creates a new Instruction with a local variable as parameter. + * @param opcode the opcode of this instruction. + * @param lvi the local variable parameter. + * @exception IllegalArgumentException if opcode is not in our subset + * or if opcode doesn't need a single local variable as parameter. + */ + public static Instruction forOpcode(int opcode, LocalVariableInfo lvi) { + if (opcode == opc_ret + || opcode >= opc_iload && opcode <= opc_aload + || opcode >= opc_istore && opcode <= opc_astore) + return new SlotInstruction(opcode, lvi); + throw new IllegalArgumentException("Instruction has no slot"); + } + + /** + * Creates a new Instruction with reference as parameter. + * @param opcode the opcode of this instruction. + * @param reference the reference parameter. + * @exception IllegalArgumentException if opcode is not in our subset + * or if opcode doesn't need a reference as parameter. + */ + public static Instruction forOpcode(int opcode, Reference reference) { + if (opcode >= opc_getstatic && opcode <= opc_invokeinterface) + return new ReferenceInstruction(opcode, reference); + throw new IllegalArgumentException("Instruction has no reference"); + } + + /** + * Creates a new Instruction with type signature as parameter. + * @param opcode the opcode of this instruction. + * @param typeSig the type signature parameter. + * @exception IllegalArgumentException if opcode is not in our subset + * or if opcode doesn't need a type signature as parameter. + */ + public static Instruction forOpcode(int opcode, String typeSig) { + switch (opcode) { + case opc_new: + case opc_checkcast: + case opc_instanceof: + return new TypeInstruction(opcode, typeSig); + default: + throw new IllegalArgumentException("Instruction has no type"); + } + } + + /** + * Creates a new switch Instruction. + * @param opcode the opcode of this instruction must be opc_lookupswitch. + * @param values an array containing the different cases. + * @exception IllegalArgumentException if opcode is not opc_lookupswitch. + */ + public static Instruction forOpcode(int opcode, int[] values) { + if (opcode == opc_lookupswitch) + return new SwitchInstruction(opcode, values); + throw new IllegalArgumentException("Instruction has no values"); + } + + /** + * Creates a new increment Instruction. + * @param opcode the opcode of this instruction. + * @param lvi the local variable parameter. + * @param increment the increment parameter. + * @exception IllegalArgumentException if opcode is not opc_iinc. + */ + public static Instruction forOpcode(int opcode, + LocalVariableInfo lvi, int increment) { + if (opcode == opc_iinc) + return new IncInstruction(opcode, lvi, increment); + throw new IllegalArgumentException("Instruction has no increment"); + } + + /** + * Creates a new Instruction with type signature and a dimension + * as parameter. + * @param opcode the opcode of this instruction. + * @param typeSig the type signature parameter. + * @param dimension the array dimension parameter. + * @exception IllegalArgumentException if opcode is not + * opc_multianewarray. + */ + public static Instruction forOpcode(int opcode, + String typeSig, int dimension) { + if (opcode == opc_multianewarray) + return new TypeDimensionInstruction(opcode, typeSig, dimension); + throw new IllegalArgumentException("Instruction has no dimension"); + } + + /** + * Creates a simple opcode, without any parameters. + */ + Instruction(int opcode) { + this.lineAndOpcode = (-1 << 8) | opcode; + } + + /** + * Gets the opcode of the instruction. + * @return the opcode of the instruction. + */ + public final int getOpcode() { + return lineAndOpcode & 0xff; + } + + /** + * Tells whether there is a line number information for this + * instruction. + * @return true if there is a line number information for this + * instruction. + */ + public final boolean hasLineNr() { + return lineAndOpcode >= 0; + } + + /** + * Gets the line number of this instruction. + * @return the line number, or -1 if there isn't one. + */ + public final int getLineNr() { + return lineAndOpcode >> 8; + } + + /** + * Sets the line number of this instruction. + * @param nr the line number; use -1 to clear it. + */ + public final void setLineNr(int nr) { + lineAndOpcode = (nr << 8) | (lineAndOpcode & 0xff); + } + + /** + * Checks whether this instruction is a local store instruction, i.e. + * one of astore, istore, lstore, + * fstore or dstore. + */ + public boolean isStore() { + return false; + } + + /** + * Checks whether this instruction accesses a local slot. + */ + public boolean hasLocal() { + return false; + } + + /** + * Gets the slot number of the local this instruction accesses. + * @return the slot number. + * @throws IllegalArgumentException if this instruction doesn't + * access a local slot. + */ + public int getLocalSlot() + { + throw new IllegalArgumentException(); + // UnsupportedOperationException would be more appropriate + } + + /** + * Gets the information of the local this instruction accesses. + * @return the local variable info. + * @throws IllegalArgumentException if this instruction doesn't + * access a local. + */ + public LocalVariableInfo getLocalInfo() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the information of the local this instruction accesses. + * @param info the local variable info. + * @throws IllegalArgumentException if this instruction doesn't + * access a local. + */ + public void setLocalInfo(LocalVariableInfo info) + { + throw new IllegalArgumentException(); + } + + /** + * Sets the slot of the local this instruction accesses. + * @param slot the local slot + * @throws IllegalArgumentException if this instruction doesn't + * access a local. + */ + public void setLocalSlot(int slot) + { + throw new IllegalArgumentException(); + } + + /** + * Gets the increment for an opc_iinc instruction. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public int getIncrement() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the increment for an opc_iinc instruction. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public void setIncrement(int incr) + { + throw new IllegalArgumentException(); + } + + /** + * Gets the dimensions for an opc_multianewarray opcode. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public int getDimensions() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the dimensions for an opc_multianewarray opcode. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public void setDimensions(int dims) + { + throw new IllegalArgumentException(); + } + + /** + * Gets the constant for a opc_ldc or opc_ldc2_w opcode. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public Object getConstant() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the constant for a opc_ldc or opc_ldc2_w opcode. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public void setConstant(Object constant) + { + throw new IllegalArgumentException(); + } + + /** + * Gets the reference of the field or method this instruction accesses. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public Reference getReference() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the reference of the field or method this instruction accesses. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public void setReference(Reference ref) + { + throw new IllegalArgumentException(); + } + + /** + * Gets the class type this instruction uses, e.g if its a class cast. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public String getClazzType() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the class type this instruction uses, e.g if its a class cast. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public void setClazzType(String type) + { + throw new IllegalArgumentException(); + } + + /** + * Gets the values of a opc_lookupswitch opcode. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public int[] getValues() + { + throw new IllegalArgumentException(); + } + + /** + * Sets the values of a opc_lookupswitch opcode. + * @throws IllegalArgumentException if this instruction doesn't + * support this. + */ + public void setValues(int[] values) + { + throw new IllegalArgumentException(); + } + + /** + * Checks whether this instruction always changes program flow. + * Returns false for opc_jsr it. + * @return true if this instruction always changes flow, i.e. if + * its an unconditional jump, a return, a throw, a ret or a switch. + */ + public final boolean doesAlwaysJump() { + switch (getOpcode()) { + case opc_ret: + case opc_goto: + case opc_lookupswitch: + case opc_ireturn: + case opc_lreturn: + case opc_freturn: + case opc_dreturn: + case opc_areturn: + case opc_return: + case opc_athrow: + return true; + default: + return false; + } + } + + /** + * This returns the number of stack entries this instruction + * pushes and pops from the stack. The result fills the given + * array. + * + * @param poppush an array of two ints. The first element will + * get the number of pops, the second the number of pushes. + */ + public void getStackPopPush(int[] poppush) + /*{ require { poppush != null && poppush.length == 2 + :: "poppush must be an array of two ints" } } */ + { + byte delta = (byte) stackDelta.charAt(getOpcode()); + poppush[0] = delta & 7; + poppush[1] = delta >> 3; + } + + /** + * Gets a printable representation of the opcode with its + * parameters. This will not include the destination for jump + * instructions, since this information is not stored inside the + * instruction. + */ + public final String getDescription() { + return toString(); + } + + /** + * Gets a printable representation of the opcode with its + * parameters. This will not include the destination for jump + * instructions, since this information is not stored inside the + * instruction. + */ + public String toString() { + return opcodeString[getOpcode()]; + } + + /** + * stackDelta contains \100 if stack count of opcode is variable + * \177 if opcode is illegal, or 8*stack_push + stack_pop otherwise + * The string is created by scripts/createStackDelta.pl + */ + final static String stackDelta = + "\000\010\010\010\010\010\010\010\010\020\020\010\010\010\020\020\010\010\010\010\020\010\020\010\020\010\010\010\010\010\020\020\020\020\010\010\010\010\020\020\020\020\010\010\010\010\012\022\012\022\012\012\012\012\001\002\001\002\001\001\001\001\001\002\002\002\002\001\001\001\001\002\002\002\002\001\001\001\001\003\004\003\004\003\003\003\003\001\002\021\032\043\042\053\064\022\012\024\012\024\012\024\012\024\012\024\012\024\012\024\012\024\012\024\012\024\011\022\011\022\012\023\012\023\012\023\012\024\012\024\012\024\000\021\011\021\012\012\022\011\021\021\012\022\012\011\011\011\014\012\012\014\014\001\001\001\001\001\001\002\002\002\002\002\002\002\002\000\000\000\001\001\001\002\001\002\001\000\100\100\100\100\100\100\100\100\177\010\011\011\011\001\011\011\001\001\177\100\001\001\000\000"; +} diff --git a/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java b/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java new file mode 100644 index 0000000..3d878f5 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/LocalVariableInfo.java @@ -0,0 +1,135 @@ +/* LocalVariableInfo Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import net.sf.jode.util.UnifyHash; +///#def COLLECTIONS java.util +import java.util.Iterator; +///#enddef + +/** + * A simple class containing the info of the LocalVariableTable. This + * info is stored decentral: every load, store or iinc instruction contains + * the info for its local. When writing code it will automatically be + * collected again.
+ * + * You can't modify a LocalVariableInfo, for this reason they can and + * will be shared.
+ * + * This information consists of name, type signature and slot number. + * There is no public constructor; use the static getInfo() methods + * instead. + */ +public final class LocalVariableInfo { + private String name, type; + private int slot; + private static LocalVariableInfo anonymous[]; + static { + grow(5); + } + private static final UnifyHash unifier = new UnifyHash(); + + private LocalVariableInfo(int slot) { + this.slot = slot; + } + + private LocalVariableInfo(int slot, String name, String type) { + this.slot = slot; + this.name = name; + this.type = type; + } + + private static void grow(int upper) { + LocalVariableInfo[] newAnon = new LocalVariableInfo[upper]; + int start = 0; + if (anonymous != null) { + start = anonymous.length; + System.arraycopy(anonymous, 0, newAnon, 0, start); + } + anonymous = newAnon; + for (int i=start; i< upper; i++) + anonymous[i] = new LocalVariableInfo(i); + } + + /** + * Creates a new local variable info, with no name or type. + * @param slot the slot number. + */ + public static LocalVariableInfo getInfo(int slot) { + if (slot >= anonymous.length) + grow(Math.max(slot + 1, anonymous.length * 2)); + return anonymous[slot]; + } + + /** + * Creates a new local variable info, with given name and type. + * @param slot the slot number. + * @param name the name of the local. + * @param type the type signature of the local. + */ + public static LocalVariableInfo getInfo(int slot, + String name, String type) { + if (name == null && type == null) + return getInfo(slot); + int hash = slot ^ name.hashCode() ^ type.hashCode(); + Iterator iter = unifier.iterateHashCode(hash); + while (iter.hasNext()) { + LocalVariableInfo lvi = (LocalVariableInfo) iter.next(); + if (lvi.slot == slot + && lvi.name.equals(name) + && lvi.type.equals(type)) + return lvi; + } + LocalVariableInfo lvi = new LocalVariableInfo(slot, name, type); + unifier.put(hash, lvi); + return lvi; + } + + /** + * Gets the slot number. + */ + public int getSlot() { + return slot; + } + + /** + * Gets the name. + */ + public String getName() { + return name; + } + + /** + * Gets the type signature. + * @see TypeSignature + */ + public String getType() { + return type; + } + + /** + * Gets a string representation for debugging purposes. + */ + public String toString() { + String result = ""+slot; + if (name != null) + result += " ["+name+","+type+"]"; + return result; + } +} diff --git a/jode/src/net/sf/jode/bytecode/MethodInfo.java b/jode/src/net/sf/jode/bytecode/MethodInfo.java new file mode 100644 index 0000000..5814315 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/MethodInfo.java @@ -0,0 +1,339 @@ +/* MethodInfo Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; +///#def COLLECTIONEXTRA java.lang +import java.lang.Comparable; +///#enddef + +/** + * Represents a java bytecode method. A method consists of the following + * parts: + * + *
+ * + *
name
The method's name
+ * + *
type
The method's {@link TypeSignature type signature} + * in bytecode format.
+ * + *
signature
The method's {@link TypeSignature type signature} + * in bytecode format including template information.
+ * + *
modifiers
The modifiers of the field like private, public etc. + * These are created by or-ing the constants {@link Modifier#PUBLIC}, + * {@link Modifier#PRIVATE}, {@link Modifier#PROTECTED}, + * {@link Modifier#STATIC}, {@link Modifier#FINAL}, + * {@link Modifier#SYNCHRONIZED}, {@link Modifier#NATIVE}, + * {@link Modifier#ABSTRACT}, {@link Modifier#STRICT} + * of class {@link java.lang.reflect.Modifier}. + * + *
basicblocks
the bytecode of the method in form of + * {@link BasicBlocks basic blocks}, null if it is native or + * abstract.
+ * + *
synthetic
true if this method is synthetic
+ * + *
deprecated
true if this method is deprecated
+ * + *
exceptions
the exceptions that the method declared in + * its throws clause
+ * + *
+ * + * @author Jochen Hoenicke + * @see net.sf.jode.bytecode.TypeSignature + * @see net.sf.jode.bytecode.BasicBlocks + */ +public final class MethodInfo extends BinaryInfo implements Comparable { + int modifier; + String name; + String typeSig; + + BasicBlocks basicblocks; + String[] exceptions; + boolean deprecatedFlag; + /** + * The type signature that also contains template information. + */ + private String signature; + + public MethodInfo() { + } + + public MethodInfo(String name, String typeSig, int modifier) { + this.name = name; + this.typeSig = typeSig; + this.modifier = modifier; + } + + protected void readAttribute + (String name, int length, ConstantPool cp, + DataInputStream input, int howMuch) throws IOException { + if (howMuch >= ClassInfo.NODEBUG && name.equals("Code")) { + basicblocks = new BasicBlocks(this); + basicblocks.read(cp, input, howMuch); + } else if (howMuch >= ClassInfo.DECLARATIONS + && name.equals("Exceptions")) { + int count = input.readUnsignedShort(); + exceptions = new String[count]; + for (int i = 0; i < count; i++) + exceptions[i] = cp.getClassName(input.readUnsignedShort()); + if (length != 2 * (count + 1)) + throw new ClassFormatException + ("Exceptions attribute has wrong length"); + } else if (name.equals("Synthetic")) { + modifier |= ACC_SYNTHETIC; + if (length != 0) + throw new ClassFormatException + ("Synthetic attribute has wrong length"); + } else if (name.equals("Deprecated")) { + deprecatedFlag = true; + if (length != 0) + throw new ClassFormatException + ("Deprecated attribute has wrong length"); + } else if (name.equals("Signature")) { + signature = cp.getUTF8(input.readUnsignedShort()); + } else + super.readAttribute(name, length, cp, input, howMuch); + } + + void read(ConstantPool constantPool, + DataInputStream input, int howMuch) throws IOException { + modifier = input.readUnsignedShort(); + name = constantPool.getUTF8(input.readUnsignedShort()); + typeSig = constantPool.getUTF8(input.readUnsignedShort()); + readAttributes(constantPool, input, howMuch); + } + + void reserveSmallConstants(GrowableConstantPool gcp) { + if (basicblocks != null) + basicblocks.reserveSmallConstants(gcp); + } + + void prepareWriting(GrowableConstantPool gcp) { + gcp.putUTF8(name); + gcp.putUTF8(typeSig); + if (basicblocks != null) { + gcp.putUTF8("Code"); + basicblocks.prepareWriting(gcp); + } + if (exceptions != null) { + gcp.putUTF8("Exceptions"); + for (int i = 0; i < exceptions.length; i++) + gcp.putClassName(exceptions[i]); + } + if (isSynthetic()) + gcp.putUTF8("Synthetic"); + if (deprecatedFlag) + gcp.putUTF8("Deprecated"); + if (signature != null) { + gcp.putUTF8("Signature"); + gcp.putUTF8(signature); + } + prepareAttributes(gcp); + } + + protected int getAttributeCount() { + int count = super.getAttributeCount(); + if (basicblocks != null) + count++; + if (exceptions != null) + count++; + if (isSynthetic()) + count++; + if (deprecatedFlag) + count++; + return count; + } + + protected void writeAttributes(GrowableConstantPool gcp, + DataOutputStream output) + throws IOException { + super.writeAttributes(gcp, output); + if (basicblocks != null) { + output.writeShort(gcp.putUTF8("Code")); + basicblocks.write(gcp, output); + } + if (exceptions != null) { + int count = exceptions.length; + output.writeShort(gcp.putUTF8("Exceptions")); + output.writeInt(2 + count * 2); + output.writeShort(count); + for (int i = 0; i < count; i++) + output.writeShort(gcp.putClassName(exceptions[i])); + } + if (isSynthetic()) { + output.writeShort(gcp.putUTF8("Synthetic")); + output.writeInt(0); + } + if (deprecatedFlag) { + output.writeShort(gcp.putUTF8("Deprecated")); + output.writeInt(0); + } + if (signature != null) { + output.writeShort(gcp.putUTF8("Signature")); + output.writeInt(2); + output.writeShort(gcp.putUTF8(signature)); + } + } + + void write(GrowableConstantPool constantPool, + DataOutputStream output) throws IOException { + output.writeShort(modifier); + output.writeShort(constantPool.putUTF8(name)); + output.writeShort(constantPool.putUTF8(typeSig)); + writeAttributes(constantPool, output); + } + + protected void drop(int keep) { + if (keep < ClassInfo.DECLARATIONS) + exceptions = null; + if (keep < ClassInfo.NODEBUG) + basicblocks = null; + else + basicblocks.drop(keep); + super.drop(keep); + } + + public String getName() { + return name; + } + + public String getType() { + return typeSig; + } + + /** + * Gets the type signature including template information of the method. + * WARNING: This field may disappear and merged into getType later. + * @return the type signature. + * @see TypeSignature + */ + public String getSignature() { + return signature != null ? signature : typeSig; + } + + public int getModifiers() { + return modifier; + } + + public boolean isConstructor() { + return name.charAt(0) == '<'; + } + + public boolean isStatic() { + return Modifier.isStatic(modifier); + } + + public boolean isSynthetic() { + return (modifier & ACC_SYNTHETIC) != 0; + } + + public boolean isDeprecated() { + return deprecatedFlag; + } + + public BasicBlocks getBasicBlocks() { + return basicblocks; + } + + public String[] getExceptions() { + return exceptions; + } + + public void setName(String newName) { + name = newName; + } + + public void setType(String newType) { + typeSig = newType; + } + + public void setModifiers(int newModifier) { + modifier = newModifier; + } + + public void setSynthetic(boolean flag) { + if (flag) + modifier |= ACC_SYNTHETIC; + else + modifier &= ~ACC_SYNTHETIC; + } + + public void setDeprecated(boolean flag) { + deprecatedFlag = flag; + } + + public void setBasicBlocks(BasicBlocks newBasicblocks) { + basicblocks = newBasicblocks; + } + + public void setExceptions(String[] newExceptions) { + exceptions = newExceptions; + } + + /** + * Compares two MethodInfo objects for method order. The method + * order is as follows: First the static class intializer followed + * by constructor with type signature sorted lexicographic. Then + * all other methods sorted lexicographically by name. If two + * methods have the same name, they are sorted by type signature. + * + * @return a positive number if this method follows the other in + * method order, a negative number if it preceeds the + * other, and 0 if they are equal. + * @exception ClassCastException if other is not a ClassInfo. + */ + public int compareTo(Object other) { + MethodInfo mi = (MethodInfo) other; + /* Normally constructors should automatically sort themself to + * the beginning, but if method name starts with a digit, the + * order would be destroyed. + * + * The JVM explicitly forbids methods starting with digits, + * nonetheless some obfuscators break this rule. + * + * But note that comes lexicographically before . + */ + if (name.charAt(0) != mi.name.charAt(0)) { + if (name.charAt(0) == '<') + return -1; + if (mi.name.charAt(0) == '<') + return 1; + } + int result = name.compareTo(mi.name); + if (result == 0) + result = typeSig.compareTo(mi.typeSig); + return result; + } + + /** + * Returns a string representation of this method. It consists + * of the method's name and type signature. + * @return a string representation of this method. + */ + public String toString() { + return "MethodInfo[name="+name+",sig="+getSignature()+"]"; + } +} diff --git a/jode/src/net/sf/jode/bytecode/Opcodes.java b/jode/src/net/sf/jode/bytecode/Opcodes.java new file mode 100644 index 0000000..826265b --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/Opcodes.java @@ -0,0 +1,280 @@ +/* Opcodes Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * This is an interface containing the constants for the byte code opcodes. + */ +public interface Opcodes { + public final static int opc_nop = 0; + public final static int opc_aconst_null = 1; + public final static int opc_iconst_m1 = 2; + public final static int opc_iconst_0 = 3; + public final static int opc_iconst_1 = 4; + public final static int opc_iconst_2 = 5; + public final static int opc_iconst_3 = 6; + public final static int opc_iconst_4 = 7; + public final static int opc_iconst_5 = 8; + public final static int opc_lconst_0 = 9; + public final static int opc_lconst_1 = 10; + public final static int opc_fconst_0 = 11; + public final static int opc_fconst_1 = 12; + public final static int opc_fconst_2 = 13; + public final static int opc_dconst_0 = 14; + public final static int opc_dconst_1 = 15; + public final static int opc_bipush = 16; + public final static int opc_sipush = 17; + public final static int opc_ldc = 18; + public final static int opc_ldc_w = 19; + public final static int opc_ldc2_w = 20; + public final static int opc_iload = 21; + public final static int opc_lload = 22; + public final static int opc_fload = 23; + public final static int opc_dload = 24; + public final static int opc_aload = 25; + public final static int opc_iload_0 = 26; + public final static int opc_iload_1 = 27; + public final static int opc_iload_2 = 28; + public final static int opc_iload_3 = 29; + public final static int opc_lload_0 = 30; + public final static int opc_lload_1 = 31; + public final static int opc_lload_2 = 32; + public final static int opc_lload_3 = 33; + public final static int opc_fload_0 = 34; + public final static int opc_fload_1 = 35; + public final static int opc_fload_2 = 36; + public final static int opc_fload_3 = 37; + public final static int opc_dload_0 = 38; + public final static int opc_dload_1 = 39; + public final static int opc_dload_2 = 40; + public final static int opc_dload_3 = 41; + public final static int opc_aload_0 = 42; + public final static int opc_aload_1 = 43; + public final static int opc_aload_2 = 44; + public final static int opc_aload_3 = 45; + public final static int opc_iaload = 46; + public final static int opc_laload = 47; + public final static int opc_faload = 48; + public final static int opc_daload = 49; + public final static int opc_aaload = 50; + public final static int opc_baload = 51; + public final static int opc_caload = 52; + public final static int opc_saload = 53; + public final static int opc_istore = 54; + public final static int opc_lstore = 55; + public final static int opc_fstore = 56; + public final static int opc_dstore = 57; + public final static int opc_astore = 58; + public final static int opc_istore_0 = 59; + public final static int opc_istore_1 = 60; + public final static int opc_istore_2 = 61; + public final static int opc_istore_3 = 62; + public final static int opc_lstore_0 = 63; + public final static int opc_lstore_1 = 64; + public final static int opc_lstore_2 = 65; + public final static int opc_lstore_3 = 66; + public final static int opc_fstore_0 = 67; + public final static int opc_fstore_1 = 68; + public final static int opc_fstore_2 = 69; + public final static int opc_fstore_3 = 70; + public final static int opc_dstore_0 = 71; + public final static int opc_dstore_1 = 72; + public final static int opc_dstore_2 = 73; + public final static int opc_dstore_3 = 74; + public final static int opc_astore_0 = 75; + public final static int opc_astore_1 = 76; + public final static int opc_astore_2 = 77; + public final static int opc_astore_3 = 78; + public final static int opc_iastore = 79; + public final static int opc_lastore = 80; + public final static int opc_fastore = 81; + public final static int opc_dastore = 82; + public final static int opc_aastore = 83; + public final static int opc_bastore = 84; + public final static int opc_castore = 85; + public final static int opc_sastore = 86; + public final static int opc_pop = 87; + public final static int opc_pop2 = 88; + public final static int opc_dup = 89; + public final static int opc_dup_x1 = 90; + public final static int opc_dup_x2 = 91; + public final static int opc_dup2 = 92; + public final static int opc_dup2_x1 = 93; + public final static int opc_dup2_x2 = 94; + public final static int opc_swap = 95; + public final static int opc_iadd = 96; + public final static int opc_ladd = 97; + public final static int opc_fadd = 98; + public final static int opc_dadd = 99; + public final static int opc_isub = 100; + public final static int opc_lsub = 101; + public final static int opc_fsub = 102; + public final static int opc_dsub = 103; + public final static int opc_imul = 104; + public final static int opc_lmul = 105; + public final static int opc_fmul = 106; + public final static int opc_dmul = 107; + public final static int opc_idiv = 108; + public final static int opc_ldiv = 109; + public final static int opc_fdiv = 110; + public final static int opc_ddiv = 111; + public final static int opc_irem = 112; + public final static int opc_lrem = 113; + public final static int opc_frem = 114; + public final static int opc_drem = 115; + public final static int opc_ineg = 116; + public final static int opc_lneg = 117; + public final static int opc_fneg = 118; + public final static int opc_dneg = 119; + public final static int opc_ishl = 120; + public final static int opc_lshl = 121; + public final static int opc_ishr = 122; + public final static int opc_lshr = 123; + public final static int opc_iushr = 124; + public final static int opc_lushr = 125; + public final static int opc_iand = 126; + public final static int opc_land = 127; + public final static int opc_ior = 128; + public final static int opc_lor = 129; + public final static int opc_ixor = 130; + public final static int opc_lxor = 131; + public final static int opc_iinc = 132; + public final static int opc_i2l = 133; + public final static int opc_i2f = 134; + public final static int opc_i2d = 135; + public final static int opc_l2i = 136; + public final static int opc_l2f = 137; + public final static int opc_l2d = 138; + public final static int opc_f2i = 139; + public final static int opc_f2l = 140; + public final static int opc_f2d = 141; + public final static int opc_d2i = 142; + public final static int opc_d2l = 143; + public final static int opc_d2f = 144; + public final static int opc_i2b = 145; + public final static int opc_i2c = 146; + public final static int opc_i2s = 147; + public final static int opc_lcmp = 148; + public final static int opc_fcmpl = 149; + public final static int opc_fcmpg = 150; + public final static int opc_dcmpl = 151; + public final static int opc_dcmpg = 152; + public final static int opc_ifeq = 153; + public final static int opc_ifne = 154; + public final static int opc_iflt = 155; + public final static int opc_ifge = 156; + public final static int opc_ifgt = 157; + public final static int opc_ifle = 158; + public final static int opc_if_icmpeq = 159; + public final static int opc_if_icmpne = 160; + public final static int opc_if_icmplt = 161; + public final static int opc_if_icmpge = 162; + public final static int opc_if_icmpgt = 163; + public final static int opc_if_icmple = 164; + public final static int opc_if_acmpeq = 165; + public final static int opc_if_acmpne = 166; + public final static int opc_goto = 167; + public final static int opc_jsr = 168; + public final static int opc_ret = 169; + public final static int opc_tableswitch = 170; + public final static int opc_lookupswitch = 171; + public final static int opc_ireturn = 172; + public final static int opc_lreturn = 173; + public final static int opc_freturn = 174; + public final static int opc_dreturn = 175; + public final static int opc_areturn = 176; + public final static int opc_return = 177; + public final static int opc_getstatic = 178; + public final static int opc_putstatic = 179; + public final static int opc_getfield = 180; + public final static int opc_putfield = 181; + public final static int opc_invokevirtual = 182; + public final static int opc_invokespecial = 183; + public final static int opc_invokestatic = 184; + public final static int opc_invokeinterface = 185; + public final static int opc_xxxunusedxxx = 186; + public final static int opc_new = 187; + public final static int opc_newarray = 188; + public final static int opc_anewarray = 189; + public final static int opc_arraylength = 190; + public final static int opc_athrow = 191; + public final static int opc_checkcast = 192; + public final static int opc_instanceof = 193; + public final static int opc_monitorenter = 194; + public final static int opc_monitorexit = 195; + public final static int opc_wide = 196; + public final static int opc_multianewarray = 197; + public final static int opc_ifnull = 198; + public final static int opc_ifnonnull = 199; + public final static int opc_goto_w = 200; + public final static int opc_jsr_w = 201; + public final static int opc_breakpoint = 202; + public final static int opc_impdep1 = 254; + public final static int opc_impdep2 = 255; + + public final static String[] opcodeString = { + "nop", "aconst_null", "iconst_m1", "iconst_0", "iconst_1", + "iconst_2", "iconst_3", "iconst_4", "iconst_5", "lconst_0", + "lconst_1", "fconst_0", "fconst_1", "fconst_2", "dconst_0", + "dconst_1", "bipush", "sipush", "ldc", "ldc_w", "ldc2_w", + "iload", "lload", "fload", "dload", "aload", "iload_0", + "iload_1", "iload_2", "iload_3", "lload_0", "lload_1", "lload_2", + "lload_3", "fload_0", "fload_1", "fload_2", "fload_3", "dload_0", + "dload_1", "dload_2", "dload_3", "aload_0", "aload_1", "aload_2", + "aload_3", "iaload", "laload", "faload", "daload", "aaload", + "baload", "caload", "saload", "istore", "lstore", "fstore", + "dstore", "astore", "istore_0", "istore_1", "istore_2", "istore_3", + "lstore_0", "lstore_1", "lstore_2", "lstore_3", "fstore_0", + "fstore_1", "fstore_2", "fstore_3", "dstore_0", "dstore_1", + "dstore_2", "dstore_3", "astore_0", "astore_1", "astore_2", + "astore_3", "iastore", "lastore", "fastore", "dastore", "aastore", + "bastore", "castore", "sastore", "pop", "pop2", "dup", "dup_x1", + "dup_x2", "dup2", "dup2_x1", "dup2_x2", "swap", "iadd", "ladd", + "fadd", "dadd", "isub", "lsub", "fsub", "dsub", "imul", "lmul", + "fmul", "dmul", "idiv", "ldiv", "fdiv", "ddiv", "irem", "lrem", + "frem", "drem", "ineg", "lneg", "fneg", "dneg", "ishl", "lshl", + "ishr", "lshr", "iushr", "lushr", "iand", "land", "ior", "lor", + "ixor", "lxor", "iinc", "i2l", "i2f", "i2d", "l2i", "l2f", "l2d", + "f2i", "f2l", "f2d", "d2i", "d2l", "d2f", "i2b", "i2c", "i2s", + "lcmp", "fcmpl", "fcmpg", "dcmpl", "dcmpg", "ifeq", "ifne", "iflt", + "ifge", "ifgt", "ifle", "if_icmpeq", "if_icmpne", "if_icmplt", + "if_icmpge", "if_icmpgt", "if_icmple", "if_acmpeq", "if_acmpne", + "goto", "jsr", "ret", "tableswitch", "lookupswitch", "ireturn", + "lreturn", "freturn", "dreturn", "areturn", "return", "getstatic", + "putstatic", "getfield", "putfield", "invokevirtual", + "invokespecial", "invokestatic", "invokeinterface", "xxxunusedxxx", + "new", "newarray", "anewarray", "arraylength", "athrow", "checkcast", + "instanceof", "monitorenter", "monitorexit", "wide", "multianewarray", + "ifnull", "ifnonnull", "goto_w", "jsr_w", "breakpoint" + }; + + + public final static String newArrayTypes = "ZCFDBSIJ"; + + public final static Object[] constants = { + null, + new Integer(-1), new Integer(0), new Integer(1), + new Integer(2), new Integer(3), new Integer(4), new Integer(5), + new Long(0), new Long(1), + new Float(0), new Float(1), new Float(2), + new Double(0), new Double(1) + }; + +} diff --git a/jode/src/net/sf/jode/bytecode/Reference.java b/jode/src/net/sf/jode/bytecode/Reference.java new file mode 100644 index 0000000..56c512e --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/Reference.java @@ -0,0 +1,79 @@ +/* Reference Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import net.sf.jode.util.UnifyHash; +///#def COLLECTIONS java.util +import java.util.Iterator; +///#enddef + +/** + * This class represents a field or method reference. It consists of + * the class name the method/field name and the type signature. + */ +public class Reference { + /** + * A reference consists of a class name, a member name and a type. + */ + private final String clazz, name, type; + + private static final UnifyHash unifier = new UnifyHash(); + + public static Reference getReference(String className, + String name, String type) { + int hash = className.hashCode() ^ name.hashCode() ^ type.hashCode(); + Iterator iter = unifier.iterateHashCode(hash); + while (iter.hasNext()) { + Reference ref = (Reference) iter.next(); + if (ref.clazz.equals(className) + && ref.name.equals(name) + && ref.type.equals(type)) + return ref; + } + Reference ref = new Reference(className, name, type); + unifier.put(hash, ref); + return ref; + } + + private Reference(String clazz, String name, String type) { + this.clazz = clazz; + this.name = name; + this.type = type; + } + + public String getClazz() { + return clazz; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public String toString() { + String classStr = clazz; + if (clazz.startsWith("L")) + classStr = clazz.substring(1, clazz.length() - 1) + .replace('/', '.'); + return classStr + "." + name + " " + type; + } +} diff --git a/jode/src/net/sf/jode/bytecode/ReferenceInstruction.java b/jode/src/net/sf/jode/bytecode/ReferenceInstruction.java new file mode 100644 index 0000000..a88969f --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/ReferenceInstruction.java @@ -0,0 +1,86 @@ +/* ReferenceInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * This class represents an instruction that needs a reference, i.e. + * a method invocation or field access instruction. + */ +class ReferenceInstruction extends Instruction { + private Reference reference; + + public ReferenceInstruction(int opcode, Reference ref) { + super(opcode); + this.reference = ref; + } + + public final Reference getReference() + { + return reference; + } + + public final void setReference(Reference ref) + { + reference = ref; + } + + /** + * This returns the number of stack entries this instruction + * pushes and pops from the stack. The result fills the given + * array. + * + * @param poppush an array of two ints. The first element will + * get the number of pops, the second the number of pushes. + */ + public void getStackPopPush(int[] poppush) + /*{ require { poppush != null && poppush.length == 2 + :: "poppush must be an array of two ints" } } */ + { + String typeSig = reference.getType(); + int opcode = getOpcode(); + switch (opcode) { + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + poppush[0] = opcode != opc_invokestatic ? 1 : 0; + poppush[0] += TypeSignature.getParameterSize(typeSig); + poppush[1] = TypeSignature.getReturnSize(typeSig); + break; + + case opc_putfield: + case opc_putstatic: + poppush[1] = 0; + poppush[0] = TypeSignature.getTypeSize(typeSig); + if (opcode == opc_putfield) + poppush[0]++; + break; + + case opc_getstatic: + case opc_getfield: + poppush[1] = TypeSignature.getTypeSize(typeSig); + poppush[0] = opcode == opc_getfield ? 1 : 0; + break; + } + } + public String toString() { + return super.toString()+' '+reference; + } +} diff --git a/jode/src/net/sf/jode/bytecode/SlotInstruction.java b/jode/src/net/sf/jode/bytecode/SlotInstruction.java new file mode 100644 index 0000000..054a6c5 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/SlotInstruction.java @@ -0,0 +1,72 @@ +/* SlotInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * This class represents an instruction in the byte code. + * + */ +class SlotInstruction extends Instruction { + private LocalVariableInfo lvi; + + /** + */ + public SlotInstruction(int opcode, LocalVariableInfo lvi) { + super(opcode); + this.lvi = lvi; + } + + public boolean isStore() { + int opcode = getOpcode(); + return opcode >= opc_istore && opcode <= opc_astore; + } + + public boolean hasLocal() { + return true; + } + + public final int getLocalSlot() + { + return lvi.getSlot(); + } + + public final LocalVariableInfo getLocalInfo() + { + return lvi; + } + + public final void setLocalInfo(LocalVariableInfo info) + { + this.lvi = info; + } + + public final void setLocalSlot(int slot) + { + if (lvi.getName() == null) + this.lvi = LocalVariableInfo.getInfo(slot); + else + this.lvi = LocalVariableInfo.getInfo(slot, + lvi.getName(), lvi.getType()); + } + + public String toString() { + return super.toString()+' '+lvi; + } +} diff --git a/jode/src/net/sf/jode/bytecode/SwitchInstruction.java b/jode/src/net/sf/jode/bytecode/SwitchInstruction.java new file mode 100644 index 0000000..66a29af --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/SwitchInstruction.java @@ -0,0 +1,58 @@ +/* SwitchInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import net.sf.jode.util.StringQuoter; + +/** + * This class represents an instruction in the byte code. + * + */ +class SwitchInstruction extends Instruction { + /** + * The values for this switch. + */ + private int[] values; + + /** + * Standard constructor: creates an opcode with parameter and + * lineNr. + */ + SwitchInstruction(int opcode, int[] values) { + super(opcode); + this.values = values; + } + + public final int[] getValues() + { + return values; + } + + public final void setValues(int[] values) + { + this.values = values; + } + + public String toString() { + StringBuffer sb = new StringBuffer(opcodeString[getOpcode()]); + for (int i=0; i< values.length; i++) + sb.append(' ').append(values[i]); + return sb.toString(); + } +} diff --git a/jode/src/net/sf/jode/bytecode/TypeDimensionInstruction.java b/jode/src/net/sf/jode/bytecode/TypeDimensionInstruction.java new file mode 100644 index 0000000..1516da4 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/TypeDimensionInstruction.java @@ -0,0 +1,72 @@ +/* TypeDimensionInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * This class represents an opc_multianewarray instruction. + * + */ +class TypeDimensionInstruction extends TypeInstruction { + /** + * The dimension of this multianewarray operation. + */ + private int dimension; + + public TypeDimensionInstruction(int opcode, String type, int dimension) { + super(opcode, type); + this.dimension = dimension; + } + + /** + * Get the dimensions for an opc_anewarray opcode. + */ + public final int getDimensions() + { + return dimension; + } + + /** + * Get the dimensions for an opc_anewarray opcode. + */ + public final void setDimensions(int dim) + { + dimension = dim; + } + + /** + * This returns the number of stack entries this instruction + * pushes and pops from the stack. The result fills the given + * array. + * + * @param poppush an array of two ints. The first element will + * get the number of pops, the second the number of pushes. + */ + public void getStackPopPush(int[] poppush) + /*{ require { poppush != null && poppush.length == 2 + :: "poppush must be an array of two ints" } } */ + { + poppush[0] = dimension; + poppush[1] = 1; + } + + public String toString() { + return super.toString()+' '+dimension; + } +} diff --git a/jode/src/net/sf/jode/bytecode/TypeInstruction.java b/jode/src/net/sf/jode/bytecode/TypeInstruction.java new file mode 100644 index 0000000..6c54357 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/TypeInstruction.java @@ -0,0 +1,50 @@ +/* TypeInstruction Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; + +/** + * This class represents an instruction in the byte code. + * + */ +class TypeInstruction extends Instruction { + /** + * The typesignature of the class/array. + */ + private String typeSig; + + public TypeInstruction(int opcode, String typeSig) { + super(opcode); + this.typeSig = typeSig; + } + + public final String getClazzType() + { + return typeSig; + } + + public final void setClazzType(String type) + { + typeSig = type; + } + + public String toString() { + return super.toString()+' '+typeSig; + } +} diff --git a/jode/src/net/sf/jode/bytecode/TypeSignature.java b/jode/src/net/sf/jode/bytecode/TypeSignature.java new file mode 100644 index 0000000..4307513 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/TypeSignature.java @@ -0,0 +1,710 @@ +/* TypeSignature Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.bytecode; +import net.sf.jode.util.UnifyHash; +///#def COLLECTIONS java.util +import java.util.Map; +///#enddef + +/** + * This class contains some static methods to handle type signatures.
+ * + * A type signature is a compact textual representation of a java + * types. It is described in the Java Virtual Machine Specification. + * Primitive types have a one letter type signature. Type signature + * of classes contains the class name. Type signatures for arrays and + * methods are recursively build from the type signatures of their + * elements.
Since java 5 there is a new class of type + * signatures supporting generics. These can be accessed with the + * getSignature methods of ClassInfo, MethodInfo and FieldInfo. + * + * Here are a few examples: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
type signatureJava type
Zboolean
Bbyte
Sshort
Cchar
Iint
Ffloat
Jlong
Ddouble
Ljava/lang/Object;java.lang.Object
[[Iint[][]
(Ljava/lang/Object;I)Vmethod with argument types Object and + * int and void return type.
()I method without arguments + * and int return type.
<E:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Collection<TE;>;
generic class over <E extends Object> extending + * Object and implementing Collections<E>
<T:Ljava/lang/Object;>([TT;)[TT;
generic method over <T extends Object> taking an + * array of T as parameters and returning an array of T.
+ * + * + * @author Jochen Hoenicke + */ +public class TypeSignature { + /** + * This is a private method for generating the signature of a + * given type. + */ + private static final StringBuffer appendSignature(StringBuffer sb, + Class javaType) { + if (javaType.isPrimitive()) { + if (javaType == Boolean.TYPE) + return sb.append('Z'); + else if (javaType == Byte.TYPE) + return sb.append('B'); + else if (javaType == Character.TYPE) + return sb.append('C'); + else if (javaType == Short.TYPE) + return sb.append('S'); + else if (javaType == Integer.TYPE) + return sb.append('I'); + else if (javaType == Long.TYPE) + return sb.append('J'); + else if (javaType == Float.TYPE) + return sb.append('F'); + else if (javaType == Double.TYPE) + return sb.append('D'); + else if (javaType == Void.TYPE) + return sb.append('V'); + else + throw new InternalError("Unknown primitive type: "+javaType); + } else if (javaType.isArray()) { + return appendSignature(sb.append('['), + javaType.getComponentType()); + } else { + return sb.append('L') + .append(javaType.getName().replace('.','/')).append(';'); + } + } + + /** + * Generates the type signature of the given Class. + * @param clazz a java.lang.Class, this may also be a primitive or + * array type. + * @return the type signature. + */ + public static String getSignature(Class clazz) { + return appendSignature(new StringBuffer(), clazz).toString(); + } + + /** + * Generates a method signature. + * @param paramT the java.lang.Class of the parameter types of the method. + * @param returnT the java.lang.Class of the return type of the method. + * @return the method type signature + */ + public static String getSignature(Class paramT[], Class returnT) { + StringBuffer sig = new StringBuffer("("); + for (int i=0; i< paramT.length; i++) + appendSignature(sig, paramT[i]); + return appendSignature(sig.append(')'), returnT).toString(); + } + + /** + * Generates a Class object for a type signature. This is the + * inverse function of getSignature. + * @param typeSig a single type signature + * @return the Class object representing that type. + */ + public static Class getClass(String typeSig) + throws ClassNotFoundException + { + switch(typeSig.charAt(0)) { + case 'Z': + return Boolean.TYPE; + case 'B': + return Byte.TYPE; + case 'C': + return Character.TYPE; + case 'S': + return Short.TYPE; + case 'I': + return Integer.TYPE; + case 'F': + return Float.TYPE; + case 'J': + return Long.TYPE; + case 'D': + return Double.TYPE; + case 'V': + return Void.TYPE; + case 'L': + typeSig = typeSig.substring(1, typeSig.length()-1) + .replace('/','.'); + /* fall through */ + case '[': + return Class.forName(typeSig); + } + throw new IllegalArgumentException(typeSig); + } + + /** + * Check if the given type is a two slot type. The only two slot + * types are long and double. + */ + private static boolean usingTwoSlots(char type) { + return "JD".indexOf(type) >= 0; + } + + /** + * Returns the number of words, an object of the given simple type + * signature takes. For long and double this is two, for all other + * types it is one. + */ + public static int getTypeSize(String typeSig) { + return usingTwoSlots(typeSig.charAt(0)) ? 2 : 1; + } + + /** + * Gets the element type of an array. + * @param typeSig type signature of the array. + * @return type signature for the element type. + * @exception IllegalArgumentException if typeSig is not an array + * type signature. + */ + public static String getElementType(String typeSig) { + if (typeSig.charAt(0) != '[') + throw new IllegalArgumentException(); + return typeSig.substring(1); + } + + /** + * Gets the ClassInfo for a class type. + * @param classpath the classpath in which the ClassInfo is searched. + * @param typeSig type signature of the class. + * @return the ClassInfo object for the class. + * @exception IllegalArgumentException if typeSig is not an class + * type signature. + */ + public static ClassInfo getClassInfo(ClassPath classpath, String typeSig) { + if (typeSig.charAt(0) != 'L') + throw new IllegalArgumentException(); + return classpath.getClassInfo + (typeSig.substring(1, typeSig.length()-1).replace('/', '.')); + } + + /** + * Skips the next entry of a method type signature + * @param methodTypeSig type signature of the method. + * @param position the index to the last entry. + * @return the index to the next entry. + */ + public static int skipType(String methodTypeSig, int position) { + char c = methodTypeSig.charAt(position++); + while (c == '[') + c = methodTypeSig.charAt(position++); + if (c == 'L' || c == 'T') { + int angledepth = 0; + c = methodTypeSig.charAt(position++); + while (c != ';' || angledepth > 0) { + if (c == '<') + angledepth++; + else if (c == '>') + angledepth--; + c = methodTypeSig.charAt(position++); + } + } + return position; + } + + /** + * Gets the number of words the parameters for the given method + * type signature takes. This is the sum of getTypeSize() for + * each parameter type. + * @param methodTypeSig the method type signature. + * @return the number of words the parameters take. + */ + public static int getParameterSize(String methodTypeSig) { + int nargs = 0; + int i = 1; + for (;;) { + char c = methodTypeSig.charAt(i); + if (c == ')') + return nargs; + i = skipType(methodTypeSig, i); + if (usingTwoSlots(c)) + nargs += 2; + else + nargs++; + } + } + + /** + * Gets the size of the return type of the given method in words. + * This is zero for void return type, two for double or long return + * type and one otherwise. + * @param methodTypeSig the method type signature. + * @return the size of the return type in words. + */ + public static int getReturnSize(String methodTypeSig) { + int length = methodTypeSig.length(); + if (methodTypeSig.charAt(length - 2) == ')') { + // This is a single character return type. + char returnType = methodTypeSig.charAt(length - 1); + return returnType == 'V' ? 0 + : usingTwoSlots(returnType) ? 2 : 1; + } else + // All multi character return types take one parameter + return 1; + } + + /** + * Gets the argument type signatures of the given template signature. + * @param templateTypeSig the template type signature. + * @return an array containing all parameter types in correct order. + */ + public static String[] getArgumentTypes(String templateTypeSig) { + System.err.println(templateTypeSig); + int pos = 1; + int count = 0; + char c; + while ((c = templateTypeSig.charAt(pos)) != '>') { + if (c == '*') { + pos++; + } else { + if (c == '+' || c == '-') + pos++; + pos = skipType(templateTypeSig, pos); + } + count++; + } + String[] params = new String[count]; + pos = 1; + for (int i = 0; i < count; i++) { + int start = pos; + c = templateTypeSig.charAt(pos); + if (c == '*') { + pos++; + } else { + if (c == '+' || c == '-') + pos++; + pos = skipType(templateTypeSig, pos); + } + params[i] = templateTypeSig.substring(start, pos); + } + return params; + } + + /** + * Gets the parameter type signatures of the given method signature. + * @param methodTypeSig the method type signature. + * @return an array containing all parameter types in correct order. + */ + public static String[] getParameterTypes(String methodTypeSig) { + System.err.println(methodTypeSig); + int pos = 1; + int count = 0; + while (methodTypeSig.charAt(pos) != ')') { + pos = skipType(methodTypeSig, pos); + count++; + } + String[] params = new String[count]; + pos = 1; + for (int i = 0; i < count; i++) { + int start = pos; + pos = skipType(methodTypeSig, pos); + params[i] = methodTypeSig.substring(start, pos); + } + return params; + } + + /** + * Gets the return type for a method signature + * @param methodTypeSig the method signature. + * @return the return type for a method signature, `V' for void methods. + */ + public static String getReturnType(String methodTypeSig) { + return methodTypeSig.substring(methodTypeSig.lastIndexOf(')')+1); + } + + /** + * Gets the names of the generic parameters of the given type signature. + * @param typeSig the type signature. + * @return an array containing all generic parameter types + * in correct order, or null if there aren't any generic parameters. + */ + public static String[] getGenericNames(String typeSig) { + System.err.println(typeSig); + if (typeSig.charAt(0) != '<') + return null; + int pos = 1; + int count = 0; + while (typeSig.charAt(pos) != '>') { + while (typeSig.charAt(pos) != ':') + pos++; + /* check for empty entry */ + if (typeSig.charAt(pos+1) == ':') + pos++; + while (typeSig.charAt(pos) == ':') { + /* skip colon and type */ + pos = skipType(typeSig, pos + 1); + } + count++; + } + String[] params = new String[count]; + pos = 1; + count = 0; + while (typeSig.charAt(pos) != '>') { + int spos = pos; + while (typeSig.charAt(pos) != ':') + pos++; + params[count++] = typeSig.substring(spos, pos); + /* check for empty entry */ + if (typeSig.charAt(pos+1) == ':') + pos++; + while (typeSig.charAt(pos) == ':') { + /* skip colon and type */ + pos = skipType(typeSig, pos + 1); + } + } + return params; + } + + private static int mapGenericsInType(String typeSig, Map generics, + StringBuffer mapped, int spos) { + int pos = spos; + char c = typeSig.charAt(pos++); + while (c == '[') + c = typeSig.charAt(pos++); + if (c == 'T') { + int epos = typeSig.indexOf(';', pos); + String key = typeSig.substring(pos, epos); + String mapval = (String) generics.get(key); + if (mapval != null) { + mapped.append(typeSig.substring(spos, pos - 1)) + .append(key); + spos = epos + 1; + } + pos = epos + 1; + } else if (c == 'L') { + c = typeSig.charAt(pos++); + while (c != ';' && c != '<') + c = typeSig.charAt(pos++); + if (c == '<') { + mapped.append(typeSig.substring(spos, pos)); + while (typeSig.charAt(pos) != '>') { + pos = mapGenericsInType(typeSig, generics, mapped, pos); + } + spos = pos; + pos += 2; + } + } + mapped.append(typeSig.substring(spos, pos)); + return pos; + } + + /** + * Map the names of the generic parameters in the given type signature + * and return the type signature with the generic parameters mapped to + * (more or less) real types. + * @param typeSig the type signature. + * @param generics A map from generic names to type signatures. + * @return the mapped generic type signature. + */ + public static String mapGenerics(String typeSig, Map generics) { + StringBuffer mapped = new StringBuffer(); + int pos = 0; + int spos = 0; + if (typeSig.length() == 0) + return ""; + char c = typeSig.charAt(pos++); + if (c == '<') { + c = typeSig.charAt(pos++); + while (c != '>') { + while (c != ':') { + c = typeSig.charAt(pos++); + } + if (typeSig.charAt(pos) == ':') + pos++; + while (c == ':') { + mapped.append(typeSig.substring(spos, pos)); + pos = mapGenericsInType(typeSig, generics, mapped, pos); + spos = pos; + c = typeSig.charAt(pos); + } + } + } + if (c == '(') { + while (typeSig.charAt(pos) != ')') { + mapped.append(typeSig.substring(spos, pos)); + pos = mapGenericsInType(typeSig, generics, mapped, pos); + spos = pos; + } + pos++; + } + mapped.append(typeSig.substring(spos, pos)); + while (pos < typeSig.length()) { + pos = mapGenericsInType(typeSig, generics, mapped, pos); + } + return mapped.toString(); + } + + /** + * Gets the default value an object of the given type has. It is + * null for objects and arrays, Integer(0) for boolean and short + * integer types or Long(0L), Double(0.0), Float(0.0F) for long, + * double and float. This seems strange, but this way the type + * returned is the same as for FieldInfo.getConstant(). + * + * @param typeSig the type signature. + * @return the default value. + * @exception IllegalArgumentException if this is a method type signature. + */ + public static Object getDefaultValue(String typeSig) { + switch(typeSig.charAt(0)) { + case 'Z': + case 'B': + case 'S': + case 'C': + case 'I': + return new Integer(0); + case 'J': + return new Long(0L); + case 'D': + return new Double(0.0); + case 'F': + return new Float(0.0F); + case 'L': + case '[': + return null; + default: + throw new IllegalArgumentException(typeSig); + } + } + + /** + * Checks if there is a valid class name starting at index + * in string typeSig and ending with a semicolon. + * @return the index at which the class name ends. + * @exception IllegalArgumentException if there was an illegal character. + * @exception StringIndexOutOfBoundsException if the typeSig ended early. + */ + private static int checkClassName(String clName, int i) + throws IllegalArgumentException, StringIndexOutOfBoundsException + { + while (true) { + char c = clName.charAt(i++); + if (c == '<') { + c = clName.charAt(i++); + do { + if (c == '*') + i++; + else { + if (c == '+' || c == '-') + c = clName.charAt(i++); + if (c != 'L' && c != 'T' && c != '[') + throw new IllegalArgumentException + ("Wrong class instantiation: "+clName); + i = checkTypeSig(clName, i - 1); + } + c = clName.charAt(i++); + } while (c != '>'); + c = clName.charAt(i++); + if (c != ';') + throw new IllegalArgumentException + ("no ; after > in "+clName); + } + if (c == ';') + return i; + if (c != '/' && !Character.isJavaIdentifierPart(c)) + throw new IllegalArgumentException("Illegal java class name: " + + clName); + } + } + + /** + * Checks if there is a valid class name starting at index + * in string typeSig and ending with a semicolon. + * @return the index at which the class name ends. + * @exception IllegalArgumentException if there was an illegal character. + * @exception StringIndexOutOfBoundsException if the typeSig ended early. + */ + private static int checkTemplateName(String clName, int i) + throws IllegalArgumentException, StringIndexOutOfBoundsException + { + while (true) { + char c = clName.charAt(i++); + if (c == ';') + return i; + if (!Character.isJavaIdentifierPart(c)) + throw new IllegalArgumentException("Illegal java class name: " + + clName); + } + } + + /** + * Checks if there is a valid simple type signature starting at index + * in string typeSig. + * @return the index at which the type signature ends. + * @exception IllegalArgumentException if there was an illegal character. + * @exception StringIndexOutOfBoundsException if the typeSig ended early. + */ + private static int checkTypeSig(String typeSig, int index) { + char c = typeSig.charAt(index++); + while (c == '[') + c = typeSig.charAt(index++); + if (c == 'L') { + index = checkClassName(typeSig, index); + } else if (c == 'T') { + index = checkTemplateName(typeSig, index); + } else { + if ("ZBSCIJFD".indexOf(c) == -1) + throw new IllegalArgumentException("Type sig error: "+typeSig); + } + return index; + } + + /** + * Checks whether a given type signature starts with valid generic + * part and returns the index where generics end. + * @param typeSig the type signature. + * @param i the start index. + * @exception NullPointerException if typeSig is null. + * @exception IllegalArgumentException if typeSig is not a valid + * type signature. + * @return 0 if no generics at beginning, otherwise the + * index after the > sign. + */ + private static int checkGenerics(String typeSig, int i) { + if (typeSig.charAt(i) == '<') { + i++; + char c = typeSig.charAt(i++); + if (c == '>') + throw new IllegalArgumentException("Empty Generics: "+typeSig); + while (c != '>') { + if (c == ':') + throw new IllegalArgumentException("Empty type name: "+typeSig); + while (c != ':') { + if (!Character.isJavaIdentifierPart(c)) + throw new IllegalArgumentException + ("Illegal generic name: "+ typeSig); + c = typeSig.charAt(i++); + } + c = typeSig.charAt(i++); + if (c != 'L' && c != 'T' && c != ':') + throw new IllegalArgumentException + ("Wrong generic extends: "+typeSig); + if (c != ':') + i = checkTypeSig(typeSig, i - 1); + + c = typeSig.charAt(i++); + while(c == ':') { + i = checkTypeSig(typeSig, i); + c = typeSig.charAt(i++); + } + } + } + return i; + } + + /** + * Checks whether a given type signature is a valid (not method) + * type signature. Throws an exception otherwise. + * @param typeSig the type signature. + * @exception NullPointerException if typeSig is null. + * @exception IllegalArgumentException if typeSig is not a valid + * type signature or if it's a method type signature. + */ + public static void checkTypeSig(String typeSig) + throws IllegalArgumentException + { + try { + int i = checkGenerics(typeSig, 0); + if (checkTypeSig(typeSig, i) != typeSig.length()) + throw new IllegalArgumentException + ("Type sig too long: "+typeSig); + } catch (StringIndexOutOfBoundsException ex) { + throw new IllegalArgumentException + ("Incomplete type sig: "+typeSig); + } + } + + /** + * Checks whether a given type signature is a valid class + * type signature. Throws an exception otherwise. + * A class type signature starts optionally with generics, + * followed by type signature of super class, followed by + * type signature of super interfaces. + * @param typeSig the type signature. + * @exception NullPointerException if typeSig is null. + * @exception IllegalArgumentException if typeSig is not a valid + * type signature or if it's a method type signature. + */ + public static void checkClassTypeSig(String typeSig) + throws IllegalArgumentException + { + try { + int i = checkGenerics(typeSig, 0); + i = checkTypeSig(typeSig, i); + while (i != typeSig.length()) { + i = checkTypeSig(typeSig, i); + } + } catch (StringIndexOutOfBoundsException ex) { + throw new IllegalArgumentException + ("Incomplete type sig: "+typeSig); + } + } + + /** + * Checks whether a given type signature is a valid method + * type signature. Throws an exception otherwise. + * @param typeSig the type signature. + * @exception NullPointerException if typeSig is null. + * @exception IllegalArgumentException if typeSig is not a valid + * method type signature. + */ + public static void checkMethodTypeSig(String typeSig) + throws IllegalArgumentException + { + try { + int i = checkGenerics(typeSig, 0); + if (typeSig.charAt(i) != '(') + throw new IllegalArgumentException + ("No method signature: "+typeSig); + i++; + while (typeSig.charAt(i) != ')') + i = checkTypeSig(typeSig, i); + // skip closing parenthesis. + i++; + if (typeSig.charAt(i) == 'V') + // accept void return type. + i++; + else + i = checkTypeSig(typeSig, i); + if (i != typeSig.length()) + throw new IllegalArgumentException + ("Type sig too long: "+typeSig); + } catch (StringIndexOutOfBoundsException ex) { + throw new IllegalArgumentException + ("Incomplete type sig: "+typeSig); + } + } +} + diff --git a/jode/src/net/sf/jode/bytecode/package.html b/jode/src/net/sf/jode/bytecode/package.html new file mode 100644 index 0000000..dcf3e90 --- /dev/null +++ b/jode/src/net/sf/jode/bytecode/package.html @@ -0,0 +1,106 @@ + + + + +Jode Bytecode Package + + + +Provides easy access to class files and their contents. To use it you +create a ClassPath object giving it the locations where +it should search for classes. Then you can ask this object for a +class and get a ClassInfo object. As third step you can actually load +the class.

+ +Please notify me if you want to use this package. I will inform you +about updates, help you with problems, etc. WARNING: Some +parts of this package may change in the future in incompatible ways. +Ask me for more information.

+ +Here is a short example, how you can use this package, see the +documentation of the classes for more details. +
+ ...
+ ClassPath path = new ClassPath("/usr/lib/java/lib/classes.zip");
+ ClassInfo clazz = path.getClassInfo("java.util.Hashtable");
+
+ try {
+   clazz.load(ClassInfo.DECLARATIONS);
+ } catch (ClassFormatException ex) {
+   System.err.println("Something is wrong with HashTable, giving up!");
+   return;
+ } catch (IOException ex) {
+   System.err.println("Can't load HashTable, giving up!");
+   return;
+ }
+
+ MethodInfo[] methods = clazz.getMethods();
+ for (int i = 0; i < methods.length; i++) {
+     String type = methods[i].getType();
+     if (TypeSignature.getReturnType(type) == TypeSignature.INT_TYPE)
+         System.out.println("Found integer method: "+method.getName());
+ }
+ ...
+
+ +You can also use this package to create and write new classes: +
+ ...
+ ClassPath path = new ClassPath("/usr/lib/java/lib/classes.zip");
+ ClassInfo clazz = path.getClassInfo("my.new.Class");
+ clazz.setModifiers(Modifier.PUBLIC);
+ clazz.setSourceFile("Class.pl");
+ clazz.set...
+ clazz.write(zipOutputStream);
+ ...
+
+ +

Advantages of this bytecode package

+
    +
  • You don't need to think of the constant pool, except when you want +to write your custom attributes.
  • +
  • The set of opcodes is drastically reduced: For example you don't +have to handle 20 different opcodes that all push a constant value on +the stack. When reading it will automatically convert them to +ldc or ldc2 and on writing it will convert +them back.
  • +
  • Wide instructions are automatically generated when needed, large +methods are supported.
  • +
  • The code is organized in {@link net.sf.jode.bytecode.BasicBlocks} +which makes flow analysis much easier.
  • +
  • The memory consumption is quite moderate.
  • +
+ +

Disadvantages

+
    +
  • You can't change every byte. For example Jode decides itself if +a lookup switch or table switch is generated.
  • +
  • Jode does a lot of checks when reading the bytecode and it is +impossible to recover from errors. This makes it sometime hard to +find out why the bytecode of a particular class files is invalid.
  • +
+ +
+
Jochen Hoenicke
+ + +Last modified: Sat Aug 11 18:44:19 MEST 2001 + + + diff --git a/jode/src/net/sf/jode/decompiler/Analyzer.java b/jode/src/net/sf/jode/decompiler/Analyzer.java new file mode 100644 index 0000000..7177bdf --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Analyzer.java @@ -0,0 +1,26 @@ +/* Analyzer Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; + +public interface Analyzer { + + public void analyze(); + public void dumpSource(TabbedPrintWriter writer) throws java.io.IOException; +} diff --git a/jode/src/net/sf/jode/decompiler/Applet.java b/jode/src/net/sf/jode/decompiler/Applet.java new file mode 100644 index 0000000..a4bf299 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Applet.java @@ -0,0 +1,124 @@ +/* Applet Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; + +public class Applet extends java.applet.Applet { + private final int BORDER = 10; + private final int BEVEL = 2; + private Window jodeWin = new Window(this); + private Insets myInsets; + private Color pageColor; + +///#ifdef AWT10 +/// public boolean action(Event e, Object arg) { +/// jodeWin.action(e, arg); +/// return true; +/// } +/// +/// public Insets insets() { +/// if (myInsets == null) { +/// Insets appInsets = super.insets(); +///#else + public Insets getInsets() { + if (myInsets == null) { + Insets appInsets = super.getInsets(); +///#endif + myInsets = new Insets + (appInsets.top+BORDER, appInsets.left+BORDER, + appInsets.bottom+BORDER, appInsets.right+BORDER); + } + return myInsets; + } + + public void paint(Graphics g) { + super.paint(g); + Color back = getBackground(); + Color bright = back.brighter(); + Color dark = back.darker(); +///#ifdef AWT10 +/// Dimension size = size(); +///#else + Dimension size = getSize(); +///#endif + + // Fill corners with page color: + g.setColor(pageColor); + g.fillRect(0 , 0 , BORDER, BORDER); + g.fillRect(size.width - BORDER, 0 , BORDER, BORDER); + g.fillRect(size.width - BORDER, size.height - BORDER, BORDER, BORDER); + g.fillRect(0 , size.height - BORDER, BORDER, BORDER); + + // put filled arcs into corners with highlight color + g.setColor(bright); + g.fillArc(0, 0, + 2*BORDER, 2*BORDER, 90, 90); + g.fillArc(size.width - 2*BORDER, 0, + 2*BORDER, 2*BORDER, 45, 45); + g.fillArc(0, size.height - 2*BORDER, + 2*BORDER, 2*BORDER, 180, 45); + + // draw highlighted edges + g.fillRect(BORDER, 0, size.width - 2*BORDER, BEVEL); + g.fillRect(0, BORDER, BEVEL, size.height - 2*BORDER); + + // The same as above on the other side with dark color. + g.setColor(dark); + g.fillArc(size.width - 2*BORDER, 0, + 2*BORDER, 2*BORDER, 0, 45); + g.fillArc(0, size.height - 2*BORDER, + 2*BORDER, 2*BORDER, 225, 45); + g.fillArc(size.width - 2*BORDER, size.height - 2*BORDER, + 2*BORDER, 2*BORDER, -90, 90); + g.fillRect(BORDER, size.height - BEVEL, size.width - 2*BORDER, BEVEL); + g.fillRect(size.width - BEVEL, BORDER, BEVEL, size.height - 2*BORDER); + + // Finally fill the corners with background color again. + g.setColor(back); + g.fillArc(BEVEL, BEVEL, + 2*(BORDER-BEVEL), 2*(BORDER-BEVEL), 90, 90); + g.fillArc(size.width - (2*BORDER-BEVEL), BEVEL, + 2*(BORDER-BEVEL), 2*(BORDER-BEVEL), 0, 90); + g.fillArc(BEVEL, size.height - 2*BORDER + BEVEL, + 2*(BORDER-BEVEL), 2*(BORDER-BEVEL), 180, 90); + g.fillArc(size.width - (2*BORDER-BEVEL), + size.height - (2*BORDER-BEVEL), + 2*(BORDER-BEVEL), 2*(BORDER-BEVEL), -90, 90); + } + + public void init() { + String colorstr = getParameter("pagecolor"); + if (colorstr == null) + colorstr = "ffffff"; + this.pageColor = new Color(Integer.parseInt(colorstr, 16)); + colorstr = getParameter("bgcolor"); + if (colorstr != null) + setBackground(new Color(Integer.parseInt(colorstr, 16))); + String cp = getParameter("classpath"); + if (cp != null) + jodeWin.setClassPath(cp); + String cls = getParameter("class"); + if (cls != null) + jodeWin.setClass(cls); + } +} diff --git a/jode/src/net/sf/jode/decompiler/ClassAnalyzer.java b/jode/src/net/sf/jode/decompiler/ClassAnalyzer.java new file mode 100644 index 0000000..e380b51 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/ClassAnalyzer.java @@ -0,0 +1,825 @@ +/* ClassAnalyzer Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.MethodType; +import net.sf.jode.type.Type; +import net.sf.jode.bytecode.ClassFormatException; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.FieldInfo; +import net.sf.jode.bytecode.MethodInfo; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.ThisOperator; +import net.sf.jode.flow.TransformConstructors; +import net.sf.jode.flow.StructuredBlock; +import net.sf.jode.util.SimpleSet; + +import java.lang.reflect.Modifier; +import java.util.NoSuchElementException; +import java.util.Vector; +import java.util.Enumeration; +import java.io.IOException; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Set; +///#enddef + +public class ClassAnalyzer + implements Scope, Declarable, ClassDeclarer +{ + ImportHandler imports; + ClassInfo clazz; + ClassDeclarer parent; + ProgressListener progressListener; + String[] generics; + Type[] genericTypes; + + /** + * The complexity for initi#alizing a class. + */ + private static double INITIALIZE_COMPLEXITY = 0.03; + /** + * The minimal visible complexity. + */ + private static double STEP_COMPLEXITY = 0.03; + /** + * The value of the strictfp modifier. + * JDK1.1 doesn't define it. + */ + private static int STRICTFP = 0x800; + + double methodComplexity = 0.0; + double innerComplexity = 0.0; + + String name; + StructuredBlock[] blockInitializers; + FieldAnalyzer[] fields; + MethodAnalyzer[] methods; + ClassAnalyzer[] inners; + int modifiers; + + TransformConstructors constrAna; + MethodAnalyzer staticConstructor; + MethodAnalyzer[] constructors; + + /** + * The outer values for method scoped classes. + */ + OuterValues outerValues; + /** + * The outer instance for non-static class scope classes. + */ + Expression outerInstance; + + public ClassAnalyzer(ClassDeclarer parent, + ClassInfo clazz, ImportHandler imports, + Expression[] outerValues) + throws ClassFormatException, IOException + { + clazz.load(ClassInfo.ALL); + String signatures = clazz.getSignature(); + if (signatures.charAt(0) == '<') { + String[] genericSignatures = signature.getGenericNames(); + generics = new String[genericSignatures.length]; + for (int i = 0; i < generics.length; i++) { + int colon = genericSignatures[i].charAt(':'); + String genName; + if (colon == -1) { + generics[i] = genericSignatures[i]; + genericType[i] = + new GenericParameterType(genericSignatures[i], + Type.tObject, + new ClassType[0]); + } else { + generics[i] = genericSignatures[i].substring(0, colon); + String remainder = genericSignatures[i].substring(colon+1); + int nextIndex = remainder.skipType(remainder, 0); + List superClazzes; + for (;;) { + String clazzSig = remainder.substring(0, nextIndex); + superClazzes.add(Type.tType(clazzSig)); + if (nextIndex >= remainder.length()) + break; + remainder = remainder.substring(nextIndex+1); + } + ClassType genSupClass = (ClassType) superClazzes.get(0); + if (!genSupClass.isInterface()) + superClazzes.remove(0); + else + genSupClass = Type.tObject; + ClassType[] genSupIfaces = (ClassType[]) + superClazzes.toArray(new ClassType[0]); + genericType[i] = new GenericParameterType(generics[i], + genSupClass, + genSupIfaces); + } + } + } + ClassInfo superClass = clazz.getSuperclass(); + String myPackage = clazz.getName().substring + (clazz.getName().lastIndexOf('.') + 1); + while (superClass != null) { + int howMuch = (superClass.getName().startsWith(myPackage) + && (superClass.getName().lastIndexOf('.') + < myPackage.length())) + ? ClassInfo.DECLARATIONS : ClassInfo.PUBLICDECLARATIONS; + try { + superClass.load(howMuch); + } catch (IOException ex) { + GlobalOptions.err.println + ("Warning: Can't get " + + (howMuch == ClassInfo.PUBLICDECLARATIONS + ? "public" : "all") + + " information of " + superClass + +" to detect name conflicts."); + GlobalOptions.err.println(ex.toString()); + superClass.guess(howMuch); + } + superClass = superClass.getSuperclass(); + } + + this.parent = parent; + this.clazz = clazz; + this.imports = imports; + + modifiers = clazz.getModifiers(); + name = clazz.getClassName(); + + /* Check if this is a normal non-static inner class and set + * outerInstance. + */ + if ((Options.options & Options.OPTION_INNER) != 0 + && parent instanceof ClassAnalyzer && !isStatic()) + outerInstance = new ThisOperator(((ClassAnalyzer) parent).clazz); + if (outerValues != null) + this.outerValues = new OuterValues(this, outerValues); + } + + public ClassAnalyzer(ClassDeclarer parent, + ClassInfo clazz, ImportHandler imports) + throws ClassFormatException, IOException + { + this(parent, clazz, imports, null); + } + + public ClassAnalyzer(ClassInfo clazz, ImportHandler imports) + throws ClassFormatException, IOException + { + this(null, clazz, imports); + } + + public ClassPath getClassPath() { + return clazz.getClassPath(); + } + + public final boolean isStatic() { + return Modifier.isStatic(modifiers); + } + + public final boolean isStrictFP() { + return (modifiers & STRICTFP) != 0; + } + + public FieldAnalyzer getField(int index) { + return fields[index]; + } + + public int getFieldIndex(String fieldName, Type fieldType) { + for (int i=0; i< fields.length; i++) { + if (fields[i].getName().equals(fieldName) + && fields[i].getType().equals(fieldType)) + return i; + } + return -1; + } + + public MethodAnalyzer getMethod(String methodName, MethodType methodType) { + for (int i=0; i< methods.length; i++) { + if (methods[i].getName().equals(methodName) + && methods[i].getType().equals(methodType)) + return methods[i]; + } + return null; + } + + public int getModifiers() { + return modifiers; + } + + public ClassDeclarer getParent() { + return parent; + } + + public void setParent(ClassDeclarer newParent) { + this.parent = newParent; + } + + public ClassInfo getClazz() { + return clazz; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OuterValues getOuterValues() { + return outerValues; + } + + public Expression getOuterInstance() { + return outerInstance; + } + + public void addBlockInitializer(int index, StructuredBlock initializer) { + if (blockInitializers[index] == null) + blockInitializers[index] = initializer; + else + blockInitializers[index].appendBlock(initializer); + } + + public void initialize() { + FieldInfo[] finfos = clazz.getFields(); + MethodInfo[] minfos = clazz.getMethods(); + ClassInfo[] innerInfos = clazz.getClasses(); + + if (finfos == null) { + /* This means that the class could not be loaded. + * give up. + */ + return; + } + + if ((Options.options & Options.OPTION_INNER) != 0 + && innerInfos != null) { + /* Create inner classes */ + int innerCount = innerInfos.length; + inners = new ClassAnalyzer[innerCount]; + for (int i=0; i < innerCount; i++) { + try { + inners[i] = new ClassAnalyzer + (this, innerInfos[i], imports, null); + } catch (ClassFormatException ex) { + GlobalOptions.err.println("Inner class "+innerInfos[i] + +" malformed!"); + ex.printStackTrace(GlobalOptions.err); + } catch (IOException ex) { + GlobalOptions.err.println("Can't read inner class " + +innerInfos[i]+"."); + ex.printStackTrace(GlobalOptions.err); + } + } + } else + inners = new ClassAnalyzer[0]; + + fields = new FieldAnalyzer[finfos.length]; + methods = new MethodAnalyzer[minfos.length]; + blockInitializers = new StructuredBlock[finfos.length+1]; + for (int j=0; j < finfos.length; j++) + fields[j] = new FieldAnalyzer(this, finfos[j], imports); + + staticConstructor = null; + Vector constrVector = new Vector(); + for (int j=0; j < methods.length; j++) { + methods[j] = new MethodAnalyzer(this, minfos[j], imports); + + if (methods[j].isConstructor()) { + if (methods[j].isStatic()) + staticConstructor = methods[j]; + else + constrVector.addElement(methods[j]); + + /* Java bytecode can't have strictfp modifier for + * classes, while java can't have strictfp modifier + * for constructors. We handle the difference here. + * + * If only a few constructors are strictfp and the + * methods aren't this would add too much strictfp, + * but that isn't really dangerous. + */ + if (methods[j].isStrictFP()) + modifiers |= STRICTFP; + } + methodComplexity += methods[j].getComplexity(); + } + + constructors = new MethodAnalyzer[constrVector.size()]; + constrVector.copyInto(constructors); + + // initialize the inner classes. + for (int j=0; j < inners.length; j++) { + if (inners[j] == null) + continue; + inners[j].initialize(); + innerComplexity += inners[j].getComplexity(); + } + } + + /** + * Gets the complexity of this class. Must be called after it has + * been initialized. This is used for a nice progress bar. + */ + public double getComplexity() { + return (methodComplexity + innerComplexity); + } + + public void analyze(ProgressListener pl, double done, double scale) { + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println("Class " + name); + double subScale = scale / methodComplexity; + if (pl != null) + pl.updateProgress(done, name); + + imports.useClass(clazz); + if (clazz.getSuperclass() != null) + imports.useClass(clazz.getSuperclass()); + ClassInfo[] interfaces = clazz.getInterfaces(); + for (int j=0; j< interfaces.length; j++) + imports.useClass(interfaces[j]); + + if (fields == null) { + /* This means that the class could not be loaded. + * give up. + */ + return; + } + + + // First analyze constructors and synthetic fields: + constrAna = null; + if (constructors.length > 0) { + for (int j=0; j< constructors.length; j++) { + if (pl != null) { + double constrCompl = constructors[j].getComplexity() + * subScale; + if (constrCompl > STEP_COMPLEXITY) + constructors[j].analyze(pl, done, constrCompl); + else { + pl.updateProgress(done, name); + constructors[j].analyze(null, 0.0, 0.0); + } + done += constrCompl; + } else + constructors[j].analyze(null, 0.0, 0.0); + } + constrAna = new TransformConstructors(this, false, constructors); + constrAna.removeSynthInitializers(); + } + if (staticConstructor != null) { + if (pl != null) { + double constrCompl + = staticConstructor.getComplexity() * subScale; + if (constrCompl > STEP_COMPLEXITY) + staticConstructor.analyze(pl, done, constrCompl); + else { + pl.updateProgress(done, name); + staticConstructor.analyze(null, 0.0, 0.0); + } + done += constrCompl; + } else + staticConstructor.analyze(null, 0.0, 0.0); + } + + // If output should be immediate, we delay analyzation to output. + // Note that this may break anonymous classes, but the user + // has been warned. + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) + return; + + // Analyze fields + for (int j=0; j < fields.length; j++) + fields[j].analyze(); + + // Now analyze remaining methods. + for (int j=0; j < methods.length; j++) { + if (!methods[j].isConstructor()) + if (pl != null) { + double methodCompl = methods[j].getComplexity() + * subScale; + if (methodCompl > STEP_COMPLEXITY) + methods[j].analyze(pl, done, methodCompl); + else { + pl.updateProgress(done, methods[j].getName()); + methods[j].analyze(null, 0.0, 0.0); + } + done += methodCompl; + } else + methods[j].analyze(null, 0.0, 0.0); + } + } + + public void analyzeInnerClasses(ProgressListener pl, + double done, double scale) { + double subScale = scale / innerComplexity; + // If output should be immediate, we delay analyzation to output. + // Note that this may break anonymous classes, but the user + // has been warned. + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) + return; + + // Now analyze the inner classes. + for (int j=0; j < inners.length; j++) { + if (inners[j] == null) + continue; + if (pl != null) { + double innerCompl = inners[j].getComplexity() * subScale; + if (innerCompl > STEP_COMPLEXITY) { + double innerscale = subScale * inners[j].methodComplexity; + inners[j].analyze(pl, done, innerscale); + inners[j].analyzeInnerClasses(null, done + innerscale, + innerCompl - innerscale); + } else { + pl.updateProgress(done, inners[j].name); + inners[j].analyze(null, 0.0, 0.0); + inners[j].analyzeInnerClasses(null, 0.0, 0.0); + } + done += innerCompl; + } else { + inners[j].analyze(null, 0.0, 0.0); + inners[j].analyzeInnerClasses(null, 0.0, 0.0); + } + } + + // Now analyze the method scoped classes. + for (int j=0; j < methods.length; j++) + methods[j].analyzeInnerClasses(); + + } + + public void makeDeclaration(Set done) { + // First prepare constructors: + if (constrAna != null) + constrAna.transform(); + if (staticConstructor != null) { + new TransformConstructors + (this, true, new MethodAnalyzer[] { staticConstructor }) + .transform(); + } + + // If output should be immediate, we delay analyzation to output. + // Note that this may break anonymous classes, but the user + // has been warned. + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) + return; + + for (int j=0; j < fields.length; j++) + fields[j].makeDeclaration(done); + for (int j=0; j < inners.length; j++) + if (inners[j] != null) + inners[j].makeDeclaration(done); + for (int j=0; j < methods.length; j++) + methods[j].makeDeclaration(done); + } + + public void dumpDeclaration(TabbedPrintWriter writer) throws IOException + { + dumpDeclaration(writer, null, 0.0, 0.0); + } + + public void dumpDeclaration(TabbedPrintWriter writer, + ProgressListener pl, double done, double scale) + throws IOException + { + if (fields == null) { + /* This means that the class could not be loaded. + * give up. + */ + return; + } + + writer.startOp(writer.NO_PAREN, 0); + /* Clear the SUPER bit, which is also used as SYNCHRONIZED bit. */ + int modifiedModifiers = modifiers & ~(Modifier.SYNCHRONIZED + | STRICTFP); + if (clazz.isInterface()) + /* interfaces are implicitily abstract */ + modifiedModifiers &= ~Modifier.ABSTRACT; + if (parent instanceof MethodAnalyzer) { + /* method scope classes are implicitly private */ + modifiedModifiers &= ~Modifier.PRIVATE; + /* anonymous classes are implicitly final */ + if (name == null) + modifiedModifiers &= ~Modifier.FINAL; + } + String modif = Modifier.toString(modifiedModifiers); + if (modif.length() > 0) + writer.print(modif + " "); + if (isStrictFP()) { + /* The STRICTFP modifier is set. + * We handle it, since java.lang.reflect.Modifier is too dumb. + */ + writer.print("strictfp "); + } + /* interface is in modif */ + if (!clazz.isInterface()) + writer.print("class "); + writer.print(name); + String signature = clazz.getSignature(); + System.err.println("Class Signature: "+signature+ " (class "+name+")"); + ClassInfo superClazz = clazz.getSuperclass(); + if (superClazz != null && + superClazz.getName() != "java.lang.Object") { + writer.breakOp(); + writer.print(" extends " + (writer.getClassString + (superClazz, Scope.CLASSNAME))); + } + ClassInfo[] interfaces = clazz.getInterfaces(); + if (interfaces.length > 0) { + writer.breakOp(); + writer.print(clazz.isInterface() ? " extends " : " implements "); + writer.startOp(writer.EXPL_PAREN, 1); + for (int i=0; i < interfaces.length; i++) { + if (i > 0) { + writer.print(", "); + writer.breakOp(); + } + writer.print(writer.getClassString + (interfaces[i], Scope.CLASSNAME)); + } + writer.endOp(); + } + writer.println(); + + writer.openBraceClass(); + writer.tab(); + dumpBlock(writer, pl, done, scale); + writer.untab(); + writer.closeBraceClass(); + } + + public void dumpBlock(TabbedPrintWriter writer) + throws IOException + { + dumpBlock(writer, null, 0.0, 0.0); + } + + public void dumpBlock(TabbedPrintWriter writer, + ProgressListener pl, double done, double scale) + throws IOException + { + + double subScale = scale / getComplexity(); + writer.pushScope(this); + boolean needFieldNewLine = false; + boolean needNewLine = false; + Set declared = null; + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) + declared = new SimpleSet(); + for (int i=0; i< fields.length; i++) { + if (blockInitializers[i] != null) { + if (needNewLine) + writer.println(); + writer.openBrace(); + writer.tab(); + blockInitializers[i].dumpSource(writer); + writer.untab(); + writer.closeBrace(); + needFieldNewLine = needNewLine = true; + } + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) { + // We now do the analyzation we skipped before. + fields[i].analyze(); + fields[i].makeDeclaration(declared); + } + if (fields[i].skipWriting()) + continue; + if (needFieldNewLine) + writer.println(); + fields[i].dumpSource(writer); + needNewLine = true; + } + if (blockInitializers[fields.length] != null) { + if (needNewLine) + writer.println(); + writer.openBrace(); + writer.tab(); + blockInitializers[fields.length].dumpSource(writer); + writer.untab(); + writer.closeBrace(); + needNewLine = true; + } + for (int i=0; i< inners.length; i++) { + if (needNewLine) + writer.println(); + + if (inners[i] == null) { + writer.println("COULDN'T READ INNER CLASS!"); + continue; + } + + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) { + // We now do the analyzation we skipped before. + inners[i].analyze(null, 0.0, 0.0); + inners[i].analyzeInnerClasses(null, 0.0, 0.0); + inners[i].makeDeclaration(declared); + } + + if (pl != null) { + double innerCompl = inners[i].getComplexity() * subScale; + if (innerCompl > STEP_COMPLEXITY) + inners[i].dumpSource(writer, pl, done, innerCompl); + else { + pl.updateProgress(done, name); + inners[i].dumpSource(writer); + } + done += innerCompl; + } else + inners[i].dumpSource(writer); + needNewLine = true; + } + for (int i=0; i< methods.length; i++) { + if ((Options.options & Options.OPTION_IMMEDIATE) != 0) { + // We now do the analyzation we skipped before. + if (!methods[i].isConstructor()) + methods[i].analyze(null, 0.0, 0.0); + methods[i].analyzeInnerClasses(); + methods[i].makeDeclaration(declared); + } + + if (methods[i].skipWriting()) + continue; + if (needNewLine) + writer.println(); + + if (pl != null) { + double methodCompl = methods[i].getComplexity() * subScale; + pl.updateProgress(done, methods[i].getName()); + methods[i].dumpSource(writer); + done += methodCompl; + } else + methods[i].dumpSource(writer); + needNewLine = true; + } + writer.popScope(); + clazz.drop(clazz.DECLARATIONS); + } + + public void dumpSource(TabbedPrintWriter writer) + throws IOException + { + dumpSource(writer, null, 0.0, 0.0); + } + + public void dumpSource(TabbedPrintWriter writer, + ProgressListener pl, double done, double scale) + throws IOException + { + dumpDeclaration(writer, pl, done, scale); + writer.println(); + } + + public void dumpJavaFile(TabbedPrintWriter writer) + throws IOException { + dumpJavaFile(writer, null); + } + + public void dumpJavaFile(TabbedPrintWriter writer, ProgressListener pl) + throws IOException { + imports.init(clazz.getName()); + LocalInfo.init(); + initialize(); + double done = 0.05; + double scale = (0.75) * methodComplexity + / (methodComplexity + innerComplexity); + analyze(pl, INITIALIZE_COMPLEXITY, scale); + done += scale; + analyzeInnerClasses(pl, done, 0.8 - done); + makeDeclaration(new SimpleSet()); + imports.dumpHeader(writer); + dumpSource(writer, pl, 0.8, 0.2); + if (pl != null) + pl.updateProgress(1.0, name); + } + + public boolean isScopeOf(Object obj, int scopeType) { + if (clazz.equals(obj) && scopeType == CLASSSCOPE) + return true; + return false; + } + + static int serialnr = 0; + public void makeNameUnique() { + name = name + "_" + serialnr++ + "_"; + } + + public boolean conflicts(String name, int usageType) { + return conflicts(clazz, name, usageType); + } + + private static boolean conflicts(ClassInfo info, + String name, int usageType) { + while (info != null) { + if (usageType == NOSUPERMETHODNAME || usageType == METHODNAME) { + MethodInfo[] minfos = info.getMethods(); + for (int i = 0; i< minfos.length; i++) + if (minfos[i].getName().equals(name)) + return true; + } + if (usageType == NOSUPERFIELDNAME || usageType == FIELDNAME + || usageType == AMBIGUOUSNAME) { + FieldInfo[] finfos = info.getFields(); + for (int i=0; i < finfos.length; i++) { + if (finfos[i].getName().equals(name)) + return true; + } + } + if (usageType == CLASSNAME || usageType == AMBIGUOUSNAME) { + try { + info.load(info.DECLARATIONS); + } catch (IOException ex) { + info.guess(info.DECLARATIONS); + } + ClassInfo[] iinfos = info.getClasses(); + if (iinfos != null) { + for (int i=0; i < iinfos.length; i++) { + if (iinfos[i].getClassName().equals(name)) + return true; + } + } + } + if (usageType == NOSUPERFIELDNAME + || usageType == NOSUPERMETHODNAME) + return false; + + ClassInfo[] ifaces = info.getInterfaces(); + for (int i = 0; i < ifaces.length; i++) + if (conflicts(ifaces[i], name, usageType)) + return true; + info = info.getSuperclass(); + } + return false; + } + + /** + * Get the class analyzer for the given class info. This searches + * the method scoped/anonymous classes in this method and all + * outer methods and the outer classes for the class analyzer. + * @param cinfo the classinfo for which the analyzer is searched. + * @return the class analyzer, or null if there is not an outer + * class that equals cinfo, and not a method scope/inner class in + * an outer method. + */ + public ClassAnalyzer getClassAnalyzer(ClassInfo cinfo) { + if (cinfo == getClazz()) + return this; + if (parent == null) + return null; + return getParent().getClassAnalyzer(cinfo); + } + + /** + * Get the class analyzer for the given inner class. + * @param name the short name of the inner class + * @return the class analyzer, or null if there is no inner + * class with the given name. + */ + public ClassAnalyzer getInnerClassAnalyzer(String name) { + /** require name != null; **/ + int innerCount = inners.length; + for (int i=0; i < innerCount; i++) { + if (inners[i] != null && inners[i].name.equals(name)) + return inners[i]; + } + return null; + } + + /** + * We add the named method scoped classes to the declarables. + */ + public void fillDeclarables(Collection used) { + for (int j=0; j < methods.length; j++) + methods[j].fillDeclarables(used); + } + + public void addClassAnalyzer(ClassAnalyzer clazzAna) { + if (parent != null) + parent.addClassAnalyzer(clazzAna); + } + + public String toString() { + return getClass().getName()+"["+getClazz()+"]"; + } +} diff --git a/jode/src/net/sf/jode/decompiler/ClassDeclarer.java b/jode/src/net/sf/jode/decompiler/ClassDeclarer.java new file mode 100644 index 0000000..cd996d5 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/ClassDeclarer.java @@ -0,0 +1,42 @@ +/* ClassDeclarer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.bytecode.ClassInfo; + +/** + * This is the interface for objects, that a method can declare + */ +public interface ClassDeclarer { + /** + * Get the parent of this ClassDeclarer. + * @return null if this is the outermost instance. + */ + public ClassDeclarer getParent(); + + /** + * Get the class analyzer for the given anonymous class info. It + * will search it in the classes we declare and in the parent + * class declarer. + * @return null if the class analyzer doesn't yet exists. + */ + public ClassAnalyzer getClassAnalyzer(ClassInfo ci); + + public void addClassAnalyzer(ClassAnalyzer classAna); +} diff --git a/jode/src/net/sf/jode/decompiler/Declarable.java b/jode/src/net/sf/jode/decompiler/Declarable.java new file mode 100644 index 0000000..132650e --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Declarable.java @@ -0,0 +1,38 @@ +/* Declarable Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; + +/** + * This is the interface for objects, that a method can declare + */ +public interface Declarable { + /** + * Get the name of this declarable. + */ + public String getName(); + + /** + * Set the name of this local. + */ + public void makeNameUnique(); + + public void dumpDeclaration(TabbedPrintWriter writer) + throws java.io.IOException; +} diff --git a/jode/src/net/sf/jode/decompiler/Decompiler.java b/jode/src/net/sf/jode/decompiler/Decompiler.java new file mode 100644 index 0000000..3608c73 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Decompiler.java @@ -0,0 +1,233 @@ +/* Decompiler Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.ClassInfo; +import java.io.File; +import java.io.PrintWriter; +import java.io.Writer; +import java.io.BufferedWriter; + +/** + * This is the interface that other java classes may use to decompile + * classes. Feel free to use it in your own GNU GPL'ed project. + * Please tell me about your project.
+ * + * Note that the GNU GPL doesn't allow you to use this interface in + * commercial programs. + * + * @author Jochen Hoenicke + * @version 1.0 + */ +public class Decompiler { + private ClassPath classPath = null; + private int importPackageLimit = ImportHandler.DEFAULT_PACKAGE_LIMIT; + private int importClassLimit = ImportHandler.DEFAULT_CLASS_LIMIT; + + private int tabWidth = 8; + private int indentSize = 4; + private int outputStyle = TabbedPrintWriter.BRACE_AT_EOL; + private int lineWidth = 79; + + /** + * We need a different pathSeparatorChar, since ':' (used for most + * UNIX System) is used a protocol separator in URLs. + * + * We currently allow both pathSeparatorChar and + * altPathSeparatorChar and decide if it is a protocol separator + * by context. + */ + public static final char altPathSeparatorChar + = ClassPath.altPathSeparatorChar; + + /** + * Create a new decompiler. Normally you need only one, but you + * can have more around, with different options and different + * class paths. + */ + public Decompiler() { + } + + /** + * Sets the class path. Should be called once before decompile is + * called, otherwise the system class path is used. + * @param classpath A comma separated classpath. + * @exception NullPointerException if classpath is null. + * @see #setClassPath(String[]) + */ + public void setClassPath(String classpath) { + this.classPath = new ClassPath(classpath); + } + + /** + * Set the class path. Should be called once before decompile is + * called, otherwise the system class path is used. + * @param classpath a non empty array of jar files and directories; + * URLs are allowed, too. + * @exception NullPointerException if classpath is null. + * @exception IndexOutOfBoundsException if classpath array is empty. + * @see #setClassPath(String) + */ + public void setClassPath(String[] classpath) { + this.classPath = new ClassPath(classpath); + } + + /** + * Set the class path. Should be called once before decompile is + * called, otherwise the system class path is used. + * @param classpath a classpath object. + * @exception NullPointerException if classpath is null. + * @exception IndexOutOfBoundsException if classpath array is empty. + * @see #setClassPath(String) + */ + public void setClassPath(ClassPath classpath) { + this.classPath = classpath; + } + + private static final String[] optionStrings = { + "lvt", "inner", "anonymous", "push", "pretty", "decrypt", + "onetime", "immediate", "verify", "contrafo" + }; + + /** + * Set an option. + * @param option the option (pretty, style, decrypt, verify, etc.) + * @param value ("1"/"0" for on/off, "sun"/"gnu" for style) + * @exception IllegalArgumentException if option or value is invalid. + */ + public void setOption(String option, String value) { + if (option.equals("style")) { + if (value.equals("gnu")) { + outputStyle = TabbedPrintWriter.GNU_SPACING + | TabbedPrintWriter.INDENT_BRACES; + indentSize = 2; + } else if (value.equals("sun")) { + outputStyle = TabbedPrintWriter.BRACE_AT_EOL; + indentSize = 4; + } else if (value.equals("pascal")) { + outputStyle = 0; + indentSize = 4; + } else + throw new IllegalArgumentException("Invalid style "+value); + return; + } + if (option.equals("tabwidth")) { + tabWidth = Integer.parseInt(value); + return; + } + if (option.equals("indent")) { + indentSize = Integer.parseInt(value); + return; + } + if (option.equals("linewidth")) { + lineWidth = Integer.parseInt(value); + return; + } + if (option.equals("import")) { + int comma = value.indexOf(','); + int packLimit = Integer.parseInt(value.substring(0, comma)); + if (packLimit == 0) + packLimit = Integer.MAX_VALUE; + int clazzLimit = Integer.parseInt(value.substring(comma+1)); + if (clazzLimit == 0) + clazzLimit = Integer.MAX_VALUE; + if (clazzLimit < 0 || packLimit < 0) + throw new IllegalArgumentException + ("Option import doesn't allow negative parameters"); + importPackageLimit = packLimit; + importClassLimit = clazzLimit; + return; + } + if (option.equals("verbose")) { + GlobalOptions.verboseLevel = Integer.parseInt(value); + return; + } + if (option.equals("debug")) { + GlobalOptions.setDebugging(value); + return; + } + for (int i=0; i < optionStrings.length; i++) { + if (option.equals(optionStrings[i])) { + if (value.equals("0") + || value.equals("off") + || value.equals("no")) + Options.options &= ~(1 << i); + else if (value.equals("1") + || value.equals("on") + || value.equals("yes")) + Options.options |= 1 << i; + else + throw new IllegalArgumentException("Illegal value for "+ + option); + return; + } + } + throw new IllegalArgumentException("Illegal option: "+option); + } + + + /** + * Set the stream where copyright and warnings/errors are printed + * to. + * @param errorStream the error stream. Note that this is a + * PrintWriter, not a PrintStream (which are deprecated since 1.1). + */ + public void setErr(PrintWriter errorStream) { + GlobalOptions.err = errorStream; + } + + /** + * Decompile a class. + * @param className full-qualified classname, dot separated, e.g. + * "java.lang.Object" + * @param writer The stream where the decompiled code should be + * written. Hint: Use a BufferedWriter for good performance. + * @param progress A progress listener (see below). Null if you + * don't need information about progress. + * @exception IllegalArgumentException if className isn't correct. + * @exception IOException if writer throws an exception. + * @exception RuntimeException If jode has a bug ;-) + */ + public void decompile(String className, Writer writer, + ProgressListener progress) + throws java.io.IOException { + if (classPath == null) { + String cp = System.getProperty("java.class.path"); + String bootcp = System.getProperty("sun.boot.class.path"); + if (bootcp != null) + cp = bootcp + altPathSeparatorChar + cp; + cp = cp.replace(File.pathSeparatorChar, altPathSeparatorChar); + classPath = new ClassPath(cp); + } + + ClassInfo clazz = classPath.getClassInfo(className); + ImportHandler imports = new ImportHandler(classPath, + importPackageLimit, + importClassLimit); + TabbedPrintWriter tabbedWriter = + new TabbedPrintWriter(writer, imports, false, + outputStyle, indentSize, + tabWidth, lineWidth); + ClassAnalyzer clazzAna = new ClassAnalyzer(null, clazz, imports); + clazzAna.dumpJavaFile(tabbedWriter, progress); + writer.flush(); + } +} diff --git a/jode/src/net/sf/jode/decompiler/FieldAnalyzer.java b/jode/src/net/sf/jode/decompiler/FieldAnalyzer.java new file mode 100644 index 0000000..d637e49 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/FieldAnalyzer.java @@ -0,0 +1,206 @@ +/* FieldAnalyzer Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.type.Type; +import net.sf.jode.bytecode.FieldInfo; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.ThisOperator; +import net.sf.jode.expr.LocalLoadOperator; +import net.sf.jode.expr.ConstOperator; +import net.sf.jode.expr.OuterLocalOperator; + +import java.lang.reflect.Modifier; +import java.io.IOException; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +public class FieldAnalyzer implements Analyzer { + ClassAnalyzer clazz; + ImportHandler imports; + int modifiers; + Type type; + String fieldName; + Expression constant; + boolean isSynthetic; + boolean isDeprecated; + boolean analyzedSynthetic = false; + + public FieldAnalyzer(ClassAnalyzer cla, FieldInfo fd, + ImportHandler i) + { + clazz = cla; + imports = i; + + modifiers = fd.getModifiers(); + type = Type.tType(cla.getClassPath(), fd.getType()); + fieldName = fd.getName(); + constant = null; + this.isSynthetic = fd.isSynthetic(); + this.isDeprecated = fd.isDeprecated(); + if (fd.getConstant() != null) { + if (fd.getConstant() instanceof String) { + constant = new ConstOperator(cla.getClassPath(), + (String) fd.getConstant()); + } else { + constant = new ConstOperator(fd.getConstant()); + } + constant.setType(type); + constant.makeInitializer(type); + } + } + + public String getName() { + return fieldName; + } + + public Type getType() { + return type; + } + + public ClassAnalyzer getClassAnalyzer() { + return clazz; + } + + public Expression getConstant() { + return constant; + } + + public boolean isSynthetic() { + return isSynthetic; + } + + public boolean isFinal() { + return Modifier.isFinal(modifiers); + } + + public void analyzedSynthetic() { + analyzedSynthetic = true; + } + + public boolean setInitializer(Expression expr) { + if (constant != null) + return constant.equals(expr); + + /* This should check for isFinal(), but sadly, sometimes jikes + * doesn't make a val$ field final. I don't know when, or why, + * so I currently ignore isFinal. + */ + if (isSynthetic + && (fieldName.startsWith("this$") + || fieldName.startsWith("val$"))) { + if (fieldName.startsWith("val$") && fieldName.length() > 4 + && expr instanceof OuterLocalOperator) { + LocalInfo li = ((OuterLocalOperator) expr).getLocalInfo(); + li.addHint(fieldName.substring(4), type); + } + analyzedSynthetic(); + } else + expr.makeInitializer(type); + + constant = expr; + return true; + } + + public boolean setClassConstant(String clazzName) { + if (constant != null) + return false; + if (clazzName.charAt(0) == '[') { + if (clazzName.charAt(clazzName.length()-1) == ';') + clazzName = clazzName.substring(0, clazzName.length()-1); + + if (fieldName.equals("array"+ (clazzName.replace('[', '$') + .replace('.', '$')))) { + analyzedSynthetic(); + return true; + } + } else { + if (fieldName.equals("class$" + clazzName.replace('.', '$')) + || fieldName.equals("class$L" + clazzName.replace('.', '$'))) { + analyzedSynthetic(); + return true; + } + } + return false; + } + + public void analyze() { + imports.useType(type); + } + + public void makeDeclaration(Set done) { + if (constant != null) { + constant.makeDeclaration(done); + constant = constant.simplify(); + } + } + + public boolean skipWriting() { + return analyzedSynthetic; + } + + public void dumpSource(TabbedPrintWriter writer) throws IOException + { + if (isDeprecated) { + writer.println("/**"); + writer.println(" * @deprecated"); + writer.println(" */"); + } + if (isSynthetic) + writer.print("/*synthetic*/ "); + int modifiedModifiers = modifiers; + /* + * JLS-1.0, section 9.3: + * + * Every field declaration in the body of an interface is + * implicitly public, static, and final. It is permitted, but + * strongly discouraged as a matter of style, to redundantly + * specify any or all of these modifiers for such fields. + * + * But I personally don't like this style..., move the + * comment mark if you think different. + + if (clazz.getClazz().isInterface()) + modifiedModifiers &= ~(Modifier.PUBLIC + | Modifier.STATIC + | Modifier.FINAL); + */ + writer.startOp(writer.NO_PAREN, 0); + String modif = Modifier.toString(modifiedModifiers); + if (modif.length() > 0) + writer.print(modif+" "); + + writer.printType(type); + writer.print(" " + fieldName); + if (constant != null) { + writer.breakOp(); + writer.print(" = "); + constant.dumpExpression(writer.IMPL_PAREN, writer); + } + writer.endOp(); + writer.println(";"); + } + + public String toString() { + return getClass().getName()+"["+clazz.getClazz()+"."+getName()+"]"; + } +} + diff --git a/jode/src/net/sf/jode/decompiler/ImportHandler.java b/jode/src/net/sf/jode/decompiler/ImportHandler.java new file mode 100644 index 0000000..f1a2d4d --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/ImportHandler.java @@ -0,0 +1,405 @@ +/* ImportHandler Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.type.Type; +import net.sf.jode.type.ArrayType; +import net.sf.jode.type.ClassInfoType; +import net.sf.jode.type.ClassType; +import net.sf.jode.type.NullType; + +///#def COLLECTIONS java.util +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.List; +import java.util.LinkedList; +import java.util.Comparator; +import java.util.Iterator; +///#enddef + +import java.io.IOException; +import java.util.Hashtable; + +public class ImportHandler { + /** + * The default package limit. MAX_VALUE means, do not import + * packages at all. + */ + public final static int DEFAULT_PACKAGE_LIMIT = Integer.MAX_VALUE; + /** + * The default class limit. 1 means, import every class used here. + */ + public final static int DEFAULT_CLASS_LIMIT = 1; + + SortedMap imports; + /* Classes that doesn't need to be qualified. */ + Hashtable cachedClassNames = null; + ClassAnalyzer main; + ClassPath classPath; + String className; + String pkg; + + int importPackageLimit; + int importClassLimit; + + /** + * A comparator to sort the imports. We want java.* and javax.* + * imports first. java.lang.* should precede java.lang.ref.*, but + * that is already guaranteed by ascii ordering. + */ + static Comparator comparator = new Comparator() { + public int compare(Object o1, Object o2) { + String s1 = (String) o1; + String s2 = (String) o2; + boolean java1 = s1.startsWith("java"); + boolean java2 = s2.startsWith("java"); + + if (java1 != java2) + return java1 ? -1 : 1; + return s1.compareTo(s2); + } + }; + + public ImportHandler(ClassPath classPath) { + this(classPath, DEFAULT_PACKAGE_LIMIT, DEFAULT_CLASS_LIMIT); + } + + public ImportHandler(ClassPath classPath, + int packageLimit, int classLimit) { + this.classPath = classPath; + importPackageLimit = packageLimit; + importClassLimit = classLimit; + } + + /** + * Checks if the className conflicts with a class imported from + * another package and must be fully qualified therefore. + * The imports must should have been cleaned up before. + *

+ * Known Bug: If a class, local, field or method with the same + * name as the package of className exists, using the fully + * qualified name is no solution. This sometimes can't be fixed + * at all (except by renaming the package). It happens only in + * ambigous contexts, namely static field/method access. + * @param name The full qualified class name. + * @return true if this className must be printed fully qualified. + */ + private boolean conflictsImport(String name) { + int pkgdelim = name.lastIndexOf('.'); + if (pkgdelim != -1) { + String pkgName = name.substring(0, pkgdelim); + /* All classes in this package doesn't conflict */ + if (pkgName.equals(pkg)) + return false; + + // name without package, but _including_ leading dot. + name = name.substring(pkgdelim); + + if (pkg.length() != 0) { + /* Does this conflict with a class in this package? */ + if (classPath.existsClass(pkg+name)) + return true; + } else { + /* Does this conflict with a class in this unnamed + * package? */ + if (classPath.existsClass(name.substring(1))) + return true; + } + + Iterator iter = imports.keySet().iterator(); + while (iter.hasNext()) { + String importName = (String) iter.next(); + if (importName.endsWith(".*")) { + /* strip the "*" */ + importName = importName.substring + (0, importName.length()-2); + if (!importName.equals(pkgName)) { + if (classPath.existsClass(importName+name)) + return true; + } + } else { + /* Is this a class import with same name? */ + if (importName.endsWith(name) + || importName.equals(name.substring(1))) + return true; + } + } + } + return false; + } + + private void cleanUpImports() { + Integer dummyVote = new Integer(Integer.MAX_VALUE); + SortedMap newImports = new TreeMap(comparator); + List classImports = new LinkedList(); + Iterator iter = imports.keySet().iterator(); + while (iter.hasNext()) { + String importName = (String) iter.next(); + Integer vote = (Integer) imports.get(importName); + if (!importName.endsWith(".*")) { + if (vote.intValue() < importClassLimit) + continue; + int delim = importName.lastIndexOf("."); + + if (delim != -1) { + /* Since the imports are sorted, newImports already + * contains the package if it should be imported. + */ + if (newImports.containsKey + (importName.substring(0, delim)+".*")) + continue; + + /* This is a single Class import, that is not + * superseeded by a package import. Mark it for + * importation, but don't put it in newImports, yet. + */ + classImports.add(importName); + } else if (pkg.length() != 0) { + /* This is a Class import from the unnamed + * package. It must always be imported. + */ + newImports.put(importName, dummyVote); + } + } else { + if (vote.intValue() < importPackageLimit) + continue; + newImports.put(importName, dummyVote); + } + } + + imports = newImports; + cachedClassNames = new Hashtable(); + /* Now check if the class import conflict with any of the + * package imports. + */ + iter = classImports.iterator(); + while (iter.hasNext()) { + /* If there are more than one single class imports with + * the same name, exactly the first (in sorted order) will + * be imported. + */ + String classFQName = (String) iter.next(); + if (!conflictsImport(classFQName)) { + imports.put(classFQName, dummyVote); + String name = + classFQName.substring(classFQName.lastIndexOf('.')+1); + cachedClassNames.put(classFQName, name); + } + } + } + + public void dumpHeader(TabbedPrintWriter writer) + throws java.io.IOException + { + writer.println("/* "+ className + + " - Decompiled by JODE"); + writer.println(" * Visit "+GlobalOptions.URL); + writer.println(" */"); + if (pkg.length() != 0) + writer.println("package "+pkg+";"); + + cleanUpImports(); + Iterator iter = imports.keySet().iterator(); + String lastFirstPart = null; + while (iter.hasNext()) { + String pkgName = (String)iter.next(); + if (!pkgName.equals("java.lang.*")) { + int firstDot = pkgName.indexOf('.'); + if (firstDot != -1) { + String firstPart = pkgName.substring(0, firstDot); + if (lastFirstPart != null + && !lastFirstPart.equals(firstPart)) { + writer.println(""); + } + lastFirstPart = firstPart; + } + writer.println("import "+pkgName+";"); + } + } + writer.println(""); + } + + public void error(String message) { + GlobalOptions.err.println(message); + } + + public void init(String className) { + imports = new TreeMap(comparator); + /* java.lang is always imported */ + imports.put("java.lang.*", new Integer(Integer.MAX_VALUE)); + + int pkgdelim = className.lastIndexOf('.'); + pkg = (pkgdelim == -1)? "" : className.substring(0, pkgdelim); + this.className = (pkgdelim == -1) ? className + : className.substring(pkgdelim+1); + } + + /* Marks the clazz as used, so that it will be imported if used often + * enough. + */ + public void useClass(ClassInfo clazz) { + for (;;) { + try { + /* First handle inner classes: For class scoped classes + * import outer class instead; for method scoped classes + * we don't import anything. + */ + clazz.load(ClassInfo.OUTERCLASS); + } catch (IOException ex) { + /* If we can't load outer class information, assume + * the clazz is not method or class scoped in this + * class. There is a big error otherwise anyways. + */ + break; + } + if (clazz.isMethodScoped()) + return; + ClassInfo outer = clazz.getOuterClass(); + if (outer == null) + break; + clazz = outer; + } + + useClass(clazz.getName()); + } + + /* Marks the clazz as used, so that it will be imported if used often + * enough. + */ + public void useClass(String name) { + + Integer i = (Integer) imports.get(name); + if (i == null) { + /* This class wasn't imported before. Mark the whole package + * as used once more. */ + + int pkgdelim = name.lastIndexOf('.'); + if (pkgdelim != -1) { + String pkgName = name.substring(0, pkgdelim); + if (pkgName.equals(pkg)) + return; + + Integer pkgVote = (Integer) imports.get(pkgName+".*"); + if (pkgVote != null + && pkgVote.intValue() >= importPackageLimit) + return; + + pkgVote = (pkgVote == null) + ? new Integer(1): new Integer(pkgVote.intValue()+1); + imports.put(pkgName+".*", pkgVote); + } + i = new Integer(1); + } else { + if (i.intValue() >= importClassLimit) + return; + i = new Integer(i.intValue()+1); + } + imports.put(name, i); + } + + public final void useType(Type type) { + if (type instanceof ArrayType) + useType(((ArrayType) type).getElementType()); + else if (type instanceof ClassInfoType) + useClass(((ClassInfoType) type).getClassInfo()); + else if (type instanceof ClassType) + useClass(((ClassType) type).getClassName()); + } + + /** + * Check if clazz is imported and maybe remove package delimiter from + * full qualified class name. + *

+ * Known Bug 1: If this is called before the imports are cleaned up, + * (that is only for debugging messages), the result is unpredictable. + *

+ * Known Bug 2: It is not checked if the class name conflicts with + * a local variable, field or method name. This is very unlikely + * since the java standard has different naming convention for those + * names. (But maybe an intelligent obfuscator may use this fact.) + * This can only happen with static fields or static methods. + * @return a legal string representation of clazz. + */ + public String getClassString(ClassInfo clazz) { + return getClassString(clazz.getName()); + } + + /** + * Check if clazz is imported and maybe remove package delimiter from + * full qualified class name. + *

+ * Known Bug 1: If this is called before the imports are cleaned up, + * (that is only for debugging messages), the result is unpredictable. + *

+ * Known Bug 2: It is not checked if the class name conflicts with + * a local variable, field or method name. This is very unlikely + * since the java standard has different naming convention for those + * names. (But maybe an intelligent obfuscator may use this fact.) + * This can only happen with static fields or static methods. + * @return a legal string representation of clazz. + */ + public String getClassString(String name) { + if (cachedClassNames == null) + /* We are not yet clean, return the full name */ + return name; + + /* First look in our cache. */ + String cached = (String) cachedClassNames.get(name); + if (cached != null) + return cached; + + int pkgdelim = name.lastIndexOf('.'); + if (pkgdelim != -1) { + + String pkgName = name.substring(0, pkgdelim); + + Integer i; + if (pkgName.equals(pkg) + || (imports.get(pkgName+".*") != null + && !conflictsImport(name))) { + String result = name.substring(pkgdelim+1); + cachedClassNames.put(name, result); + return result; + } + } + cachedClassNames.put(name, name); + return name; + } + + public String getTypeString(Type type) { + if (type instanceof ArrayType) + return getTypeString(((ArrayType) type).getElementType()) + "[]"; + else if (type instanceof ClassInfoType) + return getClassString(((ClassInfoType) type).getClassInfo()); + else if (type instanceof ClassType) + return getClassString(((ClassType) type).getClassName()); + else if (type instanceof NullType) + return "Object"; + else + return type.toString(); + } + + protected int loadFileFlags() + { + return 1; + } +} diff --git a/jode/src/net/sf/jode/decompiler/LocalInfo.java b/jode/src/net/sf/jode/decompiler/LocalInfo.java new file mode 100644 index 0000000..db0a28d --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/LocalInfo.java @@ -0,0 +1,414 @@ +/* LocalInfo Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import java.util.Enumeration; +import java.util.Vector; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.LocalVarOperator; + +/** + * The LocalInfo represents a local variable of a method. + * The problem is that two different local variables may use the same + * slot. The current strategie is to make the range of a local variable + * as small as possible.

+ * + * There may be more than one LocalInfo for a single local variable, + * because the algorithm begins with totally disjunct variables and + * then unifies locals. One of the local is then a shadow object which + * calls the member functions of the other local.

+ */ +public class LocalInfo implements Declarable { + private static int serialnr = 0; + private static int nextAnonymousSlot = -1; + private int slot; + private MethodAnalyzer methodAnalyzer; + private boolean nameIsGenerated = false; + private boolean isUnique; + private String name; + private Type type; + private LocalInfo shadow; + private Vector operators = new Vector(); + private Vector hints = new Vector(); + private boolean removed = false; + private boolean isFinal = false; + private Expression constExpr = null; + + static class Hint { + String name; + Type type; + + public Hint(String name, Type type) { + this.name = name; + this.type = type; + } + + public final Type getType() { + return type; + } + + public final String getName() { + return name; + } + + public boolean equals(Object o) { + if (o instanceof Hint) { + Hint h = (Hint) o; + return name.equals(h.name) && type.equals(h.type); + } + return false; + } + + public int hashCode() { + return name.hashCode() ^ type.hashCode(); + } + } + + /** + * Create a new local info with an anonymous slot. + */ + public LocalInfo() { + name = null; + type = Type.tUnknown; + this.slot = nextAnonymousSlot--; + } + + /** + * Create a new local info. + * @param slot The slot of this variable. + */ + public LocalInfo(MethodAnalyzer method, int slot) { + name = null; + type = Type.tUnknown; + this.methodAnalyzer = method; + this.slot = slot; + } + + public static void init() { + serialnr = 0; + } + + public void setOperator(LocalVarOperator operator) { + getLocalInfo().operators.addElement(operator); + } + + public void addHint(String name, Type type) { + getLocalInfo().hints.addElement(new Hint(name, type)); + } + + public int getUseCount() { + return getLocalInfo().operators.size(); + } + + /** + * Combines the LocalInfo with another. This will make this + * a shadow object to the other local info. That is all member + * functions will use the new local info instead of data in this + * object.

+ * If this is called with ourself nothing will happen. + * @param li the local info that we want to shadow. + */ + public void combineWith(LocalInfo shadow) { + if (this.shadow != null) { + getLocalInfo().combineWith(shadow); + return; + } + + shadow = shadow.getLocalInfo(); + if (this == shadow) + return; + + this.shadow = shadow; + if (!nameIsGenerated) + shadow.name = name; + if (constExpr != null) { + if (shadow.constExpr != null) + throw new InternalError + ("local has multiple constExpr"); + shadow.constExpr = constExpr; + } + +// GlobalOptions.err.println("combining "+name+"("+type+") and " +// +shadow.name+"("+shadow.type+")"); + shadow.setType(type); + + + boolean needTypeUpdate = !shadow.type.equals(type); + + java.util.Enumeration enumeration = operators.elements(); + while (enumeration.hasMoreElements()) { + LocalVarOperator lvo = + (LocalVarOperator) enumeration.nextElement(); + if (needTypeUpdate) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_TYPES) != 0) + GlobalOptions.err.println("updating " + lvo); + lvo.updateType(); + } + shadow.operators.addElement(lvo); + } + + enumeration = hints.elements(); + while (enumeration.hasMoreElements()) { + Object hint = enumeration.nextElement(); + if (!shadow.hints.contains(hint)) + shadow.hints.addElement(hint); + } + + /* Clear unused fields, to allow garbage collection. + */ + type = null; + name = null; + operators = null; + } + + /** + * Get the real LocalInfo. This may be different from the + * current object if this is a shadow local info. + */ + public LocalInfo getLocalInfo() { + if (shadow != null) { + while (shadow.shadow != null) { + shadow = shadow.shadow; + } + return shadow; + } + return this; + } + + /** + * Returns true if the local already has a name. + */ + public boolean hasName() { + return getLocalInfo().name != null; + } + + public String guessName() { + if (shadow != null) { + while (shadow.shadow != null) { + shadow = shadow.shadow; + } + return shadow.guessName(); + } + if (name == null) { + Enumeration enumeration = hints.elements(); + while (enumeration.hasMoreElements()) { + Hint hint = (Hint) enumeration.nextElement(); + if (type.isOfType(hint.getType())) { + name = hint.getName(); + setType(hint.getType()); + return name; + } + } + nameIsGenerated = true; + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) + GlobalOptions.err.println(getName()+" set type to getHint()"); + setType(type.getHint()); + if ((Options.options & Options.OPTION_PRETTY) != 0) { + name = type.getDefaultName(); + } else { + name = type.getDefaultName() + + (slot >= 0 ? "_" + slot : "") + "_" + serialnr++ + "_"; + isUnique = true; + } + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_LOCALS) != 0) { + GlobalOptions.err.println("Guessed name: " + name + + " from type: " + type); + Thread.dumpStack(); + } + } + return name; + } + + /** + * Get the name of this local. + */ + public String getName() { + if (shadow != null) { + while (shadow.shadow != null) { + shadow = shadow.shadow; + } + return shadow.getName(); + } + if (name == null) { + return "local_" + (slot >= 0 ? slot + "_" : "") + + Integer.toHexString(hashCode()); + } + return name; + } + + public boolean isNameGenerated() { + return getLocalInfo().nameIsGenerated; + } + + /** + * Get the slot of this local. + */ + public int getSlot() { + /* The slot may change when shadowing for anonymous locals */ + return getLocalInfo().slot; + } + + /** + * Set the name of this local. + */ + public void setName(String name) { + LocalInfo li = getLocalInfo(); + li.name = name; + } + + /** + * Set the name of this local. + */ + public void makeNameUnique() { + LocalInfo li = getLocalInfo(); + String name = li.getName(); + if (!li.isUnique) { + li.name = name + "_" + serialnr++ + "_"; + li.isUnique = true; + } + } + + /** + * Get the type of this local. + */ + public Type getType() { + return getLocalInfo().type; + } + + private int loopCount = 0; + /** + * Sets a new information about the type of this local. + * The type of the local is may be made more specific by this call. + * @param The new type information to be set. + * @return The new type of the local. + */ + public Type setType(Type otherType) { + LocalInfo li = getLocalInfo(); + if (li.loopCount++ > 5) { + GlobalOptions.err.println("Type error in local " + getName()+": " + + li.type + " seems to be recursive."); + Thread.dumpStack(); + otherType = Type.tError; + } + Type newType = li.type.intersection(otherType); + if (newType == Type.tError + && otherType != Type.tError && li.type != Type.tError) { + GlobalOptions.err.println("Type error in local " + getName()+": " + + li.type + " and " + otherType); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_TYPES) != 0) + Thread.dumpStack(); + } + else if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) + GlobalOptions.err.println(getName()+" setType, new: "+newType + + " old: "+li.type); + + if (!li.type.equals(newType)) { + li.type = newType; + java.util.Enumeration enumeration = li.operators.elements(); + while (enumeration.hasMoreElements()) { + LocalVarOperator lvo = (LocalVarOperator) enumeration.nextElement(); + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) + GlobalOptions.err.println("updating "+lvo); + lvo.updateType(); + } + } + li.loopCount--; + return li.type; + } + + public void setExpression(Expression expr) { + setType(expr.getType()); + getLocalInfo().constExpr = expr; + } + + public Expression getExpression() { + return getLocalInfo().constExpr; + } + + public boolean isShadow() { + return (shadow != null); + } + + public boolean equals(Object obj) { + return (obj instanceof LocalInfo + && ((LocalInfo)obj).getLocalInfo() == getLocalInfo()); + } + + public void remove() { + removed = true; + } + + public boolean isRemoved() { + return removed; + } + + public boolean isConstant() { + LocalInfo li = getLocalInfo(); + Enumeration enumeration = li.operators.elements(); + int writes = 0; + while (enumeration.hasMoreElements()) { + if (((LocalVarOperator) enumeration.nextElement()).isWrite()) + writes++; + } + if (writes > 1) + return false; + return true; + } + + public MethodAnalyzer getMethodAnalyzer() { + return methodAnalyzer; + } + + public boolean markFinal() { + LocalInfo li = getLocalInfo(); + Enumeration enumeration = li.operators.elements(); + int writes = 0; + while (enumeration.hasMoreElements()) { + if (((LocalVarOperator) enumeration.nextElement()).isWrite()) + writes++; + } + /* FIXME: Check if declaring final is okay */ + li.isFinal = true; + return true; + } + + public boolean isFinal() { + return getLocalInfo().isFinal; + } + + public String toString() { + return getName(); + } + + public void dumpDeclaration(TabbedPrintWriter writer) + throws java.io.IOException + { + LocalInfo li = getLocalInfo(); + if (li.isFinal) + writer.print("final "); + writer.printType(li.getType().getHint()); + writer.print(" " + li.getName().toString()); + } +} + diff --git a/jode/src/net/sf/jode/decompiler/Main.java b/jode/src/net/sf/jode/decompiler/Main.java new file mode 100644 index 0000000..bd5c4e9 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Main.java @@ -0,0 +1,429 @@ +/* Main Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.ClassFormatException; +import net.sf.jode.GlobalOptions; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.zip.ZipOutputStream; +import java.util.zip.ZipFile; +import java.util.zip.ZipEntry; +import java.util.Enumeration; + +import gnu.getopt.LongOpt; +import gnu.getopt.Getopt; + + +public class Main extends Options { + private static final int OPTION_START=0x10000; + private static final int OPTION_END =0x20000; + + private static final LongOpt[] longOptions = new LongOpt[] { + new LongOpt("cp", LongOpt.REQUIRED_ARGUMENT, null, 'c'), + new LongOpt("classpath", LongOpt.REQUIRED_ARGUMENT, null, 'c'), + new LongOpt("dest", LongOpt.REQUIRED_ARGUMENT, null, 'd'), + new LongOpt("keep-going", LongOpt.NO_ARGUMENT, null, 'k'), + new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'), + new LongOpt("version", LongOpt.NO_ARGUMENT, null, 'V'), + new LongOpt("verbose", LongOpt.OPTIONAL_ARGUMENT, null, 'v'), + new LongOpt("debug", LongOpt.OPTIONAL_ARGUMENT, null, 'D'), + new LongOpt("import", LongOpt.REQUIRED_ARGUMENT, null, 'i'), + new LongOpt("style", LongOpt.REQUIRED_ARGUMENT, null, 's'), + new LongOpt("lvt", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+0), + new LongOpt("inner", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+1), + new LongOpt("anonymous", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+2), + new LongOpt("push", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+3), + new LongOpt("pretty", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+4), + new LongOpt("decrypt", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+5), + new LongOpt("onetime", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+6), + new LongOpt("immediate", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+7), + new LongOpt("verify", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+8), + new LongOpt("contrafo", LongOpt.OPTIONAL_ARGUMENT, null, + OPTION_START+9) + }; + + public static void usage() { + PrintWriter err = GlobalOptions.err; + err.println("Version: " + GlobalOptions.version); + err.println("Usage: java net.sf.jode.decompiler.Main [OPTION]* {CLASS|JAR}*"); + err.println("Give a fully qualified CLASS name, e.g. net.sf.jode.decompiler.Main, if you want to"); + err.println("decompile a single class, or a JAR file containing many classes."); + err.println("OPTION is any of these:"); + err.println(" -h, --help "+ + "show this information."); + err.println(" -V, --version "+ + "output version information and exit."); + err.println(" -v, --verbose "+ + "be verbose (multiple times means more verbose)."); + err.println(" -c, --classpath "+ + "search for classes in specified classpath."); + err.println(" "+ + "The directories should be separated by ','."); + err.println(" -d, --dest

"+ + "write decompiled files to disk into directory destdir."); + err.println(" -s, --style {sun|gnu} "+ + "specify indentation style"); + err.println(" -i, --import ,"); + err.println(" "+ + "import classes used more than clslimit times"); + err.println(" "+ + "and packages with more then pkglimit used classes."); + err.println(" "+ + "Limit 0 means never import. Default is 0,1."); + err.println(" -k, --keep-going "+ + "After an error continue to decompile the other classes."); + err.println(" "+ + "after an error decompiling one of them."); + } + + public static boolean handleOption(int option, int longind, String arg) { + if (arg == null) + options ^= 1 << option; + else if ("yes".startsWith(arg) || arg.equals("on")) + options |= 1 << option; + else if ("no".startsWith(arg) || arg.equals("off")) + options &= ~(1 << option); + else { + GlobalOptions.err.println + ("net.sf.jode.decompiler.Main: option --"+longOptions[longind].getName() + +" takes one of `yes', `no', `on', `off' as parameter"); + return false; + } + return true; + } + + public static boolean decompileClass + (String className, ClassPath classPath, + String classPathStr, + ZipOutputStream destZip, String destDir, + TabbedPrintWriter writer, ImportHandler imports) { + try { + ClassInfo clazz; + try { + clazz = classPath.getClassInfo(className); + } catch (IllegalArgumentException ex) { + GlobalOptions.err.println + ("`"+className+"' is not a class name"); + return false; + } + if (skipClass(clazz)) + return true; + + String filename = + className.replace('.', File.separatorChar)+".java"; + if (destZip != null) { + writer.flush(); + destZip.putNextEntry(new ZipEntry(filename)); + } else if (destDir != null) { + File file = new File (destDir, filename); + File directory = new File(file.getParent()); + if (!directory.exists() && !directory.mkdirs()) { + GlobalOptions.err.println + ("Could not create directory " + + directory.getPath() + ", check permissions."); + } + writer = new TabbedPrintWriter + (new BufferedOutputStream(new FileOutputStream(file)), + imports, false); + } + + GlobalOptions.err.println(className); + + ClassAnalyzer clazzAna = new ClassAnalyzer(clazz, imports); + clazzAna.dumpJavaFile(writer); + + if (destZip != null) { + writer.flush(); + destZip.closeEntry(); + } else if (destDir != null) + writer.close(); + /* Now is a good time to clean up */ + System.gc(); + return true; + } catch (FileNotFoundException ex) { + GlobalOptions.err.println + ("Can't read "+ex.getMessage()+"."); + GlobalOptions.err.println + ("Check the class path ("+classPathStr+ + ") and check that you use the java class name."); + return false; + } catch (ClassFormatException ex) { + GlobalOptions.err.println + ("Error while reading "+className+"."); + ex.printStackTrace(GlobalOptions.err); + return false; + } catch (IOException ex) { + GlobalOptions.err.println + ("Can't write source of "+className+"."); + GlobalOptions.err.println("Check the permissions."); + ex.printStackTrace(GlobalOptions.err); + return false; + } catch(RuntimeException ex) { + GlobalOptions.err.println + ("Error whilst decompiling " + className + "."); + ex.printStackTrace(GlobalOptions.err); + return false; + } catch(InternalError ex) { + /* InternalError should not normally be + * caught, however we catch it here because + * some parts of JODE + * (e.g. TransformExceptionHandlers.analyze()) + * throw it. + * TODO: Replace InternalError with something else in + * places they can actually be thrown + */ + GlobalOptions.err.println + ("Internal error whilst decompiling " + className + "."); + ex.printStackTrace(GlobalOptions.err); + return false; + } + } + + public static void main(String[] params) { + try { + decompile(params); + } catch (ExceptionInInitializerError ex) { + ex.getException().printStackTrace(); + } catch (Throwable ex) { + ex.printStackTrace(); + } + /* When AWT applications are compiled with insufficient + * classpath the type guessing by reflection code can + * generate an awt thread that will prevent normal + * exiting. + */ + System.exit(0); + } + + public static void decompile(String[] params) { + if (params.length == 0) { + usage(); + return; + } + + ClassPath classPath; + + String classPathStr = System.getProperty("java.class.path") + .replace(File.pathSeparatorChar, ClassPath.altPathSeparatorChar); + + String bootClassPath = System.getProperty("sun.boot.class.path"); + if (bootClassPath != null) + classPathStr = classPathStr + ClassPath.altPathSeparatorChar + + bootClassPath.replace(File.pathSeparatorChar, + ClassPath.altPathSeparatorChar); + String destDir = null; + + int importPackageLimit = ImportHandler.DEFAULT_PACKAGE_LIMIT; + int importClassLimit = ImportHandler.DEFAULT_CLASS_LIMIT;; + int outputStyle = TabbedPrintWriter.BRACE_AT_EOL; + int indentSize = 4; + boolean keepGoing = false; + + GlobalOptions.err.println(GlobalOptions.copyright); + + boolean errorInParams = false; + Getopt g = new Getopt("net.sf.jode.decompiler.Main", params, "hVvkc:d:D:i:s:", + longOptions, true); + for (int opt = g.getopt(); opt != -1; opt = g.getopt()) { + switch(opt) { + case 0: + break; + case 'h': + usage(); + errorInParams = true; + break; + case 'V': + GlobalOptions.err.println(GlobalOptions.version); + break; + case 'c': + classPathStr = g.getOptarg(); + break; + case 'd': + destDir = g.getOptarg(); + break; + case 'k': + keepGoing = true; + break; + case 'v': { + String arg = g.getOptarg(); + if (arg == null) + GlobalOptions.verboseLevel++; + else { + try { + GlobalOptions.verboseLevel = Integer.parseInt(arg); + } catch (NumberFormatException ex) { + GlobalOptions.err.println + ("net.sf.jode.decompiler.Main: Argument `" + +arg+"' to --verbose must be numeric:"); + errorInParams = true; + } + } + break; + } + case 'D': { + String arg = g.getOptarg(); + if (arg == null) + arg = "help"; + errorInParams |= !GlobalOptions.setDebugging(arg); + break; + } + case 's': { + String arg = g.getOptarg(); + if (arg.equals("gnu")) { + outputStyle = TabbedPrintWriter.GNU_SPACING + | TabbedPrintWriter.INDENT_BRACES; + indentSize = 2; + } else if (arg.equals("sun")) { + outputStyle = TabbedPrintWriter.BRACE_AT_EOL; + indentSize = 4; + } else if (arg.equals("pascal")) { + outputStyle = 0; + indentSize = 4; + } else { + GlobalOptions.err.println + ("net.sf.jode.decompiler.Main: Unknown style `"+arg+"'."); + errorInParams = true; + } + break; + } + case 'i': { + String arg = g.getOptarg(); + int comma = arg.indexOf(','); + try { + int packLimit = Integer.parseInt(arg.substring(0, comma)); + if (packLimit == 0) + packLimit = Integer.MAX_VALUE; + if (packLimit < 0) + throw new IllegalArgumentException(); + int clazzLimit = Integer.parseInt(arg.substring(comma+1)); + if (clazzLimit == 0) + clazzLimit = Integer.MAX_VALUE; + if (clazzLimit < 0) + throw new IllegalArgumentException(); + + importPackageLimit = packLimit; + importClassLimit = clazzLimit; + + } catch (RuntimeException ex) { + GlobalOptions.err.println + ("net.sf.jode.decompiler.Main: Invalid argument for -i option."); + errorInParams = true; + } + break; + } + default: + if (opt >= OPTION_START && opt <= OPTION_END) { + errorInParams |= !handleOption(opt-OPTION_START, + g.getLongind(), + g.getOptarg()); + } else + errorInParams = true; + break; + } + } + if (errorInParams) + return; + classPath = new ClassPath(classPathStr); + ImportHandler imports = new ImportHandler(classPath, + importPackageLimit, + importClassLimit); + + ZipOutputStream destZip = null; + TabbedPrintWriter writer = null; + if (destDir == null) + writer = new TabbedPrintWriter(System.out, imports, true, + outputStyle, indentSize, 0, 79); + else if (destDir.toLowerCase().endsWith(".zip") + || destDir.toLowerCase().endsWith(".jar")) { + try { + destZip = new ZipOutputStream(new FileOutputStream(destDir)); + } catch (IOException ex) { + GlobalOptions.err.println("Can't open zip file "+destDir); + ex.printStackTrace(GlobalOptions.err); + return; + } + writer = new TabbedPrintWriter(new BufferedOutputStream(destZip), + imports, false, + outputStyle, indentSize, 0, 79); + } + for (int i= g.getOptind(); i< params.length; i++) { + try { + if ((params[i].endsWith(".jar") || params[i].endsWith(".zip")) + && new File(params[i]).isFile()) { + /* The user obviously wants to decompile a jar/zip file. + * Lets do him a pleasure and allow this. + */ + ClassPath zipClassPath + = new ClassPath(params[i], classPath); + Enumeration enumeration = new ZipFile(params[i]).entries(); + while (enumeration.hasMoreElements()) { + String entry + = ((ZipEntry) enumeration.nextElement()).getName(); + if (entry.endsWith(".class")) { + entry = entry.substring(0, entry.length() - 6) + .replace('/', '.'); + if (!decompileClass(entry, zipClassPath, + classPathStr, + destZip, destDir, + writer, imports) + && !keepGoing) + break; + } + } + } else { + if (!decompileClass(params[i], classPath, + classPathStr, + destZip, destDir, + writer, imports) + && !keepGoing) + break; + } + } catch (IOException ex) { + GlobalOptions.err.println + ("Can't read zip file " + params[i] + "."); + ex.printStackTrace(GlobalOptions.err); + } + } + if (destZip != null) { + try { + destZip.close(); + } catch (IOException ex) { + GlobalOptions.err.println("Can't close Zipfile"); + ex.printStackTrace(GlobalOptions.err); + } + } + } +} diff --git a/jode/src/net/sf/jode/decompiler/MethodAnalyzer.java b/jode/src/net/sf/jode/decompiler/MethodAnalyzer.java new file mode 100644 index 0000000..f9d0aef --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/MethodAnalyzer.java @@ -0,0 +1,1149 @@ +/* MethodAnalyzer Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.BasicBlocks; +import net.sf.jode.bytecode.Block; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.Handler; +import net.sf.jode.bytecode.Instruction; +import net.sf.jode.bytecode.LocalVariableInfo; +import net.sf.jode.bytecode.MethodInfo; +import net.sf.jode.jvm.SyntheticAnalyzer; +import net.sf.jode.type.*; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.ConstOperator; +import net.sf.jode.expr.CheckNullOperator; +import net.sf.jode.expr.ThisOperator; +import net.sf.jode.expr.LocalLoadOperator; +import net.sf.jode.expr.OuterLocalOperator; +import net.sf.jode.expr.InvokeOperator; +import net.sf.jode.flow.StructuredBlock; +import net.sf.jode.flow.FlowBlock; +import net.sf.jode.flow.TransformExceptionHandlers; +import net.sf.jode.flow.Jump; +import net.sf.jode.jvm.CodeVerifier; +import net.sf.jode.jvm.VerifyException; +import net.sf.jode.util.SimpleMap; + +import java.lang.reflect.Modifier; +import java.util.BitSet; +import java.util.Stack; +import java.util.Vector; +import java.util.Enumeration; +import java.io.DataInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +///#def COLLECTIONS java.util +import java.util.Map; +import java.util.Collection; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Set; +///#enddef + +/** + * A method analyzer is the main class for analyzation of methods. + * There is exactly one MethodAnalyzer object for each method (even + * for abstract methods), that should be decompiled. + * + * Method analyzation is done in three passes: + *
+ *
analyze()
+ *
the main analyzation, decompiles the code of the method
+ *
analyzeInners()
+ *
This will analyze method scopes classes by calling their + * analyze() and analyzeInners() + * methods.
+ *
makeDeclaration()
+ *
This will determine when to declare variables. For constructors + * it will do special transformations like field initialization.
+ */ +public class MethodAnalyzer implements Scope, ClassDeclarer { + /** + * The minimal visible complexity. + */ + private static double STEP_COMPLEXITY = 0.01; + /** + * The value of the strictfp modifier. + * JDK1.1 doesn't define it. + */ + private static int STRICTFP = 0x800; + /** + * The import handler where we should register our types. + */ + ImportHandler imports; + /** + * The class analyzer of the class that contains this method. + */ + ClassAnalyzer classAnalyzer; + /** + * The method info structure for this method. + */ + MethodInfo minfo; + /** + * This is the basic blocks structure, or null if this method has + * no code (abstract or native). + */ + BasicBlocks bb; + + /** + * The method name. + */ + String methodName; + /** + * The type of this method (parameter types + return type). + */ + MethodType methodType; + /** + * True, iff this method is a constructor, i.e. methodName == <(cl)?init> + */ + boolean isConstructor; + + /** + * The exceptions this method may throw. + */ + Type[] exceptions; + + /** + * If the method is synthetic (access$, class$, etc.), this is the + * synthetic analyzer describing the function of this method, otherwise + * this is null. + */ + SyntheticAnalyzer synth; + + /** + * This is the first flow block of the method code. If this + * method has no code, this is null. This is initialized at the + * end of the analyze() phase. + */ + FlowBlock methodHeader; + /** + * A list of all locals contained in this method. + */ + Vector allLocals = new Vector(); + + /** + * This array contains the locals in the parameter list, including + * the implicit this parameter for nonstatic methods. + */ + LocalInfo[] param; + + /** + * If this method is the special constructor, that is generated + * by jikes (constructor$xx), this points to the real constructor. + * If this is the real constructor and calls a constructor$xx, it + * points to this. Otherwise this is null. + */ + MethodAnalyzer jikesConstructor; + /** + * True, iff this method is the special constructor, and its first + * parameter is a reference to the outer class. + */ + boolean hasJikesOuterValue; + /** + * True, iff this method is an anonymous constructor, that is + * omitted even if it has parameters. + */ + boolean isAnonymousConstructor; + /** + * True, if this method is the special block$ method generated by jikes + * to initialize field members. + */ + boolean isJikesBlockInitializer; + + /** + * This list contains the InvokeOperator objects in the code of + * this method, that create method scoped classes. */ + Vector anonConstructors = new Vector(); + + /** + * This list contains the class analyzers of all method scoped + * classes that should be declared in this method or in a class + * that is declared in this method. + */ + Vector innerAnalyzers; + /** + * This list contains the class analyzers of all method scoped + * classes that are used in this method. + */ + Collection usedAnalyzers; + + /** + * This is the default constructor. + * @param cla the ClassAnalyzer of the class that contains this method. + * @param minfo the method info structure for this method. + * @param imports the import handler that should be informed about types. + */ + public MethodAnalyzer(ClassAnalyzer cla, MethodInfo minfo, + ImportHandler imports) { + this.classAnalyzer = cla; + this.imports = imports; + this.minfo = minfo; + this.methodName = minfo.getName(); + this.methodType = Type.tMethod(cla.getClassPath(), minfo.getSignature()); + this.isConstructor = + methodName.equals("") || methodName.equals(""); + + if (minfo.getBasicBlocks() != null) + bb = minfo.getBasicBlocks(); + + String[] excattr = minfo.getExceptions(); + if (excattr == null) { + exceptions = new Type[0]; + } else { + int excCount = excattr.length; + this.exceptions = new Type[excCount]; + for (int i=0; i< excCount; i++) + exceptions[i] = Type.tClass(classAnalyzer.getClassPath(), + excattr[i]); + } + if (minfo.isSynthetic() || methodName.indexOf('$') != -1) + synth = new SyntheticAnalyzer(cla.getClazz(), minfo, true); + } + + /** + * Returns the name of this method. + */ + public String getName() { + return methodName; + } + + /** + * Returns the type of this method. + * @return the type of this method. + */ + public MethodType getType() { + return methodType; + } + + /** + * Returns the first flow block of the code. + * @return the first flow block of the code. + */ + public FlowBlock getMethodHeader() { + return methodHeader; + } + + /** + * Returns the bytecode info for this method. + * @return the bytecode info for this method, or null if it is + * abstract or native. + */ + public final BasicBlocks getBasicBlocks() { + return bb; + } + + /** + * Returns the import handler. The import handler should be informed + * about all types we (or an expression in this method) use, so that + * the corresponding class can be imported. + * @return the import handler. + */ + public final ImportHandler getImportHandler() { + return imports; + } + + /** + * Registers a type at the import handler. This should be called + * if an expression needs to print the type name to the code. The + * corresponding class will be imported in that case (if used + * often enough). + * @param type the type that should be registered. + */ + public final void useType(Type type) { + imports.useType(type); + } + + /** + * Inserts a structured block to the beginning of the method. + * This is called by transform constructors, to move the super + * call from the real constructor to the constructor$xx method + * (the jikes constructor). + * @param insertBlock the structured block that should be inserted. + */ + public void insertStructuredBlock(StructuredBlock insertBlock) { + if (methodHeader != null) { + methodHeader.prependBlock(insertBlock); + } else { + throw new IllegalStateException(); + } + } + + /** + * Checks if this method is a constructor, i.e. getName() returns + * "" or "". + * @return true, iff this method is a real constructor. + */ + public final boolean isConstructor() { + return isConstructor; + } + + /** + * Checks if this method is static. + * @return true, iff this method is static. + */ + public final boolean isStatic() { + return minfo.isStatic(); + } + + /** + * Checks if this method is synthetic, i.e. a synthetic attribute is + * present. + * @return true, iff this method is synthetic. + */ + public final boolean isSynthetic() { + return minfo.isSynthetic(); + } + + /** + * Checks if this method is strictfp + * @return true, iff this method is synthetic. + */ + public final boolean isStrictFP() { + return (minfo.getModifiers() & STRICTFP) != 0; + } + + /** + * Tells if this method is the constructor$xx method generated by jikes. + * @param value true, iff this method is the jikes constructor. + */ + public final void setJikesConstructor(MethodAnalyzer realConstr) { + jikesConstructor = realConstr; + } + + /** + * Tells if this method is the block$xx method generated by jikes. + * @param value true, iff this method is the jikes block initializer. + */ + public final void setJikesBlockInitializer(boolean value) { + isJikesBlockInitializer = value; + } + + /** + * Tells if this (constructor$xx) method has as first (implicit) + * parameter the instance of the outer class. + * @param value true, this method has the implicit parameter. + */ + public final void setHasOuterValue(boolean value) { + hasJikesOuterValue = value; + } + + /** + * Tells if this constructor can be omited, since it is implicit. + * @param value true, this method is the implicit constructor. + */ + public final void setAnonymousConstructor(boolean value) { + isAnonymousConstructor = value; + } + + /** + * Checks if this constructor can be omited, since it is implicit. + * @return true, this method is the implicit constructor. + */ + public final boolean isAnonymousConstructor() { + return isAnonymousConstructor; + } + + /** + * Get the synthetic analyzer for this method. + * @return the synthetic analyzer, or null if this method isn't + * synthetic. + */ + public final SyntheticAnalyzer getSynthetic() { + return synth; + } + + /** + * Get the return type of this method. + */ + public Type getReturnType() { + return methodType.getReturnType(); + } + + /** + * Get the class analyzer for the class containing this method. + */ + public ClassAnalyzer getClassAnalyzer() { + return classAnalyzer; + } + + /** + * Get the class info for the class containing this method. + */ + public ClassInfo getClazz() { + return classAnalyzer.clazz; + } + + /** + * Get the local info for a parameter. This call is valid after + * the analyze pass. + * @param nr the index of the parameter (start by zero and + * count the implicit this param for nonstatic method). + * @return the local info for the specified parameter. + * @see #getLocalInfo + */ + public final LocalInfo getParamInfo(int nr) { + return param[nr]; + } + + /** + * Return the number of parameters for this method. This call is + * valid after the analyze pass. + */ + public final int getParamCount() { + return param.length; + } + + /** + * Create a local info for a local variable located at an + * instruction with the given address. + * @param lvi the local variable info of the bytecode package. + * @return a new local info representing that local. + */ + public LocalInfo getLocalInfo(LocalVariableInfo lvi) { + LocalInfo li = new LocalInfo(this, lvi.getSlot()); + if ((Options.options & Options.OPTION_LVT) != 0 + && lvi.getName() != null) + li.addHint(lvi.getName(), Type.tType(classAnalyzer.getClassPath(), + lvi.getType())); + allLocals.addElement(li); + return li; + } + + /** + * Gets the complexity of this class. Must be called after it has + * been initialized. This is used for a nice progress bar. + */ + public double getComplexity() { + if (bb == null) + return 0.0; + else { + int count = 0; + Block[] blocks = bb.getBlocks(); + for (int i=0; i < blocks.length; i++) + count += blocks[i].getInstructions().length; + return (double) count; + } + } + + /** + * Analyzes the code of this method. This creates the + * flow blocks (including methodHeader) and analyzes them. + */ + private void analyzeCode(ProgressListener pl, double done, double scale) + { + int instrsPerStep = Integer.MAX_VALUE; + double instrScale = (scale * 0.9) / getComplexity(); + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.print(methodName+": "); + + if (pl != null) + instrsPerStep = (int) (STEP_COMPLEXITY / instrScale); + + Block[] blocks = bb.getBlocks(); + FlowBlock[] flows = new FlowBlock[blocks.length]; + int returnCount; + TransformExceptionHandlers excHandlers; + { + for (int i=0; i < blocks.length; i++) + flows[i] = new FlowBlock(this, i, i > 0 ? flows[i-1]: null); + + /* While we read the opcodes into FlowBlocks + * we try to combine sequential blocks, as soon as we + * find two sequential instructions in a row, where the + * second has no predecessors. + */ + int count = 0; + for (int i=0; i < blocks.length; i++) { + int mark = 100; + Instruction[] instrs = blocks[i].getInstructions(); + for (int j=0; j < instrs.length; j++) { + if (GlobalOptions.verboseLevel > 0 && j > mark) { + GlobalOptions.err.print('.'); + mark += 100; + } + if (++count >= instrsPerStep) { + done += count * instrScale; + pl.updateProgress(done, methodName); + count = 0; + } + Opcodes.addOpcode(flows[i], instrs[j], this); + } + Block[] succs = blocks[i].getSuccs(); + FlowBlock[] flowSuccs; + int lastOpcode = instrs.length > 0 + ? instrs[instrs.length-1].getOpcode() : Opcodes.opc_nop; + if (lastOpcode >= Opcodes.opc_ireturn + && lastOpcode <= Opcodes.opc_areturn) { + flowSuccs = new FlowBlock[] { FlowBlock.END_OF_METHOD }; + } else { + flowSuccs = new FlowBlock[succs.length]; + for (int j=0; j< succs.length; j++) { + if (succs[j] == null) + flowSuccs[j] = FlowBlock.END_OF_METHOD; + else + flowSuccs[j] = flows[succs[j].getBlockNr()]; + } + } + flows[i].setSuccessors(flowSuccs); + } + + done += count * instrScale; + Block startBlock = bb.getStartBlock(); + if (startBlock == null) + methodHeader = new FlowBlock(this, 0, null); + else + methodHeader = flows[startBlock.getBlockNr()]; + methodHeader.addStartPred(); + + Handler[] handlers = bb.getExceptionHandlers(); + excHandlers = new TransformExceptionHandlers(flows); + for (int i=0; i 0) + GlobalOptions.err.print('-'); + + excHandlers.analyze(); + methodHeader.analyze(); + methodHeader.removeStartPred(); + + if ((Options.options & Options.OPTION_PUSH) == 0 + && methodHeader.mapStackToLocal()) + methodHeader.removePush(); + if ((Options.options & Options.OPTION_ONETIME) != 0) + methodHeader.removeOnetimeLocals(); + + methodHeader.mergeParams(param); + + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println(""); + if (pl != null) { + done += 0.1 * scale; + pl.updateProgress(done, methodName); + } + } + + /** + * This is the first pass of the analyzation. It will analyze the + * code of this method, but not the method scoped classes. + */ + public void analyze(ProgressListener pl, double done, double scale) + throws ClassFormatError + { + if (pl != null) + pl.updateProgress(done, methodName); + if (bb != null) { + if ((Options.options & Options.OPTION_VERIFY) != 0) { + CodeVerifier verifier + = new CodeVerifier(getClazz(), minfo, bb); + try { + verifier.verify(); + } catch (VerifyException ex) { + ex.printStackTrace(GlobalOptions.err); + throw new InternalError("Verification error"); + } + } + } + + Type[] paramTypes = getType().getParameterTypes(); + int paramCount = (isStatic() ? 0 : 1) + paramTypes.length; + param = new LocalInfo[paramCount]; + + int offset = 0; + int slot = 0; + if (!isStatic()) { + ClassInfo classInfo = classAnalyzer.getClazz(); + param[offset] = getLocalInfo(bb != null + ? bb.getParamInfo(slot) + : LocalVariableInfo.getInfo(slot)); + param[offset].setExpression(new ThisOperator(classInfo, true)); + slot++; + offset++; + } + + for (int i=0; i< paramTypes.length; i++) { + param[offset] = getLocalInfo(bb != null + ? bb.getParamInfo(slot) + : LocalVariableInfo.getInfo(slot)); + param[offset].setType(paramTypes[i]); + slot += paramTypes[i].stackSize(); + offset++; + } + + for (int i= 0; i< exceptions.length; i++) + imports.useType(exceptions[i]); + + if (!isConstructor) + imports.useType(methodType.getReturnType()); + + if (bb != null) + analyzeCode(pl, done, scale); + } + + /** + * This is the second pass of the analyzation. It will analyze + * the method scoped classes. + */ + public void analyzeInnerClasses() + throws ClassFormatError + { + int serialnr = 0; + Enumeration elts = anonConstructors.elements(); + while (elts.hasMoreElements()) { + InvokeOperator cop = (InvokeOperator) elts.nextElement(); + analyzeInvokeOperator(cop); + } + } + + /** + * This is the third and last pass of the analyzation. It will analyze + * the types and names of the local variables and where to declare them. + * It will also determine where to declare method scoped local variables. + */ + public void makeDeclaration(Set done) { + if (innerAnalyzers != null) { + for (Enumeration enumeration = innerAnalyzers.elements(); + enumeration.hasMoreElements(); ) { + ClassAnalyzer classAna = (ClassAnalyzer) enumeration.nextElement(); + if (classAna.getParent() == this) { + OuterValues innerOV = classAna.getOuterValues(); + for (int i=0; i < innerOV.getCount(); i++) { + Expression value = innerOV.getValue(i); + if (value instanceof OuterLocalOperator) { + LocalInfo li = ((OuterLocalOperator) + value).getLocalInfo(); + if (li.getMethodAnalyzer() == this) + li.markFinal(); + } + } + } + } + } + + for (Enumeration enumeration = allLocals.elements(); + enumeration.hasMoreElements(); ) { + LocalInfo li = (LocalInfo)enumeration.nextElement(); + if (!li.isShadow()) + imports.useType(li.getType()); + } + + for (int i=0; i < param.length; i++) { + param[i].guessName(); + Iterator doneIter = done.iterator(); + while (doneIter.hasNext()) { + Declarable previous = (Declarable) doneIter.next(); + if (param[i].getName().equals(previous.getName())) { + /* A name conflict happened. */ + param[i].makeNameUnique(); + break; + } + } + done.add(param[i]); + } + + if (bb != null) { + methodHeader.makeDeclaration(done); + methodHeader.simplify(); + } + for (int i=0; i < param.length; i++) { + done.remove(param[i]); + // remove the parameters, since we leave the scope + } + } + + /** + * Tells if this method is synthetic or implicit or something else, so + * that it doesn't have to be written to the source code. + * @return true, iff it shouldn't be written to the source code. + */ + public boolean skipWriting() { + if (isSynthetic() + && (minfo.getModifiers() & 0x0040 /*ACC_BRIDGE*/) != 0) + return true; + + if (synth != null) { + // We don't need this class anymore (hopefully?) + if (synth.getKind() == synth.GETCLASS) + return true; + if (synth.getKind() >= synth.ACCESSGETFIELD + && synth.getKind() <= synth.ACCESSDUPPUTSTATIC + && (Options.options & Options.OPTION_INNER) != 0 + && (Options.options & Options.OPTION_ANON) != 0) + return true; + } + + if (jikesConstructor == this) { + // This is the first empty part of a jikes constructor + return true; + } + + boolean declareAsConstructor = isConstructor; + int skipParams = 0; + int modifiedModifiers = minfo.getModifiers(); + if (isConstructor() && !isStatic() + && classAnalyzer.outerValues != null) + skipParams = classAnalyzer.outerValues.getCount(); + + if (jikesConstructor != null) { + // This is the real part of a jikes constructor + declareAsConstructor = true; + skipParams = hasJikesOuterValue + && classAnalyzer.outerValues.getCount() > 0 ? 1 : 0; + // get the modifiers of the real constructor + modifiedModifiers = jikesConstructor.minfo.getModifiers(); + } + + if (isJikesBlockInitializer) + return true; + + /* The default constructor must be empty + * and mustn't throw exceptions */ + if (getMethodHeader() == null + || !(getMethodHeader().getBlock() instanceof net.sf.jode.flow.EmptyBlock) + || !getMethodHeader().hasNoJumps() + || exceptions.length > 0) + return false; + + if (declareAsConstructor + /* The access rights of default constructor should + * be public, iff the class is public, otherwise package. + * But this rule doesn't necessarily apply for anonymous + * classes... + */ + && ((modifiedModifiers + & (Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE + | Modifier.SYNCHRONIZED | Modifier.STATIC + | Modifier.ABSTRACT | Modifier.NATIVE)) + == (classAnalyzer.getModifiers() + & (Modifier.PROTECTED | Modifier.PUBLIC)) + || classAnalyzer.getName() == null) + && classAnalyzer.constructors.length == 1) { + + // If the constructor doesn't take parameters (except outerValues) + // or if it is the anonymous constructor it can be removed. + if (methodType.getParameterTypes().length == skipParams + || isAnonymousConstructor) + return true; + } + + if (isConstructor() && isStatic()) + return true; + + return false; + } + + /** + * Dumps the source code for this method to the specified writer. + * @param writer the tabbed print writer the code should be written to. + * @exception IOException, if writer throws an exception. + */ + public void dumpSource(TabbedPrintWriter writer) + throws IOException + { + boolean declareAsConstructor = isConstructor; + int skipParams = 0; + int modifiedModifiers = minfo.getModifiers(); + + if (isConstructor() && !isStatic() + && (Options.options & Options.OPTION_CONTRAFO) != 0) { + if (classAnalyzer.outerValues != null) + skipParams = classAnalyzer.outerValues.getCount(); + else if (classAnalyzer.getOuterInstance() != null) + skipParams = 1; + } + + if (jikesConstructor != null) { + // This is the real part of a jikes constructor + declareAsConstructor = true; + skipParams = hasJikesOuterValue + && classAnalyzer.outerValues.getCount() > 0 ? 1 : 0; + // get the modifiers of the real constructor + modifiedModifiers = jikesConstructor.minfo.getModifiers(); + } + + if (minfo.isDeprecated()) { + writer.println("/**"); + writer.println(" * @deprecated"); + writer.println(" */"); + } + + writer.pushScope(this); + + /* + * JLS-1.0, section 9.4: + * + * For compatibility with older versions of Java, it is + * permitted but discouraged, as a matter of style, to + * redundantly specify the abstract modifier for methods + * declared in interfaces. + * + * Every method declaration in the body of an interface is + * implicitly public. It is permitted, but strongly + * discouraged as a matter of style, to redundantly specify + * the public modifier for interface methods. We don't + * follow this second rule and mark this method explicitly + * as public. + */ + if (classAnalyzer.getClazz().isInterface()) + modifiedModifiers &= ~Modifier.ABSTRACT; + + /* Don't ask me why, but jikes declares the static constructor + * as final. + */ + if (isConstructor() && isStatic()) + modifiedModifiers &= ~(Modifier.FINAL | Modifier.PUBLIC + | Modifier.PROTECTED | Modifier.PRIVATE); + modifiedModifiers &= ~STRICTFP; + + writer.startOp(writer.NO_PAREN, 0); + writer.startOp(writer.NO_PAREN, 5); + + String delim =""; + if (minfo.isSynthetic()) { + writer.print("/*synthetic*/"); + delim = " "; + } + + String modif = Modifier.toString(modifiedModifiers); + writer.print(delim + modif); + if (modif.length() > 0) + delim = " "; + if (isStrictFP()) { + /* The STRICTFP modifier is set. + * We handle it, since java.lang.reflect.Modifier is too dumb. + */ + + /* If STRICTFP is already set for class don't set it for method. + * And don't set STRICTFP for native methods or constructors. + */ + if (!classAnalyzer.isStrictFP() + && !isConstructor() + && (modifiedModifiers & Modifier.NATIVE) == 0) { + writer.print(delim + "strictfp"); + delim = " "; + } + } + + if (isConstructor + && (isStatic() + || (classAnalyzer.getName() == null + && skipParams == methodType.getParameterTypes().length))) { + /* static block or unnamed constructor */ + } else { + writer.print(delim); + if (declareAsConstructor) + writer.print(classAnalyzer.getName()); + else { + writer.printType(getReturnType()); + writer.print(" " + methodName); + } + writer.breakOp(); + writer.printOptionalSpace(); + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 0); + int offset = skipParams + (isStatic() ? 0 : 1); + for (int i = offset; i < param.length; i++) { + if (i > offset) { + writer.print(", "); + writer.breakOp(); + } + param[i].dumpDeclaration(writer); + } + writer.endOp(); + writer.print(")"); + } + writer.endOp(); + if (exceptions.length > 0) { + writer.breakOp(); + writer.print(" throws "); + writer.startOp(writer.NO_PAREN, 0); + for (int i= 0; i< exceptions.length; i++) { + if (i > 0) { + writer.print(", "); + writer.breakOp(); + } + writer.printType(exceptions[i]); + } + writer.endOp(); + } + writer.endOp(); + if (bb != null) { + writer.openBraceNoIndent(); + writer.tab(); + methodHeader.dumpSource(writer); + writer.untab(); + writer.closeBraceNoIndent(); + } else + writer.println(";"); + writer.popScope(); + } + + /** + * Checks if the variable set contains a local with the given name. + * @return the local info the has the given name, or null if it doesn't + * exists. + */ + public LocalInfo findLocal(String name) { + Enumeration enumeration = allLocals.elements(); + while (enumeration.hasMoreElements()) { + LocalInfo li = (LocalInfo) enumeration.nextElement(); + if (li.getName().equals(name)) + return li; + } + return null; + } + + /** + * Checks if a method scoped class with the given name exists in this + * method (not in a parent method). + * @return the class analyzer with the given name, or null if it + * doesn' exists. + */ + public ClassAnalyzer findAnonClass(String name) { + if (innerAnalyzers != null) { + Enumeration enumeration = innerAnalyzers.elements(); + while (enumeration.hasMoreElements()) { + ClassAnalyzer classAna = (ClassAnalyzer) enumeration.nextElement(); + if (classAna.getParent() == this + && classAna.getName() != null + && classAna.getName().equals(name)) { + return classAna; + } + } + } + return null; + } + + /** + * Checks if the specified object lies in this scope. + * @param obj the object. + * @param scopeType the type of this object. + */ + public boolean isScopeOf(Object obj, int scopeType) { + if (scopeType == METHODSCOPE + && obj instanceof ClassInfo) { + ClassAnalyzer ana = getClassAnalyzer((ClassInfo)obj); + if (ana != null) + return ana.getParent() == this; + } + return false; + } + + /** + * Checks if the specified name conflicts with an object in this scope. + * @param name the name to check. + * @param scopeType the usage type of this name, AMBIGUOUSNAME if it is + * ambiguous. + */ + public boolean conflicts(String name, int usageType) { + if (usageType == AMBIGUOUSNAME || usageType == LOCALNAME) + return findLocal(name) != null; + if (usageType == AMBIGUOUSNAME || usageType == CLASSNAME) + return findAnonClass(name) != null; + return false; + } + + /** + * Gets the parent scope, i.e. the class analyzer for the class containing + * this method. + * @XXX needed? + */ + public ClassDeclarer getParent() { + return getClassAnalyzer(); + } + + /** + * Registers an anonymous constructor invokation. This should be called + * in the analyze or analyzeInner pass by invoke subexpressions. + * @param cop the constructor invokation, that creates the method scoped + * class. + */ + public void addAnonymousConstructor(InvokeOperator cop) { + anonConstructors.addElement(cop); + } + + public void analyzeInvokeOperator(InvokeOperator cop) { + ClassInfo clazz = (ClassInfo) cop.getClassInfo(); + ClassAnalyzer anonAnalyzer = getParent().getClassAnalyzer(clazz); + + if (anonAnalyzer == null) { + /* Create a new outerValues array corresponding to the + * first constructor invocation. + */ + Expression[] outerValueArray; + Expression[] subExprs = cop.getSubExpressions(); + outerValueArray = new Expression[subExprs.length-1]; + + for (int j=0; j < outerValueArray.length; j++) { + Expression expr = subExprs[j+1].simplify(); + if (expr instanceof CheckNullOperator) + expr = ((CheckNullOperator) + expr).getSubExpressions()[0]; + if (expr instanceof ThisOperator) { + outerValueArray[j] = + new ThisOperator(((ThisOperator) expr).getClassInfo()); + continue; + } + LocalInfo li = null; + if (expr instanceof LocalLoadOperator) { + li = ((LocalLoadOperator) expr).getLocalInfo(); + if (!li.isConstant()) + li = null; + } + if (expr instanceof OuterLocalOperator) + li = ((OuterLocalOperator) expr).getLocalInfo(); + + if (li != null) { + outerValueArray[j] = new OuterLocalOperator(li); + continue; + } + + Expression[] newOuter = new Expression[j]; + System.arraycopy(outerValueArray, 0, newOuter, 0, j); + outerValueArray = newOuter; + break; + } + try { + anonAnalyzer = new ClassAnalyzer(this, clazz, imports, + outerValueArray); + } catch (IOException ex) { + GlobalOptions.err.println + ("Error while reading anonymous class "+clazz+"."); + return; + } + addClassAnalyzer(anonAnalyzer); + anonAnalyzer.initialize(); + anonAnalyzer.analyze(null, 0.0, 0.0); + anonAnalyzer.analyzeInnerClasses(null, 0.0, 0.0); + } else { + /* + * Get the previously created outerValues and + * its length. + */ + OuterValues outerValues = anonAnalyzer.getOuterValues(); + /* + * Merge the other constructor invocation and + * possibly shrink outerValues array. + */ + Expression[] subExprs = cop.getSubExpressions(); + for (int j=0; j < outerValues.getCount(); j++) { + if (j+1 < subExprs.length) { + Expression expr = subExprs[j+1].simplify(); + if (expr instanceof CheckNullOperator) + expr = ((CheckNullOperator) expr) + .getSubExpressions()[0]; + + if (outerValues.unifyOuterValues(j, expr)) + continue; + + } + outerValues.setCount(j); + break; + } + } + + if (usedAnalyzers == null) + usedAnalyzers = new ArrayList(); + usedAnalyzers.add(anonAnalyzer); + } + + /** + * Get the class analyzer for the given class info. This searches + * the method scoped/anonymous classes in this method and all + * outer methods and the outer classes for the class analyzer. + * @param cinfo the classinfo for which the analyzer is searched. + * @return the class analyzer, or null if there is not an outer + * class that equals cinfo, and not a method scope/inner class in + * an outer method. + */ + public ClassAnalyzer getClassAnalyzer(ClassInfo cinfo) { + if (innerAnalyzers != null) { + Enumeration enumeration = innerAnalyzers.elements(); + while (enumeration.hasMoreElements()) { + ClassAnalyzer classAna = (ClassAnalyzer) enumeration.nextElement(); + if (classAna.getClazz().equals(cinfo)) { + if (classAna.getParent() != this) { + ClassDeclarer declarer = classAna.getParent(); + while (declarer != this) { + if (declarer instanceof MethodAnalyzer) + ((MethodAnalyzer) declarer) + .innerAnalyzers.removeElement(classAna); + declarer = declarer.getParent(); + } + classAna.setParent(this); + } + return classAna; + } + } + } + return getParent().getClassAnalyzer(cinfo); + } + + public void addClassAnalyzer(ClassAnalyzer clazzAna) { + if (innerAnalyzers == null) + innerAnalyzers = new Vector(); + innerAnalyzers.addElement(clazzAna); + getParent().addClassAnalyzer(clazzAna); + } + + /** + * We add the named method scoped classes to the declarables. + */ + public void fillDeclarables(Collection used) { + if (usedAnalyzers != null) + used.addAll(usedAnalyzers); + if (innerAnalyzers != null) { + Enumeration enumeration = innerAnalyzers.elements(); + while (enumeration.hasMoreElements()) { + ClassAnalyzer classAna = (ClassAnalyzer) enumeration.nextElement(); + if (classAna.getParent() == this) + classAna.fillDeclarables(used); + } + } + } + + public boolean isMoreOuterThan(ClassDeclarer declarer) { + ClassDeclarer ancestor = declarer; + while (ancestor != null) { + if (ancestor == this) + return true; + ancestor = ancestor.getParent(); + } + return false; + } + + public String toString() { + return getClass().getName()+"["+getClazz()+"."+getName()+"]"; + } +} diff --git a/jode/src/net/sf/jode/decompiler/Opcodes.java b/jode/src/net/sf/jode/decompiler/Opcodes.java new file mode 100644 index 0000000..f4287f5 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Opcodes.java @@ -0,0 +1,443 @@ +/* Opcodes Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.type.Type; +import net.sf.jode.type.IntegerType; +import net.sf.jode.type.MethodType; +import net.sf.jode.expr.*; +import net.sf.jode.flow.*; +import net.sf.jode.bytecode.*; +import java.io.*; +import java.util.Vector; + +/** + * This is an abstract class which creates flow blocks for the + * opcodes in a byte stream. + */ +public abstract class Opcodes implements net.sf.jode.bytecode.Opcodes { + + private final static Type tIntHint + = new IntegerType(IntegerType.IT_I, + IntegerType.IT_I + | IntegerType.IT_B + | IntegerType.IT_C + | IntegerType.IT_S); + private final static Type tBoolIntHint + = new IntegerType(IntegerType.IT_I + | IntegerType.IT_Z, + IntegerType.IT_I + | IntegerType.IT_B + | IntegerType.IT_C + | IntegerType.IT_S + | IntegerType.IT_Z); + + private final static int LOCAL_TYPES = 0; + private final static int ARRAY_TYPES = 1; + private final static int UNARY_TYPES = 2; + private final static int I2BCS_TYPES = 3; + private final static int BIN_TYPES = 4; + private final static int ZBIN_TYPES = 5; + + private final static Type types[][] = { + // Local types + { Type.tBoolUInt, Type.tLong, Type.tFloat, Type.tDouble, + Type.tUObject }, + // Array types + { Type.tInt, Type.tLong, Type.tFloat, Type.tDouble, Type.tUObject, + Type.tBoolByte, Type.tChar, Type.tShort }, + // ifld2ifld and shl types + { Type.tInt, Type.tLong, Type.tFloat, Type.tDouble, Type.tUObject }, + // i2bcs types + { Type.tByte, Type.tChar, Type.tShort }, + // cmp/add/sub/mul/div types + { tIntHint, Type.tLong, Type.tFloat, Type.tDouble, Type.tUObject }, + // and/or/xor types + { tBoolIntHint, Type.tLong, Type.tFloat, Type.tDouble, Type.tUObject } + }; + + private static StructuredBlock createNormal(MethodAnalyzer ma, + Instruction instr, + Expression expr) + { + return new InstructionBlock(expr); + } + + private static StructuredBlock createSpecial(MethodAnalyzer ma, + Instruction instr, + int type, + int stackcount, int param) + { + return new SpecialBlock(type, stackcount, param); + } + + private static StructuredBlock createGoto(MethodAnalyzer ma, + Instruction instr) + { + return new EmptyBlock(); + } + + private static StructuredBlock createJsr(MethodAnalyzer ma, + Instruction instr) + { + return new JsrBlock(); + } + + private static StructuredBlock createIfGoto(MethodAnalyzer ma, + Instruction instr, + Expression expr) + { + return new ConditionalBlock(expr); + } + + private static StructuredBlock createSwitch(MethodAnalyzer ma, + Instruction instr, + int[] cases) + { + return new SwitchBlock(new NopOperator(Type.tUInt), cases); + } + + private static StructuredBlock createBlock(MethodAnalyzer ma, + Instruction instr, + StructuredBlock block) + { + return block; + } + + private static StructuredBlock createRet(MethodAnalyzer ma, + Instruction instr, + LocalInfo local) + { + return new RetBlock(local); + } + + /** + * Converts an instruction to a StructuredBlock and appencs it to the + * flow block. + * @param flow The flowblock to which we should add. + * @param instr The instruction to add. + * @param ma The Method Analyzer + * (where further information can be get from). + * @return The FlowBlock representing this opcode + * or null if the stream is empty. + */ + public static void addOpcode(FlowBlock flow, Instruction instr, + MethodAnalyzer ma) + { + ClassPath cp = ma.getClassAnalyzer().getClassPath(); + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_nop: + break; + case opc_ldc: + case opc_ldc2_w: + { + Expression expr; + if (instr.getConstant() instanceof Reference) { + Reference ref = (Reference) instr.getConstant(); + expr = new ClassFieldOperator + (Type.tType(cp, ref.getType()), + Type.tType(cp, ref.getClazz())); + } else if (instr.getConstant() instanceof String) { + expr = new ConstOperator(cp, (String) instr.getConstant()); + } else { + expr = new ConstOperator(instr.getConstant()); + } + flow.appendBlock(createNormal(ma, instr, expr)); + } + break; + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: { + LocalInfo local = ma.getLocalInfo(instr.getLocalInfo()); + flow.appendReadBlock + (createNormal + (ma, instr, new LocalLoadOperator + (types[LOCAL_TYPES][opcode-opc_iload], ma, local)), local); + break; + } + case opc_iaload: case opc_laload: + case opc_faload: case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + flow.appendBlock + (createNormal + (ma, instr, new ArrayLoadOperator + (types[ARRAY_TYPES][opcode - opc_iaload]))); + break; + case opc_istore: case opc_lstore: + case opc_fstore: case opc_dstore: case opc_astore: { + LocalInfo local = ma.getLocalInfo(instr.getLocalInfo()); + flow.appendWriteBlock + (createNormal + (ma, instr, new StoreInstruction + (new LocalStoreOperator + (types[LOCAL_TYPES][opcode-opc_istore], local))), local); + break; + } + case opc_iastore: case opc_lastore: + case opc_fastore: case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: + flow.appendBlock + (createNormal + (ma, instr, new StoreInstruction + (new ArrayStoreOperator + (types[ARRAY_TYPES][opcode - opc_iastore])))); + break; + case opc_pop: case opc_pop2: + flow.appendBlock + (createSpecial + (ma, instr, SpecialBlock.POP, opcode - opc_pop + 1, 0)); + break; + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + flow.appendBlock + (createSpecial + (ma, instr, SpecialBlock.DUP, + (opcode - opc_dup)/3+1, (opcode - opc_dup)%3)); + break; + case opc_swap: + flow.appendBlock + (createSpecial(ma, instr, SpecialBlock.SWAP, 1, 0)); + break; + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + flow.appendBlock + (createNormal + (ma, instr, new BinaryOperator + (types[BIN_TYPES][(opcode - opc_iadd)%4], + (opcode - opc_iadd)/4+Operator.ADD_OP))); + break; + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: + flow.appendBlock + (createNormal + (ma, instr, new UnaryOperator + (types[UNARY_TYPES][opcode - opc_ineg], Operator.NEG_OP))); + break; + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: + flow.appendBlock + (createNormal + (ma, instr, new ShiftOperator + (types[UNARY_TYPES][(opcode - opc_ishl)%2], + (opcode - opc_ishl)/2 + Operator.SHIFT_OP))); + break; + case opc_iand: case opc_land: + case opc_ior : case opc_lor : + case opc_ixor: case opc_lxor: + flow.appendBlock + (createNormal + (ma, instr, new BinaryOperator + (types[ZBIN_TYPES][(opcode - opc_iand)%2], + (opcode - opc_iand)/2 + Operator.AND_OP))); + break; + case opc_iinc: { + LocalInfo local = ma.getLocalInfo(instr.getLocalInfo()); + int value = instr.getIncrement(); + int operation = Operator.ADD_OP; + if (value < 0) { + value = -value; + operation = Operator.SUB_OP; + } + flow.appendReadBlock + (createNormal + (ma, instr, new IIncOperator + (new LocalStoreOperator(Type.tInt, local), + value, operation + Operator.OPASSIGN_OP)), local); + break; + } + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: { + int from = (opcode-opc_i2l)/3; + int to = (opcode-opc_i2l)%3; + if (to >= from) + to++; + flow.appendBlock + (createNormal + (ma, instr, new ConvertOperator(types[UNARY_TYPES][from], + types[UNARY_TYPES][to]))); + break; + } + case opc_i2b: case opc_i2c: case opc_i2s: + flow.appendBlock(createNormal + (ma, instr, new ConvertOperator + (types[UNARY_TYPES][0], types[I2BCS_TYPES][opcode-opc_i2b]))); + break; + case opc_lcmp: + case opc_fcmpl: case opc_fcmpg: + case opc_dcmpl: case opc_dcmpg: + flow.appendBlock(createNormal + (ma, instr, new CompareToIntOperator + (types[BIN_TYPES][(opcode-(opc_lcmp-3))/2], + (opcode == opc_fcmpg || opcode == opc_dcmpg)))); + break; + case opc_ifeq: case opc_ifne: + flow.appendBlock(createIfGoto + (ma, instr, + new CompareUnaryOperator + (Type.tBoolInt, opcode - (opc_ifeq-Operator.COMPARE_OP)))); + break; + case opc_iflt: case opc_ifge: case opc_ifgt: case opc_ifle: + flow.appendBlock(createIfGoto + (ma, instr, + new CompareUnaryOperator + (Type.tInt, opcode - (opc_ifeq-Operator.COMPARE_OP)))); + break; + case opc_if_icmpeq: case opc_if_icmpne: + flow.appendBlock + (createIfGoto + (ma, instr, + new CompareBinaryOperator + (tBoolIntHint, + opcode - (opc_if_icmpeq-Operator.COMPARE_OP)))); + break; + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + flow.appendBlock + (createIfGoto + (ma, instr, + new CompareBinaryOperator + (tIntHint, opcode - (opc_if_icmpeq-Operator.COMPARE_OP)))); + break; + case opc_if_acmpeq: case opc_if_acmpne: + flow.appendBlock + (createIfGoto + (ma, instr, + new CompareBinaryOperator + (Type.tUObject, + opcode - (opc_if_acmpeq-Operator.COMPARE_OP)))); + break; + case opc_jsr: + flow.appendBlock(createJsr(ma, instr)); + break; + case opc_ret: { + LocalInfo local = ma.getLocalInfo(instr.getLocalInfo()); + flow.appendReadBlock(createRet(ma, instr, local), local); + break; + } + case opc_lookupswitch: { + int[] cases = instr.getValues(); + flow.appendBlock(createSwitch(ma, instr, cases)); + break; + } + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: { + Type retType = Type.tSubType(ma.getReturnType()); + flow.appendBlock + (createBlock + (ma, instr, new ReturnBlock(new NopOperator(retType)))); + break; + } + case opc_return: + throw new InternalError("opc_return no longer allowed"); + + case opc_getstatic: + case opc_getfield: { + Reference ref = instr.getReference(); + flow.appendBlock(createNormal + (ma, instr, new GetFieldOperator + (ma, opcode == opc_getstatic, ref))); + break; + } + case opc_putstatic: + case opc_putfield: { + Reference ref = instr.getReference(); + flow.appendBlock + (createNormal + (ma, instr, new StoreInstruction + (new PutFieldOperator(ma, opcode == opc_putstatic, ref)))); + break; + } + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic : + case opc_invokeinterface: { + Reference ref = instr.getReference(); + int flag = (ref.getName().equals("") + ? InvokeOperator.CONSTRUCTOR + : opcode == opc_invokestatic ? InvokeOperator.STATIC + : opcode == opc_invokespecial ? InvokeOperator.SPECIAL + : InvokeOperator.VIRTUAL); + StructuredBlock block = createNormal + (ma, instr, new InvokeOperator(ma, flag, ref)); + flow.appendBlock(block); + break; + } + case opc_new: { + Type type = Type.tType(cp, instr.getClazzType()); + ma.useType(type); + flow.appendBlock(createNormal(ma, instr, new NewOperator(type))); + break; + } + case opc_arraylength: + flow.appendBlock(createNormal + (ma, instr, new ArrayLengthOperator())); + break; + case opc_athrow: + flow.appendBlock(createBlock + (ma, instr, + new ThrowBlock(new NopOperator(Type.tUObject)))); + break; + case opc_checkcast: { + Type type = Type.tType(cp, instr.getClazzType()); + ma.useType(type); + flow.appendBlock(createNormal + (ma, instr, new CheckCastOperator(type))); + break; + } + case opc_instanceof: { + Type type = Type.tType(cp, instr.getClazzType()); + ma.useType(type); + flow.appendBlock(createNormal + (ma, instr, new InstanceOfOperator(type))); + break; + } + case opc_monitorenter: + flow.appendBlock(createNormal(ma, instr, + new MonitorEnterOperator())); + break; + case opc_monitorexit: + flow.appendBlock(createNormal(ma, instr, + new MonitorExitOperator())); + break; + case opc_multianewarray: { + Type type = Type.tType(cp, instr.getClazzType()); + ma.useType(type); + int dimension = instr.getDimensions(); + flow.appendBlock(createNormal + (ma, instr, + new NewArrayOperator(type, dimension))); + break; + } + case opc_ifnull: case opc_ifnonnull: + flow.appendBlock(createIfGoto + (ma, instr, new CompareUnaryOperator + (Type.tUObject, + opcode - (opc_ifnull-Operator.COMPARE_OP)))); + break; + default: + throw new InternalError("Invalid opcode "+opcode); + } + } +} + diff --git a/jode/src/net/sf/jode/decompiler/Options.java b/jode/src/net/sf/jode/decompiler/Options.java new file mode 100644 index 0000000..ac52057 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Options.java @@ -0,0 +1,59 @@ +/* Options Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.bytecode.ClassInfo; +import java.io.IOException; + +public class Options { + public static final int OPTION_LVT = 0x0001; + public static final int OPTION_INNER = 0x0002; + public static final int OPTION_ANON = 0x0004; + public static final int OPTION_PUSH = 0x0008; + public static final int OPTION_PRETTY = 0x0010; + public static final int OPTION_DECRYPT = 0x0020; + public static final int OPTION_ONETIME = 0x0040; + public static final int OPTION_IMMEDIATE = 0x0080; + public static final int OPTION_VERIFY = 0x0100; + public static final int OPTION_CONTRAFO = 0x0200; + + public static int options = + OPTION_LVT | OPTION_INNER | OPTION_ANON | OPTION_PRETTY | + OPTION_DECRYPT | OPTION_VERIFY | OPTION_CONTRAFO; + + public final static boolean doAnonymous() { + return (options & OPTION_ANON) != 0; + } + + public final static boolean doInner() { + return (options & OPTION_INNER) != 0; + } + + public static boolean skipClass(ClassInfo clazz) { + if (!doInner() && !doAnonymous()) + return false; + try { + clazz.load(ClassInfo.OUTERCLASS); + } catch (IOException ex) { + return false; + } + return (doInner() && clazz.getOuterClass() != null + || doAnonymous() && clazz.isMethodScoped()); + } +} diff --git a/jode/src/net/sf/jode/decompiler/OuterValueListener.java b/jode/src/net/sf/jode/decompiler/OuterValueListener.java new file mode 100644 index 0000000..1c95119 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/OuterValueListener.java @@ -0,0 +1,37 @@ +/* OuterValueListener Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; + +/** + * Interface, that every one should implement who is interested in + * outer value changes. + * + * outerValues + */ +public interface OuterValueListener { + /** + * Tells that the guessed number of outerValues was too big and thus + * needs shrinking right now. + * @param clazzAna The clazzAnalyzer for which this info is. + * @param newCount The new number of outer values (not slot number) + * before the first parameter. + */ + public void shrinkingOuterValues(OuterValues ov, int newCount); +} diff --git a/jode/src/net/sf/jode/decompiler/OuterValues.java b/jode/src/net/sf/jode/decompiler/OuterValues.java new file mode 100644 index 0000000..a51d113 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/OuterValues.java @@ -0,0 +1,392 @@ +/* OuterValues Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import net.sf.jode.GlobalOptions; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.ThisOperator; +import net.sf.jode.expr.LocalLoadOperator; +import net.sf.jode.expr.OuterLocalOperator; +import net.sf.jode.util.SimpleMap; +import net.sf.jode.type.Type; + +import java.util.Vector; +import java.util.Enumeration; + +/** + *

A list of local variables that a method scoped class inherits + * from its declaring method.

+ * + *

A method scoped class is a class that is declared in a method + * and it can access other (final) local variables declared earlier. + * To realize this the java compiler adds hidden parameters to the + * constructor of the method scoped class, where it passes the values + * of the local varaiables. If a method scoped class has more than + * one constructor, each gets this hidden parameters. These hidden + * parameters are the outerValues, because they are used to transport + * a value of a local variable from an outer method.

+ * + *

Unfortunately there is no definite way to distinguish this outer + * value parameters from the real parameters, so jode has to do a + * guess: It first assumes that everything is an outer value parameter + * added by the compiler and if this leads to contradiction shrinks + * the count of these parameters. A contradiction can occur, because + * the constructor is called two times with different values.

+ * + *

On the other hand the TransformConstructor class assumes at some + * point that some parameters are outer values. If later a + * contradiction occurs, jode has to give up and complain loudly.

+ * + *

Every class interested in outer values, may register itself as + * OuterValueListener. It will then be notified every time the outer + * values shrink. Sometimes there are real listener queues: if + * another method scoped class creates instances of the first in its + * constructor by passing some of its own outer value parameter, it + * may first seem that all parameters of the first class's constructor + * are outer values. Because we can't be sure that the parameter from + * the second class's constructor is really an outer value, we have to + * add a listener. If later a constructor invokation for the second + * class is found, where a parameter does not have the right outer + * value, the listener will also shrink the outer values list of the + * first class.

+ * + *

A non static _class_ scoped class (i.e. a normal inner class) also + * has a hidden parameter, namely the instance of its outer class. + * This hidden parameter is not considered as outer value though. + * Note that you can even explicitly invoke the constructor with a + * different outer class instance, by using the + * outerInstance.new InnerClass() construct. This + * exception doesn't apply to method scoped classes, though.

+ * + *

Anonymous classes can of course also extend class or method scoped + * classes. If they are compiled by jikes the constructor takes as + * last parameter the outer instance of its super class. This should + * really be the first parameter just after the outerValues, as it + * is under javac. We mark such classes as jikesAnonymousInner. This + * is done in the initialize() pass.

+ * + * @see #addOuterValueListener + * @since 1.0.93 */ +public class OuterValues +{ + private ClassAnalyzer clazzAnalyzer; + + /** + * The outer values. An outer value is either a + * LocalLoadOperator, a ThisOperator, or a OuterLocalOperator. + */ + private Expression[] head; + private Vector ovListeners; + private boolean jikesAnonymousInner; + private boolean implicitOuterClass; + + /** + * The maximal number of parameters used for outer values. + */ + private int headCount; + /** + * The minimal number of parameters used for outer values. + */ + private int headMinCount; + + + public OuterValues(ClassAnalyzer ca, Expression[] head) { + this.clazzAnalyzer = ca; + this.head = head; + this.headMinCount = 0; + this.headCount = head.length; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("Created OuterValues: "+this); + } + + public Expression getValue(int i) { + /** require i < getCount() **/ + return head[i]; + } + + public int getCount() { + return headCount; + } + + private int getNumberBySlot(int slot) { + slot--; // skip this parameter (not an outer value) + for (int i=0; slot >= 0 && i < headCount; i++) { + if (slot == 0) + return i; + slot -= head[i].getType().stackSize(); + } + return -1; + } + + /** + * Get the outer value corresponding to a given slot. This will + * also adjust the minSlot value. This only considers head slots. + * @return index into outerValues array or -1, if not matched. + */ + public Expression getValueBySlot(int slot) { + slot--; // skip this parameter (not an outer value) + for (int i=0; i < headCount; i++) { + if (slot == 0) { + Expression expr = head[i]; + if (i >= headMinCount) + headMinCount = i; + return expr; + } + slot -= head[i].getType().stackSize(); + } + return null; + } + + /** + * If li is a local variable of a constructor, and it could be + * an outer value, return this outer value and mark ourself as + * listener. If that outer value gets invalid later, we shrink + * ourself to the given nr. + * @param expr The expression to lift. + * @param nr The nr of outer values we shrink to, if something + * happens later. + * @return the outer value if the above conditions are true, + * null otherwise. + */ + private Expression liftOuterValue(LocalInfo li, final int nr) { + MethodAnalyzer method = li.getMethodAnalyzer(); + + if (!method.isConstructor() || method.isStatic()) + return null; + OuterValues ov = method.getClassAnalyzer().getOuterValues(); + if (ov == null) + return null; + + int ovNr = ov.getNumberBySlot(li.getSlot()); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" ovNr "+ovNr+","+ov); + if (ovNr < 0 && ov.getCount() >= 1 && ov.isJikesAnonymousInner()) { + /* Second chance if this is a jikesAnonInner class: + * last parameter is this parameter. XXX + */ + Type[] paramTypes = method.getType().getParameterTypes(); + int lastSlot = 1; + for (int i=0; i < paramTypes.length - 1; i++) + lastSlot += paramTypes[i].stackSize(); + + /* jikesAnonInner corresponds to the first outer value */ + if (li.getSlot() == lastSlot) + ovNr = 0; + } + if (ovNr < 0) + return null; + if (ov != this || ovNr > nr) { + final int limit = ovNr; + ov.addOuterValueListener(new OuterValueListener() { + public void shrinkingOuterValues + (OuterValues other, int newCount) { + if (newCount <= limit) + setCount(nr); + } + }); + } + return ov.head[ovNr]; + } + + public boolean unifyOuterValues(int nr, + Expression otherExpr) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("unifyOuterValues: "+this+"," + +nr+","+otherExpr); + /** require nr < getCount() **/ + Expression expr1 = otherExpr; + Expression expr2 = head[nr]; + LocalInfo li1; + + /* Wow, unifying outer values of different constructors in + * different methods of different classes can get complicated. + * We have not committed the number of OuterValues. So we + * can't say for sure, if the local load matches an outer + * local if this is a constructor. Even worse: The previous + * outerValues may be a load of a constructor local, that + * should be used as outer value... + * + * See MethodScopeTest for examples. + * + * We look if there is a way to merge them and register an + * outer value listener to lots of classes. + */ + + if (expr1 instanceof ThisOperator) { + li1 = null; + } else if (expr1 instanceof OuterLocalOperator) { + li1 = ((OuterLocalOperator) expr1).getLocalInfo(); + } else if (expr1 instanceof LocalLoadOperator) { + li1 = ((LocalLoadOperator) expr1).getLocalInfo(); + } else + return false; + + /* First lift expr1 until it is a parent of this class */ + while (li1 != null + && !li1.getMethodAnalyzer().isMoreOuterThan(clazzAnalyzer)) { + expr1 = liftOuterValue(li1, nr); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" lift1 "+li1 + +" in "+li1.getMethodAnalyzer() + +" to "+expr1); + + if (expr1 instanceof ThisOperator) { + li1 = null; + } else if (expr1 instanceof OuterLocalOperator) { + li1 = ((OuterLocalOperator) expr1).getLocalInfo(); + } else + return false; + } + /* Now lift expr2 until expr1 and expr2 are equal */ + while (!expr1.equals(expr2)) { + if (expr2 instanceof OuterLocalOperator) { + LocalInfo li2 = ((OuterLocalOperator) expr2).getLocalInfo(); + + /* if expr1 and expr2 point to same local, we have + * succeeded (note that expr1 may be an LocalLoadOperator) + */ + if (li2.equals(li1)) + break; + + expr2 = liftOuterValue(li2, nr); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" lift2 "+li2 + +" in "+li2.getMethodAnalyzer() + +" to "+expr2); + + } else + return false; + } + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("unifyOuterValues succeeded."); + return true; + } + + /** + * Jikes gives the outer class reference in an unusual place (as last + * parameter) for anonymous classes that extends an inner (or method + * scope) class. This method tells if this is such a class. + */ + public boolean isJikesAnonymousInner() { + return jikesAnonymousInner; + } + + /** + * Javac 1.3 doesn't give an outer class reference for anonymous + * classes that extend inner classes, provided the outer class is + * the normal this parameter. Instead it takes a normal outer + * value parameter for this. This method tells if this is such a + * class. + */ + public boolean isImplicitOuterClass() { + return implicitOuterClass; + } + + public void addOuterValueListener(OuterValueListener l) { + if (ovListeners == null) + ovListeners = new Vector(); + ovListeners.addElement(l); + } + + /** + * Jikes gives the outer class reference in an unusual place (as last + * parameter) for anonymous classes that extends an inner (or method + * scope) class. This method tells if this is such a class. + */ + public void setJikesAnonymousInner(boolean value) { + jikesAnonymousInner = value; + } + + public void setImplicitOuterClass(boolean value) { + implicitOuterClass = value; + } + + private static int countSlots(Expression[] exprs, int length) { + int slots = 0; + for (int i=0; i < length; i++) + slots += exprs[i].getType().stackSize(); + return slots; + } + + public void setMinCount(int newMin) { + if (headCount < newMin) { + GlobalOptions.err.println + ("WARNING: something got wrong with scoped class " + +clazzAnalyzer.getClazz()+": " +newMin+","+headCount); + new Throwable().printStackTrace(GlobalOptions.err); + headMinCount = headCount; + } else if (newMin > headMinCount) + headMinCount = newMin; + } + + public void setCount(int newHeadCount) { + if (newHeadCount >= headCount) + return; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) { + GlobalOptions.err.println("setCount: "+this+","+newHeadCount); + new Throwable().printStackTrace(GlobalOptions.err); + } + + headCount = newHeadCount; + if (newHeadCount < headMinCount) { + GlobalOptions.err.println + ("WARNING: something got wrong with scoped class " + +clazzAnalyzer.getClazz()+": " + +headMinCount+","+headCount); + new Throwable().printStackTrace(GlobalOptions.err); + headMinCount = newHeadCount; + } + + if (ovListeners != null) { + for (Enumeration enumeration = ovListeners.elements(); + enumeration.hasMoreElements();) + ((OuterValueListener) enumeration.nextElement() + ).shrinkingOuterValues(this, newHeadCount); + } + } + + public String toString() { + StringBuffer sb = new StringBuffer() + .append(clazzAnalyzer.getClazz()) + .append(".OuterValues["); + String comma = ""; + int slot = 1; + for (int i=0; i < headCount; i++) { + if (i == headMinCount) + sb.append("<-"); + sb.append(comma).append(slot).append(":").append(head[i]); + slot += head[i].getType().stackSize(); + comma = ","; + } + if (jikesAnonymousInner) + sb.append("!jikesAnonymousInner"); + if (implicitOuterClass) + sb.append("!implicitOuterClass"); + return sb.append("]").toString(); + } +} diff --git a/jode/src/net/sf/jode/decompiler/ProgressListener.java b/jode/src/net/sf/jode/decompiler/ProgressListener.java new file mode 100644 index 0000000..188fdf9 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/ProgressListener.java @@ -0,0 +1,37 @@ +/* ProgressListener Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; + +/** + * This interface is used by jode to tell about its progress. You + * supply an instance of this interface to the + * {@link Decompiler#decompile} method.
+ * + * @author Jochen Hoenicke + * @version 1.0 */ +public interface ProgressListener { + /** + * Gets called when jode makes some progress. + * @param progress A number between 0.0 and 1.0 + * @param detail + * The name of the currently decompiled method or class. + */ + public void updateProgress(double progress, String detail); +} diff --git a/jode/src/net/sf/jode/decompiler/Scope.java b/jode/src/net/sf/jode/decompiler/Scope.java new file mode 100644 index 0000000..ed9ff58 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Scope.java @@ -0,0 +1,60 @@ +/* Scope Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; + +/** + * This interface describes a scope. The basic scopes are: the package + * scope, the class scope (one more for each inner class) and the method + * scope. + * + * @author Jochen Hoenicke + */ +public interface Scope { + public final int PACKAGENAME = 0; + public final int CLASSNAME = 1; + public final int METHODNAME = 2; + public final int FIELDNAME = 3; + public final int AMBIGUOUSNAME = 4; + public final int LOCALNAME = 5; + + public final int NOSUPERMETHODNAME = 12; + public final int NOSUPERFIELDNAME = 13; + + /** + * Tells that we want to allow a classanalyzer as scope. + */ + public final int CLASSSCOPE = 1; + /** + * Tells that we want to allow a methodanalyzer as scope. + */ + public final int METHODSCOPE = 2; + + /** + * Tells if this is the scope of the given object, which is of + * scopeType. + * @param object the object for which the scope + * @param usageType either CLASSCOPE or METHODSCOPE + * @return true if the given object is in this scope. + */ + public boolean isScopeOf(Object object, int scopeType); + public boolean conflicts(String name, int usageType); +} + + diff --git a/jode/src/net/sf/jode/decompiler/TabbedPrintWriter.java b/jode/src/net/sf/jode/decompiler/TabbedPrintWriter.java new file mode 100644 index 0000000..2be7843 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/TabbedPrintWriter.java @@ -0,0 +1,825 @@ +/* TabbedPrintWriter Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import java.io.*; +import java.util.Stack; +import java.util.Vector; +import java.util.Enumeration; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.type.*; + +public class TabbedPrintWriter { + /* The indentation size. */ + private int indentsize; + /* The size of a tab, 0 if we shouldn't use tabs at all. */ + private int tabWidth; + private int style; + private int lineWidth; + private int currentIndent = 0; + private String indentStr = ""; + private PrintWriter pw; + private ImportHandler imports; + private Stack scopes = new Stack(); + + public static final int BRACE_AT_EOL = 0x10; + public static final int INDENT_BRACES = 0x20; + public static final int GNU_SPACING = 0x40; + + /** + * This string contains a few tab characters followed by tabWidth - 1 + * spaces. It is used to quickly calculate the indentation string. + */ + private String tabSpaceString; + private StringBuffer currentLine; + private BreakPoint currentBP; + + public final static int EXPL_PAREN = 0; + public final static int NO_PAREN = 1; + public final static int IMPL_PAREN = 2; + public final static int DONT_BREAK = 3; + + /** + * The amount of tabs for which we can use the tabSpaceString. + */ + private final static int FASTINDENT = 20; + + /** + * Convert the numeric indentation to a string. + */ + protected String makeIndentStr(int indent) { + if (indent < 0) + return "NEGATIVEINDENT"+indent; + + int tabs = indent / tabWidth; + indent -= tabs * tabWidth; + if (tabs <= FASTINDENT) { + /* The fast way. */ + return tabSpaceString.substring(FASTINDENT - tabs, + FASTINDENT + indent); + } else { + /* the not so fast way */ + StringBuffer sb = new StringBuffer(tabs + indent); + while (tabs > FASTINDENT) { + sb.append(tabSpaceString.substring(0, FASTINDENT)); + tabs -= 20; + } + sb.append(tabSpaceString.substring(FASTINDENT - tabs, + FASTINDENT + indent)); + return sb.toString(); + } + } + + class BreakPoint { + int options; + int breakPenalty; + int breakPos; + int startPos; + BreakPoint parentBP; + Vector childBPs; + int nesting = 0; + int endPos; + int whatBreak = 0; + + public BreakPoint(BreakPoint parent, int position) { + this.breakPos = position; + this.parentBP = parent; + this.options = DONT_BREAK; + this.breakPenalty = 0; + this.startPos = -1; + this.endPos = -1; + this.whatBreak = 0; + this.childBPs = null; + } + + public void startOp(int opts, int penalty, int pos) { + if (startPos != -1) + throw new InternalError("missing breakOp"); + startPos = pos; + options = opts; + breakPenalty = penalty; + childBPs = new Vector(); + breakOp(pos); + } + + public void breakOp(int pos) { + childBPs.addElement (new BreakPoint(this, pos)); + } + + public void endOp(int pos) { + endPos = pos; + if (childBPs.size() == 1) { + /* There is no breakpoint in this op, replace this with + * our child, if possible. + */ + BreakPoint child = (BreakPoint) childBPs.elementAt(0); + options = Math.min(options, child.options); + startPos = child.startPos; + endPos = child.endPos; + breakPenalty = child.breakPenalty; + childBPs = child.childBPs; + } + } + + public void dump(String line) { + if (startPos == -1) { + pw.print(line); + } else { + pw.print(line.substring(0, startPos)); + dumpRegion(line); + pw.print(line.substring(endPos)); + } + } + + public void dumpRegion(String line) { + String parens = "{\010{}\010}<\010<>\010>[\010[]\010]`\010`'\010'" + .substring(options*6, options*6+6); + pw.print(parens.substring(0,3)); + Enumeration enumeration = childBPs.elements(); + int cur = startPos; + BreakPoint child = (BreakPoint) enumeration.nextElement(); + if (child.startPos >= 0) { + pw.print(line.substring(cur, child.startPos)); + child.dumpRegion(line); + cur = child.endPos; + } + while (enumeration.hasMoreElements()) { + child = (BreakPoint) enumeration.nextElement(); + pw.print(line.substring(cur, child.breakPos)); + pw.print("!\010!"+breakPenalty); + cur = child.breakPos; + if (child.startPos >= 0) { + pw.print(line.substring(child.breakPos, child.startPos)); + child.dumpRegion(line); + cur = child.endPos; + } + } + pw.print(line.substring(cur, endPos)); + pw.print(parens.substring(3)); + } + + public void printLines(int indent, String line) { + if (startPos == -1) { + pw.print(line); + } else { + pw.print(line.substring(0, startPos)); + printRegion(indent + startPos, line); + pw.print(line.substring(endPos)); + } + } + + public void printRegion(int indent, String line) { + if (options == IMPL_PAREN) { + pw.print("("); + indent++; + } + + Enumeration enumeration = childBPs.elements(); + int cur = startPos; + BreakPoint child = (BreakPoint) enumeration.nextElement(); + if (child.startPos >= 0) { + pw.print(line.substring(cur, child.startPos)); + child.printRegion(indent + child.startPos - cur, line); + cur = child.endPos; + } + if (options == NO_PAREN) + indent += indentsize; + String indentStr = makeIndentStr(indent); + while (enumeration.hasMoreElements()) { + child = (BreakPoint) enumeration.nextElement(); + pw.print(line.substring(cur, child.breakPos)); + pw.println(); + pw.print(indentStr); + cur = child.breakPos; + if (cur < endPos && line.charAt(cur) == ' ') + cur++; + if (child.startPos >= 0) { + pw.print(line.substring(cur, child.startPos)); + child.printRegion(indent + child.startPos - cur, line); + cur = child.endPos; + } + } + pw.print(line.substring(cur, endPos)); + if (options == IMPL_PAREN) + pw.print(")"); + } + + public BreakPoint commitMinPenalty(int space, int lastSpace, + int minPenalty) { + if (startPos == -1 || lastSpace > endPos - startPos + || minPenalty == 10 * (endPos - startPos - lastSpace)) { + /* We don't have to break anything */ + startPos = -1; + childBPs = null; + return this; + } + + int size = childBPs.size(); + if (size > 1 && options != DONT_BREAK) { + /* penalty if we are breaking the line here. */ + int breakPen + = getBreakPenalty(space, lastSpace, minPenalty + 1); + if (minPenalty == breakPen) { + commitBreakPenalty(space, lastSpace, breakPen); + return this; + } + } + + /* penalty if we are breaking only one child */ + for (int i=0; i < size; i++) { + BreakPoint child = (BreakPoint) childBPs.elementAt(i); + int front = child.startPos - startPos; + int tail = endPos - child.endPos; + int needPenalty = minPenalty - (i < size - 1 ? 1 : 0); + if (needPenalty == + child.getMinPenalty(space - front, + lastSpace - front - tail, + needPenalty + 1)) { + child = child.commitMinPenalty(space - front, + lastSpace - front - tail, + needPenalty); + child.breakPos = breakPos; + return child; + } + } + throw new IllegalStateException("Can't commit line break!"); + } + + public int getMinPenalty(int space, int lastSpace, int minPenalty) { + if (10 * -lastSpace >= minPenalty) { + return minPenalty; + } + + if (startPos == -1) + return 10 * -lastSpace; + + if (lastSpace > endPos - startPos) { + return 0; + } + + if (minPenalty <= 1) { + return minPenalty; + } + + if (minPenalty > 10 * (endPos - startPos - lastSpace)) + minPenalty = 10 * (endPos - startPos - lastSpace); + + int size = childBPs.size(); + if (size == 0) + return minPenalty; + + if (size > 1 && options != DONT_BREAK) { + /* penalty if we are breaking at this level. */ + minPenalty = getBreakPenalty(space, lastSpace, minPenalty); + } + + /* penalty if we are breaking only one child */ + for (int i=0; i < size; i++) { + BreakPoint child = (BreakPoint) childBPs.elementAt(i); + int front = child.startPos - startPos; + int tail = endPos - child.endPos; + int penalty = (i < size - 1 ? 1 : 0); + minPenalty = penalty + + child.getMinPenalty(space - front, + lastSpace - front - tail, + minPenalty - penalty); + } + return minPenalty; + } + + public void commitBreakPenalty(int space, int lastSpace, + int minPenalty) { + if (options == IMPL_PAREN) { + space--; + lastSpace -= 2; + } + + Enumeration enumeration = childBPs.elements(); + childBPs = new Vector(); + int currInd = 0; + BreakPoint lastChild, nextChild; + boolean indentNext = options == NO_PAREN; + for (lastChild = (BreakPoint) enumeration.nextElement(); + enumeration.hasMoreElements(); lastChild = nextChild) { + nextChild = (BreakPoint) enumeration.nextElement(); + int childStart = lastChild.breakPos; + int childEnd = nextChild.breakPos; + + if (currInd > 0) { + currInd += childEnd - childStart; + if (currInd <= space) + continue; + } + if (childStart < endPos + && currentLine.charAt(childStart) == ' ') + childStart++; + + if (childEnd - childStart > space) { + int front = lastChild.startPos - childStart; + int tail = childEnd - lastChild.endPos; + int childPenalty = lastChild.getMinPenalty + (space - front, space - front - tail, minPenalty); + currInd = 0; + childBPs.addElement + (lastChild.commitMinPenalty + (space - front, space - front - tail, childPenalty)); + } else { + lastChild.startPos = -1; + lastChild.childBPs = null; + childBPs.addElement(lastChild); + currInd = childEnd - childStart; + } + + if (indentNext) { + space -= indentsize; + lastSpace -= indentsize; + indentNext = false; + } + } + int childStart = lastChild.breakPos; + if (currInd > 0 && currInd + endPos - childStart <= lastSpace) + return; + + if (childStart < endPos + && currentLine.charAt(childStart) == ' ') + childStart++; + if (endPos - childStart > lastSpace) { + int front = lastChild.startPos - childStart; + int tail = endPos - lastChild.endPos; + int childPenalty = lastChild.getMinPenalty + (space - front, lastSpace - front - tail, minPenalty + 1); + childBPs.addElement + (lastChild.commitMinPenalty + (space - front, lastSpace - front - tail, childPenalty)); + } else { + lastChild.startPos = -1; + lastChild.childBPs = null; + childBPs.addElement(lastChild); + } + } + + public int getBreakPenalty(int space, int lastSpace, int minPenalty) { + int penalty = breakPenalty; + int currInd = 0; + if (options == IMPL_PAREN) { + space--; + lastSpace -= 2; + } + if (space < 0) + return minPenalty; + Enumeration enumeration = childBPs.elements(); + BreakPoint lastChild, nextChild; + boolean indentNext = options == NO_PAREN; + for (lastChild = (BreakPoint) enumeration.nextElement(); + enumeration.hasMoreElements(); lastChild = nextChild) { + nextChild = (BreakPoint) enumeration.nextElement(); + int childStart = lastChild.breakPos; + int childEnd = nextChild.breakPos; + + if (currInd > 0) { + currInd += childEnd - childStart; + if (currInd <= space) + continue; + + penalty++; + if (indentNext) { + space -= indentsize; + lastSpace -= indentsize; + indentNext = false; + } + } + + if (childStart < endPos + && currentLine.charAt(childStart) == ' ') + childStart++; + + if (childEnd - childStart > space) { + int front = lastChild.startPos - childStart; + int tail = childEnd - lastChild.endPos; + penalty += 1 + lastChild.getMinPenalty + (space - front, space - front - tail, + minPenalty - penalty - 1); + + if (indentNext) { + space -= indentsize; + lastSpace -= indentsize; + indentNext = false; + } + currInd = 0; + } else + currInd = childEnd - childStart; + + if (penalty >= minPenalty) + return minPenalty; + } + int childStart = lastChild.breakPos; + if (currInd > 0) { + if (currInd + endPos - childStart <= lastSpace) + return penalty; + + penalty++; + if (indentNext) { + space -= indentsize; + lastSpace -= indentsize; + indentNext = false; + } + } + if (childStart < endPos + && currentLine.charAt(childStart) == ' ') + childStart++; + if (endPos - childStart > lastSpace) { + int front = lastChild.startPos - childStart; + int tail = endPos - lastChild.endPos; + penalty += lastChild.getMinPenalty + (space - front, lastSpace - front - tail, + minPenalty - penalty); + } + if (penalty < minPenalty) + return penalty; + return minPenalty; + } + } + + public TabbedPrintWriter (OutputStream os, ImportHandler imports, + boolean autoFlush, int style, + int indentSize, int tabWidth, int lineWidth) { + pw = new PrintWriter(os, autoFlush); + this.imports = imports; + this.style = style; + this.indentsize = indentSize; + this.tabWidth = tabWidth; + this.lineWidth = lineWidth; + init(); + } + + public TabbedPrintWriter (Writer os, ImportHandler imports, + boolean autoFlush, int style, + int indentSize, int tabWidth, int lineWidth) { + pw = new PrintWriter(os, autoFlush); + this.imports = imports; + this.style = style; + this.indentsize = indentSize; + this.tabWidth = tabWidth; + this.lineWidth = lineWidth; + init(); + } + + public TabbedPrintWriter (OutputStream os, ImportHandler imports, + boolean autoFlush) { + this(os, imports, autoFlush, BRACE_AT_EOL, 4, 8, 79); + } + + public TabbedPrintWriter (Writer os, ImportHandler imports, + boolean autoFlush) { + this(os, imports, autoFlush, BRACE_AT_EOL, 4, 8, 79); + } + + public TabbedPrintWriter (OutputStream os, ImportHandler imports) { + this(os, imports, true, BRACE_AT_EOL, 4, 8, 79); + } + + public TabbedPrintWriter (Writer os, ImportHandler imports) { + this(os, imports, true, BRACE_AT_EOL, 4, 8, 79); + } + + public TabbedPrintWriter (OutputStream os) { + this(os, null, true, BRACE_AT_EOL, 4, 8, 79); + } + + public TabbedPrintWriter (Writer os) { + this(os, null, true, BRACE_AT_EOL, 4, 8, 79); + } + + private void init() { + currentLine = new StringBuffer(); + currentBP = new BreakPoint(null, 0); + currentBP.startOp(DONT_BREAK, 1, 0); + initTabString(); + } + + private void initTabString() { + char tabChar = '\t'; + if (tabWidth == 0) { + /* If tabWidth is 0 use spaces instead of tabs. */ + tabWidth = 1; + tabChar = ' '; + } + StringBuffer sb = new StringBuffer(FASTINDENT + tabWidth - 1); + for (int i = 0; i < FASTINDENT; i++) + sb.append(tabChar); + for (int i = 0; i < tabWidth - 1; i++) + sb.append(' '); + tabSpaceString = sb.toString(); + } + + public void tab() { + currentIndent += indentsize; + indentStr = makeIndentStr(currentIndent); + } + + public void untab() { + currentIndent -= indentsize; + indentStr = makeIndentStr(currentIndent); + } + + public void startOp(int options, int penalty) { + currentBP = (BreakPoint) currentBP.childBPs.lastElement(); + currentBP.startOp(options, penalty, currentLine.length()); + } + + public void breakOp() { + int pos = currentLine.length(); + if (pos > currentBP.startPos && currentLine.charAt(pos-1) == ' ') + pos--; + currentBP.breakOp(pos); + } + + public void endOp() { + currentBP.endOp(currentLine.length()); + currentBP = currentBP.parentBP; + if (currentBP == null) + throw new NullPointerException(); + } + + public Object saveOps() { + Stack state = new Stack(); + int pos = currentLine.length(); + while (currentBP.parentBP != null) { + state.push(new Integer(currentBP.breakPenalty)); + /* We don't want parentheses or unconventional line breaking */ + currentBP.options = DONT_BREAK; + currentBP.endPos = pos; + currentBP = currentBP.parentBP; + } + return state; + } + + public void restoreOps(Object s) { + Stack state = (Stack) s; + while (!state.isEmpty()) { + int penalty = ((Integer) state.pop()).intValue(); + startOp(DONT_BREAK, penalty); + } + } + + public void println(String str) { + print(str); + println(); + } + + public void flushLine() { + currentBP.endPos = currentLine.length(); + +// pw.print(indentStr); +// currentBP.dump(currentLine.toString()); +// pw.println(); + + int lw = lineWidth - currentIndent; + int minPenalty = currentBP.getMinPenalty(lw, lw, Integer.MAX_VALUE/2); + currentBP = currentBP.commitMinPenalty(lw, lw, minPenalty); + +// pw.print(indentStr); +// currentBP.dump(currentLine.toString()); +// pw.println(); + pw.print(indentStr); + currentBP.printLines(currentIndent, currentLine.toString()); + + currentLine.setLength(0); + currentBP = new BreakPoint(null, 0); + currentBP.startOp(DONT_BREAK, 1, 0); + } + + public void println() { + flushLine(); + pw.println(); + } + + public void print(String str) { + currentLine.append(str); + } + + public void printType(Type type) { + print(getTypeString(type)); + } + + public void pushScope(Scope scope) { + scopes.push(scope); + } + + public void popScope() { + scopes.pop(); + } + + /** + * Checks if the name in inScope conflicts with an identifier in a + * higher scope. + */ + public boolean conflicts(String name, Scope inScope, int context) { + int dot = name.indexOf('.'); + if (dot >= 0) + name = name.substring(0, dot); + int count = scopes.size(); + for (int ptr = count; ptr-- > 0; ) { + Scope scope = (Scope) scopes.elementAt(ptr); + if (scope == inScope) + return false; + if (scope.conflicts(name, context)) { + return true; + } + } + return false; + } + + public Scope getScope(Object obj, int scopeType) { + int count = scopes.size(); + for (int ptr = count; ptr-- > 0; ) { + Scope scope = (Scope) scopes.elementAt(ptr); + if (scope.isScopeOf(obj, scopeType)) + return scope; + } + return null; + } + + public String getClassString(ClassInfo clazz, int scopeType) { + try { + clazz.load(ClassInfo.OUTERCLASS); + } catch (IOException ex) { + clazz.guess(ClassInfo.OUTERCLASS); + } + if ((Options.options & Options.OPTION_INNER) != 0 + && clazz.getOuterClass() != null) { + + String className = clazz.getClassName(); + Scope scope = getScope(clazz.getOuterClass(), Scope.CLASSSCOPE); + if (scope != null && + !conflicts(className, scope, scopeType)) + return className; + + return getClassString(clazz.getOuterClass(), scopeType) + + "." + className; + } + + if ((Options.options & Options.OPTION_ANON) != 0 + && clazz.isMethodScoped()) { + + String className = clazz.getClassName(); + if (className == null) + return "ANONYMOUS CLASS "+clazz.getName(); + + Scope scope = getScope(clazz, Scope.METHODSCOPE); + if (scope != null && + !conflicts(className, scope, scopeType)) + return className; + + if (scope != null) + return "NAME CONFLICT " + className; + else + return "UNREACHABLE " + className; + } + if (imports != null) { + String importedName = imports.getClassString(clazz); + if (!conflicts(importedName, null, scopeType)) + return importedName; + } + String name = clazz.getName(); + if (conflicts(name, null, Scope.AMBIGUOUSNAME)) + return "PKGNAMECONFLICT "+ name; + return name; + } + + public String getTypeString(Type type) { + if (type instanceof ArrayType) + return getTypeString(((ArrayType) type).getElementType()) + "[]"; + else if (type instanceof ClassInfoType) { + ClassInfo clazz = ((ClassInfoType) type).getClassInfo(); + return getClassString(clazz, Scope.CLASSNAME); + } else if (type instanceof ClassType) { + String name = ((ClassType) type).getClassName(); + if (imports != null) { + String importedName = imports.getClassString(name); + if (!conflicts(importedName, null, Scope.CLASSNAME)) + return importedName; + } + if (conflicts(name, null, Scope.AMBIGUOUSNAME)) + return "PKGNAMECONFLICT "+ name; + return name; + } else if (type instanceof NullType) + return "Object"; + else + return type.toString(); + } + + public void printOptionalSpace() { + if ((style & GNU_SPACING) != 0) + print(" "); + } + + /** + * Print a opening brace with the current indentation style. + * Called at the end of the line of the instance that opens the + * brace. It doesn't do a tab stop after opening the brace. + */ + public void openBrace() { + boolean bracePrinted = false; + if (currentLine.length() > 0) { + if ((style & BRACE_AT_EOL) != 0) { + print(" {"); + bracePrinted = true; + } + println(); + } + if ((style & INDENT_BRACES) != 0 && currentIndent > 0) + tab(); + + if (!bracePrinted) + println("{"); + } + + public void openBraceClass() { + openBraceNoIndent(); + } + + /** + * Print a opening brace with the current indentation style. + * Called at the end the line of a method declaration. + */ + public void openBraceNoIndent() { + if (currentLine.length() > 0) { + if ((style & BRACE_AT_EOL) != 0) + print(" "); + else + println(); + } + println("{"); + } + + /** + * Print an opening brace with the current indentation style. + * Called at the end of the line of the instance that opens the + * brace. It doesn't do a tab stop after opening the brace. + */ + public void openBraceNoSpace() { + boolean bracePrinted = false; + if (currentLine.length() > 0) { + if ((style & BRACE_AT_EOL) != 0) { + print("{"); + bracePrinted = true; + } + println(); + } + if ((style & INDENT_BRACES) != 0 && currentIndent > 0) + tab(); + if (!bracePrinted) + println("{"); + } + + public void closeBraceContinue() { + if ((style & BRACE_AT_EOL) != 0) + print("} "); + else + println("}"); + if ((style & INDENT_BRACES) != 0 && currentIndent > 0) + untab(); + } + + public void closeBraceClass() { + print("}"); + } + + public void closeBrace() { + println("}"); + if ((style & INDENT_BRACES) != 0 && currentIndent > 0) + untab(); + } + + public void closeBraceNoIndent() { + println("}"); + } + + public void flush() { + flushLine(); + pw.flush(); + } + + public void close() { + flushLine(); + pw.close(); + } +} diff --git a/jode/src/net/sf/jode/decompiler/Window.java b/jode/src/net/sf/jode/decompiler/Window.java new file mode 100644 index 0000000..0cd1037 --- /dev/null +++ b/jode/src/net/sf/jode/decompiler/Window.java @@ -0,0 +1,307 @@ +/* Window Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.decompiler; +import java.applet.*; +import java.awt.*; +///#ifndef AWT10 +import java.awt.event.*; +///#endif +import java.io.*; +import net.sf.jode.GlobalOptions; + +public class Window + implements Runnable +///#ifndef AWT10 + , ActionListener +///#endif + +{ + TextField classpathField, classField; + TextArea sourcecodeArea, errorArea; + Checkbox verboseCheck, prettyCheck; + Button startButton, saveButton; + String lastClassName, lastClassPath; + Frame frame; + + PrintWriter errStream; + Decompiler decompiler = new Decompiler(); + Thread decompileThread; + + public Window(Container window) { + buildComponents(window); + } + + private void buildComponents(Container window) { + if (window instanceof Frame) + frame = (Frame) window; + + window.setFont(new Font("dialog", Font.PLAIN, 10)); + + classpathField = new TextField(50); + classField = new TextField(50); + sourcecodeArea = new TextArea(20, 80); + errorArea = new TextArea(3, 80); + verboseCheck = new Checkbox("verbose", true); + prettyCheck = new Checkbox("pretty", true); + startButton = new Button("start"); + saveButton = new Button("save"); +///#ifdef AWT10 +/// saveButton.disable(); +///#else + saveButton.setEnabled(false); +///#endif + + sourcecodeArea.setEditable(false); + errorArea.setEditable(false); + Font monospaced = new Font("monospaced", Font.PLAIN, 10); + sourcecodeArea.setFont(monospaced); + errorArea.setFont(monospaced); + + GridBagLayout gbl = new GridBagLayout(); + window.setLayout(gbl); + GridBagConstraints labelConstr = new GridBagConstraints(); + GridBagConstraints textConstr = new GridBagConstraints(); + GridBagConstraints areaConstr = new GridBagConstraints(); + GridBagConstraints checkConstr = new GridBagConstraints(); + GridBagConstraints buttonConstr = new GridBagConstraints(); + labelConstr.fill = GridBagConstraints.NONE; + textConstr.fill = GridBagConstraints.HORIZONTAL; + areaConstr.fill = GridBagConstraints.BOTH; + checkConstr.fill = GridBagConstraints.NONE; + buttonConstr.fill = GridBagConstraints.NONE; + labelConstr.anchor = GridBagConstraints.EAST; + textConstr.anchor = GridBagConstraints.CENTER; + checkConstr.anchor = GridBagConstraints.WEST; + buttonConstr.anchor = GridBagConstraints.CENTER; + labelConstr.anchor = GridBagConstraints.EAST; + textConstr.gridwidth = GridBagConstraints.REMAINDER; + textConstr.weightx = 1.0; + areaConstr.gridwidth = GridBagConstraints.REMAINDER; + areaConstr.weightx = 1.0; + areaConstr.weighty = 1.0; + +///#ifdef AWT10 +/// Label label = new Label("class path: "); +/// gbl.setConstraints(label, labelConstr); +/// window.add(label); +/// gbl.setConstraints(classpathField, textConstr); +/// window.add(classpathField); +/// label = new Label("class name: "); +/// gbl.setConstraints(label, labelConstr); +/// window.add(label); +/// gbl.setConstraints(classField, textConstr); +/// window.add(classField); +/// gbl.setConstraints(verboseCheck, checkConstr); +/// window.add(verboseCheck); +/// gbl.setConstraints(prettyCheck, checkConstr); +/// window.add(prettyCheck); +/// labelConstr.weightx = 1.0; +/// label = new Label(); +/// gbl.setConstraints(label, labelConstr); +/// window.add(label); +/// gbl.setConstraints(startButton, buttonConstr); +/// window.add(startButton); +/// buttonConstr.gridwidth = GridBagConstraints.REMAINDER; +/// gbl.setConstraints(saveButton, buttonConstr); +/// window.add(saveButton); +/// gbl.setConstraints(sourcecodeArea, areaConstr); +/// window.add(sourcecodeArea); +/// areaConstr.gridheight = GridBagConstraints.REMAINDER; +/// areaConstr.weighty = 0.0; +/// gbl.setConstraints(errorArea, areaConstr); +/// window.add(errorArea); +///#else + window.add(new Label("class path: "), labelConstr); + window.add(classpathField, textConstr); + window.add(new Label("class name: "), labelConstr); + window.add(classField, textConstr); + window.add(verboseCheck, checkConstr); + window.add(prettyCheck, checkConstr); + labelConstr.weightx = 1.0; + window.add(new Label(), labelConstr); + window.add(startButton, buttonConstr); + buttonConstr.gridwidth = GridBagConstraints.REMAINDER; + window.add(saveButton, buttonConstr); + window.add(sourcecodeArea, areaConstr); + areaConstr.gridheight = GridBagConstraints.REMAINDER; + areaConstr.weighty = 0.0; + window.add(errorArea, areaConstr); + + startButton.addActionListener(this); + saveButton.addActionListener(this); +///#endif + errStream = new PrintWriter(new AreaWriter(errorArea)); + decompiler.setErr(errStream); + } + + public void setClassPath(String cp) { + classpathField.setText(cp); + } + public void setClass(String cls) { + classField.setText(cls); + } + +///#ifdef AWT10 +/// public synchronized void action(Event e, Object target) { +///#else + public synchronized void actionPerformed(ActionEvent e) { + Object target = e.getSource(); +///#endif + if (target == startButton) { + +///#ifdef AWT10 +/// startButton.disable(); +///#else + startButton.setEnabled(false); +///#endif + decompileThread = new Thread(this); + sourcecodeArea.setText("Please wait, while decompiling...\n"); + decompileThread.start(); + } else if (target == saveButton) { + if (frame == null) + frame = new Frame(); //XXX + FileDialog fd = new FileDialog(frame, + "Save decompiled code", + FileDialog.SAVE); + fd.setFile(lastClassName.substring + (lastClassName.lastIndexOf('.')+1).concat(".java")); + fd.show(); + String fileName = fd.getFile(); + if (fileName == null) + return; + try { + File f = new File(new File(fd.getDirectory()), fileName); + FileWriter out = new FileWriter(f); + out.write(sourcecodeArea.getText()); + out.close(); + } catch (IOException ex) { + errorArea.setText(""); + errStream.println("Couldn't write to file " + + fileName + ": "); + ex.printStackTrace(errStream); + } catch (SecurityException ex) { + errorArea.setText(""); + errStream.println("Couldn't write to file " + + fileName + ": "); + ex.printStackTrace(errStream); + } + } + } + + public class AreaWriter extends Writer { + boolean initialized = false; + private TextArea area; + + public AreaWriter(TextArea a) { + area = a; + } + + public void write(char[] b, int off, int len) throws IOException { + if (!initialized) { + area.setText(""); + initialized = true; + } +///#ifdef AWT10 +/// area.appendText(new String(b, off, len)); +///#else + area.append(new String(b, off, len)); +///#endif + } + + public void flush() { + } + + public void close() { + } + } + + public void run() { + decompiler.setOption("verbose", verboseCheck.getState() ? "1" : "0"); + decompiler.setOption("pretty", prettyCheck.getState() ? "1" : "0"); + errorArea.setText(""); +///#ifdef AWT10 +/// saveButton.disable(); +///#else + saveButton.setEnabled(false); +///#endif + + lastClassName = classField.getText(); + String newClassPath = classpathField.getText(); + if (!newClassPath.equals(lastClassPath)) { + decompiler.setClassPath(newClassPath); + lastClassPath = newClassPath; + } + + try { + Writer writer + = new BufferedWriter(new AreaWriter(sourcecodeArea), 512); + try { + decompiler.decompile(lastClassName, writer, null); + } catch (IllegalArgumentException ex) { + sourcecodeArea.setText + ("`"+lastClassName+"' is not a class name.\n" + +"You have to give a full qualified classname " + +"with '.' as package delimiter \n" + +"and without .class ending."); + return; + } +///#ifdef AWT10 +/// saveButton.enable(); +///#else + saveButton.setEnabled(true); +///#endif + } catch (Throwable t) { + sourcecodeArea.setText("Didn't succeed.\n" + +"Check the below area for more info."); + t.printStackTrace(); + } finally { + synchronized(this) { + decompileThread = null; +///#ifdef AWT10 +/// startButton.enable(); +///#else + startButton.setEnabled(true); +///#endif + } + } + } + + public static void main(String argv[]) { + Frame frame = new Frame(GlobalOptions.copyright); + Window win = new Window(frame); + + String cp = System.getProperty("java.class.path"); + if (cp != null) + win.setClassPath(cp.replace(File.pathSeparatorChar, + Decompiler.altPathSeparatorChar)); + String cls = win.getClass().getName(); + win.setClass(cls); + +///#ifndef AWT10 + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); +///#endif + frame.pack(); + frame.show(); + } +} diff --git a/jode/src/net/sf/jode/expr/ArrayLengthOperator.java b/jode/src/net/sf/jode/expr/ArrayLengthOperator.java new file mode 100644 index 0000000..005e199 --- /dev/null +++ b/jode/src/net/sf/jode/expr/ArrayLengthOperator.java @@ -0,0 +1,47 @@ +/* ArrayLengthOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ArrayLengthOperator extends Operator { + + public ArrayLengthOperator() { + super(Type.tInt, 0); + initOperands(1); + } + + public int getPriority() { + return 950; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tArray(Type.tUnknown)); + } + + public void updateType() { + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, 900); + writer.print(".length"); + } +} diff --git a/jode/src/net/sf/jode/expr/ArrayLoadOperator.java b/jode/src/net/sf/jode/expr/ArrayLoadOperator.java new file mode 100644 index 0000000..210d1b2 --- /dev/null +++ b/jode/src/net/sf/jode/expr/ArrayLoadOperator.java @@ -0,0 +1,58 @@ +/* ArrayLoadOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.type.ArrayType; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ArrayLoadOperator extends Operator { + + public ArrayLoadOperator(Type type) { + super(type, 0); + initOperands(2); + } + + public int getPriority() { + return 950; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tSubType(Type.tArray(type))); + subExpressions[1].setType(Type.tSubType(Type.tInt)); + } + + public void updateType() { + Type subType = Type.tSuperType(subExpressions[0].getType()) + .intersection(Type.tArray(type)); + if (!(subType instanceof ArrayType)) + updateParentType(Type.tError); + else + updateParentType(((ArrayType)subType).getElementType()); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, 950); + writer.breakOp(); + writer.print("["); + subExpressions[1].dumpExpression(writer, 0); + writer.print("]"); + } +} diff --git a/jode/src/net/sf/jode/expr/ArrayStoreOperator.java b/jode/src/net/sf/jode/expr/ArrayStoreOperator.java new file mode 100644 index 0000000..bd05733 --- /dev/null +++ b/jode/src/net/sf/jode/expr/ArrayStoreOperator.java @@ -0,0 +1,60 @@ +/* ArrayStoreOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.type.ArrayType; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ArrayStoreOperator extends ArrayLoadOperator + implements LValueExpression { + + public ArrayStoreOperator(Type type) { + super(type); + } + + public boolean matches(Operator loadop) { + return loadop instanceof ArrayLoadOperator; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + Type arrType = subExpressions[0].getType().getHint(); + if (arrType instanceof ArrayType) { + Type elemType = ((ArrayType) arrType).getElementType(); + if (!elemType.isOfType(getType())) { + /* We need an explicit widening cast */ + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 1); + writer.print("("); + writer.printType(Type.tArray(getType().getHint())); + writer.print(") "); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.print(")"); + writer.breakOp(); + writer.print("["); + subExpressions[1].dumpExpression(writer, 0); + writer.print("]"); + return; + } + } + super.dumpExpression(writer); + } +} diff --git a/jode/src/net/sf/jode/expr/BinaryOperator.java b/jode/src/net/sf/jode/expr/BinaryOperator.java new file mode 100644 index 0000000..411b1f0 --- /dev/null +++ b/jode/src/net/sf/jode/expr/BinaryOperator.java @@ -0,0 +1,94 @@ +/* BinaryOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class BinaryOperator extends Operator { + + public BinaryOperator(Type type, int op) { + super(type, op); + initOperands(2); + } + + public int getPriority() { + switch (operatorIndex) { + case 1: case 2: + return 610; + case 3: case 4: case 5: + return 650; + case 6: case 7: case 8: + return 600; + case 9: + return 450; + case 10: + return 410; + case 11: + return 420; + case 12: case 13: case 14: case 15: case 16: case 17: + case 18: case 19: case 20: case 21: case 22: case 23: + return 100; + case LOG_OR_OP: + return 310; + case LOG_AND_OP: + return 350; + } + throw new RuntimeException("Illegal operator"); + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tSubType(type)); + subExpressions[1].setType(Type.tSubType(type)); + } + + public void updateType() { + Type leftType = Type.tSuperType(subExpressions[0].getType()); + Type rightType = Type.tSuperType(subExpressions[1].getType()); + subExpressions[0].setType(Type.tSubType(rightType)); + subExpressions[1].setType(Type.tSubType(leftType)); + updateParentType(leftType.intersection(rightType)); + } + + public Expression negate() { + if (getOperatorIndex() == LOG_AND_OP || + getOperatorIndex() == LOG_OR_OP) { + setOperatorIndex(getOperatorIndex() ^ 1); + for (int i=0; i< 2; i++) { + subExpressions[i] = subExpressions[i].negate(); + subExpressions[i].parent = this; + } + return this; + } + return super.negate(); + } + + public boolean opEquals(Operator o) { + return (o instanceof BinaryOperator) && + o.operatorIndex == operatorIndex; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, getPriority()); + writer.breakOp(); + writer.print(getOperatorString()); + subExpressions[1].dumpExpression(writer, getPriority()+1); + } +} diff --git a/jode/src/net/sf/jode/expr/CheckCastOperator.java b/jode/src/net/sf/jode/expr/CheckCastOperator.java new file mode 100644 index 0000000..9a59c9e --- /dev/null +++ b/jode/src/net/sf/jode/expr/CheckCastOperator.java @@ -0,0 +1,74 @@ +/* CheckCastOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class CheckCastOperator extends Operator { + Type castType; + + public CheckCastOperator(Type type) { + super(type, 0); + castType = type; + initOperands(1); + } + + public int getPriority() { + return 700; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tUObject); + } + + public void updateType() { + } + + public Expression simplify() { + if (subExpressions[0].getType().getCanonic() + .isOfType(Type.tSubType(castType))) + /* This is an unnecessary widening cast, probably that inserted + * by jikes for inner classes constructors. + */ + return subExpressions[0].simplify(); + return super.simplify(); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("("); + writer.printType(castType); + writer.print(") "); + writer.breakOp(); + + /* There are special cases where a cast isn't allowed. We must cast + * to the common super type before. This cases always give a runtime + * error, but we want to decompile even bad programs. + */ + Type superType = castType.getCastHelper(subExpressions[0].getType()); + if (superType != null) { + writer.print("("); + writer.printType(superType); + writer.print(") "); + writer.breakOp(); + } + subExpressions[0].dumpExpression(writer, 700); + } +} diff --git a/jode/src/net/sf/jode/expr/CheckNullOperator.java b/jode/src/net/sf/jode/expr/CheckNullOperator.java new file mode 100644 index 0000000..14f97ac --- /dev/null +++ b/jode/src/net/sf/jode/expr/CheckNullOperator.java @@ -0,0 +1,96 @@ +/* CheckNullOperator Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; + +///#def COLLECTIONS java.util +import java.util.Collection; +///#enddef + +/** + * This is a pseudo operator, which represents the check against null + * that jikes and javac generates for inner classes: + * + *
+ *   outer.new Inner()
+ * 
+ * is translated by javac to + *
+ *   new Outer$Inner(outer ((void) DUP.getClass()));
+ * 
+ * and by jikes to + *
+ *   new Outer$Inner(outer (DUP == null ? throw null));
+ * 
+ */ + +public class CheckNullOperator extends Operator { + LocalInfo local; + + public CheckNullOperator(Type type, LocalInfo li) { + super(type, 0); + local = li; + initOperands(1); + } + + public int getPriority() { + return 200; + } + + public void updateSubTypes() { + local.setType(type); + subExpressions[0].setType(Type.tSubType(type)); + } + + public void updateType() { + Type newType = Type.tSuperType(subExpressions[0].getType()) + .intersection(type); + local.setType(newType); + updateParentType(newType); + } + + public void removeLocal() { + local.remove(); + } + + public void fillInGenSet(Collection in, Collection gen) { + if (gen != null) + gen.add(local); + super.fillInGenSet(in, gen); + } + + public void fillDeclarables(Collection used) { + used.add(local); + super.fillDeclarables(used); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("("+local.getName()+" = "); + subExpressions[0].dumpExpression(writer, 0); + writer.print(").getClass() != null ? "+local.getName()+" : null"); + } + + public boolean opEquals(Operator o) { + return o instanceof CheckNullOperator; + } +} diff --git a/jode/src/net/sf/jode/expr/ClassFieldOperator.java b/jode/src/net/sf/jode/expr/ClassFieldOperator.java new file mode 100644 index 0000000..23fbfbc --- /dev/null +++ b/jode/src/net/sf/jode/expr/ClassFieldOperator.java @@ -0,0 +1,42 @@ +/* ClassFieldOperator Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ClassFieldOperator extends NoArgOperator { + Type classType; + + public ClassFieldOperator(Type javaLangClass, Type classType) { + super(javaLangClass); + this.classType = classType; + } + + public int getPriority() { + return 950; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.printType(classType); + writer.print(".class"); + } +} diff --git a/jode/src/net/sf/jode/expr/CombineableOperator.java b/jode/src/net/sf/jode/expr/CombineableOperator.java new file mode 100644 index 0000000..fa2e9a3 --- /dev/null +++ b/jode/src/net/sf/jode/expr/CombineableOperator.java @@ -0,0 +1,36 @@ +/* CombineableOperator Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; + +public interface CombineableOperator { + /** + * Returns the LValue. + */ + public LValueExpression getLValue(); + /** + * Checks if the loadOp is combinable with the lvalue. + */ + public boolean lvalueMatches(Operator loadOp); + /** + * Make this operator return a value compatible with the loadOp + * that it should replace. + */ + public void makeNonVoid(); +} diff --git a/jode/src/net/sf/jode/expr/CompareBinaryOperator.java b/jode/src/net/sf/jode/expr/CompareBinaryOperator.java new file mode 100644 index 0000000..4363fde --- /dev/null +++ b/jode/src/net/sf/jode/expr/CompareBinaryOperator.java @@ -0,0 +1,94 @@ +/* CompareBinaryOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class CompareBinaryOperator extends Operator { + boolean allowsNaN = false; + Type compareType; + + public CompareBinaryOperator(Type type, int op) { + super(Type.tBoolean, op); + compareType = type; + initOperands(2); + } + + public CompareBinaryOperator(Type type, int op, boolean allowsNaN) { + super(Type.tBoolean, op); + compareType = type; + this.allowsNaN = allowsNaN; + initOperands(2); + } + + public int getPriority() { + switch (getOperatorIndex()) { + case 26: + case 27: + return 500; + case 28: + case 29: + case 30: + case 31: + return 550; + } + throw new RuntimeException("Illegal operator"); + } + + public Type getCompareType() { + return compareType; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tSubType(compareType)); + subExpressions[1].setType(Type.tSubType(compareType)); + } + + public void updateType() { + Type leftType = Type.tSuperType(subExpressions[0].getType()); + Type rightType = Type.tSuperType(subExpressions[1].getType()); + compareType = compareType + .intersection(leftType).intersection(rightType); + subExpressions[0].setType(Type.tSubType(rightType)); + subExpressions[1].setType(Type.tSubType(leftType)); + /* propagate hints? XXX */ + } + + public Expression negate() { + if (!allowsNaN || getOperatorIndex() <= NOTEQUALS_OP) { + setOperatorIndex(getOperatorIndex() ^ 1); + return this; + } + return super.negate(); + } + + public boolean opEquals(Operator o) { + return (o instanceof CompareBinaryOperator) + && o.operatorIndex == operatorIndex; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, getPriority()+1); + writer.breakOp(); + writer.print(getOperatorString()); + subExpressions[1].dumpExpression(writer, getPriority()+1); + } +} diff --git a/jode/src/net/sf/jode/expr/CompareToIntOperator.java b/jode/src/net/sf/jode/expr/CompareToIntOperator.java new file mode 100644 index 0000000..b42c92a --- /dev/null +++ b/jode/src/net/sf/jode/expr/CompareToIntOperator.java @@ -0,0 +1,64 @@ +/* CompareToIntOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class CompareToIntOperator extends Operator { + boolean allowsNaN; + boolean greaterOnNaN; + Type compareType; + + public CompareToIntOperator(Type type, boolean greaterOnNaN) { + super(Type.tInt, 0); + compareType = type; + this.allowsNaN = (type == Type.tFloat || type == Type.tDouble); + this.greaterOnNaN = greaterOnNaN; + initOperands(2); + } + + public int getPriority() { + return 499; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tSubType(compareType)); + subExpressions[1].setType(Type.tSubType(compareType)); + } + + public void updateType() { + } + + public boolean opEquals(Operator o) { + return (o instanceof CompareToIntOperator); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException + { + subExpressions[0].dumpExpression(writer, 550); + writer.breakOp(); + writer.print(" <=>"); + if (allowsNaN) + writer.print(greaterOnNaN ? "g" : "l"); + writer.print(" "); + subExpressions[1].dumpExpression(writer, 551); + } +} diff --git a/jode/src/net/sf/jode/expr/CompareUnaryOperator.java b/jode/src/net/sf/jode/expr/CompareUnaryOperator.java new file mode 100644 index 0000000..5524f05 --- /dev/null +++ b/jode/src/net/sf/jode/expr/CompareUnaryOperator.java @@ -0,0 +1,116 @@ +/* CompareUnaryOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class CompareUnaryOperator extends Operator { + boolean objectType; + Type compareType; + + public CompareUnaryOperator(Type type, int op) { + super(Type.tBoolean, op); + compareType = type; + objectType = (type.isOfType(Type.tUObject)); + initOperands(1); + } + + public int getPriority() { + switch (getOperatorIndex()) { + case 26: + case 27: + return 500; + case 28: + case 29: + case 30: + case 31: + return 550; + } + throw new RuntimeException("Illegal operator"); + } + + public Type getCompareType() { + return compareType; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tSubType(compareType)); + } + + public void updateType() { + } + + public Expression simplify() { + if (subExpressions[0] instanceof CompareToIntOperator) { + + CompareToIntOperator cmpOp + = (CompareToIntOperator) subExpressions[0]; + + boolean negated = false; + int opIndex = getOperatorIndex(); + if (cmpOp.allowsNaN && getOperatorIndex() > NOTEQUALS_OP) { + if (cmpOp.greaterOnNaN == + (opIndex == GREATEREQ_OP || opIndex == GREATER_OP)) { + negated = true; + opIndex ^= 1; + } + } + Expression newOp = new CompareBinaryOperator + (cmpOp.compareType, opIndex, cmpOp.allowsNaN) + .addOperand(cmpOp.subExpressions[1]) + .addOperand(cmpOp.subExpressions[0]); + + if (negated) + return newOp.negate().simplify(); + return newOp.simplify(); + } + if (subExpressions[0].getType().isOfType(Type.tBoolean)) { + /* xx == false */ + if (getOperatorIndex() == EQUALS_OP) + return subExpressions[0].negate().simplify(); + /* xx != false */ + if (getOperatorIndex() == NOTEQUALS_OP) + return subExpressions[0].simplify(); + } + return super.simplify(); + } + + public Expression negate() { + if ((getType() != Type.tFloat && getType() != Type.tDouble) + || getOperatorIndex() <= NOTEQUALS_OP) { + setOperatorIndex(getOperatorIndex() ^ 1); + return this; + } + return super.negate(); + } + + public boolean opEquals(Operator o) { + return (o instanceof CompareUnaryOperator) + && o.getOperatorIndex() == getOperatorIndex(); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, getPriority()+1); + writer.breakOp(); + writer.print(getOperatorString()); + writer.print(objectType?"null":"0"); + } +} diff --git a/jode/src/net/sf/jode/expr/ConstOperator.java b/jode/src/net/sf/jode/expr/ConstOperator.java new file mode 100644 index 0000000..c8078d7 --- /dev/null +++ b/jode/src/net/sf/jode/expr/ConstOperator.java @@ -0,0 +1,204 @@ +/* ConstOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.type.Type; +import net.sf.jode.type.IntegerType; +import net.sf.jode.util.StringQuoter; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ConstOperator extends NoArgOperator { + Object value; + boolean isInitializer = false; + + private static final Type tBoolConstInt + = new IntegerType(IntegerType.IT_I | IntegerType.IT_C + | IntegerType.IT_Z + | IntegerType.IT_S | IntegerType.IT_B); + + public ConstOperator(ClassPath cp, String constant) { + super(Type.tClass(cp, "java.lang.String")); + value = constant; + } + + public ConstOperator(Object constant) { + super(Type.tUnknown); + if (constant instanceof Boolean) { + updateParentType(Type.tBoolean); + constant = new Integer(((Boolean)constant).booleanValue() ? 1 : 0); + } else if (constant instanceof Integer) { + int intVal = ((Integer) constant).intValue(); + updateParentType + ((intVal == 0 || intVal == 1) ? tBoolConstInt + : (intVal < Short.MIN_VALUE + || intVal > Character.MAX_VALUE) ? Type.tInt + : new IntegerType + ((intVal < Byte.MIN_VALUE) + ? IntegerType.IT_S|IntegerType.IT_I + : (intVal < 0) + ? IntegerType.IT_S|IntegerType.IT_B|IntegerType.IT_I + : (intVal <= Byte.MAX_VALUE) + ? (IntegerType.IT_S|IntegerType.IT_B + |IntegerType.IT_C|IntegerType.IT_I) + : (intVal <= Short.MAX_VALUE) + ? IntegerType.IT_S|IntegerType.IT_C|IntegerType.IT_I + : IntegerType.IT_C|IntegerType.IT_I)); + } else if (constant instanceof Long) + updateParentType(Type.tLong); + else if (constant instanceof Float) + updateParentType(Type.tFloat); + else if (constant instanceof Double) + updateParentType(Type.tDouble); + else if (constant == null) + updateParentType(Type.tUObject); + else + throw new IllegalArgumentException("Illegal constant type: " + +constant.getClass()); + value = constant; + } + + public Object getValue() { + return value; + } + + /** + * Return true, if this value is a one of the given type. + * This is used for ++ and -- instructions. + * @param type the type for which this must be a one. This may + * be different from the type this value actually is. + */ + public boolean isOne(Type type) { + if (type instanceof IntegerType) { + return (value instanceof Integer + && ((Integer)value).intValue() == 1); + } else if (type == Type.tLong) { + return (value instanceof Long + && ((Long)value).longValue() == 1L); + } else if (type == Type.tFloat) { + return (value instanceof Float + && ((Float)value).floatValue() == 1.0f); + } else if (type == Type.tDouble) { + return (value instanceof Double + && ((Double)value).doubleValue() == 1.0); + } + return false; + } + + public int getPriority() { + return 1000; + } + + public boolean opEquals(Operator o) { + if (o instanceof ConstOperator) { + Object otherValue = ((ConstOperator)o).value; + return value == null + ? otherValue == null : value.equals(otherValue); + } + return false; + } + + public void makeInitializer(Type type) { + isInitializer = true; + } + + public String toString() { + String strVal = String.valueOf(value); + if (type.isOfType(Type.tBoolean)) { + int intVal = ((Integer)value).intValue(); + if (intVal == 0) + return "false"; + else if (intVal == 1) + return "true"; + else + throw new InternalError + ("boolean is neither false nor true"); + } + if (type.getHint().equals(Type.tChar)) { + char c = (char) ((Integer) value).intValue(); + return StringQuoter.quote(c); + } else if (value instanceof String) { + return StringQuoter.quote(strVal); + } else if (parent != null) { + int opindex = parent.getOperatorIndex(); + if (opindex >= OPASSIGN_OP + ADD_OP + && opindex < OPASSIGN_OP + ASSIGN_OP) + opindex -= OPASSIGN_OP; + + if (opindex >= AND_OP && opindex < AND_OP + 3) { + /* For bit wise and/or/xor change representation. + */ + if (type.isOfType(Type.tUInt)) { + int i = ((Integer) value).intValue(); + if (i < -1) + strVal = "~0x"+Integer.toHexString(-i-1); + else + strVal = "0x"+Integer.toHexString(i); + } else if (type.equals(Type.tLong)) { + long l = ((Long) value).longValue(); + if (l < -1) + strVal = "~0x"+Long.toHexString(-l-1); + else + strVal = "0x"+Long.toHexString(l); + } + } + } + if (type.isOfType(Type.tLong)) + return strVal+"L"; + if (type.isOfType(Type.tFloat)) { + if (strVal.equals("NaN")) + return "Float.NaN"; + if (strVal.equals("-Infinity")) + return "Float.NEGATIVE_INFINITY"; + if (strVal.equals("Infinity")) + return "Float.POSITIVE_INFINITY"; + return strVal+"F"; + } + if (type.isOfType(Type.tDouble)) { + if (strVal.equals("NaN")) + return "Double.NaN"; + if (strVal.equals("-Infinity")) + return "Double.NEGATIVE_INFINITY"; + if (strVal.equals("Infinity")) + return "Double.POSITIVE_INFINITY"; + return strVal; + } + if (!type.isOfType(Type.tInt) + && (type.getHint().equals(Type.tByte) + || type.getHint().equals(Type.tShort)) + && !isInitializer + && !(parent instanceof StoreInstruction + && parent.getOperatorIndex() != ASSIGN_OP + && parent.subExpressions[1] == this)) { + /* One of the strange things in java. All constants + * are int and must be explicitly casted to byte,...,short. + * But in assignments and initializers this cast is unnecessary. + * See JLS section 5.2 + */ + return "("+type.getHint()+") "+strVal; + } + + return strVal; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print(toString()); + } +} diff --git a/jode/src/net/sf/jode/expr/ConstantArrayOperator.java b/jode/src/net/sf/jode/expr/ConstantArrayOperator.java new file mode 100644 index 0000000..fce087d --- /dev/null +++ b/jode/src/net/sf/jode/expr/ConstantArrayOperator.java @@ -0,0 +1,121 @@ +/* ConstantArrayOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.type.ArrayType; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ConstantArrayOperator extends Operator { + boolean isInitializer; + ConstOperator empty; + Type argType; + + public ConstantArrayOperator(Type type, int size) { + super(type); + argType = (type instanceof ArrayType) + ? Type.tSubType(((ArrayType)type).getElementType()) : Type.tError; + + Object emptyVal; + if (argType == type.tError || argType.isOfType(Type.tUObject)) + emptyVal = null; + else if (argType.isOfType(Type.tBoolUInt)) + emptyVal = new Integer(0); + else if (argType.isOfType(Type.tLong)) + emptyVal = new Long(0); + else if (argType.isOfType(Type.tFloat)) + emptyVal = new Float(0); + else if (argType.isOfType(Type.tDouble)) + emptyVal = new Double(0); + else + throw new IllegalArgumentException("Illegal Type: "+argType); + + empty = new ConstOperator(emptyVal); + empty.setType(argType); + empty.makeInitializer(argType); + initOperands(size); + for (int i=0; i < subExpressions.length; i++) + setSubExpressions(i, empty); + } + + public void updateSubTypes() { + argType = (type instanceof ArrayType) + ? Type.tSubType(((ArrayType)type).getElementType()) : Type.tError; + for (int i=0; i< subExpressions.length; i++) + if (subExpressions[i] != null) + subExpressions[i].setType(argType); + } + + public void updateType() { + } + + public boolean setValue(int index, Expression value) { + if (index < 0 || + index > subExpressions.length || + subExpressions[index] != empty) + return false; + value.setType(argType); + setType(Type.tSuperType(Type.tArray(value.getType()))); + subExpressions[index] = value; + value.parent = this; + value.makeInitializer(argType); + return true; + } + + public int getPriority() { + return 200; + } + + public void makeInitializer(Type type) { + if (type.getHint().isOfType(getType())) + isInitializer = true; + } + + public Expression simplify() { + for (int i=0; i< subExpressions.length; i++) { + if (subExpressions[i] != null) + subExpressions[i] = subExpressions[i].simplify(); + } + return this; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + if (!isInitializer) { + writer.print("new "); + writer.printType(type.getHint()); + writer.breakOp(); + writer.print(" "); + } + writer.print("{ "); + writer.startOp(writer.EXPL_PAREN, 0); + for (int i=0; i< subExpressions.length; i++) { + if (i>0) { + writer.print(", "); + writer.breakOp(); + } + if (subExpressions[i] != null) + subExpressions[i].dumpExpression(writer, 0); + else + empty.dumpExpression(writer, 0); + } + writer.endOp(); + writer.print(" }"); + } +} diff --git a/jode/src/net/sf/jode/expr/ConvertOperator.java b/jode/src/net/sf/jode/expr/ConvertOperator.java new file mode 100644 index 0000000..80efaa0 --- /dev/null +++ b/jode/src/net/sf/jode/expr/ConvertOperator.java @@ -0,0 +1,55 @@ +/* ConvertOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class ConvertOperator extends Operator { + Type from; + + public ConvertOperator(Type from, Type to) { + super(to, 0); + this.from = from; + initOperands(1); + } + + public boolean opEquals(Operator o) { + return (o instanceof ConvertOperator) + && type == o.type; + } + + public int getPriority() { + return 700; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tSubType(from)); + } + public void updateType() { + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("("); + writer.printType(type.getCanonic()); + writer.print(") "); + subExpressions[0].dumpExpression(writer, 700); + } +} diff --git a/jode/src/net/sf/jode/expr/Expression.java b/jode/src/net/sf/jode/expr/Expression.java new file mode 100644 index 0000000..8a31d0a --- /dev/null +++ b/jode/src/net/sf/jode/expr/Expression.java @@ -0,0 +1,318 @@ +/* Expression Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.GlobalOptions; +import net.sf.jode.decompiler.TabbedPrintWriter; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Set; +///#enddef + +public abstract class Expression { + protected Type type; + + Operator parent = null; + + public Expression(Type type) { + this.type = type; + } + + public void setType(Type otherType) { + Type newType = otherType.intersection(type); + if (type.equals(newType)) + return; + if (newType == Type.tError && otherType != Type.tError) { + GlobalOptions.err.println("setType: Type error in "+this + +": merging "+type+" and "+otherType); + if (parent != null) + GlobalOptions.err.println("\tparent is "+parent); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_TYPES) != 0) + Thread.dumpStack(); + } + type = newType; + if (type != Type.tError) + updateSubTypes(); + } + + public void updateParentType(Type otherType) { + setType(otherType); + if (parent != null) + parent.updateType(); + } + + /** + * Tells an expression that an inner expression may have changed and + * that the type should be recalculated. + * + * This may call setType of the caller again. + */ + public abstract void updateType(); + + /** + * Tells an expression that an outer expression has changed our type + * and that the inner types should be recalculated. + */ + public abstract void updateSubTypes(); + + public Type getType() { + return type; + } + + public Operator getParent() { + return parent; + } + + /** + * Get priority of the operator. + * Currently this priorities are known: + *
  • 1000 constant + *
  • 950 new, .(field access), [] + *
  • 900 new[] + *
  • 800 ++,-- (post) + *
  • 700 ++,--(pre), +,-(unary), ~, !, cast + *
  • 650 *,/, % + *
  • 610 +,- + *
  • 600 <<, >>, >>> + *
  • 550 >, <, >=, <=, instanceof + *
  • 500 ==, != + *
  • 450 & + *
  • 420 ^ + *
  • 410 | + *
  • 350 && + *
  • 310 || + *
  • 200 ?: + *
  • 100 =, +=, -=, etc. + *
+ */ + public abstract int getPriority(); + + /** + * Get the penalty for splitting up this operator. + */ + public int getBreakPenalty() { + return 0; + } + + /** + * Get the number of operands. + * @return The number of stack entries this expression needs. + */ + public abstract int getFreeOperandCount(); + + public abstract Expression addOperand(Expression op); + + public Expression negate() { + Operator negop = + new UnaryOperator(Type.tBoolean, Operator.LOG_NOT_OP); + negop.addOperand(this); + return negop; + } + + /** + * Checks if the value of the given expression can change, due to + * side effects in this expression. If this returns false, the + * expression can safely be moved behind the current expresion. + * @param expr the expression that should not change. + */ + public boolean hasSideEffects(Expression expr) { + return false; + } + + /** + * Checks if the given Expression (which should be a CombineableOperator) + * can be combined into this expression. + * @param e The store expression, must be of type void. + * @return 1, if it can, 0, if no match was found and -1, if a + * conflict was found. You may wish to check for >0. + */ + public int canCombine(CombineableOperator combOp) { + return 0; + } + + /** + * Checks if this expression contains a load, that matches the + * given CombineableOperator (which must be an Operator) + * @param e The store expression. + * @return if this expression contains a matching load. + * @exception ClassCastException, if e.getOperator + * is not a CombineableOperator. + */ + public boolean containsMatchingLoad(CombineableOperator e) { + return false; + } + + /** + * Checks if this expression contains a conflicting load, that + * matches the given CombineableOperator. The sub expressions are + * not checked. + * @param op The combineable operator. + * @return if this expression contains a matching load. */ + public boolean containsConflictingLoad(MatchableOperator op) { + return false; + } + + /** + * Combines the given Expression (which should be a StoreInstruction) + * into this expression. You must only call this if + * canCombine returns the value 1. + * @param e The store expression, + * the operator must be a CombineableOperator. + * @return The combined expression. + * @exception ClassCastException, if e.getOperator + * is not a CombineableOperator. + */ + public Expression combine(CombineableOperator comb) { + return null; + } + + /** + * This method should remove local variables that are only written + * and read one time directly after another.
+ * + * In this case this is a non void LocalStoreOperator, whose local + * isn't used in other places. + * @return an expression where the locals are removed. + */ + public Expression removeOnetimeLocals() { + return this; + } + + public Expression simplify() { + return this; + } + public Expression simplifyString() { + return this; + } + + public Expression simplifyStringBuffer() { + return null; + } + + public void makeInitializer(Type type) { + } + + public boolean isConstant() { + return true; + } + + public void fillInGenSet(Collection in, Collection gen) { + } + + public void fillDeclarables(Collection used) { + } + + public void makeDeclaration(Set done) { + } + + public abstract void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException; + + public void dumpExpression(int options, TabbedPrintWriter writer) + throws java.io.IOException + { + writer.startOp(options, getBreakPenalty()); + dumpExpression(writer); + writer.endOp(); + } + + public void dumpExpression(TabbedPrintWriter writer, int minPriority) + throws java.io.IOException { + int options; + boolean needParen1 = false, needParen2 = false; + boolean needEndOp1 = false, needEndOp2 = false; + + String typecast = ""; + + if (type == Type.tError) + typecast = "/*TYPE_ERROR*/"; + else if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_TYPES) != 0) + typecast = "(TYPE "+type+")"; + + if (typecast != "") { + if (minPriority > 700) { + needParen1 = true; + needEndOp1 = true; + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 0); + } else if (minPriority < 700) { + needEndOp1 = true; + writer.startOp(writer.IMPL_PAREN, 1); + } + writer.print(typecast); + writer.breakOp(); + writer.print(" "); + minPriority = 700; + } + + int priority = getPriority(); + if (priority < minPriority) { + needParen2 = true; + needEndOp2 = true; + writer.print("("); + writer.startOp(writer.EXPL_PAREN, getBreakPenalty()); + } else if (priority != minPriority) { + needEndOp2 = true; + if (getType() == Type.tVoid) + writer.startOp(writer.NO_PAREN, getBreakPenalty()); + else + writer.startOp(writer.IMPL_PAREN, 1 + getBreakPenalty()); + } + + try { + dumpExpression(writer); + } catch (RuntimeException ex) { + writer.print("(RUNTIME ERROR IN EXPRESSION)"); + ex.printStackTrace(); + } + + if (needEndOp2) { + writer.endOp(); + if (needParen2) + writer.print(")"); + } + if (needEndOp1) { + writer.endOp(); + if (needParen1) + writer.print(")"); + } + } + + public String toString() { + try { + java.io.StringWriter strw = new java.io.StringWriter(); + TabbedPrintWriter writer = new TabbedPrintWriter(strw); + dumpExpression(writer); + writer.close(); + return strw.toString(); + } catch (java.io.IOException ex) { + return "/*IOException*/"+super.toString(); + } catch (RuntimeException ex) { + return "/*RuntimeException*/"+super.toString(); + } + } + + public boolean isVoid() { + return getType() == Type.tVoid; + } +} diff --git a/jode/src/net/sf/jode/expr/FieldOperator.java b/jode/src/net/sf/jode/expr/FieldOperator.java new file mode 100644 index 0000000..189c137 --- /dev/null +++ b/jode/src/net/sf/jode/expr/FieldOperator.java @@ -0,0 +1,316 @@ +/* FieldOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.type.NullType; +import net.sf.jode.type.ClassInfoType; +import net.sf.jode.bytecode.FieldInfo; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.TypeSignature; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.ClassAnalyzer; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.FieldAnalyzer; +import net.sf.jode.decompiler.Options; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.decompiler.Scope; + +import java.io.IOException; +import java.lang.reflect.Modifier; +///#def COLLECTIONS java.util +import java.util.Collection; +///#enddef + +/** + * This class contains everything shared between PutFieldOperator and + * GetFieldOperator + */ +public abstract class FieldOperator extends Operator { + MethodAnalyzer methodAnalyzer; + boolean staticFlag; + Reference ref; + Type classType; + ClassInfo classInfo; + ClassPath classPath; + String callerPackage; + + public FieldOperator(MethodAnalyzer methodAnalyzer, boolean staticFlag, + Reference ref) { + super(Type.tUnknown); + + this.classPath = methodAnalyzer.getClassAnalyzer().getClassPath(); + + this.methodAnalyzer = methodAnalyzer; + this.staticFlag = staticFlag; + this.type = Type.tType(classPath, ref.getType()); + this.classType = Type.tType(classPath, ref.getClazz()); + this.ref = ref; + if (staticFlag) + methodAnalyzer.useType(classType); + initOperands(staticFlag ? 0 : 1); + + callerPackage = methodAnalyzer.getClassAnalyzer().getClass().getName(); + int dot = callerPackage.lastIndexOf('.'); + callerPackage = callerPackage.substring(0, dot); + if (classType instanceof ClassInfoType) { + classInfo = ((ClassInfoType) classType).getClassInfo(); + if ((Options.options & Options.OPTION_ANON) != 0 + || (Options.options & Options.OPTION_INNER) != 0) { + try { + classInfo.load(ClassInfo.OUTERCLASS); + } catch (IOException ex) { + classInfo.guess(ClassInfo.OUTERCLASS); + } + } + } + } + + public int getPriority() { + return 950; + } + + public void updateSubTypes() { + if (!staticFlag) + subExpressions[0].setType(Type.tSubType(classType)); + } + + public void updateType() { + updateParentType(getFieldType()); + } + + public boolean isStatic() { + return staticFlag; + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + /** + * Returns the field analyzer for the field, if the field is + * declared in the same class or some outer class as the method + * containing this instruction. Otherwise it returns null. + * @return see above. + */ + public FieldAnalyzer getField() { + ClassInfo clazz = classInfo; + if (clazz != null) { + ClassAnalyzer ana = methodAnalyzer.getClassAnalyzer(); + while (true) { + if (clazz == ana.getClazz()) { + int field = ana.getFieldIndex + (ref.getName(), Type.tType(classPath, ref.getType())); + if (field >= 0) + return ana.getField(field); + return null; + } + if (ana.getParent() == null) + return null; + if (ana.getParent() instanceof MethodAnalyzer) + ana = ((MethodAnalyzer) ana.getParent()) + .getClassAnalyzer(); + else if (ana.getParent() instanceof ClassAnalyzer) + ana = (ClassAnalyzer) ana.getParent(); + else + throw new InternalError("Unknown parent"); + } + } + return null; + } + + public String getFieldName() { + return ref.getName(); + } + + public Type getFieldType() { + return Type.tType(classPath, ref.getType()); + } + + private void loadFields(ClassInfo clazz) { + int howMuch = (clazz.getName().startsWith(callerPackage) + && (clazz.getName().lastIndexOf('.') + < callerPackage.length())) + ? ClassInfo.DECLARATIONS : ClassInfo.PUBLICDECLARATIONS; + try { + clazz.load(howMuch); + } catch (IOException ex) { + GlobalOptions.err.println("Warning: Can't find fields of " + +clazz+" to detect hiding conflicts"); + clazz.guess(howMuch); + } + } + + private FieldInfo getFieldInfo(ClassInfo clazz, + String name, String type) { + while (clazz != null) { + loadFields(clazz); + FieldInfo field = clazz.findField(name, type); + if (field != null) + return field; + + ClassInfo[] ifaces = clazz.getInterfaces(); + for (int i = 0; i < ifaces.length; i++) { + field = getFieldInfo(ifaces[i], name, type); + if (field != null) + return field; + } + + clazz = clazz.getSuperclass(); + } + return null; + } + + public FieldInfo getFieldInfo() { + return getFieldInfo(classInfo, ref.getName(), ref.getType()); + } + + /** + * Checks if we need a cast to the super class, to which the field + * belongs. + * @param type the canonic type of the zeroth subexpression. + */ + public boolean needsCast(Type type) { + if (type instanceof NullType) + return true; + if (!(type instanceof ClassInfoType + && classType instanceof ClassInfoType)) + return false; + + ClassInfo subexprClass = ((ClassInfoType) type).getClassInfo(); + FieldInfo field = getFieldInfo(); + if (field == null) { + /* Weird, field not existing? */ + return false; + } + + /** + * We need an explicit cast if we access a private field + * of a parent class (which is only possible if the parent + * class is an outer class of the current class). + */ + if (Modifier.isPrivate(field.getModifiers())) + return subexprClass != classInfo; + else if ((field.getModifiers() + & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) { + /* Field is protected. We need a cast if subexprClass is in + * other package than classInfo. + */ + int lastDot = classInfo.getName().lastIndexOf('.'); + if (lastDot == -1 + || lastDot != subexprClass.getName().lastIndexOf('.') + || !(subexprClass.getName() + .startsWith(classInfo.getName().substring(0,lastDot)))) + return true; + } + + /* We also need an explicit cast if the field is hidden by a + * declaration in param class. + */ + while (classInfo != subexprClass && classInfo != null) { + FieldInfo[] fields = subexprClass.getFields(); + for (int i = 0; i < fields.length; i++) { + if (fields[i].getName().equals(ref.getName())) + return true; + } + subexprClass = subexprClass.getSuperclass(); + } + return false; + } + + /** + * We add the named method scoped classes to the declarables. + */ + public void fillDeclarables(Collection used) { + ClassInfo clazz = getClassInfo(); + ClassAnalyzer clazzAna = methodAnalyzer.getClassAnalyzer(clazz); + + if ((Options.options & Options.OPTION_ANON) != 0 + && clazz != null + && clazz.isMethodScoped() && clazz.getClassName() != null + && clazzAna != null + && clazzAna.getParent() == methodAnalyzer) { + + /* This is a named method scope class, declare it. + * But first declare all method scoped classes, + * that are used inside; order does matter. + */ + clazzAna.fillDeclarables(used); + used.add(clazzAna); + } + super.fillDeclarables(used); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + boolean opIsThis = !staticFlag + && subExpressions[0] instanceof ThisOperator; + String fieldName = ref.getName(); + if (staticFlag) { + if (!classType.equals(Type.tClass(methodAnalyzer.getClazz())) + || methodAnalyzer.findLocal(fieldName) != null) { + writer.printType(classType); + writer.breakOp(); + writer.print("."); + } + } else if (needsCast(subExpressions[0].getType().getCanonic())) { + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 1); + writer.print("("); + writer.printType(classType); + writer.print(") "); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + writer.breakOp(); + writer.print("."); + } else if (opIsThis) { + ThisOperator thisOp = (ThisOperator) subExpressions[0]; + Scope scope = writer.getScope(thisOp.getClassInfo(), + Scope.CLASSSCOPE); + + if (scope == null || writer.conflicts(fieldName, scope, + Scope.FIELDNAME)) { + thisOp.dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } else if (writer.conflicts(fieldName, scope, + Scope.AMBIGUOUSNAME) + || (/* This is a inherited field conflicting + * with a field name in some outer class. + */ + getField() == null + && writer.conflicts(fieldName, null, + Scope.NOSUPERFIELDNAME))) { + thisOp.dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } + } else { + subExpressions[0].dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } + writer.print(fieldName); + } +} diff --git a/jode/src/net/sf/jode/expr/GetFieldOperator.java b/jode/src/net/sf/jode/expr/GetFieldOperator.java new file mode 100644 index 0000000..cc3fb34 --- /dev/null +++ b/jode/src/net/sf/jode/expr/GetFieldOperator.java @@ -0,0 +1,57 @@ +/* GetFieldOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.FieldAnalyzer; + +public class GetFieldOperator extends FieldOperator { + public GetFieldOperator(MethodAnalyzer methodAnalyzer, boolean staticFlag, + Reference ref) { + super(methodAnalyzer, staticFlag, ref); + } + + public Expression simplify() { + if (!staticFlag) { + subExpressions[0] = subExpressions[0].simplify(); + subExpressions[0].parent = this; + if (subExpressions[0] instanceof ThisOperator) { + FieldAnalyzer field = getField(); + /* This should check for isFinal(), but sadly, + * sometimes jikes doesn't make a val$ field final. I + * don't know when, or why, so I currently ignore + * isFinal. + */ + if (field != null && field.isSynthetic()) { + Expression constant = field.getConstant(); + if (constant instanceof ThisOperator + || constant instanceof OuterLocalOperator) + return constant; + } + } + } + return this; + } + + public boolean opEquals(Operator o) { + return o instanceof GetFieldOperator + && ((GetFieldOperator)o).ref.equals(ref); + } +} diff --git a/jode/src/net/sf/jode/expr/IIncOperator.java b/jode/src/net/sf/jode/expr/IIncOperator.java new file mode 100644 index 0000000..ac8311a --- /dev/null +++ b/jode/src/net/sf/jode/expr/IIncOperator.java @@ -0,0 +1,87 @@ +/* IIncOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class IIncOperator extends Operator + implements CombineableOperator { + int value; + + public IIncOperator(LocalStoreOperator localStore, int value, + int operator) { + super(Type.tVoid, operator); + this.value = value; + initOperands(1); + setSubExpressions(0, localStore); + } + + public LValueExpression getLValue() { + return (LValueExpression) subExpressions[0]; + } + + public int getValue() { + return value; + } + + public int getPriority() { + return 100; + } + + public void updateSubTypes() { + subExpressions[0].setType(type != Type.tVoid ? type : Type.tInt); + } + + + public void updateType() { + if (type != Type.tVoid) + updateParentType(subExpressions[0].getType()); + } + + /** + * Makes a non void expression out of this store instruction. + */ + public void makeNonVoid() { + if (type != Type.tVoid) + throw new InternalError("already non void"); + type = subExpressions[0].getType(); + } + + public boolean lvalueMatches(Operator loadop) { + return getLValue().matches(loadop); + } + + public Expression simplify() { + if (value == 1) { + int op = (getOperatorIndex() == OPASSIGN_OP+ADD_OP) + ? INC_OP : DEC_OP; + return new PrePostFixOperator + (getType(), op, getLValue(), isVoid()).simplify(); + } + return super.simplify(); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, 950); + writer.print(getOperatorString() + value); + } +} diff --git a/jode/src/net/sf/jode/expr/IfThenElseOperator.java b/jode/src/net/sf/jode/expr/IfThenElseOperator.java new file mode 100644 index 0000000..e183779 --- /dev/null +++ b/jode/src/net/sf/jode/expr/IfThenElseOperator.java @@ -0,0 +1,138 @@ +/* IfThenElseOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.decompiler.FieldAnalyzer; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class IfThenElseOperator extends Operator { + public IfThenElseOperator(Type type) { + super(type, 0); + initOperands(3); + } + + public int getPriority() { + return 200; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tBoolean); + subExpressions[1].setType(Type.tSubType(type)); + subExpressions[2].setType(Type.tSubType(type)); + } + + public void updateType() { + Type commonType = Type.tSuperType(subExpressions[1].getType()) + .intersection(Type.tSuperType(subExpressions[2].getType())); + updateParentType(commonType); + } + + public Expression simplify() { + if (getType().isOfType(Type.tBoolean)) { + if (subExpressions[1] instanceof ConstOperator + && subExpressions[2] instanceof ConstOperator) { + ConstOperator c1 = (ConstOperator) subExpressions[1]; + ConstOperator c2 = (ConstOperator) subExpressions[2]; + if (c1.getValue().equals(new Integer(1)) && + c2.getValue().equals(new Integer(0))) + return subExpressions[0].simplify(); + if (c2.getValue().equals(new Integer(1)) && + c1.getValue().equals(new Integer(0))) + return subExpressions[0].negate().simplify(); + } + } + if (subExpressions[0] instanceof CompareUnaryOperator + && ((((CompareUnaryOperator) subExpressions[0]) + .getOperatorIndex() & ~1) == Operator.COMPARE_OP)) { + CompareUnaryOperator cmp + = (CompareUnaryOperator) subExpressions[0]; + int cmpType = cmp.getOperatorIndex() & 1; + if ((subExpressions[2 - cmpType] instanceof GetFieldOperator) + && (subExpressions[1 + cmpType] instanceof StoreInstruction)) { + // Check for + // class$classname != null ? class$classname : + // (class$classname = class$("classname")) + // and replace with + // classname.class + GetFieldOperator get + = (GetFieldOperator) subExpressions[2 - cmpType]; + StoreInstruction put + = (StoreInstruction) subExpressions[1 + cmpType]; + int opIndex = cmp.getOperatorIndex(); + FieldAnalyzer field; + if (put.getLValue() instanceof PutFieldOperator + && ((field = ((PutFieldOperator)put.getLValue()) + .getField()) != null) && field.isSynthetic() + && put.lvalueMatches(get) + && (cmp.subExpressions[0] instanceof GetFieldOperator) + && put.lvalueMatches((GetFieldOperator) + cmp.subExpressions[0]) + && put.subExpressions[1] instanceof InvokeOperator) { + InvokeOperator invoke = (InvokeOperator) + put.subExpressions[1]; + if (invoke.isGetClass() + && invoke.subExpressions[0] instanceof ConstOperator + && (invoke.subExpressions[0].getType() + .equals(Type.tString))) { + String clazz = (String) + ((ConstOperator)invoke.subExpressions[0]) + .getValue(); + ClassPath cp = field.getClassAnalyzer().getClassPath(); + if (field.setClassConstant(clazz)) + return new ClassFieldOperator + (invoke.getType(), + clazz.charAt(0) == '[' + ? Type.tType(cp, clazz) + : Type.tClass(cp, clazz)); + } + } + } + } + return super.simplify(); + } + + public boolean opEquals(Operator o) { + return (o instanceof IfThenElseOperator); + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + subExpressions[0].dumpExpression(writer, 201); + writer.breakOp(); + writer.print(" ? "); + int subPriority = 0; + if (!subExpressions[1].getType().getHint().isOfType + (subExpressions[2].getType())) { + writer.startOp(writer.IMPL_PAREN, 2); + /* We need a cast here */ + writer.print("("); + writer.printType(getType().getHint()); + writer.print(") "); + subPriority = 700; + } + subExpressions[1].dumpExpression(writer, subPriority); + if (subPriority == 700) + writer.endOp(); + writer.breakOp(); + writer.print(" : "); + subExpressions[2].dumpExpression(writer, 200); + } +} diff --git a/jode/src/net/sf/jode/expr/InstanceOfOperator.java b/jode/src/net/sf/jode/expr/InstanceOfOperator.java new file mode 100644 index 0000000..e45d5d9 --- /dev/null +++ b/jode/src/net/sf/jode/expr/InstanceOfOperator.java @@ -0,0 +1,67 @@ +/* InstanceOfOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class InstanceOfOperator extends Operator { + + Type instanceType; + + public InstanceOfOperator(Type type) { + super(Type.tBoolean, 0); + this.instanceType = type; + initOperands(1); + } + + public int getPriority() { + return 550; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tUObject); + } + + public void updateType() { + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + /* There are special cases where a cast isn't allowed. We + * must cast to the common super type before. In these cases + * instanceof always return false, but we want to decompile + * even bad programs. */ + Type superType + = instanceType.getCastHelper(subExpressions[0].getType()); + if (superType != null) { + writer.startOp(writer.IMPL_PAREN, 2); + writer.print("("); + writer.printType(superType); + writer.print(") "); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + } else + subExpressions[0].dumpExpression(writer, 550); + writer.breakOp(); + writer.print(" instanceof "); + writer.printType(instanceType); + } +} diff --git a/jode/src/net/sf/jode/expr/InvokeOperator.java b/jode/src/net/sf/jode/expr/InvokeOperator.java new file mode 100644 index 0000000..f799b50 --- /dev/null +++ b/jode/src/net/sf/jode/expr/InvokeOperator.java @@ -0,0 +1,1286 @@ +/* InvokeOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import java.lang.reflect.Modifier; + +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.ClassAnalyzer; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.decompiler.Options; +import net.sf.jode.decompiler.OuterValues; +import net.sf.jode.decompiler.Scope; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.*; +import net.sf.jode.jvm.*; +import net.sf.jode.type.*; +import net.sf.jode.util.SimpleMap; + +import java.lang.reflect.InvocationTargetException; +import java.io.IOException; +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +///#enddef + +public final class InvokeOperator extends Operator + implements MatchableOperator { + + public final static int VIRTUAL = 0; + public final static int SPECIAL = 1; + public final static int STATIC = 2; + public final static int CONSTRUCTOR = 3; + public final static int ACCESSSPECIAL = 4; + + /** + * The methodAnalyzer of the method, that contains this invocation. + * This is not the method that we should call. + */ + MethodAnalyzer methodAnalyzer; + int methodFlag; + MethodType methodType; + String methodName; + Reference ref; + int skippedArgs; + ClassType classType; + Type[] hints; + ClassInfo classInfo; + ClassPath classPath; + String callerPackage; + + /** + * This hash map contains hints for every library method. Some + * library method take or return an int, but it should be a char + * instead. We will remember that here to give them the right + * hint. + * + * The key is the string: methodName + "." + methodType, the value + * is a map: It maps base class types for which this hint applies, + * to an array of hint types corresponding to the parameters: The + * first element is the hint type of the return value, the + * remaining entries are the hint types of the parameters. All + * hint types may be null, if that parameter shouldn't be hinted. + * + * The reason why we don't put the class name into the top level + * key, is that we don't necessarily know the class. We may have + * a sub class, but the hint should of course still apply. + */ + private final static HashMap hintTypes = new HashMap(); + + static { + /* Fill the hint type hash map. For example, the first + * parameter of String.indexOf should be hinted as char, even + * though the formal parameter is an int. + * First hint is hint of return value (even if void) + * other hints are that of the parameters in order + * + * You only have to hint the base class. Other classes will + * inherit the hints. + * + * We reuse a lot of objects, since they are all unchangeable + * this is no problem. We only hint for chars; it doesn't + * make much sense to hint for byte, since its constant + * representation is more difficult than an int + * representation. If you have more hints to suggest, please + * contact me. (see GlobalOptions.EMAIL) + */ + Type tCharHint = new IntegerType(IntegerType.IT_I, IntegerType.IT_C); + Type[] hintC = new Type[] { tCharHint }; + Type[] hint0C = new Type[] { null, tCharHint }; + Type[] hint0C0 = new Type[] { null, tCharHint, null }; + + ClassType tWriter = + Type.tSystemClass("java.io.Writer", + Type.tObject, Type.EMPTY_IFACES, false, false); + ClassType tReader = + Type.tSystemClass("java.io.Reader", + Type.tObject, Type.EMPTY_IFACES, false, false); + ClassType tFilterReader = + Type.tSystemClass("java.io.FilterReader", + tReader, Type.EMPTY_IFACES, false, false); + ClassType tPBReader = + Type.tSystemClass("java.io.PushBackReader", + tFilterReader, Type.EMPTY_IFACES, + false, false); + + Map hintString0CMap = new SimpleMap + (Collections.singleton + (new SimpleMap.SimpleEntry(Type.tString, hint0C))); + Map hintString0C0Map = new SimpleMap + (Collections.singleton + (new SimpleMap.SimpleEntry(Type.tString, hint0C0))); + hintTypes.put("indexOf.(I)I", hintString0CMap); + hintTypes.put("lastIndexOf.(I)I", hintString0CMap); + hintTypes.put("indexOf.(II)I", hintString0C0Map); + hintTypes.put("lastIndexOf.(II)I", hintString0C0Map); + hintTypes.put("write.(I)V", new SimpleMap + (Collections.singleton + (new SimpleMap.SimpleEntry + (tWriter, hint0C)))); + hintTypes.put("read.()I", new SimpleMap + (Collections.singleton + (new SimpleMap.SimpleEntry + (tReader, hintC)))); + hintTypes.put("unread.(I)V", new SimpleMap + (Collections.singleton + (new SimpleMap.SimpleEntry + (tPBReader, hint0C)))); + } + + + public InvokeOperator(MethodAnalyzer methodAnalyzer, + int methodFlag, Reference reference) { + super(Type.tUnknown, 0); + this.classPath = methodAnalyzer.getClassAnalyzer().getClassPath(); + this.ref = reference; + this.methodType = Type.tMethod(classPath, reference.getType()); + this.methodName = reference.getName(); + this.classType = (ClassType) + Type.tType(classPath, reference.getClazz()); + this.hints = null; + Map allHints = (Map) hintTypes.get(methodName+"."+methodType); + if (allHints != null) { + for (Iterator i = allHints.entrySet().iterator(); i.hasNext();) { + Map.Entry e = (Map.Entry) i.next(); + if (classType.isOfType(((Type)e.getKey()).getSubType())) { + this.hints = (Type[]) e.getValue(); + break; + } + } + } + if (hints != null && hints[0] != null) + this.type = hints[0]; + else + this.type = methodType.getReturnType(); + this.methodAnalyzer = methodAnalyzer; + this.methodFlag = methodFlag; + if (methodFlag == STATIC) + methodAnalyzer.useType(classType); + skippedArgs = (methodFlag == STATIC ? 0 : 1); + initOperands(skippedArgs + methodType.getParameterTypes().length); + + callerPackage = methodAnalyzer.getClassAnalyzer().getClass().getName(); + int dot = callerPackage.lastIndexOf('.'); + callerPackage = callerPackage.substring(0, dot); + if (classType instanceof ClassInfoType) { + classInfo = ((ClassInfoType) classType).getClassInfo(); + if ((Options.options & Options.OPTION_ANON) != 0 + || (Options.options & Options.OPTION_INNER) != 0) { + try { + classInfo.load(ClassInfo.OUTERCLASS); + } catch (IOException ex) { + classInfo.guess(ClassInfo.OUTERCLASS); + } + checkAnonymousClasses(); + } + } + } + + public final ClassPath getClassPath() { + return classPath; + } + + public final boolean isStatic() { + return methodFlag == STATIC; + } + + public MethodType getMethodType() { + return methodType; + } + + public String getMethodName() { + return methodName; + } + + private static MethodInfo getMethodInfo(ClassInfo clazz, + String name, String type) { + while (clazz != null) { + try { + clazz.load(clazz.DECLARATIONS); + } catch (IOException ex) { + clazz.guess(clazz.DECLARATIONS); + } + MethodInfo method = clazz.findMethod(name, type); + if (method != null) + return method; + clazz = clazz.getSuperclass(); + } + return null; + } + + public MethodInfo getMethodInfo() { + ClassInfo clazz; + if (ref.getClazz().charAt(0) == '[') + clazz = classPath.getClassInfo("java.lang.Object"); + else + clazz = TypeSignature.getClassInfo(classPath, ref.getClazz()); + return getMethodInfo(clazz, ref.getName(), ref.getType()); + } + + public Type getClassType() { + return classType; + } + + public int getPriority() { + return 950; + } + + public void checkAnonymousClasses() { + if (methodFlag != CONSTRUCTOR + || (Options.options & Options.OPTION_ANON) == 0) + return; + if (classInfo != null + && classInfo.isMethodScoped()) + methodAnalyzer.addAnonymousConstructor(this); + } + + public void updateSubTypes() { + int offset = 0; + if (!isStatic()) { + subExpressions[offset++].setType(getClassType().getSubType()); + } + Type[] paramTypes = methodType.getParameterTypes(); + for (int i=0; i < paramTypes.length; i++) { + Type pType = (hints != null && hints[i+1] != null) + ? hints[i+1] : paramTypes[i]; + subExpressions[offset++].setType(pType.getSubType()); + } + } + + public void updateType() { + } + + /** + * Makes a non void expression, in case this is a constructor. + */ + public void makeNonVoid() { + if (type != Type.tVoid) + throw new InternalError("already non void"); + ClassInfo clazz = classInfo; + if (clazz != null + && clazz.isMethodScoped() && clazz.getClassName() == null) { + /* This is an anonymous class */ + if (clazz.getInterfaces().length > 0) + type = Type.tClass(clazz.getInterfaces()[0]); + else + type = Type.tClass(clazz.getSuperclass()); + } else + type = subExpressions[0].getType(); + } + + public boolean isConstructor() { + return methodFlag == CONSTRUCTOR; + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + /** + * Checks, whether this is a call of a method from this class. + */ + public boolean isThis() { + return classInfo == methodAnalyzer.getClazz(); + } + + /** + * Tries to locate the class analyzer for the callee class. This + * is mainly useful for inner and anonymous classes. + * + * @param callee the callee class. + * @return The class analyzer, if the callee class is declared + * inside the same base class as the caller class, null otherwise. + */ + private ClassAnalyzer getClassAnalyzer(ClassInfo callee) { + if (callee == null) + return null; + if ((Options.options & + (Options.OPTION_ANON | Options.OPTION_INNER)) == 0) + return null; + + if ((Options.options & Options.OPTION_INNER) != 0 + && callee.getOuterClass() != null) { + /* If the callee class is an inner class we get the + * analyzer of its parent instead and ask it for the inner + * class analyzer. + */ + ClassAnalyzer outerAna = getClassAnalyzer(callee.getOuterClass()); + return outerAna == null ? null + : outerAna.getInnerClassAnalyzer(callee.getClassName()); + } + + /* First check if our methodAnlyzer knows about it */ + ClassAnalyzer ana = methodAnalyzer.getClassAnalyzer(callee); + + if (ana == null) { + /* Now we iterate through the parent clazz analyzers until + * we find the class analyzer for callee. + */ + ana = methodAnalyzer.getClassAnalyzer(); + while (callee != ana.getClazz()) { + if (ana.getParent() == null) + return null; + if (ana.getParent() instanceof MethodAnalyzer + && (Options.options & Options.OPTION_ANON) != 0) + ana = ((MethodAnalyzer) ana.getParent()) + .getClassAnalyzer(); + else if (ana.getParent() instanceof ClassAnalyzer + && (Options.options + & Options.OPTION_INNER) != 0) + ana = (ClassAnalyzer) ana.getParent(); + else + throw new InternalError + ("Unknown parent: "+ana+": "+ana.getParent()); + } + } + return ana; + } + + /** + * Tries to locate the class analyzer for the callee class. This + * is mainly useful for inner and anonymous classes. + * + * @return The class analyzer, if the callee class is declared + * inside the same base class as the caller class, null otherwise. + */ + public ClassAnalyzer getClassAnalyzer() { + return getClassAnalyzer(classInfo); + } + + /** + * Checks, whether this is a call of a method from this class or an + * outer instance. + */ + public boolean isOuter() { + if (classInfo != null) { + ClassAnalyzer ana = methodAnalyzer.getClassAnalyzer(); + while (true) { + if (classInfo == ana.getClazz()) + return true; + if (ana.getParent() == null) + break; + if (ana.getParent() instanceof MethodAnalyzer + && (Options.options & Options.OPTION_ANON) != 0) + ana = ((MethodAnalyzer) ana.getParent()) + .getClassAnalyzer(); + else if (ana.getParent() instanceof ClassAnalyzer + && (Options.options + & Options.OPTION_INNER) != 0) + ana = (ClassAnalyzer) ana.getParent(); + else + throw new InternalError + ("Unknown parent: "+ana+": "+ana.getParent()); + } + } + return false; + } + + /** + * Tries to locate the method analyzer for the callee. This + * is mainly useful for inner and anonymous classes. + * + * @return The method analyzer, if the callee is declared + * inside the same base class as the caller class, null otherwise. + */ + public MethodAnalyzer getMethodAnalyzer() { + ClassAnalyzer ana = getClassAnalyzer(classInfo); + if (ana == null) + return null; + return ana.getMethod(methodName, methodType); + } + + /** + * Checks, whether this is a call of a method from the super class. + */ + public boolean isSuperOrThis() { + return classType.maybeSuperTypeOf + (Type.tClass(methodAnalyzer.getClazz())); + } + + public boolean isConstant() { + if ((Options.options & Options.OPTION_ANON) == 0) + return super.isConstant(); + + ClassInfo clazz = classInfo; + if (clazz != null + && clazz.isMethodScoped() && clazz.getClassName() != null) { + ClassAnalyzer clazzAna = methodAnalyzer.getClassAnalyzer(clazz); + if (clazzAna != null && clazzAna.getParent() == methodAnalyzer) + /* This is a named class of this method, it needs + * declaration. And therefore can't be moved into a + * field initializer. + */ + return false; + } + return super.isConstant(); + } + + /** + * Checks if the value of the operator can be changed by this expression. + */ + public boolean matches(Operator loadop) { + return (loadop instanceof InvokeOperator + || loadop instanceof GetFieldOperator); + } + + /** + * Checks if the method is the magic class$ method. + * @return true if this is the magic class$ method, false otherwise. + */ + public boolean isGetClass() { + MethodAnalyzer mana = getMethodAnalyzer(); + if (mana == null) + return false; + SyntheticAnalyzer synth = getMethodAnalyzer().getSynthetic(); + return (synth != null + && synth.getKind() == SyntheticAnalyzer.GETCLASS); + } + + class Environment extends SimpleRuntimeEnvironment { + + Interpreter interpreter; + ClassInfo classInfo; + String classSig; + + public Environment(ClassInfo classInfo) { + this.classInfo = classInfo; + this.classSig = "L" + classInfo.getName().replace('.','/') + ";"; + } + + public Object invokeMethod(Reference ref, boolean isVirtual, + Object cls, Object[] params) + throws InterpreterException, InvocationTargetException { + if (cls == null && ref.getClazz().equals(classSig)) { + BasicBlocks bb = classInfo + .findMethod(ref.getName(), ref.getType()) + .getBasicBlocks(); + if (bb != null) + return interpreter.interpretMethod(bb, null, params); + throw new InterpreterException + ("Can't interpret static native method: "+ref); + } else + return super.invokeMethod(ref, isVirtual, cls, params); + } + } + + public ConstOperator deobfuscateString(ConstOperator op) { + ClassAnalyzer clazz = methodAnalyzer.getClassAnalyzer(); + MethodAnalyzer ma = clazz.getMethod(methodName, methodType); + if (ma == null) + return null; + Environment env = new Environment(methodAnalyzer.getClazz()); + Interpreter interpreter = new Interpreter(env); + env.interpreter = interpreter; + + String result; + try { + result = (String) interpreter.interpretMethod + (ma.getBasicBlocks(), null, new Object[] { op.getValue() }); + } catch (InterpreterException ex) { + if ((GlobalOptions.debuggingFlags & + GlobalOptions.DEBUG_INTERPRT) != 0) { + GlobalOptions.err.println("Warning: Can't interpret method " + +methodName); + ex.printStackTrace(GlobalOptions.err); + } + return null; + } catch (InvocationTargetException ex) { + if ((GlobalOptions.debuggingFlags & + GlobalOptions.DEBUG_INTERPRT) != 0) { + GlobalOptions.err.println("Warning: Interpreted method throws" + +" an uncaught exception: "); + ex.getTargetException().printStackTrace(GlobalOptions.err); + } + return null; + } + return new ConstOperator(classPath, result); + } + + public Expression simplifyStringBuffer() { + if (getClassType().equals(Type.tStringBuffer) + || getClassType().equals(Type.tStringBuilder)) { + if (isConstructor() + && subExpressions[0] instanceof NewOperator) { + if (methodType.getParameterTypes().length == 0) + return emptyString(); + if (methodType.getParameterTypes().length == 1 + && methodType.getParameterTypes()[0].equals(Type.tString)) + return subExpressions[1].simplifyString(); + } + + if (!isStatic() + && getMethodName().equals("append") + && getMethodType().getParameterTypes().length == 1) { + + Expression firstOp = subExpressions[0].simplifyStringBuffer(); + if (firstOp == null) + return null; + + subExpressions[1] = subExpressions[1].simplifyString(); + + if (isEmptyString(firstOp) + && subExpressions[1].getType().isOfType(Type.tString)) + return subExpressions[1]; + + if (firstOp instanceof StringAddOperator + && isEmptyString(((Operator) firstOp) + .getSubExpressions()[0])) + firstOp = ((Operator) firstOp).getSubExpressions()[1]; + + Expression secondOp = subExpressions[1]; + Type[] paramTypes = new Type[] { + getClassType(), secondOp.getType().getCanonic() + }; + if (needsCast(1, paramTypes)) { + Type castType = methodType.getParameterTypes()[0]; + Operator castOp = new ConvertOperator(castType, castType); + castOp.addOperand(secondOp); + secondOp = castOp; + } + Operator result = new StringAddOperator(); + result.addOperand(secondOp); + result.addOperand(firstOp); + return result; + } + } + return null; + } + + public boolean isEmptyString(Expression expr) { + return (expr instanceof ConstOperator + && ((ConstOperator) expr).getValue() == ""); + } + + public ConstOperator emptyString() { + return new ConstOperator(classPath, ""); + } + + public Expression simplifyString() { + if (getMethodName().equals("toString") + && !isStatic() + && (getClassType().equals(Type.tStringBuffer) + || getClassType().equals(Type.tStringBuilder)) + && subExpressions.length == 1) { + Expression simple = subExpressions[0].simplifyStringBuffer(); + if (simple != null) + return simple; + } + else if (getMethodName().equals("valueOf") + && isStatic() + && getClassType().equals(Type.tString) + && subExpressions.length == 1) { + + if (subExpressions[0].getType().isOfType(Type.tString)) + return subExpressions[0]; + + Operator op = new StringAddOperator(); + op.addOperand(subExpressions[0]); + op.addOperand(emptyString()); + } + /* The pizza way (pizza is the compiler of kaffe) */ + else if (getMethodName().equals("concat") + && !isStatic() + && getClassType().equals(Type.tString)) { + + Expression result = new StringAddOperator(); + Expression right = subExpressions[1].simplify(); + if (right instanceof StringAddOperator) { + Operator op = (Operator) right; + if (op.subExpressions != null + && isEmptyString(op.subExpressions[0])) + right = op.subExpressions[1]; + } + result.addOperand(right); + result.addOperand(subExpressions[0].simplify()); + } + else if ((Options.options & Options.OPTION_DECRYPT) != 0 + && isThis() && isStatic() + && methodType.getParameterTypes().length == 1 + && methodType.getParameterTypes()[0].equals(Type.tString) + && methodType.getReturnType().equals(Type.tString)) { + + Expression expr = subExpressions[0].simplifyString(); + if (expr instanceof ConstOperator) { + expr = deobfuscateString((ConstOperator)expr); + if (expr != null) + return expr; + } + } + return this; + } + + public Expression simplifyAccess() { + if (getMethodAnalyzer() != null) { + SyntheticAnalyzer synth = getMethodAnalyzer().getSynthetic(); + if (synth != null) { + int unifyParam = synth.getUnifyParam(); + Expression op = null; + switch (synth.getKind()) { + case SyntheticAnalyzer.ACCESSGETFIELD: + op = new GetFieldOperator(methodAnalyzer, false, + synth.getReference()); + break; + case SyntheticAnalyzer.ACCESSGETSTATIC: + op = new GetFieldOperator(methodAnalyzer, true, + synth.getReference()); + break; + case SyntheticAnalyzer.ACCESSPUTFIELD: + case SyntheticAnalyzer.ACCESSDUPPUTFIELD: + op = new StoreInstruction + (new PutFieldOperator(methodAnalyzer, false, + synth.getReference())); + if (synth.getKind() == synth.ACCESSDUPPUTFIELD) + ((StoreInstruction) op).makeNonVoid(); + break; + case SyntheticAnalyzer.ACCESSPUTSTATIC: + case SyntheticAnalyzer.ACCESSDUPPUTSTATIC: + op = new StoreInstruction + (new PutFieldOperator(methodAnalyzer, true, + synth.getReference())); + if (synth.getKind() == synth.ACCESSDUPPUTSTATIC) + ((StoreInstruction) op).makeNonVoid(); + break; + case SyntheticAnalyzer.ACCESSMETHOD: + op = new InvokeOperator(methodAnalyzer, ACCESSSPECIAL, + synth.getReference()); + break; + case SyntheticAnalyzer.ACCESSSTATICMETHOD: + op = new InvokeOperator(methodAnalyzer, STATIC, + synth.getReference()); + break; + case SyntheticAnalyzer.ACCESSCONSTRUCTOR: + if (subExpressions[unifyParam] instanceof ConstOperator + && ((ConstOperator) + subExpressions[unifyParam]).getValue() == null) { + op = new InvokeOperator(methodAnalyzer, CONSTRUCTOR, + synth.getReference()); + } + break; + } + + if (op != null) { + if (subExpressions != null) { + for (int i=subExpressions.length; i-- > 0; ) { + if (synth.getKind() + == SyntheticAnalyzer.ACCESSCONSTRUCTOR + && i == unifyParam) + // skip the null param. + continue; + op = op.addOperand(subExpressions[i]); + if (subExpressions[i].getFreeOperandCount() > 0) + break; + } + } + return op; + } + } + } + return null; + } + + private MethodInfo[] loadMethods(ClassInfo clazz) { + int howMuch = (clazz.getName().startsWith(callerPackage) + && (clazz.getName().lastIndexOf('.') + < callerPackage.length())) + ? ClassInfo.DECLARATIONS : ClassInfo.PUBLICDECLARATIONS; + try { + clazz.load(howMuch); + } catch (IOException ex) { + GlobalOptions.err.println("Warning: Can't find methods of " + +clazz+" to detect overload conflicts"); + clazz.guess(howMuch); + } + return clazz.getMethods(); + } + + public boolean needsCast(int param, Type[] paramTypes) { + Type realClassType; + if (methodFlag == STATIC) + realClassType = classType; + else if (param == 0) { + if (paramTypes[0] instanceof NullType) + return true; + if (!(paramTypes[0] instanceof ClassInfoType + && classType instanceof ClassInfoType)) + return false; + + ClassInfo clazz = ((ClassInfoType) classType).getClassInfo(); + ClassInfo parClazz + = ((ClassInfoType) paramTypes[0]).getClassInfo(); + MethodInfo method = getMethodInfo(); + if (method == null) + /* This is a NoSuchMethodError */ + return false; + if (Modifier.isPrivate(method.getModifiers())) + return parClazz != clazz; + else if ((method.getModifiers() + & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) { + /* Method is protected. We need a cast if parClazz is in + * other package than clazz. + */ + int lastDot = clazz.getName().lastIndexOf('.'); + if (lastDot != parClazz.getName().lastIndexOf('.') + || !(parClazz.getName() + .startsWith(clazz.getName().substring(0,lastDot+1)))) + return true; + } + return false; + } else { + realClassType = paramTypes[0]; + } + + if (!(realClassType instanceof ClassInfoType)) { + /* Arrays don't have overloaded methods, all okay */ + return false; + } + ClassInfo clazz = ((ClassInfoType) realClassType).getClassInfo(); + int offset = skippedArgs; + + Type[] myParamTypes = methodType.getParameterTypes(); + if (myParamTypes[param-offset].equals(paramTypes[param])) { + /* Type at param is okay. */ + return false; + } + /* Now check if there is a conflicting method in this class or + * a superclass. */ + while (clazz != null) { + MethodInfo[] methods = loadMethods(clazz); + next_method: + for (int i=0; i< methods.length; i++) { + if (!methods[i].getName().equals(methodName)) + /* method name doesn't match*/ + continue next_method; + + Type[] otherParamTypes + = Type.tMethod(classPath, methods[i].getType()) + .getParameterTypes(); + if (otherParamTypes.length != myParamTypes.length) { + /* parameter count doesn't match*/ + continue next_method; + } + + if (myParamTypes[param-offset].isOfType + (Type.tSubType(otherParamTypes[param-offset]))) { + /* cast to myParamTypes cannot resolve any conflicts. */ + continue next_method; + } + for (int p = offset; p < paramTypes.length; p++) { + if (!paramTypes[p] + .isOfType(Type.tSubType(otherParamTypes[p-offset]))){ + /* No conflict here */ + continue next_method; + } + } + /* There is a conflict that can be resolved by a cast. */ + return true; + } + clazz = clazz.getSuperclass(); + } + return false; + } + + public Expression simplify() { + Expression expr = simplifyAccess(); + if (expr != null) + return expr.simplify(); + expr = simplifyString(); + if (expr != this) + return expr.simplify(); + return super.simplify(); + } + + + /** + * We add the named method scoped classes to the declarables, and + * only fillDeclarables on the parameters we will print. + */ + public void fillDeclarables(Collection used) { + ClassInfo clazz = classInfo; + ClassAnalyzer clazzAna = methodAnalyzer.getClassAnalyzer(clazz); + + if ((Options.options & Options.OPTION_ANON) != 0 + && clazz != null + && clazz.isMethodScoped() && clazz.getClassName() != null) { + + if (clazzAna != null && clazzAna.getParent() == methodAnalyzer) { + /* This is a named method scope class, declare it. + * But first declare all method scoped classes, + * that are used inside; order does matter. + */ + clazzAna.fillDeclarables(used); + used.add(clazzAna); + } + } + + if (!isConstructor() || isStatic()) { + super.fillDeclarables(used); + return; + } + int arg = 1; + int length = subExpressions.length; + boolean jikesAnonymousInner = false; + boolean implicitOuterClass = false; + + if ((Options.options & Options.OPTION_ANON) != 0 + && clazzAna != null && clazz.isMethodScoped()) { + + OuterValues ov = clazzAna.getOuterValues(); + arg += ov.getCount(); + jikesAnonymousInner = ov.isJikesAnonymousInner(); + implicitOuterClass = ov.isImplicitOuterClass(); + + for (int i=1; i< arg; i++) { + Expression expr = subExpressions[i]; + if (expr instanceof CheckNullOperator) { + CheckNullOperator cno = (CheckNullOperator) expr; + expr = cno.subExpressions[0]; + } + expr.fillDeclarables(used); + } + + if (clazz.getClassName() == null) { + /* This is an anonymous class */ + ClassInfo superClazz = clazz.getSuperclass(); + ClassInfo[] interfaces = clazz.getInterfaces(); + if (interfaces.length == 1 + && (superClazz == null + || superClazz.getName() == "java.lang.Object")) { + clazz = interfaces[0]; + } else { + clazz = superClazz; + } + } + } + + if ((~Options.options & (Options.OPTION_INNER + | Options.OPTION_CONTRAFO)) == 0 + && clazz.getOuterClass() != null + && !Modifier.isStatic(clazz.getModifiers()) + && !implicitOuterClass + && arg < length) { + + Expression outerExpr = jikesAnonymousInner + ? subExpressions[--length] + : subExpressions[arg++]; + if (outerExpr instanceof CheckNullOperator) { + CheckNullOperator cno = (CheckNullOperator) outerExpr; + outerExpr = cno.subExpressions[0]; + } + outerExpr.fillDeclarables(used); + } + for (int i=arg; i < length; i++) + subExpressions[i].fillDeclarables(used); + } + + /** + * We add the named method scoped classes to the declarables, and + * only fillDeclarables on the parameters we will print. + */ + public void makeDeclaration(Set done) { + super.makeDeclaration(done); + + if (isConstructor() && !isStatic() + && (Options.options & Options.OPTION_ANON) != 0) { + ClassInfo clazz = classInfo; + if (clazz != null + && clazz.isMethodScoped() && clazz.getClassName() == null) { + ClassAnalyzer clazzAna + = methodAnalyzer.getClassAnalyzer(clazz); + + /* call makeDeclaration on the anonymous class, since + * _we_ will declare the anonymous class. + */ + if (clazzAna != null) + clazzAna.makeDeclaration(done); + } + } + } + + public int getBreakPenalty() { + return 5; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + + /* This is the most complex dumpExpression method you will + * ever find. Most of the complexity is due to handling of + * constructors, especially for inner and method scoped + * classes. + */ + + /* All subExpressions from arg to length are arguments. We + * assume a normal virtual method here, otherwise arg and + * length will change later. + */ + int arg = 1; + int length = subExpressions.length; + + /* Tells if this is an anonymous constructor */ + boolean anonymousNew = false; + /* The ClassInfo for the method we call, null for an array */ + ClassInfo clazz = classInfo; + /* The ClassAnalyzer for the method we call (only for inner + * classes), null if we didn't analyze the class. */ + ClassAnalyzer clazzAna = null; + + /* The canonic types of the arguments. Used to see if we need + * casts. + */ + Type[] paramTypes = new Type[subExpressions.length]; + for (int i=0; i< subExpressions.length; i++) + paramTypes[i] = subExpressions[i].getType().getCanonic(); + + /* Now write the method call. This is the complex part: + * we have to differentiate all kinds of method calls: static, + * virtual, constructor, anonymous constructors, super calls etc. + */ + writer.startOp(writer.NO_PAREN, 0); + switch (methodFlag) { + case CONSTRUCTOR: { + + boolean qualifiedNew = false; + boolean jikesAnonymousInner = false; + boolean implicitOuterClass = false; + + /* clazz != null, since an array doesn't have a constructor */ + + clazzAna = methodAnalyzer.getClassAnalyzer(clazz); + if ((Options.options & Options.OPTION_ANON) != 0 + && clazzAna != null && clazz.isMethodScoped()) { + + /* This is a known method scoped class, skip the outerValues */ + OuterValues ov = clazzAna.getOuterValues(); + arg += ov.getCount(); + jikesAnonymousInner = ov.isJikesAnonymousInner(); + implicitOuterClass = ov.isImplicitOuterClass(); + + if (clazz.getClassName() == null) { + /* This is an anonymous class */ + anonymousNew = true; + ClassInfo superClazz = clazz.getSuperclass(); + ClassInfo[] interfaces = clazz.getInterfaces(); + if (interfaces.length == 1 + && superClazz.getName() == "java.lang.Object") { + clazz = interfaces[0]; + } else { + if (interfaces.length > 0) { + writer.print("too many supers in ANONYMOUS "); + } + clazz = superClazz; + } + if (jikesAnonymousInner && clazz.isMethodScoped()) { + Expression thisExpr = subExpressions[--length]; + if (thisExpr instanceof CheckNullOperator) { + CheckNullOperator cno + = (CheckNullOperator) thisExpr; + thisExpr = cno.subExpressions[0]; + } + if (!(thisExpr instanceof ThisOperator) + || (((ThisOperator) thisExpr).getClassInfo() + != methodAnalyzer.getClazz())) + writer.print("ILLEGAL ANON CONSTR"); + } + } + } + + /* Check if this is an inner class. It will dump the outer + * class expression, except if its default. + */ + if (clazz.getOuterClass() != null + && !Modifier.isStatic(clazz.getModifiers()) + && (~Options.options & + (Options.OPTION_INNER + | Options.OPTION_CONTRAFO)) == 0) { + + if (implicitOuterClass) { + /* Outer class is "this" and is not given + * explicitly. No need to print something. + */ + } else if (arg < length) { + Expression outerExpr = jikesAnonymousInner + ? subExpressions[--length] + : subExpressions[arg++]; + if (outerExpr instanceof CheckNullOperator) { + CheckNullOperator cno = (CheckNullOperator) outerExpr; + outerExpr = cno.subExpressions[0]; + } else { + /* We used to complain about MISSING CHECKNULL + * here except for ThisOperators. But javac + * v8 doesn't seem to create CHECKNULL ops. + */ + } + + if (outerExpr instanceof ThisOperator) { + Scope scope = writer.getScope + (((ThisOperator) outerExpr).getClassInfo(), + Scope.CLASSSCOPE); + if (writer.conflicts(clazz.getClassName(), + scope, Scope.CLASSNAME)) { + qualifiedNew = true; + outerExpr.dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } + } else { + qualifiedNew = true; + if (outerExpr.getType().getCanonic() + instanceof NullType) { + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 1); + writer.print("("); + writer.printType(Type.tClass(clazz)); + writer.print(") "); + writer.breakOp(); + outerExpr.dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + } else + outerExpr.dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } + } else + writer.print("MISSING OUTEREXPR "); + } + + if (subExpressions[0] instanceof NewOperator + && paramTypes[0].equals(classType)) { + writer.print("new "); + if (qualifiedNew) + writer.print(clazz.getClassName()); + else + writer.printType(Type.tClass(clazz)); + break; + } + + if (subExpressions[0] instanceof ThisOperator + && (((ThisOperator)subExpressions[0]).getClassInfo() + == methodAnalyzer.getClazz())) { + if (isThis()) + writer.print("this"); + else + writer.print("super"); + break; + } + + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 0); + writer.print("(UNCONSTRUCTED)"); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + writer.breakOp(); + writer.print("."); + writer.printType(Type.tClass(clazz)); + break; + } + case SPECIAL: + if (isSuperOrThis() + && subExpressions[0] instanceof ThisOperator + && (((ThisOperator)subExpressions[0]).getClassInfo() + == methodAnalyzer.getClazz())) { + if (!isThis()) { + /* We don't have to check if this is the real super + * class, as long as ACC_SUPER is set. + */ + writer.print("super"); + ClassInfo superClazz = classInfo.getSuperclass(); + paramTypes[0] = superClazz == null + ? Type.tObject : Type.tClass(superClazz); + writer.breakOp(); + writer.print("."); + } else { + /* XXX check if this is a private method. */ + } + } else if (isThis()) { + /* XXX check if this is a private method. */ + if (needsCast(0, paramTypes)){ + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 1); + writer.print("("); + writer.printType(classType); + writer.print(") "); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + paramTypes[0] = classType; + } else + subExpressions[0].dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } else { + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 0); + writer.print("(NON VIRTUAL "); + writer.printType(classType); + writer.print(") "); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + writer.breakOp(); + writer.print("."); + } + writer.print(methodName); + break; + + case ACCESSSPECIAL: + /* Calling a private method in another class. (This is + * allowed for inner classes.) + */ + if (paramTypes[0].equals(classType)) + subExpressions[0].dumpExpression(writer, 950); + else { + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 0); + writer.print("("); + writer.printType(classType); + writer.print(") "); + writer.breakOp(); + paramTypes[0] = classType; + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + } + writer.breakOp(); + writer.print("."); + writer.print(methodName); + break; + + case STATIC: { + arg = 0; + Scope scope = writer.getScope(classInfo, + Scope.CLASSSCOPE); + if (scope == null + ||writer.conflicts(methodName, scope, Scope.METHODNAME)) { + writer.printType(classType); + writer.breakOp(); + writer.print("."); + } + writer.print(methodName); + break; + } + + case VIRTUAL: + if (subExpressions[0] instanceof ThisOperator) { + ThisOperator thisOp = (ThisOperator) subExpressions[0]; + Scope scope = writer.getScope(thisOp.getClassInfo(), + Scope.CLASSSCOPE); + if (writer.conflicts(methodName, scope, Scope.METHODNAME) + || (/* This method is inherited from the parent of + * an outer class, or it is inherited from the + * parent of this class and there is a conflicting + * method in some outer class. + */ + getMethodAnalyzer() == null + && (!isThis() || + writer.conflicts(methodName, null, + Scope.NOSUPERMETHODNAME)))) { + thisOp.dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } + } else { + if (needsCast(0, paramTypes)){ + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 1); + writer.print("("); + writer.printType(classType); + writer.print(") "); + writer.breakOp(); + subExpressions[0].dumpExpression(writer, 700); + writer.endOp(); + writer.print(")"); + paramTypes[0] = classType; + } else + subExpressions[0].dumpExpression(writer, 950); + writer.breakOp(); + writer.print("."); + } + writer.print(methodName); + } + writer.endOp(); + + /* Now the easier part: Dump the arguments from arg to length. + * We still need to check for casts though. + */ + writer.breakOp(); + writer.printOptionalSpace(); + writer.print("("); + writer.startOp(writer.EXPL_PAREN, 0); + boolean first = true; + int offset = skippedArgs; + while (arg < length) { + if (!first) { + writer.print(", "); + writer.breakOp(); + } else + first = false; + int priority = 0; + if (needsCast(arg, paramTypes)) { + Type castType = methodType.getParameterTypes()[arg-offset]; + writer.startOp(writer.IMPL_PAREN, 1); + writer.print("("); + writer.printType(castType); + writer.print(") "); + writer.breakOp(); + paramTypes[arg] = castType; + priority = 700; + } + subExpressions[arg++].dumpExpression(writer, priority); + if (priority == 700) + writer.endOp(); + } + writer.endOp(); + writer.print(")"); + + /* If this was an anonymous constructor call, we must now + * dump the source code of the anonymous class. + */ + if (anonymousNew) { + Object state = writer.saveOps(); + writer.openBraceClass(); + writer.tab(); + clazzAna.dumpBlock(writer); + writer.untab(); + writer.closeBraceClass(); + writer.restoreOps(state); + } + } + + public boolean opEquals(Operator o) { + if (o instanceof InvokeOperator) { + InvokeOperator i = (InvokeOperator)o; + return classType.equals(i.classType) + && methodName.equals(i.methodName) + && methodType.equals(i.methodType) + && methodFlag == i.methodFlag; + } + return false; + } +} diff --git a/jode/src/net/sf/jode/expr/LValueExpression.java b/jode/src/net/sf/jode/expr/LValueExpression.java new file mode 100644 index 0000000..4f9c130 --- /dev/null +++ b/jode/src/net/sf/jode/expr/LValueExpression.java @@ -0,0 +1,26 @@ +/* LValueExpression Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.GlobalOptions; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public interface LValueExpression extends MatchableOperator { +} diff --git a/jode/src/net/sf/jode/expr/LocalLoadOperator.java b/jode/src/net/sf/jode/expr/LocalLoadOperator.java new file mode 100644 index 0000000..ba6f9c8 --- /dev/null +++ b/jode/src/net/sf/jode/expr/LocalLoadOperator.java @@ -0,0 +1,64 @@ +/* LocalLoadOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.ClassAnalyzer; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class LocalLoadOperator extends LocalVarOperator { + + MethodAnalyzer methodAnalyzer; + + public LocalLoadOperator(Type type, MethodAnalyzer methodAnalyzer, + LocalInfo local) { + super(type, local); + this.methodAnalyzer = methodAnalyzer; + } + + public boolean isRead() { + return true; + } + + public boolean isWrite() { + return false; + } + + public boolean isConstant() { + return false; + } + + public void setMethodAnalyzer(MethodAnalyzer ma) { + methodAnalyzer = ma; + } + + public boolean opEquals(Operator o) { + return (o instanceof LocalLoadOperator && + ((LocalLoadOperator) o).local.getSlot() == local.getSlot()); + } + + public Expression simplify() { + if (local.getExpression() != null) + return local.getExpression().simplify(); + return super.simplify(); + } +} diff --git a/jode/src/net/sf/jode/expr/LocalStoreOperator.java b/jode/src/net/sf/jode/expr/LocalStoreOperator.java new file mode 100644 index 0000000..2f15a00 --- /dev/null +++ b/jode/src/net/sf/jode/expr/LocalStoreOperator.java @@ -0,0 +1,48 @@ +/* LocalStoreOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class LocalStoreOperator extends LocalVarOperator + implements LValueExpression { + + public LocalStoreOperator(Type lvalueType, LocalInfo local) { + super(lvalueType, local); + } + + public boolean isRead() { + /* if it is part of a += operator, this is a read. */ + return parent != null && parent.getOperatorIndex() != ASSIGN_OP; + } + + public boolean isWrite() { + return true; + } + + public boolean matches(Operator loadop) { + return loadop instanceof LocalLoadOperator && + ((LocalLoadOperator)loadop).getLocalInfo().getSlot() + == local.getSlot(); + } +} + diff --git a/jode/src/net/sf/jode/expr/LocalVarOperator.java b/jode/src/net/sf/jode/expr/LocalVarOperator.java new file mode 100644 index 0000000..3e04adf --- /dev/null +++ b/jode/src/net/sf/jode/expr/LocalVarOperator.java @@ -0,0 +1,83 @@ +/* LocalVarOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; + +///#def COLLECTIONS java.util +import java.util.Collection; +///#enddef + +public abstract class LocalVarOperator extends Operator { + LocalInfo local; + + public LocalVarOperator(Type lvalueType, LocalInfo local) { + super(lvalueType); + this.local = local; + local.setOperator(this); + initOperands(0); + } + + public abstract boolean isRead(); + public abstract boolean isWrite(); + + public void updateSubTypes() { + if (parent != null + && (GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) + GlobalOptions.err.println("local type changed in: "+parent); + local.setType(type); + } + + public void updateType() { + updateParentType(local.getType()); + } + + public void fillInGenSet(Collection in, Collection gen) { + if (isRead() && in != null) + in.add(getLocalInfo()); + if (gen != null) + gen.add(getLocalInfo()); + super.fillInGenSet(in, gen); + } + + public void fillDeclarables(Collection used) { + used.add(local); + super.fillDeclarables(used); + } + + public LocalInfo getLocalInfo() { + return local.getLocalInfo(); + } + + public void setLocalInfo(LocalInfo newLocal) { + local = newLocal; + updateType(); + } + + public int getPriority() { + return 1000; + } + + public void dumpExpression(TabbedPrintWriter writer) { + writer.print(local.getName()); + } +} diff --git a/jode/src/net/sf/jode/expr/MatchableOperator.java b/jode/src/net/sf/jode/expr/MatchableOperator.java new file mode 100644 index 0000000..d7990e3 --- /dev/null +++ b/jode/src/net/sf/jode/expr/MatchableOperator.java @@ -0,0 +1,27 @@ +/* MatchableOperator Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; + +public interface MatchableOperator { + /** + * Checks if the loadOp is combinable with this operator. + */ + public boolean matches(Operator loadOp); +} diff --git a/jode/src/net/sf/jode/expr/MonitorEnterOperator.java b/jode/src/net/sf/jode/expr/MonitorEnterOperator.java new file mode 100644 index 0000000..f9d5939 --- /dev/null +++ b/jode/src/net/sf/jode/expr/MonitorEnterOperator.java @@ -0,0 +1,46 @@ +/* MonitorEnterOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class MonitorEnterOperator extends Operator { + public MonitorEnterOperator() { + super(Type.tVoid, 0); + initOperands(1); + } + + public int getPriority() { + return 700; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tUObject); + } + + public void updateType() { + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("MONITORENTER "); + subExpressions[0].dumpExpression(writer, 700); + } +} diff --git a/jode/src/net/sf/jode/expr/MonitorExitOperator.java b/jode/src/net/sf/jode/expr/MonitorExitOperator.java new file mode 100644 index 0000000..2f5f1d8 --- /dev/null +++ b/jode/src/net/sf/jode/expr/MonitorExitOperator.java @@ -0,0 +1,46 @@ +/* MonitorExitOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class MonitorExitOperator extends Operator { + public MonitorExitOperator() { + super(Type.tVoid, 0); + initOperands(1); + } + + public int getPriority() { + return 700; + } + + public void updateSubTypes() { + subExpressions[0].setType(Type.tUObject); + } + + public void updateType() { + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("MONITOREXIT "); + subExpressions[0].dumpExpression(writer, 700); + } +} diff --git a/jode/src/net/sf/jode/expr/NewArrayOperator.java b/jode/src/net/sf/jode/expr/NewArrayOperator.java new file mode 100644 index 0000000..281aa89 --- /dev/null +++ b/jode/src/net/sf/jode/expr/NewArrayOperator.java @@ -0,0 +1,67 @@ +/* NewArrayOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.type.ArrayType; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class NewArrayOperator extends Operator { + String baseTypeString; + + public NewArrayOperator(Type arrayType, int dimensions) { + super(arrayType, 0); + initOperands(dimensions); + } + + public int getDimensions() { + return subExpressions.length; + } + + public int getPriority() { + return 900; + } + + public void updateSubTypes() { + for (int i=0; i< subExpressions.length; i++) + subExpressions[i].setType(Type.tUInt); + } + + public void updateType() { + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + Type flat = type.getCanonic(); + int depth = 0; + while (flat instanceof ArrayType) { + flat = ((ArrayType)flat).getElementType(); + depth++; + } + writer.print("new "); + writer.printType(flat.getHint()); + for (int i=0; i< depth; i++) { + writer.breakOp(); + writer.print("["); + if (i < subExpressions.length) + subExpressions[i].dumpExpression(writer, 0); + writer.print("]"); + } + } +} diff --git a/jode/src/net/sf/jode/expr/NewOperator.java b/jode/src/net/sf/jode/expr/NewOperator.java new file mode 100644 index 0000000..560727c --- /dev/null +++ b/jode/src/net/sf/jode/expr/NewOperator.java @@ -0,0 +1,38 @@ +/* NewOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public class NewOperator extends NoArgOperator { + public NewOperator(Type type) { + super(type); + } + + public int getPriority() { + return 950; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("new "); + writer.printType(type); + } +} diff --git a/jode/src/net/sf/jode/expr/NoArgOperator.java b/jode/src/net/sf/jode/expr/NoArgOperator.java new file mode 100644 index 0000000..dafd222 --- /dev/null +++ b/jode/src/net/sf/jode/expr/NoArgOperator.java @@ -0,0 +1,39 @@ +/* NoArgOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +public abstract class NoArgOperator extends Operator { + + public NoArgOperator(Type type, int operator) { + super(type, operator); + initOperands(0); + } + + public NoArgOperator(Type type) { + this(type, 0); + } + + public void updateType() { + } + public void updateSubTypes() { + } +} diff --git a/jode/src/net/sf/jode/expr/NopOperator.java b/jode/src/net/sf/jode/expr/NopOperator.java new file mode 100644 index 0000000..494e03a --- /dev/null +++ b/jode/src/net/sf/jode/expr/NopOperator.java @@ -0,0 +1,73 @@ +/* NopOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; + +/** + * A NopOperator takes one arguments and returns it again. It is used + * as placeholder when the real operator is not yet known (e.g. in + * SwitchBlock, but also in every other Operator). + * + * A Nop operator doesn't have subExpressions; getSubExpressions() simply + * returns zero. + * + * @author Jochen Hoenicke */ +public class NopOperator extends Expression { + public NopOperator(Type type) { + super(type); + } + + public int getFreeOperandCount() { + return 1; + } + + public int getPriority() { + return 1000; + } + + public void updateSubTypes() { + } + public void updateType() { + } + + public Expression addOperand(Expression op) { + op.setType(type); + op.parent = parent; + return op; + } + + public boolean isConstant() { + return false; + } + + public boolean equals(Object o) { + return (o instanceof NopOperator); + } + + public Expression simplify() { + return this; + } + + public void dumpExpression(TabbedPrintWriter writer) + throws java.io.IOException { + writer.print("POP"); + } +} diff --git a/jode/src/net/sf/jode/expr/Operator.java b/jode/src/net/sf/jode/expr/Operator.java new file mode 100644 index 0000000..da39581 --- /dev/null +++ b/jode/src/net/sf/jode/expr/Operator.java @@ -0,0 +1,331 @@ +/* Operator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.expr; +import net.sf.jode.type.Type; +import net.sf.jode.GlobalOptions; +import net.sf.jode.decompiler.TabbedPrintWriter; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Set; +///#enddef + +public abstract class Operator extends Expression { + /* Don't reorder these constants unless you know what you are doing! */ + public final static int ADD_OP = 1; + public final static int SUB_OP = 2; + public final static int SHIFT_OP = 6; + public final static int AND_OP = 9; + public final static int ASSIGN_OP = 12; + public final static int OPASSIGN_OP= 12; + public final static int INC_OP = 24; /* must be even! */ + public final static int DEC_OP = 25; + public final static int COMPARE_OP = 26; /* must be even! */ + public final static int EQUALS_OP = 26; + public final static int NOTEQUALS_OP = 27; + public final static int LESS_OP = 28; + public final static int GREATEREQ_OP = 29; + public final static int GREATER_OP = 30; + public final static int LESSEQ_OP = 31; + public final static int LOG_AND_OP = 32; /* must be even! */ + public final static int LOG_OR_OP = 33; + public final static int LOG_NOT_OP = 34; + public final static int NEG_OP = 36; + static String opString[] = { + "", " + ", " - ", " * ", " / ", " % ", + " << ", " >> ", " >>> ", " & ", " | ", " ^ ", + " = ", " += ", " -= ", " *= ", " /= ", " %= ", + " <<= ", " >>= ", " >>>= ", " &= ", " |= ", " ^= ", + "++", "--", + " == "," != "," < "," >= "," > ", " <= ", " && ", " || ", + "!", "~", "-" + }; + + protected int operatorIndex; + private int operandcount; + + Expression[] subExpressions; + + public Operator(Type type) { + this(type,0); + } + + public Operator(Type type, int op) { + super(type); + this.operatorIndex = op; + if (type == null) + throw new InternalError("type == null"); + } + + public void initOperands(int opcount) { + operandcount = opcount; + subExpressions = new Expression[opcount]; + for (int i=0; i < opcount; i++) { + subExpressions[i] = new NopOperator(Type.tUnknown); + subExpressions[i].parent = this; + } + updateSubTypes(); + } + + public int getFreeOperandCount() { + return operandcount; + } + + /** + * Tells if this is an operator, that doesn't have any + * subExpression, yet. + */ + public boolean isFreeOperator() { + return subExpressions.length == 0 + || subExpressions[subExpressions.length-1] instanceof NopOperator; + } + + /** + * Tells if this is an operator, that doesn't have any + * subExpression, yet, and that expects opcount operands + */ + public boolean isFreeOperator(int opcount) { + return subExpressions.length == opcount + && (opcount == 0 + || subExpressions[opcount-1] instanceof NopOperator); + } + + public Expression addOperand(Expression op) { + for (int i= subExpressions.length; i-- > 0;) { + int opcount = subExpressions[i].getFreeOperandCount(); + if (opcount > 0) { + subExpressions[i] = subExpressions[i].addOperand(op); + operandcount + += subExpressions[i].getFreeOperandCount() - opcount; + updateType(); + return this; + } + } + throw new InternalError("addOperand called, but no operand needed"); + } + + public Operator getOperator() { + return this; + } + + public Expression[] getSubExpressions() { + return subExpressions; + } + + public void setSubExpressions(int i, Expression expr) { + int diff = expr.getFreeOperandCount() + - subExpressions[i].getFreeOperandCount(); + subExpressions[i] = expr; + expr.parent = this; + for (Operator ce = this; ce != null; ce = (Operator) ce.parent) + ce.operandcount += diff; + updateType(); + } + + public int getOperatorIndex() { + return operatorIndex; + } + public void setOperatorIndex(int op) { + operatorIndex = op; + } + + public String getOperatorString() { + return opString[operatorIndex]; + } + + public boolean opEquals(Operator o) { + return this == o; + } + + public Expression simplify() { + for (int i=0; i< subExpressions.length; i++) { + subExpressions[i] = subExpressions[i].simplify(); + subExpressions[i].parent = this; + } + return this; + } + + public void fillInGenSet(Collection in, Collection gen) { + for (int i=0; i< subExpressions.length; i++) + subExpressions[i].fillInGenSet(in,gen); + } + + public void fillDeclarables(Collection used) { + for (int i=0; i< subExpressions.length; i++) + subExpressions[i].fillDeclarables(used); + } + + public void makeDeclaration(Set done) { + for (int i=0; i< subExpressions.length; i++) + subExpressions[i].makeDeclaration(done); + } + + + /** + * Checks if the value of the given expression can change, due to + * side effects in this expression. If this returns false, the + * expression can safely be moved behind the current expresion. + * @param expr the expression that should not change. + */ + public boolean hasSideEffects(Expression expr) { + if (expr instanceof MatchableOperator + && expr.containsConflictingLoad((MatchableOperator)expr)) + return true; + for (int i=0; i < subExpressions.length; i++) { + if (subExpressions[i].hasSideEffects(expr)) + return true; + } + return false; + } + + /** + * Checks if this expression contains a conflicting load, that + * matches the given CombineableOperator. The sub expressions are + * not checked. + * @param op The combineable operator. + * @return if this expression contains a matching load. + */ + public boolean containsConflictingLoad(MatchableOperator op) { + if (op.matches(this)) + return true; + for (int i=0; i < subExpressions.length; i++) { + if (subExpressions[i].containsConflictingLoad(op)) + return true; + } + return false; + } + + /** + * Checks if this expression contains a matching load, that matches the + * given Expression. + * @param comb The store expression. + * @return true, iff this expression contains a matching load. + * @exception ClassCastException, if e.getOperator + * is not a CombineableOperator. + */ + public boolean containsMatchingLoad(CombineableOperator comb) { + Operator combOp = (Operator) comb; + if (comb.getLValue().matches(this)) { + if (subsEquals((Operator) comb.getLValue())) + return true; + } + for (int i=0; i < subExpressions.length; i++) { + if (subExpressions[i].containsMatchingLoad(comb)) + return true; + } + return false; + } + + /** + * Checks if the given Expression (which must be a CombineableOperator) + * can be combined into this expression. + * @param e The store expression, must be of type void. + * @return 1, if it can, 0, if no match was found and -1, if a + * conflict was found. You may wish to check for >0. + * @exception ClassCastException, if e.getOperator + * is not a CombineableOperator. + */ + public int canCombine(CombineableOperator combOp) { +// GlobalOptions.err.println("Try to combine "+e+" into "+this); + if (combOp.getLValue() instanceof LocalStoreOperator + && ((Operator)combOp).getFreeOperandCount() == 0) { + // Special case for locals created on inlining methods, which may + // combine everywhere, as long as there are no side effects. + + for (int i=0; i < subExpressions.length; i++) { + int result = subExpressions[i].canCombine(combOp); + if (result != 0) + return result; + if (subExpressions[i].hasSideEffects((Expression)combOp)) + return -1; + } + } + + if (combOp.lvalueMatches(this)) + return subsEquals((Operator)combOp) ? 1 : -1; + if (subExpressions.length > 0) + return subExpressions[0].canCombine(combOp); + return 0; + } + + /** + * Combines the given Expression (which should be a StoreInstruction) + * into this expression. You must only call this if + * canCombine returns the value 1. + * @param e The store expression. + * @return The combined expression. + * @exception ClassCastException, if e.getOperator + * is not a CombineableOperator. + */ + public Expression combine(CombineableOperator comb) { + Operator combOp = (Operator) comb; + if (comb.lvalueMatches(this)) { + /* We already checked in canCombine that subExpressions match */ + comb.makeNonVoid(); + combOp.parent = parent; + return combOp; + } + for (int i=0; i < subExpressions.length; i++) { + Expression combined = subExpressions[i].combine(comb); + if (combined != null) { + subExpressions[i] = combined; + updateType(); + return this; + } + } + return null; + } + + public boolean subsEquals(Operator other) { + if (this == other) + return true; + if (other.subExpressions == null) + return (subExpressions == null); + + if (subExpressions.length != other.subExpressions.length) + return false; + + for (int i=0; i 0) + GlobalOptions.err.print('s'); + + synBlock.isEntered = true; + synBlock.moveDefinitions(last.outer,last); + last.replace(last.outer); + return true; + } + + /** + * This combines the initial expression describing the object + * into a synchronized statement. + */ + public static boolean combineObject(SynchronizedBlock synBlock, + StructuredBlock last) { + + /* Is there another expression? */ + if (!(last.outer instanceof SequentialBlock)) + return false; + SequentialBlock sequBlock = (SequentialBlock) last.outer; + + if (!(sequBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + InstructionBlock ib = (InstructionBlock) sequBlock.subBlocks[0]; + + if (!(ib.getInstruction() instanceof StoreInstruction)) + return false; + StoreInstruction assign = (StoreInstruction) ib.getInstruction(); + + if (!(assign.getLValue() instanceof LocalStoreOperator)) + return false; + LocalStoreOperator lvalue = (LocalStoreOperator) assign.getLValue(); + + if (lvalue.getLocalInfo() != synBlock.local.getLocalInfo() + || assign.getSubExpressions()[1] == null) + return false; + + synBlock.object = assign.getSubExpressions()[1]; + synBlock.moveDefinitions(last.outer,last); + last.replace(last.outer); + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/ConditionalBlock.java b/jode/src/net/sf/jode/flow/ConditionalBlock.java new file mode 100644 index 0000000..839b9be --- /dev/null +++ b/jode/src/net/sf/jode/flow/ConditionalBlock.java @@ -0,0 +1,140 @@ +/* ConditionalBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.LocalVarOperator; + +/** + * An ConditionalBlock is the structured block representing an if + * instruction. The else part may be null. + */ +public class ConditionalBlock extends InstructionContainer { + /** + * The loads that are on the stack before instr is executed. + */ + VariableStack stack; + + EmptyBlock trueBlock; + + public void checkConsistent() { + super.checkConsistent(); + if (!(trueBlock instanceof EmptyBlock)) + throw new InternalError("Inconsistency"); + } + + /** + * Creates a new if conditional block. + */ + public ConditionalBlock(Expression cond) { + super(cond); + /* cond is a CompareBinary or CompareUnary operator, so no + * check for LocalVarOperator (for condJump) is needed here. + */ + trueBlock = new EmptyBlock(); + trueBlock.outer = this; + } + + /* The implementation of getNext[Flow]Block is the standard + * implementation + */ + + /** + * Sets the successors of this structured block. This should be only + * called once, by FlowBlock.setSuccessors(). + */ + public void setSuccessors(Jump[] jumps) { + if (jumps.length != 2) { + /* A conditional block can only exactly two jumps. */ + throw new IllegalArgumentException("Not exactly two jumps."); + } + trueBlock.setJump(jumps[0]); + setJump(jumps[1]); + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return new StructuredBlock[] { trueBlock }; + } + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + throw new InternalError("replaceSubBlock on ConditionalBlock"); + } + + /** + * This does take the instr into account and modifies stack + * accordingly. It then calls super.mapStackToLocal. + * @param stack the stack before the instruction is called + * @return stack the stack afterwards. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + VariableStack newStack; + int params = instr.getFreeOperandCount(); + if (params > 0) { + this.stack = stack.peek(params); + newStack = stack.pop(params); + } else + newStack = stack; + + trueBlock.jump.stackMap = newStack; + if (jump != null) { + jump.stackMap = newStack; + return null; + } + return newStack; + } + + public void removePush() { + if (stack != null) + instr = stack.mergeIntoExpression(instr); + trueBlock.removePush(); + } + + /** + * Print the source code for this structured block. + */ + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + writer.print("IF ("); + instr.dumpExpression(writer.EXPL_PAREN, writer); + writer.println(")"); + writer.tab(); + trueBlock.dumpSource(writer); + writer.untab(); + } + + public boolean doTransformations() { + StructuredBlock last = flowBlock.lastModified; + return super.doTransformations() + || CombineIfGotoExpressions.transform(this, last) + || CreateIfThenElseOperator.createFunny(this, last); + } +} + diff --git a/jode/src/net/sf/jode/flow/ContinueBlock.java b/jode/src/net/sf/jode/flow/ContinueBlock.java new file mode 100644 index 0000000..726b173 --- /dev/null +++ b/jode/src/net/sf/jode/flow/ContinueBlock.java @@ -0,0 +1,102 @@ +/* ContinueBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; + +/** + * + */ +public class ContinueBlock extends StructuredBlock { + LoopBlock continuesBlock; + String continueLabel; + + public ContinueBlock(LoopBlock continuesBlock, boolean needsLabel) { + this.continuesBlock = continuesBlock; + if (needsLabel) + continueLabel = continuesBlock.getLabel(); + else + continueLabel = null; + } + + public void checkConsistent() { + super.checkConsistent(); + StructuredBlock sb = outer; + while (sb != continuesBlock) { + if (sb == null) + throw new RuntimeException("Inconsistency"); + sb = sb.outer; + } + } + + /** + * Tells if this block is empty and only changes control flow. + */ + public boolean isEmpty() { + return true; + } + + /** + * Returns the block where the control will normally flow to, when + * this block is finished (not ignoring the jump after this block). + */ + public StructuredBlock getNextBlock() { + /* This continues to continuesBlock. */ + return continuesBlock; + } + + /** + * Returns the flow block where the control will normally flow to, + * when this block is finished (not ignoring the jump after this + * block). + * @return null, if the control flows into a non empty structured + * block or if this is the outermost block. + */ + public FlowBlock getNextFlowBlock() { + return null; + } + + /** + * This is called after the analysis is completely done. It + * will remove all PUSH/stack_i expressions, (if the bytecode + * is correct). + * @param stack the stackmap at begin of the block + * @return null if the bytecode isn't correct and stack mapping + * didn't worked, otherwise the stack after the block has executed. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + continuesBlock.mergeContinueStack(stack); + return null; + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + writer.println("continue"+ + (continueLabel == null ? "" : " "+continueLabel) + ";"); + } + + public boolean needsBraces() { + return false; + } + + public boolean jumpMayBeChanged() { + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateAssignExpression.java b/jode/src/net/sf/jode/flow/CreateAssignExpression.java new file mode 100644 index 0000000..8dec5a4 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateAssignExpression.java @@ -0,0 +1,216 @@ +/* CreateAssignExpression Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.expr.*; +import net.sf.jode.type.Type; + +public class CreateAssignExpression { + + public static boolean transform(InstructionContainer ic, + StructuredBlock last) { + if (!(last.outer instanceof SequentialBlock) + || !(ic.getInstruction() instanceof StoreInstruction) + || !(ic.getInstruction().isVoid())) + return false; + + return (createAssignOp(ic, last) || createAssignExpression(ic, last)); + } + + public static boolean createAssignOp(InstructionContainer ic, + StructuredBlock last) { + + /* Situation: + * + * (push loadstoreOps) <- not checked + * sequBlock: + * dup (may be missing for static / local variables) + * opBlock: + * PUSH (optional narrow)((optional wide) load(stack) * RHS) + * (optional dup_x) + * store(POP) + * + * We transform it to: + * (push loadstoreOps) + * rightHandSide + * (optional dup_x) + * store(stack) *= (stack) + * + * If the optional dup is present the store*= becomes non void. */ + + SequentialBlock opBlock = (SequentialBlock) last.outer; + StoreInstruction store = (StoreInstruction) ic.getInstruction(); + if (!store.isFreeOperator() || store.isOpAssign()) + return false; + Expression lvalue = store.getSubExpressions()[0]; + int lvalueCount = lvalue.getFreeOperandCount(); + + boolean isAssignOp = false; + if (opBlock.subBlocks[0] instanceof SpecialBlock) { + SpecialBlock dup = (SpecialBlock) opBlock.subBlocks[0]; + if (dup.type != SpecialBlock.DUP + || dup.depth != lvalueCount + || dup.count != lvalue.getType().stackSize() + || !(opBlock.outer instanceof SequentialBlock)) + return false; + opBlock = (SequentialBlock) opBlock.outer; + isAssignOp = true; + } + + if (!(opBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + + InstructionBlock ib = (InstructionBlock) opBlock.subBlocks[0]; + if (!(ib.getInstruction() instanceof Operator)) + return false; + Operator expr = (Operator) ib.getInstruction(); + if (expr.getFreeOperandCount() != lvalueCount) + return false; + Type rvalueType = expr.getType(); + + SpecialBlock dup = null; + + if (lvalueCount > 0) { + if (!(opBlock.outer instanceof SequentialBlock) + || !(opBlock.outer.getSubBlocks()[0] instanceof SpecialBlock)) + return false; + + SequentialBlock sequBlock = (SequentialBlock) opBlock.outer; + dup = (SpecialBlock) sequBlock.subBlocks[0]; + + if (dup.type != SpecialBlock.DUP + || dup.depth != 0 || dup.count != lvalueCount) + return false; + } + int opIndex; + Expression rightHandSide; + + if (expr instanceof ConvertOperator + && expr.getSubExpressions()[0] instanceof Operator + && expr.getType().isOfType(lvalue.getType())) { + + /* This gets tricky. We need to allow something like + * s = (short) (int) ((double) s / 0.1); + */ + expr = (Operator) expr.getSubExpressions()[0]; + while (expr instanceof ConvertOperator + && expr.getSubExpressions()[0] instanceof Operator) + expr = (Operator) expr.getSubExpressions()[0]; + } + if (expr instanceof BinaryOperator) { + opIndex = expr.getOperatorIndex(); + if (opIndex < expr.ADD_OP || opIndex >= expr.ASSIGN_OP) + return false; + + if (!(expr.getSubExpressions()[0] instanceof Operator)) + return false; + + Operator loadExpr = (Operator) expr.getSubExpressions()[0]; + while (loadExpr instanceof ConvertOperator + && loadExpr.getSubExpressions()[0] instanceof Operator) + loadExpr = (Operator) loadExpr.getSubExpressions()[0]; + + if (!store.lvalueMatches((Operator) loadExpr) + || !(loadExpr.isFreeOperator(lvalueCount))) + return false; + + if (lvalue instanceof LocalStoreOperator) + ((LocalLoadOperator)loadExpr).getLocalInfo().combineWith + (((LocalStoreOperator)lvalue).getLocalInfo()); + + rightHandSide = expr.getSubExpressions()[1]; + } else { + /* For String += the situation is more complex. + * what is marked as load(stack) * rightHandSide above is + * really (after simplification): + * + * PUSH ((load(stack) + right) + Hand) + Side + */ + Expression simple = expr.simplifyString(); + rightHandSide = simple; + /* Now search for the leftmost operand ... */ + Operator lastExpr = null; + Operator parent = null; + while (simple instanceof StringAddOperator) { + parent = lastExpr; + lastExpr = (Operator) simple; + simple = lastExpr.getSubExpressions()[0]; + } + + /* ... check it ... */ + if (lastExpr == null + || !(simple instanceof Operator) + || !store.lvalueMatches((Operator) simple) + || !(((Operator) simple).isFreeOperator(lvalueCount))) + return false; + + if (lvalue instanceof LocalStoreOperator) + ((LocalLoadOperator)simple).getLocalInfo().combineWith + (((LocalStoreOperator)lvalue).getLocalInfo()); + + /* ... and remove it. */ + if (parent != null) { + parent.setSubExpressions(0, lastExpr.getSubExpressions()[1]); + } else { + rightHandSide = lastExpr.getSubExpressions()[1]; + } + + opIndex = Operator.ADD_OP; + } + + if (dup != null) + dup.removeBlock(); + ib.setInstruction(rightHandSide); + + lvalue.setType(rvalueType); + store.makeOpAssign(store.OPASSIGN_OP + opIndex); + + if (isAssignOp) + store.makeNonVoid(); + last.replace(opBlock.subBlocks[1]); + return true; + } + + public static boolean createAssignExpression(InstructionContainer ic, + StructuredBlock last) { + /* Situation: + * sequBlock: + * dup_X(lvalue_count) + * store(POP) = POP + */ + SequentialBlock sequBlock = (SequentialBlock) last.outer; + StoreInstruction store = (StoreInstruction) ic.getInstruction(); + + if (sequBlock.subBlocks[0] instanceof SpecialBlock + && store.isFreeOperator()) { + + Expression lvalue = store.getSubExpressions()[0]; + SpecialBlock dup = (SpecialBlock) sequBlock.subBlocks[0]; + if (dup.type != SpecialBlock.DUP + || dup.depth != lvalue.getFreeOperandCount() + || dup.count != lvalue.getType().stackSize()) + return false; + + dup.removeBlock(); + store.makeNonVoid(); + return true; + } + return false; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateCheckNull.java b/jode/src/net/sf/jode/flow/CreateCheckNull.java new file mode 100644 index 0000000..c26a736 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateCheckNull.java @@ -0,0 +1,123 @@ +/* CreateCheckNull Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.expr.*; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; + +public class CreateCheckNull { + + /* Situation: + * + * javac: + * DUP + * POP.getClass(); + * + * jikes: + * DUP + * if (POP == null) + * throw null; + */ + + /** + * Transforms the code + *
+     *   DUP
+     *   POP.getClass()
+     * 
+ * to a CheckNullOperator. This is what javac generates when it + * calls ".new" on an operand. + */ + public static boolean transformJavac(InstructionContainer ic, + StructuredBlock last) { + if (!(last.outer instanceof SequentialBlock) + || !(ic.getInstruction() instanceof Operator) + || !(last.outer.getSubBlocks()[0] instanceof SpecialBlock)) + return false; + + SpecialBlock dup = (SpecialBlock) last.outer.getSubBlocks()[0]; + if (dup.type != SpecialBlock.DUP + || dup.count != 1 || dup.depth != 0) + return false; + + Operator ce = (Operator) ic.getInstruction(); + + if (!(ce.getOperator() instanceof PopOperator) + || !(ce.getSubExpressions()[0] instanceof InvokeOperator)) + return false; + + InvokeOperator getClassCall + = (InvokeOperator) ce.getSubExpressions()[0]; + if (!getClassCall.getMethodName().equals("getClass") + || !(getClassCall.getMethodType().toString() + .equals("()Ljava/lang/Class;"))) + return false; + + LocalInfo li = new LocalInfo(); + ic.setInstruction(new CheckNullOperator(Type.tUObject, li)); + last.replace(last.outer); + return true; + } + + /** + * Transforms the code + *
+     *   DUP
+     *   if (POP == null) {
+     *       throw null
+     *   }
+     * 
+ * to a CheckNullOperator. This is what jikes generates when it + * calls ".new" on an operand. + */ + public static boolean transformJikes(IfThenElseBlock ifBlock, + StructuredBlock last) { + if (!(last.outer instanceof SequentialBlock) + || !(last.outer.getSubBlocks()[0] instanceof SpecialBlock) + || ifBlock.elseBlock != null + || !(ifBlock.thenBlock instanceof ThrowBlock)) + return false; + + SpecialBlock dup = (SpecialBlock) last.outer.getSubBlocks()[0]; + if (dup.type != SpecialBlock.DUP + || dup.count != 1 || dup.depth != 0) + return false; + + if (!(ifBlock.cond instanceof CompareUnaryOperator)) + return false; + CompareUnaryOperator cmpOp = (CompareUnaryOperator) ifBlock.cond; + if (cmpOp.getOperatorIndex() != Operator.EQUALS_OP + || !(cmpOp.getCompareType().isOfType(Type.tUObject))) + return false; + + LocalInfo li = new LocalInfo(); + InstructionContainer ic = + new InstructionBlock(new CheckNullOperator(Type.tUObject, li)); + ic.moveJump(ifBlock.jump); + if (last == ifBlock) { + ic.replace(last.outer); + last = ic; + } else { + ic.replace(ifBlock); + last.replace(last.outer); + } + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateClassField.java b/jode/src/net/sf/jode/flow/CreateClassField.java new file mode 100644 index 0000000..35a2825 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateClassField.java @@ -0,0 +1,89 @@ +/* CreateClassField Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.expr.*; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; + +public class CreateClassField { + + + public static boolean transform(IfThenElseBlock ifBlock, + StructuredBlock last) { + // convert + // if (class$classname == null) + // class$classname = class$("java.lang.Object"); + // to + // if (classname.class == null) { + // } + if (!(ifBlock.cond instanceof CompareUnaryOperator) + || !(((Operator)ifBlock.cond) + .getOperatorIndex() == Operator.EQUALS_OP) + || !(ifBlock.thenBlock instanceof InstructionBlock) + || ifBlock.elseBlock != null) + return false; + + if (ifBlock.thenBlock.jump != null + && (ifBlock.jump == null + || (ifBlock.jump.destination + != ifBlock.thenBlock.jump.destination))) + return false; + + CompareUnaryOperator cmp = (CompareUnaryOperator) ifBlock.cond; + Expression instr = + ((InstructionBlock)ifBlock.thenBlock).getInstruction(); + if (!(cmp.getSubExpressions()[0] instanceof GetFieldOperator) + || !(instr instanceof StoreInstruction)) + return false; + + StoreInstruction store = (StoreInstruction) instr; + if (!(store.getLValue() instanceof PutFieldOperator)) + return false; + PutFieldOperator put = (PutFieldOperator) store.getLValue(); + if (put.getField() == null + || !put.matches((GetFieldOperator)cmp.getSubExpressions()[0]) + || !(store.getSubExpressions()[1] instanceof InvokeOperator)) + return false; + + InvokeOperator invoke = (InvokeOperator) store.getSubExpressions()[1]; + if (!invoke.isGetClass()) + return false; + + Expression param = invoke.getSubExpressions()[0]; + if (param instanceof ConstOperator + && ((ConstOperator)param).getValue() instanceof String) { + String clazz = (String) ((ConstOperator)param).getValue(); + if (put.getField().setClassConstant(clazz)) { + ClassPath cp = invoke.getClassPath(); + cmp.setSubExpressions + (0, new ClassFieldOperator(put.getType(), + clazz.charAt(0) == '[' + ? Type.tType(cp, clazz) + : Type.tClass(cp, clazz))); + EmptyBlock empty = new EmptyBlock(); + empty.moveJump(ifBlock.thenBlock.jump); + ifBlock.setThenBlock(empty); + return true; + } + } + return false; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateConstantArray.java b/jode/src/net/sf/jode/flow/CreateConstantArray.java new file mode 100644 index 0000000..82aa644 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateConstantArray.java @@ -0,0 +1,116 @@ +/* CreateConstantArray Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; +import net.sf.jode.expr.*; +import net.sf.jode.type.Type; + +public class CreateConstantArray { + + public static boolean transform(InstructionContainer ic, + StructuredBlock last) { + /* Situation: + * PUSH new Array[] // or a constant array operator. + * DUP // duplicate array reference + * POP[index] = value + * ... + */ + if (last.outer instanceof SequentialBlock) { + + SequentialBlock sequBlock = (SequentialBlock) last.outer; + + if (!(ic.getInstruction() instanceof StoreInstruction) + || ic.getInstruction().getFreeOperandCount() != 1 + || !(sequBlock.subBlocks[0] instanceof SpecialBlock) + || !(sequBlock.outer instanceof SequentialBlock)) + return false; + + StoreInstruction store = (StoreInstruction) ic.getInstruction(); + + if (!(store.getLValue() instanceof ArrayStoreOperator)) + return false; + + ArrayStoreOperator lvalue = (ArrayStoreOperator) store.getLValue(); + + if (!(lvalue.getSubExpressions()[0] instanceof NopOperator) + || !(lvalue.getSubExpressions()[1] instanceof ConstOperator)) + return false; + + Expression expr = store.getSubExpressions()[1]; + ConstOperator indexOp + = (ConstOperator) lvalue.getSubExpressions()[1]; + SpecialBlock dup = (SpecialBlock) sequBlock.subBlocks[0]; + sequBlock = (SequentialBlock) sequBlock.outer; + + if (dup.type != SpecialBlock.DUP + || dup.depth != 0 || dup.count != 1 + || !(indexOp.getValue() instanceof Integer) + || !(sequBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + + int index = ((Integer) indexOp.getValue()).intValue(); + InstructionBlock ib = (InstructionBlock)sequBlock.subBlocks[0]; + + if (ib.getInstruction() instanceof NewArrayOperator) { + /* This is the first element */ + NewArrayOperator newArray = + (NewArrayOperator) ib.getInstruction(); + if (newArray.getDimensions() != 1 + || !(newArray.getSubExpressions()[0] + instanceof ConstOperator)) + return false; + + ConstOperator countop = + (ConstOperator) newArray.getSubExpressions()[0]; + if (!(countop.getValue() instanceof Integer)) + return false; + + int arraylength = ((Integer) countop.getValue()).intValue(); + if (arraylength <= index) + return false; + + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.print('a'); + + ConstantArrayOperator cao + = new ConstantArrayOperator(newArray.getType(), + arraylength); + cao.setValue(index, expr); + ic.setInstruction(cao); + ic.moveDefinitions(sequBlock, last); + last.replace(sequBlock); + return true; + + } else if (ib.getInstruction() instanceof ConstantArrayOperator) { + ConstantArrayOperator cao + = (ConstantArrayOperator) ib.getInstruction(); + if (cao.setValue(index, expr)) { + /* adding Element succeeded */ + ic.setInstruction(cao); + ic.moveDefinitions(sequBlock, last); + last.replace(sequBlock); + return true; + } + } + + } + return false; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateExpression.java b/jode/src/net/sf/jode/flow/CreateExpression.java new file mode 100644 index 0000000..2f41cd0 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateExpression.java @@ -0,0 +1,119 @@ +/* CreateExpression Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; +import net.sf.jode.expr.*; + +/** + * This transformation creates expressions. It transforms + *
+ *  Sequ[expr_1, Sequ[expr_2, ..., Sequ[expr_n, op] ...]] 
+ * 
+ * to + *
+ *  expr(op, [ expr_1, ..., expr_n ])
+ * 
+ */ +public class CreateExpression { + + /** + * This does the transformation. + * @return true if flow block was simplified. + */ + public static boolean transform(InstructionContainer ic, + StructuredBlock last) { + int params = ic.getInstruction().getFreeOperandCount(); + if (params == 0) + return false; + + if (!(last.outer instanceof SequentialBlock)) + return false; + SequentialBlock sequBlock = (SequentialBlock)last.outer; + + /* First check if Expression can be created, but do nothing yet. + */ + Expression lastExpression = ic.getInstruction(); + while (true) { + if (!(sequBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + + Expression expr = + ((InstructionBlock) sequBlock.subBlocks[0]).getInstruction(); + + if (!expr.isVoid()) + break; + + if (expr.getFreeOperandCount() > 0 + || !(expr instanceof CombineableOperator) + || lastExpression.canCombine((CombineableOperator) expr) <= 0) + return false; + + /* Hmm, we should really set lastExpression to + * lastExpression.combine(expr), but that may change the + * expressions :-( XXX + * + * We do a conservative approach and check if there are + * no possible side effects with the skipped expressions. + * Theoretically we would only have to check expressions, + * that are combined at an earlier point. + */ + SequentialBlock block = sequBlock; + while (block != last.outer) { + block = (SequentialBlock) block.subBlocks[1]; + if (((InstructionBlock)block.subBlocks[0]) + .getInstruction().hasSideEffects(expr)) + return false; + } + + + if (!(sequBlock.outer instanceof SequentialBlock)) + return false; + sequBlock = (SequentialBlock) sequBlock.outer; + } + + /* Now, do the combination. Everything must succeed now. + */ + sequBlock = (SequentialBlock) last.outer; + lastExpression = ic.getInstruction(); + while (true) { + + Expression expr = + ((InstructionBlock) sequBlock.subBlocks[0]).getInstruction(); + + if (!expr.isVoid()) { + lastExpression = lastExpression.addOperand(expr); + break; + } + + lastExpression = lastExpression.combine + ((CombineableOperator) expr); + sequBlock = (SequentialBlock)sequBlock.outer; + } + + if (GlobalOptions.verboseLevel > 0 + && lastExpression.getFreeOperandCount() == 0) + GlobalOptions.err.print('x'); + + ic.setInstruction(lastExpression); + ic.moveDefinitions(sequBlock, last); + last.replace(sequBlock); + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateForInitializer.java b/jode/src/net/sf/jode/flow/CreateForInitializer.java new file mode 100644 index 0000000..3e30147 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateForInitializer.java @@ -0,0 +1,55 @@ +/* CreateForInitializer Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; +import net.sf.jode.expr.*; + +public class CreateForInitializer { + + /** + * This combines an variable initializer into a for statement + * @param forBlock the for block + * @param last the lastModified of the flow block. + */ + public static boolean transform(LoopBlock forBlock, StructuredBlock last) { + + if (!(last.outer instanceof SequentialBlock)) + return false; + + SequentialBlock sequBlock = (SequentialBlock) last.outer; + + if (!(sequBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + + InstructionBlock init = (InstructionBlock) sequBlock.subBlocks[0]; + + if (!init.getInstruction().isVoid() + || !(init.getInstruction() instanceof CombineableOperator) + || !forBlock.conditionMatches((CombineableOperator) + init.getInstruction())) + return false; + + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.print('f'); + + forBlock.setInit((InstructionBlock) sequBlock.subBlocks[0]); + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateIfThenElseOperator.java b/jode/src/net/sf/jode/flow/CreateIfThenElseOperator.java new file mode 100644 index 0000000..a2ca73b --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateIfThenElseOperator.java @@ -0,0 +1,236 @@ +/* CreateIfThenElseOperator Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.expr.*; + +import java.util.Enumeration; +import java.util.Vector; + +public class CreateIfThenElseOperator { + + /** + * This handles the body of createFunny. There are three cases: + * + *
+     * --------
+     *  IF (c2)
+     *    GOTO trueDest            ->   PUSH c2
+     *  PUSH false
+     * --------
+     *  PUSH bool                  ->   PUSH bool
+     * --------
+     *  if (c2)
+     *    (handled recursively)    ->   PUSH (c2 ? expr1 : expr2)
+     *  else
+     *    (handled recursively)
+     * --------
+     * 
+ */ + private static boolean createFunnyHelper(FlowBlock trueDest, + FlowBlock falseDest, + StructuredBlock block) { + + if (block instanceof InstructionBlock + && !((InstructionBlock)block).getInstruction().isVoid()) + return true; + + if (block instanceof IfThenElseBlock) { + IfThenElseBlock ifBlock = (IfThenElseBlock) block; + Expression expr1, expr2; + if (ifBlock.elseBlock == null) + return false; + + /* Next is a non-shortcut "or": simplify both blocks! */ + if (!createFunnyHelper(trueDest, falseDest, ifBlock.thenBlock) + | !createFunnyHelper(trueDest, falseDest, ifBlock.elseBlock)) + return false; + + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.print('?'); + + Expression iteo = new IfThenElseOperator(Type.tBoolean) + .addOperand(((InstructionBlock) ifBlock.elseBlock) + .getInstruction()) + .addOperand(((InstructionBlock) ifBlock.thenBlock) + .getInstruction()) + .addOperand(ifBlock.cond); + ((InstructionBlock)ifBlock.thenBlock).setInstruction(iteo); + + ifBlock.thenBlock.moveDefinitions(ifBlock, null); + ifBlock.thenBlock.replace(ifBlock); + return true; + } + + if (block instanceof SequentialBlock + && block.getSubBlocks()[0] instanceof ConditionalBlock + && block.getSubBlocks()[1] instanceof InstructionBlock) { + + ConditionalBlock condBlock = + (ConditionalBlock) block.getSubBlocks()[0]; + InstructionBlock pushBlock = + (InstructionBlock) block.getSubBlocks()[1]; + + if (!(pushBlock.getInstruction() instanceof ConstOperator)) + return false; + + ConstOperator constOp = + (ConstOperator) pushBlock.getInstruction(); + + if (condBlock.trueBlock.jump.destination == trueDest + && constOp.getValue().equals(new Integer(0))) { + + Expression cond = condBlock.getInstruction(); + condBlock.flowBlock.removeSuccessor(condBlock.trueBlock.jump); + condBlock.trueBlock.removeJump(); + + pushBlock.setInstruction(cond); + pushBlock.moveDefinitions(block, null); + pushBlock.replace(block); + return true; + } + } + return false; + } + + /** + * This handles the more complicated form of the ?-:-operator used + * in a conditional block. The simplest case is: + *
+     *   if (cond)
+     *       PUSH e1
+     *   else {
+     *       IF (c2)
+     *           GOTO flow_2_
+     *       PUSH false
+     *   }
+     * ->IF (stack_0 == 0)
+     *     GOTO flow_1_
+     *   GOTO flow_2_
+     * 
+ * is transformed to + *
+     *   push cond ? e1 : c2
+     * ->IF (stack_0 == 0)
+     *     GOTO flow_1_
+     *   GOTO flow_2_
+     * 
+ * + * The -> points to the lastModified block. Note + * that both the if and the then part may contain this + * condition+push0-block. There may be even stack if-then-else-blocks + * for very complicated nested ?-:-Operators.

+ * + * Also note that the produced code is suboptimal: The push-0 could + * sometimes be better replaced with a correct jump. + */ + public static boolean createFunny(ConditionalBlock cb, + StructuredBlock last) { + + if (cb.jump == null + || !(cb.getInstruction() instanceof CompareUnaryOperator) + || !(last.outer instanceof SequentialBlock) + || !(last.outer.getSubBlocks()[0] instanceof IfThenElseBlock)) + return false; + + CompareUnaryOperator compare = + (CompareUnaryOperator) cb.getInstruction(); + + FlowBlock trueDestination; + FlowBlock falseDestination; + if (compare.getOperatorIndex() == compare.EQUALS_OP) { + trueDestination = cb.jump.destination; + falseDestination = cb.trueBlock.jump.destination; + } else if (compare.getOperatorIndex() == compare.NOTEQUALS_OP) { + falseDestination = cb.jump.destination; + trueDestination = cb.trueBlock.jump.destination; + } else + return false; + + Expression[] e = new Expression[3]; + IfThenElseBlock ifBlock; + + SequentialBlock sequBlock = (SequentialBlock) last.outer; + return createFunnyHelper(trueDestination, falseDestination, + sequBlock.subBlocks[0]); + } + + /** + * This handles the normal form of the ?-:-operator: + *

+     *   if (cond)
+     *       push e1
+     *       GOTO flow_1_
+     * ->push e2
+     *   GOTO flow_2
+     * 
+ * is transformed to + *
+     * ->push cond ? e1 : e2
+     * 
+ * The -> points to the lastModified block. + */ + public static boolean create(InstructionContainer ic, + StructuredBlock last) { + Expression cond, thenExpr, elseExpr; + InstructionBlock thenBlock; + if (ic.jump == null + || !(last.outer instanceof SequentialBlock)) + return false; + SequentialBlock sequBlock = (SequentialBlock)last.outer; + if (!(sequBlock.subBlocks[0] instanceof IfThenElseBlock)) + return false; + + IfThenElseBlock ifBlock = (IfThenElseBlock)sequBlock.subBlocks[0]; + if (!(ifBlock.thenBlock instanceof InstructionBlock) + || ifBlock.thenBlock.jump == null + || ifBlock.thenBlock.jump.destination != ic.jump.destination + || ifBlock.elseBlock != null) + return false; + + thenBlock = (InstructionBlock) ifBlock.thenBlock; + + thenExpr = thenBlock.getInstruction(); + if (thenExpr.isVoid() || thenExpr.getFreeOperandCount() > 0) + return false; + elseExpr = ic.getInstruction(); + if (elseExpr.isVoid() || elseExpr.getFreeOperandCount() > 0) + return false; + cond = ifBlock.cond; + + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.print('?'); + + thenBlock.flowBlock.removeSuccessor(thenBlock.jump); + thenBlock.removeJump(); + + IfThenElseOperator iteo = new IfThenElseOperator + (Type.tSuperType(thenExpr.getType()) + .intersection(Type.tSuperType(elseExpr.getType()))); + iteo.addOperand(elseExpr); + iteo.addOperand(thenExpr); + iteo.addOperand(cond); + ic.setInstruction(iteo); + ic.moveDefinitions(last.outer, last); + last.replace(last.outer); + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/CreateNewConstructor.java b/jode/src/net/sf/jode/flow/CreateNewConstructor.java new file mode 100644 index 0000000..5dc8853 --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreateNewConstructor.java @@ -0,0 +1,241 @@ +/* CreateNewConstructor Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.expr.*; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.type.Type; + +public class CreateNewConstructor { + + public static boolean transform(InstructionContainer ic, + StructuredBlock last) { + return transformNormal(ic, last) || transformJikesString(ic, last); + } + + static boolean transformJikesString(InstructionContainer ic, + StructuredBlock last) { + /* special Situation for Jikes String +=: + * + * PUSH new StringBuffer() + * SWAP + * PUSH POP.append(POP) + * + * We transform it to the javac String +=: + * + * PUSH new StringBuffer(String.valueOf(POP)) + */ + if (!(last.outer instanceof SequentialBlock) + || !(ic.getInstruction() instanceof InvokeOperator)) + return false; + + InvokeOperator appendCall = (InvokeOperator) ic.getInstruction(); + if (!appendCall.getClassType().equals(Type.tStringBuffer) + || !appendCall.isFreeOperator(2) + || appendCall.isStatic() + || !appendCall.getMethodName().equals("append") + || appendCall.getMethodType().getParameterTypes().length != 1) + return false; + + SequentialBlock sequBlock = (SequentialBlock) last.outer; + if (!(sequBlock.outer instanceof SequentialBlock) + || !(sequBlock.subBlocks[0] instanceof SpecialBlock)) + return false; + + SpecialBlock swapBlock = (SpecialBlock) sequBlock.subBlocks[0]; + sequBlock = (SequentialBlock) sequBlock.outer; + if (swapBlock.type != SpecialBlock.SWAP + || !(sequBlock.subBlocks[0] instanceof InstructionBlock) + || !(sequBlock.outer instanceof SequentialBlock)) + return false; + + InstructionBlock ib = (InstructionBlock) sequBlock.subBlocks[0]; + sequBlock = (SequentialBlock) sequBlock.outer; + if (!(ib.getInstruction() instanceof InvokeOperator) + || !(sequBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + + InvokeOperator constr = (InvokeOperator) ib.getInstruction(); + ib = (InstructionBlock) sequBlock.subBlocks[0]; + + if (!constr.isConstructor() + || !constr.getClassType().equals(Type.tStringBuffer) + || constr.isVoid() + || constr.getMethodType().getParameterTypes().length != 0) + return false; + + /* Okay everything checked. */ + MethodAnalyzer methodAna = ib.flowBlock.method; + Expression expr = ib.getInstruction(); + Type appendType = appendCall.getMethodType().getParameterTypes()[0]; + if (!appendType.equals(Type.tString)) { + InvokeOperator valueOf = new InvokeOperator + (methodAna, InvokeOperator.STATIC, + Reference.getReference("Ljava/lang/String;", "valueOf", + "(" + appendType.getTypeSignature() + + ")Ljava/lang/String;")); + expr = valueOf.addOperand(expr); + } + InvokeOperator newConstr = new InvokeOperator + (methodAna, InvokeOperator.CONSTRUCTOR, + Reference.getReference("Ljava/lang/StringBuffer;", "", + "(Ljava/lang/String;)V")); + newConstr.makeNonVoid(); + newConstr.setSubExpressions(0, constr.getSubExpressions()[0]); + newConstr.setSubExpressions(1, expr); + ic.setInstruction(newConstr); + last.replace(sequBlock); + return true; + } + + static boolean transformNormal(InstructionContainer ic, + StructuredBlock last) { + /* Situation (normal): + * + * new + * (optional DUP) + * (void resolved expressions) + * stack_n.(resolved expressions) + * + * transform it to + * + * (void resolved expressions) + * (optional PUSH) new ((optional: stack_n), + * resolved expressions) + * + * special situation for string1 += string2: + * + * new + * (optional DUP) + * (void resolved expressions) + * PUSH load_ops + * DUP_X2/1 <= 2 if above DUP is present + * stack_n.(stack_n, resolved expressions) + * + * transform it to + * + * (void resolved expressions) + * PUSH load_ops + * DUP <= remove the depth + * (optional PUSH) new (stack_n, resolved expressions) + */ + + if (!(last.outer instanceof SequentialBlock)) + return false; + if (!(ic.getInstruction() instanceof InvokeOperator)) + return false; + InvokeOperator constrCall = (InvokeOperator) ic.getInstruction(); + if (!constrCall.isConstructor() || !constrCall.isVoid()) + return false; + + /* The rest should probably succeed */ + + SpecialBlock optDupX2 = null; + SequentialBlock sequBlock = (SequentialBlock) last.outer; + Expression[] subs = constrCall.getSubExpressions(); + int opcount = constrCall.getFreeOperandCount(); + if (subs != null) { + if (!(subs[0] instanceof NopOperator)) + return false; + if (constrCall.getFreeOperandCount() > 1) { + if (!(sequBlock.outer instanceof SequentialBlock) + || !(sequBlock.subBlocks[0] instanceof SpecialBlock)) + return false; + optDupX2 = (SpecialBlock) sequBlock.subBlocks[0]; + sequBlock = (SequentialBlock) sequBlock.outer; + if (optDupX2.type != SpecialBlock.DUP + || optDupX2.depth == 0) + return false; + int count = optDupX2.count; + do { + if (!(sequBlock.outer instanceof SequentialBlock) + || !(sequBlock.subBlocks[0] + instanceof InstructionBlock)) + return false; + Expression expr = + ((InstructionBlock) + sequBlock.subBlocks[0]).getInstruction(); + sequBlock = (SequentialBlock) sequBlock.outer; + + if (expr.isVoid()) + continue; + count -= expr.getType().stackSize(); + opcount--; + } while (count > 0 && opcount > 1); + if (count != 0) + return false; + } + } + if (opcount != 1) + return false; + + while (sequBlock.subBlocks[0] instanceof InstructionBlock + && sequBlock.outer instanceof SequentialBlock) { + Expression expr + = ((InstructionBlock)sequBlock.subBlocks[0]).getInstruction(); + if (!expr.isVoid() || expr.getFreeOperandCount() > 0) + break; + sequBlock = (SequentialBlock) sequBlock.outer; + } + + SpecialBlock dup = null; + if (sequBlock.outer instanceof SequentialBlock + && sequBlock.subBlocks[0] instanceof SpecialBlock) { + + dup = (SpecialBlock) sequBlock.subBlocks[0]; + if (dup.type != SpecialBlock.DUP + || dup.count != 1 || dup.depth != 0) + return false; + sequBlock = (SequentialBlock)sequBlock.outer; + if (optDupX2 != null && optDupX2.depth != 2) + return false; + } else if (optDupX2 != null && optDupX2.depth != 1) + return false; + + if (!(sequBlock.subBlocks[0] instanceof InstructionBlock)) + return false; + InstructionBlock block = (InstructionBlock) sequBlock.subBlocks[0]; + if (!(block.getInstruction() instanceof NewOperator)) + return false; + + NewOperator op = (NewOperator) block.getInstruction(); + if (constrCall.getClassType() != op.getType()) + return false; + + block.removeBlock(); + if (dup != null) + dup.removeBlock(); + if (optDupX2 != null) + optDupX2.depth = 0; + + constrCall.setSubExpressions(0, op); + if (dup != null) + constrCall.makeNonVoid(); +// Expression newExpr = new ConstructorOperator +// (constrCall, dup == null); + +// if (subs != null) { +// for (int i=subs.length; i-- > 1; ) +// newExpr = newExpr.addOperand(subs[i]); +// } +// ic.setInstruction(newExpr); + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/CreatePrePostIncExpression.java b/jode/src/net/sf/jode/flow/CreatePrePostIncExpression.java new file mode 100644 index 0000000..5d76c9c --- /dev/null +++ b/jode/src/net/sf/jode/flow/CreatePrePostIncExpression.java @@ -0,0 +1,183 @@ +/* CreatePrePostIncExpression Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.expr.*; +import net.sf.jode.type.Type; + +public class CreatePrePostIncExpression { + + public static boolean transform(InstructionContainer ic, + StructuredBlock last) + { + return (createLocalPrePostInc(ic, last) || createPostInc(ic, last)); + } + + public static boolean createLocalPrePostInc(InstructionContainer ic, + StructuredBlock last) { + /* Situations: + * + * PUSH local_x -> PUSH local_x++ + * IINC local_x, +/-1 + * + * IINC local_x, +/-1 + * PUSH local_x -> PUSH ++local_x + */ + + if (!(last.outer instanceof SequentialBlock) + || !(last.outer.getSubBlocks()[0] instanceof InstructionBlock)) + return false; + + Expression instr1 = ((InstructionBlock) + last.outer.getSubBlocks()[0]).getInstruction(); + Expression instr2 = ic.getInstruction(); + + IIncOperator iinc; + LocalLoadOperator load; + boolean isPost; + if (instr1 instanceof IIncOperator + && instr2 instanceof LocalLoadOperator) { + iinc = (IIncOperator) instr1; + load = (LocalLoadOperator) instr2; + isPost = false; + } else if (instr1 instanceof LocalLoadOperator + && instr2 instanceof IIncOperator) { + load = (LocalLoadOperator) instr1; + iinc = (IIncOperator) instr2; + isPost = true; + } else + return false; + + int op; + if (iinc.getOperatorIndex() == iinc.ADD_OP + iinc.OPASSIGN_OP) + op = Operator.INC_OP; + else if (iinc.getOperatorIndex() == iinc.SUB_OP + iinc.OPASSIGN_OP) + op = Operator.DEC_OP; + else + return false; + + if (iinc.getValue() == -1) + op ^= 1; + else if (iinc.getValue() != 1) + return false; + + if (!iinc.lvalueMatches(load)) + return false; + + Type type = load.getType().intersection(Type.tUInt); + iinc.makeNonVoid(); + Operator ppop = + new PrePostFixOperator(type, op, iinc.getLValue(), isPost); + + ic.setInstruction(ppop); + ic.moveDefinitions(last.outer, last); + last.replace(last.outer); + return true; + } + + public static boolean createPostInc(InstructionContainer ic, + StructuredBlock last) { + + /* Situation: + * + * PUSH load/storeOps (optional/ + * not checked) PUSH load/storeOps + * DUP load/storeOps (optional) PUSH store++/-- + * PUSH load(stack) + * DUP_X(storeOps count) -> + * store(stack) = stack_0 +/- 1 + */ + + if (!(ic.getInstruction() instanceof StoreInstruction)) + return false; + + StoreInstruction store = (StoreInstruction) ic.getInstruction(); + + /* Make sure that the lvalue part of the store is + * not yet resolved (and note that the rvalue part + * should also have 1 remaining operand) + */ + Expression lvalue = store.getSubExpressions()[0]; + int lvalueCount = lvalue.getFreeOperandCount(); + if (!((Operator)lvalue).isFreeOperator() + || !store.isVoid() + || !(store.getSubExpressions()[1] instanceof BinaryOperator)) + return false; + + BinaryOperator binOp = (BinaryOperator) store.getSubExpressions()[1]; + if (binOp.getSubExpressions() == null + || !(binOp.getSubExpressions()[0] instanceof NopOperator) + || !(binOp.getSubExpressions()[1] instanceof ConstOperator)) + return false; + + ConstOperator constOp = (ConstOperator) binOp.getSubExpressions()[1]; + int op; + if (binOp.getOperatorIndex() == store.ADD_OP) + op = Operator.INC_OP; + else if (binOp.getOperatorIndex() == store.SUB_OP) + op = Operator.DEC_OP; + else + return false; + + if (!constOp.isOne(lvalue.getType())) + return false; + + if (!(last.outer instanceof SequentialBlock)) + return false; + SequentialBlock sb = (SequentialBlock)last.outer; + if (!(sb.subBlocks[0] instanceof SpecialBlock)) + return false; + + SpecialBlock dup = (SpecialBlock) sb.subBlocks[0]; + if (dup.type != SpecialBlock.DUP + || dup.count != lvalue.getType().stackSize() + || dup.depth != lvalueCount) + return false; + + if (!(sb.outer instanceof SequentialBlock)) + return false; + sb = (SequentialBlock) sb.outer; + if (!(sb.subBlocks[0] instanceof InstructionBlock)) + return false; + InstructionBlock ib = (InstructionBlock) sb.subBlocks[0]; + + if (!(ib.getInstruction() instanceof Operator) + || !store.lvalueMatches((Operator) ib.getInstruction())) + return false; + + if (lvalueCount > 0) { + if (!(sb.outer instanceof SequentialBlock)) + return false; + sb = (SequentialBlock) sb.outer; + if (!(sb.subBlocks[0] instanceof SpecialBlock)) + return false; + SpecialBlock dup2 = (SpecialBlock) sb.subBlocks[0]; + if (dup2.type != SpecialBlock.DUP + || dup2.count != lvalueCount + || dup2.depth != 0) + return false; + } + ic.setInstruction + (new PrePostFixOperator(lvalue.getType(), op, + store.getLValue(), true)); + ic.moveDefinitions(sb, last); + last.replace(sb); + return true; + } +} diff --git a/jode/src/net/sf/jode/flow/DescriptionBlock.java b/jode/src/net/sf/jode/flow/DescriptionBlock.java new file mode 100644 index 0000000..6199570 --- /dev/null +++ b/jode/src/net/sf/jode/flow/DescriptionBlock.java @@ -0,0 +1,49 @@ +/* DescriptionBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; + +/** + * This is a block which contains a comment/description of what went + * wrong. Use this, if you want to tell the user, that a construct + * doesn't have the exspected form. + * + * @author Jochen Hoenicke + */ +public class DescriptionBlock extends StructuredBlock { + String description; + + public DescriptionBlock(String description) { + this.description = description; + } + + /** + * Tells if this block is empty and only changes control flow. + */ + public boolean isEmpty() { + return true; + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + writer.println(description); + } +} diff --git a/jode/src/net/sf/jode/flow/EmptyBlock.java b/jode/src/net/sf/jode/flow/EmptyBlock.java new file mode 100644 index 0000000..e412e0e --- /dev/null +++ b/jode/src/net/sf/jode/flow/EmptyBlock.java @@ -0,0 +1,79 @@ +/* EmptyBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; + +/** + * This is the structured block for an empty block. + */ +public class EmptyBlock extends StructuredBlock { + public EmptyBlock() { + } + + public EmptyBlock(Jump jump) { + setJump(jump); + } + + /** + * Tells if this block is empty and only changes control flow. + */ + public boolean isEmpty() { + return true; + } + + /** + * Appends a block to this block. + * @return the new combined block. + */ + public StructuredBlock appendBlock(StructuredBlock block) { + if (outer instanceof ConditionalBlock) { + IfThenElseBlock ifBlock = + new IfThenElseBlock(((ConditionalBlock)outer). + getInstruction()); + ifBlock.moveDefinitions(outer, this); + ifBlock.replace(outer); + ifBlock.moveJump(outer.jump); + ifBlock.setThenBlock(this); + } + block.replace(this); + return block; + } + + /** + * Prepends a block to this block. + * @return the new combined block. + */ + public StructuredBlock prependBlock(StructuredBlock block) { + /* For empty blocks: append == prepend modulo jump */ + block = appendBlock(block); + block.moveJump(this.jump); + return block; + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + /* Only print the comment if jump null, since otherwise the block + * isn't completely empty ;-) + */ + if (jump == null) + writer.println("/* empty */"); + } +} diff --git a/jode/src/net/sf/jode/flow/FinallyBlock.java b/jode/src/net/sf/jode/flow/FinallyBlock.java new file mode 100644 index 0000000..c9d0cbc --- /dev/null +++ b/jode/src/net/sf/jode/flow/FinallyBlock.java @@ -0,0 +1,108 @@ +/* FinallyBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; + +/** + * + * @author Jochen Hoenicke + */ +public class FinallyBlock extends StructuredBlock { + /** + * The catch block. + */ + StructuredBlock subBlock; + + public FinallyBlock() { + } + + /** + * Sets the catch block. + * @param subBlock the catch block. + */ + public void setCatchBlock(StructuredBlock subBlock) { + this.subBlock = subBlock; + subBlock.outer = this; + subBlock.setFlowBlock(flowBlock); + } + + /* The implementation of getNext[Flow]Block is the standard + * implementation */ + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + if (subBlock == oldBlock) + subBlock = newBlock; + else + return false; + return true; + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return new StructuredBlock[] { subBlock }; + } + + /** + * A finally block starts with empty stack. It must return with empty + * stack too, but that need not to be checked. If the JSR's aren't + * correctly determined this may even not be true. + * + * @param stack the stack before the instruction is called + * @return stack the stack afterwards. */ + public VariableStack mapStackToLocal(VariableStack stack) { + super.mapStackToLocal(stack); + return null; + } + + /** + * Returns the block where the control will normally flow to, when + * the given sub block is finished (not ignoring the jump + * after this block). FinallyBlock have a special behaviour, since + * the finally block has no default successor at all (it is more a + * subroutine) that will be called by try or any exception. + * + * @return null, if the control flows to another FlowBlock. + */ + public StructuredBlock getNextBlock(StructuredBlock subBlock) { + return null; + } + + public FlowBlock getNextFlowBlock(StructuredBlock subBlock) { + return null; + } + + public void dumpInstruction(net.sf.jode.decompiler.TabbedPrintWriter writer) + throws java.io.IOException { + writer.closeBraceContinue(); + writer.print("finally"); + writer.openBrace(); + writer.tab(); + subBlock.dumpSource(writer); + writer.untab(); + } +} diff --git a/jode/src/net/sf/jode/flow/FlowBlock.java b/jode/src/net/sf/jode/flow/FlowBlock.java new file mode 100644 index 0000000..66bdee4 --- /dev/null +++ b/jode/src/net/sf/jode/flow/FlowBlock.java @@ -0,0 +1,1889 @@ +/* FlowBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.CombineableOperator; +import net.sf.jode.util.SimpleMap; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Map; +import java.util.Iterator; +import java.util.Set; +import java.util.ArrayList; +import java.util.List; +///#enddef + +/** + * A flow block is the structure of which the flow graph consists. A + * flow block contains structured code together with some conditional + * or unconditional jumps to the head of other flow blocks. + * + * We do a T1/T2 analysis to combine all flow blocks to a single. If + * the graph isn't reducible that doesn't work, but java can only + * produce reducible flow graphs. + * + * We don't use the notion of basic flow graphs. A flow block may be + * anything from a single bytecode opcode, to the whole method. + */ +public class FlowBlock { + + public static FlowBlock END_OF_METHOD; +// public static FlowBlock NEXT_BY_ADDR; + + static { + END_OF_METHOD = new FlowBlock(null, Integer.MAX_VALUE, null); + END_OF_METHOD.label = "END_OF_METHOD"; + +// NEXT_BY_ADDR = new FlowBlock(null, -1); +// NEXT_BY_ADDR.appendBlock(new DescriptionBlock("FALL THROUGH"), 0); +// NEXT_BY_ADDR.label = "NEXT_BY_ADDR"; + } + + /** + * The method analyzer. This is used to pretty printing the + * Types and to get information about all locals in this code. + */ + MethodAnalyzer method; + + /** + * The in locals. This are the locals, which are used in this + * flow block and whose values may be the result of a assignment + * outside of this flow block. That means, that there is a + * path from the start of the flow block to the instruction that + * uses that variable, on which it is never assigned + */ + private SlotSet in = new SlotSet(); + /** + * The gen locals. This are the locals, to which are written + * somewhere in this flow block. This is only used for try + * catch blocks. + */ + VariableSet used = new VariableSet(); + /** + * The gen locals. This are the locals, to which are written + * somewhere in this flow block. This is only used for try + * catch blocks. + */ + VariableSet gen = new VariableSet(); + /** + * The gen locals. This are the locals, to which are written + * somewhere in this flow block. This is only used for try + * catch blocks. + */ + SlotSet kill = new SlotSet(); + + /** + * The starting blockNr of this flow block. This is mainly used + * to produce the source code in code order. + */ + private int blockNr; + + /** + * The number of flow blocks that were combined into this block so far. + */ + private int length; + + /** + * The outermost structructed block in this flow block. + */ + StructuredBlock block; + + /** + * The last modified structured block. This is probably the + * last instruction in the outermost block, that is in the + * outermost chain of SequentialBlock. + */ + StructuredBlock lastModified; + + /** + * This contains a map of all successing flow blocks and there + * jumps. The key of this map are the flow blocks, while + * the elements is the first jump to that flow block. The other + * jumps are accessible via the jump.next field. + */ + private Map successors = new SimpleMap(); + + /** + * This is a vector of flow blocks, which reference this block. + * Only if this vector contains exactly one element, it can be + * moved into the preceding flow block. + * + * If this vectors contains the null element, this is the first + * flow block in a method. + */ + List predecessors = new ArrayList(); + + /** + * This is a pointer to the next flow block in byte code order. + * It is null for the last flow block. + */ + FlowBlock nextByCodeOrder; + + /** + * This is a pointer to the previous flow block in byte code order. + * It is null for the first flow block. + */ + FlowBlock prevByCodeOrder; + + /** + * The stack map. This tells how many objects are on stack at + * begin of the flow block, and to what locals they are maped. + * @see #mapStackToLocal + */ + VariableStack stackMap; + + static class SuccessorInfo { + /** + * The kill locals. This are the slots, which must be + * overwritten in this block on every path to the successor. + * That means, that all paths from the start of the current + * flow block to the successor contain (unconditional) + * assignments to this slot. + */ + SlotSet kill; + + /** + * The gen locals. This are the locals, which can be + * overwritten in this block on a path to the successor. That + * means, that there exists a path form the start of the + * current flow block to the successor that contains a + * assignments to this local, and that is not overwritten + * afterwards. + */ + VariableSet gen; + + /** + * The linked list of jumps. + */ + Jump jumps; + } + + /** + * The default constructor. Creates a new empty flowblock. + */ + public FlowBlock(MethodAnalyzer method, int blockNr, FlowBlock lastFlow) { + this.method = method; + this.blockNr = blockNr; + + length = 1; + prevByCodeOrder = lastFlow; + if (lastFlow != null) + lastFlow.nextByCodeOrder = this; + block = new EmptyBlock(); + block.setFlowBlock(this); + lastModified = block; + } + + public int getNextBlockNr() { + return blockNr + length; + } + + public boolean hasNoJumps() { + return successors.size() == 0 && predecessors.size() == 0; + } + + /** + * This method resolves some of the jumps to successor. + * @param jumps The list of jumps with that successor. + * @param succ The successing flow block. + * @return The remaining jumps, that couldn't be resolved. + */ + public Jump resolveSomeJumps(Jump jumps, FlowBlock succ) { + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("before Resolve: "+this); + + /* We will put all jumps that we can not resolve into this + * linked list. + */ + Jump remainingJumps = null; + + if (lastModified.jump == null) { + /* This can happen if lastModified is a breakable block, and + * there is no break to it yet. We give lastModified this jump + * as successor since many other routines rely on this. + */ + Jump lastJump = new Jump(succ); + lastModified.setJump(lastJump); + remainingJumps = lastJump; + } + + for (Jump jump = jumps; jump != null; jump = jump.next) { + /* First swap all conditional blocks, that have two jumps, + * so that the jump to succ will be on the outside. + */ + if (jump.prev.outer instanceof ConditionalBlock + && jump.prev.outer.jump != null) { + + StructuredBlock prev = jump.prev; + ConditionalBlock cb = (ConditionalBlock) prev.outer; + Expression instr = cb.getInstruction(); + + cb.setInstruction(instr.negate()); + cb.swapJump(prev); + } + } + next_jump: + while (jumps != null) { + Jump jump = jumps; + jumps = jumps.next; + + /* if the jump is the jump of lastModified, skip it. + */ + if (jump.prev == lastModified) { + jump.next = remainingJumps; + remainingJumps = jump; + continue; + } + + /* jump.prev.outer is not null, otherwise jump.prev would + * be lastModified. + */ + + if (jump.prev.outer instanceof ConditionalBlock) { + StructuredBlock prev = jump.prev; + ConditionalBlock cb = (ConditionalBlock) prev.outer; + Expression instr = cb.getInstruction(); + + /* This is a jump inside an ConditionalBlock. + * + * cb is the conditional block, + * prev the empty block containing the jump + * instr is the condition */ + + if (cb.jump != null) { + /* This can only happen if cb also jumps to succ. + * This is a weired "if (cond) empty"-block. We + * transform it by hand. + */ + prev.removeJump(); + IfThenElseBlock ifBlock = + new IfThenElseBlock(cb.getInstruction().negate()); + ifBlock.moveDefinitions(cb, prev); + ifBlock.replace(cb); + ifBlock.moveJump(cb.jump); + ifBlock.setThenBlock(prev); + if (cb == lastModified) + lastModified = ifBlock; + continue; + } + + /* Now cb.jump is null, so cb.outer is not null, + * since otherwise it would have no successor. */ + + if (cb.outer instanceof LoopBlock + || (cb.outer instanceof SequentialBlock + && cb.outer.getSubBlocks()[0] == cb + && cb.outer.outer instanceof LoopBlock)) { + + /* If this is the first instruction of a + * while/for(true) block, make this the loop condition + * (negated of course). + */ + + LoopBlock loopBlock = (cb.outer instanceof LoopBlock) ? + (LoopBlock) cb.outer : (LoopBlock) cb.outer.outer; + + if (loopBlock.getCondition() == LoopBlock.TRUE && + loopBlock.getType() != LoopBlock.DOWHILE && + (loopBlock.jumpMayBeChanged() + || loopBlock.getNextFlowBlock() == succ)) { + + if (loopBlock.jump == null) { + /* consider this jump again */ + loopBlock.moveJump(jump); + jumps = jump; + } else + jump.prev.removeJump(); + + loopBlock.setCondition(instr.negate()); + loopBlock.moveDefinitions(cb, null); + cb.removeBlock(); + continue; + } + + } else if (cb.outer instanceof SequentialBlock + && cb.outer.getSubBlocks()[1] == cb) { + + /* And now for do/while loops, where the jump is + * at the end of the loop. + */ + + /* First find the beginning of the loop */ + StructuredBlock sb = cb.outer.outer; + while (sb instanceof SequentialBlock) { + sb = sb.outer; + } + /* sb is now the first and cb is the last + * instruction in the current block. + */ + if (sb instanceof LoopBlock) { + LoopBlock loopBlock = (LoopBlock) sb; + if (loopBlock.getCondition() == LoopBlock.TRUE && + loopBlock.getType() == LoopBlock.WHILE && + (loopBlock.jumpMayBeChanged() + || loopBlock.getNextFlowBlock() == succ)) { + + if (loopBlock.jump == null) { + /* consider this jump again */ + loopBlock.moveJump(jump); + jumps = jump; + } else + jump.prev.removeJump(); + + loopBlock.setType(LoopBlock.DOWHILE); + loopBlock.setCondition(instr.negate()); + loopBlock.moveDefinitions(cb, null); + cb.removeBlock(); + continue; + } + } + } + + /* This is still a jump inside an ConditionalBlock. + * + * cb is the conditional block, + * prev the empty block containing the jump + * instr is the condition */ + + /* replace all conditional jumps to the successor, which + * are followed by a block which has the end of the block + * as normal successor, with "if (not condition) block": + * + * /IF cond GOTO succ if (!cond) + * \block ===> block + * -> normal Succesor succ -> normal Successor succ + */ + if (cb.outer instanceof SequentialBlock && + cb.outer.getSubBlocks()[0] == cb && + (cb.outer.getNextFlowBlock() == succ || + cb.outer.jumpMayBeChanged())) { + + SequentialBlock sequBlock = (SequentialBlock) cb.outer; + + IfThenElseBlock newIfBlock + = new IfThenElseBlock(instr.negate()); + StructuredBlock thenBlock = sequBlock.getSubBlocks()[1]; + + newIfBlock.moveDefinitions(sequBlock, thenBlock); + newIfBlock.replace(sequBlock); + newIfBlock.setThenBlock(thenBlock); + + if (thenBlock.contains(lastModified)) { + if (lastModified.jump.destination == succ) { + newIfBlock.moveJump(lastModified.jump); + lastModified = newIfBlock; + jump.prev.removeJump(); + continue; + } + lastModified = newIfBlock; + } + + newIfBlock.moveJump(jump); +// /* consider this jump again */ +// jumps = jump; + /* Consider all jumps again, since the ones that moved + * into the thenBlock may be obsolete now. + * XXX only jumps in then should be considered. + */ + if (remainingJumps == null) + jumps = jump; + else { + jumps = remainingJumps; + while (remainingJumps.next != null) + remainingJumps = remainingJumps.next; + remainingJumps.next = jump; + remainingJumps = null; + } + continue; + } + } else { + + /* remove this jump if it jumps to the + * getNextFlowBlock(). */ + if (jump.destination + == jump.prev.outer.getNextFlowBlock(jump.prev)) { + jump.prev.removeJump(); + continue; + } + + + /* Now find the real outer block, i.e. traverse the + * chain of SequentialBlocks. + * + * Note that only the last instr in a SequentialBlock chain + * can have a jump. + * + * We rely on the fact, that instanceof returns false + * for a null pointer. + */ + StructuredBlock sb = jump.prev.outer; + while (sb instanceof SequentialBlock) + sb = sb.outer; + + /* If the block is a catch, go up to the try block. + */ + if (sb instanceof CatchBlock + && sb.jumpMayBeChanged()) + sb = sb.outer; + + /* If the block is a synchronized or try block + * and the jump may be changed, move the jump up. + */ + if ((sb instanceof CatchBlock + || sb instanceof SynchronizedBlock + || sb instanceof TryBlock) + && sb.jumpMayBeChanged()) { + sb.moveJump(jump); + /* consider this jump again */ + jumps = jump; + continue; + } + + /* if this is an unconditional jump at the end of a + * then block belonging to a if-then block without + * else part, and the if block has a jump then replace + * the if-then block with a if-then-else block with an + * else block that contains only the jump and move the + * unconditional jump to the if. (The jump in the else + * block will later probably be replaced with a break, + * continue or return statement.) + */ + if (sb instanceof IfThenElseBlock) { + IfThenElseBlock ifBlock = (IfThenElseBlock) sb; + if (ifBlock.elseBlock == null && ifBlock.jump != null) { + ifBlock.setElseBlock(new EmptyBlock()); + ifBlock.elseBlock.moveJump(ifBlock.jump); + ifBlock.moveJump(jump); + /* consider this jump again */ + jumps = jump; + continue; + } + } + + /* if this is a jump at the end of a then block belonging + * to a if-then block without else part, and the if-then + * block is followed by a single block, then replace the + * if-then block with a if-then-else block and move the + * unconditional jump to the if. + */ + if (sb instanceof IfThenElseBlock + && sb.outer instanceof SequentialBlock + && sb.outer.getSubBlocks()[0] == sb) { + + IfThenElseBlock ifBlock = (IfThenElseBlock) sb; + SequentialBlock sequBlock = (SequentialBlock) sb.outer; + StructuredBlock elseBlock = sequBlock.subBlocks[1]; + + if (ifBlock.elseBlock == null + && (elseBlock.getNextFlowBlock() == succ + || elseBlock.jump != null + || elseBlock.jumpMayBeChanged())) { + + ifBlock.replace(sequBlock); + ifBlock.setElseBlock(elseBlock); + + if (elseBlock.contains(lastModified)) { + if (lastModified.jump.destination == succ) { + ifBlock.moveJump(lastModified.jump); + lastModified = ifBlock; + jump.prev.removeJump(); + continue; + } + lastModified = ifBlock; + } + + ifBlock.moveJump(jump); + + /* Consider all jumps again, since the ones that moved + * into the thenBlock may be obsolete now. + * XXX only jumps in then should be considered. + * XXX I'm not sure if this is complete. + */ + if (remainingJumps == null) + jumps = jump; + else { + jumps = remainingJumps; + while (remainingJumps.next != null) + remainingJumps = remainingJumps.next; + remainingJumps.next = jump; + remainingJumps = null; + } + continue; + } + } + } + + /* if this is a jump in a breakable block, and that block + * has not yet a next block, then create a new jump to that + * successor. + * + * The break to the block will be generated later. + */ + + for (StructuredBlock surrounder = jump.prev.outer; + surrounder != null; surrounder = surrounder.outer) { + if (surrounder instanceof BreakableBlock) { + if (surrounder.getNextFlowBlock() == succ) + /* We can break to that block; this is done later. */ + break; + + if (surrounder.jumpMayBeChanged()) { + surrounder.setJump(new Jump(succ)); + /* put surrounder in todo list */ + surrounder.jump.next = jumps; + jumps = surrounder.jump; + /* The break is inserted later */ + break; + } + if (succ == END_OF_METHOD) { + /* If the jump can be replaced by a return + * we won't do labeled breaks, so we must + * stop here + */ + break; + } + } + } + jump.next = remainingJumps; + remainingJumps = jump; + } + return remainingJumps; + } + + /** + * Resolve remaining jumps to the successor by generating break + * instructions. As last resort generate a do while(false) block. + * @param jumps The jump list that need to be resolved. + */ + void resolveRemaining(Jump jumps) { + LoopBlock doWhileFalse = null; + StructuredBlock outerMost = lastModified; + boolean removeLast = false; + next_jump: + for (; jumps != null; jumps = jumps.next) { + StructuredBlock prevBlock = jumps.prev; + + if (prevBlock == lastModified) { + /* handled below */ + removeLast = true; + continue; + } + + int breaklevel = 0; + BreakableBlock breakToBlock = null; + for (StructuredBlock surrounder = prevBlock.outer; + surrounder != null; surrounder = surrounder.outer) { + if (surrounder instanceof BreakableBlock) { + breaklevel++; + if (surrounder.getNextFlowBlock() == jumps.destination) { + breakToBlock = (BreakableBlock) surrounder; + break; + } + } + } + + prevBlock.removeJump(); + + if (breakToBlock == null) { + /* Nothing else helped, so put a do/while(0) + * block around outerMost and break to that + * block. + */ + if (doWhileFalse == null) { + doWhileFalse = new LoopBlock(LoopBlock.DOWHILE, + LoopBlock.FALSE); + } + /* Adapt outermost, so that it contains the break. */ + while (!outerMost.contains(prevBlock)) + outerMost = outerMost.outer; + prevBlock.appendBlock + (new BreakBlock(doWhileFalse, breaklevel > 0)); + } else + prevBlock.appendBlock + (new BreakBlock(breakToBlock, breaklevel > 1)); + } + + if (removeLast) + lastModified.removeJump(); + + if (doWhileFalse != null) { + doWhileFalse.replace(outerMost); + doWhileFalse.setBody(outerMost); + lastModified = doWhileFalse; + } + } + + /** + * Move the successors of the given flow block to this flow block. + * @param succ the other flow block + */ + void mergeSuccessors(FlowBlock succ) { + /* Merge the successors from the successing flow block + */ + Iterator iter = succ.successors.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + FlowBlock dest = (FlowBlock) entry.getKey(); + SuccessorInfo hisInfo = (SuccessorInfo) entry.getValue(); + SuccessorInfo myInfo = (SuccessorInfo) successors.get(dest); + + if (dest != END_OF_METHOD) + dest.predecessors.remove(succ); + if (myInfo == null) { + if (dest != END_OF_METHOD) + dest.predecessors.add(this); + successors.put(dest, hisInfo); + } else { + myInfo.gen.addAll(hisInfo.gen); + myInfo.kill.retainAll(hisInfo.kill); + Jump myJumps = myInfo.jumps; + while (myJumps.next != null) + myJumps = myJumps.next; + myJumps.next = hisInfo.jumps; + } + } + } + + /** + * Fixes the blockNr chained list, after merging this block with succ. + */ + public void mergeBlockNr(FlowBlock succ) { + if (succ.nextByCodeOrder == this || succ.prevByCodeOrder == null) { + /* Merge succ with its nextByCodeOrder. + * Note: succ.nextByCodeOrder != null, since this is on the + * nextByCodeOrder chain. */ + succ.nextByCodeOrder.blockNr = succ.blockNr; + succ.nextByCodeOrder.length += succ.length; + + succ.nextByCodeOrder.prevByCodeOrder = succ.prevByCodeOrder; + if (succ.prevByCodeOrder != null) + succ.prevByCodeOrder.nextByCodeOrder = succ.nextByCodeOrder; + } else { + /* Merge succ with its prevByCodeOrder */ + succ.prevByCodeOrder.length += succ.length; + + succ.prevByCodeOrder.nextByCodeOrder = succ.nextByCodeOrder; + if (succ.nextByCodeOrder != null) + succ.nextByCodeOrder.prevByCodeOrder = succ.prevByCodeOrder; + } + } + + /** + * Updates the gen/kill Sets of all jumps in this block. + * @param gens The locals in this block that are visible at the + * begin of successor. + * @param kills The slots that are always overwritten on the way to + * successor. This may be null. + * @return The variables that must be defined * in this block. + */ + void updateGenKill(VariableSet gens, SlotSet kills) { + /* Merge the locals used in successing block with those written + * by this blocks. + */ + in.merge(gens); + + /* The gen/kill sets must be updated for every jump + * in the other block */ + Iterator i = successors.values().iterator(); + while (i.hasNext()) { + SuccessorInfo succInfo = (SuccessorInfo) i.next(); + succInfo.gen.mergeGenKill(gens, succInfo.kill); + if (kills != null) + succInfo.kill.mergeKill(kills); + } + } + + /** + * Updates the in/out-Vectors of the structured block of the + * successing flow block simultanous to a T2 transformation. + * @param successor The flow block which is unified with this flow + * block. + * @param gens The locals in this block that are visible at the + * begin of successor. + * @param kills The slots that are always overwritten on the way to + * successor. + * @return The variables that must be defined in this block. + */ + void updateInOut(FlowBlock successor, VariableSet gens, SlotSet kills) { + successor.updateGenKill(gens, kills); + + /* The ins of the successor that are not killed + * (i.e. unconditionally overwritten) by this block are new + * ins for this block. + */ + SlotSet newIn = (SlotSet) successor.in.clone(); + newIn.removeAll(kills); + this.in.addAll(newIn); + this.used.addAll(successor.used); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_INOUT) != 0) { + GlobalOptions.err.println("UpdateInOut: gens : "+gens); + GlobalOptions.err.println(" kills: "+kills); + GlobalOptions.err.println(" s.in : "+successor.in); + GlobalOptions.err.println(" in : "+in); + } + } + + /** + * Updates the in/out-Vectors of the structured block of the + * successing flow block for a try catch block. The main difference + * to updateInOut in FlowBlock is, that this function works, as if + * every instruction would have a jump. This is because every + * instruction can throw an exception and thus enter the catch block.
+ * + * For example this code prints 0: + *
+     *   int a=3;
+     *   try {
+     *     a = 5 / (a=0);
+     *   } catch (DivideByZeroException ex) {
+     *     System.out.println(a);
+     *   }
+     * 
+ * + * @param successor The flow block which is unified with this flow + * block. + * @return The variables that must be defined in this block. + */ + public void updateInOutCatch (FlowBlock catchFlow) { + VariableSet gens = ((TryBlock)block).gen; + + /* Merge the locals used in the catch block with those written + * by the try block + */ + catchFlow.in.merge(gens); + + /* The gen/kill sets must be updated for every jump + * in the other block */ + Iterator i = catchFlow.successors.values().iterator(); + while (i.hasNext()) { + SuccessorInfo succSuccInfo = (SuccessorInfo) i.next(); + succSuccInfo.gen.mergeGenKill(gens, succSuccInfo.kill); + } + in.addAll(catchFlow.in); + used.addAll(catchFlow.used); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_INOUT) != 0) { + GlobalOptions.err.println("UpdateInOutCatch: gens : "+gens); + GlobalOptions.err.println(" s.in : "+catchFlow.in); + GlobalOptions.err.println(" in : "+in); + } + } + + + /** + * Checks if the FlowBlock and its StructuredBlocks are + * consistent. There are to many conditions to list them + * here, the best way is to read this function and all other + * checkConsistent functions. + */ + public void checkConsistent() { + /* This checks are very time consuming, so don't do them + * normally. + */ + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CHECK) == 0) + return; + + try { + if (block.outer != null || block.flowBlock != this) { + throw new InternalError("Inconsistency: outer:" + block.outer + + " block.flow"+block.flowBlock + + " this: "+this); + } + block.checkConsistent(); + + for (Iterator i = predecessors.iterator(); i.hasNext(); ) { + FlowBlock pred = (FlowBlock)i.next(); + if (pred == null) + /* The special start marker */ + continue; + if (!pred.successors.containsKey(this)) + throw new InternalError + ("Inconsistency: "+pred.getLabel()+" not in " + +this.getLabel()+".successors"); + } + + StructuredBlock last = lastModified; + while (last.outer instanceof SequentialBlock + || last.outer instanceof TryBlock + || last.outer instanceof FinallyBlock) + last = last.outer; + if (last.outer != null) + throw new InternalError + ("Inconsistency: last "+lastModified + +" surrounded by unexpected structure"); + + Iterator iter = successors.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + FlowBlock dest = (FlowBlock) entry.getKey(); + if (dest.predecessors.contains(this) == (dest == END_OF_METHOD)) + throw new InternalError + ("Inconsistency: dest "+dest.getLabel() + +" doesn't contain this predecessor"); + + Jump jumps = ((SuccessorInfo) entry.getValue()).jumps; + if (jumps == null) + throw new InternalError("Inconsistency: no jumps for " + +dest.getLabel()); + + for (; jumps != null; jumps = jumps.next) { + + if (jumps.destination != dest) + throw new InternalError("Inconsistency:" +jumps + + "doesn't point to " + +dest.getLabel()); + + if (jumps.prev == null + || jumps.prev.flowBlock != this + || jumps.prev.jump != jumps) + throw new InternalError("Inconsistency in" +jumps); + + prev_loop: + for (StructuredBlock prev = jumps.prev; prev != block; + prev = prev.outer) { + if (prev.outer == null) + throw new InternalError("Inconsistency: " +prev + +" not in flowblock"); + StructuredBlock[] blocks = prev.outer.getSubBlocks(); + int i; + for (i=0; i 0) + lastModified.setSuccessors(jumps); + gen = null; + kill = null; + checkConsistent(); + } + + /** + * Do a T2 transformation with succ if possible. It is possible, + * iff succ has exactly this block as predecessor. + * @param succ the successor block, must be a valid successor of this + * block, i.e. not null + */ + public boolean doT2(FlowBlock succ) { + /* check if this successor has only this block as predecessor. + * if the predecessor is not unique, return false. */ + if (succ.predecessors.size() != 1 || + succ.predecessors.get(0) != this) + return false; + + checkConsistent(); + succ.checkConsistent(); + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("T2(["+blockNr+","+getNextBlockNr()+"],[" + +succ.blockNr+","+succ.getNextBlockNr()+"])"); + + SuccessorInfo succInfo = (SuccessorInfo) successors.remove(succ); + + /* Update the in/out-Vectors now */ + updateInOut(succ, succInfo.gen, succInfo.kill); + + /* Try to eliminate as many jumps as possible. + */ + Jump jumps = resolveSomeJumps(succInfo.jumps, succ); + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("before Remaining: "+this); + resolveRemaining(jumps); + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("after Resolve: "+this); + + /* Now unify the blocks. + */ + lastModified = lastModified.appendBlock(succ.block); + mergeSuccessors(succ); + + /* This will also set last modified to the new correct value. */ + doTransformations(); + + /* Set blockNr and length to correct value and update nextByCodeOrder */ + mergeBlockNr(succ); + + /* T2 transformation succeeded */ + checkConsistent(); + return true; + } + + /** + * Do a T2 transformation with the end_of_method block. + */ + public void mergeEndBlock() { + checkConsistent(); + + SuccessorInfo endInfo + = (SuccessorInfo) successors.remove(END_OF_METHOD); + if (endInfo == null) + return; + + Jump allJumps = endInfo.jumps; + /* First remove all implicit jumps to the END_OF_METHOD block. + */ + Jump jumps = null; + for (; allJumps != null; ) { + Jump jump = allJumps; + allJumps = allJumps.next; + + if (jump.prev instanceof ReturnBlock) { + /* This jump is implicit */ + jump.prev.removeJump(); + continue; + } + jump.next = jumps; + jumps = jump; + } + + /* Try to eliminate as many jumps as possible. + */ + jumps = resolveSomeJumps(jumps, END_OF_METHOD); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("before remaining: "+this); + + next_jump: + for (; jumps != null; jumps = jumps.next) { + + StructuredBlock prevBlock = jumps.prev; + + if (lastModified == prevBlock) + /* handled later */ + continue; + + BreakableBlock breakToBlock = null; + for (StructuredBlock surrounder = prevBlock.outer; + surrounder != null; surrounder = surrounder.outer) { + if (surrounder instanceof BreakableBlock) { + if (surrounder.getNextFlowBlock() == END_OF_METHOD) + breakToBlock = (BreakableBlock) surrounder; + + /* We don't want labeled breaks, because we can + * simply return. */ + break; + } + } + prevBlock.removeJump(); + + if (breakToBlock == null) + /* The successor is the dummy return instruction, so + * replace the jump with a return. + */ + prevBlock.appendBlock(new ReturnBlock()); + else + prevBlock.appendBlock + (new BreakBlock(breakToBlock, false)); + } + + /* Now remove the jump of the lastModified if it points to + * END_OF_METHOD. + */ + if (lastModified.jump.destination == END_OF_METHOD) + lastModified.removeJump(); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("before Transformation: "+this); + + doTransformations(); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("after Transformation: "+this); + + /* transformation succeeded */ + checkConsistent(); + } + + public boolean doT1(int start, int end) { + /* If there are no jumps to the beginning of this flow block + * or if this block has other predecessors with a not yet + * considered block number, return false. The second condition + * make sure that not for each continue a while is created. + */ + if (!predecessors.contains(this)) + return false; + for (Iterator i = predecessors.iterator(); i.hasNext(); ) { + FlowBlock predFlow = (FlowBlock) i.next(); + if (predFlow != null && predFlow != this + && predFlow.blockNr >= start && predFlow.blockNr < end) { + return false; + } + } + + checkConsistent(); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println("T1(["+blockNr+","+getNextBlockNr()+"])"); + SuccessorInfo succInfo = (SuccessorInfo) successors.remove(this); + + /* Update the in/out-Vectors now */ + updateGenKill(succInfo.gen, null); + Jump jumps = succInfo.jumps; + + StructuredBlock bodyBlock = block; + + /* If there is only one jump to the beginning and it is + * the last jump (lastModified) and (there is a + * do/while(0) block surrounding everything but the last + * instruction, or the last instruction is a + * increase/decrease statement), replace the do/while(0) + * with a for(;;last_instr) resp. create a new one and + * replace breaks to do/while with continue to for. */ + + boolean createdForBlock = false; + + if (jumps.next == null + && jumps.prev == lastModified + && lastModified instanceof InstructionBlock + && ((InstructionBlock)lastModified).getInstruction().isVoid()) { + + if (lastModified.outer instanceof SequentialBlock + && lastModified.outer.getSubBlocks()[0] + instanceof LoopBlock) { + + LoopBlock lb = + (LoopBlock) lastModified.outer.getSubBlocks()[0]; + if (lb.cond == lb.FALSE && lb.type == lb.DOWHILE) { + + /* The jump is directly following a + * do-while(false) block + * + * Remove do/while, create a for(;;last_instr) + * and replace break to that block with + * continue to for. + */ + + lastModified.removeJump(); + LoopBlock forBlock = + new LoopBlock(LoopBlock.FOR, LoopBlock.TRUE); + forBlock.replace(bodyBlock); + forBlock.setBody(bodyBlock); + forBlock.incrInstr + = ((InstructionBlock) lastModified).getInstruction(); + forBlock.replaceBreakContinue(lb); + + lb.bodyBlock.replace(lastModified.outer); + createdForBlock = true; + } + + } + + if (!createdForBlock + && (((InstructionBlock) lastModified).getInstruction() + instanceof CombineableOperator)) { + + /* The only jump is the jump of the last + * instruction lastModified, there is a big + * chance, that this is a for block, but we + * can't be sure until we have seen the condition. + * We will transform it to a for block, and take + * that back, when we get a non matching condition. + */ + + lastModified.removeJump(); + LoopBlock forBlock = + new LoopBlock(LoopBlock.POSSFOR, LoopBlock.TRUE); + forBlock.replace(bodyBlock); + forBlock.setBody(bodyBlock); + forBlock.incrBlock = (InstructionBlock) lastModified; + + createdForBlock = true; + } + } + + if (!createdForBlock) { + /* Creating a for block didn't succeed; create a + * while block instead. */ + + /* Try to eliminate as many jumps as possible. + */ + jumps = resolveSomeJumps(jumps, this); + + LoopBlock whileBlock = + new LoopBlock(LoopBlock.WHILE, LoopBlock.TRUE); + + /* The block may have been changed above. */ + bodyBlock = block; + whileBlock.replace(bodyBlock); + whileBlock.setBody(bodyBlock); + + /* if there are further jumps to this, replace every jump with a + * continue to while block and return true. + */ + for (; jumps != null; jumps = jumps.next) { + + if (jumps.prev == lastModified) + /* handled later */ + continue; + + StructuredBlock prevBlock = jumps.prev; + + int breaklevel = 0, continuelevel = 0; + BreakableBlock breakToBlock = null; + for (StructuredBlock surrounder = prevBlock.outer; + surrounder != whileBlock; + surrounder = surrounder.outer) { + if (surrounder instanceof BreakableBlock) { + if (surrounder instanceof LoopBlock) + continuelevel++; + breaklevel++; + if (surrounder.getNextFlowBlock() == this) { + breakToBlock = (BreakableBlock) surrounder; + break; + } + } + } + prevBlock.removeJump(); + if (breakToBlock == null) + prevBlock.appendBlock + (new ContinueBlock(whileBlock, continuelevel > 0)); + else + prevBlock.appendBlock + (new BreakBlock(breakToBlock, breaklevel > 1)); + } + + /* Now remove the jump of lastModified if it points to this. + */ + if (lastModified.jump != null + && lastModified.jump.destination == this) + lastModified.removeJump(); + } + + /* remove ourself from the predecessor list. + */ + predecessors.remove(this); + lastModified = block; + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("before Transformation: "+this); + + doTransformations(); + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("after Transformation: "+this); + + /* T1 analysis succeeded */ + checkConsistent(); + + return true; + } + + public void doTransformations() { + while (lastModified instanceof SequentialBlock) { + if (lastModified.getSubBlocks()[0].doTransformations()) + continue; + lastModified = lastModified.getSubBlocks()[1]; + } + while (lastModified.doTransformations()) + { /* empty */ } + } + + /** + * Search for an apropriate successor. + * @param prevSucc The successor, that was previously tried. + * @param start The minimum blockNr + * @param end The maximum blockNr + 1. + * @return the successor with smallest block number greater than prevSucc + * or null if there isn't any further successor at all. + */ + FlowBlock getSuccessor(int start, int end) { + /* search successor with smallest blockNr. */ + Iterator keys = successors.keySet().iterator(); + FlowBlock succ = null; + while (keys.hasNext()) { + FlowBlock fb = (FlowBlock) keys.next(); + if (fb.blockNr < start || fb.blockNr >= end || fb == this) + continue; + if (succ == null || fb.blockNr < succ.blockNr) { + succ = fb; + } + } + return succ; + } + + /** + * The main analyzation. This calls doT1 and doT2 on apropriate + * regions until the whole function is transformed to a single + * block. + */ + public void analyze() { + while (analyze(0, Integer.MAX_VALUE)) + { } + mergeEndBlock(); + } + + /** + * The main analyzation. This calls doT1 and doT2 on apropriate + * regions. Only blocks whose block number lies in the given block number + * range are considered. + * @param start the start of the block number range. + * @param end the end of the block number range. + */ + public boolean analyze(int start, int end) { + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println("analyze("+start+", "+end+")"); + + checkConsistent(); + boolean changed = false; + + while (true) { + + if (lastModified instanceof SwitchBlock) { + /* analyze the switch first. + */ + changed |= analyzeSwitch(start, end); + } + + /* Do T1 analysis when there is a loop, but only if: + * - the loop has only one exit (plus returns) + * - or the successor block doesn't belong to the loop. + * + * The reason for the extra checks are loops like + * + * while (x) + * ... + * if (y) + * ... + * return + * + * which would otherwise be translated to: + * + * outer: do { + * do { + * if (!x) break outer; + * ... + * } while(!y) + * ... + * return + * } while(false) + */ + if (successors.containsKey(this) + && (!successors.containsKey(nextByCodeOrder) + || successors.size() == 2 + || (successors.size() == 3 + && successors.containsKey(END_OF_METHOD))) + && doT1(start, end)) { + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("after T1: "+this); + + /* T1 transformation succeeded. This may + * make another T2 analysis in the previous + * block possible. + */ + return true; + } + + FlowBlock succ = getSuccessor(start, end); + while (true) { + if (succ == null) { + /* the Block has no successor where T2 is applicable. + * Finish this analyzation. + */ + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("No more successors applicable: " + + start + " - " + end + "; " + + blockNr + " - " + getNextBlockNr()); + return changed; + } else if ((nextByCodeOrder == succ + || succ.nextByCodeOrder == this) + /* Only do T2 transformation if the blocks are + * adjacent. + */ + && doT2(succ)) { + /* T2 transformation succeeded. */ + changed = true; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("after T2: "+this); + break; + + } else { + + /* Check if all predecessors of either succ or this + * block lie in range [start,end). Otherwise + * we have no chance to combine these two blocks. + */ + boolean predOutOfRange = false; + for (Iterator i = succ.predecessors.iterator(); + i.hasNext(); ) { + FlowBlock pred = (FlowBlock)i.next(); + if (pred == null /* the start marker */ + || pred.blockNr < start || pred.blockNr >= end) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + + GlobalOptions.err.println + ("breaking analyze(" + + start + ", " + end + "); " + + blockNr + " - " + getNextBlockNr()); + return changed; + } + } + /* analyze succ, the new region is the + * continuous region of + * [start,end) \cap \compl [blockNr, getNextBlockNr()) + * where succ.blockNr lies in. + */ + int newStart = (succ.blockNr > blockNr) + ? getNextBlockNr() : start; + int newEnd = (succ.blockNr > blockNr) + ? end : blockNr; + if (succ.analyze(newStart, newEnd)) + break; + } + + /* Try the next successor. + */ + succ = getSuccessor(succ.blockNr+1, end); + } + } + } + + /** + * The switch analyzation. This calls doSwitchT2 and doT1 on apropriate + * regions. Only blocks whose block number lies in the given block number + * range are considered and it is taken care of, that the switch + * is never left.

+ * The current flow block must contain the switch block as lastModified. + * @param start the start of the block number range. + * @param end the end of the block number range. + */ + public boolean analyzeSwitch(int start, int end) { + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println("analyzeSwitch("+start+", "+end+")"); + + SwitchBlock switchBlock = (SwitchBlock) lastModified; + boolean changed = false; + + int last = -1; + FlowBlock lastFlow = null; + for (int i=0; i < switchBlock.caseBlocks.length; i++) { + if (switchBlock.caseBlocks[i].subBlock instanceof EmptyBlock + && switchBlock.caseBlocks[i].subBlock.jump != null) { + FlowBlock nextFlow = switchBlock.caseBlocks[i]. + subBlock.jump.destination; + if (nextFlow.blockNr >= end) + break; + else if (nextFlow.blockNr >= start) { + + /* First analyze the nextFlow block. It may + * return early after a T1 trafo so call it + * until nothing more is possible. + */ + while (nextFlow.analyze(getNextBlockNr(), end)) + changed = true; + + if (nextFlow.blockNr != getNextBlockNr()) + break; + + /* Check if nextFlow has only the previous case + * and this case as predecessor. Otherwise + * break the analysis. + */ + if (nextFlow.predecessors.size() > 2 + || (nextFlow.predecessors.size() > 1 + && (lastFlow == null + || !nextFlow.predecessors.contains(lastFlow))) + || (((SuccessorInfo)successors.get(nextFlow)) + .jumps.next != null)) + break; + + checkConsistent(); + + /* note that this info only contains + * the single caseBlock jump */ + SuccessorInfo info = (SuccessorInfo) + successors.remove(nextFlow); + + if (nextFlow.predecessors.size() == 2) { + SuccessorInfo lastInfo = (SuccessorInfo) + lastFlow.successors.remove(nextFlow); + + /* Do the in/out analysis with all jumps + * Note that this won't update lastFlow.in, but + * this will not be used anymore. + */ + info.kill.retainAll(lastInfo.kill); + info.gen.addAll(lastInfo.gen); + + Jump lastJumps = lastFlow.resolveSomeJumps + (lastInfo.jumps, nextFlow); + lastFlow.resolveRemaining(lastJumps); + switchBlock.caseBlocks[last+1].isFallThrough = true; + } + updateInOut(nextFlow, info.gen, info.kill); + + if (lastFlow != null) { + lastFlow.block.replace + (switchBlock.caseBlocks[last].subBlock); + mergeSuccessors(lastFlow); + } + + /* We merge the blocks into the caseBlock later, but + * that doesn't affect consistency. + */ + + switchBlock.caseBlocks[i].subBlock.removeJump(); + mergeBlockNr(nextFlow); + + lastFlow = nextFlow; + last = i; + + checkConsistent(); + changed = true; + } + } + } + if (lastFlow != null) { + lastFlow.block.replace + (switchBlock.caseBlocks[last].subBlock); + mergeSuccessors(lastFlow); + } + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_FLOW) != 0) + GlobalOptions.err.println("after analyzeSwitch: "+this); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("analyzeSwitch done: " + start + " - " + end + "; " + + blockNr + " - " + getNextBlockNr()); + checkConsistent(); + return changed; + } + + /** + * Mark the flow block as first flow block in a method. + */ + public void addStartPred() { + predecessors.add(null); + } + + public void removeStartPred() { + predecessors.remove(null); + } + + public void removeSuccessor(Jump jump) { + SuccessorInfo destInfo + = (SuccessorInfo) successors.get(jump.destination); + Jump prev = null; + Jump destJumps = destInfo.jumps; + while (destJumps != jump && destJumps != null) { + prev = destJumps; + destJumps = destJumps.next; + } + if (destJumps == null) + throw new IllegalArgumentException + (blockNr+": removing non existent jump: " + jump); + + if (prev != null) + prev.next = destJumps.next; + else { + if (destJumps.next == null) { + successors.remove(jump.destination); + jump.destination.predecessors.remove(this); + } else + destInfo.jumps = destJumps.next; + } + } + + public Jump getJumps(FlowBlock dest) { + return ((SuccessorInfo) successors.get(dest)).jumps; + } + + public Jump removeJumps(FlowBlock dest) { + if (dest != END_OF_METHOD) + dest.predecessors.remove(this); + return ((SuccessorInfo) successors.remove(dest)).jumps; + } + + public Set getSuccessors() { + return successors.keySet(); + } + +// public void addSuccessor(Jump jump) { +// SuccessorInfo info = (SuccessorInfo) successors.get(jump.destination); +// if (info == null) { +// info = new SuccessorInfo(); +// info.jumps = jump; +// if (jump.destination != END_OF_METHOD) +// jump.destination.predecessors.add(this); +// successors.put(jump.destination, info); +// } else { +// jump.next = info.jumps; +// info.jumps = jump; +// } +// } + + /** + * This is called after the analysis is completely done. It + * will remove all PUSH/stack_i expressions, (if the bytecode + * is correct). + * @return true, if the stack mapping succeeded. + */ + public final boolean mapStackToLocal() { + mapStackToLocal(VariableStack.EMPTY); + return true; + } + + /** + * This is called after the analysis is completely done. It + * will remove all PUSH/stack_i expressions, (if the bytecode + * is correct). + * @param initialStack the stackmap at begin of the flow block + * @return false if the bytecode isn't correct and stack mapping + * didn't worked. + */ + public void mapStackToLocal(VariableStack initialStack) { + if (initialStack == null) + throw new InternalError("initial stack is null"); + stackMap = initialStack; + block.mapStackToLocal(initialStack); + Iterator iter = successors.values().iterator(); + while (iter.hasNext()) { + SuccessorInfo succInfo = (SuccessorInfo) iter.next(); + Jump jumps = succInfo.jumps; + VariableStack stack; + FlowBlock succ = jumps.destination; + if (succ == END_OF_METHOD) + continue; + stack = succ.stackMap; + for (/**/; jumps != null; jumps = jumps.next) { + if (jumps.stackMap == null) + GlobalOptions.err.println("Dead jump? "+jumps.prev + +" in "+this); + + stack = VariableStack.merge(stack, jumps.stackMap); + } + if (succ.stackMap == null) + succ.mapStackToLocal(stack); + } + } + + public void removePush() { + if (stackMap == null) + /* already done or mapping didn't succeed */ + return; + stackMap = null; + block.removePush(); + Iterator iter = successors.keySet().iterator(); + while (iter.hasNext()) { + FlowBlock succ = (FlowBlock)iter.next(); + succ.removePush(); + } + } + + public void removeOnetimeLocals() { + block.removeOnetimeLocals(); + if (nextByCodeOrder != null) + nextByCodeOrder.removeOnetimeLocals(); + } + + private void promoteInSets() { + for (Iterator i = predecessors.iterator(); i.hasNext(); ) { + FlowBlock pred = (FlowBlock) i.next(); + /* Skip the start marker */ + if (pred == null) + continue; + SuccessorInfo succInfo = (SuccessorInfo) pred.successors.get(this); + + /* First get the gen/kill sets of all jumps of predecessor + * to this block and calculate the intersection. + */ + VariableSet gens = succInfo.gen; + SlotSet kills = succInfo.kill; + + /* Merge in locals of this block with those condionally + * written by previous blocks */ + in.merge(gens); + + /* The ins of the successor that are not killed + * (i.e. unconditionally overwritten) by this block are new + * ins for this block. + */ + SlotSet newIn = (SlotSet) in.clone(); + newIn.removeAll(kills); + + if (pred.in.addAll(newIn)) + pred.promoteInSets(); + } + + if (nextByCodeOrder != null) + nextByCodeOrder.promoteInSets(); + } + + /** + * Merge the parameter locals with the in set of this flow block. + * This will also make a successive analysis to merge the gen/kill + * set of the jumps with the next flow block. */ + public void mergeParams(LocalInfo[] param) { + // Now we promote the final (maybe slow) in set analysis + promoteInSets(); + + VariableSet paramSet = new VariableSet(param); + in.merge(paramSet); + } + + /** + * Make declarations. It will determine, where in each block the + * variables and method scoped classes must be declared. + */ + public void makeDeclaration(Set done) { + block.propagateUsage(); + block.makeDeclaration(done); + if (nextByCodeOrder != null) + nextByCodeOrder.makeDeclaration(done); + } + + /** + * Simplify this and all following flowblocks. + */ + public void simplify() { + block.simplify(); + if (nextByCodeOrder != null) + nextByCodeOrder.simplify(); + } + + /** + * Print the source code for this structured block. This handles + * everything that is unique for all structured blocks and calls + * dumpInstruction afterwards. + * @param writer The tabbed print writer, where we print to. + */ + public void dumpSource(TabbedPrintWriter writer) + throws java.io.IOException + { + if (predecessors.size() != 0) { + writer.untab(); + writer.println(getLabel()+":"); + writer.tab(); + } + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_INOUT) != 0) { + writer.println("in: "+in); + } + + block.dumpSource(writer); + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_INOUT) != 0) { + + Iterator iter = successors.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + FlowBlock dest = (FlowBlock) entry.getKey(); + SuccessorInfo info = (SuccessorInfo) entry.getValue(); + writer.println("successor: "+dest.getLabel() + +" gen : "+ info.gen + +" kill: "+ info.kill); + } + } + + if (nextByCodeOrder != null) + nextByCodeOrder.dumpSource(writer); + } + + /** + * The serial number for labels. + */ + static int serialno = 0; + + /** + * The label of this instruction, or null if it needs no label. + */ + String label = null; + + /** + * Returns the block number, where the code in this flow block starts. + */ + public int getBlockNr() { + return blockNr; + } + + /** + * Returns the label of this block and creates a new label, if + * there wasn't a label previously. + */ + public String getLabel() { + if (label == null) + label = "flow_"+blockNr+"_"+(serialno++)+"_"; + return label; + } + + /** + * Returns the structured block, that this flow block contains. + */ + public StructuredBlock getBlock() { + return block; + } + + public String toString() { + try { + java.io.StringWriter strw = new java.io.StringWriter(); + TabbedPrintWriter writer = new TabbedPrintWriter(strw); + writer.println(super.toString() + ": "+blockNr); + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_INOUT) != 0) { + writer.println("in: "+in); + } + writer.tab(); + block.dumpSource(writer); + writer.untab(); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_INOUT) != 0) { + + Iterator iter = successors.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + FlowBlock dest = (FlowBlock) entry.getKey(); + SuccessorInfo info = (SuccessorInfo) entry.getValue(); + writer.println("successor: "+dest.getLabel() + +" gen : "+ info.gen + +" kill: "+ info.kill); + } + } + return strw.toString(); + } catch (RuntimeException ex) { + return super.toString(); + } catch (java.io.IOException ex) { + return super.toString(); + } + } +} diff --git a/jode/src/net/sf/jode/flow/IfThenElseBlock.java b/jode/src/net/sf/jode/flow/IfThenElseBlock.java new file mode 100644 index 0000000..b664640 --- /dev/null +++ b/jode/src/net/sf/jode/flow/IfThenElseBlock.java @@ -0,0 +1,235 @@ +/* IfThenElseBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.expr.Expression; +import net.sf.jode.type.Type; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +/** + * An IfThenElseBlock is the structured block representing an if + * instruction. The else part may be null. + */ +public class IfThenElseBlock extends StructuredBlock { + + /** + * The condition. Must be of boolean type. + */ + Expression cond; + /** + * The loads that are on the stack before cond is executed. + */ + VariableStack condStack; + + + /** + * The then part. This is always a valid block and not null + */ + StructuredBlock thenBlock; + + /** + * The else part, may be null, and mustn't be the then part. + */ + StructuredBlock elseBlock; + + /** + * Creates a new if then else block. The method setThenBlock must + * be called shortly after the creation. + */ + public IfThenElseBlock(Expression cond) { + this.cond = cond; + } + + /** + * Sets the then block. + * @param thenBlock the then block, must be non null. + */ + public void setThenBlock(StructuredBlock thenBlock) { + this.thenBlock = thenBlock; + thenBlock.outer = this; + thenBlock.setFlowBlock(flowBlock); + } + + /** + * Sets the else block. + * @param elseBlock the else block + */ + public void setElseBlock(StructuredBlock elseBlock) { + this.elseBlock = elseBlock; + elseBlock.outer = this; + elseBlock.setFlowBlock(flowBlock); + } + + /* The implementation of getNext[Flow]Block is the standard + * implementation */ + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + if (thenBlock == oldBlock) + thenBlock = newBlock; + else if (elseBlock == oldBlock) + elseBlock = newBlock; + else + return false; + return true; + } + + /** + * This does take the instr into account and modifies stack + * accordingly. It then calls super.mapStackToLocal. + * @param stack the stack before the instruction is called + * @return stack the stack afterwards. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + VariableStack newStack; + int params = cond.getFreeOperandCount(); + if (params > 0) { + condStack = stack.peek(params); + newStack = stack.pop(params); + } else + newStack = stack; + + VariableStack after + = VariableStack.merge(thenBlock.mapStackToLocal(newStack), + elseBlock == null ? newStack + : elseBlock.mapStackToLocal(newStack)); + if (jump != null) { + jump.stackMap = after; + return null; + } + return after; + } + + public void removePush() { + if (condStack != null) + cond = condStack.mergeIntoExpression(cond); + thenBlock.removePush(); + if (elseBlock != null) + elseBlock.removePush(); + } + + public Set getDeclarables() { + Set used = new SimpleSet(); + cond.fillDeclarables(used); + return used; + } + + /** + * Make the declarations, i.e. initialize the declare variable + * to correct values. This will declare every variable that + * is marked as used, but not done.
+ * + * This will now also combine locals, that use the same slot, have + * compatible types and are declared in the same block.
+ * + * @param done The set of the already declare variables. + */ + public void makeDeclaration(Set done) { + cond.makeDeclaration(done); + super.makeDeclaration(done); + } + + /** + * Print the source code for this structured block. This may be + * called only once, because it remembers which local variables + * were declared. + */ + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + boolean needBrace = thenBlock.needsBraces(); + writer.print("if ("); + cond.dumpExpression(writer.EXPL_PAREN, writer); + writer.print(")"); + if (needBrace) + writer.openBrace(); + else + writer.println(); + writer.tab(); + thenBlock.dumpSource(writer); + writer.untab(); + if (elseBlock != null) { + if (needBrace) + writer.closeBraceContinue(); + + if (elseBlock instanceof IfThenElseBlock + && (elseBlock.declare == null + || elseBlock.declare.isEmpty())) { + needBrace = false; + writer.print("else "); + elseBlock.dumpSource(writer); + } else { + needBrace = elseBlock.needsBraces(); + writer.print("else"); + if (needBrace) + writer.openBrace(); + else + writer.println(); + writer.tab(); + elseBlock.dumpSource(writer); + writer.untab(); + } + } + if (needBrace) + writer.closeBrace(); + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return (elseBlock == null) + ? new StructuredBlock[] { thenBlock } + : new StructuredBlock[] { thenBlock, elseBlock }; + } + + /** + * Determines if there is a sub block, that flows through to the end + * of this block. If this returns true, you know that jump is null. + * @return true, if the jump may be safely changed. + */ + public boolean jumpMayBeChanged() { + return (thenBlock.jump != null || thenBlock.jumpMayBeChanged()) + && elseBlock != null + && (elseBlock.jump != null || elseBlock.jumpMayBeChanged()); + } + + public void simplify() { + cond = cond.simplify(); + super.simplify(); + } + + public boolean doTransformations() { + StructuredBlock last = flowBlock.lastModified; + return CreateCheckNull.transformJikes(this, last) + || CreateClassField.transform(this,last); + } +} diff --git a/jode/src/net/sf/jode/flow/InstructionBlock.java b/jode/src/net/sf/jode/flow/InstructionBlock.java new file mode 100644 index 0000000..41a24f3 --- /dev/null +++ b/jode/src/net/sf/jode/flow/InstructionBlock.java @@ -0,0 +1,162 @@ +/* InstructionBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.StoreInstruction; +import net.sf.jode.expr.LocalStoreOperator; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +/** + * This is the structured block for atomic instructions. + */ +public class InstructionBlock extends InstructionContainer { + /** + * The loads that are on the stack before cond is executed. + */ + VariableStack stack; + /** + * The local to which we push to, if the instruction is non void + */ + LocalInfo pushedLocal = null; + + /** + * Tells if this expression is a initializing declaration. This + * can only be set to true and then never be reset. It is changed + * by makeDeclaration. */ + boolean isDeclaration = false; + + public InstructionBlock(Expression instr) { + super(instr); + } + + /** + * This does take the instr into account and modifies stack + * accordingly. It then calls super.mapStackToLocal. + * @param stack the stack before the instruction is called + * @return stack the stack afterwards. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + VariableStack newStack; + int params = instr.getFreeOperandCount(); + if (params > 0) + this.stack = stack.peek(params); + + if (instr.getType() != Type.tVoid) { + pushedLocal = new LocalInfo(); + pushedLocal.setType(instr.getType()); + newStack = stack.poppush(params, pushedLocal); + } else if (params > 0) { + newStack = stack.pop(params); + } else + newStack = stack; + return super.mapStackToLocal(newStack); + } + + public void removePush() { + if (stack != null) + instr = stack.mergeIntoExpression(instr); + if (pushedLocal != null) { + Expression store = new StoreInstruction + (new LocalStoreOperator + (pushedLocal.getType(), pushedLocal)).addOperand(instr); + instr = store; + } + super.removePush(); + } + + /** + * Tells if this block needs braces when used in a if or while block. + * @return true if this block should be sorrounded by braces. + */ + public boolean needsBraces() { + return isDeclaration || (declare != null && !declare.isEmpty()); + } + + /** + * Check if this is an local store instruction to a not yet declared + * variable. In that case mark this as declaration and return the + * variable. + */ + public void checkDeclaration(Set declareSet) { + if (instr instanceof StoreInstruction + && (((StoreInstruction)instr).getLValue() + instanceof LocalStoreOperator)) { + StoreInstruction storeOp = (StoreInstruction) instr; + LocalInfo local = + ((LocalStoreOperator) storeOp.getLValue()).getLocalInfo(); + if (declareSet.contains(local)) { + /* Special case: This is a variable assignment, and + * the variable has not been declared before. We can + * change this to a initializing variable declaration. + */ + isDeclaration = true; + declareSet.remove(local); + } + } + } + + /** + * Make the declarations, i.e. initialize the declare variable + * to correct values. This will declare every variable that + * is marked as used, but not done. + * @param done The set of the already declare variables. + */ + public void makeDeclaration(Set done) { + super.makeDeclaration(done); + checkDeclaration(declare); + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + if (isDeclaration) { + StoreInstruction store = (StoreInstruction) instr; + LocalInfo local = + ((LocalStoreOperator) store.getLValue()).getLocalInfo(); + writer.startOp(writer.NO_PAREN, 0); + local.dumpDeclaration(writer); + writer.breakOp(); + writer.print(" = "); + store.getSubExpressions()[1].makeInitializer(local.getType()); + store.getSubExpressions()[1].dumpExpression(writer.IMPL_PAREN, + writer); + writer.endOp(); + } else { + try { + + if (instr.getType() != Type.tVoid) { + writer.print("PUSH "); + instr.dumpExpression(writer.IMPL_PAREN, writer); + } else + instr.dumpExpression(writer.NO_PAREN, writer); + } catch (RuntimeException ex) { + writer.print("(RUNTIME ERROR IN EXPRESSION)"); + } + } + writer.println(";"); + } +} diff --git a/jode/src/net/sf/jode/flow/InstructionContainer.java b/jode/src/net/sf/jode/flow/InstructionContainer.java new file mode 100644 index 0000000..c7254a4 --- /dev/null +++ b/jode/src/net/sf/jode/flow/InstructionContainer.java @@ -0,0 +1,133 @@ +/* InstructionContainer Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.InvokeOperator; +import net.sf.jode.expr.LocalVarOperator; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +/** + * This is a method for block containing a single instruction. + */ +public abstract class InstructionContainer extends StructuredBlock { + /** + * The instruction. + */ + Expression instr; + + public InstructionContainer(Expression instr) { + this.instr = instr; + } + + /** + * Make the declarations, i.e. initialize the declare variable + * to correct values. This will declare every variable that + * is marked as used, but not done.
+ * + * This will now also combine locals, that use the same slot, have + * compatible types and are declared in the same block.
+ * + * @param done The set of the already declare variables. + */ + public void makeDeclaration(Set done) { + if (instr != null) + instr.makeDeclaration(done); + super.makeDeclaration(done); + } + + /** + * This method should remove local variables that are only written + * and read one time directly after another.
+ * + * This is especially important for stack locals, that are created + * when there are unusual swap or dup instructions, but also makes + * inlined functions more pretty (but not that close to the + * bytecode). + */ + public void removeOnetimeLocals() { + if (instr != null) + instr = instr.removeOnetimeLocals(); + super.removeOnetimeLocals(); + } + + /** + * Fill all in variables into the given VariableSet. + * @param in The VariableSet, the in variables should be stored to. + */ + public void fillInGenSet(Set in, Set gen) { + if (instr != null) + instr.fillInGenSet(in, gen); + } + + public Set getDeclarables() { + Set used = new SimpleSet(); + if (instr != null) + instr.fillDeclarables(used); + return used; + } + + public boolean doTransformations() { + if (instr == null) + return false; + /* Do on the fly access$ transformation, since some further + * operations need this. + */ + if (instr instanceof InvokeOperator) { + Expression expr = ((InvokeOperator)instr).simplifyAccess(); + if (expr != null) + instr = expr; + } + StructuredBlock last = flowBlock.lastModified; + return CreateNewConstructor.transform(this, last) + || CreateAssignExpression.transform(this, last) + || CreateExpression.transform(this, last) + || CreatePrePostIncExpression.transform(this, last) + || CreateIfThenElseOperator.create(this, last) + || CreateConstantArray.transform(this, last) + || CreateCheckNull.transformJavac(this, last); + } + + /** + * Get the contained instruction. + * @return the contained instruction. + */ + public final Expression getInstruction() { + return instr; + } + + public void simplify() { + if (instr != null) + instr = instr.simplify(); + super.simplify(); + } + + /** + * Set the contained instruction. + * @param instr the new instruction. + */ + public final void setInstruction(Expression instr) { + this.instr = instr; + } +} diff --git a/jode/src/net/sf/jode/flow/JsrBlock.java b/jode/src/net/sf/jode/flow/JsrBlock.java new file mode 100644 index 0000000..be3ce30 --- /dev/null +++ b/jode/src/net/sf/jode/flow/JsrBlock.java @@ -0,0 +1,124 @@ +/* JsrBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.type.Type; + +/** + * This block represents a jsr instruction. A jsr instruction is + * used to call the finally block, or to call the monitorexit block in + * a synchronized block. + * + * @author Jochen Hoenicke + */ +public class JsrBlock extends StructuredBlock { + /** + * The inner block that jumps to the subroutine. + */ + StructuredBlock innerBlock; + boolean good = false; + + public JsrBlock() { + innerBlock = new EmptyBlock(); + innerBlock.outer = this; + } + + public void setGood(boolean g) { + good = g; + } + + public boolean isGood() { + return good; + } + + /** + * Sets the successors of this structured block. This should be only + * called once, by FlowBlock.setSuccessors(). + */ + public void setSuccessors(Jump[] jumps) { + if (jumps.length != 2) { + /* A conditional block can only exactly two jumps. */ + throw new IllegalArgumentException("Not exactly two jumps."); + } + innerBlock.setJump(jumps[0]); + setJump(jumps[1]); + } + + /* The implementation of getNext[Flow]Block is the standard + * implementation */ + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + if (innerBlock == oldBlock) + innerBlock = newBlock; + else + return false; + return true; + } + + /** + * This is called after the analysis is completely done. It + * will remove all PUSH/stack_i expressions, (if the bytecode + * is correct).

+ * The default implementation merges the stack after each sub block. + * This may not be, what you want.

+ * + * @param initialStack the stackmap at begin of the block + * @return the stack after the block has executed. + * @throw RuntimeException if something did get wrong. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + /* There shouldn't be any JSR blocks remaining, but who knows. + */ + /* The innerBlock is startet with a new stack entry (return address) + * It should GOTO immediately and never complete. + */ + LocalInfo retAddr = new LocalInfo(); + retAddr.setType(Type.tUObject); + innerBlock.mapStackToLocal(stack.push(retAddr)); + if (jump != null) { + jump.stackMap = stack; + return null; + } + return stack; + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return new StructuredBlock[] { innerBlock }; + } + + public void dumpInstruction(net.sf.jode.decompiler.TabbedPrintWriter writer) + throws java.io.IOException + { + writer.println("JSR"); + writer.tab(); + innerBlock.dumpSource(writer); + writer.untab(); + } +} diff --git a/jode/src/net/sf/jode/flow/Jump.java b/jode/src/net/sf/jode/flow/Jump.java new file mode 100644 index 0000000..9a0c21e --- /dev/null +++ b/jode/src/net/sf/jode/flow/Jump.java @@ -0,0 +1,74 @@ +/* Jump Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; + +/** + * This class represents an unconditional jump. + */ +public class Jump { + /** + * The structured block that precedes this jump. + */ + StructuredBlock prev; + /** + * The destination block of this jump, null if not known, or illegal. + */ + FlowBlock destination; + + /** + * The jumps in a flow block, that have the same destination, are + * in a link list. This field points to the next jump in this link. + */ + Jump next; + + /** + * The stack map. This tells how many objects are on stack at + * begin of the flow block, and to what locals they are maped. + * @see FlowBlock#mapStackToLocal + */ + VariableStack stackMap; + + public Jump (FlowBlock dest) { + this.destination = dest; + } + + public Jump (Jump jump) { + destination = jump.destination; + next = jump.next; + jump.next = this; + } + + /** + * Print the source code for this structured block. This handles + * everything that is unique for all structured blocks and calls + * dumpInstruction afterwards. + * @param writer The tabbed print writer, where we print to. + */ + public void dumpSource(net.sf.jode.decompiler.TabbedPrintWriter writer) + throws java.io.IOException + { + if (destination == null) + writer.println ("GOTO null-ptr!!!!!"); + else + writer.println("GOTO "+destination.getLabel()); + } +} + diff --git a/jode/src/net/sf/jode/flow/LoopBlock.java b/jode/src/net/sf/jode/flow/LoopBlock.java new file mode 100644 index 0000000..58295a8 --- /dev/null +++ b/jode/src/net/sf/jode/flow/LoopBlock.java @@ -0,0 +1,547 @@ +/* LoopBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.ConstOperator; +import net.sf.jode.expr.StoreInstruction; +import net.sf.jode.expr.LocalStoreOperator; +import net.sf.jode.expr.CombineableOperator; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +/** + * This is the structured block for an Loop block. + */ +public class LoopBlock extends StructuredBlock implements BreakableBlock { + + public static final int WHILE = 0; + public static final int DOWHILE = 1; + public static final int FOR = 2; + public static final int POSSFOR = 3; + + public static final Expression TRUE = + new ConstOperator(Boolean.TRUE); + public static final Expression FALSE = + new ConstOperator(Boolean.FALSE); + + /** + * The condition. Must be of boolean type. + */ + Expression cond; + /** + * The stack the condition eats. + */ + VariableStack condStack; + /** + * The init instruction block, only valid if type == POSSFOR + */ + InstructionBlock initBlock; + /** + * The increase instruction block, only valid if type == POSSFOR. + */ + InstructionBlock incrBlock; + + /** + * The init instruction, only valid if type == FOR. + */ + Expression initInstr; + /** + * The increase instruction, only valid if type == FOR. + */ + Expression incrInstr; + + /** + * True, if the initializer is a declaration. + */ + boolean isDeclaration; + + /** + * The type of the loop. This must be one of DOWHILE, WHILE or FOR. + */ + int type; + + /** + * The body of this loop. This is always a valid block and not null. + */ + StructuredBlock bodyBlock; + + /** + * The stack after the break. + */ + VariableStack breakedStack; + + /** + * The stack at begin of the loop. + */ + VariableStack continueStack; + + /*{ invariant { type == POSSFOR || (incrBlock == null && initBlock == null) + :: "(while/do while) with incr"; + type == FOR || (incrInstr == null && initInstr == null) + :: "(while/do while/poss for) with init"; + type != POSSFOR || incrBlock != null + :: "possible for without incr"; + type != FOR || incrInstr != null + :: "for without incr"; + type != POSSFOR || + incrBlock.getInstruction() instanceof CombineableOperator + :: "possible for with invalid incr"; + initBlock == null || + (initBlock.getInstruction() instanceof CombinableOperator) + :: "Initializer is not combinableOperator"; + initInstr == null || + (initInstr instanceof CombinableOperator) + :: "Initializer is not combinableOperator"; + cond != null && cond.getType() == Type.tBoolean + :: "invalid condition type"; + type != POSSFOR || bodyBlock.contains(incr) + :: "incr is not in body of possible for" } }*/ + + /** + * Returns the block where the control will normally flow to, when + * the given sub block is finished (not ignoring the jump + * after this block). (This is overwritten by SequentialBlock and + * SwitchBlock). If this isn't called with a direct sub block, + * the behaviour is undefined, so take care. + * @return null, if the control flows to another FlowBlock. */ + public StructuredBlock getNextBlock(StructuredBlock subBlock) { + return this; + } + + public FlowBlock getNextFlowBlock(StructuredBlock subBlock) { + return null; + } + + public LoopBlock(int type, Expression cond) { + this.type = type; + this.cond = cond; + this.mayChangeJump = (cond == TRUE); + } + + public void setBody(StructuredBlock body) { + bodyBlock = body; + bodyBlock.outer = this; + body.setFlowBlock(flowBlock); + } + + public void setInit(InstructionBlock initBlock) { + if (type == POSSFOR) { + this.initBlock = initBlock; + } else if (type == FOR) { + this.initInstr = initBlock.getInstruction(); + initBlock.removeBlock(); + } + } + + public boolean conditionMatches(CombineableOperator combinable) { + return (type == POSSFOR || cond.containsMatchingLoad(combinable)); + } + + + public Expression getCondition() { + return cond; + } + + public void setCondition(Expression cond) { + this.cond = cond; + if (type == POSSFOR) { + /* We can now say, if this is a for block or not. + */ + if (cond.containsMatchingLoad((CombineableOperator) + incrBlock.getInstruction())) { + type = FOR; + incrInstr = incrBlock.getInstruction(); + incrBlock.removeBlock(); + if (initBlock != null) { + if (cond.containsMatchingLoad + ((CombineableOperator) initBlock.getInstruction())) { + initInstr = initBlock.getInstruction(); + initBlock.removeBlock(); + } + } + } else { + /* This is not a for block, as it seems first. Make + * it a while block again, and forget about init and + * incr. */ + type = WHILE; + } + initBlock = incrBlock = null; + } + mayChangeJump = false; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + if (bodyBlock == oldBlock) + bodyBlock = newBlock; + else + return false; + newBlock.outer = this; + oldBlock.outer = null; + return true; + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return new StructuredBlock[] { bodyBlock }; + } + + /** + * Remove all variables from set, that we can declare inside the + * loop-block. This is the initializer for for-blocks. + */ + public void removeLocallyDeclareable(Set set) { + if (type == FOR && initInstr instanceof StoreInstruction) { + StoreInstruction storeOp = (StoreInstruction) initInstr; + if (storeOp.getLValue() instanceof LocalStoreOperator) { + LocalInfo local = + ((LocalStoreOperator) storeOp.getLValue()).getLocalInfo(); + set.remove(local); + } + } + } + + public Set getDeclarables() { + Set used = new SimpleSet(); + if (type == FOR) { + incrInstr.fillDeclarables(used); + if (initInstr != null) + initInstr.fillDeclarables(used); + } + cond.fillDeclarables(used); + return used; + } + + /** + * Check if this is an local store instruction to a not yet declared + * variable. In that case mark this as declaration and return the + * variable. + */ + public void checkDeclaration(Set declareSet) { + if (initInstr instanceof StoreInstruction + && (((StoreInstruction)initInstr).getLValue() + instanceof LocalStoreOperator)) { + StoreInstruction storeOp = (StoreInstruction) initInstr; + LocalInfo local = + ((LocalStoreOperator) storeOp.getLValue()).getLocalInfo(); + if (declareSet.contains(local)) { + /* Special case: This is a variable assignment, and + * the variable has not been declared before. We can + * change this to a initializing variable declaration. + */ + isDeclaration = true; + declareSet.remove(local); + } + } + } + + /** + * Make the declarations, i.e. initialize the declare variable + * to correct values. This will declare every variable that + * is marked as used, but not done. + * @param done The set of the already declare variables. + */ + public void makeDeclaration(Set done) { + if (type == FOR) { + if (initInstr != null) + initInstr.makeDeclaration(done); + incrInstr.makeDeclaration(done); + } + cond.makeDeclaration(done); + super.makeDeclaration(done); + if (type == FOR && initInstr != null) + checkDeclaration(declare); + } + + public void dumpSource(TabbedPrintWriter writer) + throws java.io.IOException + { + super.dumpSource(writer); + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + if (label != null) { + writer.untab(); + writer.println(label+":"); + writer.tab(); + } + boolean needBrace = bodyBlock.needsBraces(); + switch (type) { + case POSSFOR: + /* a possible for is now treated like a WHILE */ + case WHILE: + if (cond == TRUE) + /* special syntax for endless loops: */ + writer.print("for (;;)"); + else { + writer.print("while ("); + cond.dumpExpression(writer.EXPL_PAREN, writer); + writer.print(")"); + } + break; + case DOWHILE: + writer.print("do"); + break; + case FOR: + writer.print("for ("); + writer.startOp(writer.EXPL_PAREN, 0); + if (initInstr != null) { + if (isDeclaration) { + StoreInstruction store = (StoreInstruction) initInstr; + LocalInfo local = ((LocalStoreOperator) store + .getLValue()).getLocalInfo(); + writer.startOp(writer.NO_PAREN, 1); + local.dumpDeclaration(writer); + writer.breakOp(); + writer.print(" = "); + store.getSubExpressions()[1] + .makeInitializer(local.getType()); + store.getSubExpressions()[1].dumpExpression(writer, 100); + writer.endOp(); + } else + initInstr.dumpExpression(writer.NO_PAREN, writer); + } else { + writer.print("/**/"); + } + writer.print("; "); + writer.breakOp(); + cond.dumpExpression(writer.IMPL_PAREN, writer); + writer.print("; "); + writer.breakOp(); + incrInstr.dumpExpression(writer.NO_PAREN, writer); + writer.endOp(); + writer.print(")"); + break; + } + if (needBrace) + writer.openBrace(); + else + writer.println(); + writer.tab(); + bodyBlock.dumpSource(writer); + writer.untab(); + if (type == DOWHILE) { + if (needBrace) + writer.closeBraceContinue(); + writer.print("while ("); + cond.dumpExpression(writer.EXPL_PAREN, writer); + writer.println(");"); + } else if (needBrace) + writer.closeBrace(); + } + + boolean mayChangeJump = true; + + /** + * The serial number for labels. + */ + static int serialno = 0; + + /** + * The label of this instruction, or null if it needs no label. + */ + String label = null; + + /** + * Returns the label of this block and creates a new label, if + * there wasn't a label previously. + */ + public String getLabel() { + if (label == null) + label = "while_"+(serialno++)+"_"; + return label; + } + + /** + * Is called by BreakBlock, to tell us that this block is breaked. + */ + public void setBreaked() { + mayChangeJump = false; + } + + /** + * This is called after the analysis is completely done. It + * will remove all PUSH/stack_i expressions, (if the bytecode + * is correct). + * @param stack the stack at begin of the block + * @return null if there is no way to the end of this block, + * otherwise the stack after the block has executed. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + if (type == DOWHILE) { + VariableStack afterBody = bodyBlock.mapStackToLocal(stack); + if (afterBody != null) + mergeContinueStack(afterBody); + + if (continueStack != null) { + VariableStack newStack; + int params = cond.getFreeOperandCount(); + if (params > 0) { + condStack = continueStack.peek(params); + newStack = continueStack.pop(params); + } else + newStack = continueStack; + + if (cond != TRUE) + mergeBreakedStack(newStack); + if (cond != FALSE) + stack.merge(newStack); + } + } else { + continueStack = stack; + VariableStack newStack; + int params = cond.getFreeOperandCount(); + if (params > 0) { + condStack = stack.peek(params); + newStack = stack.pop(params); + } else + newStack = stack; + if (cond != TRUE) + breakedStack = newStack; + VariableStack afterBody = bodyBlock.mapStackToLocal(newStack); + if (afterBody != null) + mergeContinueStack(afterBody); + } + return breakedStack; + } + + /** + * Is called by BreakBlock, to tell us what the stack can be after a + * break. + * @return false if the stack is inconsistent. + */ + public void mergeContinueStack(VariableStack stack) { + if (continueStack == null) + continueStack = stack; + else + continueStack.merge(stack); + } + + /** + * Is called by BreakBlock, to tell us what the stack can be after a + * break. + * @return false if the stack is inconsistent. + */ + public void mergeBreakedStack(VariableStack stack) { + if (breakedStack != null) + breakedStack.merge(stack); + else + breakedStack = stack; + } + + public void removePush() { + if (condStack != null) + cond = condStack.mergeIntoExpression(cond); + bodyBlock.removePush(); + } + + /** + * This method should remove local variables that are only written + * and read one time directly after another.
+ * + * This is especially important for stack locals, that are created + * when there are unusual swap or dup instructions, but also makes + * inlined functions more pretty (but not that close to the + * bytecode). + */ + public void removeOnetimeLocals() { + cond = cond.removeOnetimeLocals(); + if (type == FOR) { + if (initInstr != null) + initInstr.removeOnetimeLocals(); + incrInstr.removeOnetimeLocals(); + } + super.removeOnetimeLocals(); + } + + /** + * Replace all breaks to block with a continue to this. + * @param block the breakable block where the breaks originally + * breaked to (Have a break now, if you didn't understand that :-). + */ + public void replaceBreakContinue(BreakableBlock block) { + java.util.Stack todo = new java.util.Stack(); + todo.push(block); + while (!todo.isEmpty()) { + StructuredBlock[] subs = + ((StructuredBlock)todo.pop()).getSubBlocks(); + for (int i=0; i 0) { + this.stack = stack.peek(params); + newStack = stack.pop(params); + } + } + return null; + } + + public void removePush() { + if (stack != null) + instr = stack.mergeIntoExpression(instr); + } + + /** + * Tells if this block needs braces when used in a if or while block. + * @return true if this block should be sorrounded by braces. + */ + public boolean needsBraces() { + return declare != null && !declare.isEmpty(); + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + writer.print("return"); + if (instr != null) { + writer.print(" "); + instr.dumpExpression(writer.IMPL_PAREN, writer); + } + writer.println(";"); + } +} diff --git a/jode/src/net/sf/jode/flow/SequentialBlock.java b/jode/src/net/sf/jode/flow/SequentialBlock.java new file mode 100644 index 0000000..8ee7279 --- /dev/null +++ b/jode/src/net/sf/jode/flow/SequentialBlock.java @@ -0,0 +1,249 @@ +/* SequentialBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.expr.LocalStoreOperator; +import net.sf.jode.expr.StoreInstruction; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +/** + * A sequential block combines exactly two structured blocks to a new + * one. The first sub block mustn't be another sequential block, + * instead the second sub block should be used for this. + */ +public class SequentialBlock extends StructuredBlock { + StructuredBlock[] subBlocks; + + public SequentialBlock() { + subBlocks = new StructuredBlock[2]; + } + + public void setFirst(StructuredBlock sb) { + subBlocks[0] = sb; + sb.outer = this; + sb.setFlowBlock(flowBlock); + } + + public void setSecond(StructuredBlock sb) { + subBlocks[1] = sb; + sb.outer = this; + sb.setFlowBlock(flowBlock); + } + + public void checkConsistent() { + super.checkConsistent(); + if (subBlocks[0].jump != null + || subBlocks[0] instanceof SequentialBlock + || jump != null) + throw new InternalError("Inconsistency"); + } + + /** + * This does take the instr into account and modifies stack + * accordingly. It then calls super.mapStackToLocal. + * @param stack the stack before the instruction is called + * @return stack the stack afterwards. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + if (stack == null) + net.sf.jode.GlobalOptions.err.println("map stack to local called with null: " + this+ " in "+this.flowBlock); + VariableStack middle = subBlocks[0].mapStackToLocal(stack); + if (middle != null) + // Otherwise the second block is at least "logical" dead code + return subBlocks[1].mapStackToLocal(middle); + net.sf.jode.GlobalOptions.err.println("Dead code after Block " + subBlocks[0]); + return null; + } + + /** + * This method should remove local variables that are only written + * and read one time directly after another.
+ * + * This is especially important for stack locals, that are created + * when there are unusual swap or dup instructions, but also makes + * inlined functions more pretty (but not that close to the + * bytecode). + */ + public void removeOnetimeLocals() { + StructuredBlock secondBlock = subBlocks[1]; + if (secondBlock instanceof SequentialBlock) + secondBlock = ((SequentialBlock)secondBlock).subBlocks[0]; + if (subBlocks[0] instanceof InstructionBlock + && secondBlock instanceof InstructionContainer) { + InstructionBlock first = (InstructionBlock) subBlocks[0]; + InstructionContainer second = (InstructionContainer) secondBlock; + /* check if subBlocks[0] writes to a local, second reads + * that local, the local is only used by this two blocks, + * and there are no side effects. In that case replace + * the LoadLocal with the righthandside of subBlocks[0] + * and replace subBlocks[1] with this block. Call + * removeOnetimelLocals on subBlocks[1] afterwards and + * return. + */ + + if (first.getInstruction() instanceof StoreInstruction) { + StoreInstruction store + = (StoreInstruction) first.getInstruction(); + if (store.getLValue() instanceof LocalStoreOperator + && (((LocalStoreOperator) store.getLValue()) + .getLocalInfo().getUseCount() == 2) + && (second.getInstruction().canCombine(store) > 0)) { + System.err.println("before: "+first+second); + + second.setInstruction(second.getInstruction() + .combine(store)); + System.err.println("after: "+second); + StructuredBlock sb = subBlocks[1]; + sb.moveDefinitions(this, sb); + sb.replace(this); + sb.removeOnetimeLocals(); + return; + } + } + } + super.removeOnetimeLocals(); + } + + /** + * Returns the block where the control will normally flow to, when + * the given sub block is finished (not ignoring the jump + * after this block). (This is overwritten by SequentialBlock and + * SwitchBlock). If this isn't called with a direct sub block, + * the behaviour is undefined, so take care. + * @return null, if the control flows to another FlowBlock. */ + public StructuredBlock getNextBlock(StructuredBlock subBlock) { + if (subBlock == subBlocks[0]) { + if (subBlocks[1].isEmpty()) + return subBlocks[1].getNextBlock(); + else + return subBlocks[1]; + } + return getNextBlock(); + } + + public FlowBlock getNextFlowBlock(StructuredBlock subBlock) { + if (subBlock == subBlocks[0]) { + if (subBlocks[1].isEmpty()) + return subBlocks[1].getNextFlowBlock(); + else + return null; + } + return getNextFlowBlock(); + } + + /** + * Tells if the sub block is the single exit point of the current block. + * @param subBlock the sub block. + * @return true, if the sub block is the single exit point of the + * current block. + */ + public boolean isSingleExit(StructuredBlock subBlock) { + return (subBlock == subBlocks[1]); + } + + /** + * Propagate the used set. Sequential blocks are special, because + * they "use" everything the first block uses. This is, because + * the first block can't declare something that is only visible in + * the first block. + * + * @return all locals that are used in this block or in some sub + * block (this is not the used set). + */ + public Set propagateUsage() { + used = new SimpleSet(); + Set allUse = new SimpleSet(); + Set childUse0 = subBlocks[0].propagateUsage(); + Set childUse1 = subBlocks[1].propagateUsage(); + /* All variables used somewhere inside both sub blocks, are + * used in this block, too. + * Also the variables used in first block are used in this + * block, except when it can be declared locally. (Note that + * subBlocks[0].used != childUse0) + */ + used.addAll(subBlocks[0].used); + if (subBlocks[0] instanceof LoopBlock) + ((LoopBlock) subBlocks[0]).removeLocallyDeclareable(used); + allUse.addAll(childUse0); + allUse.addAll(childUse1); + childUse0.retainAll(childUse1); + used.addAll(childUse0); + return allUse; + } + + /** + * Make the declarations, i.e. initialize the declare variable + * to correct values. This will declare every variable that + * is marked as used, but not done. + * @param done The set of the already declare variables. + */ + public void makeDeclaration(Set done) { + super.makeDeclaration(done); + if (subBlocks[0] instanceof InstructionBlock) + /* An instruction block may declare a variable for us. + */ + ((InstructionBlock) subBlocks[0]).checkDeclaration(this.declare); + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + subBlocks[0].dumpSource(writer); + subBlocks[1].dumpSource(writer); + } + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + for (int i=0; i<2; i++) { + if (subBlocks[i] == oldBlock) { + subBlocks[i] = newBlock; + return true; + } + } + return false; + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return subBlocks; + } + + /** + * Determines if there is a sub block, that flows through to the end + * of this block. If this returns true, you know that jump is null. + * @return true, if the jump may be safely changed. + */ + public boolean jumpMayBeChanged() { + return (subBlocks[1].jump != null || subBlocks[1].jumpMayBeChanged()); + } +} diff --git a/jode/src/net/sf/jode/flow/SlotSet.java b/jode/src/net/sf/jode/flow/SlotSet.java new file mode 100644 index 0000000..aef2e2a --- /dev/null +++ b/jode/src/net/sf/jode/flow/SlotSet.java @@ -0,0 +1,217 @@ +/* SlotSet Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.util.ArrayEnum; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.AbstractSet; +import java.util.Set; +import java.util.Iterator; +///#enddef + +/** + * This class represents a set of local info, all having different + * slots. It is used for representing the in sets of flow block.

+ * + * Its add method will automatically merge any localinfo that have + * the same slot and is in the method.

+ */ +public final class SlotSet extends AbstractSet implements Cloneable { + LocalInfo[] locals; + int count; + + /** + * Creates a new empty variable set + */ + public SlotSet() { + locals = null; + count = 0; + } + + /** + * Creates a new pre initialized variable set + */ + public SlotSet(LocalInfo[] locals) { + count = locals.length; + this.locals = locals; + } + + public final void grow(int size) { + if (locals != null) { + size += count; + if (size > locals.length) { + int nextSize = locals.length * 2; +// GlobalOptions.err.println("wanted: "+size+" next: "+nextSize); + LocalInfo[] newLocals + = new LocalInfo[nextSize > size ? nextSize : size]; + System.arraycopy(locals, 0, newLocals, 0, count); + locals = newLocals; + } + } else if (size > 0) + locals = new LocalInfo[size]; + } + + /** + * Adds a local info to this variable set. + */ + public boolean add(Object o) { + LocalInfo li = (LocalInfo) o; + LocalInfo contained = findSlot(li.getSlot()); + if (contained != null) { + li.combineWith(contained); + return false; + } else { + grow(1); + locals[count++] = li; + return true; + } + } + + public final boolean contains(Object o) { + return containsSlot(((LocalInfo)o).getSlot()); + } + + /** + * Checks if the variable set contains a local with the given name. + */ + public final boolean containsSlot(int slot) { + return findSlot(slot) != null; + } + + /** + * Checks if the variable set contains a local with the given slot. + */ + public LocalInfo findSlot(int slot) { + for (int i=0; i 0) { + other.locals = new LocalInfo[count]; + System.arraycopy(locals, 0, other.locals, 0, count); + } + return other; + } catch (CloneNotSupportedException ex) { + throw new InternalError("Clone?"); + } + } + + /** + * Merges this SlotSet with another. For all slots occuring + * in both variable sets, all corresponding LocalInfos are merged. + * The sets are not changed (use addAll for this). + * @return The merged variables. + * @param vs the other variable set. */ + public void merge(VariableSet vs) { + for (int i=0; i + *

  • if-then-(else)-block (IfThenElseBlock) + *
  • (do)-while/for-block (LoopBlock) + *
  • switch-block (SwitchBlock) + *
  • try-catch-block (CatchBlock) + *
  • try-finally-block (FinallyBlock) + *
  • synchronized-block (SynchronizedBlock) + *
  • one-instruction (InstructionBlock) + *
  • empty-block (EmptyBlock) + *
  • multi-blocks-block (SequentialBlock) + * + */ + +public abstract class StructuredBlock { + /* Invariants: + * outer != null ==> flowBlock = outer.flowBlock; + * outer == null ==> flowBlock.block = this; + * jump == null ==> outer != null; + * getNextBlock() != null ^ getNextFlowBlock() != null; + * outer != null ==> + * outer.getNextBlock(this) != null + * ^ outer.getNextFlowBlock(this) != null; + */ + + /** + * The Set containing all Declarables that are used in this + * block. + */ + Set used; + + /** + * The Set containing all Declarables we must declare. + * The analyzation is done in makeDeclaration + */ + Set declare; + Set done; + + /** + * The surrounding structured block. If this is the outermost + * block in a flow block, outer is null. */ + StructuredBlock outer; + +// /** +// * The surrounding non sequential block. This is the same as if +// * you would repeatedly get outer until you reach a non sequential +// * block. This is field is only valid, if the outer block is a +// * sequential block. +// */ +// StructuredBlock realOuter; + + /** + * The flow block in which this structured block lies. */ + FlowBlock flowBlock; + + /** + * The jump that follows on this block, or null if there is no + * jump, but the control flows normal (only allowed if + * getNextBlock != null). + */ + Jump jump; + + /** + * Returns the block where the control will normally flow to, when + * this block is finished. + */ + public StructuredBlock getNextBlock() { + if (jump != null) + return null; + if (outer != null) + return outer.getNextBlock(this); + return null; + } + + /** + * Sets the successors of this structured block. This should be only + * called once, by FlowBlock.setSuccessors(). + */ + public void setSuccessors(Jump[] jumps) { + if (jumps.length > 1) { + /* A normal block can only handle a single jump. */ + throw new IllegalArgumentException("Too many jumps."); + } + setJump(jumps[0]); + } + + public void setJump(Jump jump) { + this.jump = jump; + jump.prev = this; + } + + /** + * Returns the flow block where the control will normally flow to, + * when this block is finished. + * @return null, if the control flows into a non empty structured + * block or if this is the outermost block. + */ + public FlowBlock getNextFlowBlock() { + if (jump != null) + return jump.destination; + if (outer != null) + return outer.getNextFlowBlock(this); + return null; + } + + /** + * Returns the block where the control will normally flow to, when + * the given sub block is finished. (This is overwritten by + * SequentialBlock and SwitchBlock). If this isn't called with a + * direct sub block, the behaviour is undefined, so take care. + * @return null, if the control flows to another FlowBlock. */ + public StructuredBlock getNextBlock(StructuredBlock subBlock) { + return getNextBlock(); + } + + public FlowBlock getNextFlowBlock(StructuredBlock subBlock) { + return getNextFlowBlock(); + } + + /** + * Tells if this block is empty and only changes control flow. + */ + public boolean isEmpty() { + return false; + } + + /** + * Tells if the sub block is the single exit point of the current block. + * @param subBlock the sub block. + * @return true, if the sub block is the single exit point of the + * current block. + */ + public boolean isSingleExit(StructuredBlock subBlock) { + return false; + } + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + return false; + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return new StructuredBlock[0]; + } + + /** + * Returns if this block contains the given block. + * @param child the block which should be contained by this block. + * @return false, if child is null, or is not contained in this block. + */ + public boolean contains(StructuredBlock child) { + while (child != this && child != null) + child = child.outer; + return (child == this); + } + + /** + * Removes the jump. This does not update the successors vector + * of the flow block, you have to do it yourself. + */ + public final void removeJump() { + if (jump != null) { + jump.prev = null; + jump = null; + } + } + + /** + * This will move the definitions of sb and childs to this block, + * but only descend to sub and not further. It is assumed that + * sub will become a sub block of this block, but may not yet. + * + * @param sb The structured block that should be replaced. + * @param sub The uppermost sub block of structured block, that + * will be moved to this block (may be this). + */ + void moveDefinitions(StructuredBlock from, StructuredBlock sub) { + } + + /** + * This function replaces sb with this block. It copies outer and + * from sb, and updates the outer block, so it knows that sb was + * replaced. You have to replace sb.outer or mustn't use sb + * anymore.

    + * It will also move the definitions of sb and childs to this block, + * but only descend to sub and not further. It is assumed that + * sub will become a sub block of this block. + * @param sb The structured block that should be replaced. + * @param sub The uppermost sub block of structured block, + * that will be moved to this block (may be this). + */ + public void replace(StructuredBlock sb) { + outer = sb.outer; + setFlowBlock(sb.flowBlock); + if (outer != null) { + outer.replaceSubBlock(sb, this); + } else { + flowBlock.block = this; + } + } + + /** + * This function swaps the jump with another block. + * @param block The block whose jump is swapped. + */ + public void swapJump(StructuredBlock block) { + Jump tmp = block.jump; + block.jump = jump; + jump = tmp; + + jump.prev = this; + block.jump.prev = block; + } + + /** + * This function moves the jump to this block. + * The jump field of the previous owner is cleared afterwards. + * If the given jump is null, nothing bad happens. + * @param jump The jump that should be moved, may be null. + */ + public void moveJump(Jump jump) { + if (this.jump != null) + throw new InternalError("overriding with moveJump()"); + this.jump = jump; + if (jump != null) { + jump.prev.jump = null; + jump.prev = this; + } + } + + /** + * This function copies the jump to this block. + * If the given jump is null, nothing bad happens. + * @param jump The jump that should be moved, may be null. + */ + public void copyJump(Jump jump) { + if (this.jump != null) + throw new InternalError("overriding with moveJump()"); + if (jump != null) { + this.jump = new Jump(jump); + this.jump.prev = this; + } + } + + /** + * Appends a block to this block. + * @return the new combined block. + */ + public StructuredBlock appendBlock(StructuredBlock block) { + if (block instanceof EmptyBlock) { + moveJump(block.jump); + return this; + } else { + SequentialBlock sequBlock = new SequentialBlock(); + sequBlock.replace(this); + sequBlock.setFirst(this); + sequBlock.setSecond(block); + return sequBlock; + } + } + + /** + * Prepends a block to this block. + * @return the new combined block. + */ + public StructuredBlock prependBlock(StructuredBlock block) { + SequentialBlock sequBlock = new SequentialBlock(); + sequBlock.replace(this); + sequBlock.setFirst(block); + sequBlock.setSecond(this); + return sequBlock; + } + + /** + * Removes this block, or replaces it with an EmptyBlock. + */ + public final void removeBlock() { + + if (outer instanceof SequentialBlock) { + if (outer.getSubBlocks()[1] == this) { + if (jump != null) + outer.getSubBlocks()[0].moveJump(jump); + outer.getSubBlocks()[0].replace(outer); + } else + outer.getSubBlocks()[1].replace(outer); + return; + } + + EmptyBlock eb = new EmptyBlock(); + eb.moveJump(jump); + eb.replace(this); + } + + /** + * Determines if there is a path, that flows through the end + * of this block. If there is such a path, it is forbidden to + * change the control flow in after this block and this method + * returns false. + * @return true, if the jump may be safely changed. + */ + public boolean flowMayBeChanged() { + return jump != null || jumpMayBeChanged(); + } + + public boolean jumpMayBeChanged() { + return false; + } + + public Set getDeclarables() { + return Collections.EMPTY_SET; + } + + /** + * Propagate the used set. Initially the used block contains the + * local that are used in some expression directly in this block. + * This will extend the set, so that a variable is used if it is + * used in at least two sub blocks. + * + * @return all locals that are used in this block or in some sub + * block (this is not the used set). */ + public Set propagateUsage() { + used = new SimpleSet(); + used.addAll(getDeclarables()); + StructuredBlock[] subs = getSubBlocks(); + Set allUse = new SimpleSet(); + allUse.addAll(used); + for (int i=0; i + * The default implementation merges the stack after each sub block. + * This may not be, what you want.

    + * + * @param initialStack the stackmap at begin of the block + * @return the stack after the block has executed. + * @throw RuntimeException if something did get wrong. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + StructuredBlock[] subBlocks = getSubBlocks(); + VariableStack after; + if (subBlocks.length == 0) + after = stack; + else { + after = null; + for (int i=0; i< subBlocks.length; i++) { + after = VariableStack.merge + (after, subBlocks[i].mapStackToLocal(stack)); + } + } + if (jump != null) { + /* assert(after != null) */ + jump.stackMap = after; + return null; + } + return after; + } + + /** + * This is called after mapStackToLocal to do the stack to local + * transformation. + */ + public void removePush() { + StructuredBlock[] subBlocks = getSubBlocks(); + for (int i=0; i< subBlocks.length; i++) + subBlocks[i].removePush(); + } + + /** + * This method should remove local variables that are only written + * and read one time directly after another.
    + * + * This is especially important for stack locals, that are created + * when there are unusual swap or dup instructions, but also makes + * inlined functions more pretty (but not that close to the + * bytecode). + */ + public void removeOnetimeLocals() { + StructuredBlock[] subBlocks = getSubBlocks(); + for (int i=0; i< subBlocks.length; i++) + subBlocks[i].removeOnetimeLocals(); + } + + /** + * Make the declarations, i.e. initialize the declare variable + * to correct values. This will declare every variable that + * is marked as used, but not done.
    + * + * This will now also combine locals, that use the same slot, have + * compatible types and are declared in the same block.
    + * + * @param done The set of the already declare variables. + */ + public void makeDeclaration(Set done) { + this.done = new SimpleSet(); + this.done.addAll(done); + + declare = new SimpleSet(); + Iterator iter = used.iterator(); + next_used: + while (iter.hasNext()) { + Declarable declarable = (Declarable) iter.next(); + + // Check if this is already declared. + if (done.contains(declarable)) + continue next_used; + + if (declarable instanceof LocalInfo) { + LocalInfo local = (LocalInfo) declarable; + + /* First generate the names for the locals, since this may + * also change their types, if they are in the local + * variable table. + */ + String localName = local.guessName(); + + // Merge with all locals in this block, that use the same + // slot and have compatible types and names. + Iterator doneIter = done.iterator(); + while (doneIter.hasNext()) { + Declarable previous = (Declarable) doneIter.next(); + if (!(previous instanceof LocalInfo)) + continue; + LocalInfo prevLocal = (LocalInfo) previous; + /* We only merge locals living in the same + * method and having the same slot. + * + * We don't want to merge variables, whose names + * are not generated by us and differ. And we + * don't want to merge special locals that have a + * constant expression, e.g. this. + */ + if (prevLocal.getMethodAnalyzer() + == local.getMethodAnalyzer() + && prevLocal.getSlot() == local.getSlot() + && prevLocal.getType().isOfType(local.getType()) + && (prevLocal.isNameGenerated() + || local.isNameGenerated() + || localName.equals(prevLocal.getName())) + && !prevLocal.isFinal() + && !local.isFinal() + && prevLocal.getExpression() == null + && local.getExpression() == null) { + local.combineWith(prevLocal); + continue next_used; + } + } + } + + if (declarable.getName() != null) { + Iterator doneIter = done.iterator(); + while (doneIter.hasNext()) { + Declarable previous = (Declarable) doneIter.next(); + if (declarable.getName().equals(previous.getName())) { + /* A name conflict happened. */ + declarable.makeNameUnique(); + break; + } + } + } + done.add(declarable); + declare.add(declarable); + if (declarable instanceof ClassAnalyzer) + ((ClassAnalyzer) declarable).makeDeclaration(done); + } + StructuredBlock[] subs = getSubBlocks(); + for (int i=0; i c2.value) + return 1; + return 0; + } + }; + Arrays.sort(caseBlocks, caseBlockComparator); + + int newCases = 0; + for (int i=0; i < caseBlocks.length; i++) { + Jump jump = caseBlocks[i].subBlock.jump; + if (i < caseBlocks.length - 1 + && jump.destination + == caseBlocks[i+1].subBlock.jump.destination) { + // This case falls into the next one. + caseBlocks[i].subBlock.removeJump(); + flowBlock.removeSuccessor(jump); + + if (caseBlocks[i+1].subBlock.jump.destination == defaultDest) + continue; // remove this case, it jumps to the default. + } + caseBlocks[newCases++] = caseBlocks[i]; + } + caseBlocks[newCases-1].isLastBlock = true; + + CaseBlock[] newCaseBlocks = new CaseBlock[newCases]; + System.arraycopy(caseBlocks, 0, newCaseBlocks, 0, newCases); + caseBlocks = newCaseBlocks; + } + + /** + * This does take the instr into account and modifies stack + * accordingly. It then calls super.mapStackToLocal. + * @param stack the stack before the instruction is called + * @return stack the stack afterwards. + */ + public VariableStack mapStackToLocal(VariableStack stack) { + VariableStack newStack; + int params = instr.getFreeOperandCount(); + if (params > 0) { + exprStack = stack.peek(params); + newStack = stack.pop(params); + } else + newStack = stack; + VariableStack lastStack = newStack; + for (int i=0; i< caseBlocks.length; i++) { + if (lastStack != null) + newStack.merge(lastStack); + lastStack = caseBlocks[i].mapStackToLocal(newStack); + } + if (lastStack != null) + mergeBreakedStack(lastStack); + if (jump != null) { + jump.stackMap = breakedStack; + return null; + } + return breakedStack; + } + + /** + * Is called by BreakBlock, to tell us what the stack can be after a + * break. + */ + public void mergeBreakedStack(VariableStack stack) { + if (breakedStack != null) + breakedStack.merge(stack); + else + breakedStack = stack; + } + + public void removePush() { + if (exprStack != null) + instr = exprStack.mergeIntoExpression(instr); + super.removePush(); + } + + /** + * Find the case that jumps directly to destination. + * @return The sub block of the case block, which jumps to destination. + */ + public StructuredBlock findCase(FlowBlock destination) { + for (int i=0; i < caseBlocks.length; i++) { + if (caseBlocks[i].subBlock != null + && caseBlocks[i].subBlock instanceof EmptyBlock + && caseBlocks[i].subBlock.jump != null + && caseBlocks[i].subBlock.jump.destination == destination) + + return caseBlocks[i].subBlock; + } + return null; + } + + /** + * Find the case that precedes the given case. + * @param block The sub block of the case, whose predecessor should + * be returned. + * @return The sub block of the case precedes the given case. + */ + public StructuredBlock prevCase(StructuredBlock block) { + for (int i=caseBlocks.length-1; i>=0; i--) { + if (caseBlocks[i].subBlock == block) { + for (i--; i>=0; i--) { + if (caseBlocks[i].subBlock != null) + return caseBlocks[i].subBlock; + } + } + } + return null; + } + + /** + * Returns the block where the control will normally flow to, when + * the given sub block is finished (not ignoring the jump + * after this block). (This is overwritten by SequentialBlock and + * SwitchBlock). If this isn't called with a direct sub block, + * the behaviour is undefined, so take care. + * @return null, if the control flows to another FlowBlock. */ + public StructuredBlock getNextBlock(StructuredBlock subBlock) { + for (int i=0; i< caseBlocks.length-1; i++) { + if (subBlock == caseBlocks[i]) { + return caseBlocks[i+1]; + } + } + return getNextBlock(); + } + + public FlowBlock getNextFlowBlock(StructuredBlock subBlock) { + for (int i=0; i< caseBlocks.length-1; i++) { + if (subBlock == caseBlocks[i]) { + return null; + } + } + return getNextFlowBlock(); + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + if (label != null) { + writer.untab(); + writer.println(label+":"); + writer.tab(); + } + writer.print("switch ("); + instr.dumpExpression(writer); + writer.print(")"); + writer.openBrace(); + for (int i=0; i < caseBlocks.length; i++) + caseBlocks[i].dumpSource(writer); + writer.closeBrace(); + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return caseBlocks; + } + + boolean isBreaked = false; + + /** + * The serial number for labels. + */ + static int serialno = 0; + + /** + * The label of this instruction, or null if it needs no label. + */ + String label = null; + + /** + * Returns the label of this block and creates a new label, if + * there wasn't a label previously. + */ + public String getLabel() { + if (label == null) + label = "switch_"+(serialno++)+"_"; + return label; + } + + /** + * Is called by BreakBlock, to tell us that this block is breaked. + */ + public void setBreaked() { + isBreaked = true; + } + + /** + * Determines if there is a sub block, that flows through to the end + * of this block. If this returns true, you know that jump is null. + * @return true, if the jump may be safely changed. + */ + public boolean jumpMayBeChanged() { + return !isBreaked + && (caseBlocks[caseBlocks.length-1].jump != null + || caseBlocks[caseBlocks.length-1].jumpMayBeChanged()); + } +} diff --git a/jode/src/net/sf/jode/flow/SynchronizedBlock.java b/jode/src/net/sf/jode/flow/SynchronizedBlock.java new file mode 100644 index 0000000..04f7fd0 --- /dev/null +++ b/jode/src/net/sf/jode/flow/SynchronizedBlock.java @@ -0,0 +1,127 @@ +/* SynchronizedBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.expr.Expression; +import net.sf.jode.util.SimpleSet; + +///#def COLLECTIONS java.util +import java.util.Set; +///#enddef + +/** + * This class represents a synchronized structured block. + * + * @author Jochen Hoenicke + */ +public class SynchronizedBlock extends StructuredBlock { + + Expression object; + LocalInfo local; + boolean isEntered; + + StructuredBlock bodyBlock; + + public SynchronizedBlock(LocalInfo local) { + this.local = local; + } + + /** + * Sets the body block. + */ + public void setBodyBlock(StructuredBlock body) { + bodyBlock = body; + body.outer = this; + body.setFlowBlock(flowBlock); + } + + /** + * Returns all sub block of this structured block. + */ + public StructuredBlock[] getSubBlocks() { + return new StructuredBlock[] { bodyBlock }; + } + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + if (bodyBlock == oldBlock) + bodyBlock = newBlock; + else + return false; + return true; + } + + public Set getDeclarables() { + Set used = new SimpleSet(); + if (object != null) + object.fillDeclarables(used); + else + used.add(local); + return used; + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + if (!isEntered) + writer.println("MISSING MONITORENTER"); + writer.print("synchronized ("); + if (object != null) + object.dumpExpression(writer.EXPL_PAREN, writer); + else + writer.print(local.getName()); + writer.print(")"); + writer.openBrace(); + writer.tab(); + bodyBlock.dumpSource(writer); + writer.untab(); + writer.closeBrace(); + } + + public void simplify() { + if (object != null) + object = object.simplify(); + super.simplify(); + } + + /** + * Determines if there is a sub block, that flows through to the end + * of this block. If this returns true, you know that jump is null. + * @return true, if the jump may be safely changed. + */ + public boolean jumpMayBeChanged() { + return (bodyBlock.jump != null || bodyBlock.jumpMayBeChanged()); + } + + + public boolean doTransformations() { + StructuredBlock last = flowBlock.lastModified; + return (!isEntered && CompleteSynchronized.enter(this, last)) + || (isEntered && object == null + && CompleteSynchronized.combineObject(this, last)); + } +} diff --git a/jode/src/net/sf/jode/flow/ThrowBlock.java b/jode/src/net/sf/jode/flow/ThrowBlock.java new file mode 100644 index 0000000..018db20 --- /dev/null +++ b/jode/src/net/sf/jode/flow/ThrowBlock.java @@ -0,0 +1,39 @@ +/* ThrowBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.expr.Expression; + +/** + * This is the structured block for an Throw block. + */ +public class ThrowBlock extends ReturnBlock { + public ThrowBlock(Expression instr) { + super(instr); + } + + public void dumpInstruction(TabbedPrintWriter writer) + throws java.io.IOException + { + writer.print("throw "); + instr.dumpExpression(writer.NO_PAREN, writer); + writer.println(";"); + } +} diff --git a/jode/src/net/sf/jode/flow/TransformConstructors.java b/jode/src/net/sf/jode/flow/TransformConstructors.java new file mode 100644 index 0000000..95f2d3d --- /dev/null +++ b/jode/src/net/sf/jode/flow/TransformConstructors.java @@ -0,0 +1,1111 @@ +/* TransformConstructors Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import java.lang.reflect.Modifier; +import net.sf.jode.GlobalOptions; +import net.sf.jode.decompiler.Analyzer; +import net.sf.jode.decompiler.ClassAnalyzer; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.FieldAnalyzer; +import net.sf.jode.decompiler.MethodAnalyzer; +import net.sf.jode.decompiler.Options; +import net.sf.jode.decompiler.OuterValues; +import net.sf.jode.decompiler.OuterValueListener; +import net.sf.jode.expr.*; +import net.sf.jode.type.MethodType; +import net.sf.jode.type.Type; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.MethodInfo; + +import java.io.IOException; +import java.util.Vector; +import java.util.Enumeration; + +/** + * This class will transform the constructors. We differ three types of + * constructors: + *

    type0
    + *
    are constructors, that call no constructors or whose default super + * call was already removed. java.lang.Object.<init> + * and static constructors are examples for the first kind.
    + *
    type1
    + *
    are constructors, that call the constructor of super class
    + *
    type2
    + *
    are constructors, that call another constructor of the same class
    + *
    + * + * The transformation involves several steps: + * + * The first step is done by removeSynthInitializers, which does the following: + *
    • For inner classes check if the this$0 field(s) is/are + * initialized corectly, remove the initializer and mark that + * field.
    • + *
    • For method scope classes also check the val$xx fields.
    • + *

    + * + * In the last analyze phase (makeDeclaration) the rest is done: + *
      + *
    • remove implicit super() call
    • + *
    • move constant field initializations that occur in all constructors + * (except those that start with a this() call) to the fields.
    • + *
    • For jikes class check for a constructor$xx call, and mark that + * as the real constructor, moving the super call of the original + * constructor
    • + *
    • For anonymous classes check that the constructor only contains + * a super call and mark it as default
    + * + * It will make use of the outerValues expression, that + * tell which parameters (this and final method variables) are always + * given to the constructor. + * + * You can debug this class with the --debug=constructors + * switch. + * + * @author Jochen Hoenicke + * @see net.sf.jode.decompiler.FieldAnalyzer#setInitializer + * @see net.sf.jode.decompiler.ClassAnalyzer#getOuterValues */ +public class TransformConstructors { + /* What is sometimes confusing is the distinction between slot and + * parameter. Most times parameter nr = slot nr, but double and + * long parameters take two slots, so the remaining parameters + * will shift. + */ + + ClassAnalyzer clazzAnalyzer; + boolean isStatic; + + /* The method analyzers of the constructors: type0 constructors + * come first, then type1, then type2. + */ + MethodAnalyzer[] cons; + int type0Count; + int type01Count; + + OuterValues outerValues; + + public TransformConstructors(ClassAnalyzer clazzAnalyzer, + boolean isStatic, MethodAnalyzer[] cons) { + this.clazzAnalyzer = clazzAnalyzer; + this.isStatic = isStatic; + this.cons = cons; + if (!isStatic) + this.outerValues = clazzAnalyzer.getOuterValues(); + lookForConstructorCall(); + } + + /** + * Returns the type of the constructor. We differ three types of + * constructors: + *
    type1
    + *
    are constructors, that call the constructor of super class
    + *
    type2
    + *
    are constructors, that call another constructor of the same + * class
    + *
    type0
    + *
    are constructors, that call no constructors. + * java.lang.Object.<init> and static constructors + * are examples for this
    + *
    + * @param body the content of the constructor. + * @return the type of the constructor. + */ + private int getConstructorType(StructuredBlock body) { + /* A non static constructor must begin with a call to + * another constructor. Either to a constructor of the + * same class or to the super class */ + InstructionBlock ib; + if (body instanceof InstructionBlock) + ib = (InstructionBlock)body; + else if (body instanceof SequentialBlock + && (body.getSubBlocks()[0] + instanceof InstructionBlock)) + ib = (InstructionBlock) body.getSubBlocks()[0]; + else + return 0; + + Expression superExpr = ib.getInstruction().simplify(); + if (!(superExpr instanceof InvokeOperator) + || superExpr.getFreeOperandCount() != 0) + return 0; + InvokeOperator superInvoke = (InvokeOperator) superExpr; + if (!superInvoke.isConstructor() + || !superInvoke.isSuperOrThis()) + return 0; + Expression thisExpr = superInvoke.getSubExpressions()[0]; + if (!isThis(thisExpr, clazzAnalyzer.getClazz())) + return 0; + + if (superInvoke.isThis()) + return 2; + else + return 1; + } + + private void lookForConstructorCall() { + type01Count = cons.length; + + for (int i=0; i< type01Count; ) { + MethodAnalyzer current = cons[i]; + if (!isStatic + && (Options.options & Options.OPTION_CONTRAFO) != 0 + && clazzAnalyzer.getOuterInstance() != null) + current.getParamInfo(1).setExpression + (clazzAnalyzer.getOuterInstance()); + + FlowBlock header = cons[i].getMethodHeader(); + /* Check that code block is fully analyzed */ + if (header == null || !header.hasNoJumps()) + return; + + StructuredBlock body = cons[i].getMethodHeader().block; + int type = isStatic ? 0 : getConstructorType(body); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("constr "+i+": type"+type+" "+body); + + switch(type) { + case 0: + // type0 are moved to the beginning. + cons[i] = cons[type0Count]; + cons[type0Count++] = current; + /* fall through */ + case 1: + // type1 are not moved at all. + i++; + break; + case 2: + // type2 are moved to the end. + cons[i] = cons[--type01Count]; + cons[type01Count] = current; + break; + } + } + } + + public static boolean isThis(Expression thisExpr, ClassInfo clazz) { + return ((thisExpr instanceof ThisOperator) + && (((ThisOperator)thisExpr).getClassInfo() == clazz)); + } + + /** + * Check if this is a single anonymous constructor and mark it as + * such. We only check if the super() call is correctly formed and + * ignore the rest of the body. + * + * This method also marks the jikesAnonymousInner. + */ + private void checkAnonymousConstructor() { + if (isStatic || cons.length != 1 + || type01Count - type0Count != 1 + || clazzAnalyzer.getName() != null) + return; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("checkAnonymousConstructor of " + +clazzAnalyzer.getClazz()); + + StructuredBlock sb = cons[0].getMethodHeader().block; + if (sb instanceof SequentialBlock) + sb = sb.getSubBlocks()[0]; + + InstructionBlock superBlock = (InstructionBlock) sb; + /** + * Situation: + * constructor(outerValues, params) { + * super(someOuters, params); + * } + * + * For jikes anonymous classes that extends class or method + * scoped classes the situation is more unusal for type1. We + * check if this is the case and mark the class as + * jikesAnonymousInner: + * + * constructor(outerValues, params, outerClass) { + * outerClass.super(someOuters, params); + * constructor$?(outerValues[0], params); + * } + * + * Mark constructor as anonymous constructor. */ + Expression expr = superBlock.getInstruction().simplify(); + InvokeOperator superCall = (InvokeOperator) expr; + Expression[] subExpr = superCall.getSubExpressions(); + + /* An anonymous constructor may only give locals + * to its super constructor. + */ + for (int i = 1; i < subExpr.length; i++) { + if (!(subExpr[i] instanceof LocalLoadOperator)) + return; + } + + Type[] params = cons[0].getType().getParameterTypes(); + boolean jikesAnon = false; + + int minOuter = params.length; + + int slot = 1; + for (int i = 0; i < params.length - 1; i++) + slot += params[i].stackSize(); + + /* slot counts from last slot down. */ + + int start = 1; + if (subExpr.length >= 2) { + LocalLoadOperator llop = (LocalLoadOperator) subExpr[1]; + + if (llop.getLocalInfo().getSlot() == slot) { + jikesAnon = true; + start++; + // This is not an outer value. + outerValues.setCount(params.length - 1); + minOuter--; + slot -= params[minOuter - 1].stackSize(); + } + } + + int sub = subExpr.length - 1; + /* Check how many parameters are passed correctly. */ + while (sub >= start) { + LocalLoadOperator llop = (LocalLoadOperator) subExpr[sub]; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" pos "+sub+": "+slot+"," + + llop.getLocalInfo().getSlot()+ + "; "+minOuter); + + if (llop.getLocalInfo().getSlot() != slot) { + // restore the slot. + slot += params[minOuter - 1].stackSize(); + break; + } + sub--; + /* This parameter is not forced to be an outer value */ + minOuter--; + if (minOuter == 0) + break; + slot -= params[minOuter - 1].stackSize(); + } + ClassAnalyzer superAna = superCall.getClassAnalyzer(); + OuterValues superOV = null; + if (superAna != null + && superAna.getParent() instanceof MethodAnalyzer) { + // super is a method scope class. + superOV = superAna.getOuterValues(); + } + int minSuperOuter = sub - start + 1; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" super outer: " + superOV); + + /* The remaining sub expressions must be outerValues. */ + for (; sub >= start; sub--) { + LocalLoadOperator llop = (LocalLoadOperator) subExpr[sub]; + if (llop.getLocalInfo().getSlot() >= slot) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" Illegal slot at "+sub+":" + + llop.getLocalInfo().getSlot()); + return; + } + } + + if (minSuperOuter == 1 + && superAna.getParent() instanceof ClassAnalyzer) { + /* Check if this is the implicit Outer Class */ + LocalLoadOperator llop = (LocalLoadOperator) subExpr[start]; + if (outerValues.getValueBySlot(llop.getLocalInfo().getSlot()) + instanceof ThisOperator) { + minSuperOuter = 0; + outerValues.setImplicitOuterClass(true); + } + } + + if (minSuperOuter > 0) { + if (superOV == null || superOV.getCount() < minSuperOuter) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" super outer doesn't match: " + + minSuperOuter); + return; + } + superOV.setMinCount(minSuperOuter); + } + + outerValues.setMinCount(minOuter); + if (superOV != null) { + final int ovdiff = minOuter - minSuperOuter; + outerValues.setCount(superOV.getCount() + ovdiff); + superOV.addOuterValueListener(new OuterValueListener() { + public void shrinkingOuterValues + (OuterValues other, int newCount) { + outerValues.setCount(newCount + ovdiff); + } + }); + } else + outerValues.setCount(minOuter); + + if (jikesAnon) + outerValues.setJikesAnonymousInner(true); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" succeeded: "+outerValues); + cons[0].setAnonymousConstructor(true); + superBlock.removeBlock(); + type0Count++; + } + + private boolean checkJikesSuper(Expression expr) { + if (expr instanceof LocalStoreOperator + || expr instanceof IIncOperator) + return false; + if (expr instanceof Operator) { + Expression subExpr[] = ((Operator)expr).getSubExpressions(); + for (int i=0; i< subExpr.length; i++) { + if (!checkJikesSuper(subExpr[i])) + return false; + } + } + return true; + } + + private Expression renameJikesSuper(Expression expr, + MethodAnalyzer methodAna, + Expression outer0, + int firstOuterSlot, + int firstParamSlot) { + if (expr instanceof LocalLoadOperator) { + LocalLoadOperator llop = (LocalLoadOperator) expr; + int slot = llop.getLocalInfo().getSlot(); + if (slot >= firstOuterSlot && slot < firstParamSlot) + return outerValues.getValueBySlot(slot); + else if (slot == 1) { + return outer0; + } else { + Type[] paramTypes = methodAna.getType().getParameterTypes(); + int param; + /* Adjust the slot */ + if (slot >= firstParamSlot) + slot -= firstParamSlot - firstOuterSlot; + for (param = 0; slot > 1 && param < paramTypes.length; param++) + slot -= paramTypes[param].stackSize(); + llop.setLocalInfo(methodAna.getParamInfo(1+param)); + llop.setMethodAnalyzer(methodAna); + return llop; + } + } + if (expr instanceof Operator) { + Expression subExpr[] = ((Operator)expr).getSubExpressions(); + for (int i=0; i< subExpr.length; i++) { + Expression newSubExpr = + renameJikesSuper(subExpr[i], methodAna, outer0, + firstOuterSlot, firstParamSlot); + if (newSubExpr != subExpr[i]) + ((Operator)expr).setSubExpressions(i, newSubExpr); + } + } + return expr; + } + + private void checkJikesContinuation() { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + System.err.println("checkJikesContinuation: "+outerValues); + constr_loop: + for (int i=0; i < cons.length; i++) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("constr "+i+" type" + + (i < type0Count ? 0 : + i < type01Count ? 1 : 2) + " : " + + cons[i].getMethodHeader()); + + MethodAnalyzer constr = cons[i]; + MethodType constrType = constr.getType(); + + /* + * constructor(outerValues, params, opt. jikesAnonInner param) { + * optional super/this(expressions); + * constructor$?(optional outerValues[0], params); + * } + * + * The outerValues[0] parameter is the this local in the + * surrounding method. But we can't be sure, what the + * surrounding method is, since it could be either the method + * that uses the class, or a method that declares the class, that + * contains the method that uses the class.
    + * + * If the surrounding method is static, the outerValues[0] + * parameter disappears. + * + * Move optional super to method constructor$? + * (renaming local variables) and mark constructor and + * constructor$? as Jikes constructor. */ + StructuredBlock sb = constr.getMethodHeader().block; + + Vector localLoads = null; + InstructionBlock superBlock = null; + InvokeOperator superInvoke = null; + if (i >= type0Count) { + /* Extract the super() or this() call at the beginning + * of the constructor + */ + if (!(sb instanceof SequentialBlock) + || !(sb.getSubBlocks()[1] instanceof InstructionBlock)) + continue constr_loop; + + superBlock = (InstructionBlock) sb.getSubBlocks()[0]; + sb = sb.getSubBlocks()[1]; + + superInvoke = (InvokeOperator) + superBlock.getInstruction().simplify(); + if (!checkJikesSuper(superInvoke)) + continue constr_loop; + } + + if (!(sb instanceof InstructionBlock)) + continue constr_loop; + + /* Now check the constructor$? invocation */ + Expression lastExpr + = ((InstructionBlock)sb).getInstruction().simplify(); + if (!(lastExpr instanceof InvokeOperator)) + continue constr_loop; + + InvokeOperator invoke = (InvokeOperator) lastExpr; + if (!invoke.isThis() + || invoke.getFreeOperandCount() != 0) + continue constr_loop; + MethodAnalyzer methodAna = invoke.getMethodAnalyzer(); + if (methodAna == null) + continue constr_loop; + MethodType methodType = methodAna.getType(); + Expression[] methodParams = invoke.getSubExpressions(); + + if (!methodAna.getName().startsWith("constructor$") + || methodType.getReturnType() != Type.tVoid) + continue constr_loop; + + if (!isThis(methodParams[0], clazzAnalyzer.getClazz())) + continue constr_loop; + for (int j=1; j < methodParams.length; j++) { + if (!(methodParams[j] instanceof LocalLoadOperator)) + continue constr_loop; + } + + Type[] paramTypes = constr.getType().getParameterTypes(); + int paramCount = paramTypes.length; + if (outerValues.isJikesAnonymousInner()) + paramCount--; + + int maxOuterCount = paramCount - methodParams.length + 2; + int minOuterCount = maxOuterCount - 1; + int slot = 1; + int firstParam = 1; + Expression outer0 = null; + + if (maxOuterCount > 0 + && methodParams.length > 1 + && outerValues.getCount() > 0) { + /* Check if the outerValues[0] param is present. + * we can't be sure if maxOuterCount equals 1, but + * we assume so, since at this time all possible + * info about outers have been collected. + */ + if (((LocalLoadOperator)methodParams[firstParam] + ).getLocalInfo().getSlot() == 1) { + minOuterCount = maxOuterCount; + outer0 = outerValues.getValue(0); + firstParam++; + } else + maxOuterCount--; + for (int j=0; j < maxOuterCount; j++) + slot += paramTypes[j].stackSize(); + } + + if (minOuterCount > outerValues.getCount()) + continue constr_loop; + + int firstParamSlot = slot; + int firstOuterSlot = firstParam; + int slotDist = firstParamSlot - firstOuterSlot; + /* check the remaining parameters. + */ + for (int j=firstParam; j < methodParams.length; j++) { + if (((LocalLoadOperator) methodParams[j] + ).getLocalInfo().getSlot() != slot) + continue constr_loop; + slot += methodParams[j].getType().stackSize(); + } + + outerValues.setMinCount(minOuterCount); + outerValues.setCount(maxOuterCount); + + /* Now move the constructor call. + */ + if (superBlock != null) { + InvokeOperator newSuper = (InvokeOperator) + renameJikesSuper(superInvoke, methodAna, outer0, + firstOuterSlot, firstParamSlot); + superBlock.removeBlock(); + if (i > type0Count) { + cons[i] = cons[type0Count]; + cons[type0Count] = constr; + } + type0Count++; + if (!isDefaultSuper(newSuper)) { + superBlock.setInstruction(newSuper); + methodAna.insertStructuredBlock(superBlock); + } + } + if (outer0 != null) { + methodAna.getParamInfo(1).setExpression(outer0); + methodAna.getMethodHeader().simplify(); + } + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" succeeded"); + + constr.setJikesConstructor(constr); + methodAna.setJikesConstructor(constr); + methodAna.setHasOuterValue(firstOuterSlot == 2); + if (constr.isAnonymousConstructor()) + methodAna.setAnonymousConstructor(true); + } + } + + /** + * This methods checks if expr is a valid field initializer. It + * will also merge outerValues, that occur in expr. + * @param expr the initializer to check + * @return the transformed initializer or null if expr is not valid. + */ + private Expression transformFieldInitializer(int fieldSlot, + Expression expr) { + if (expr instanceof LocalVarOperator) { + if (!(expr instanceof LocalLoadOperator)) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("illegal local op: "+expr); + return null; + } + if (outerValues != null + && (Options.options & Options.OPTION_CONTRAFO) != 0) { + int slot = ((LocalLoadOperator)expr).getLocalInfo().getSlot(); + Expression outExpr = outerValues.getValueBySlot(slot); + if (outExpr != null) + return outExpr; + } + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("not outerValue: "+expr + +" "+outerValues); + return null; + } + if (expr instanceof FieldOperator) { + if (expr instanceof PutFieldOperator) + return null; + FieldOperator fo = (FieldOperator) expr; + if (fo.getClassInfo() == clazzAnalyzer.getClazz() + && clazzAnalyzer.getFieldIndex(fo.getFieldName(), + fo.getFieldType()) >= fieldSlot) + return null; + } + if (expr instanceof InvokeOperator) { + /* Don't allow method invocations that can throw a checked + * exception to leave the constructor. + */ + MethodInfo method = ((InvokeOperator) expr).getMethodInfo(); + String[] excs = method == null ? null : method.getExceptions(); + if (excs != null) { + ClassPath classPath = clazzAnalyzer.getClassPath(); + ClassInfo runtimeException + = classPath.getClassInfo("java.lang.RuntimeException"); + ClassInfo error = classPath.getClassInfo("java.lang.Error"); + for (int i = 0; i < excs.length; i++) { + ClassInfo exClass = classPath.getClassInfo(excs[i]); + try { + if (!runtimeException.superClassOf(exClass) + && !error.superClassOf(exClass)) + return null; + } catch (IOException ex) { + return null; + } + } + } + } + if (expr instanceof Operator) { + Operator op = (Operator) expr; + Expression[] subExpr = op.getSubExpressions(); + for (int i=0; i< subExpr.length; i++) { + Expression transformed + = transformFieldInitializer(fieldSlot, subExpr[i]); + if (transformed == null) + return null; + if (transformed != subExpr[i]) + op.setSubExpressions(i, transformed); + } + } + return expr; + } + + /** + * Remove initializers of synthetic fields and + * This is called for non static constructors in the analyze pass, + * after the constructors are analyzed. + */ + public void removeSynthInitializers() { + if ((Options.options & Options.OPTION_CONTRAFO) == 0 + || isStatic || type01Count == 0) + return; + + if ((Options.options & Options.OPTION_ANON) != 0) + checkAnonymousConstructor(); + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("removeSynthInitializers of " + +clazzAnalyzer.getClazz()); + + /* sb will iterate the instructions of the constructor. */ + StructuredBlock[] sb = new StructuredBlock[type01Count]; + for (int i=0; i < type01Count; i++) { + sb[i] = cons[i].getMethodHeader().block; + if (i >= type0Count) { + if (sb[i] instanceof SequentialBlock) + sb[i] = sb[i].getSubBlocks()[1]; + else + /* One constructor is done. There is no field */ + return; + } + } + + big_loop: + for (;;) { + StructuredBlock ib = + (sb[0] instanceof SequentialBlock) + ? sb[0].getSubBlocks()[0] + : sb[0]; + + if (!(ib instanceof InstructionBlock)) + break big_loop; + + Expression instr + = ((InstructionBlock) ib).getInstruction().simplify(); + + if (!(instr instanceof StoreInstruction) + || instr.getFreeOperandCount() != 0) + break big_loop; + + StoreInstruction store = (StoreInstruction) instr; + if (!(store.getLValue() instanceof PutFieldOperator)) + break big_loop; + + PutFieldOperator pfo = (PutFieldOperator) store.getLValue(); + if (pfo.isStatic() != isStatic + || pfo.getClassInfo() != clazzAnalyzer.getClazz()) + break big_loop; + + if (!isThis(pfo.getSubExpressions()[0], + clazzAnalyzer.getClazz())) + break big_loop; + + int field = clazzAnalyzer.getFieldIndex(pfo.getFieldName(), + pfo.getFieldType()); + if (field < 0) + break big_loop; + FieldAnalyzer fieldAna = clazzAnalyzer.getField(field); + + /* Don't check for final. Jikes sometimes omits this attribute. + */ + if (!fieldAna.isSynthetic()) + break big_loop; + + Expression expr = store.getSubExpressions()[1]; + expr = transformFieldInitializer(field, expr); + if (expr == null) + break big_loop; + + for (int i=1; i< type01Count; i++) { + ib = (sb[i] instanceof SequentialBlock) + ? sb[i].getSubBlocks()[0] + : sb[i]; + if (!(ib instanceof InstructionBlock) + || !(((InstructionBlock)ib).getInstruction().simplify() + .equals(instr))) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" constr 0 and "+i + +" differ: " + +instr+"<-/->"+ib); + break big_loop; + } + } + + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" field " + pfo.getFieldName() + + " = " + expr); + + if (!(fieldAna.setInitializer(expr))) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" setField failed"); + break big_loop; + } + + + boolean done = false; + for (int i=0; i< type01Count; i++) { + if (sb[i] instanceof SequentialBlock) { + StructuredBlock next = sb[i].getSubBlocks()[1]; + next.replace(sb[i]); + sb[i] = next; + } else { + sb[i].removeBlock(); + sb[i] = null; + done = true; + } + } + + if (done) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("one constr is over"); + break; + } + } + } + + + private int transformOneField(int lastField, StructuredBlock ib) { + + if (!(ib instanceof InstructionBlock)) + return -1; + + Expression instr = ((InstructionBlock) ib).getInstruction().simplify(); + + if (!(instr instanceof StoreInstruction) + || instr.getFreeOperandCount() != 0) + return -1; + + StoreInstruction store = (StoreInstruction) instr; + if (!(store.getLValue() instanceof PutFieldOperator)) + return -1; + + PutFieldOperator pfo = (PutFieldOperator) store.getLValue(); + if (pfo.isStatic() != isStatic + || pfo.getClassInfo() != clazzAnalyzer.getClazz()) + return -1; + + if (!isStatic) { + if (!isThis(pfo.getSubExpressions()[0], + clazzAnalyzer.getClazz())) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" not this: "+instr); + return -1; + } + } + + int field = clazzAnalyzer.getFieldIndex(pfo.getFieldName(), + pfo.getFieldType()); + + if (field <= lastField) + return -1; + + Expression expr = store.getSubExpressions()[1]; + expr = transformFieldInitializer(field, expr); + if (expr == null) + return -1; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" field " + pfo.getFieldName() + + " = " + expr); + + // if field does not exists: -1 <= lastField. + if (field <= lastField + || !(clazzAnalyzer.getField(field).setInitializer(expr))) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("set field failed"); + return -1; + } + return field; + } + + private void transformBlockInitializer(StructuredBlock block) { + StructuredBlock start = null; + StructuredBlock tail = null; + int lastField = -1; + while (block instanceof SequentialBlock) { + StructuredBlock ib = block.getSubBlocks()[0]; + int field = transformOneField(lastField, ib); + if (field < 0) + clazzAnalyzer.addBlockInitializer(lastField + 1, ib); + else + lastField = field; + block = block.getSubBlocks()[1]; + } + if (transformOneField(lastField, block) < 0) + clazzAnalyzer.addBlockInitializer(lastField + 1, block); + } + + private boolean checkBlockInitializer(InvokeOperator invoke) { + if (!invoke.isThis() + || invoke.getFreeOperandCount() != 0) + return false; + MethodAnalyzer methodAna = invoke.getMethodAnalyzer(); + if (methodAna == null) + return false; + FlowBlock flow = methodAna.getMethodHeader(); + MethodType methodType = methodAna.getType(); + if (!methodAna.getName().startsWith("block$") + || methodType.getParameterTypes().length != 0 + || methodType.getReturnType() != Type.tVoid) + return false; + if (flow == null || !flow.hasNoJumps()) + return false; + + if (!isThis(invoke.getSubExpressions()[0], + clazzAnalyzer.getClazz())) + return false; + + methodAna.setJikesBlockInitializer(true); + transformBlockInitializer(flow.block); + return true; + } + + + /* Checks if superInvoke is the default super call. + */ + private boolean isDefaultSuper(InvokeOperator superInvoke) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("isDefaultSuper: "+superInvoke); + + ClassInfo superClazz = superInvoke.getClassInfo(); + Expression[] params = superInvoke.getSubExpressions(); + if (superClazz == null) + return false; + + if ((Options.options & Options.OPTION_INNER) != 0 + && superClazz.getOuterClass() != null + && !Modifier.isStatic(superClazz.getModifiers())) { + + /* Super class is an inner class. Check if the default outer + * instance is passed. + */ + if (params.length != 2) + return false; + + Expression superOuterExpr = params[1].simplify(); + if (superOuterExpr instanceof ThisOperator + && (((ThisOperator) superOuterExpr).getClassInfo() + == superClazz.getOuterClass())) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" isDefaultSuper success"); + return true; + } + return false; + } + + int outerValCount = 0; + ClassAnalyzer superClazzAna = superInvoke.getClassAnalyzer(); + if (superClazzAna != null) { + OuterValues superOV = superClazzAna.getOuterValues(); + if (superOV != null) + outerValCount = superOV.getCount(); + } + + /* Check if only this and the outer value parameters are + * transmitted. The analyze pass already made sure, that the + * outer value parameters are correct. + */ + if (params.length != outerValCount + 1) + return false; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println(" isDefaultSuper success"); + return true; + } + + private void removeDefaultSuper() { + /* Check if we can remove the super() call of type1 constructors. + * This transforms a type1 constructor in a type0 constructor. + */ + for (int i=type0Count; i< type01Count; i++) { + MethodAnalyzer current = cons[i]; + FlowBlock header = cons[i].getMethodHeader(); + StructuredBlock body = header.block; + InstructionBlock ib; + if (body instanceof InstructionBlock) + ib = (InstructionBlock) body; + else + ib = (InstructionBlock) body.getSubBlocks()[0]; + + InvokeOperator superInvoke = (InvokeOperator) + ib.getInstruction().simplify(); + if (isDefaultSuper(superInvoke)) { + ib.removeBlock(); + if (i > type0Count) { + cons[i] = cons[type0Count]; + cons[type0Count] = current; + } + type0Count++; + } + } + } + + private void removeInitializers() { + if (type01Count == 0) + return; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("removeInitializers"); + + StructuredBlock[] sb = new StructuredBlock[type01Count]; + for (int i=0; i< type01Count; i++) { + FlowBlock header = cons[i].getMethodHeader(); + /* sb[i] will iterate the instructions of the constructor. */ + sb[i] = header.block; + if (i >= type0Count) { + if (sb[i] instanceof SequentialBlock) + sb[i] = sb[i].getSubBlocks()[1]; + else { + sb[i] = null; + return; + } + } + } + int lastField = -1; + big_loop: + for (;;) { + StructuredBlock ib = + (sb[0] instanceof SequentialBlock) + ? sb[0].getSubBlocks()[0] + : sb[0]; + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("Instruction: "+ib); + + if (!(ib instanceof InstructionBlock)) + break big_loop; + + Expression instr + = ((InstructionBlock) ib).getInstruction().simplify(); + + for (int i=1; i < type01Count; i++) { + ib = (sb[i] instanceof SequentialBlock) + ? sb[i].getSubBlocks()[0] + : sb[i]; + if (!(ib instanceof InstructionBlock) + || !(((InstructionBlock)ib).getInstruction().simplify() + .equals(instr))) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("constr "+i+" differs: "+ib); + break big_loop; + } + } + + if (instr instanceof InvokeOperator + && checkBlockInitializer((InvokeOperator) instr)) { + for (int i=0; i< type01Count; i++) { + if (sb[i] instanceof SequentialBlock) { + StructuredBlock next = sb[i].getSubBlocks()[1]; + next.replace(sb[i]); + sb[i] = next; + } else { + sb[i].removeBlock(); + sb[i] = null; + } + } + break big_loop; + } + + int field = transformOneField(lastField, ib); + if (field < 0) + break big_loop; + + lastField = field; + + boolean done = false; + for (int i=0; i< type01Count; i++) { + if (sb[i] instanceof SequentialBlock) { + StructuredBlock next = sb[i].getSubBlocks()[1]; + next.replace(sb[i]); + sb[i] = next; + } else { + sb[i].removeBlock(); + sb[i] = null; + done = true; + } + } + + if (done) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_CONSTRS) != 0) + GlobalOptions.err.println("one constr is over"); + break; + } + } + } + + /** + * This does the normal constructor transformations. + * + * javac copies the field initializers to each constructor. This + * will undo the transformation: it will tell the fields about the + * initial value and removes the initialization from all constructors. + * + * There are of course many checks necessary: All field + * initializers must be equal in all constructors, and there + * mustn't be locals that used in field initialization (except + * outerValue - locals). + */ + public void transform() { + if ((Options.options & Options.OPTION_CONTRAFO) == 0 + || cons.length == 0) + return; + + removeInitializers(); + checkJikesContinuation(); + + if (outerValues != null) { + /* Now tell all constructors the value of outerValues parameters */ + for (int i=0; i< cons.length; i++) { + for (int j = 0; j < outerValues.getCount(); j++) + cons[i].getParamInfo(j+1) + .setExpression(outerValues.getValue(j)); + } + } + removeDefaultSuper(); + } +} diff --git a/jode/src/net/sf/jode/flow/TransformExceptionHandlers.java b/jode/src/net/sf/jode/flow/TransformExceptionHandlers.java new file mode 100644 index 0000000..b4d966f --- /dev/null +++ b/jode/src/net/sf/jode/flow/TransformExceptionHandlers.java @@ -0,0 +1,998 @@ +/* TransformExceptionHandlers Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.GlobalOptions; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.expr.*; + +///#def COLLECTIONS java.util +import java.util.TreeSet; +import java.util.SortedSet; +import java.util.Set; +import java.util.Map; +import java.util.Iterator; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.Comparable; +///#enddef + +/** + * + * @author Jochen Hoenicke + */ +public class TransformExceptionHandlers { + SortedSet handlers; + FlowBlock[] flowBlocks; + + static class Handler implements Comparable { + FlowBlock start; + FlowBlock end; + FlowBlock handler; + Type type; + + public Handler(FlowBlock tryBlock, FlowBlock endBlock, + FlowBlock catchBlock, Type type) { + this.start = tryBlock; + this.end = endBlock; + this.handler = catchBlock; + this.type = type; + } + + public int compareTo (Object o) { + Handler second = (Handler) o; + + /* First sort by start offsets, highest block number first...*/ + if (start.getBlockNr() != second.start.getBlockNr()) + /* this subtraction is save since block numbers are only 16 bit */ + return second.start.getBlockNr() - start.getBlockNr(); + + /* ...Second sort by end offsets, lowest block number first... + * this will move the innermost blocks to the beginning. */ + if (end.getBlockNr() != second.end.getBlockNr()) + return end.getBlockNr() - second.end.getBlockNr(); + + /* ...Last sort by handler offsets, lowest first */ + if (handler.getBlockNr() != second.handler.getBlockNr()) + return handler.getBlockNr() - second.handler.getBlockNr(); + + /* ...Last sort by typecode signature. Shouldn't happen to often. + */ + if (type == second.type) + return 0; + if (type == null) + return -1; + if (second.type == null) + return 1; + return type.getTypeSignature() + .compareTo(second.type.getTypeSignature()); + } + } + + public TransformExceptionHandlers(FlowBlock[] flowBlocks) { + handlers = new TreeSet(); + this.flowBlocks = flowBlocks; + } + + /** + * Add an exception Handler. + * @param start The start block number of the exception range. + * @param end The end block number of the exception range + 1. + * @param handler The block number of the handler. + * @param type The type of the exception, null for ALL. + */ + public void addHandler(FlowBlock tryBlock, FlowBlock endBlock, + FlowBlock catchBlock, Type type) { + handlers.add(new Handler(tryBlock, endBlock, catchBlock, type)); + } + + /** + * Merge the try flow block with the catch flow block. This is a kind + * of special T2 transformation, as all jumps to the catch block are + * implicit (exception can be thrown everywhere).
    + * + * This method doesn't actually merge the contents of the blocks. The + * caller should do it right afterwards.
    + * + * The flow block catchFlow mustn't have any predecessors. + * @param tryFlow the flow block containing the try. + * @param catchFlow the flow block containing the catch handler. + */ + static void mergeTryCatch(FlowBlock tryFlow, FlowBlock catchFlow) { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("mergeTryCatch(" + tryFlow.getBlockNr() + + ", " + catchFlow.getBlockNr() + ")"); + tryFlow.updateInOutCatch(catchFlow); + tryFlow.mergeSuccessors(catchFlow); + tryFlow.mergeBlockNr(catchFlow); + } + + + /** + * Analyzes a simple try/catch block. The try and catch part are both + * analyzed, the try block is already created, but the catch block + * isn't.
    + * The catchFlow block mustn't have any predecessors. + * + * @param type The type of the exception which is caught. + * @param tryFlow The flow block containing the try. The contained + * block must be a try block. + * @param catchFlow the flow block containing the catch handler. + */ + static void analyzeCatchBlock(Type type, FlowBlock tryFlow, + FlowBlock catchFlow) { + /* Merge try and catch flow blocks */ + mergeTryCatch(tryFlow, catchFlow); + + /* Insert catch block into tryFlow */ + CatchBlock newBlock = new CatchBlock(type); + ((TryBlock)tryFlow.block).addCatchBlock(newBlock); + newBlock.setCatchBlock(catchFlow.block); + tryFlow.lastModified = tryFlow.block; + } + + /** + * This transforms a sub routine, i.e. it checks if the beginning + * local assignment matches the final ret and removes both. It also + * accepts sub routines that just pop their return address. + */ + boolean transformSubRoutine(StructuredBlock subRoutineBlock) { + StructuredBlock firstBlock = subRoutineBlock; + if (firstBlock instanceof SequentialBlock) + firstBlock = subRoutineBlock.getSubBlocks()[0]; + + LocalInfo local = null; + if (firstBlock instanceof SpecialBlock) { + SpecialBlock popBlock + = (SpecialBlock) firstBlock; + if (popBlock.type != SpecialBlock.POP + || popBlock.count != 1) + return false; + } else if (firstBlock instanceof InstructionBlock) { + Expression expr + = ((InstructionBlock) firstBlock).getInstruction(); + if (expr instanceof StoreInstruction + && ((StoreInstruction) + expr).getLValue() instanceof LocalStoreOperator) { + LocalStoreOperator store = (LocalStoreOperator) + ((StoreInstruction)expr).getLValue(); + local = store.getLocalInfo(); + expr = ((StoreInstruction) expr).getSubExpressions()[1]; + } + if (!(expr instanceof NopOperator)) + return false; + } else + return false; + + /* We are now committed and can start changing code. Remove + * the first Statement which stores/removes the return + * address. + */ + firstBlock.removeBlock(); + + /* We don't check if there is a RET in the middle. + * + * This is a complicated task which isn't needed for javac nor + * jikes. We just check if the last instruction is a ret and + * remove this. This will never produce code with wrong semantic, + * as long as the bytecode was verified correctly. + */ + while (subRoutineBlock instanceof SequentialBlock) + subRoutineBlock = subRoutineBlock.getSubBlocks()[1]; + + if (subRoutineBlock instanceof RetBlock + && (((RetBlock) subRoutineBlock).local.equals(local))) { + subRoutineBlock.removeBlock(); + } + return true; + } + + /** + * Remove the locale that javac introduces to temporary store the return + * value, when it executes a finally block resp. monitorexit + * @param ret the ReturnBlock. + */ + private void removeReturnLocal(ReturnBlock ret) { + StructuredBlock pred = getPredecessor(ret); + if (!(pred instanceof InstructionBlock)) + return; + Expression instr = ((InstructionBlock) pred).getInstruction(); + if (!(instr instanceof StoreInstruction)) + return; + + Expression retInstr = ret.getInstruction(); + if (!(retInstr instanceof LocalLoadOperator + && ((StoreInstruction) instr).lvalueMatches + ((LocalLoadOperator) retInstr))) + return; + + Expression rvalue = ((StoreInstruction) instr).getSubExpressions()[1]; + ret.setInstruction(rvalue); + ret.replace(ret.outer); + } + + /** + * Remove the JSRs jumping to the specified subRoutine. The right + * JSRs are marked and we can just remove them. For the other JSR + * instructions we replace them with a warning. + * @param tryFlow the FlowBlock of the try block. + * @param subRoutine the FlowBlock of the sub routine. + */ + private void removeJSR(FlowBlock tryFlow, FlowBlock subRoutine) { + for (Jump jumps = tryFlow.removeJumps(subRoutine); + jumps != null; jumps = jumps.next) { + + StructuredBlock prev = jumps.prev; + prev.removeJump(); + + if (prev instanceof EmptyBlock + && prev.outer instanceof JsrBlock + && ((JsrBlock) prev.outer).isGood()) { + StructuredBlock next = prev.outer.getNextBlock(); + prev.outer.removeBlock(); + if (next instanceof ReturnBlock) + removeReturnLocal((ReturnBlock) next); + } else { + /* We have a jump to the subroutine, that is badly placed. + * We complain here. + */ + DescriptionBlock msg = new DescriptionBlock + ("ERROR: invalid jump to finally block!"); + prev.appendBlock(msg); + } + } + } + + private static StructuredBlock getPredecessor(StructuredBlock stmt) + { + if (stmt.outer instanceof SequentialBlock) { + SequentialBlock seq = (SequentialBlock) stmt.outer; + if (seq.subBlocks[1] == stmt) + return seq.subBlocks[0]; + else if (seq.outer instanceof SequentialBlock) + return seq.outer.getSubBlocks()[0]; + } + return null; + } + + /** + * Gets the slot of the monitorexit instruction instr in the + * stmt, or -1 if stmt isn't a InstructionBlock with a + * monitorexit instruction. + * @param stmt the stmt, may be null. + */ + private static int getMonitorExitSlot(StructuredBlock stmt) { + if (stmt instanceof InstructionBlock) { + Expression instr = ((InstructionBlock) stmt).getInstruction(); + if (instr instanceof MonitorExitOperator) { + MonitorExitOperator monExit = (MonitorExitOperator)instr; + if (monExit.getFreeOperandCount() == 0 + && (monExit.getSubExpressions()[0] + instanceof LocalLoadOperator)) + return ((LocalLoadOperator) monExit.getSubExpressions()[0]) + .getLocalInfo().getSlot(); + } + } + return -1; + } + + private boolean isMonitorExitSubRoutine(FlowBlock subRoutine, + LocalInfo local) { + if (transformSubRoutine(subRoutine.block) + && getMonitorExitSlot(subRoutine.block) == local.getSlot()) + return true; + return false; + } + + private static StructuredBlock skipFinExitChain(StructuredBlock block) + { + StructuredBlock pred, result; + if (block instanceof ReturnBlock) + pred = getPredecessor(block); + else + pred = block; + result = null; + + while (pred instanceof JsrBlock + || getMonitorExitSlot(pred) >= 0) { + result = pred; + pred = getPredecessor(pred); + } + return result; + } + + + private void checkAndRemoveJSR(FlowBlock tryFlow, + FlowBlock subRoutine, + int startOutExit, int endOutExit) { + Iterator iter = tryFlow.getSuccessors().iterator(); + dest_loop: + while (iter.hasNext()) { + FlowBlock dest = (FlowBlock) iter.next(); + if (dest == subRoutine) + continue dest_loop; + + boolean isFirstJump = true; + for (Jump jumps = tryFlow.getJumps(dest); + jumps != null; jumps = jumps.next, isFirstJump = false) { + + StructuredBlock prev = jumps.prev; + if (prev instanceof EmptyBlock + && prev.outer instanceof JsrBlock) { + /* This jump is a jsr, since it doesn't leave the + * block forever, we can ignore it. + */ + continue; + } + + StructuredBlock pred = skipFinExitChain(prev); + if (pred instanceof JsrBlock) { + JsrBlock jsr = (JsrBlock) pred; + StructuredBlock jsrInner = jsr.innerBlock; + if (jsrInner instanceof EmptyBlock + && jsrInner.jump != null + && jsrInner.jump.destination == subRoutine) { + /* The jump is preceeded by the right jsr. Mark the + * jsr as good. + */ + jsr.setGood(true); + continue; + } + } + + if (pred == null && isFirstJump) { + /* Now we have a jump that is not preceded by any + * jsr. There's a last chance: the jump jumps + * directly to a correct jsr instruction, which + * lies outside the try/catch block. + */ + if (jumps.destination.predecessors.size() == 1 + && jumps.destination.getBlockNr() >= startOutExit + && jumps.destination.getNextBlockNr() <= endOutExit) { + jumps.destination.analyze(startOutExit, endOutExit); + + StructuredBlock sb = jumps.destination.block; + if (sb instanceof SequentialBlock) + sb = sb.getSubBlocks()[0]; + if (sb instanceof JsrBlock + && sb.getSubBlocks()[0] instanceof EmptyBlock + && (sb.getSubBlocks()[0].jump.destination + == subRoutine)) { + StructuredBlock jsrInner = sb.getSubBlocks()[0]; + jumps.destination.removeSuccessor(jsrInner.jump); + jsrInner.removeJump(); + sb.removeBlock(); + continue dest_loop; + } + } + } + + /* Now we have a jump with a wrong destination. + * Complain! + */ + DescriptionBlock msg + = new DescriptionBlock("ERROR: no jsr to finally"); + if (pred != null) + pred.prependBlock(msg); + else { + prev.appendBlock(msg); + msg.moveJump(prev.jump); + } + } + } + if (tryFlow.getSuccessors().contains(subRoutine)) + removeJSR(tryFlow, subRoutine); + } + + private void checkAndRemoveMonitorExit(FlowBlock tryFlow, + LocalInfo local, + int start, int end) { + FlowBlock subRoutine = null; + Iterator succs = tryFlow.getSuccessors().iterator(); + dest_loop: + while (succs.hasNext()) { + boolean isFirstJump = true; + FlowBlock successor = (FlowBlock) succs.next(); + for (Jump jumps = tryFlow.getJumps(successor); + jumps != null; jumps = jumps.next, isFirstJump = false) { + + StructuredBlock prev = jumps.prev; + if (prev instanceof EmptyBlock + && prev.outer instanceof JsrBlock) { + /* This jump is really a jsr, since it doesn't + * leave the block forever, we can ignore it. + */ + continue; + } + StructuredBlock pred = skipFinExitChain(prev); + if (pred instanceof JsrBlock) { + JsrBlock jsr = (JsrBlock) pred; + StructuredBlock jsrInner = jsr.innerBlock; + if (jsrInner instanceof EmptyBlock + && jsrInner.jump != null) { + FlowBlock dest = jsrInner.jump.destination; + + if (subRoutine == null + && dest.getBlockNr() >= start + && dest.getNextBlockNr() <= end) { + dest.analyze(start, end); + if (isMonitorExitSubRoutine(dest, local)) + subRoutine = dest; + } + + if (dest == subRoutine) { + /* The jump is preceeded by the right jsr. + * Mark it as good. + */ + jsr.setGood(true); + continue; + } + } + } else if (getMonitorExitSlot(pred) == local.getSlot()) { + /* The jump is preceeded by the right monitor + * exit instruction. + */ + pred.removeBlock(); + if (prev instanceof ReturnBlock) + removeReturnLocal((ReturnBlock) prev); + continue; + } + + if (pred == null && isFirstJump) { + /* Now we have a jump that is not preceded by a + * monitorexit. There's a last chance: the jump + * jumps directly to the correct monitorexit + * instruction, which lies outside the try/catch + * block. + */ + if (successor.predecessors.size() == 1 + && successor.getBlockNr() >= start + && successor.getNextBlockNr() <= end) { + successor.analyze(start, end); + + StructuredBlock sb = successor.block; + if (sb instanceof SequentialBlock) + sb = sb.getSubBlocks()[0]; + if (sb instanceof JsrBlock + && sb.getSubBlocks()[0] instanceof EmptyBlock) { + StructuredBlock jsrInner = sb.getSubBlocks()[0]; + FlowBlock dest = jsrInner.jump.destination; + if (subRoutine == null + && dest.getBlockNr() >= start + && dest.getNextBlockNr() <= end) { + dest.analyze(start, end); + if (isMonitorExitSubRoutine(dest, local)) + subRoutine = dest; + } + + if (subRoutine == dest) { + successor.removeSuccessor(jsrInner.jump); + jsrInner.removeJump(); + sb.removeBlock(); + continue dest_loop; + } + } + if (getMonitorExitSlot(sb) == local.getSlot()) { + sb.removeBlock(); + continue dest_loop; + } + } + } + + /* Complain! + */ + DescriptionBlock msg + = new DescriptionBlock("ERROR: no monitorexit"); + prev.appendBlock(msg); + msg.moveJump(jumps); + } + } + + if (subRoutine != null) { + if (tryFlow.getSuccessors().contains(subRoutine)) + removeJSR(tryFlow, subRoutine); + if (subRoutine.predecessors.size() == 0) + tryFlow.mergeBlockNr(subRoutine); + } + } + + private StoreInstruction getExceptionStore(StructuredBlock catchBlock) { + if (!(catchBlock instanceof SequentialBlock) + || !(catchBlock.getSubBlocks()[0] instanceof InstructionBlock)) + return null; + + Expression instr = + ((InstructionBlock)catchBlock.getSubBlocks()[0]).getInstruction(); + if (!(instr instanceof StoreInstruction)) + return null; + + StoreInstruction store = (StoreInstruction) instr; + if (!(store.getLValue() instanceof LocalStoreOperator + && store.getSubExpressions()[1] instanceof NopOperator)) + return null; + + return store; + } + + private boolean analyzeSynchronized(FlowBlock tryFlow, + FlowBlock catchFlow, + int endHandler) { + /* Check if this is a synchronized block. We mustn't change + * anything until we are sure. + */ + StructuredBlock catchBlock = catchFlow.block; + + /* Check for a optional exception store and skip it */ + StoreInstruction excStore = getExceptionStore(catchBlock); + if (excStore != null) + catchBlock = catchBlock.getSubBlocks()[1]; + + /* Check for the monitorexit instruction */ + if (!(catchBlock instanceof SequentialBlock + && catchBlock.getSubBlocks()[0] + instanceof InstructionBlock)) + return false; + Expression instr = + ((InstructionBlock)catchBlock.getSubBlocks()[0]).getInstruction(); + if (!(instr instanceof MonitorExitOperator + && instr.getFreeOperandCount() == 0 + && (((MonitorExitOperator)instr).getSubExpressions()[0] + instanceof LocalLoadOperator) + && catchBlock.getSubBlocks()[1] instanceof ThrowBlock)) + return false; + + /* Check for the throw instruction */ + Expression throwInstr = + ((ThrowBlock)catchBlock.getSubBlocks()[1]).getInstruction(); + if (excStore != null) { + if (!(throwInstr instanceof Operator + && excStore.lvalueMatches((Operator)throwInstr))) + return false; + } else { + if (!(throwInstr instanceof NopOperator)) + return false; + } + + /* This is a synchronized block: + * + * local_x = monitor object; // later + * monitorenter local_x // later + * tryFlow: + * |- synchronized block + * | ... + * | every jump to outside is preceded by jsr subroutine-, + * | ... | + * |- monitorexit local_x | + * ` jump after this block (without jsr monexit) | + * | + * catchBlock: | + * local_n = stack | + * monitorexit local_x | + * throw local_n | + * [OR ALTERNATIVELY:] | + * monitorexit local_x | + * throw stack | + * optional subroutine: <-----------------------------------' + * astore_n + * monitorexit local_x + * return_n + */ + + /* Merge try and catch flow blocks. No need to insert the + * catchFlow.block into the try flow though, since all its + * instructions are synthetic. + */ + mergeTryCatch(tryFlow, catchFlow); + + MonitorExitOperator monexit = (MonitorExitOperator) + ((InstructionBlock) catchBlock.getSubBlocks()[0]).instr; + LocalInfo local = + ((LocalLoadOperator)monexit.getSubExpressions()[0]) + .getLocalInfo(); + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("analyzeSynchronized(" + tryFlow.getBlockNr() + + "," + tryFlow.getNextBlockNr() + "," + endHandler + ")"); + + checkAndRemoveMonitorExit + (tryFlow, local, tryFlow.getNextBlockNr(), endHandler); + + SynchronizedBlock syncBlock = new SynchronizedBlock(local); + TryBlock tryBlock = (TryBlock) tryFlow.block; + syncBlock.replace(tryBlock); + syncBlock.moveJump(tryBlock.jump); + syncBlock.setBodyBlock(tryBlock.subBlocks.length == 1 + ? tryBlock.subBlocks[0] : tryBlock); + tryFlow.lastModified = syncBlock; + return true; + } + + /** + * Merge try and finally flow blocks. + * @param tryFlow The try flow block. Its contained block must be + * a try block. + * @param catchFlow The catch flow block that contains the finally + * block. + * @param finallyBlock block that either contains the finally block. + * It is part of the catchFlow. The other parts of catchFlow are + * synthetic and can be removed. + */ + private void mergeFinallyBlock(FlowBlock tryFlow, FlowBlock catchFlow, + StructuredBlock finallyBlock) { + TryBlock tryBlock = (TryBlock) tryFlow.block; + if (tryBlock.getSubBlocks()[0] instanceof TryBlock) { + /* A try { try { } catch {} } finally{} is equivalent + * to a try {} catch {} finally {} + * so remove the surrounding tryBlock. + */ + TryBlock innerTry = (TryBlock)tryBlock.getSubBlocks()[0]; + innerTry.gen = tryBlock.gen; + innerTry.replace(tryBlock); + tryBlock = innerTry; + tryFlow.lastModified = tryBlock; + tryFlow.block = tryBlock; + } + + /* Now merge try and catch flow blocks */ + mergeTryCatch(tryFlow, catchFlow); + FinallyBlock newBlock = new FinallyBlock(); + newBlock.setCatchBlock(finallyBlock); + tryBlock.addCatchBlock(newBlock); + } + + private boolean analyzeFinally(FlowBlock tryFlow, + FlowBlock catchFlow, int end) { + + /* Layout of a try-finally block: + * + * tryFlow: + * |- first instruction + * | ... + * | every jump to outside is preceded by jsr finally + * | ... + * | jsr finally -----------------, + * `- jump after finally | + * | + * catchBlock: | + * local_n = stack v + * jsr finally ---------------->| + * throw local_n; | + * finally: <-----------------------' + * astore_n + * ... + * return_n + */ + + StructuredBlock catchBlock = catchFlow.block; + StoreInstruction excStore = getExceptionStore(catchBlock); + if (excStore == null) + return false; + + catchBlock = catchBlock.getSubBlocks()[1]; + if (!(catchBlock instanceof SequentialBlock)) + return false; + + StructuredBlock finallyBlock = null; + + if (catchBlock.getSubBlocks()[0] instanceof LoopBlock) { + /* In case the try block has no exit (that means, it + * throws an exception or loops forever), the finallyBlock + * was already merged with the catchBlock. We have to + * check for this case separately: + * + * do { + * JSR + * break; + * throw local_x + * } while(false); + * finallyBlock; (starts with POP / local_y = POP) + */ + LoopBlock doWhileFalse = (LoopBlock)catchBlock.getSubBlocks()[0]; + if (doWhileFalse.type == LoopBlock.DOWHILE + && doWhileFalse.cond == LoopBlock.FALSE + && doWhileFalse.bodyBlock instanceof SequentialBlock) { + if (transformSubRoutine(catchBlock.getSubBlocks()[1])) { + finallyBlock = catchBlock.getSubBlocks()[1]; + catchBlock = (SequentialBlock) doWhileFalse.bodyBlock; + } + } + } + + if (!(catchBlock instanceof SequentialBlock + && catchBlock.getSubBlocks()[0] instanceof JsrBlock + && catchBlock.getSubBlocks()[1] instanceof ThrowBlock)) + + return false; + + JsrBlock jsrBlock = (JsrBlock)catchBlock.getSubBlocks()[0]; + ThrowBlock throwBlock = (ThrowBlock) catchBlock.getSubBlocks()[1]; + + + if (!(throwBlock.getInstruction() instanceof Operator + && excStore.lvalueMatches((Operator) + throwBlock.getInstruction()))) + return false; + + FlowBlock subRoutine; + if (finallyBlock != null) { + /* Check if the jsr breaks (see comment above). We don't + * need to check if it breaks to the right block, because + * we know that there is only one Block around the jsr. + */ + if (!(jsrBlock.innerBlock instanceof BreakBlock)) + return false; + + /* Check if the try block has no exit + */ + if (tryFlow.getSuccessors().size() > 0) + return false; + + catchBlock = finallyBlock; + subRoutine = null; + } else { + if (!(jsrBlock.innerBlock instanceof EmptyBlock)) + return false; + finallyBlock = jsrBlock.innerBlock; + subRoutine = finallyBlock.jump.destination; + + /* We are committed now and can start changing the try + * block. + */ + checkAndRemoveJSR(tryFlow, subRoutine, + tryFlow.getNextBlockNr(), end); + + + /* Now analyze and transform the subroutine. + */ + while (subRoutine.analyze(tryFlow.getNextBlockNr(), end)); + if (subRoutine.predecessors.size() == 1) { + /* catchFlow is synthetic, so we can safely remove it + * here. + */ + subRoutine.mergeBlockNr(catchFlow); + catchFlow = subRoutine; + + if (!transformSubRoutine(subRoutine.block)) { + finallyBlock = subRoutine.block; + DescriptionBlock msg = new DescriptionBlock + ("ERROR: Missing return address handling"); + StructuredBlock subblock = subRoutine.block; + msg.replace(finallyBlock); + msg.appendBlock(finallyBlock); + } + finallyBlock = subRoutine.block; + } + } + + /* Now finish it. + */ + mergeFinallyBlock(tryFlow, catchFlow, finallyBlock); + return true; + } + + private boolean analyzeSpecialFinally(FlowBlock tryFlow, + FlowBlock catchFlow, int end) { + StructuredBlock finallyBlock = catchFlow.block; + StructuredBlock firstInstr = + finallyBlock instanceof SequentialBlock + ? finallyBlock.getSubBlocks()[0]: finallyBlock; + + if (!(firstInstr instanceof SpecialBlock + && ((SpecialBlock)firstInstr).type == SpecialBlock.POP + && ((SpecialBlock)firstInstr).count == 1)) + return false; + + /* This is a special try/finally-block, where + * the finally block ends with a break, return or + * similar. + */ + + /* Make sure that resolveJump only works on the inside of the try + */ + tryFlow.lastModified = tryFlow.block.getSubBlocks()[0]; + if (finallyBlock instanceof SequentialBlock) + finallyBlock = finallyBlock.getSubBlocks()[1]; + else { + finallyBlock = new EmptyBlock(); + finallyBlock.moveJump(firstInstr.jump); + + /* Handle the jumps in the tryFlow to finallyFlow. + */ + FlowBlock finallyFlow = finallyBlock.jump.destination; + if (tryFlow.getSuccessors().contains(finallyFlow)) { + Jump jumps = tryFlow.removeJumps(finallyFlow); + jumps = tryFlow.resolveSomeJumps(jumps, finallyFlow); + tryFlow.resolveRemaining(jumps); + } + } + + /* Complain about all other jumps in try block */ + Set trySuccs = tryFlow.getSuccessors(); + for (Iterator i = trySuccs.iterator(); i.hasNext(); ) { + for (Jump jumps = tryFlow.getJumps((FlowBlock) i.next()); + jumps != null; jumps = jumps.next) { + DescriptionBlock msg = + new DescriptionBlock + ("ERROR: doesn't go through finally block!"); + if (jumps.prev instanceof ReturnBlock) { + msg.replace(jumps.prev); + msg.appendBlock(jumps.prev); + } else { + jumps.prev.appendBlock(msg); + msg.moveJump(jumps); + } + } + } + + mergeFinallyBlock(tryFlow, catchFlow, finallyBlock); + /* Following code will work be put inside the finallyBlock */ + tryFlow.lastModified = finallyBlock; + return true; + } + + void checkTryCatchOrder() { + /* Check if try/catch ranges are okay. The following succeeds + * for all classes generated by the sun java compiler, but hand + * optimized classes (or generated by other compilers) will fail. + */ + Handler last = null; + for (Iterator i = handlers.iterator(); i.hasNext(); ) { + Handler exc = (Handler) i.next(); + int start = exc.start.getBlockNr(); + int end = exc.end.getBlockNr(); + int handler = exc.handler.getBlockNr(); + if (start > end || handler <= end) + throw new InternalError + ("ExceptionHandler order failed: not " + + start + " < " + end + " <= " + handler); + if (last != null + && (last.start.getBlockNr() != start + || last.end.getBlockNr() != end)) { + /* The last handler does catch another range. + * Due to the order: + * start < last.start.getBlockNr() + * || end > last.end.getBlockNr() + */ + if (end >= last.start.getBlockNr() + && end < last.end.getBlockNr()) + throw new InternalError + ("Exception handlers ranges are intersecting: [" + + last.start.getBlockNr()+", " + + last.end.getBlockNr()+"] and [" + + start+", "+end+"]."); + } + last = exc; + } + } + + /** + * Analyzes all exception handlers to try/catch/finally or + * synchronized blocks. + */ + public void analyze() { + checkTryCatchOrder(); + + Iterator i = handlers.iterator(); + Handler exc = null; + Handler next = i.hasNext() ? (Handler) i.next() : null; + while(next != null) { + Handler last = exc; + exc = next; + next = i.hasNext() ? (Handler) i.next() : null; + + int startNr = exc.start.getBlockNr(); + int endNr = exc.end.getBlockNr(); + int endHandler = Integer.MAX_VALUE; + /* If the next exception handler catches a bigger range + * it must surround the handler completely. + */ + if (next != null + && next.end.getBlockNr() > endNr) + endHandler = next.end.getBlockNr() + 1; + + FlowBlock tryFlow = exc.start; + tryFlow.checkConsistent(); + + if (last == null || exc.type == null + || last.start.getBlockNr() != startNr + || last.end.getBlockNr() != endNr) { + /* The last handler does catch another range. + * Create a new try block. + */ + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("analyzeTry(" + startNr + ", " + endNr+")"); + while(true) { + while (tryFlow.analyze(startNr, endNr+1)); + int nextNr = tryFlow.getNextBlockNr(); + if (nextNr > endNr) + break; + tryFlow = flowBlocks[nextNr]; + } + if (tryFlow.getBlockNr() != startNr) { + GlobalOptions.err.println + ("Warning: Can't completely analyze try."); + } + TryBlock tryBlock = new TryBlock(tryFlow); + } else if (!(tryFlow.block instanceof TryBlock)) + throw new InternalError("no TryBlock"); + + FlowBlock catchFlow = exc.handler; + boolean isMultiUsed = catchFlow.predecessors.size() != 0; + if (!isMultiUsed && next != null) { + for (Iterator j = handlers.tailSet(next).iterator(); + j.hasNext();) { + Handler h = (Handler) j.next(); + if (h.handler == catchFlow) { + isMultiUsed = true; + break; + } + } + } + + if (isMultiUsed) { + /* If this exception is used in other exception handlers, + * create a new flow block, that jumps to the handler. + * This will be our new exception handler. + */ + FlowBlock newFlow = new FlowBlock + (catchFlow.method, catchFlow.getBlockNr(), + catchFlow.prevByCodeOrder); + newFlow.setSuccessors(new FlowBlock[] { catchFlow }); + newFlow.nextByCodeOrder = catchFlow; + catchFlow.prevByCodeOrder = newFlow; + catchFlow = newFlow; + } else { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("analyzeCatch(" + + catchFlow.getBlockNr() + ", " + endHandler + ")"); + while (catchFlow.analyze(catchFlow.getBlockNr(), + endHandler)); + } + + if (exc.type != null) + analyzeCatchBlock(exc.type, tryFlow, catchFlow); + + else if (! analyzeSynchronized(tryFlow, catchFlow, endHandler) + && ! analyzeFinally(tryFlow, catchFlow, endHandler) + && ! analyzeSpecialFinally(tryFlow, catchFlow, + endHandler)) + /* As last resort make a catch(Object) block. This doesn't + * compile, but at least it gives a hint what the code + * does. + */ + analyzeCatchBlock(Type.tObject, tryFlow, catchFlow); + + tryFlow.checkConsistent(); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_ANALYZE) != 0) + GlobalOptions.err.println + ("analyzeTryCatch(" + tryFlow.getBlockNr() + ", " + + tryFlow.getNextBlockNr() + ") done."); + } + } +} diff --git a/jode/src/net/sf/jode/flow/TryBlock.java b/jode/src/net/sf/jode/flow/TryBlock.java new file mode 100644 index 0000000..5bf8165 --- /dev/null +++ b/jode/src/net/sf/jode/flow/TryBlock.java @@ -0,0 +1,226 @@ +/* TryBlock Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.type.*; +import net.sf.jode.expr.Expression; +import net.sf.jode.expr.InvokeOperator; +import net.sf.jode.expr.LocalLoadOperator; + +/** + * A TryBlock is created for each exception in the + * ExceptionHandlers attribute.
    + * + * For each catch block (there may be more than one catch block + * appending a single try block) and for each finally and each + * synchronized block such a TryBlock is created. The + * finally/synchronized-blocks have a null exception type so that they + * are easily distinguishable from the catch blocks.
    + * + * A TryBlock may be converted later into a SynchronizedBlock. + * + * @date 1998/09/16 + * @author Jochen Hoenicke + * @see CatchBlock + * @see FinallyBlock + * @see SynchronizedBlock + */ + +public class TryBlock extends StructuredBlock { + + VariableSet gen; + StructuredBlock[] subBlocks = new StructuredBlock[1]; + + public TryBlock(FlowBlock tryFlow) { + this.gen = (VariableSet) tryFlow.used.clone(); + this.flowBlock = tryFlow; + + StructuredBlock bodyBlock = tryFlow.block; + replace(bodyBlock); + this.subBlocks = new StructuredBlock[] { bodyBlock }; + bodyBlock.outer = this; + tryFlow.lastModified = this; + tryFlow.checkConsistent(); + } + + public void addCatchBlock(StructuredBlock catchBlock) { + StructuredBlock[] newSubBlocks = + new StructuredBlock[subBlocks.length+1]; + System.arraycopy(subBlocks, 0, newSubBlocks, 0, subBlocks.length); + newSubBlocks[subBlocks.length] = catchBlock; + subBlocks = newSubBlocks; + catchBlock.outer = this; + catchBlock.setFlowBlock(flowBlock); + } + + /** + * Replaces the given sub block with a new block. + * @param oldBlock the old sub block. + * @param newBlock the new sub block. + * @return false, if oldBlock wasn't a direct sub block. + */ + public boolean replaceSubBlock(StructuredBlock oldBlock, + StructuredBlock newBlock) { + for (int i=0; i + * try { + * PUSH ARRAY.clone() + * } catch (CloneNotSupportedException ex) { + * throw new InternalError(ex.getMessage()); + * } + * + * which is simply transformed to the content of the try block. + */ + public boolean checkJikesArrayClone() { + + if (subBlocks.length != 2 + || !(subBlocks[0] instanceof InstructionBlock) + || !(subBlocks[1] instanceof CatchBlock)) + return false; + + Expression instr = + ((InstructionBlock)subBlocks[0]).getInstruction(); + CatchBlock catchBlock = (CatchBlock) subBlocks[1]; + + if (instr.isVoid() || instr.getFreeOperandCount() != 0 + || !(instr instanceof InvokeOperator) + || !(catchBlock.catchBlock instanceof ThrowBlock) + || !(((ClassType) catchBlock.exceptionType).getClassName().equals + ("java.lang.CloneNotSupportedException"))) + return false; + + InvokeOperator arrayClone = (InvokeOperator) instr; + if (!arrayClone.getMethodName().equals("clone") + || arrayClone.isStatic() + || !(arrayClone.getMethodType().getTypeSignature() + .equals("()Ljava/lang/Object;")) + || !(arrayClone.getSubExpressions()[0] + .getType().isOfType(Type.tArray(Type.tUnknown)))) + return false; + + Expression throwExpr = + ((ThrowBlock) catchBlock.catchBlock).getInstruction(); + + if (throwExpr.getFreeOperandCount() != 0 + || !(throwExpr instanceof InvokeOperator)) + return false; + + InvokeOperator throwOp = (InvokeOperator) throwExpr; + if (!throwOp.isConstructor() + || !(throwOp.getClassType() instanceof ClassType) + || !(((ClassType) throwOp.getClassType()).getClassName() + .equals("java.lang.InternalError")) + || throwOp.getMethodType().getParameterTypes().length != 1) + return false; + + Expression getMethodExpr = throwOp.getSubExpressions()[1]; + if (!(getMethodExpr instanceof InvokeOperator)) + return false; + + InvokeOperator invoke = (InvokeOperator) getMethodExpr; + if (!invoke.getMethodName().equals("getMessage") + || invoke.isStatic() + || (invoke.getMethodType().getParameterTypes() + .length != 0) + || (invoke.getMethodType().getReturnType() + != Type.tString)) + return false; + + Expression exceptExpr = invoke.getSubExpressions()[0]; + if (!(exceptExpr instanceof LocalLoadOperator) + || !(((LocalLoadOperator) exceptExpr).getLocalInfo() + .equals(catchBlock.exceptionLocal))) + return false; + + subBlocks[0].replace(this); + if (flowBlock.lastModified == this) + flowBlock.lastModified = subBlocks[0]; + return true; + } + + public boolean doTransformations() { + return checkJikesArrayClone(); + } +} diff --git a/jode/src/net/sf/jode/flow/VariableSet.java b/jode/src/net/sf/jode/flow/VariableSet.java new file mode 100644 index 0000000..22e10f6 --- /dev/null +++ b/jode/src/net/sf/jode/flow/VariableSet.java @@ -0,0 +1,257 @@ +/* VariableSet Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import net.sf.jode.util.ArrayEnum; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.AbstractSet; +import java.util.Iterator; +///#enddef + +/** + * This class represents a set of Variables, which are mainly used in + * the in/out sets of StructuredBlock. The type of the Variables is + * LocalInfo.

    + * + * It defines some Helper-Function, like intersecting, merging, union + * and difference.

    + * + * Note that a variable set can contain LocalInfos that use the same + * slot, but are different. + */ +public final class VariableSet extends AbstractSet implements Cloneable { + LocalInfo[] locals; + int count; + + /** + * Creates a new empty variable set + */ + public VariableSet() { + locals = null; + count = 0; + } + + /** + * Creates a new pre initialized variable set + */ + public VariableSet(LocalInfo[] locals) { + count = locals.length; + this.locals = locals; + } + + public final void grow(int size) { + if (locals != null) { + size += count; + if (size > locals.length) { + int nextSize = locals.length * 2; +// GlobalOptions.err.println("wanted: "+size+" next: "+nextSize); + LocalInfo[] newLocals + = new LocalInfo[nextSize > size ? nextSize : size]; + System.arraycopy(locals, 0, newLocals, 0, count); + locals = newLocals; + } + } else if (size > 0) + locals = new LocalInfo[size]; + } + + /** + * Adds a local info to this variable set. + */ + public boolean add(Object li) { + if (contains(li)) + return false; + grow(1); + locals[count++] = (LocalInfo) li; + return true; + } + + /** + * Checks if the variable set contains the given local info. + */ + public boolean contains(Object li) { + li = ((LocalInfo) li).getLocalInfo(); + for (int i=0; i 0) { + other.locals = new LocalInfo[count]; + System.arraycopy(locals, 0, other.locals, 0, count); + } + return other; + } catch (CloneNotSupportedException ex) { + throw new InternalError("Clone?"); + } + } + + /** + * Intersects the current VariableSet with another and returns the + * intersection. The existing VariableSet are not changed. + * @param vs the other variable set. + */ + public VariableSet intersect(VariableSet vs) { + VariableSet intersection = new VariableSet(); + intersection.grow(Math.min(count, vs.count)); + big_loop: + for (int i=0; i + * + * This class is immutable, but note, that the local infos can get merged. + * @see FlowBlock#mapStackToLocal + * @see FlowBlock#removePush + */ +public class VariableStack { + public final static VariableStack EMPTY = new VariableStack(); + + final LocalInfo[] stackMap; + + private VariableStack() { + stackMap = new LocalInfo[0]; + } + + private VariableStack(LocalInfo[] stack) { + stackMap = stack; + } + + public boolean isEmpty() { + return stackMap.length == 0; + } + + public VariableStack pop(int count) { + LocalInfo[] newStack = new LocalInfo[stackMap.length - count]; + System.arraycopy(stackMap, 0, newStack, 0, stackMap.length - count); + return new VariableStack(newStack); + } + + public VariableStack push(LocalInfo li) { + return poppush(0, li); + } + + public VariableStack poppush(int count, LocalInfo li) { + LocalInfo[] newStack = new LocalInfo[stackMap.length - count + 1]; + System.arraycopy(stackMap, 0, newStack, 0, stackMap.length - count); + newStack[stackMap.length - count] = li; + return new VariableStack(newStack); + } + + public VariableStack peek(int count) { + LocalInfo[] peeked = new LocalInfo[count]; + System.arraycopy(stackMap, stackMap.length - count, peeked, 0, count); + return new VariableStack(peeked); + } + + public void merge(VariableStack other) { + if (stackMap.length != other.stackMap.length) + throw new IllegalArgumentException("stack length differs"); + for (int i=0; i= 0; i--) { +// if (!used.contains(stackMap[i])) +// used.addElement(stackMap[i]); + expr = expr.addOperand + (new LocalLoadOperator(stackMap[i].getType(), null, + stackMap[i])); + } + return expr; + } + + public VariableStack executeSpecial(SpecialBlock special) { + if (special.type == special.POP) { + int popped = 0; + int newLength = stackMap.length; + while (popped < special.count) { + newLength--; + popped += stackMap[newLength].getType().stackSize(); + } + if (popped != special.count) + throw new IllegalArgumentException("wrong POP"); + LocalInfo[] newStack = new LocalInfo[newLength]; + System.arraycopy(stackMap, 0, newStack, 0, newLength); + return new VariableStack(newStack); + } else if (special.type == special.DUP) { + int popped = 0; + int numDup = 0; + int startDup = stackMap.length; + while (popped < special.count) { + startDup--; + numDup++; + popped += stackMap[startDup].getType().stackSize(); + } + if (popped != special.count) + throw new IllegalArgumentException("wrong DUP"); + int destDup = startDup; + int depth = 0; + while (depth < special.depth) { + destDup--; + depth += stackMap[destDup].getType().stackSize(); + } + if (depth != special.depth) + throw new IllegalArgumentException("wrong DUP"); + LocalInfo[] newStack = new LocalInfo[stackMap.length + numDup]; + System.arraycopy(stackMap, 0, newStack, 0, destDup); + System.arraycopy(stackMap, startDup, newStack, destDup, numDup); + System.arraycopy(stackMap, destDup, newStack, destDup + numDup, + startDup - destDup); + System.arraycopy(stackMap, startDup, newStack, startDup + numDup, + numDup); + return new VariableStack(newStack); + } else if (special.type == special.SWAP) { + LocalInfo[] newStack = new LocalInfo[stackMap.length]; + System.arraycopy(stackMap, 0, newStack, 0, stackMap.length - 2); + if (stackMap[stackMap.length-2].getType().stackSize() != 1 + || stackMap[stackMap.length-1].getType().stackSize() != 1) + throw new IllegalArgumentException("wrong SWAP"); + newStack[stackMap.length-2] = stackMap[stackMap.length-1]; + newStack[stackMap.length-1] = stackMap[stackMap.length-2]; + return new VariableStack(newStack); + } else + throw new InternalError("Unknown SpecialBlock"); + } + + public String toString() { + StringBuffer result = new StringBuffer("["); + for (int i=0; i < stackMap.length; i++) { + if (i>0) + result.append(", "); + result.append(stackMap[i].getName()); + } + return result.append("]").toString(); + } +} diff --git a/jode/src/net/sf/jode/jvm/CodeVerifier.java b/jode/src/net/sf/jode/jvm/CodeVerifier.java new file mode 100644 index 0000000..3d5d267 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/CodeVerifier.java @@ -0,0 +1,1526 @@ +/* CodeVerifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.BasicBlocks; +import net.sf.jode.bytecode.Block; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.Handler; +import net.sf.jode.bytecode.Instruction; +import net.sf.jode.bytecode.MethodInfo; +import net.sf.jode.bytecode.Opcodes; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.TypeSignature; + +import java.io.IOException; +import java.util.BitSet; +///#def COLLECTIONS java.util +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +///#enddef + +/** + * Verifies a given method. + * + * @author Jochen Hoenicke + */ +public class CodeVerifier implements Opcodes { + ClassInfo ci; + MethodInfo mi; + BasicBlocks bb; + ClassPath classpath; + + String methodType; + + Type returnType; + Type tInt; + Type tLong; + Type tFloat; + Type tDouble; + Type tNone; + Type tSecondPart; + + Type tString; + Type tObject; + + Map typeHash = new HashMap(); + + private Type tType(String typeSig) { + Type type = (Type) typeHash.get(typeSig); + if (type == null) { + int obj = typeSig.charAt(0) == 'N' ? 0 : typeSig.indexOf('L'); + int semi = typeSig.indexOf(';'); + if (obj != -1 && semi != -1) { + String subTypeSig = typeSig.substring(0, obj+1) + + typeSig.substring(semi+1); + String className + = typeSig.substring(obj+1, semi).replace('/', '.'); + ClassInfo classInfo = classpath.getClassInfo(className); + type = new Type(subTypeSig, classInfo, null); + } else + type = new Type(typeSig, null, null); + typeHash.put(typeSig, type); + } + return type; + } + + private Type tType(Block jsrTarget) { + Type type = (Type) typeHash.get(jsrTarget); + if (type == null) { + type = new Type("R", null, jsrTarget); + typeHash.put(jsrTarget, type); + } + return type; + } + + private Type tType(String head, ClassInfo classInfo) { + String typeSig = head + classInfo.getName().replace('.', '/') + ';'; + Type type = (Type) typeHash.get(typeSig); + if (type == null) { + type = new Type(head, classInfo, null); + typeHash.put(typeSig, type); + } + return type; + } + + /** + * We need some more types, than mentioned in jvm. + */ + private static class Type { + /* "ZBCSIFJD" are the normal primitive types. + * "V" stands for void type. + * "L" is normal class type, the class is in classInfo field. + * "[..." is normal array type + * "?" stands for type error + * "N" stands for the uninitialized this of a constructor, + * and classInfo is set. + * "Nxxx" stands for a new uninitialized type, where xxx is + * a unique identifier for the new instruction + * and classInfo is set. + * "0" stands for null type. + * "R" stands for return address type. + * "2" stands for second half of a two word type. + */ + private String typeSig; + + /* The classInfo if this is or contains a class type. + */ + private ClassInfo classInfo; + + /** + * The target block of the jsr if this is a "R" type. + */ + private Block jsrTarget; + + public Type(String typeSig, ClassInfo classInfo, Block jsrTarget) { + if ((typeSig.indexOf('L') >= 0 || typeSig.charAt(0) == 'N') + && classInfo == null) + throw new IllegalArgumentException(); + this.typeSig = typeSig; + this.classInfo = classInfo; + this.jsrTarget = jsrTarget; + } + + public String getTypeSig() { + return typeSig; + } + + public Block getJsrTarget() { + return jsrTarget; + } + + /** + * @param t2 the type signature of the type to check for. + * This may be one of the special signatures: + *

    "[*"
    array of something
    + *
    "+"
    (uninitialized) object/returnvalue type
    + *
    "L"
    (initialized) object/returnvalue type
    + *
    "2", "R"
    as the typeSig parameter
    + *
    + * @return true, iff this is castable to t2 by a + * widening cast. */ + public boolean isOfType(Type destType) { + String thisSig = typeSig; + String destSig = destType.typeSig; + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_VERIFIER) != 0) + GlobalOptions.err.println("isOfType("+thisSig+","+destSig+")"); + if (thisSig.equals(destSig)) + return true; + + char c1 = thisSig.charAt(0); + char c2 = destSig.charAt(0); + switch (c2) { + case 'Z': case 'B': case 'C': case 'S': case 'I': + /* integer type */ + return ("ZBCSI".indexOf(c1) >= 0); + case '+': + return ("L[nNR0".indexOf(c1) >= 0); + + case '[': + if (c1 == '0') + return true; + while (c1 == '[' && c2 == '[') { + thisSig = thisSig.substring(1); + destSig = destSig.substring(1); + c1 = thisSig.charAt(0); + c2 = destSig.charAt(0); + } + + if (c2 == '*') + /* destType is array of unknowns */ + return true; + /* Note that short[] is only compatible to short[], + * therefore we only need to handle Object types specially. + */ + + if (c2 != 'L') + return false; + case 'L': + if (c1 == '0') + return true; + if ("L[".indexOf(c1) < 0) + return false; + + ClassInfo wantedType = destType.classInfo; + if (wantedType == null + || wantedType.getName() == "java.lang.Object") + return true; + + try { + wantedType.load(ClassInfo.HIERARCHY); + if (wantedType.isInterface()) + return true; + if (c1 == 'L') + return wantedType.superClassOf(classInfo); + } catch (IOException ex) { + GlobalOptions.err.println + ("WARNING: Can't get full hierarchy of " + + wantedType + "."); + return true; + } + case 'N': + if (typeSig.charAt(0) != 'N') + return false; + + /* New types must match exactly ... */ + if (this.classInfo == destType.classInfo) + return true; + + /* ... except that a constructor can call the super + * constructor */ + if (typeSig.length() > 1) + return false; + + try { + classInfo.load(ClassInfo.HIERARCHY); + return (classInfo.getSuperclass() == destType.classInfo); + } catch (IOException ex) { + /* ignore, type is maybe correct. */ + return true; + } + } + return false; + } + + /** + * @return The common super type of this and type2. + */ + public Type mergeType(CodeVerifier cv, Type type2) { + String sig1 = typeSig; + String sig2 = type2.typeSig; + + if (this.equals(type2)) + return this; + + char c1 = sig1.charAt(0); + char c2 = sig2.charAt(0); + if (c1 == '*') + return type2; + if (c2 == '*') + return this; + if ("ZBCSI".indexOf(c1) >= 0 && "ZBCSI".indexOf(c2) >= 0) + return this; + + if (c1 == '0') + return ("L[0".indexOf(c2) >= 0) ? type2 : cv.tNone; + if (c2 == '0') + return ("L[".indexOf(c1) >= 0) ? this : cv.tNone; + + + int dimensions = 0; + /* Note that short[] is only compatible to short[], + * therefore we make the array handling after the primitive + * type handling. Also note that we don't allow arrays of + * special types. + */ + while (c1 == '[' && c2 == '[') { + sig1 = sig1.substring(1); + sig2 = sig2.substring(1); + c1 = sig1.charAt(0); + c2 = sig2.charAt(0); + dimensions++; + } + + // One of them is array now, the other is an object, + // the common super is tObject + if ((c1 == '[' && c2 == 'L') + || (c1 == 'L' && c2 == '[')) { + if (dimensions == 0) + return cv.tObject; + StringBuffer result = new StringBuffer(dimensions + 18); + for (int i=0; i< dimensions; i++) + result.append("["); + result.append("Ljava/lang/Object;"); + return cv.tType(result.toString()); + } + + if (c1 == 'L' && c2 == 'L') { + ClassInfo clazz1 = classInfo; + ClassInfo clazz2 = type2.classInfo; + try { + if (clazz1.superClassOf(clazz2)) + return this; + } catch (IOException ex) { + /* clazz1 has no complete hierarchy, we can assume + * that it extends class2. + */ + return this; + } + try { + if (clazz2.superClassOf(clazz1)) + return type2; + } catch (IOException ex) { + /* clazz1 has no complete hierarchy, we can assume + * that it extends class2. + */ + return this; + } + /* Now the complete hierarchy of clazz1 and + * clazz2 is loaded */ + try { + do { + clazz1 = clazz1.getSuperclass(); + } while (!clazz1.superClassOf(clazz2)); + } catch (IOException ex) { + throw new InternalError("Hierarchy vanished?"); + } + StringBuffer result = new StringBuffer + (dimensions + clazz1.getName().length() + 2); + for (int i=0; i< dimensions; i++) + result.append("["); + result.append("L"); + return cv.tType(result.toString(), clazz1); + } + + // Both were arrays, but of different primitive types. The + // common super is tObject with one dimension less. + if (dimensions > 0) { + if (dimensions == 1) + return cv.tObject; + StringBuffer result = new StringBuffer(dimensions + 17); + for (int i=0; i< dimensions - 1; i++) + result.append("["); + result.append("Ljava/lang/Object;"); + return cv.tType(result.toString()); + } + return cv.tNone; + } + + public boolean equals(Object other) { + if (other instanceof Type) { + Type type2 = (Type) other; + return typeSig.equals(type2.typeSig) + && classInfo == type2.classInfo + && jsrTarget == type2.jsrTarget; + } + return false; + } + + public String toString() { + StringBuffer sb = new StringBuffer(typeSig); + if (classInfo != null) + sb.append(classInfo.getName()); + if (jsrTarget != null) + sb.append(jsrTarget); + return sb.toString(); + } + } + + /** + * JLS 4.9.6: Verifying code that contains a finally clause: + * - Each instruction keeps track of the list of jsr targets. + * - For each instruction and each jsr needed to reach that instruction + * a bit vector is maintained of all local vars accessed or modified. + */ + final class JsrUsedInfo { + /** + * The last jsrTarget that must have been traversed. + */ + Block jsrTarget; + + /** + * The set of locals changed since the last jsrTarget. + */ + BitSet jsrUsed; + + JsrUsedInfo(Block jsrTarget, BitSet jsrUsed) { + this.jsrTarget = jsrTarget; + this.jsrUsed = jsrUsed; + } + + JsrUsedInfo(JsrUsedInfo orig) { + this.jsrTarget = orig.jsrTarget; + this.jsrUsed = orig.jsrUsed; + } + + public String toString() { + return ""+jsrTarget+'-'+jsrUsed; + } + } + + class SubroutineInfo { + /** + * The previous used Info, null if this is the outermost jsr. + */ + JsrUsedInfo prevInfo; + /** + * Block number of the return. + */ + Block retBlock = null; + /** + * The VerifyInfo after the ret. + */ + VerifyInfo retInfo; + /** + * The locals used in this subroutine + */ + BitSet usedLocals; + /** + * The bitset containing the numbers of the blocks following + * that may follow a JSR to this subroutine. + */ + BitSet jsrSuccessors = new BitSet(); + } + + /** + * The VerifyInfo contains informations about the state of the stack + * and local variables, as well as the current subroutine. + * + * We create a VerifyInfo for every reachable basic block in the + * verifyInfos array. For not yet reached basic blocks, that are + * the successor of an jsr, it contains the state just _before_ + * the jsr. If later a ret is found, it will take care to correct + * that. + * + * We also have an intermediate VerifyInfo, which is modified + * while we are "simulating" the instructions in a basic block. + * After the basic block is fully simulated, we merge that temporary + * VerifyInfo (cloning() it if necessary) and free it afterwards. + * + * Last but not least, we have a VerifyInfo in SubroutineInfo, that + * records the local/stack state just after the ret. + * + * For information about typechecking jsrs: + * + * JLS 4.9.6: Verifying code that contains a finally clause: + * - Each instruction keeps track of the list of jsr targets. + * - For each instruction and each jsr needed to reach that instruction + * a bit vector is maintained of all local vars accessed or modified. + * + * The difficult part are subroutines inside a method (the jsr and + * ret instructions). We remember the last jsrtarget that must be + * traversed to reach this block (a jsrTarget is a basic block to + * which a jsr jumps). Since a jsrTarget has a "R" + * type on the stack there is no possibility to reach a jsrTarget + * on another way. + * + * We only remember the innermost subroutine, but its SubroutineInfo + * will contain the information about outer subroutines. + * + * If we change a local we remember it in jsrUsed. This is + * needed for the local merging on a ret as specified in the jvm + * spec. + */ + final class VerifyInfo implements Cloneable { + /** + * The jsr and used info for the innermost surrounding jsr. + * Normally this is null. + */ + JsrUsedInfo jsrInfo; + + /** + * The current stack height + */ + int stackHeight = 0; + /** + * The types currently on the stack. The entries at indices + * bigger or equal stackHeight are _undefined_. + * @see Type + */ + Type[] stack = new Type[bb.getMaxStack()]; + + /** + * The types currently in local slots. An entry is null, if + * the local may not be used. + * @see Type + */ + Type[] locals = new Type[bb.getMaxLocals()]; + + public Object clone() { + try { + VerifyInfo result = (VerifyInfo) super.clone(); + result.stack = (Type[]) stack.clone(); + result.locals = (Type[]) locals.clone(); + if (jsrInfo != null) + result.jsrInfo = new JsrUsedInfo(jsrInfo); + return result; + } catch(CloneNotSupportedException ex) { + throw new InternalError("Clone not supported?"); + } + } + + public final void reserve(int count) throws VerifyException { + if (stackHeight + count > stack.length) + throw new VerifyException("stack overflow"); + } + + public final void need(int count) throws VerifyException { + if (stackHeight < count) + throw new VerifyException("stack underflow"); + } + + public final void push(Type type) throws VerifyException { + reserve(1); + stack[stackHeight++] = type; + } + + public final Type pop() throws VerifyException { + need(1); + return stack[--stackHeight]; + } + + public String toString() { + StringBuffer result = new StringBuffer("locals:["); + String comma = ""; + for (int i=0; i= bb.getMaxLocals()) + throw new VerifyException("Too few local slots"); + if (mi.getName().equals("")) + info.locals[slot++] = tType("N", ci); + else + info.locals[slot++] = tType("L", ci); + } + + String[] paramTypes = TypeSignature.getParameterTypes(methodType); + for (int i = 0; i < paramTypes.length; i++) { + if (slot >= bb.getMaxLocals()) + throw new VerifyException("Too few local slots"); + info.locals[slot++] = tType(paramTypes[i]); + if (TypeSignature.getTypeSize(paramTypes[i]) == 2) { + if (slot >= bb.getMaxLocals()) + throw new VerifyException("Too few local slots"); + info.locals[slot++] = tSecondPart; + } + } + while (slot < bb.getMaxLocals()) + info.locals[slot++] = tNone; + return info; + } + + /** + * Merges the second JsrUsedInfo into the first. + * @return true if first JsrUsedInfo changed in this merge, i.e. + * got more specific. + */ + private boolean mergeJsrTarget(JsrUsedInfo first, JsrUsedInfo second) { + /* trivial cases first. */ + if (first.jsrTarget == second.jsrTarget) + return false; + if (first.jsrTarget == null || second.jsrTarget == null) + return false; + + /* Now the bitsets can't be null */ + int firstDepth = 0; + for (JsrUsedInfo t = first; t != null; + t = subInfos[t.jsrTarget.getBlockNr()].prevInfo) + firstDepth++; + int secondDepth = 0; + for (JsrUsedInfo t = second; t != null; + t = subInfos[t.jsrTarget.getBlockNr()].prevInfo) + secondDepth++; + + boolean changed = false; + Block secondTarget = second.jsrTarget; + while (firstDepth > secondDepth) { + JsrUsedInfo firstPrev + = subInfos[first.jsrTarget.getBlockNr()].prevInfo; + if (firstPrev == null) + first.jsrTarget = null; + else + first.jsrTarget = firstPrev.jsrTarget; + firstDepth--; + } + while (secondDepth > firstDepth) { + changed = true; + JsrUsedInfo secondPrev + = subInfos[secondTarget.getBlockNr()].prevInfo; + if (secondPrev != null) { + first.jsrUsed.or(secondPrev.jsrUsed); + secondTarget = secondPrev.jsrTarget; + } else + secondTarget = null; + secondDepth--; + } + while (first.jsrTarget != secondTarget) { + changed = true; + JsrUsedInfo firstPrev + = subInfos[first.jsrTarget.getBlockNr()].prevInfo; + if (firstPrev == null) + first.jsrTarget = null; + else + first.jsrTarget = firstPrev.jsrTarget; + JsrUsedInfo secondPrev + = subInfos[secondTarget.getBlockNr()].prevInfo; + if (secondPrev != null) { + first.jsrUsed.or(secondPrev.jsrUsed); + secondTarget = secondPrev.jsrTarget; + } else + secondTarget = null; + } + return changed; + } + + private boolean mergeInfo(Block block, VerifyInfo info) + throws VerifyException { + int blockNr = block.getBlockNr(); + if (verifyInfos[blockNr] == null) { + verifyInfos[blockNr] = (VerifyInfo) info.clone(); + return true; + } + boolean changed = false; + VerifyInfo oldInfo = verifyInfos[blockNr]; + if (oldInfo.stackHeight != info.stackHeight) + throw new VerifyException("Stack height differ at: " + blockNr); + for (int i=0; i < oldInfo.stackHeight; i++) { + Type newType = oldInfo.stack[i].mergeType(this, info.stack[i]); + if (!newType.equals(oldInfo.stack[i])) { + if (newType == tNone) + throw new VerifyException("Type error while merging: " + + oldInfo.stack[i] + + " and " + info.stack[i]); + changed = true; + oldInfo.stack[i] = newType; + } + } + for (int i=0; i < bb.getMaxLocals(); i++) { + Type newType = oldInfo.locals[i].mergeType(this, info.locals[i]); + if (!newType.equals(oldInfo.locals[i])) { + changed = true; + oldInfo.locals[i] = newType; + } + } + if (oldInfo.jsrInfo != null) { + if (info.jsrInfo == null) { + oldInfo.jsrInfo = null; + changed = true; + } else if (mergeJsrTarget(oldInfo.jsrInfo, info.jsrInfo)) { + if (oldInfo.jsrInfo.jsrTarget == null) + oldInfo.jsrInfo = null; + changed = true; + } + } + return changed; + } + + private void modelEffect(Instruction instr, VerifyInfo info) + throws VerifyException { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_VERIFIER) != 0) + GlobalOptions.err.println(""+info+instr); + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_nop: + case opc_goto: + break; + case opc_ldc: { + Type type; + Object constant = instr.getConstant(); + if (constant == null) + type = tType("0"); + else if (constant instanceof Integer) + type = tInt; + else if (constant instanceof Float) + type = tFloat; + else + type = tString; + info.push(type); + break; + } + case opc_ldc2_w: { + Type type; + Object constant = instr.getConstant(); + if (constant instanceof Long) + type = tLong; + else + type = tDouble; + info.push(type); + info.push(tSecondPart); + break; + } + case opc_iload: + case opc_lload: + case opc_fload: + case opc_dload: + case opc_aload: { + if (info.jsrInfo != null) { + info.jsrInfo.jsrUsed.set(instr.getLocalSlot()); + if ((opcode & 0x1) == 0) + info.jsrInfo.jsrUsed.set(instr.getLocalSlot() + 1); + } + if ((opcode & 0x1) == 0 + && info.locals[instr.getLocalSlot()+1] != tSecondPart) + throw new VerifyException(instr.getDescription()); + Type type = info.locals[instr.getLocalSlot()]; + if (!type.isOfType(types[opcode - opc_iload])) + throw new VerifyException(instr.getDescription()); + info.push(type); + if ((opcode & 0x1) == 0) + info.push(tSecondPart); + break; + } + case opc_iaload: case opc_laload: + case opc_faload: case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: { + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + Type arrType = info.pop(); + if (!arrType.isOfType(arrayTypes[opcode - opc_iaload]) + && (opcode != opc_baload + || !arrType.isOfType(tType("[Z")))) + throw new VerifyException(instr.getDescription()); + + String typeSig = arrType.getTypeSig(); + Type elemType; + if (typeSig.charAt(0) == '[') { + if (arrType.classInfo != null) + elemType = tType(typeSig.substring(1), arrType.classInfo); + else + elemType = tType(typeSig.substring(1)); + } else if(opcode == opc_aaload) + elemType = tType("0"); + else + elemType = types[opcode - opc_iaload]; + info.push(elemType); + if (((1 << opcode - opc_iaload) & 0xa) != 0) + info.push(tSecondPart); + break; + } + case opc_istore: case opc_lstore: + case opc_fstore: case opc_dstore: case opc_astore: { + if (info.jsrInfo != null) { + info.jsrInfo.jsrUsed.set(instr.getLocalSlot()); + if ((opcode & 0x1) != 0) + info.jsrInfo.jsrUsed.set(instr.getLocalSlot()+1); + } + if ((opcode & 0x1) != 0 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + Type type = info.pop(); + if (!type.isOfType(types[opcode - opc_istore])) + if (opcode != opc_astore || !type.isOfType(tType("R"))) + throw new VerifyException(instr.getDescription()); + info.locals[instr.getLocalSlot()] = type; + if ((opcode & 0x1) != 0) + info.locals[instr.getLocalSlot()+1] = tSecondPart; + break; + } + case opc_iastore: case opc_lastore: + case opc_fastore: case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: { + if (((1 << opcode - opc_iastore) & 0xa) != 0 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + Type type = info.pop(); + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + Type arrType = info.pop(); + if (!arrType.isOfType(arrayTypes[opcode - opc_iastore]) + && (opcode != opc_bastore || !arrType.isOfType(tType("[Z")))) + throw new VerifyException(instr.getDescription()); + Type elemType = opcode >= opc_bastore ? tInt + : types[opcode - opc_iastore]; + if (!type.isOfType(elemType)) + throw new VerifyException(instr.getDescription()); + break; + } + case opc_pop: case opc_pop2: { + int count = opcode - (opc_pop-1); + info.need(count); + info.stackHeight -= count; + break; + } + case opc_dup: case opc_dup_x1: case opc_dup_x2: { + int depth = opcode - opc_dup; + info.reserve(1); + info.need(depth+1); + if (info.stack[info.stackHeight-1] == tSecondPart) + throw new VerifyException(instr.getDescription()); + + int stackdepth = info.stackHeight - (depth + 1); + if (info.stack[stackdepth] == tSecondPart) + throw new VerifyException(instr.getDescription() + + " on long or double"); + for (int i=info.stackHeight; i > stackdepth; i--) + info.stack[i] = info.stack[i-1]; + info.stack[stackdepth] = info.stack[info.stackHeight++]; + break; + } + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: { + int depth = opcode - opc_dup2; + info.reserve(2); + info.need(depth+2); + if (info.stack[info.stackHeight-2] == tSecondPart) + throw new VerifyException(instr.getDescription() + + " on misaligned long or double"); + int stacktop = info.stackHeight; + int stackdepth = stacktop - (depth + 2); + if (info.stack[stackdepth] == tSecondPart) + throw new VerifyException(instr.getDescription() + + " on long or double"); + for (int i=stacktop; i > stackdepth; i--) + info.stack[i+1] = info.stack[i-1]; + info.stack[stackdepth+1] = info.stack[stacktop+1]; + info.stack[stackdepth] = info.stack[stacktop]; + info.stackHeight+=2; + break; + } + case opc_swap: { + info.need(2); + if (info.stack[info.stackHeight-2] == tSecondPart + || info.stack[info.stackHeight-1] == tSecondPart) + throw new VerifyException(instr.getDescription() + + " on misaligned long or double"); + Type tmp = info.stack[info.stackHeight-1]; + info.stack[info.stackHeight-1] = + info.stack[info.stackHeight-2]; + info.stack[info.stackHeight-2] = tmp; + break; + } + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: { + Type type = types[(opcode - opc_iadd) & 3]; + if ((opcode & 1) != 0 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(type)) + throw new VerifyException(instr.getDescription()); + if ((opcode & 1) != 0) { + info.need(2); + if (info.stack[info.stackHeight-1] != tSecondPart + || !info.stack[info.stackHeight-2].isOfType(type)) + throw new VerifyException(instr.getDescription()); + } else { + info.need(1); + if (!info.stack[info.stackHeight-1].isOfType(type)) + throw new VerifyException(instr.getDescription()); + } + break; + } + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: { + Type type = types[(opcode - opc_ineg) & 3]; + if ((opcode & 1) != 0) { + info.need(2); + if (info.stack[info.stackHeight-1] != tSecondPart + || !info.stack[info.stackHeight-2].isOfType(type)) + throw new VerifyException(instr.getDescription()); + } else { + info.need(1); + if (!info.stack[info.stackHeight-1].isOfType(type)) + throw new VerifyException(instr.getDescription()); + } + break; + } + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + + if ((opcode & 1) != 0) { + info.need(2); + if (info.stack[info.stackHeight-1] != tSecondPart || + !info.stack[info.stackHeight-2].isOfType(tLong)) + throw new VerifyException(instr.getDescription()); + } else { + info.need(1); + if (!info.stack[info.stackHeight-1].isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + } + break; + + case opc_iand: case opc_land: + case opc_ior : case opc_lor : + case opc_ixor: case opc_lxor: + if ((opcode & 1) != 0 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(types[opcode & 1])) + throw new VerifyException(instr.getDescription()); + if ((opcode & 1) != 0) { + info.need(2); + if (info.stack[info.stackHeight-1] != tSecondPart + || !info.stack[info.stackHeight-2].isOfType(tLong)) + throw new VerifyException(instr.getDescription()); + } else { + info.need(1); + if (!info.stack[info.stackHeight-1].isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + } + break; + + case opc_iinc: + if (!info.locals[instr.getLocalSlot()].isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + break; + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: { + int from = (opcode-opc_i2l)/3; + int to = (opcode-opc_i2l)%3; + if (to >= from) + to++; + if ((from & 1) != 0 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(types[from])) + throw new VerifyException(instr.getDescription()); + + info.push(types[to]); + if ((to & 1) != 0) + info.push(tSecondPart); + break; + } + case opc_i2b: case opc_i2c: case opc_i2s: + info.need(1); + if (!info.stack[info.stackHeight-1].isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + break; + + case opc_lcmp: + if (info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tLong)) + throw new VerifyException(instr.getDescription()); + if (info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tLong)) + throw new VerifyException(instr.getDescription()); + info.push(tInt); + break; + case opc_dcmpl: case opc_dcmpg: + if (info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tDouble)) + throw new VerifyException(instr.getDescription()); + if (info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tDouble)) + throw new VerifyException(instr.getDescription()); + info.push(tInt); + break; + case opc_fcmpl: case opc_fcmpg: + if (!info.pop().isOfType(tFloat)) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tFloat)) + throw new VerifyException(instr.getDescription()); + info.push(tInt); + break; + + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_tableswitch: + case opc_lookupswitch: + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + break; + + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + break; + case opc_if_acmpeq: case opc_if_acmpne: + if (!info.pop().isOfType(tType("+"))) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tType("+"))) + throw new VerifyException(instr.getDescription()); + break; + case opc_ifnull: case opc_ifnonnull: + if (!info.pop().isOfType(tType("+"))) + throw new VerifyException(instr.getDescription()); + break; + + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: { + if (((1 << opcode - opc_ireturn) & 0xa) != 0 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + Type type = info.pop(); + if (!type.isOfType(types[opcode - opc_ireturn]) + || !type.isOfType(returnType)) + throw new VerifyException(instr.getDescription()); + break; + } + case opc_jsr: + case opc_ret: + // handled in main loop + break; + case opc_return: + if (!returnType.typeSig.equals("V")) + throw new VerifyException(instr.getDescription()); + break; + case opc_getstatic: { + Reference ref = instr.getReference(); + String type = ref.getType(); + info.push(tType(type)); + if (TypeSignature.getTypeSize(type) == 2) + info.push(tSecondPart); + break; + } + case opc_getfield: { + Reference ref = instr.getReference(); + Type classType = tType(ref.getClazz()); + if (!info.pop().isOfType(classType)) + throw new VerifyException(instr.getDescription()); + String type = ref.getType(); + info.push(tType(type)); + if (TypeSignature.getTypeSize(type) == 2) + info.push(tSecondPart); + break; + } + case opc_putstatic: { + Reference ref = instr.getReference(); + String type = ref.getType(); + if (TypeSignature.getTypeSize(type) == 2 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tType(type))) + throw new VerifyException(instr.getDescription()); + break; + } + case opc_putfield: { + Reference ref = instr.getReference(); + String type = ref.getType(); + if (TypeSignature.getTypeSize(type) == 2 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tType(type))) + throw new VerifyException(instr.getDescription()); + Type classType = tType(ref.getClazz()); + Type classOnStack = info.pop(); + if (!classOnStack.isOfType(classType)) { + /* Sometimes synthetic code writes to uninitialized classes. */ + classType = tType("N" + ref.getClazz().substring(1)); + if (!classOnStack.isOfType(classType)) + throw new VerifyException(instr.getDescription()); + } + break; + } + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic : + case opc_invokeinterface: { + Reference ref = instr.getReference(); + String refmt = ref.getType(); + String[] paramTypes = TypeSignature.getParameterTypes(refmt); + for (int i=paramTypes.length - 1; i >= 0; i--) { + if (TypeSignature.getTypeSize(paramTypes[i]) == 2 + && info.pop() != tSecondPart) + throw new VerifyException(instr.getDescription()); + if (!info.pop().isOfType(tType(paramTypes[i]))) + throw new VerifyException(instr.getDescription()); + } + if (ref.getName().equals("")) { + Type clazz = info.pop(); + String typeSig = clazz.getTypeSig(); + String refClazzSig = ref.getClazz(); + Type refClazz = tType("N" + refClazzSig.substring(1)); + if (opcode != opc_invokespecial + || refClazzSig.charAt(0) != 'L' + || !clazz.isOfType(refClazz)) + throw new VerifyException(instr.getDescription()); + Type newType = tType("L" + clazz.classInfo.getName() + .replace('.','/')+";"); + for (int i=0; i< info.stackHeight; i++) + if (info.stack[i] == clazz) + info.stack[i] = newType; + for (int i=0; i< info.locals.length; i++) + if (info.locals[i] == clazz) + info.locals[i] = newType; + } else if (opcode != opc_invokestatic) { + Type classType = tType(ref.getClazz()); + if (!info.pop().isOfType(classType)) + throw new VerifyException(instr.getDescription()); + } + String type = TypeSignature.getReturnType(refmt); + if (!type.equals("V")) { + info.push(tType(type)); + if (TypeSignature.getTypeSize(type) == 2) + info.push(tSecondPart); + } + break; + } + case opc_new: { + String clName = instr.getClazzType(); + info.stack[info.stackHeight++] = + tType("N" + clName.substring(1) + instr.hashCode()); + break; + } + case opc_arraylength: { + if (!info.pop().isOfType(tType("[*"))) + throw new VerifyException(instr.getDescription()); + info.push(tInt); + break; + } + case opc_athrow: { + if (!info.pop().isOfType(tType("Ljava/lang/Throwable;"))) + throw new VerifyException(instr.getDescription()); + break; + } + case opc_checkcast: { + Type classType = tType(instr.getClazzType()); + if (!info.pop().isOfType(tType("+"))) + throw new VerifyException(instr.getDescription()); + info.push(classType); + break; + } + case opc_instanceof: { + if (!info.pop().isOfType(tType("Ljava/lang/Object;"))) + throw new VerifyException(instr.getDescription()); + info.push(tInt); + break; + } + case opc_monitorenter: + case opc_monitorexit: + if (!info.pop().isOfType(tType("Ljava/lang/Object;"))) + throw new VerifyException(instr.getDescription()); + break; + case opc_multianewarray: { + int dimension = instr.getDimensions(); + for (int i=dimension - 1; i >= 0; i--) + if (!info.pop().isOfType(tInt)) + throw new VerifyException(instr.getDescription()); + Type classType = tType(instr.getClazzType()); + info.push(classType); + break; + } + default: + throw new InternalError("Invalid opcode "+opcode); + } + } + + /* We manually program a bitset, since the best features are + * missing in jdk 1.1. + */ + private class MyBitSet { + int[] data; + // This is always smaller than the first set bit. + int firstBit; + + public MyBitSet(int maxLength) { + data = new int[(maxLength + 31) / 32]; + firstBit = 0; + } + + public void set(int bit) { + data[bit >> 5] |= 1 << (bit & 0x1f); + if (bit < firstBit) + firstBit = bit; + } + + public void clear(int bit) { + data[bit >> 5] &= ~(1 << (bit & 0x1f)); + } + + public int findFirst() { + int first = firstBit >> 5; + while (data[first] == 0) { + first++; + firstBit = first << 5; + } + int bitmask = data[first] >> (firstBit & 0x1f); + while ((bitmask & 1) == 0) { + bitmask >>= 1; + firstBit++; + } + return firstBit; + } + + public boolean isEmpty() { + for (int i = firstBit >> 5; i < data.length; i++) { + if (data[i] != 0) + return false; + } + return true; + } + } + + private void doVerify() throws VerifyException { + Block[] blocks = bb.getBlocks(); + int len = blocks.length; + verifyInfos = new VerifyInfo[len]; + beforeJsrs = new VerifyInfo[len]; + subInfos = new SubroutineInfo[len]; + + MyBitSet todoSet = new MyBitSet(blocks.length); + Block firstBlock = bb.getStartBlock(); + if (firstBlock == null) { + /* empty method is okay */ + return; + } + verifyInfos[firstBlock.getBlockNr()] = initInfo(); + todoSet.set(firstBlock.getBlockNr()); + while (!todoSet.isEmpty()) { + int blockNr = todoSet.findFirst(); + todoSet.clear(blockNr); + Block block = blocks[blockNr]; + VerifyInfo info = (VerifyInfo) verifyInfos[blockNr].clone(); + + Handler[] handlers = block.getHandlers(); + if (handlers.length > 0) { + VerifyInfo excInfo = (VerifyInfo) info.clone(); + excInfo.stackHeight = 1; + for (int i=0; i < handlers.length; i++) { + String type = handlers[i].getType(); + if (type != null) + excInfo.stack[0] = + tType("L" + type.replace('.', '/') + ";"); + else + excInfo.stack[0] + = tType("Ljava/lang/Throwable;"); + Block catcher = handlers[i].getCatcher(); + if (mergeInfo(catcher, excInfo)) + todoSet.set(catcher.getBlockNr()); + } + } + + + Instruction instr = null; + Iterator iter = Arrays.asList(block.getInstructions()).iterator(); + while (iter.hasNext()) { + instr = (Instruction) iter.next(); + modelEffect(instr, info); + + if (handlers.length > 0 && instr.isStore()) { + for (int i=0; i < handlers.length; i++) { + int slot = instr.getLocalSlot(); + Block catcher = handlers[i].getCatcher(); + int catcherNr = catcher.getBlockNr(); + VerifyInfo oldInfo = verifyInfos[catcherNr]; + Type newType = oldInfo.locals[slot] + .mergeType(this, info.locals[slot]); + if (!newType.equals(oldInfo.locals[slot])) { + oldInfo.locals[slot] = newType; + todoSet.set(catcherNr); + } + } + } + } + + int opcode = instr.getOpcode(); + if (opcode == opc_jsr) { + Block jsrTarget = block.getSuccs()[0]; + Block nextBlock = block.getSuccs()[1]; + + if (info.jsrInfo != null) { + // Check for recursive jsrs. + for (JsrUsedInfo jui = info.jsrInfo; + jui != null; + jui = subInfos[jui.jsrTarget.getBlockNr()] + .prevInfo) { + // Don't assume this is recursive, but assume + // that the previous rets were left instead. + //XXXXXXXXXXXXXXXXX + if (jui.jsrTarget == jsrTarget) { + // This is a recursive jsr. Or the previous + // invocation of the jsr terminated without a + // ret. We forbid this! XXX I think this too + // harsh, but doing it right is very difficult, + // so I stay on secure side. + throw new VerifyException("Recursive JSR!"); + } + } + } + + // Create the VerifyInfo for the state after the jsr + // is performed. + VerifyInfo targetInfo = (VerifyInfo) info.clone(); + targetInfo.push(tType(jsrTarget)); + targetInfo.jsrInfo + = new JsrUsedInfo(jsrTarget, new BitSet()); + // Merge the target info + if (mergeInfo(jsrTarget, targetInfo)) + todoSet.set(jsrTarget.getBlockNr()); + + SubroutineInfo subInfo = subInfos[jsrTarget.getBlockNr()]; + // Create the subroutine info if it doesn't yet exists. + if (subInfo == null) { + subInfo = new SubroutineInfo(); + subInfos[jsrTarget.getBlockNr()] = subInfo; + if (info.jsrInfo != null) + subInfo.prevInfo = new JsrUsedInfo(info.jsrInfo); + } else { + boolean changed; + if (info.jsrInfo != null) { + changed = mergeJsrTarget + (subInfo.prevInfo, info.jsrInfo); + if (subInfo.prevInfo.jsrTarget == null) + subInfo.prevInfo = null; + } else { + subInfo.prevInfo = null; + changed = true; + } + if (changed + && subInfos[jsrTarget.getBlockNr()].retBlock != null) + todoSet.set(subInfos[jsrTarget.getBlockNr()] + .retBlock.getBlockNr()); + } + + if (nextBlock != null) { + // Add our successor to the successor list. + subInfo.jsrSuccessors.set(nextBlock.getBlockNr()); + + if (subInfo.retInfo != null) { + // The jsr target already knows its return + // instruction, we do the ret merging immediately + VerifyInfo retInfo = subInfo.retInfo; + info.stack = retInfo.stack; + info.stackHeight = retInfo.stackHeight; + if (subInfo.prevInfo != null) + info.jsrInfo = new JsrUsedInfo(subInfo.prevInfo); + else + info.jsrInfo = null; + BitSet usedLocals = subInfo.usedLocals; + for (int j = 0; j < bb.getMaxLocals(); j++) { + if (usedLocals.get(j)) + info.locals[j] = retInfo.locals[j]; + } + if (mergeInfo(nextBlock, info)) + todoSet.set(nextBlock.getBlockNr()); + } else { + beforeJsrs[nextBlock.getBlockNr()] = info; + } + } + } else if (opcode == opc_ret) { + Type retVarType = info.locals[instr.getLocalSlot()]; + if (info.jsrInfo == null || !retVarType.isOfType(tType("R"))) + throw new VerifyException(instr.getDescription()); + Block jsrTarget = retVarType.getJsrTarget(); + BitSet usedLocals = (BitSet) info.jsrInfo.jsrUsed; + for (Block lastTarget = info.jsrInfo.jsrTarget; + jsrTarget != lastTarget; + lastTarget = subInfos[lastTarget.getBlockNr()] + .prevInfo.jsrTarget) { + if (lastTarget == null) + throw new VerifyException("returned to a leaved jsr"); + usedLocals.or(subInfos[lastTarget.getBlockNr()] + .prevInfo.jsrUsed); + } + + SubroutineInfo subInfo = subInfos[jsrTarget.getBlockNr()]; + if (subInfo.retBlock != null && subInfo.retBlock != block) + throw new VerifyException + ("JsrTarget has more than one ret: " + jsrTarget); + subInfo.retInfo = info; + subInfo.usedLocals = usedLocals; + for (int i=0; i < blocks.length; i++) { + if (subInfo.jsrSuccessors.get(i)) { + VerifyInfo afterJsrInfo; + // If this was the first time, copy the info before + // the jsr. + if (subInfo.retBlock == null) { + afterJsrInfo = beforeJsrs[i]; + // Check if the infos are mergeable, + // i.e. the items on the stack match. + // This isn't specified by the virtual + // machine specification, but it would be + // really bad, if we have to support such + // weird jsrs. The decompiler doesn't + // like them! + if (afterJsrInfo.stackHeight + != info.stackHeight) + throw new VerifyException + ("Stack height differ after jsr to: " + + jsrTarget); + for (int k = 0; k < info.stackHeight; k++) { + if (info.stack[i].mergeType + (this, afterJsrInfo.stack[k]) == tNone) + throw new VerifyException + ("Type error while"+ + " merging stacks after jsr"); + } + } else + afterJsrInfo = (VerifyInfo) verifyInfos[i].clone(); + + afterJsrInfo.stack = info.stack; + afterJsrInfo.stackHeight = info.stackHeight; + afterJsrInfo.jsrInfo = subInfo.prevInfo; + if (subInfo.prevInfo != null) + afterJsrInfo.jsrInfo + = new JsrUsedInfo(subInfo.prevInfo); + else + afterJsrInfo.jsrInfo = null; + for (int j = 0; j < bb.getMaxLocals(); j++) { + if (usedLocals.get(j)) + afterJsrInfo.locals[j] = info.locals[j]; + } + if (mergeInfo(blocks[i], afterJsrInfo)) + todoSet.set(i); + } + } + subInfo.retBlock = block; + } else { + Block[] succs = block.getSuccs(); + for (int i=0; i< succs.length; i++) { + Block succ = succs[i]; + if (succ == null) { + if (info.stackHeight != 0) + throw new VerifyException(); + continue; + } + + /* We don't need to check for uninitialized objects + * in back-branch. The reason is the following: + * + * An uninitialized object can't merge with anything + * else, so if this is really a back-branch to already + * analyzed code, the uninitialized object will simply + * vanish to unknown on the merge. + */ + if (mergeInfo(succ, info)) + todoSet.set(succ.getBlockNr()); + } + } + } + + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_VERIFIER) != 0) { + dumpInfo(GlobalOptions.err); + } + } + + public void verify() throws VerifyException { + try { + doVerify(); + } catch (VerifyException ex) { + dumpInfo(GlobalOptions.err); + throw ex; + } catch (RuntimeException ex) { + dumpInfo(GlobalOptions.err); + throw ex; + } + } +} diff --git a/jode/src/net/sf/jode/jvm/Interpreter.java b/jode/src/net/sf/jode/jvm/Interpreter.java new file mode 100644 index 0000000..80b9ed9 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/Interpreter.java @@ -0,0 +1,767 @@ +/* Interpreter Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.BasicBlocks; +import net.sf.jode.bytecode.Block; +import net.sf.jode.bytecode.Handler; +import net.sf.jode.bytecode.Instruction; +import net.sf.jode.bytecode.Opcodes; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.TypeSignature; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +///#def COLLECTIONS java.util +import java.util.Arrays; +import java.util.Iterator; +///#enddef + +/** + * This class is a java virtual machine written in java :-). Well not + * exactly. It is only a bytecode interpreter, you have to supply the + * rest of the VM (the runtime environment). + * + * @author Jochen Hoenicke + */ +public class Interpreter implements Opcodes { + + private final static int CMP_EQ = 0; + private final static int CMP_NE = 1; + private final static int CMP_LT = 2; + private final static int CMP_GE = 3; + private final static int CMP_GT = 4; + private final static int CMP_LE = 5; + private final static int CMP_GREATER_MASK + = (1 << CMP_GT)|(1 << CMP_GE)|(1 << CMP_NE); + private final static int CMP_LESS_MASK + = (1 << CMP_LT)|(1 << CMP_LE)|(1 << CMP_NE); + private final static int CMP_EQUAL_MASK + = (1 << CMP_GE)|(1 << CMP_LE)|(1 << CMP_EQ); + + private RuntimeEnvironment env; + + public Interpreter(RuntimeEnvironment env) { + this.env = env; + } + + private Value[] fillParameters(BasicBlocks bb, + Object cls, Object[] params) { + Value[] locals = new Value[bb.getMaxLocals()]; + for (int i=0; i< locals.length; i++) + locals[i] = new Value(); + + String myType = bb.getMethodInfo().getType(); + String[] myParamTypes = TypeSignature.getParameterTypes(myType); + int slot = 0; + if (!bb.getMethodInfo().isStatic()) + locals[slot++].setObject(cls); + for (int i=0; i< myParamTypes.length; i++) { + locals[slot].setObject(params[i]); + slot += TypeSignature.getTypeSize(myParamTypes[i]); + } + return locals; + } + + public Object interpretMethod(BasicBlocks bb, + Object instance, Object[] myParams) + throws InterpreterException, InvocationTargetException { + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_INTERPRT) != 0) + GlobalOptions.err.println("Interpreting "+bb); + + Value[] locals = fillParameters(bb, instance, myParams); + Value[] stack = new Value[bb.getMaxStack()]; + for (int i=0; i < stack.length; i++) + stack[i] = new Value(); + + Block[] blocks = bb.getBlocks(); + Block nextBlock = bb.getStartBlock(); + + int stacktop = 0; + Block[] succs = null; + Handler[] handlers = null; + Iterator iter = null; + + big_loop: + for(;;) { + if (iter == null || !iter.hasNext()) { + /* If block is over continue with the next block */ + if (nextBlock == null) + return Void.TYPE; + iter = Arrays.asList(nextBlock.getInstructions()).iterator(); + succs = nextBlock.getSuccs(); + handlers = nextBlock.getHandlers(); + nextBlock = succs.length > 0 ? succs[succs.length - 1] : null; + } + try { + Instruction instr = (Instruction) iter.next(); + if ((GlobalOptions.debuggingFlags + & GlobalOptions.DEBUG_INTERPRT) != 0) { + GlobalOptions.err.println(instr.getDescription()); + GlobalOptions.err.print("stack: ["); + for (int i=0; i0) + GlobalOptions.err.print(","); + GlobalOptions.err.print(stack[i]); + if (stack[i].objectValue() instanceof char[]) { + GlobalOptions.err.print + (new String((char[])stack[i].objectValue())); + } + } + GlobalOptions.err.println("]"); + GlobalOptions.err.print("local: ["); + for (int i=0; i> stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_iushr: + stack[stacktop-2].setInt(stack[stacktop-2].intValue() + >>> stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_iand: + stack[stacktop-2].setInt(stack[stacktop-2].intValue() + & stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_ior : + stack[stacktop-2].setInt(stack[stacktop-2].intValue() + | stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_ixor: + stack[stacktop-2].setInt(stack[stacktop-2].intValue() + ^ stack[stacktop-1].intValue()); + stacktop--; + break; + + case opc_lshl: + stack[stacktop-3].setLong(stack[stacktop-3].longValue() + << stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_lshr: + stack[stacktop-3].setLong(stack[stacktop-3].longValue() + >> stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_lushr: + stack[stacktop-3].setLong(stack[stacktop-3].longValue() + >>> stack[stacktop-1].intValue()); + stacktop--; + break; + case opc_land: + stacktop-=2; + stack[stacktop-2].setLong(stack[stacktop-2].longValue() + & stack[stacktop].longValue()); + break; + case opc_lor : + stacktop-=2; + stack[stacktop-2].setLong(stack[stacktop-2].longValue() + | stack[stacktop].longValue()); + break; + case opc_lxor: + stacktop-=2; + stack[stacktop-2].setLong(stack[stacktop-2].longValue() + ^ stack[stacktop].longValue()); + break; + + case opc_iinc: + locals[instr.getLocalSlot()].setInt + (locals[instr.getLocalSlot()].intValue() + instr.getIncrement()); + break; + case opc_i2l: + stack[stacktop-1] + .setLong((long)stack[stacktop-1].intValue()); + stacktop++; + break; + case opc_i2f: + stack[stacktop-1] + .setFloat((float)stack[stacktop-1].intValue()); + break; + case opc_i2d: + stack[stacktop-1] + .setDouble((double)stack[stacktop-1].intValue()); + stacktop++; + break; + + case opc_l2i: + stacktop--; + stack[stacktop-1] + .setInt((int)stack[stacktop-1].longValue()); + break; + case opc_l2f: + stacktop--; + stack[stacktop-1] + .setFloat((float)stack[stacktop-1].longValue()); + break; + case opc_l2d: + stack[stacktop-2] + .setDouble((double)stack[stacktop-2].longValue()); + break; + + case opc_f2i: + stack[stacktop-1] + .setInt((int)stack[stacktop-1].floatValue()); + break; + case opc_f2l: + stack[stacktop-1] + .setLong((long)stack[stacktop-1].floatValue()); + stacktop++; + break; + case opc_f2d: + stack[stacktop-1] + .setDouble((double)stack[stacktop-1].floatValue()); + stacktop++; + break; + + case opc_d2i: + stacktop--; + stack[stacktop-1] + .setInt((int)stack[stacktop-1].doubleValue()); + break; + case opc_d2l: + stack[stacktop-2] + .setLong((long)stack[stacktop-2].doubleValue()); + break; + case opc_d2f: + stacktop--; + stack[stacktop-1] + .setFloat((float)stack[stacktop-1].doubleValue()); + break; + + case opc_i2b: + stack[stacktop-1] + .setInt((byte)stack[stacktop-1].intValue()); + break; + case opc_i2c: + stack[stacktop-1] + .setInt((char)stack[stacktop-1].intValue()); + break; + case opc_i2s: + stack[stacktop-1] + .setInt((short)stack[stacktop-1].intValue()); + break; + case opc_lcmp: { + stacktop -= 3; + long val1 = stack[stacktop-1].longValue(); + long val2 = stack[stacktop+1].longValue(); + stack[stacktop-1].setInt + (val1 == val2 ? 0 : val1 < val2 ? -1 : 1); + break; + } + case opc_fcmpl: case opc_fcmpg: { + float val1 = stack[stacktop-2].floatValue(); + float val2 = stack[--stacktop].floatValue(); + stack[stacktop-1].setInt + (val1 == val2 ? 0 + : ( opcode == opc_fcmpg + ? (val1 < val2 ? -1 : 1) + : (val1 > val2 ? 1 : -1))); + break; + } + case opc_dcmpl: case opc_dcmpg: { + stacktop -= 3; + double val1 = stack[stacktop-1].doubleValue(); + double val2 = stack[stacktop+1].doubleValue(); + stack[stacktop-1].setInt + (val1 == val2 ? 0 + : ( opcode == opc_dcmpg + ? (val1 < val2 ? -1 : 1) + : (val1 > val2 ? 1 : -1))); + break; + } + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: { + int value; + if (opcode >= opc_if_acmpeq) { + Object objValue = stack[--stacktop].objectValue(); + if (opcode >= opc_ifnull) { + value = objValue == null ? 0 : 1; + opcode -= opc_ifnull; + } else { + value = objValue + == stack[--stacktop].objectValue() ? 0 : 1; + opcode -= opc_if_acmpeq; + } + } else { + value = stack[--stacktop].intValue(); + if (opcode >= opc_if_icmpeq) { + int val1 = stack[--stacktop].intValue(); + value = (val1 == value ? 0 + : val1 < value ? -1 : 1); + opcode -= opc_if_icmpeq; + } else + opcode -= opc_ifeq; + } + int opc_mask = 1 << opcode; + if (value > 0 && (opc_mask & CMP_GREATER_MASK) != 0 + || value < 0 && (opc_mask & CMP_LESS_MASK) != 0 + || value == 0 && (opc_mask & CMP_EQUAL_MASK) != 0) { + nextBlock = succs[0]; + } + break; + } + case opc_jsr: + case opc_jsr_w: + stack[stacktop++].setObject(nextBlock); + nextBlock = succs[0]; + break; + case opc_ret: + nextBlock + = (Block) locals[instr.getLocalSlot()].objectValue(); + break; + case opc_lookupswitch: { + int value = stack[--stacktop].intValue(); + int[] values = instr.getValues(); + int pos = Arrays.binarySearch(values, value); + if (pos >= 0) + nextBlock = succs[pos]; + break; + } + case opc_ireturn: case opc_freturn: case opc_areturn: + return stack[--stacktop].objectValue(); + case opc_lreturn: case opc_dreturn: + return stack[stacktop -= 2].objectValue(); + case opc_return: + return Void.TYPE; + case opc_getstatic: { + Reference ref = instr.getReference(); + Object result = env.getField(instr.getReference(), null); + stack[stacktop].setObject(result); + stacktop += TypeSignature.getTypeSize(ref.getType()); + break; + } + case opc_getfield: { + Reference ref = instr.getReference(); + Object cls = stack[--stacktop].objectValue(); + if (cls == null) + throw new InvocationTargetException + (new NullPointerException()); + Object result = env.getField(instr.getReference(), cls); + stack[stacktop].setObject(result); + stacktop += TypeSignature.getTypeSize(ref.getType()); + break; + } + case opc_putstatic: { + Reference ref = instr.getReference(); + stacktop -= TypeSignature.getTypeSize(ref.getType()); + Object value = stack[stacktop].objectValue(); + env.putField(instr.getReference(), null, value); + break; + } + case opc_putfield: { + Reference ref = instr.getReference(); + stacktop -= TypeSignature.getTypeSize(ref.getType()); + Object value = stack[stacktop].objectValue(); + Object cls = stack[--stacktop].objectValue(); + if (cls == null) + throw new InvocationTargetException + (new NullPointerException()); + env.putField(instr.getReference(), cls, value); + break; + } + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic : + case opc_invokeinterface: { + Reference ref = instr.getReference(); + String[] paramTypes + = TypeSignature.getParameterTypes(ref.getType()); + Object[] args = new Object[paramTypes.length]; + for (int i = paramTypes.length - 1; i >= 0; i--) { + stacktop -= TypeSignature.getTypeSize(paramTypes[i]); + args[i] = stack[stacktop].objectValue(); + } + + Object result = null; + if (opcode == opc_invokespecial + && ref.getName().equals("") + && stack[--stacktop].getNewObject() != null) { + NewObject newObj = stack[stacktop].getNewObject(); + if (!newObj.getType().equals(ref.getClazz())) + throw new InterpreterException + ("constructor doesn't match new"); + newObj.setObject(env.invokeConstructor(ref, args)); + } else if (opcode == opc_invokestatic) { + result = env.invokeMethod(ref, false, null, args); + } else { + Object cls = stack[--stacktop].objectValue(); + if (cls == null) + throw new InvocationTargetException + (new NullPointerException()); + result = env.invokeMethod + (ref, opcode != opc_invokespecial, cls, args); + } + String retType + = TypeSignature.getReturnType(ref.getType()); + if (!retType.equals("V")) { + stack[stacktop].setObject(result); + stacktop += TypeSignature.getTypeSize(retType); + } + break; + } + case opc_new: { + String clazz = instr.getClazzType(); + stack[stacktop++].setNewObject(new NewObject(clazz)); + break; + } + case opc_arraylength: { + Object array = stack[--stacktop].objectValue(); + if (array == null) + throw new InvocationTargetException + (new NullPointerException()); + stack[stacktop++].setInt(Array.getLength(array)); + break; + } + case opc_athrow: { + Throwable exc = + (Throwable) stack[--stacktop].objectValue(); + throw new InvocationTargetException + (exc == null ? new NullPointerException() : exc); + } + case opc_checkcast: { + Object obj = stack[stacktop-1].objectValue(); + if (obj != null + && !env.instanceOf(obj, instr.getClazzType())) + throw new InvocationTargetException + (new ClassCastException(obj.getClass().getName())); + break; + } + case opc_instanceof: { + Object obj = stack[--stacktop].objectValue(); + stack[stacktop++].setInt + (env.instanceOf(obj, instr.getClazzType()) ? 1 : 0); + break; + } + case opc_monitorenter: + env.enterMonitor(stack[--stacktop].objectValue()); + break; + case opc_monitorexit: + env.exitMonitor(stack[--stacktop].objectValue()); + break; + case opc_multianewarray: { + int dimension = instr.getDimensions(); + int[] dims = new int[dimension]; + for (int i=dimension - 1; i >= 0; i--) + dims[i] = stack[--stacktop].intValue(); + try { + stack[stacktop++].setObject + (env.newArray(instr.getClazzType(), dims)); + } catch (NegativeArraySizeException ex) { + throw new InvocationTargetException(ex); + } + break; + } + default: + throw new InternalError("Invalid opcode "+opcode); + } + } catch (InvocationTargetException ex) { + iter = null; + Throwable obj = ex.getTargetException(); + for (int i=0; i < handlers.length; i++) { + if (handlers[i].getType() == null + || env.instanceOf(obj, handlers[i].getType())) { + stacktop = 0; + stack[stacktop++].setObject(obj); + nextBlock = handlers[i].getCatcher(); + continue big_loop; + } + } + throw ex; + } + } + } +} diff --git a/jode/src/net/sf/jode/jvm/InterpreterException.java b/jode/src/net/sf/jode/jvm/InterpreterException.java new file mode 100644 index 0000000..cbbab7f --- /dev/null +++ b/jode/src/net/sf/jode/jvm/InterpreterException.java @@ -0,0 +1,35 @@ +/* InterpreterException Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; + +/** + * This exception is thrown by the interpreter on various conditions. + * + * @author Jochen Hoenicke + */ +public class InterpreterException extends Exception { + public InterpreterException(String detail) { + super(detail); + } + public InterpreterException() { + super(); + } +} + diff --git a/jode/src/net/sf/jode/jvm/NewObject.java b/jode/src/net/sf/jode/jvm/NewObject.java new file mode 100644 index 0000000..d2c2f14 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/NewObject.java @@ -0,0 +1,54 @@ +/* NewObject Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; + +/** + * This class represents a new object, that may not be initialized yet. + * + * @author Jochen Hoenicke + */ +class NewObject { + Object instance; + String type; + + public NewObject(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setObject(Object obj) { + instance = obj; + } + + public Object objectValue() { + return instance; + } + + public String toString() { + if (instance == null) + return "new "+type; + else + return instance.toString(); + } +} + diff --git a/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java b/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java new file mode 100644 index 0000000..d1f46b0 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/RuntimeEnvironment.java @@ -0,0 +1,123 @@ +/* RuntimeEnvironment Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.bytecode.Reference; +import java.lang.reflect.InvocationTargetException; + +/** + * This interface is used by the Interpreter to actually modify objects, + * invoke methods, etc.
    + * + * The objects used in this runtime environment need not to be of the + * real type, but can be some other type of your choice. But some + * mappings must be preserved, since they are used inside the + * Interpreter: + *
    • boolean, byte, short, char and int are mapped to Integer.
    • + *
    • float, long, double are mapped to Float, Long, Double resp.
    • + *
    • array of primitive type is mapped to itself (not array of Integer)
    • + *
    • array of other types are mapped to array of mapped other type
    • + *
    + * + * @author Jochen Hoenicke + * @see SimpleRuntimeEnvironment + */ +public interface RuntimeEnvironment { + /** + * Get the value of a field member. + * @param fieldref the Reference of the field. + * @param obj the object of which the field should be taken, null + * if the field is static. + * @return the field value. Primitive types are wrapped to + * Object. + * @exception InterpreterException if the field does not exists, the + * object is not supported etc. + */ + public Object getField(Reference fieldref, Object obj) + throws InterpreterException; + + /** + * Set the value of a field member. + * @param fieldref the Reference of the field. + * @param obj the object of which the field should be taken, null + * if the field is static. + * @param value the field value. Primitive types are wrapped to + * Object. + * @exception InterpreterException if the field does not exists, the + * object is not supported etc. + */ + public void putField(Reference fieldref, Object obj, Object value) + throws InterpreterException; + + + /** + * Invoke a method. + * @param methodRef the reference to the method. + * @param isVirtual true, iff the call is virtual + * @param cls the object on which the method should be called, null + * if the method is static. + * @param params the params of the method. + * @return the return value of the method. Void type is ignored, + * may be null. + * @exception InterpreterException if the field does not exists, the + * object is not supported etc. */ + public Object invokeMethod(Reference methodRef, boolean isVirtual, + Object cls, Object[] params) + throws InterpreterException, InvocationTargetException; + + /** + * Create a new instance of an object. + * @param methodRef the reference of the constructor to invoke + * @param params the params of the method. + * @return the new object. + */ + public Object invokeConstructor(Reference methodRef, Object[] params) + throws InterpreterException, InvocationTargetException; + + /** + * Check if obj is an instance of className + * @param className the type signature of the class. + * @return true, if obj is an instance of className, false otherwise. + */ + public boolean instanceOf(Object obj, String className) + throws InterpreterException; + + /** + * Create a new multidimensional Array. + * @param type the type of the elements. + * @param dimensions the size in every dimension. + * @return the new array (this must be an array, see class comment). + */ + public Object newArray(String type, int[] dimensions) + throws InterpreterException; + + /** + * Enter a monitor. + * @param obj the object whose monitor should be taken. + */ + public void enterMonitor(Object obj) + throws InterpreterException; + /** + * Exit a monitor. + * @param obj the object whose monitor should be freed. + */ + public void exitMonitor(Object obj) + throws InterpreterException; +} + diff --git a/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java b/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java new file mode 100644 index 0000000..31c9ce4 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/SimpleRuntimeEnvironment.java @@ -0,0 +1,235 @@ +/* SimpleRuntimeEnvironment Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.TypeSignature; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; + +/** + * This is a runtime environment using reflection. + * Monitors are not supported by this class so exit/enterMonitor + * will through an InterpreterException. + */ +public class SimpleRuntimeEnvironment implements RuntimeEnvironment { + + public static Object fromReflectType(String typeSig, Object value) { + switch(typeSig.charAt(0)) { + case 'Z': + return new Integer(((Boolean) value).booleanValue() ? 1 : 0); + case 'B': + case 'S': + return new Integer(((Number) value).intValue()); + case 'C': + return new Integer(((Character) value).charValue()); + default: + return value; + } + } + + public static Object toReflectType(String typeSig, Object value) { + switch(typeSig.charAt(0)) { + case 'Z': + return new Boolean(((Integer)value).intValue() != 0); + case 'B': + return new Byte(((Integer)value).byteValue()); + case 'S': + return new Short(((Integer)value).shortValue()); + case 'C': + return new Character((char) ((Integer)value).intValue()); + default: + return value; + } + } + + public Object getField(Reference ref, Object obj) + throws InterpreterException { + Field f; + try { + Class clazz = TypeSignature.getClass(ref.getClazz()); + try { + f = clazz.getField(ref.getName()); + } catch (NoSuchFieldException ex) { + f = clazz.getDeclaredField(ref.getName()); + } + } catch (ClassNotFoundException ex) { + throw new InterpreterException + (ref+": Class not found"); + } catch (NoSuchFieldException ex) { + throw new InterpreterException + ("Constructor "+ref+" not found"); + } catch (SecurityException ex) { + throw new InterpreterException + (ref+": Security exception"); + } + try { + return fromReflectType(ref.getType(), f.get(obj)); + } catch (IllegalAccessException ex) { + throw new InterpreterException + ("Field " + ref + " not accessible"); + } + } + + public void putField(Reference ref, Object obj, Object value) + throws InterpreterException { + Field f; + try { + Class clazz = TypeSignature.getClass(ref.getClazz()); + try { + f = clazz.getField(ref.getName()); + } catch (NoSuchFieldException ex) { + f = clazz.getDeclaredField(ref.getName()); + } + } catch (ClassNotFoundException ex) { + throw new InterpreterException + (ref+": Class not found"); + } catch (NoSuchFieldException ex) { + throw new InterpreterException + ("Constructor "+ref+" not found"); + } catch (SecurityException ex) { + throw new InterpreterException + (ref+": Security exception"); + } + try { + f.set(obj, toReflectType(ref.getType(), value)); + } catch (IllegalAccessException ex) { + throw new InterpreterException + ("Field " + ref + " not accessible"); + } + } + + public Object invokeConstructor(Reference ref, Object[] params) + throws InterpreterException, InvocationTargetException { + Constructor c; + try { + String[] paramTypeSigs + = TypeSignature.getParameterTypes(ref.getType()); + Class clazz = TypeSignature.getClass(ref.getClazz()); + Class[] paramTypes = new Class[paramTypeSigs.length]; + for (int i=0; i< paramTypeSigs.length; i++) { + params[i] = toReflectType(paramTypeSigs[i], params[i]); + paramTypes[i] = TypeSignature.getClass(paramTypeSigs[i]); + } + try { + c = clazz.getConstructor(paramTypes); + } catch (NoSuchMethodException ex) { + c = clazz.getDeclaredConstructor(paramTypes); + } + } catch (ClassNotFoundException ex) { + throw new InterpreterException + (ref+": Class not found"); + } catch (NoSuchMethodException ex) { + throw new InterpreterException + ("Constructor "+ref+" not found"); + } catch (SecurityException ex) { + throw new InterpreterException + (ref+": Security exception"); + } + + try { + return c.newInstance(params); + } catch (IllegalAccessException ex) { + throw new InterpreterException + ("Constructor " + ref + " not accessible"); + } catch (InstantiationException ex) { + throw new InterpreterException + ("InstantiationException in " + ref + "."); + } + } + + public Object invokeMethod(Reference ref, boolean isVirtual, + Object cls, Object[] params) + throws InterpreterException, InvocationTargetException { + if (!isVirtual && cls != null) /*XXX*/ + throw new InterpreterException + ("Can't invoke nonvirtual Method " + ref + "."); + + Method m; + try { + String[] paramTypeSigs + = TypeSignature.getParameterTypes(ref.getType()); + Class clazz = TypeSignature.getClass(ref.getClazz()); + Class[] paramTypes = new Class[paramTypeSigs.length]; + for (int i=0; i< paramTypeSigs.length; i++) { + params[i] = toReflectType(paramTypeSigs[i], params[i]); + paramTypes[i] = TypeSignature.getClass(paramTypeSigs[i]); + } + try { + m = clazz.getMethod(ref.getName(), paramTypes); + } catch (NoSuchMethodException ex) { + m = clazz.getDeclaredMethod(ref.getName(), paramTypes); + } + } catch (ClassNotFoundException ex) { + throw new InterpreterException + (ref+": Class not found"); + } catch (NoSuchMethodException ex) { + throw new InterpreterException + ("Method "+ref+" not found"); + } catch (SecurityException ex) { + throw new InterpreterException + (ref+": Security exception"); + } + String retType = TypeSignature.getReturnType(ref.getType()); + try { + return fromReflectType(retType, m.invoke(cls, params)); + } catch (IllegalAccessException ex) { + throw new InterpreterException + ("Method " + ref + " not accessible"); + } + } + + public boolean instanceOf(Object obj, String className) + throws InterpreterException { + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException ex) { + throw new InterpreterException + ("Class "+ex.getMessage()+" not found"); + } + return obj != null && !clazz.isInstance(obj); + } + + public Object newArray(String type, int[] dimensions) + throws InterpreterException, NegativeArraySizeException { + Class clazz; + try { + /* get the base class (strip leading "[") */ + clazz = TypeSignature.getClass(type.substring(dimensions.length)); + } catch (ClassNotFoundException ex) { + throw new InterpreterException + ("Class "+ex.getMessage()+" not found"); + } + return Array.newInstance(clazz, dimensions); + } + + public void enterMonitor(Object obj) + throws InterpreterException { + throw new InterpreterException("monitor not implemented"); + } + public void exitMonitor(Object obj) + throws InterpreterException { + throw new InterpreterException("monitor not implemented"); + } +} diff --git a/jode/src/net/sf/jode/jvm/SyntheticAnalyzer.java b/jode/src/net/sf/jode/jvm/SyntheticAnalyzer.java new file mode 100644 index 0000000..5ac72e4 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/SyntheticAnalyzer.java @@ -0,0 +1,413 @@ +/* SyntheticAnalyzer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.BasicBlocks; +import net.sf.jode.bytecode.Block; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.FieldInfo; +import net.sf.jode.bytecode.Handler; +import net.sf.jode.bytecode.Instruction; +import net.sf.jode.bytecode.MethodInfo; +import net.sf.jode.bytecode.Opcodes; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.TypeSignature; +import net.sf.jode.type.Type; +import net.sf.jode.type.MethodType; + +import java.lang.reflect.Modifier; +import java.io.IOException; + +///#def COLLECTIONS java.util +import java.util.Arrays; +import java.util.Iterator; +///#enddef + +public class SyntheticAnalyzer implements Opcodes { + public final static int UNKNOWN = 0; + public final static int GETCLASS = 1; + public final static int ACCESSGETFIELD = 2; + public final static int ACCESSPUTFIELD = 3; + public final static int ACCESSMETHOD = 4; + public final static int ACCESSGETSTATIC = 5; + public final static int ACCESSPUTSTATIC = 6; + public final static int ACCESSSTATICMETHOD = 7; + public final static int ACCESSCONSTRUCTOR = 8; + public final static int ACCESSDUPPUTFIELD = 9; + public final static int ACCESSDUPPUTSTATIC = 10; + + int kind = UNKNOWN; + + int unifyParam = -1; + Reference reference; + ClassInfo classInfo; + MethodInfo method; + + public SyntheticAnalyzer(ClassInfo classInfo, MethodInfo method, + boolean checkName) { + this.classInfo = classInfo; + this.method = method; + if (method.getBasicBlocks() == null) + return; + if (!checkName || method.getName().equals("class$")) + if (checkGetClass()) + return; + if (!checkName || method.getName().startsWith("access$")) + if (checkAccess()) + return; + if (method.getName().equals("")) + if (checkConstructorAccess()) + return; + } + + public int getKind() { + return kind; + } + + public Reference getReference() { + return reference; + } + + /** + * Gets the index of the dummy parameter for an ACCESSCONSTRUCTOR. + * Normally the 1 but for inner classes it may be 2. + */ + public int getUnifyParam() { + return unifyParam; + } + + private static final int[] getClassOpcodes = { + opc_aload, opc_invokestatic, opc_areturn, + opc_astore, opc_new, opc_dup, opc_aload, + opc_invokevirtual, opc_invokespecial, opc_athrow + }; + + boolean checkGetClass() { + if (!method.isStatic() + || !(method.getType() + .equals("(Ljava/lang/String;)Ljava/lang/Class;"))) + return false; + + BasicBlocks bb = method.getBasicBlocks(); + + Block[] blocks = bb.getBlocks(); + Block startBlock = bb.getStartBlock(); + Handler[] excHandlers = bb.getExceptionHandlers(); + if (startBlock == null + || startBlock.getInstructions().length < 2 + || startBlock.getInstructions().length > 3 + || excHandlers.length != 1 + || excHandlers[0].getStart() != startBlock + || excHandlers[0].getEnd() != startBlock + || !"java.lang.ClassNotFoundException" + .equals(excHandlers[0].getType())) + return false; + + for (int i=0; i< 2; i++) { + Instruction instr = startBlock.getInstructions()[i]; + if (instr.getOpcode() != getClassOpcodes[i]) + return false; + if (i == 1) { + Reference ref = instr.getReference(); + if (!ref.getClazz().equals("Ljava/lang/Class;") + || !ref.getName().equals("forName")) + return false; + } + if (i == 0 && instr.getLocalSlot() != 0) + return false; + } + if (startBlock.getInstructions().length == 2) { + /* JDK 1.4: The return is outside of startBlock */ + Block nextBlock = startBlock.getSuccs()[0]; + Instruction[] instrs = nextBlock.getInstructions(); + if (instrs[0].getOpcode() != opc_areturn) + return false; + } else { + /* JDK 1.3 */ + if (startBlock.getInstructions()[2].getOpcode() != opc_areturn) + return false; + } + + Block catchBlock = excHandlers[0].getCatcher(); + if (catchBlock.getInstructions().length != 7) + return false; + int excSlot = -1; + for (int i=0; i< 7; i++) { + Instruction instr = catchBlock.getInstructions()[i]; + if (instr.getOpcode() != getClassOpcodes[3+i]) + return false; + if (i == 0) + excSlot = instr.getLocalSlot(); + if (i == 1 && !instr.getClazzType().equals + ("Ljava/lang/NoClassDefFoundError;")) + return false; + if (i == 3 && instr.getLocalSlot() != excSlot) + return false; + if (i == 4 + && !instr.getReference().getName().equals("getMessage")) + return false; + if (i == 5 + && !instr.getReference().getName().equals("")) + return false; + } + this.kind = GETCLASS; + return true; + } + + private final int modifierMask = Modifier.PUBLIC; + + /** + * Check if this is a field/method access method. We have only + * very few checks: The parameter must be loaded in correct order, + * followed by an get/put/invoke-field/static/special consuming + * all loaded parameters. The CodeVerifier has already checked + * that types of parameters are okay. + */ + private boolean checkAccess() { + BasicBlocks bb = method.getBasicBlocks(); + Handler[] excHandlers = bb.getExceptionHandlers(); + if (excHandlers != null && excHandlers.length != 0) + return false; + Block[] blocks = bb.getBlocks(); + Block startBlock = bb.getStartBlock(); + if (startBlock == null) + return false; + Block[] succBlocks = startBlock.getSuccs(); + if (succBlocks.length > 1 || + (succBlocks.length == 1 && succBlocks[0] != null)) + return false; + Iterator iter = Arrays.asList(startBlock.getInstructions()).iterator(); + boolean dupSeen = false; + + if (!iter.hasNext()) + return false; + Instruction instr = (Instruction) iter.next(); + + int params = 0, slot = 0; + while (instr.getOpcode() >= opc_iload + && instr.getOpcode() <= opc_aload + && instr.getLocalSlot() == slot) { + params++; + slot += (instr.getOpcode() == opc_lload + || instr.getOpcode() == opc_dload) ? 2 : 1; + if (!iter.hasNext()) + return false; + instr = (Instruction) iter.next(); + } + + if (instr.getOpcode() == opc_getstatic + || instr.getOpcode() == opc_getfield) { + boolean isStatic = instr.getOpcode() == opc_getstatic; + if (!isStatic) + params--; + if (params != 0) + return false; + Reference ref = instr.getReference(); + ClassInfo refClazz = TypeSignature + .getClassInfo(classInfo.getClassPath(), ref.getClazz()); + try { + if (!refClazz.superClassOf(classInfo)) + return false; + } catch (IOException ex) { + /* Can't get enough info to ensure that refClazz is correct */ + return false; + } + FieldInfo refField + = refClazz.findField(ref.getName(), ref.getType()); + if (refField == null + || (refField.getModifiers() & modifierMask) != 0) + return false; + if (!iter.hasNext()) + return false; + instr = (Instruction) iter.next(); + if (instr.getOpcode() < opc_ireturn + || instr.getOpcode() > opc_areturn) + return false; + /* For valid bytecode the type matches automatically */ + reference = ref; + kind = (isStatic ? ACCESSGETSTATIC : ACCESSGETFIELD); + return true; + } + if (instr.getOpcode() == (opc_dup - 3) + 3 * slot) { + /* This is probably a opc_dup or opc_dup2, + * preceding a opc_putfield + */ + instr = (Instruction) iter.next(); + if (instr.getOpcode() != opc_putstatic + && instr.getOpcode() != opc_putfield) + return false; + dupSeen = true; + } + if (instr.getOpcode() == opc_putfield + || instr.getOpcode() == opc_putstatic) { + boolean isStatic = instr.getOpcode() == opc_putstatic; + if (!isStatic) + params--; + if (params != 1) + return false; + /* For valid bytecode the type of param matches automatically */ + Reference ref = instr.getReference(); + ClassInfo refClazz = TypeSignature + .getClassInfo(classInfo.getClassPath(), ref.getClazz()); + try { + if (!refClazz.superClassOf(classInfo)) + return false; + } catch (IOException ex) { + /* Can't get enough info to ensure that refClazz is correct */ + return false; + } + FieldInfo refField + = refClazz.findField(ref.getName(), ref.getType()); + if (refField == null + || (refField.getModifiers() & modifierMask) != 0) + return false; + if (dupSeen) { + if (!iter.hasNext()) + return false; + instr = (Instruction) iter.next(); + if (instr.getOpcode() < opc_ireturn + || instr.getOpcode() > opc_areturn) + return false; + kind = (isStatic ? ACCESSDUPPUTSTATIC : ACCESSDUPPUTFIELD); + } else { + if (iter.hasNext()) + return false; + kind = (isStatic ? ACCESSPUTSTATIC : ACCESSPUTFIELD); + } + reference = ref; + return true; + } + if (instr.getOpcode() == opc_invokestatic + || instr.getOpcode() == opc_invokespecial) { + boolean isStatic = instr.getOpcode() == opc_invokestatic; + if (!isStatic) + params--; + Reference ref = instr.getReference(); + ClassInfo refClazz = TypeSignature + .getClassInfo(classInfo.getClassPath(), ref.getClazz()); + try { + if (!refClazz.superClassOf(classInfo)) + return false; + } catch (IOException ex) { + /* Can't get enough info to ensure that refClazz is correct */ + return false; + } + MethodInfo refMethod + = refClazz.findMethod(ref.getName(), ref.getType()); + MethodType refType = Type.tMethod(classInfo.getClassPath(), + ref.getType()); + if ((refMethod.getModifiers() & modifierMask) != 0 + || refType.getParameterTypes().length != params) + return false; + if (refType.getReturnType() == Type.tVoid) { + if (iter.hasNext()) + return false; + } else { + if (!iter.hasNext()) + return false; + instr = (Instruction) iter.next(); + if (instr.getOpcode() < opc_ireturn + || instr.getOpcode() > opc_areturn) + return false; + } + + /* For valid bytecode the types matches automatically */ + reference = ref; + kind = (isStatic ? ACCESSSTATICMETHOD : ACCESSMETHOD); + return true; + } + return false; + } + + private boolean checkConstructorAccess() { + BasicBlocks bb = method.getBasicBlocks(); + String[] paramTypes + = TypeSignature.getParameterTypes(method.getType()); + Handler[] excHandlers = bb.getExceptionHandlers(); + if (excHandlers != null && excHandlers.length != 0) + return false; + Block startBlock = bb.getStartBlock(); + if (startBlock == null) + return false; + Block[] succBlocks = startBlock.getSuccs(); + if (succBlocks.length != 1 || succBlocks[0] != null) + return false; + Iterator iter = Arrays.asList(startBlock.getInstructions()).iterator(); + if (!iter.hasNext()) + return false; + + unifyParam = -1; + Instruction instr = (Instruction) iter.next(); + int params = 0, slot = 0; + while (instr.getOpcode() >= opc_iload + && instr.getOpcode() <= opc_aload) { + if (instr.getLocalSlot() > slot + && unifyParam == -1 && params > 0 + && paramTypes[params-1].charAt(0) == 'L') { + unifyParam = params; + params++; + slot++; + } + if (instr.getLocalSlot() != slot) + return false; + + params++; + slot += (instr.getOpcode() == opc_lload + || instr.getOpcode() == opc_dload) ? 2 : 1; + if (!iter.hasNext()) + return false; + instr = (Instruction) iter.next(); + } + if (unifyParam == -1 + && params > 0 && params <= paramTypes.length + && paramTypes[params-1].charAt(0) == 'L') { + unifyParam = params; + params++; + slot++; + } + if (params > 0 && instr.getOpcode() == opc_invokespecial) { + Reference ref = instr.getReference(); + ClassInfo refClazz = TypeSignature + .getClassInfo(classInfo.getClassPath(), ref.getClazz()); + if (refClazz != classInfo) + return false; + MethodInfo refMethod + = refClazz.findMethod(ref.getName(), ref.getType()); + MethodType refType = Type.tMethod(classInfo.getClassPath(), + ref.getType()); + if ((refMethod.getModifiers() & modifierMask) != 0 + || !refMethod.getName().equals("") + || unifyParam == -1 + || refType.getParameterTypes().length != params - 2) + return false; + if (iter.hasNext()) + return false; + /* We don't check if types matches. No problem since we only + * need to make sure, this constructor doesn't do anything + * more than relay to the real one. + */ + reference = ref; + kind = ACCESSCONSTRUCTOR; + return true; + } + return false; + } +} + diff --git a/jode/src/net/sf/jode/jvm/Value.java b/jode/src/net/sf/jode/jvm/Value.java new file mode 100644 index 0000000..5a1c851 --- /dev/null +++ b/jode/src/net/sf/jode/jvm/Value.java @@ -0,0 +1,98 @@ +/* Value Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.bytecode.*; + +/** + * This class represents a stack value. + * + * @author Jochen Hoenicke + */ +class Value { + Object value; + NewObject newObj; + + public Value() { + } + + public void setObject(Object obj) { + newObj = null; + value = obj; + } + + public Object objectValue() { + if (newObj != null) + return newObj.objectValue(); + return value; + } + + public void setInt(int i) { + newObj = null; + value = new Integer(i); + } + + public int intValue() { + return ((Integer)value).intValue(); + } + + public void setLong(long i) { + newObj = null; + value = new Long(i); + } + + public long longValue() { + return ((Long)value).longValue(); + } + + public void setFloat(float i) { + newObj = null; + value = new Float(i); + } + + public float floatValue() { + return ((Float)value).floatValue(); + } + + public void setDouble(double i) { + newObj = null; + value = new Double(i); + } + + public double doubleValue() { + return ((Double)value).doubleValue(); + } + + public void setNewObject(NewObject n) { + newObj = n; + } + + public NewObject getNewObject() { + return newObj; + } + + public void setValue(Value val) { + value = val.value; + newObj = val.newObj; + } + + public String toString() { + return newObj != null ? newObj.toString() : ""+value; + } +} diff --git a/jode/src/net/sf/jode/jvm/VerifyException.java b/jode/src/net/sf/jode/jvm/VerifyException.java new file mode 100644 index 0000000..5ed773d --- /dev/null +++ b/jode/src/net/sf/jode/jvm/VerifyException.java @@ -0,0 +1,36 @@ +/* VerifyException Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.jvm; +import net.sf.jode.bytecode.*; + +/** + * This exception is thrown by the CodeVerifier on various conditions. + * + * @author Jochen Hoenicke + */ +public class VerifyException extends Exception { + public VerifyException(String detail) { + super(detail); + } + public VerifyException() { + super(); + } +} + diff --git a/jode/src/net/sf/jode/obfuscator/ClassBundle.java b/jode/src/net/sf/jode/obfuscator/ClassBundle.java new file mode 100644 index 0000000..af42c6e --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/ClassBundle.java @@ -0,0 +1,497 @@ +/* ClassBundle Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.obfuscator.modules.WildCard; +import net.sf.jode.obfuscator.modules.MultiIdentifierMatcher; +import net.sf.jode.obfuscator.modules.SimpleAnalyzer; +import net.sf.jode.obfuscator.modules.IdentityRenamer; +import java.io.*; +import java.util.zip.ZipOutputStream; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +///#ifdef JDK12 +///#def COLLECTIONS java.util +import java.util.WeakHashMap; +///#enddef +///#endif + +public class ClassBundle implements OptionHandler { + PackageIdentifier basePackage; + + /** + * the identifiers that must be analyzed. + */ + Set toAnalyze = new HashSet(); + + ClassPath classPath; + String destDir; + + String tableFile; + String toTableFile; + + IdentifierMatcher loading; + IdentifierMatcher preserving; + IdentifierMatcher reaching; + CodeTransformer[] preTrafos; + CodeAnalyzer analyzer; + CodeTransformer[] postTrafos; + Renamer renamer; + + + public ClassBundle() { + destDir = "."; + basePackage = new PackageIdentifier(this, null, "", ""); + basePackage.setReachable(); + basePackage.setPreserved(); + } + +///#ifdef JDK12 + private static final Map aliasesHash = new WeakHashMap(); +///#else +/// private static final Map aliasesHash = new HashMap(); +///#endif + private static final Map clazzCache = new HashMap(); + private static final Map referenceCache = new HashMap(); + + public static void setStripOptions(Collection stripString) { + } + public void setOption(String option, Collection values) { + if (option.equals("classpath")) { + Iterator i = values.iterator(); + StringBuffer sb = new StringBuffer((String) i.next()); + while (i.hasNext()) { + sb.append(ClassPath.altPathSeparatorChar) + .append((String)i.next()); + } + classPath = new ClassPath(sb.toString()); + return; + } + + if (option.equals("dest")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one destination path allowed"); + destDir = (String) values.iterator().next(); + return; + } + + if (option.equals("table")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one destination path allowed"); + tableFile = (String) values.iterator().next(); + return; + } + + if (option.equals("revtable")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one destination path allowed"); + toTableFile = (String) values.iterator().next(); + return; + } + if (option.equals("strip")) { + next_token: + for (Iterator iter = values.iterator(); iter.hasNext(); ) { + String token = (String) iter.next(); + for (int i=0; i < Main.stripNames.length; i++) { + if (token.equals(Main.stripNames[i])) { + Main.stripping |= 1 << i; + continue next_token; + } + } + throw new IllegalArgumentException("Unknown strip option: `" + +token+"'"); + } + return; + } + + if (option.equals("load")) { + if (values.size() == 1) { + Object value = values.iterator().next(); + if (value instanceof String) + loading = new WildCard((String)value); + else + loading = (IdentifierMatcher) value; + } else { + IdentifierMatcher[] matchers + = new IdentifierMatcher[values.size()]; + int j = 0; + for (Iterator i = values.iterator(); i.hasNext(); ) { + Object value = i.next(); + matchers[j++] = (value instanceof String + ? new WildCard((String)value) + : (IdentifierMatcher) value); + } + loading = new MultiIdentifierMatcher + (MultiIdentifierMatcher.OR, matchers); + } + return; + } + + if (option.equals("preserve")) { + if (values.size() == 1) { + Object value = values.iterator().next(); + if (value instanceof String) + preserving = new WildCard((String)value); + else + preserving = (IdentifierMatcher) value; + } else { + IdentifierMatcher[] matchers + = new IdentifierMatcher[values.size()]; + int j = 0; + for (Iterator i = values.iterator(); i.hasNext(); ) { + Object value = i.next(); + matchers[j++] = (value instanceof String + ? new WildCard((String)value) + : (IdentifierMatcher) value); + } + preserving = new MultiIdentifierMatcher + (MultiIdentifierMatcher.OR, matchers); + } + return; + } + + if (option.equals("reach")) { + // NOT IMPLEMENTED YET + if (values.size() == 1) { + Object value = values.iterator().next(); + if (value instanceof String) + reaching = new WildCard((String)value); + else + reaching = (IdentifierMatcher) value; + } else { + IdentifierMatcher[] matchers + = new IdentifierMatcher[values.size()]; + int j = 0; + for (Iterator i = values.iterator(); i.hasNext(); ) { + Object value = i.next(); + matchers[j++] = (value instanceof String + ? new WildCard((String)value) + : (IdentifierMatcher) value); + } + reaching = new MultiIdentifierMatcher + (MultiIdentifierMatcher.OR, matchers); + } + } + + if (option.equals("pre")) { + preTrafos = (CodeTransformer[]) + values.toArray(new CodeTransformer[values.size()]); + return; + } + if (option.equals("analyzer")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one analyzer is allowed"); + analyzer = (CodeAnalyzer) values.iterator().next(); + return; + } + if (option.equals("post")) { + postTrafos = (CodeTransformer[]) + values.toArray(new CodeTransformer[values.size()]); + return; + } + + if (option.equals("renamer")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one renamer allowed"); + renamer = (Renamer) values.iterator().next(); + return; + } + throw new IllegalArgumentException("Invalid option `"+option+"'."); + } + + public Reference getReferenceAlias(Reference ref) { + Reference alias = (Reference) aliasesHash.get(ref); + if (alias == null) { + Identifier ident = getIdentifier(ref); + String newType = getTypeAlias(ref.getType()); + if (ident == null) + alias = Reference.getReference + (ref.getClazz(), ref.getName(), newType); + else + alias = Reference.getReference + ("L"+ident.getParent().getFullAlias().replace('.','/')+';', + ident.getAlias(), newType); + aliasesHash.put(ref, alias); + } + return alias; + } + + public String getTypeAlias(String typeSig) { + String alias = (String) aliasesHash.get(typeSig); + if (alias == null) { + StringBuffer newSig = new StringBuffer(); + int index = 0, nextindex; + while ((nextindex = typeSig.indexOf('L', index)) != -1) { + newSig.append(typeSig.substring(index, nextindex+1)); + index = typeSig.indexOf(';', nextindex); + String typeAlias = basePackage.findAlias + (typeSig.substring(nextindex+1, index).replace('/','.')); + newSig.append(typeAlias.replace('.', '/')); + } + alias = newSig.append(typeSig.substring(index)) + .toString().intern(); + aliasesHash.put(typeSig, alias); + } + return alias; + } + + public void addClassIdentifier(Identifier ident) { + } + + public ClassPath getClassPath() { + return classPath; + } + + public ClassIdentifier getClassIdentifier(String name) { + if (clazzCache.containsKey(name)) + return (ClassIdentifier) clazzCache.get(name); + ClassIdentifier ident + = (ClassIdentifier) basePackage.getIdentifier(name); + clazzCache.put(name, ident); + return ident; + } + + public Identifier getIdentifier(Reference ref) { + if (referenceCache.containsKey(ref)) + return (Identifier) referenceCache.get(ref); + + String clName = ref.getClazz(); + if (clName.charAt(0) == '[') + /* Can't represent arrays */ + return null; + ClassIdentifier clazzIdent = + getClassIdentifier(clName.substring(1, clName.length()-1) + .replace('/','.')); + Identifier ident = + clazzIdent == null ? null + : clazzIdent.getIdentifier(ref.getName(), ref.getType()); + referenceCache.put(ref, ident); + return ident; + } + + public void reachableClass(String clazzName) { + ClassIdentifier ident = getClassIdentifier(clazzName); + if (ident != null) + ident.setReachable(); + } + + public void reachableReference(Reference ref, boolean isVirtual) { + String clName = ref.getClazz(); + if (clName.charAt(0) == '[') + /* Can't represent arrays */ + return; + ClassIdentifier ident = + getClassIdentifier(clName.substring(1, clName.length()-1) + .replace('/','.')); + if (ident != null) + ident.reachableReference(ref, isVirtual); + } + + public void analyzeIdentifier(Identifier ident) { + if (ident == null) + throw new NullPointerException(); + toAnalyze.add(ident); + } + + public void analyze() { + while(!toAnalyze.isEmpty()) { + Identifier ident = (Identifier) toAnalyze.iterator().next(); + toAnalyze.remove(ident); + ident.analyze(); + } + } + + public IdentifierMatcher getPreserveRule() { + return preserving; + } + + public CodeAnalyzer getCodeAnalyzer() { + return analyzer; + } + + public CodeTransformer[] getPreTransformers() { + return preTrafos; + } + + public CodeTransformer[] getPostTransformers() { + return postTrafos; + } + + public void buildTable(Renamer renameRule) { + basePackage.buildTable(renameRule); + } + + public void readTable() { + try { + TranslationTable table = new TranslationTable(); + InputStream input = new FileInputStream(tableFile); + table.load(input); + input.close(); + basePackage.readTable(table); + } catch (java.io.IOException ex) { + GlobalOptions.err.println("Can't read rename table " + tableFile); + ex.printStackTrace(GlobalOptions.err); + } + } + + public void writeTable() { + TranslationTable table = new TranslationTable(); + basePackage.writeTable(table); + try { + OutputStream out = new FileOutputStream(toTableFile); + table.store(out); + out.close(); + } catch (java.io.IOException ex) { + GlobalOptions.err.println("Can't write rename table "+toTableFile); + ex.printStackTrace(GlobalOptions.err); + } + } + + public void doTransformations() { + basePackage.doTransformations(); + } + + public void storeClasses() { + if (destDir.endsWith(".jar") || + destDir.endsWith(".zip")) { + try { + ZipOutputStream zip = new ZipOutputStream + (new FileOutputStream(destDir)); + basePackage.storeClasses(zip); + zip.close(); + } catch (IOException ex) { + GlobalOptions.err.println + ("Can't write zip file: "+destDir); + ex.printStackTrace(GlobalOptions.err); + } + } else { + File directory = new File(destDir); + if (!directory.exists()) { + GlobalOptions.err.println("Destination directory " + +directory.getPath() + +" doesn't exists."); + return; + } + basePackage.storeClasses(new File(destDir)); + } + } + + public void run() { + if (classPath == null) { + String cp = System.getProperty("java.class.path") + .replace(File.pathSeparatorChar, + ClassPath.altPathSeparatorChar); + classPath = new ClassPath(cp); + } + + if (analyzer == null) + analyzer = new SimpleAnalyzer(); + if (preTrafos == null) + preTrafos = new CodeTransformer[0]; + if (postTrafos == null) + postTrafos = new CodeTransformer[0]; + if (renamer == null) + renamer = new IdentityRenamer(); + + Runtime runtime = Runtime.getRuntime(); + long free = runtime.freeMemory(); + long last; + do { + last = free; + runtime.gc(); + runtime.runFinalization(); + free = runtime.freeMemory(); + } while (free < last); + System.err.println("used before: "+(runtime.totalMemory()- free)); + + GlobalOptions.err.println("Loading and preserving classes"); + + long time = System.currentTimeMillis(); + basePackage.loadMatchingClasses(loading); + basePackage.initialize(); + basePackage.applyPreserveRule(preserving); + System.err.println("Time used: "+(System.currentTimeMillis() - time)); + + + GlobalOptions.err.println("Computing reachability"); + time = System.currentTimeMillis(); + analyze(); + System.err.println("Time used: "+(System.currentTimeMillis() - time)); + + free = runtime.freeMemory(); + do { + last = free; + runtime.gc(); + runtime.runFinalization(); + free = runtime.freeMemory(); + } while (free < last); + System.err.println("used after analyze: " + + (runtime.totalMemory() - free)); + + GlobalOptions.err.println("Renaming methods"); + time = System.currentTimeMillis(); + if (tableFile != null) + readTable(); + buildTable(renamer); + if (toTableFile != null) + writeTable(); + System.err.println("Time used: "+(System.currentTimeMillis() - time)); + + GlobalOptions.err.println("Transforming the classes"); + time = System.currentTimeMillis(); + doTransformations(); + System.err.println("Time used: "+(System.currentTimeMillis() - time)); + + free = runtime.freeMemory(); + do { + last = free; + runtime.gc(); + runtime.runFinalization(); + free = runtime.freeMemory(); + } while (free < last); + System.err.println("used after transform: " + + (runtime.totalMemory() - free)); + + GlobalOptions.err.println("Writing new classes"); + time = System.currentTimeMillis(); + storeClasses(); + System.err.println("Time used: "+(System.currentTimeMillis() - time)); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/ClassIdentifier.java b/jode/src/net/sf/jode/obfuscator/ClassIdentifier.java new file mode 100644 index 0000000..541d4a7 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/ClassIdentifier.java @@ -0,0 +1,785 @@ +/* ClassIdentifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.*; +import net.sf.jode.obfuscator.modules.ModifierMatcher; +///#def COLLECTIONS java.util +import java.util.Comparator; +import java.util.Collection; +import java.util.Collections; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; +import java.util.Random; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +import java.lang.reflect.Modifier; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.io.OutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class ClassIdentifier extends Identifier { + PackageIdentifier pack; + String name; + String fullName; + ClassInfo info; + String superName; + String[] ifaceNames; + + List fieldIdents, methodIdents; + List knownSubClasses = new LinkedList(); + List virtualReachables = new LinkedList(); + + boolean initialized; + + public ClassIdentifier(PackageIdentifier pack, String fullName, + String name, ClassInfo info) { + super(name); + this.pack = pack; + this.fullName = fullName; + this.name = name; + this.info = info; + } + + public void addSubClass(ClassIdentifier ci) { + knownSubClasses.add(ci); + for(Iterator i = virtualReachables.iterator(); i.hasNext(); ) + ci.reachableReference((Reference) i.next(), true); + } + + private FieldIdentifier findField(String name, String typeSig) { + for (Iterator i = fieldIdents.iterator(); i.hasNext(); ) { + FieldIdentifier ident = (FieldIdentifier) i.next(); + if (ident.getName().equals(name) + && ident.getType().equals(typeSig)) + return ident; + } + return null; + } + + private MethodIdentifier findMethod(String name, String typeSig) { + for (Iterator i = methodIdents.iterator(); i.hasNext(); ) { + MethodIdentifier ident = (MethodIdentifier) i.next(); + if (ident.getName().equals(name) + && ident.getType().equals(typeSig)) + return ident; + } + return null; + } + + public void reachableReference(Reference ref, boolean isVirtual) { + boolean found = false; + for (Iterator i = getChilds(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (ref.getName().equals(ident.getName()) + && ref.getType().equals(ident.getType())) { + ident.setReachable(); + found = true; + } + } + if (!found) { + // This means that the method is inherited from parent and + // must be marked as reachable there, (but not virtual). + // Consider following: + // A method in Collection and AbstractCollection is not reachable + // but it is reachable in Set and not implemented in AbstractSet + // In that case the method must be marked reachable in + // AbstractCollection. + ClassIdentifier superIdent = Main.getClassBundle() + .getClassIdentifier(info.getSuperclass().getName()); + if (superIdent != null) + superIdent.reachableReference(ref, false); + } + + if (isVirtual) { + for(Iterator i = virtualReachables.iterator(); i.hasNext(); ) { + Reference prevRef = (Reference) i.next(); + if (prevRef.getName().equals(ref.getName()) + && prevRef.getType().equals(ref.getType())) + // already handled. + return; + } + for (Iterator i = knownSubClasses.iterator(); i.hasNext(); ) + ((ClassIdentifier)i.next()) + .reachableReference(ref, false); + virtualReachables.add(ref); + } + } + + public void chainMethodIdentifier(Identifier chainIdent) { + String name = chainIdent.getName(); + String typeSig = chainIdent.getType(); + for (Iterator i = methodIdents.iterator(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (ident.getName().equals(name) + && ident.getType().equals(typeSig)) + chainIdent.addShadow(ident); + } + } + + /** + * This is partly taken from the classpath project. + */ + public long calcSerialVersionUID() { + final MessageDigest md; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + GlobalOptions.err.println("Can't calculate serialVersionUID"); + return 0L; + } + OutputStream digest = new OutputStream() { + + public void write(int b) { + md.update((byte) b); + } + + public void write(byte[] data, int offset, int length) { + md.update(data, offset, length); + } + }; + DataOutputStream out = new DataOutputStream(digest); + try { + out.writeUTF(info.getName()); + + int modifiers = info.getModifiers(); + // just look at interesting bits + modifiers = modifiers & ( Modifier.ABSTRACT | Modifier.FINAL + | Modifier.INTERFACE | Modifier.PUBLIC ); + out.writeInt(modifiers); + + ClassInfo[] interfaces + = (ClassInfo[]) info.getInterfaces().clone(); + Arrays.sort(interfaces, new Comparator() { + public int compare( Object o1, Object o2 ) { + return ((ClassInfo)o1).getName() + .compareTo(((ClassInfo)o2).getName()); + } + }); + for( int i=0; i < interfaces.length; i++ ) { + out.writeUTF(interfaces[i].getName()); + } + + + Comparator identCmp = new Comparator() { + public int compare(Object o1, Object o2) { + Identifier i1 = (Identifier)o1; + Identifier i2 = (Identifier)o2; + String name1 = i1.getName(); + String name2 = i2.getName(); + boolean special1 = (name1.equals("") + || name1.equals("")); + boolean special2 = (name2.equals("") + || name2.equals("")); + // Put constructors at the beginning + if (special1 != special2) { + return special1 ? -1 : 1; + } + + int comp = i1.getName().compareTo(i2.getName()); + if (comp != 0) { + return comp; + } else { + return i1.getType().compareTo(i2.getType()); + } + } + }; + + List fields = Arrays.asList(fieldIdents.toArray()); + List methods = Arrays.asList(methodIdents.toArray()); + Collections.sort(fields, identCmp); + Collections.sort(methods, identCmp); + + for (Iterator i = fields.iterator(); i.hasNext();) { + FieldIdentifier field = (FieldIdentifier) i.next(); + modifiers = field.info.getModifiers(); + if ((modifiers & Modifier.PRIVATE) != 0 + && (modifiers & (Modifier.STATIC + | Modifier.TRANSIENT)) != 0) + continue; + + out.writeUTF(field.getName()); + out.writeInt(modifiers); + out.writeUTF(field.getType()); + } + for (Iterator i = methods.iterator(); i.hasNext(); ) { + MethodIdentifier method = (MethodIdentifier) i.next(); + modifiers = method.info.getModifiers(); + if (Modifier.isPrivate(modifiers)) + continue; + + out.writeUTF(method.getName()); + out.writeInt(modifiers); + + // the replacement of '/' with '.' was needed to make computed + // SUID's agree with those computed by JDK + out.writeUTF(method.getType().replace('/', '.')); + } + + out.close(); + + byte[] sha = md.digest(); + long result = 0; + for (int i=0; i < 8; i++) { + result += (long)(sha[i] & 0xFF) << (8 * i); + } + return result; + } catch (IOException ex) { + ex.printStackTrace(); + GlobalOptions.err.println("Can't calculate serialVersionUID"); + return 0L; + } + } + + public void addSUID() { + /* add a field serializableVersionUID if not existent */ + long serialVersion = calcSerialVersionUID(); + FieldInfo UIDField = new FieldInfo + ("serialVersionUID", "J", + Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL); + UIDField.setConstant(new Long(serialVersion)); + FieldIdentifier UIDident = new FieldIdentifier(this, UIDField); + fieldIdents.add(UIDident); + UIDident.setPreserved(); + } + + public boolean isSerializable() { + try { + return info.getClassPath().getClassInfo("java.lang.Serializable") + .implementedBy(info); + } catch (IOException ex) { + throw new RuntimeException("Can't load full hierarchy of "+info); + } + } + public boolean hasSUID() { + return (findField("serialVersionUID", "J") != null); + } + + /** + * Marks the package as preserved, too. + */ + protected void setSinglePreserved() { + pack.setPreserved(); + } + + public void setSingleReachable() { + super.setSingleReachable(); + Main.getClassBundle().analyzeIdentifier(this); + } + + public void analyzeSuperClasses(ClassInfo superclass) { + while (superclass != null) { + + ClassIdentifier superident = Main.getClassBundle() + .getClassIdentifier(superclass.getName()); + if (superident != null) { + superident.addSubClass(this); + } else { + // all virtual methods in superclass are reachable now! + String clazzType = ("L"+superclass.getName().replace('.', '/') + +";").intern(); + MethodInfo[] topmethods = superclass.getMethods(); + for (int i=0; i< topmethods.length; i++) { + int modif = topmethods[i].getModifiers(); + if (((Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL) + & modif) == 0 + && !topmethods[i].getName().equals("")) { + reachableReference + (Reference.getReference(clazzType, + topmethods[i].getName(), + topmethods[i].getType()), + true); + } + } + } + ClassInfo[] ifaces = superclass.getInterfaces(); + for (int i=0; i < ifaces.length; i++) + analyzeSuperClasses(ifaces[i]); + superclass = superclass.getSuperclass(); + } + } + + public void analyze() { + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println("Reachable: "+this); + + ClassInfo[] ifaces = info.getInterfaces(); + for (int i=0; i < ifaces.length; i++) + analyzeSuperClasses(ifaces[i]); + analyzeSuperClasses(info.getSuperclass()); + } + + public void initSuperClasses(ClassInfo superclass) { + while (superclass != null) { + ClassIdentifier superident = Main.getClassBundle() + .getClassIdentifier(superclass.getName()); + if (superident != null) { + superident.initClass(); + for (Iterator i = superident.getMethodIdents().iterator(); + i.hasNext(); ) { + MethodIdentifier mid = (MethodIdentifier) i.next(); + // all virtual methods in superclass must be chained. + int modif = mid.info.getModifiers(); + if (((Modifier.PRIVATE + | Modifier.STATIC + | Modifier.FINAL) & modif) == 0 + && !(mid.getName().equals(""))) { + // chain the preserved/same name lists. + chainMethodIdentifier(mid); + } + } + } else { + // all methods and fields in superclass are preserved! + try { + superclass.load(ClassInfo.DECLARATIONS); + } catch (IOException ex) { + throw new RuntimeException + ("Can't read declarations of class " + + superclass.getName() + + ": " + ex.getMessage()); + } + + MethodInfo[] topmethods = superclass.getMethods(); + for (int i=0; i< topmethods.length; i++) { + // all virtual methods in superclass may be + // virtually reachable + int modif = topmethods[i].getModifiers(); + if (((Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL) + & modif) == 0 + && !topmethods[i].getName().equals("")) { + Identifier method = findMethod + (topmethods[i].getName(), topmethods[i].getType()); + if (method != null) + method.setPreserved(); + } + } + } + ClassInfo[] ifaces = superclass.getInterfaces(); + for (int i=0; i < ifaces.length; i++) + initSuperClasses(ifaces[i]); + superclass = superclass.getSuperclass(); + } + } + + public void initClass() { + if (initialized) + return; + initialized = true; + + try { + info.load(info.ALL); + } catch (IOException ex) { + throw new RuntimeException("Can't read class " + info.getName() + + ": " + ex.getMessage()); + } + + FieldInfo[] finfos = info.getFields(); + MethodInfo[] minfos = info.getMethods(); + if (Main.swapOrder) { + Random rand = new Random(); + Collections.shuffle(Arrays.asList(finfos), rand); + Collections.shuffle(Arrays.asList(minfos), rand); + } + fieldIdents = new ArrayList(finfos.length); + methodIdents = new ArrayList(minfos.length); + for (int i=0; i< finfos.length; i++) + fieldIdents.add(new FieldIdentifier(this, finfos[i])); + + for (int i=0; i< minfos.length; i++) { + MethodIdentifier ident = new MethodIdentifier(this, minfos[i]); + methodIdents.add(ident); + if (ident.getName().equals("")) { + /* If there is a static initializer, it is automatically + * reachable (even if this class wouldn't be otherwise). + */ + ident.setPreserved(); + ident.setReachable(); + } else if (ident.getName().equals("")) + ident.setPreserved(); + } + + // preserve / chain inherited methods and fields. + ClassInfo[] ifaces = info.getInterfaces(); + ifaceNames = new String[ifaces.length]; + for (int i=0; i < ifaces.length; i++) { + ifaceNames[i] = ifaces[i].getName(); + initSuperClasses(ifaces[i]); + } + + if (info.getSuperclass() != null) { + superName = info.getSuperclass().getName(); + initSuperClasses(info.getSuperclass()); + } + + if ((Main.stripping & Main.STRIP_SOURCE) != 0) { + info.setSourceFile(null); + } + if ((Main.stripping & Main.STRIP_INNERINFO) != 0) { + info.setClasses(new ClassInfo[0]); + info.setOuterClass(null); + } + // load inner classes + ClassInfo outerClass = info.getOuterClass(); + ClassInfo[] innerClasses = info.getClasses(); + if (outerClass != null) + Main.getClassBundle().getClassIdentifier(outerClass.getName()); + + if (innerClasses != null) { + for (int i=0; i < innerClasses.length; i++) { + Main.getClassBundle() + .getClassIdentifier(innerClasses[i].getName()); + } + } + } + + /** + * Add the ClassInfo objects of the interfaces of ancestor. But if + * an interface of ancestor is not reachable it will add its interfaces + * instead. + * @param result The Collection where the interfaces should be added to. + * @param ancestor The ancestor whose interfaces should be added. + */ + public void addIfaces(Collection result, ClassIdentifier ancestor) { + String[] ifaces = ancestor.ifaceNames; + ClassInfo[] ifaceInfos = ancestor.info.getInterfaces(); + for (int i=0; i < ifaces.length; i++) { + ClassIdentifier ifaceident + = Main.getClassBundle().getClassIdentifier(ifaces[i]); + if (ifaceident != null && !ifaceident.isReachable()) + addIfaces(result, ifaceident); + else + result.add(ifaceInfos[i]); + } + } + + /** + * Generates the new super class and interfaces, removing super + * classes and interfaces that are not reachable. + * @return an array of class names (full qualified, dot separated) + * where the first entry is the super class (may be null) and the + * other entries are the interfaces. + */ + public void transformSuperIfaces() { + if ((Main.stripping & Main.STRIP_UNREACH) == 0) + return; + + Collection newIfaces = new LinkedList(); + ClassIdentifier ancestor = this; + while(true) { + addIfaces(newIfaces, ancestor); + ClassIdentifier superident + = Main.getClassBundle().getClassIdentifier(ancestor.superName); + if (superident == null || superident.isReachable()) + break; + ancestor = superident; + } + ClassInfo superInfo = ancestor.info.getSuperclass(); + ClassInfo[] ifaces = (ClassInfo[]) + newIfaces.toArray(new ClassInfo[newIfaces.size()]); + info.setSuperclass(superInfo); + info.setInterfaces(ifaces); + } + + public void transformInnerClasses() { + ClassInfo outerClass = info.getOuterClass(); + if ((Main.stripping & Main.STRIP_UNREACH) != 0) { + while (outerClass != null) { + ClassIdentifier outerIdent = Main.getClassBundle() + .getClassIdentifier(outerClass.getName()); + if (outerIdent != null && outerIdent.isReachable()) + break; + outerClass = outerClass.getOuterClass(); + } + } + info.setOuterClass(outerClass); + + ClassInfo[] innerClasses = info.getClasses(); + if (innerClasses != null) { + int newInnerCount = innerClasses.length; + if ((Main.stripping & Main.STRIP_UNREACH) != 0) { + for (int i=0; i < innerClasses.length; i++) { + ClassIdentifier innerIdent = Main.getClassBundle() + .getClassIdentifier(innerClasses[i].getName()); + if (innerIdent != null && !innerIdent.isReachable()) { + innerClasses[i] = null; + newInnerCount--; + } + } + } + + ClassInfo[] newInners = new ClassInfo[newInnerCount]; + int pos = 0; + for (int i=0; i 0) + GlobalOptions.err.println("Transforming "+this); + info.setName(getFullAlias()); + transformSuperIfaces(); + transformInnerClasses(); + + Collection newFields = new ArrayList(fieldIdents.size()); + Collection newMethods = new ArrayList(methodIdents.size()); + + for (Iterator i = fieldIdents.iterator(); i.hasNext(); ) { + FieldIdentifier ident = (FieldIdentifier)i.next(); + if ((Main.stripping & Main.STRIP_UNREACH) == 0 + || ident.isReachable()) { + ident.doTransformations(); + newFields.add(ident.info); + } else if (GlobalOptions.verboseLevel > 2) { + GlobalOptions.err.println("Field "+ ident+" not reachable"); + } + } + for (Iterator i = methodIdents.iterator(); i.hasNext(); ) { + MethodIdentifier ident = (MethodIdentifier)i.next(); + if ((Main.stripping & Main.STRIP_UNREACH) == 0 + || ident.isReachable()) { + ident.doTransformations(); + newMethods.add(ident.info); + } else if (GlobalOptions.verboseLevel > 2) { + GlobalOptions.err.println("Method "+ ident+" not reachable"); + } + } + + info.setFields((FieldInfo[]) newFields.toArray + (new FieldInfo[newFields.size()])); + info.setMethods((MethodInfo[]) newMethods.toArray + (new MethodInfo[newMethods.size()])); + } + + public void storeClass(DataOutputStream out) throws IOException { + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println("Writing "+this); + info.write(out); + info = null; + fieldIdents = methodIdents = null; + } + + public Identifier getParent() { + return pack; + } + + /** + * @return the full qualified name, excluding trailing dot. + */ + public String getFullName() { + return fullName; + } + + /** + * @return the full qualified alias, excluding trailing dot. + */ + public String getFullAlias() { + if (pack.parent == null) + return getAlias(); + else + return pack.getFullAlias() + "." + getAlias(); + } + + public String getName() { + return name; + } + + public String getType() { + return "Ljava/lang/Class;"; + } + + public int getModifiers() { + return info.getModifiers(); + } + + public List getFieldIdents() { + return fieldIdents; + } + + public List getMethodIdents() { + return methodIdents; + } + + public Iterator getChilds() { + final Iterator fieldIter = fieldIdents.iterator(); + final Iterator methodIter = methodIdents.iterator(); + + return new Iterator() { + boolean fieldsNext = fieldIter.hasNext(); + public boolean hasNext() { + return fieldsNext ? true : methodIter.hasNext(); + } + + public Object next() { + if (fieldsNext) { + Object result = fieldIter.next(); + fieldsNext = fieldIter.hasNext(); + return result; + } + return methodIter.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public String toString() { + return "ClassIdentifier "+getFullName(); + } + + public Identifier getIdentifier(String fieldName, String typeSig) { + for (Iterator i = getChilds(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (ident.getName().equals(fieldName) + && ident.getType().startsWith(typeSig)) + return ident; + } + + if (superName != null) { + ClassIdentifier superident = Main.getClassBundle() + .getClassIdentifier(superName); + if (superident != null) { + Identifier ident + = superident.getIdentifier(fieldName, typeSig); + if (ident != null) + return ident; + } + } + return null; + } + + public boolean containsFieldAliasDirectly(String fieldName, String typeSig, + IdentifierMatcher matcher) { + for (Iterator i = fieldIdents.iterator(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (((Main.stripping & Main.STRIP_UNREACH) == 0 + || ident.isReachable()) + && ident.wasAliased() + && ident.getAlias().equals(fieldName) + && ident.getType().startsWith(typeSig) + && matcher.matches(ident)) + return true; + } + return false; + } + + public boolean containsMethodAliasDirectly(String methodName, + String paramType, + IdentifierMatcher matcher) { + for (Iterator i = methodIdents.iterator(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (((Main.stripping & Main.STRIP_UNREACH) == 0 + || ident.isReachable()) + && ident.wasAliased() + && ident.getAlias().equals(methodName) + && ident.getType().startsWith(paramType) + && matcher.matches(ident)) + return true; + } + return false; + } + + public boolean fieldConflicts(FieldIdentifier field, String newAlias) { + String typeSig = (Main.options & Main.OPTION_STRONGOVERLOAD) != 0 + ? field.getType() : ""; + + /* Fields are similar to static methods: They are not + * overriden but hidden. We must only take care, that the + * reference of every getfield/putfield opcode points to the + * exact class, afterwards we can use doubled name as much as + * we want (even the decompiler can handle this). + */ + + ModifierMatcher mm = ModifierMatcher.allowAll; + if (containsFieldAliasDirectly(newAlias, typeSig, mm)) + return true; + return false; + } + + public boolean methodConflicts(MethodIdentifier method, String newAlias) { + String paramType = method.getType(); + if ((Main.options & Main.OPTION_STRONGOVERLOAD) == 0) + paramType = paramType.substring(0, paramType.indexOf(')')+1); + + ModifierMatcher matcher = ModifierMatcher.allowAll; + if (containsMethodAliasDirectly(newAlias, paramType, matcher)) + return true; + + ModifierMatcher packMatcher = matcher.forceAccess(0, true); + if (method.info.isStatic()) { + /* A static method does not conflict with static methods + * in super classes or sub classes. + */ + packMatcher.forbidModifier(Modifier.STATIC); + } + /* We don't have to check interfaces: sub classes must always + * implement all methods in the interface (maybe abstract, but + * they must be there!). + */ + ClassInfo superInfo = info.getSuperclass(); + ClassIdentifier superIdent = this; + while (superInfo != null) { + ClassIdentifier superident = Main.getClassBundle() + .getClassIdentifier(superInfo.getName()); + if (superident != null) { + if (superident.containsMethodAliasDirectly + (newAlias, paramType, packMatcher)) + return true; + } else { + MethodInfo[] minfos = superInfo.getMethods(); + for (int i=0; i< minfos.length; i++) { + if (minfos[i].getName().equals(newAlias) + && minfos[i].getType().startsWith(paramType) + && packMatcher.matches(minfos[i].getModifiers())) + return true; + } + } + superInfo = superInfo.getSuperclass(); + } + if (packMatcher.matches(method)) { + for (Iterator i = knownSubClasses.iterator(); i.hasNext(); ) { + ClassIdentifier ci = (ClassIdentifier) i.next(); + if (ci.containsMethodAliasDirectly(newAlias, paramType, + packMatcher)) + return true; + } + } + return false; + } + + public boolean conflicting(String newAlias) { + return pack.contains(newAlias, this); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/CodeAnalyzer.java b/jode/src/net/sf/jode/obfuscator/CodeAnalyzer.java new file mode 100644 index 0000000..27885f6 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/CodeAnalyzer.java @@ -0,0 +1,25 @@ +/* CodeAnalyzer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.bytecode.BasicBlocks; + +public interface CodeAnalyzer extends CodeTransformer { + public void analyzeCode(MethodIdentifier parent, BasicBlocks bb); +} diff --git a/jode/src/net/sf/jode/obfuscator/CodeTransformer.java b/jode/src/net/sf/jode/obfuscator/CodeTransformer.java new file mode 100644 index 0000000..d483224 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/CodeTransformer.java @@ -0,0 +1,25 @@ +/* CodeTransformer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.bytecode.BasicBlocks; + +public interface CodeTransformer { + public void transformCode(BasicBlocks bb); +} diff --git a/jode/src/net/sf/jode/obfuscator/ConstantRuntimeEnvironment.java b/jode/src/net/sf/jode/obfuscator/ConstantRuntimeEnvironment.java new file mode 100644 index 0000000..0568150 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/ConstantRuntimeEnvironment.java @@ -0,0 +1,294 @@ +/* ConstantRuntimeEnvironment Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.jvm.Interpreter; +import net.sf.jode.jvm.SimpleRuntimeEnvironment; +import net.sf.jode.jvm.InterpreterException; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.BasicBlocks; +import net.sf.jode.bytecode.TypeSignature; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; + +///#def COLLECTIONS java.util +import java.util.Set; +import java.util.HashSet; +///#enddef + +public class ConstantRuntimeEnvironment extends SimpleRuntimeEnvironment { + + /** + * The references that may be used in constant methods. + */ + static Set whiteList = new HashSet(); + + static void addWhite(Reference ref) { + whiteList.add(ref); + } + + public static boolean isWhite(Reference ref) { + return whiteList.contains(ref); + } + + public static boolean isWhite(String retTypeSig) { + return retTypeSig.length() == 1 // primitive type + || whiteList.contains(retTypeSig); + } + + static { + addWhite(Reference.getReference + ("Ljava/lang/String;", "toCharArray", "()[C")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "", + "(Ljava/lang/String;)V")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "", "()V")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(Ljava/lang/String;)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(C)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(B)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(S)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(Z)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(F)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(I)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(J)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "append", + "(D)Ljava/lang/StringBuffer;")); + addWhite(Reference.getReference + ("Ljava/lang/StringBuffer;", "toString", + "()Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "", "()V")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "", "([C)V")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "", "([CII)V")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "", + "(Ljava/lang/String;)V")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "", + "(Ljava/lang/StringBuffer;)V")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "length", "()I")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "replace", + "(CC)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(Z)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(B)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(S)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(C)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(D)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(F)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(I)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(J)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "valueOf", + "(Ljava/lang/Object;)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "substring", + "(I)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/String;", "substring", + "(II)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava.lang/reflect/Modifier;", "toString", + "(I)Ljava/lang/String;")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "abs", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "abs", "(F)F")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "abs", "(I)I")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "abs", "(J)J")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "acos", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "asin", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "atan", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "atan2", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "ceil", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "cos", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "exp", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "floor", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "IEEEremainder", "(DD)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "log", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "max", "(DD)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "max", "(FF)F")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "max", "(II)I")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "max", "(JJ)J")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "min", "(DD)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "min", "(FF)F")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "min", "(II)I")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "min", "(JJ)J")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "pow", "(DD)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "rint", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "round", "(D)J")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "round", "(F)I")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "sin", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "sqrt", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "tan", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "toDegrees", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "toRadians", "(D)D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "E", "D")); + addWhite(Reference.getReference + ("Ljava/lang/Math;", "PI", "D")); + + whiteList.add("Ljava/lang/String;"); +// whiteList.add("Ljava/lang/Class;"); +// whiteList.add("Ljava/lang/reflect/Method;"); +// whiteList.add("Ljava/lang/reflect/Field;"); + } + + private Interpreter interpreter; + + public ConstantRuntimeEnvironment() { + interpreter = new Interpreter(this); + } + + public Object getField(Reference ref, Object obj) + throws InterpreterException { + if (isWhite(ref)) + return super.getField(ref, obj); + FieldIdentifier fi + = (FieldIdentifier) Main.getClassBundle().getIdentifier(ref); + if (fi != null && !fi.isNotConstant()) { + Object result = fi.getConstant(); + if (result == null) + result = TypeSignature.getDefaultValue(ref.getType()); + return result; + } + throw new InterpreterException("Field " + ref + " not constant"); + } + + public void putField(Reference ref, Object obj, Object value) + throws InterpreterException { + throw new InterpreterException("Modifying Field " + ref + "."); + } + + public Object invokeConstructor(Reference ref, Object[] params) + throws InterpreterException, InvocationTargetException { + if (isWhite(ref)) + return super.invokeConstructor(ref, params); + throw new InterpreterException("Creating new Object " + ref + "."); + } + + public Object invokeMethod(Reference ref, boolean isVirtual, + Object cls, Object[] params) + throws InterpreterException, InvocationTargetException { + if (isWhite(ref)) + return super.invokeMethod(ref, isVirtual, cls, params); + MethodIdentifier mi + = (MethodIdentifier) Main.getClassBundle().getIdentifier(ref); + if (mi != null) { + BasicBlocks bb = mi.info.getBasicBlocks(); + if (bb != null) + return interpreter.interpretMethod(bb, cls, params); + } + throw new InterpreterException("Invoking library method " + ref + "."); + } + + public boolean instanceOf(Object obj, String className) + throws InterpreterException { + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException ex) { + throw new InterpreterException + ("Class "+ex.getMessage()+" not found"); + } + return obj != null && clazz.isInstance(obj); + } + + public Object newArray(String type, int[] dimensions) + throws InterpreterException, NegativeArraySizeException { + if (type.length() == dimensions.length + 1) { + Class clazz; + try { + clazz = TypeSignature + .getClass(type.substring(dimensions.length)); + } catch (ClassNotFoundException ex) { + throw new InterpreterException + ("Class "+ex.getMessage()+" not found"); + } + return Array.newInstance(clazz, dimensions); + } + throw new InterpreterException("Creating object array."); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/FieldIdentifier.java b/jode/src/net/sf/jode/obfuscator/FieldIdentifier.java new file mode 100644 index 0000000..c56ffc0 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/FieldIdentifier.java @@ -0,0 +1,156 @@ +/* FieldIdentifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import java.lang.reflect.Modifier; +import net.sf.jode.bytecode.*; +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.HashSet; +import java.util.Map; +///#enddef + + +public class FieldIdentifier extends Identifier{ + FieldInfo info; + ClassIdentifier clazz; + String name; + String type; + /** + * This field tells if the value is not constant. It is initially + * set to false, and if a write to that field is found, it is set + * to true. + */ + private boolean notConstant; + private Object constant; + + /** + * The FieldChangeListener that should be notified if a + * write to this field is found. + */ + private Collection fieldListeners; + + public FieldIdentifier(ClassIdentifier clazz, FieldInfo info) { + super(info.getName()); + this.name = info.getName(); + this.type = info.getType(); + this.info = info; + this.clazz = clazz; + this.constant = info.getConstant(); + } + + public void setSingleReachable() { + super.setSingleReachable(); + Main.getClassBundle().analyzeIdentifier(this); + } + + public void setSinglePreserved() { + super.setSinglePreserved(); + setNotConstant(); + } + + public void analyze() { + String type = getType(); + int index = type.indexOf('L'); + if (index != -1) { + int end = type.indexOf(';', index); + Main.getClassBundle().reachableClass + (type.substring(index+1, end).replace('/', '.')); + } + } + + public Identifier getParent() { + return clazz; + } + + public String getFullName() { + return clazz.getFullName() + "." + getName() + "." + getType(); + } + + public String getFullAlias() { + return clazz.getFullAlias() + "." + getAlias() + "." + + Main.getClassBundle().getTypeAlias(getType()); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public int getModifiers() { + return info.getModifiers(); + } + + public Iterator getChilds() { + return Collections.EMPTY_LIST.iterator(); + } + + public boolean isNotConstant() { + return notConstant; + } + + public Object getConstant() { + return constant; + } + + public void addFieldListener(Identifier ident) { + if (ident == null) + throw new NullPointerException(); + if (fieldListeners == null) + fieldListeners = new HashSet(); + if (!fieldListeners.contains(ident)) + fieldListeners.add(ident); + } + + public void removeFieldListener(Identifier ident) { + if (fieldListeners != null) + fieldListeners.remove(ident); + } + + public void setNotConstant() { + if (notConstant) + return; + + notConstant = true; + if (fieldListeners == null) + return; + + for (Iterator i = fieldListeners.iterator(); i.hasNext(); ) + Main.getClassBundle().analyzeIdentifier((Identifier) i.next()); + fieldListeners = null; + } + + public String toString() { + return "FieldIdentifier "+getFullName(); + } + + public boolean conflicting(String newAlias) { + return clazz.fieldConflicts(this, newAlias); + } + + public void doTransformations() { + info.setName(getAlias()); + info.setType(Main.getClassBundle().getTypeAlias(type)); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/Identifier.java b/jode/src/net/sf/jode/obfuscator/Identifier.java new file mode 100644 index 0000000..0e4cf14 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/Identifier.java @@ -0,0 +1,264 @@ +/* Identifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.GlobalOptions; +import java.io.*; +///#def COLLECTIONS java.util +import java.util.Map; +import java.util.Iterator; +///#enddef + +public abstract class Identifier { + /** + * This is a doubly list of identifiers, that must have always + * have the same names, and same preserved settings. + */ + private Identifier right = null; + private Identifier left = null; + + private boolean reachable = false; + private boolean preserved = false; + + private String alias = null; + private boolean wasAliased = false; + + public Identifier(String alias) { + this.alias = alias; + } + + + /** + * Returns true, if this identifier is reachable in some way, false if it + * is dead and can be removed. + */ + public final boolean isReachable() { + return reachable; + } + + /** + * true, if this identifier must preserve its name, false if the + * name may be obfuscated. + */ + public final boolean isPreserved() { + return preserved; + } + + /** + * Marks this identifier as preserved. This will also make the + * identifier reachable, if it isn't already. + * + * You shouldn't call this directly, but use setPreserved instead. + */ + protected void setSinglePreserved() { + } + + /** + * Marks this identifier as reachable. + * + * You should override this method for method identifier, which may + * mark other methods as reachable. + * + * You shouldn't call this directly, but use setReachable instead. + */ + protected void setSingleReachable() { + if (getParent() != null) + getParent().setReachable(); + } + + /** + * Mark all shadows as reachable. + */ + public void setReachable() { + if (!reachable) { + reachable = true; + setSingleReachable(); + } + } + + /** + * Mark all shadows as preserved. + */ + public void setPreserved() { + if (!preserved) { + preserved = true; + Identifier ptr = this; + while (ptr != null) { + ptr.setSinglePreserved(); + ptr = ptr.left; + } + ptr = right; + while (ptr != null) { + ptr.setSinglePreserved(); + ptr = ptr.right; + } + } + } + + public Identifier getRepresentative() { + Identifier ptr = this; + while (ptr.left != null) + ptr = ptr.left; + return ptr; + } + + public final boolean isRepresentative() { + return left == null; + } + + public final boolean wasAliased() { + return getRepresentative().wasAliased; + } + + public final void setAlias(String name) { + if (name != null) { + Identifier rep = getRepresentative(); + rep.wasAliased = true; + rep.alias = name; + } + } + + public final String getAlias() { + return getRepresentative().alias; + } + + /** + * Mark that this identifier and the given identifier must always have + * the same name. + */ + public void addShadow(Identifier orig) { + if (isPreserved() && !orig.isPreserved()) + orig.setPreserved(); + else if (!isPreserved() && orig.isPreserved()) + setPreserved(); + + Identifier ptr = this; + while (ptr.right != null) + ptr = ptr.right; + + /* Check if orig is already on the ptr chain */ + Identifier check = orig; + while (check.right != null) + check = check.right; + if (check == ptr) + return; + + while (orig.left != null) + orig = orig.left; + ptr.right = orig; + orig.left = ptr; + } + + static int serialnr = 0; + + public void buildTable(Renamer renameRule) { + if (!isReachable() + && (Main.stripping & Main.STRIP_UNREACH) != 0) + return; + + if (isPreserved()) { + if (GlobalOptions.verboseLevel > 4) + GlobalOptions.err.println(toString() + " is preserved"); + } else { + Identifier rep = getRepresentative(); + if (!rep.wasAliased) { + rep.wasAliased = true; + + // set alias to empty string, so it won't conflict! + rep.alias = ""; + Iterator aliases = renameRule.generateNames(this); + next_alias: + for (;;) { + String newAlias = (String) aliases.next(); + Identifier ptr = rep; + while (ptr != null) { + if (ptr.conflicting(newAlias)) + continue next_alias; + ptr = ptr.right; + } + setAlias(newAlias.toString()); + break; + } + } + } + for (Iterator i = getChilds(); i.hasNext(); ) + ((Identifier)i.next()).buildTable(renameRule); + } + + public void writeTable(Map table) { + if (!isReachable() + && (Main.stripping & Main.STRIP_UNREACH) != 0) + return; + + if (getAlias().length() != 0) { + String name = getName(); + Identifier outer = getParent(); + while (outer != null && outer.getAlias().length() == 0) { + if (outer.getName().length() > 0) + name = outer.getName() + "." + name; + outer = outer.getParent(); + } + table.put(getFullAlias(), name); + } + + for (Iterator i = getChilds(); i.hasNext(); ) + ((Identifier)i.next()).writeTable(table); + } + + public void readTable(Map table) { + Identifier rep = getRepresentative(); + if (!rep.wasAliased) { + String newAlias = (String) table.get(getFullName()); + if (newAlias != null) { + rep.wasAliased = true; + rep.setAlias(newAlias); + } + } + for (Iterator i = getChilds(); i.hasNext(); ) + ((Identifier)i.next()).readTable(table); + } + + public void applyPreserveRule(IdentifierMatcher preserveRule) { + if (preserveRule.matches(this)) { + System.err.println("preserving: "+this); + setReachable(); + Identifier ident = this; + while (ident != null) { + ident.setPreserved(); + ident = ident.getParent(); + } + } + for (Iterator i = getChilds(); i.hasNext(); ) + ((Identifier)i.next()).applyPreserveRule(preserveRule); + } + public abstract Iterator getChilds(); + public abstract Identifier getParent(); + public abstract String getName(); + public abstract String getType(); + public abstract String getFullName(); + public abstract String getFullAlias(); + public abstract boolean conflicting(String newAlias); + + /** + * This is called by ClassBundle when it a class is added with + * ClassBundle.analyzeIdentifier(). + */ + public void analyze() { + } +} diff --git a/jode/src/net/sf/jode/obfuscator/IdentifierMatcher.java b/jode/src/net/sf/jode/obfuscator/IdentifierMatcher.java new file mode 100644 index 0000000..4a7cb30 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/IdentifierMatcher.java @@ -0,0 +1,44 @@ +/* IdentifierMatcher Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; + +public interface IdentifierMatcher { + /** + * Returns true, if the ident is matched by this matcher. + */ + public boolean matches(Identifier ident); + + /** + * Returns true, if there may be a sub ident, that is matched by + * this matcher. + * @param subIdent the name of the sub ident, or null if every + * name is okay. + */ + public boolean matchesSub(Identifier ident, String subIdent); + + /** + * Returns the unique name of the single sub item, for which matches + * or matchesSub returns true. + * @return the unique name, or null, if there is not a unique sub + * item. + */ + public String getNextComponent(Identifier ident); +} + diff --git a/jode/src/net/sf/jode/obfuscator/LocalIdentifier.java b/jode/src/net/sf/jode/obfuscator/LocalIdentifier.java new file mode 100644 index 0000000..af4beb9 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/LocalIdentifier.java @@ -0,0 +1,64 @@ +/* LocalIdentifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; + +///#def COLLECTIONS java.util +import java.util.Collections; +import java.util.Iterator; +///#enddef + +public class LocalIdentifier extends Identifier { + String name; + String type; + + public LocalIdentifier(String name, String type, MethodIdentifier mIdent) { + super(name); + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public Iterator getChilds() { + return Collections.EMPTY_LIST.iterator(); + } + + public Identifier getParent() { + return null; + } + + public String getFullName() { + return name; + } + + public String getFullAlias() { + return getAlias(); + } + + public boolean conflicting(String newAlias) { + return false; + } +} diff --git a/jode/src/net/sf/jode/obfuscator/Main.java b/jode/src/net/sf/jode/obfuscator/Main.java new file mode 100644 index 0000000..01bc7cd --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/Main.java @@ -0,0 +1,183 @@ +/* Main Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.GlobalOptions; + +import gnu.getopt.LongOpt; +import gnu.getopt.Getopt; + +import java.lang.reflect.Modifier; +import java.io.PrintWriter; +import java.io.FileReader; +import java.io.InputStreamReader; +import java.io.IOException; +///#def COLLECTIONS java.util +import java.util.Collections; +///#enddef + +public class Main { + public static boolean swapOrder = false; + + public static final int OPTION_STRONGOVERLOAD = 0x0001; + public static final int OPTION_PRESERVESERIAL = 0x0002; + public static int options = OPTION_PRESERVESERIAL; + + private static final LongOpt[] longOptions = new LongOpt[] { + new LongOpt("cp", LongOpt.REQUIRED_ARGUMENT, null, 'c'), + new LongOpt("classpath", LongOpt.REQUIRED_ARGUMENT, null, 'c'), + new LongOpt("destpath", LongOpt.REQUIRED_ARGUMENT, null, 'd'), + new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'), + new LongOpt("version", LongOpt.NO_ARGUMENT, null, 'V'), + new LongOpt("verbose", LongOpt.OPTIONAL_ARGUMENT, null, 'v'), + new LongOpt("debug", LongOpt.OPTIONAL_ARGUMENT, null, 'D'), + }; + + public static final String[] stripNames = { + "unreach", "inner", "lvt", "lnt", "source" + }; + public static final int STRIP_UNREACH = 0x0001; + public static final int STRIP_INNERINFO = 0x0002; + public static final int STRIP_LVT = 0x0004; + public static final int STRIP_LNT = 0x0008; + public static final int STRIP_SOURCE = 0x0010; + public static int stripping = 0x1; + + private static ClassBundle bundle; + + public static void usage() { + PrintWriter err = GlobalOptions.err; + err.println("usage: net.sf.jode.obfuscator.Main flags* script"); + err.println(" -h, --help "+ + "show this information."); + err.println(" -V, --version "+ + "output version information and exit."); + err.println(" -v, --verbose "+ + "be verbose (multiple times means more verbose)."); + err.println(" -c, --classpath "+ + "search for classes in specified classpath."); + err.println(" "+ + "The directories should be separated by ','."); + err.println(" -d, --dest "+ + "write decompiled files to disk into directory destdir."); + err.println(" -D, --debug=... "+ + "use --debug=help for more information."); + } + + + public static ClassBundle getClassBundle() { + return bundle; + } + + public static void main(String[] params) { + if (params.length == 0) { + usage(); + return; + } + String cp = null, dest = null; + + GlobalOptions.err.println(GlobalOptions.copyright); + bundle = new ClassBundle(); + boolean errorInParams = false; + Getopt g = new Getopt("net.sf.jode.obfuscator.Main", params, "hVvc:d:D:", + longOptions, true); + for (int opt = g.getopt(); opt != -1; opt = g.getopt()) { + switch(opt) { + case 0: + break; + case 'h': + usage(); + errorInParams = true; + break; + case 'V': + GlobalOptions.err.println(GlobalOptions.version); + break; + case 'c': + cp = g.getOptarg(); + break; + case 'd': + dest = g.getOptarg(); + break; + case 'v': { + String arg = g.getOptarg(); + if (arg == null) + GlobalOptions.verboseLevel++; + else { + try { + GlobalOptions.verboseLevel = Integer.parseInt(arg); + } catch (NumberFormatException ex) { + GlobalOptions.err.println + ("net.sf.jode.obfuscator.Main: Argument `" + +arg+"' to --verbose must be numeric:"); + errorInParams = true; + } + } + break; + } + case 'D': { + String arg = g.getOptarg(); + if (arg == null) + arg = "help"; + errorInParams |= !GlobalOptions.setDebugging(arg); + break; + } + default: + errorInParams = true; + break; + } + } + if (errorInParams) + return; + + if (g.getOptind() != params.length - 1) { + GlobalOptions.err.println("You must specify exactly one script."); + return; + } + + + try { + String filename = params[g.getOptind()]; + ScriptParser parser = new ScriptParser + (filename.equals("-") + ? new InputStreamReader(System.in) + : new FileReader(filename)); + parser.parseOptions(bundle); + } catch (IOException ex) { + GlobalOptions.err.println + ("IOException while reading script file."); + ex.printStackTrace(GlobalOptions.err); + return; + } catch (ParseException ex) { + GlobalOptions.err.println("Syntax error in script file: "); + GlobalOptions.err.println(ex.getMessage()); + if (GlobalOptions.verboseLevel > 5) + ex.printStackTrace(GlobalOptions.err); + return; + } + + // Command Line overwrites script options: + if (cp != null) + bundle.setOption("classpath", Collections.singleton(cp)); + if (dest != null) + bundle.setOption("dest", Collections.singleton(dest)); + + bundle.run(); + } +} + diff --git a/jode/src/net/sf/jode/obfuscator/MethodIdentifier.java b/jode/src/net/sf/jode/obfuscator/MethodIdentifier.java new file mode 100644 index 0000000..bb7449e --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/MethodIdentifier.java @@ -0,0 +1,252 @@ +/* MethodIdentifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.*; + +///#def COLLECTIONS java.util +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +///#enddef + +import java.lang.reflect.Modifier; +import java.util.BitSet; + +public class MethodIdentifier extends Identifier implements Opcodes { + ClassIdentifier clazz; + MethodInfo info; + String name; + String type; + + boolean globalSideEffects; + BitSet localSideEffects; + + /** + * The code analyzer of this method, or null if there isn't any. + */ + CodeAnalyzer codeAnalyzer; + + public MethodIdentifier(ClassIdentifier clazz, MethodInfo info) { + super(info.getName()); + this.name = info.getName(); + this.type = info.getType(); + this.clazz = clazz; + this.info = info; + + BasicBlocks bb = info.getBasicBlocks(); + if (bb != null) { + if ((Main.stripping & + (Main.STRIP_LVT | Main.STRIP_LNT)) != 0) { + Block[] blocks = bb.getBlocks(); + for (int i = 0; i < blocks.length; i++) { + Instruction[] instrs = blocks[i].getInstructions(); + for (int j = 0; j < instrs.length; j++) { + if ((Main.stripping & Main.STRIP_LVT) != 0 + && instrs[j].hasLocal()) + instrs[j].setLocalInfo + (LocalVariableInfo + .getInfo(instrs[j].getLocalSlot())); + if ((Main.stripping & Main.STRIP_LNT) != 0) + instrs[j].setLineNr(-1); + } + } + } + codeAnalyzer = Main.getClassBundle().getCodeAnalyzer(); + + CodeTransformer[] trafos + = Main.getClassBundle().getPreTransformers(); + for (int i = 0; i < trafos.length; i++) { + trafos[i].transformCode(bb); + } + } + } + + public Iterator getChilds() { + return Collections.EMPTY_LIST.iterator(); + } + + public void setSingleReachable() { + super.setSingleReachable(); + Main.getClassBundle().analyzeIdentifier(this); + } + + public void analyze() { + if (GlobalOptions.verboseLevel > 1) + GlobalOptions.err.println("Analyze: "+this); + + String type = getType(); + int index = type.indexOf('L'); + while (index != -1) { + int end = type.indexOf(';', index); + Main.getClassBundle().reachableClass + (type.substring(index+1, end).replace('/', '.')); + index = type.indexOf('L', end); + } + + String[] exceptions = info.getExceptions(); + if (exceptions != null) { + for (int i=0; i< exceptions.length; i++) + Main.getClassBundle() + .reachableClass(exceptions[i]); + } + + BasicBlocks bb = info.getBasicBlocks(); + if (bb != null) + codeAnalyzer.analyzeCode(this, bb); + } + + public Identifier getParent() { + return clazz; + } + + public String getFullName() { + return clazz.getFullName() + "." + getName() + "." + getType(); + } + + public String getFullAlias() { + return clazz.getFullAlias() + "." + getAlias() + "." + + Main.getClassBundle().getTypeAlias(getType()); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public int getModifiers() { + return info.getModifiers(); + } + + public boolean conflicting(String newAlias) { + return clazz.methodConflicts(this, newAlias); + } + + public String toString() { + return "MethodIdentifier "+getFullName(); + } + + public boolean hasGlobalSideEffects() { + return globalSideEffects; + } + + public boolean getLocalSideEffects(int paramNr) { + return globalSideEffects || localSideEffects.get(paramNr); + } + + public void setGlobalSideEffects() { + globalSideEffects = true; + } + + public void setLocalSideEffects(int paramNr) { + localSideEffects.set(paramNr); + } + + /** + * This method does the code transformation. This include + *
    • new slot distribution for locals
    • + *
    • obfuscating transformation of flow
    • + *
    • renaming field, method and class references
    • + *
    + */ + boolean wasTransformed = false; + public void doTransformations() { + if (wasTransformed) + throw new InternalError + ("doTransformation called on transformed method"); + wasTransformed = true; + info.setName(getAlias()); + ClassBundle bundle = Main.getClassBundle(); + info.setType(bundle.getTypeAlias(type)); + if (codeAnalyzer != null) { + BasicBlocks bb = info.getBasicBlocks(); + try { + codeAnalyzer.transformCode(bb); + CodeTransformer[] trafos = bundle.getPostTransformers(); + for (int i = 0; i < trafos.length; i++) { + trafos[i].transformCode(bb); + } + } catch (RuntimeException ex) { + ex.printStackTrace(GlobalOptions.err); + bb.dumpCode(GlobalOptions.err); + } + + Block[] blocks = bb.getBlocks(); + for (int i = 0; i < blocks.length; i++) { + Instruction[] instrs = blocks[i].getInstructions(); + for (int j = 0; j < instrs.length; j++) { + switch (instrs[j].getOpcode()) { + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: { + instrs[j].setReference + (Main.getClassBundle() + .getReferenceAlias(instrs[j].getReference())); + break; + + } + case opc_putstatic: + case opc_putfield: + case opc_getstatic: + case opc_getfield: { + instrs[j].setReference + (Main.getClassBundle() + .getReferenceAlias(instrs[j].getReference())); + break; + } + case opc_new: + case opc_checkcast: + case opc_instanceof: + case opc_multianewarray: { + instrs[j].setClazzType + (Main.getClassBundle() + .getTypeAlias(instrs[j].getClazzType())); + break; + } + } + } + } + + Handler[] handlers = bb.getExceptionHandlers(); + for (int i=0; i< handlers.length; i++) { + if (handlers[i].getType() != null) { + ClassIdentifier ci = Main.getClassBundle() + .getClassIdentifier(handlers[i].getType()); + if (ci != null) + handlers[i].setType(ci.getFullAlias()); + } + } + } + + String[] exceptions = info.getExceptions(); + if (exceptions != null) { + for (int i=0; i< exceptions.length; i++) { + ClassIdentifier ci = Main.getClassBundle() + .getClassIdentifier(exceptions[i]); + if (ci != null) + exceptions[i] = ci.getFullAlias(); + } + } + } +} diff --git a/jode/src/net/sf/jode/obfuscator/OptionHandler.java b/jode/src/net/sf/jode/obfuscator/OptionHandler.java new file mode 100644 index 0000000..51ed2b6 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/OptionHandler.java @@ -0,0 +1,29 @@ +/* OptionHandler Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; + +///#def COLLECTIONS java.util +import java.util.Collection; +///#enddef + +public interface OptionHandler { + public void setOption(String option, Collection values) + throws IllegalArgumentException; +} diff --git a/jode/src/net/sf/jode/obfuscator/PackageIdentifier.java b/jode/src/net/sf/jode/obfuscator/PackageIdentifier.java new file mode 100644 index 0000000..b0c39a5 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/PackageIdentifier.java @@ -0,0 +1,504 @@ +/* PackageIdentifier Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.FieldInfo; +import net.sf.jode.bytecode.MethodInfo; +import java.lang.reflect.Modifier; +import java.io.*; +import java.util.Vector; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +///#def COLLECTIONS java.util +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.Random; +import java.util.Arrays; +import java.util.Collections; +///#enddef + +public class PackageIdentifier extends Identifier { + ClassBundle bundle; + PackageIdentifier parent; + String name; + String fullName; + + boolean loadOnDemand; + boolean initialized = false; + Map loadedClasses; + List swappedClasses; + Random rand = new Random(); + + public PackageIdentifier(ClassBundle bundle, + PackageIdentifier parent, + String fullName, String name) { + super(name); + this.bundle = bundle; + this.parent = parent; + this.fullName = fullName; + this.name = name; + this.loadedClasses = new HashMap(); + } + + /** + * Marks the parent package as preserved, too. + */ + protected void setSinglePreserved() { + if (parent != null) + parent.setPreserved(); + } + + public void setLoadOnDemand() { + if (loadOnDemand) + return; + loadOnDemand = true; + if ((Main.stripping & Main.STRIP_UNREACH) == 0) { + String fullNamePrefix = + (fullName.length() > 0) ? fullName + "." : ""; + + // Load all classes and packages now, so they don't get stripped + Enumeration enumeration = + bundle.getClassPath().listClassesAndPackages(getFullName()); + while (enumeration.hasMoreElements()) { + String subclazz = ((String)enumeration.nextElement()).intern(); + if (loadedClasses.containsKey(subclazz)) + continue; + String subFull = (fullNamePrefix + subclazz).intern(); + + if (bundle.getClassPath().isPackage(subFull)) { + PackageIdentifier ident = new PackageIdentifier + (bundle, this, subFull, subclazz); + loadedClasses.put(subclazz, ident); + swappedClasses = null; + ident.setLoadOnDemand(); + } else { + ClassIdentifier ident = new ClassIdentifier + (this, subFull, subclazz, + bundle.getClassPath().getClassInfo(subFull)); + + if (GlobalOptions.verboseLevel > 1) + GlobalOptions.err.println("preloading Class " + + subFull); + loadedClasses.put(subclazz, ident); + swappedClasses = null; + bundle.addClassIdentifier(ident); + } + } + // Everything is loaded, we don't need to load on demand anymore. + loadOnDemand = false; + } + } + + public void loadMatchingClasses(IdentifierMatcher matcher) { + String component = matcher.getNextComponent(this); + if (component != null) { + Identifier ident = (Identifier) loadedClasses.get(component); + if (ident == null) { + component = component.intern(); + String subFull = (fullName.length() > 0) + ? fullName + "."+ component : component; + subFull = subFull.intern(); + if (bundle.getClassPath().isPackage(subFull)) { + ident = new PackageIdentifier(bundle, this, + subFull, component); + loadedClasses.put(component, ident); + swappedClasses = null; + if (loadOnDemand) + ((PackageIdentifier) ident).setLoadOnDemand(); + if (initialized) + ((PackageIdentifier) ident).initialize(); + } else if (bundle.getClassPath().existsClass(subFull)) { + if (GlobalOptions.verboseLevel > 1) + GlobalOptions.err.println("loading Class " +subFull); + ident = new ClassIdentifier(this, subFull, component, + bundle.getClassPath() + .getClassInfo(subFull)); + if (loadOnDemand || matcher.matches(ident)) { + loadedClasses.put(component, ident); + if (initialized) + ((ClassIdentifier) ident).initClass(); + swappedClasses = null; + bundle.addClassIdentifier(ident); + } + } else { + GlobalOptions.err.println + ("Warning: Can't find class/package " + subFull); + } + } + if (ident instanceof PackageIdentifier) { + if (matcher.matches(ident)) { + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println("loading Package " + +ident.getFullName()); + ((PackageIdentifier) ident).setLoadOnDemand(); + } + + if (matcher.matchesSub(ident, null)) + ((PackageIdentifier) ident).loadMatchingClasses(matcher); + } + } else { + String fullNamePrefix = + (fullName.length() > 0) ? fullName + "." : ""; + /* Load all matching classes and packages */ + Enumeration enumeration = + bundle.getClassPath().listClassesAndPackages(getFullName()); + while (enumeration.hasMoreElements()) { + String subclazz = ((String)enumeration.nextElement()).intern(); + if (loadedClasses.containsKey(subclazz)) + continue; + String subFull = (fullNamePrefix + subclazz).intern(); + + if (matcher.matchesSub(this, subclazz)) { + if (bundle.getClassPath().isPackage(subFull)) { + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println("loading Package " + + subFull); + PackageIdentifier ident = new PackageIdentifier + (bundle, this, subFull, subclazz); + loadedClasses.put(subclazz, ident); + swappedClasses = null; + if (loadOnDemand || matcher.matches(ident)) + ident.setLoadOnDemand(); + if (initialized) + ((PackageIdentifier) ident).initialize(); + } else { + ClassIdentifier ident = new ClassIdentifier + (this, subFull, subclazz, + bundle.getClassPath().getClassInfo(subFull)); + + if (loadOnDemand || matcher.matches(ident)) { + if (GlobalOptions.verboseLevel > 1) + GlobalOptions.err.println("loading Class " + + subFull); + loadedClasses.put(subclazz, ident); + swappedClasses = null; + bundle.addClassIdentifier(ident); + if (initialized) + ((ClassIdentifier) ident).initClass(); + } + } + } + } + List list = new ArrayList(); + list.addAll(loadedClasses.values()); + for (Iterator i = list.iterator(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (ident instanceof PackageIdentifier) { + if (matcher.matches(ident)) + ((PackageIdentifier) ident).setLoadOnDemand(); + + if (matcher.matchesSub(ident, null)) + ((PackageIdentifier) ident) + .loadMatchingClasses(matcher); + } + } + } + } + + public void initialize() { + for (Iterator i = getChilds(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (ident instanceof ClassIdentifier) + ((ClassIdentifier) ident).initClass(); + else + ((PackageIdentifier) ident).initialize(); + } + initialized = true; + } + + public Identifier getIdentifier(String name) { + if (loadOnDemand) { + return loadClass(name); + } + int index = name.indexOf('.'); + if (index == -1) { + return (Identifier) loadedClasses.get(name); + } else { + PackageIdentifier pack = (PackageIdentifier) + loadedClasses.get(name.substring(0, index)); + if (pack != null) + return pack.getIdentifier(name.substring(index+1)); + else + return null; + } + } + + private Identifier loadClass(String name) { + int index = name.indexOf('.'); + if (index == -1) { + Identifier ident = (Identifier) loadedClasses.get(name); + if (ident == null) { + String subFull = + (fullName.length() > 0) ? fullName + "."+ name : name; + subFull = subFull.intern(); + if (bundle.getClassPath().isPackage(subFull)) { + PackageIdentifier pack + = new PackageIdentifier(bundle, this, subFull, name); + loadedClasses.put(name, pack); + swappedClasses = null; + pack.setLoadOnDemand(); + ident = pack; + } else if (!bundle.getClassPath().existsClass(subFull)) { + GlobalOptions.err.println("Warning: Can't find class " + + subFull); + Thread.dumpStack(); + } else { + ident = new ClassIdentifier(this, subFull, name, + bundle.getClassPath() + .getClassInfo(subFull)); + loadedClasses.put(name, ident); + ((ClassIdentifier) ident).initClass(); + swappedClasses = null; + bundle.addClassIdentifier(ident); + } + } + return ident; + } else { + String subpack = name.substring(0, index); + PackageIdentifier pack = + (PackageIdentifier) loadedClasses.get(subpack); + if (pack == null) { + String subFull = (fullName.length() > 0) + ? fullName + "."+ subpack : subpack; + subFull = subFull.intern(); + if (bundle.getClassPath().isPackage(subFull)) { + pack = new PackageIdentifier(bundle, this, + subFull, subpack); + loadedClasses.put(subpack, pack); + swappedClasses = null; + if (loadOnDemand) + pack.setLoadOnDemand(); + } + } + + if (pack != null) + return pack.loadClass(name.substring(index+1)); + else + return null; + } + } + + public void applyPreserveRule(IdentifierMatcher preserveRule) { + if (loadOnDemand) + loadMatchingClasses(preserveRule); + super.applyPreserveRule(preserveRule); + } + + /** + * @return the full qualified name. + */ + public String getFullName() { + return fullName; + } + + /** + * @return the full qualified alias. + */ + public String getFullAlias() { + if (parent != null) { + String parentAlias = parent.getFullAlias(); + String alias = getAlias(); + if (alias.length() == 0) + return parentAlias; + else if (parentAlias.length() == 0) + return alias; + else + return parentAlias + "." + alias; + } + return ""; + } + + public String findAlias(String className) { + int index = className.indexOf('.'); + if (index == -1) { + Identifier ident = getIdentifier(className); + if (ident != null) + return ident.getFullAlias(); + } else { + Identifier pack = getIdentifier(className.substring(0, index)); + if (pack != null) + return ((PackageIdentifier)pack) + .findAlias(className.substring(index+1)); + } + return className; + } + + public void buildTable(Renamer renameRule) { + loadOnDemand = false; + super.buildTable(renameRule); + } + + public void doTransformations() { + for (Iterator i = getChilds(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if (ident instanceof ClassIdentifier) { + ((ClassIdentifier) ident).doTransformations(); + } else + ((PackageIdentifier) ident).doTransformations(); + } + } + + public void readTable(Map table) { + if (parent != null) + setAlias((String) table.get(getFullName())); + for (Iterator i = loadedClasses.values().iterator(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if ((Main.stripping & Main.STRIP_UNREACH) == 0 + || ident.isReachable()) + ident.readTable(table); + } + } + + public Identifier getParent() { + return parent; + } + + public String getName() { + return name; + } + + public String getType() { + return "package"; + } + + public Iterator getChilds() { + /* Since loadedClasses is somewhat sorted by the hashcode + * of the _original_ names, swap it here to prevent to guess + * even parts of the names. + */ + if (swappedClasses == null) { + swappedClasses = Arrays.asList(loadedClasses.values().toArray()); + Collections.shuffle(swappedClasses, rand); + } + return swappedClasses.iterator(); + } + + public void storeClasses(ZipOutputStream zip) { + for (Iterator i = getChilds(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if ((Main.stripping & Main.STRIP_UNREACH) != 0 + && !ident.isReachable()) { + if (GlobalOptions.verboseLevel > 4) + GlobalOptions.err.println("Class/Package " + + ident.getFullName() + + " is not reachable"); + continue; + } + if (ident instanceof PackageIdentifier) + ((PackageIdentifier) ident).storeClasses(zip); + else { + try { + String filename = ident.getFullAlias().replace('.','/') + + ".class"; + zip.putNextEntry(new ZipEntry(filename)); + DataOutputStream out = new DataOutputStream + (new BufferedOutputStream(zip)); + ((ClassIdentifier) ident).storeClass(out); + out.flush(); + zip.closeEntry(); + } catch (java.io.IOException ex) { + GlobalOptions.err.println("Can't write Class " + + ident.getName()); + ex.printStackTrace(GlobalOptions.err); + } + } + } + } + + public void storeClasses(File destination) { + File newDest = (parent == null) ? destination + : new File(destination, getAlias()); + if (!newDest.exists() && !newDest.mkdir()) { + GlobalOptions.err.println("Could not create directory " + +newDest.getPath()+", check permissions."); + } + for (Iterator i = getChilds(); i.hasNext(); ) { + Identifier ident = (Identifier) i.next(); + if ((Main.stripping & Main.STRIP_UNREACH) != 0 + && !ident.isReachable()) { + if (GlobalOptions.verboseLevel > 4) + GlobalOptions.err.println("Class/Package " + + ident.getFullName() + + " is not reachable"); + continue; + } + if (ident instanceof PackageIdentifier) + ((PackageIdentifier) ident) + .storeClasses(newDest); + else { + try { + File file = new File(newDest, ident.getAlias()+".class"); +// if (file.exists()) { +// GlobalOptions.err.println +// ("Refuse to overwrite existing class file " +// +file.getPath()+". Remove it first."); +// return; +// } + DataOutputStream out = new DataOutputStream + (new BufferedOutputStream + (new FileOutputStream(file))); + ((ClassIdentifier) ident).storeClass(out); + out.close(); + } catch (java.io.IOException ex) { + GlobalOptions.err.println("Can't write Class " + + ident.getName()); + ex.printStackTrace(GlobalOptions.err); + } + } + } + } + + public String toString() { + return (parent == null) ? "base package" : getFullName(); + } + + public boolean contains(String newAlias, Identifier except) { + for (Iterator i = loadedClasses.values().iterator(); i.hasNext(); ) { + Identifier ident = (Identifier)i.next(); + if (ident != except) { + if (((Main.stripping & Main.STRIP_UNREACH) == 0 + || ident.isReachable()) + && ident.getAlias().equalsIgnoreCase(newAlias)) + return true; + if (ident instanceof PackageIdentifier + && ident.getAlias().length() == 0 + && (((PackageIdentifier) ident) + .contains(newAlias, this))) + return true; + } + } + if (getAlias().length() == 0 + && parent != null + && parent != except + && parent.contains(newAlias, this)) + return true; + return false; + } + + public boolean conflicting(String newAlias) { + return parent.contains(newAlias, this); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/ParseException.java b/jode/src/net/sf/jode/obfuscator/ParseException.java new file mode 100644 index 0000000..bc96ba7 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/ParseException.java @@ -0,0 +1,26 @@ +/* ParseException Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; + +public class ParseException extends Exception { + public ParseException(int linenr, String message) { + super ("line "+linenr+": "+message); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/Renamer.java b/jode/src/net/sf/jode/obfuscator/Renamer.java new file mode 100644 index 0000000..f73b034 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/Renamer.java @@ -0,0 +1,34 @@ +/* Renamer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; +///#def COLLECTIONS java.util +import java.util.Iterator; +///#enddef + +public interface Renamer { + + /** + * Generates a new name. + * @param ident the identifier that should be renamed. + * @param lastName the last name generated for this identifier, or + * null, if no name was generated yet. + */ + public Iterator generateNames(Identifier ident); +} diff --git a/jode/src/net/sf/jode/obfuscator/ScriptParser.java b/jode/src/net/sf/jode/obfuscator/ScriptParser.java new file mode 100644 index 0000000..6a20e1d --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/ScriptParser.java @@ -0,0 +1,282 @@ +/* ScriptParser Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; + +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.LinkedList; +///#enddef + +public class ScriptParser { + static int NO_TOKEN = -2; + static int EOF_TOKEN = -1; + static int STRING_TOKEN = 0; + static int NEW_TOKEN = 1; + static int EQUALS_TOKEN = 2; + static int COMMA_TOKEN = 3; + static int OPENBRACE_TOKEN = 4; + static int CLOSEBRACE_TOKEN = 5; + static int IDENTIFIER_TOKEN = 6; + static int NUMBER_TOKEN = 7; + Scanner scanner; + + class Scanner { + BufferedReader input; + String value; + String line; + int column; + int linenr; + int pushback = NO_TOKEN; + + public Scanner(Reader i) { + input = new BufferedReader(i); + } + + public void readString() throws ParseException { + StringBuffer val = new StringBuffer(); + while (column < line.length()) { + char c = line.charAt(column++); + if (c == '"') { + value = val.toString(); + return; + } + if (c == '\\') { + c = line.charAt(column++); + switch (c) { + case 'n': + val.append('\n'); + break; + case 't': + val.append('\t'); + break; + case 'r': + val.append('\r'); + break; + case 'u': + if (column+4 <= line.length()) { + try { + char uni = (char) Integer.parseInt + (line.substring(column, column+4), 16); + column += 4; + val.append(uni); + } catch (NumberFormatException ex) { + throw new ParseException + (linenr, + "Invalid unicode escape character"); + } + } else + throw new ParseException + (linenr, + "Invalid unicode escape character"); + break; + default: + val.append(c); + } + } else + val.append(c); + } + throw new ParseException(linenr, + "String spans over multiple lines"); + } + public void readIdentifier() { + int start = column-1; + while (column < line.length() + && Character.isUnicodeIdentifierPart(line.charAt(column))) + column++; + value = line.substring(start, column); + } + + public void readNumber() { + boolean hex = false; + int start = column-1; + /* special case for hex numbers */ + if (line.charAt(start) == '0' && line.charAt(column) == 'x') { + column++; + hex = true; + } + while (column < line.length()) { + char c = line.charAt(column); + if (!Character.isDigit(c)) { + if (!hex) + break; + if ((c < 'A' || c > 'F') && (c < 'a' || c > 'f')) + break; + } + column++; + } + value = line.substring(start, column); + } + + public void pushbackToken(int token) { + if (pushback != NO_TOKEN) + throw new IllegalStateException + ("Can only handle one pushback"); + pushback = token; + } + + public int getToken() throws ParseException, IOException { + if (pushback != NO_TOKEN) { + int result = pushback; + pushback = NO_TOKEN; + return result; + } + value = null; + while (true) { + if (line == null) { + line = input.readLine(); + if (line == null) + return EOF_TOKEN; + linenr++; + column = 0; + } + while (column < line.length()) { + char c = line.charAt(column++); + if (Character.isWhitespace(c)) + continue; + if (c == '#') + // this is a comment, skip this line + break; + if (c == '=') + return EQUALS_TOKEN; + if (c == ',') + return COMMA_TOKEN; + if (c == '{') + return OPENBRACE_TOKEN; + if (c == '}') + return CLOSEBRACE_TOKEN; + if (c == '"') { + readString(); + return STRING_TOKEN; + } + if (Character.isDigit(c) || c == '+' || c == '-') { + readNumber(); + return NUMBER_TOKEN; + } + if (Character.isUnicodeIdentifierStart(c)) { + readIdentifier(); + if (value.equals("new")) + return NEW_TOKEN; + return IDENTIFIER_TOKEN; + } + throw new ParseException + (linenr, "Illegal character `"+c+"'"); + } + line = null; + } + } + + public String getValue() { + return value; + } + + public int getLineNr() { + return linenr; + } + } + + public ScriptParser(Reader reader) { + this.scanner = new Scanner(reader); + } + + public Object parseClass() throws ParseException, IOException { + int linenr = scanner.getLineNr(); + int token = scanner.getToken(); + if (token != IDENTIFIER_TOKEN) + throw new ParseException(linenr, "Class name expected"); + Object instance; + try { + Class clazz = Class.forName("net.sf.jode.obfuscator.modules." + +scanner.getValue()); + instance = clazz.newInstance(); + } catch (ClassNotFoundException ex) { + throw new ParseException(scanner.getLineNr(), + "Class `"+scanner.getValue() + +"' not found"); + } catch (Exception ex) { + throw new ParseException(scanner.getLineNr(), + "Class `"+scanner.getValue() + +"' not valid: "+ex.getMessage()); + } + + token = scanner.getToken(); + if (token == OPENBRACE_TOKEN) { + if (!(instance instanceof OptionHandler)) + throw new ParseException + (scanner.getLineNr(), + "Class `"+instance.getClass().getName() + +"' doesn't handle options."); + parseOptions((OptionHandler) instance); + if (scanner.getToken() != CLOSEBRACE_TOKEN) + throw new ParseException(scanner.getLineNr(), "`}' expected"); + } else + scanner.pushbackToken(token); + return instance; + } + + public void parseOptions(OptionHandler optionHandler) + throws ParseException, IOException + { + int token = scanner.getToken(); + while (true) { + if (token == EOF_TOKEN || token == CLOSEBRACE_TOKEN) { + scanner.pushbackToken(token); + return; + } + if (token != IDENTIFIER_TOKEN) + throw new ParseException(scanner.getLineNr(), + "identifier expected"); + String ident = scanner.getValue(); + if (scanner.getToken() != EQUALS_TOKEN) + throw new ParseException(scanner.getLineNr(), + "equal sign expected"); + + int linenr = scanner.getLineNr(); + Collection values = new LinkedList(); + do { + token = scanner.getToken(); + if (token == NEW_TOKEN) { + values.add(parseClass()); + } else if (token == STRING_TOKEN) { + values.add(scanner.getValue()); + } else if (token == NUMBER_TOKEN) { + values.add(new Integer(scanner.getValue())); + } + token = scanner.getToken(); + } while (token == COMMA_TOKEN); + try { + optionHandler.setOption(ident, values); + } catch (IllegalArgumentException ex) { + throw new ParseException(linenr, + optionHandler.getClass().getName() + +": "+ex.getMessage()); + } catch (RuntimeException ex) { + throw new ParseException(linenr, + optionHandler.getClass().getName() + +": Illegal value: " + +ex.getClass().getName() + +": "+ex.getMessage()); + } + } + } +} diff --git a/jode/src/net/sf/jode/obfuscator/TranslationTable.java b/jode/src/net/sf/jode/obfuscator/TranslationTable.java new file mode 100644 index 0000000..ac23528 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/TranslationTable.java @@ -0,0 +1,80 @@ +/* TranslationTable Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator; + +///#def COLLECTIONS java.util +import java.util.Map; +import java.util.TreeMap; +import java.util.Iterator; +///#enddef + +///#ifndef JDK12 +///#def COLLECTIONS java.util +///import java.util.Comparator; +///#enddef +///#endif + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.IOException; + +public class TranslationTable extends TreeMap { + +///#ifndef JDK12 +/// public TranslationTable() { +/// super(createStringComparator()); +/// } +/// +/// private static Comparator createStringComparator() { +/// return new Comparator() { +/// public int compare(Object o1, Object o2) { +/// return ((String) o1).compareTo((String) o2); +/// } +/// }; +/// } +///#endif + + public void load(InputStream in) throws IOException { + BufferedReader reader = + new BufferedReader(new InputStreamReader(in)); + + String line; + while ((line = reader.readLine()) != null) { + if (line.charAt(0) == '#') + continue; + int delim = line.indexOf('='); + String key = line.substring(0, delim); + String value = line.substring(delim+1); + put(key, value); + } + } + + public void store(OutputStream out) throws IOException { + PrintWriter writer = new PrintWriter(out); + for (Iterator i = entrySet().iterator(); i.hasNext(); ) { + Map.Entry e = (Map.Entry) i.next(); + writer.println(e.getKey()+"="+e.getValue()); + } + writer.flush(); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/modules/ConstantAnalyzer.java b/jode/src/net/sf/jode/obfuscator/modules/ConstantAnalyzer.java new file mode 100644 index 0000000..98ad9c5 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/ConstantAnalyzer.java @@ -0,0 +1,1781 @@ +/* ConstantAnalyzer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; + +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.*; +import net.sf.jode.jvm.InterpreterException; +import net.sf.jode.obfuscator.*; +import net.sf.jode.util.StringQuoter; + +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; +import java.io.PrintWriter; +import java.util.BitSet; + +///#def COLLECTIONS java.util +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.HashMap; +import java.util.Map; +import java.util.Iterator; +import java.util.ListIterator; +///#enddef + +/** + * Analyze the code, assuming every field that is not yet written to + * is constant. This may imply that some code is dead code. + * + * While we analyze the Code we remember, which local variable and + * which stack slot is constant for each instruction and if the + * instruction is dead. First we assume that every local and every + * slot is constant for each instruction, and that all instructions + * are dead code. + * + * Now we mark all local variables of the first instruction as not + * constant and the first instruction as modified. + * + * While there is a modified instruction, we pick one and repeat the + * following algorithm: + * + * If the instruction produces a constant result (because it is a ldc + * instruction, or it combines constant values), we put that instruction + * on the ConstantListener-Queue of all constant inputs and put the + * constant result on the ConstantListener-Queue of that instruction. + * + * + * @author Jochen Hoenicke */ +public class ConstantAnalyzer extends SimpleAnalyzer { + + private static ConstantRuntimeEnvironment runtime + = new ConstantRuntimeEnvironment(); + + private final static int CMP_EQ = 0; + private final static int CMP_NE = 1; + private final static int CMP_LT = 2; + private final static int CMP_GE = 3; + private final static int CMP_GT = 4; + private final static int CMP_LE = 5; + private final static int CMP_GREATER_MASK + = (1 << CMP_GT)|(1 << CMP_GE)|(1 << CMP_NE); + private final static int CMP_LESS_MASK + = (1 << CMP_LT)|(1 << CMP_LE)|(1 << CMP_NE); + private final static int CMP_EQUAL_MASK + = (1 << CMP_GE)|(1 << CMP_LE)|(1 << CMP_EQ); + + final static int CONSTANT = 0x02; + final static int CONSTANTFLOW = 0x04; + + /** + * The blocks, that are not analyzed yet, but whose before is + * already set. + */ + TodoQueue modifiedQueue = new TodoQueue(); + + /** + * The basic blocks for the current method. + */ + BasicBlocks bb; + /** + * All block infos of all blocks in the current method. + */ + BlockInfo[] infos; + /** + * The currently analyzed method, only valid while analyzeCode is running. + */ + MethodIdentifier methodIdent; + Map fieldDependencies; + + Map bbInfos = new HashMap(); + Map constantInfos = new HashMap(); + + private interface ConstantListener { + public void constantChanged(); + } + + private static class ConstValue implements ConstantListener { + public final static Object VOLATILE = new Object(); + /** + * The constant value, VOLATILE if value is not constant. + * + * This may also be an instance of BlockInfo and point + * to the target block of the jsr, for which this variable + * contains the return value. + */ + Object value; + /** + * The number of slots this value takes on the stack. + */ + int stackSize; + /** + * The constant listeners, that want to be informed if this is + * no longer constant. + */ + Set listeners; + + public ConstValue(Object constant) { + value = constant; + stackSize = (constant instanceof Double + || constant instanceof Long) ? 2 : 1; + listeners = new HashSet(); + } + + public ConstValue(int stackSize) { + this.value = VOLATILE; + this.stackSize = stackSize; + } + + public ConstValue(ConstValue constant) { + value = constant.value; + stackSize = constant.stackSize; + listeners = new HashSet(); + constant.addConstantListener(this); + } + + public ConstValue copy() { + return value == VOLATILE ? this + : new ConstValue(this); + } + + /** + * Merge the other value into this value. + */ + public void merge(ConstValue other) { + if (this == other) + return; + + if (value == null ? other.value == null + : value.equals(other.value)) { + if (other.value != VOLATILE) + other.addConstantListener(this); + return; + } + + if (value != VOLATILE) + fireChanged(); + } + + public void addConstantListener(ConstantListener l) { + listeners.add(l); + } + + public void removeConstantListener(ConstantListener l) { + listeners.remove(l); + } + + public void fireChanged() { + value = VOLATILE; + for (Iterator i = listeners.iterator(); i.hasNext(); ) { + ConstantListener l = (ConstantListener) i.next(); +// System.err.println(" notifying: "+ l); + l.constantChanged(); + } + listeners = null; + } + + public void constantChanged() { + if (value != VOLATILE) + fireChanged(); + } + + public String toString() { + String result; + if (value == VOLATILE) + result = "vol("+stackSize+")"; + else if (value instanceof String) + result = StringQuoter.quote((String) value); + else + result = String.valueOf(value); + +// StringBuffer sb = new StringBuffer(result).append('{'); +// Iterator i = listeners.iterator(); +// while (i.hasNext()) +// sb.append(i.next()).append(','); +// result = sb.append('}').toString(); + return result+"@"+hashCode(); + } + } + + /** + * This class handles information necessary for jsr analysis. + * Jsr is probably the most difficult opcode to handle, we have + * to keep track of changed locals, of nested jsrs, if there is + * a corresponding ret instruction and much more. + */ + private final static class JsrInfo implements Cloneable { + BlockInfo jsrTarget; + Collection callers; + + /** + * The number of outer subroutines + */ + int jsrDepth; + + /** + * The outer jsr info, or null if this is a top level jsr. + */ + JsrInfo outerJsr; + + /** + * The locals used in the outer subroutine, or null if this is + * a top level jsr. + * The locals used inside this jsr are in the BlockInfo itself. + */ + BitSet usedInOuter; + + /** + * The info for the ret block of this subroutine. + */ + BlockInfo retInfo; + + public JsrInfo(BlockInfo target, JsrInfo outer, BitSet used) { + jsrTarget = target; + if (outer != null) { + outerJsr = outer; + usedInOuter = (BitSet) used.clone(); + } + jsrDepth = (outer != null ? outer.jsrDepth + 1 : 0); + callers = new ArrayList(); + } + + public void setRetInfo(BlockInfo retInfo) { + this.retInfo = retInfo; + for (Iterator i = callers.iterator(); i.hasNext(); ) + ((BlockInfo)i.next()).mergeRetLocals(this, retInfo); + } + + public void addCaller(BlockInfo caller) { + if (callers.contains(caller)) + return; + callers.add(caller); + if (retInfo != null) + caller.mergeRetLocals(this, retInfo); + } + + public JsrInfo intersect(JsrInfo other, BitSet used) { + int otherDepth = other != null ? other.jsrDepth : -1; + JsrInfo isect = this; + int myDepth = jsrDepth; + + while (otherDepth > myDepth) { + if (other.usedInOuter != null) + used.or(other.usedInOuter); + other = other.outerJsr; + otherDepth--; + } + while (myDepth > otherDepth) { + if (isect.usedInOuter != null) + used.or(isect.usedInOuter); + isect = isect.outerJsr; + myDepth--; + } + while (isect != other) { + if (other.usedInOuter != null) { + used.or(other.usedInOuter); + used.or(isect.usedInOuter); + } + other = other.outerJsr; + isect = isect.outerJsr; + } + return isect; + } + + public void merge(JsrInfo outer, BitSet used) { + if (outerJsr != null) + outerJsr = outerJsr.intersect(outer, used); + if (outerJsr == null) + usedInOuter = null; + else + usedInOuter.or(used); + } + + public String toString() { + StringBuffer sb = new StringBuffer(String.valueOf(jsrTarget.nr)); + if (retInfo != null) + sb.append("->").append(retInfo.nr); + if (outerJsr != null) + sb.append("used="+usedInOuter+",outer=["+outerJsr+"]"); + return sb.toString(); + } + } + + private static class StackLocalInfo { + ConstValue[] stack; + ConstValue[] locals; + int stackDepth; + + private StackLocalInfo(ConstValue[] stack, + ConstValue[] locals, int stackDepth) { + this.stack = stack; + this.locals = locals; + this.stackDepth = stackDepth; + } + + public StackLocalInfo(int maxStack, int numLocals, + boolean isStatic, String methodTypeSig) { + + String[] paramTypes + = TypeSignature.getParameterTypes(methodTypeSig); + locals = new ConstValue[numLocals]; + stack = new ConstValue[maxStack]; + stackDepth = 0; + int slot = 0; + if (!isStatic) + locals[slot++] = unknownValue[0]; + for (int i=0; i< paramTypes.length; i++) { + int stackSize = TypeSignature.getTypeSize(paramTypes[i]); + locals[slot] = unknownValue[stackSize-1]; + slot += stackSize; + } + } + + public StackLocalInfo(StackLocalInfo original) { + locals = new ConstValue[original.locals.length]; + for (int i=0; i< locals.length; i++) { + if (original.locals[i] != null) + locals[i] = original.locals[i].copy(); + } + stack = new ConstValue[original.stack.length]; + stackDepth = original.stackDepth; + for (int i=0; i< stackDepth; i++) { + if (original.stack[i] != null) + stack[i] = original.stack[i].copy(); + } + } + + public StackLocalInfo poppush(int pops, ConstValue push) { + for (int i = pops; i > 0; i--) + stack[--stackDepth] = null; + if (push == null) + throw new NullPointerException(); + stack[stackDepth] = push; + stackDepth += push.stackSize; + return this; + } + + public StackLocalInfo pop(int pops) { + for (int i = pops; i > 0; i--) + stack[--stackDepth] = null; + return this; + } + + public StackLocalInfo dup(int count, int depth) { + int bottom = stackDepth - count - depth; + System.arraycopy(stack, stackDepth - count, + stack, stackDepth, count); + if (depth > 0) { + System.arraycopy(stack, bottom, + stack, bottom + count, depth); + System.arraycopy(stack, stackDepth, + stack, bottom, count); + } + stackDepth += count; + return this; + } + + public StackLocalInfo swap() { + ConstValue tmp = stack[stackDepth - 1]; + stack[stackDepth-1] = stack[stackDepth-2]; + stack[stackDepth-2] = tmp; + return this; + } + + public StackLocalInfo copy() { + ConstValue[] newStack = (ConstValue[]) stack.clone(); + ConstValue[] newLocals = (ConstValue[]) locals.clone(); + return new StackLocalInfo(newStack, newLocals, stackDepth); + } + + public ConstValue getLocal(int slot) { + return locals[slot]; + } + + public ConstValue getStack(int depth) { + return stack[stackDepth - depth]; + } + + public StackLocalInfo setLocal(int slot, ConstValue value) { + locals[slot] = value; + if (value != null && value.stackSize == 2) + locals[slot+1] = null; + return this; + } + + public void mergeOneLocal(int slot, ConstValue cv) { + if (locals[slot] != null) { + if (cv == null) + // Other can be not initialized + // implies local can be not initialized + locals[slot] = null; + else + locals[slot].merge(cv); + } + } + + public void merge(StackLocalInfo other) { + for (int i=0; i < locals.length; i++) + mergeOneLocal(i, other.locals[i]); + if (stack.length != other.stack.length) + throw new InternalError("stack length differs"); + for (int i=0; i < stack.length; i++) { + if ((other.stack[i] == null) != (stack[i] == null)) + throw new InternalError("stack types differ"); + else if (stack[i] != null) + stack[i].merge(other.stack[i]); + } + } + + public String toString() { + return "StackLocalInfo[locals="+Arrays.asList(locals) + +",stack="+Arrays.asList(stack)+",stackDepth="+stackDepth+"]"; + } + } + + private static class ConstantInfo implements ConstantListener { + ConstantInfo() { + this(0, null); + } + + ConstantInfo(int flags) { + this(flags, null); + } + + ConstantInfo(int flags, Object constant) { + this.flags = flags; + this.constant = constant; + } + + int flags; + /** + * The constant, may be an Instruction for CONSTANTFLOW. + */ + Object constant; + + public void constantChanged() { + constant = null; + flags &= ~(CONSTANT | CONSTANTFLOW); + } + } + + private static ConstValue[] unknownValue = { + new ConstValue(1), new ConstValue(2) + }; + + private static ConstantInfo unknownConstInfo = new ConstantInfo(); + + /** + * The block info contains the info needed for a single block. + */ + private class BlockInfo implements ConstantListener { + + int nr; + Block block; + BlockInfo nextTodo; + + int constantFlow = -1; + /** + * The state of the locals and stack before this block is + * executed. + */ + StackLocalInfo before; + + /** + * The state of the locals and stack after this block is + * executed, but before the last jump instruction is done. + * So for conditional jumps the stack still contains the + * operands. + */ + StackLocalInfo after; + + /** + * The JsrInfo of the innermost surrounding subroutine. If + * before is null, this value is null and means unknown. If + * before is not null, a value of null means no surrounding + * subroutine. + */ + JsrInfo jsrInfo; + + /** + * The locals used between the jsr instruction for jsrInfo and + * the end of the current block; null if jsrInfo is null. + */ + BitSet usedLocals; + + public BlockInfo(int nr, Block block) { + this.nr = nr; + this.block = block; + } + + public boolean isReachable() { + return before != null; + } + + public void mergeOneLocal(int slot, ConstValue cv) { + before.mergeOneLocal(slot, cv); + } + + public void mergeBefore(StackLocalInfo info, + JsrInfo newJsrInfo, + BitSet usedLocals) { + if (before == null) { +// System.err.println("mergeBefore:::"+info); + before = new StackLocalInfo(info); + this.jsrInfo = newJsrInfo; + if (usedLocals != null) + this.usedLocals = (BitSet) usedLocals.clone(); + modifiedQueue.enqueue(this); + } else { +// System.err.println("merging:::: "+before+":::AND:::"+info); + before.merge(info); + if (jsrInfo != null) + jsrInfo = jsrInfo.intersect(newJsrInfo, usedLocals); + if (jsrInfo == null) + this.usedLocals = null; + else + this.usedLocals.or(usedLocals); + if (constantFlow >= 0) + propagateAfter(); + } + } + + public void constantChanged() { + if (constantFlow >= 0) + propagateAfter(); + } + + public void useLocal(int slot) { + if (usedLocals != null) + usedLocals.set(slot); + } + + public void mergeRetLocals(JsrInfo myJsrInfo, BlockInfo retBlock) { + if (constantFlow == 0) { + Instruction[] instrs = block.getInstructions(); + // remove the constantFlow info + constantInfos.remove(instrs[instrs.length-1]); + constantFlow = -1; + } + + Block nextBlock = block.getSuccs()[1]; + if (nextBlock == null) { + /* The calling jsr is just before a return. We don't + * have to fuzz around, since nobody is interested in + * constant values. + */ + return; + } + + ConstValue[] newLocals = (ConstValue[]) after.locals.clone(); + BitSet nextUsed = new BitSet(); + for (int slot = 0; slot < newLocals.length; slot++) { + if (retBlock.usedLocals.get(slot)) { + newLocals[slot] = retBlock.after.locals[slot]; + if (nextUsed != null) + nextUsed.set(slot); + } + } + StackLocalInfo nextInfo + = new StackLocalInfo(after.stack, newLocals, after.stackDepth); + JsrInfo nextJsrInfo = null; + if (usedLocals != null) + nextUsed.or(usedLocals); + if (myJsrInfo.usedInOuter != null) + nextUsed.or(myJsrInfo.usedInOuter); + if (jsrInfo != null) + nextJsrInfo = jsrInfo.intersect(myJsrInfo.outerJsr, + nextUsed); + if (nextJsrInfo == null) + nextUsed = null; + + infos[nextBlock.getBlockNr()].mergeBefore(nextInfo, + nextJsrInfo, nextUsed); + } + + public void propagateAfter() { + Instruction[] instrs = block.getInstructions(); + Instruction instr = instrs[instrs.length-1]; + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: { + int size = 1; + ConstValue stacktop = after.getStack(1); + ConstValue other = null; + boolean known = stacktop.value != ConstValue.VOLATILE; + if (opcode >= opc_if_icmpeq && opcode <= opc_if_acmpne) { + other = after.getStack(2); + size = 2; + known &= other.value != ConstValue.VOLATILE; + } + StackLocalInfo nextInfo = after.copy().pop(size); + if (known) { + if (constantFlow >= 0) + /* Nothing changed... */ + return; + int opc_mask; + if (opcode >= opc_if_acmpeq) { + if (opcode >= opc_ifnull) { + opc_mask = stacktop.value == null + ? CMP_EQUAL_MASK : CMP_GREATER_MASK; + opcode -= opc_ifnull; + } else { + opc_mask = stacktop.value == other.value + ? CMP_EQUAL_MASK : CMP_GREATER_MASK; + opcode -= opc_if_acmpeq; + } + } else { + int value = ((Integer) stacktop.value).intValue(); + if (opcode >= opc_if_icmpeq) { + int val1 = ((Integer) other.value).intValue(); + opc_mask = (val1 == value ? CMP_EQUAL_MASK + : val1 < value ? CMP_LESS_MASK + : CMP_GREATER_MASK); + opcode -= opc_if_icmpeq; + } else { + opc_mask = (value == 0 ? CMP_EQUAL_MASK + : value < 0 ? CMP_LESS_MASK + : CMP_GREATER_MASK); + opcode -= opc_ifeq; + } + } + + constantFlow = ((opc_mask & (1<= 0) + /* Nothing changed... */ + return; + Instruction pc; + int value = ((Integer) stacktop.value).intValue(); + int[] values = instr.getValues(); + constantFlow = Arrays.binarySearch(values, value); + if (constantFlow < 0) + constantFlow = values.length; + ConstantInfo constInfo = new ConstantInfo + (CONSTANTFLOW, new Integer(constantFlow)); + constantInfos.put(instr, constInfo); + stacktop.addConstantListener(this); + Block constantSucc = block.getSuccs()[constantFlow]; + if (constantSucc != null) + infos[constantSucc.getBlockNr()] + .mergeBefore(nextInfo, jsrInfo, usedLocals); + } else { + constantInfos.remove(instr); + Block[] succs = block.getSuccs(); + for (int i=0; i < succs.length; i++) { + if (i != constantFlow) { + if (succs[i] != null) + infos[succs[i].getBlockNr()] + .mergeBefore(nextInfo, jsrInfo, + usedLocals); + } + } + constantFlow = -1; + } + break; + } + case opc_jsr: { + /* Assume there is no ret for this jsr. If the ret + * was already found this info will be corrected + * immediately by the jsrInfo.addCaller. + */ + constantFlow = 0; + ConstantInfo constInfo = new ConstantInfo + (CONSTANTFLOW, new Integer(0)); + constantInfos.put(instr, constInfo); + + + BlockInfo target = infos[block.getSuccs()[0].getBlockNr()]; + ConstValue result = new ConstValue(target); + + JsrInfo newJsrInfo; + BitSet newUsed; + if (target.before == null) { + /* The info for this jsr target wasn't created before. */ + newJsrInfo = new JsrInfo(target, jsrInfo, usedLocals); + newJsrInfo.addCaller(this); + newUsed = new BitSet(); + } else if (target.jsrInfo == null + || target.jsrInfo.jsrTarget != target) { + /* The jsr exits instantenously. */ + newJsrInfo = jsrInfo; + newUsed = usedLocals; + } else { + /* Normal case, merge jsr infos. */ + newJsrInfo = target.jsrInfo; + newJsrInfo.merge(jsrInfo, usedLocals); + newJsrInfo.addCaller(this); + newUsed = new BitSet(); + } + + target.mergeBefore(after.copy().poppush(0, result), + newJsrInfo, newUsed); + break; + } + case opc_ret: { + ConstValue result = after.getLocal(instr.getLocalSlot()); + BlockInfo jsrTarget = (BlockInfo) result.value; + while (jsrInfo.jsrTarget != jsrTarget) { + usedLocals.or(jsrInfo.usedInOuter); + jsrInfo = jsrInfo.outerJsr; + } + jsrInfo.setRetInfo(this); + break; + } + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: + case opc_areturn: case opc_return: + case opc_athrow: + break; + default: { + Block succ = block.getSuccs()[0]; + if (succ != null) + infos[succ.getBlockNr()].mergeBefore + (after, jsrInfo, usedLocals); + } + } + } + + StackLocalInfo handleOpcode(Instruction instr, StackLocalInfo info) { + constantInfos.remove(instr); + int opcode = instr.getOpcode(); + ConstValue result; + switch (opcode) { + case opc_nop: + return info.pop(0); + + case opc_ldc: + case opc_ldc2_w: + result = new ConstValue(instr.getConstant()); + return info.poppush(0, result); + + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: + result = info.getLocal(instr.getLocalSlot()); + if (result.value != ConstValue.VOLATILE) { + ConstantInfo constInfo + = new ConstantInfo(CONSTANT, result.value); + result.addConstantListener(constInfo); + constantInfos.put(instr, constInfo); + } + return info.poppush(0, result) + .setLocal(instr.getLocalSlot(), result); + case opc_iaload: case opc_laload: + case opc_faload: case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: { + result = unknownValue[(opcode == opc_laload + || opcode == opc_daload) ? 1 : 0]; + return info.poppush(2, result); + } + case opc_istore: case opc_fstore: case opc_astore: { + int slot = instr.getLocalSlot(); + useLocal(slot); + result = info.getStack(1); + return info.pop(1).setLocal(slot, result); + } + case opc_lstore: case opc_dstore: { + int slot = instr.getLocalSlot(); + useLocal(slot); + useLocal(slot + 1); + result = info.getStack(2); + return info.pop(2).setLocal(slot, result); + } + case opc_iastore: case opc_lastore: + case opc_fastore: case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: { + int size = (opcode == opc_lastore + || opcode == opc_dastore) ? 2 : 1; + return info.pop(2+size); + } + case opc_pop: + return info.pop(1); + case opc_pop2: + return info.pop(2); + + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + return info.dup((opcode - (opc_dup - 3)) / 3, + (opcode - (opc_dup - 3)) % 3); + case opc_swap: + return info.swap(); + + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + case opc_iand: case opc_land: + case opc_ior : case opc_lor : + case opc_ixor: case opc_lxor: { + int size = 1 + (opcode - opc_iadd & 1); + ConstValue value1 = info.getStack(2*size); + ConstValue value2 = info.getStack(1*size); + boolean known = value1.value != ConstValue.VOLATILE + && value2.value != ConstValue.VOLATILE; + if (known) { + if (((opcode == opc_idiv || opcode == opc_irem) + && ((Integer)value2.value).intValue() == 0) + || ((opcode == opc_ldiv || opcode == opc_lrem) + && ((Long)value2.value).longValue() == 0)) + known = false; + } + if (known) { + Object newValue; + switch (opcode) { + case opc_iadd: + newValue = new Integer + (((Integer)value1.value).intValue() + + ((Integer)value2.value).intValue()); + break; + case opc_isub: + newValue = new Integer + (((Integer)value1.value).intValue() + - ((Integer)value2.value).intValue()); + break; + case opc_imul: + newValue = new Integer + (((Integer)value1.value).intValue() + * ((Integer)value2.value).intValue()); + break; + case opc_idiv: + newValue = new Integer + (((Integer)value1.value).intValue() + / ((Integer)value2.value).intValue()); + break; + case opc_irem: + newValue = new Integer + (((Integer)value1.value).intValue() + % ((Integer)value2.value).intValue()); + break; + case opc_iand: + newValue = new Integer + (((Integer)value1.value).intValue() + & ((Integer)value2.value).intValue()); + break; + case opc_ior: + newValue = new Integer + (((Integer)value1.value).intValue() + | ((Integer)value2.value).intValue()); + break; + case opc_ixor: + newValue = new Integer + (((Integer)value1.value).intValue() + ^ ((Integer)value2.value).intValue()); + break; + + case opc_ladd: + newValue = new Long + (((Long)value1.value).longValue() + + ((Long)value2.value).longValue()); + break; + case opc_lsub: + newValue = new Long + (((Long)value1.value).longValue() + - ((Long)value2.value).longValue()); + break; + case opc_lmul: + newValue = new Long + (((Long)value1.value).longValue() + * ((Long)value2.value).longValue()); + break; + case opc_ldiv: + newValue = new Long + (((Long)value1.value).longValue() + / ((Long)value2.value).longValue()); + break; + case opc_lrem: + newValue = new Long + (((Long)value1.value).longValue() + % ((Long)value2.value).longValue()); + break; + case opc_land: + newValue = new Long + (((Long)value1.value).longValue() + & ((Long)value2.value).longValue()); + break; + case opc_lor: + newValue = new Long + (((Long)value1.value).longValue() + | ((Long)value2.value).longValue()); + break; + case opc_lxor: + newValue = new Long + (((Long)value1.value).longValue() + ^ ((Long)value2.value).longValue()); + break; + + case opc_fadd: + newValue = new Float + (((Float)value1.value).floatValue() + + ((Float)value2.value).floatValue()); + break; + case opc_fsub: + newValue = new Float + (((Float)value1.value).floatValue() + - ((Float)value2.value).floatValue()); + break; + case opc_fmul: + newValue = new Float + (((Float)value1.value).floatValue() + * ((Float)value2.value).floatValue()); + break; + case opc_fdiv: + newValue = new Float + (((Float)value1.value).floatValue() + / ((Float)value2.value).floatValue()); + break; + case opc_frem: + newValue = new Float + (((Float)value1.value).floatValue() + % ((Float)value2.value).floatValue()); + break; + + case opc_dadd: + newValue = new Double + (((Double)value1.value).doubleValue() + + ((Double)value2.value).doubleValue()); + break; + case opc_dsub: + newValue = new Double + (((Double)value1.value).doubleValue() + - ((Double)value2.value).doubleValue()); + break; + case opc_dmul: + newValue = new Double + (((Double)value1.value).doubleValue() + * ((Double)value2.value).doubleValue()); + break; + case opc_ddiv: + newValue = new Double + (((Double)value1.value).doubleValue() + / ((Double)value2.value).doubleValue()); + break; + case opc_drem: + newValue = new Double + (((Double)value1.value).doubleValue() + % ((Double)value2.value).doubleValue()); + break; + default: + throw new InternalError("Can't happen."); + } + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newValue); + constantInfos.put(instr, constInfo); + result = new ConstValue(newValue); + result.addConstantListener(constInfo); + value1.addConstantListener(result); + value2.addConstantListener(result); + } else + result = unknownValue[size-1]; + return info.poppush(2*size, result); + } + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: { + int size = 1 + (opcode - opc_ineg & 1); + ConstValue value = info.getStack(size); + if (value.value != ConstValue.VOLATILE) { + Object newValue; + switch (opcode) { + case opc_ineg: + newValue = new Integer + (-((Integer)value.value).intValue()); + break; + case opc_lneg: + newValue = new Long + (- ((Long)value.value).longValue()); + break; + case opc_fneg: + newValue = new Float + (- ((Float)value.value).floatValue()); + break; + case opc_dneg: + newValue = new Double + (- ((Double)value.value).doubleValue()); + break; + default: + throw new InternalError("Can't happen."); + } + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newValue); + constantInfos.put(instr, constInfo); + result = new ConstValue(newValue); + result.addConstantListener(constInfo); + value.addConstantListener(result); + } else + result = unknownValue[size-1]; + return info.poppush(size, result); + } + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: { + int size = 1 + (opcode - opc_iadd & 1); + ConstValue value1 = info.getStack(size+1); + ConstValue value2 = info.getStack(1); + if (value1.value != ConstValue.VOLATILE + && value2.value != ConstValue.VOLATILE) { + Object newValue; + switch (opcode) { + case opc_ishl: + newValue = new Integer + (((Integer)value1.value).intValue() + << ((Integer)value2.value).intValue()); + break; + case opc_ishr: + newValue = new Integer + (((Integer)value1.value).intValue() + >> ((Integer)value2.value).intValue()); + break; + case opc_iushr: + newValue = new Integer + (((Integer)value1.value).intValue() + >>> ((Integer)value2.value).intValue()); + break; + + case opc_lshl: + newValue = new Long + (((Long)value1.value).longValue() + << ((Integer)value2.value).intValue()); + break; + case opc_lshr: + newValue = new Long + (((Long)value1.value).longValue() + >> ((Integer)value2.value).intValue()); + break; + case opc_lushr: + newValue = new Long + (((Long)value1.value).longValue() + >>> ((Integer)value2.value).intValue()); + break; + default: + throw new InternalError("Can't happen."); + } + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newValue); + constantInfos.put(instr, constInfo); + result = new ConstValue(newValue); + result.addConstantListener(constInfo); + value1.addConstantListener(result); + value2.addConstantListener(result); + } else + result = unknownValue[size-1]; + return info.poppush(size+1, result); + } + case opc_iinc: { + int slot = instr.getLocalSlot(); + useLocal(slot); + ConstValue local = info.getLocal(slot); + if (local.value != ConstValue.VOLATILE) { + result = new ConstValue + (new Integer(((Integer)local.value).intValue() + + instr.getIncrement())); + local.addConstantListener(result); + } else + result = unknownValue[0]; + return info.setLocal(instr.getLocalSlot(), result); + } + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: { + int insize = 1 + ((opcode - opc_i2l) / 3 & 1); + ConstValue stack = info.getStack(insize); + if (stack.value != ConstValue.VOLATILE) { + Object newVal; + switch(opcode) { + case opc_l2i: case opc_f2i: case opc_d2i: + newVal = new Integer(((Number)stack.value).intValue()); + break; + case opc_i2l: case opc_f2l: case opc_d2l: + newVal = new Long(((Number)stack.value).longValue()); + break; + case opc_i2f: case opc_l2f: case opc_d2f: + newVal = new Float(((Number)stack.value).floatValue()); + break; + case opc_i2d: case opc_l2d: case opc_f2d: + newVal = new Double(((Number)stack.value).doubleValue()); + break; + default: + throw new InternalError("Can't happen."); + } + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newVal); + constantInfos.put(instr, constInfo); + result = new ConstValue(newVal); + result.addConstantListener(constInfo); + stack.addConstantListener(result); + } else { + switch (opcode) { + case opc_i2l: case opc_f2l: case opc_d2l: + case opc_i2d: case opc_l2d: case opc_f2d: + result = unknownValue[1]; + break; + default: + result = unknownValue[0]; + } + } + return info.poppush(insize, result); + } + case opc_i2b: case opc_i2c: case opc_i2s: { + ConstValue stack = info.getStack(1); + if (stack.value != ConstValue.VOLATILE) { + int val = ((Integer)stack.value).intValue(); + switch(opcode) { + case opc_i2b: + val = (byte) val; + break; + case opc_i2c: + val = (char) val; + break; + case opc_i2s: + val = (short) val; + break; + } + Integer newVal = new Integer(val); + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newVal); + constantInfos.put(instr, constInfo); + result = new ConstValue(newVal); + stack.addConstantListener(constInfo); + stack.addConstantListener(result); + } else + result = unknownValue[0]; + return info.poppush(1, result); + } + case opc_lcmp: { + ConstValue val1 = info.getStack(4); + ConstValue val2 = info.getStack(2); + if (val1.value != ConstValue.VOLATILE + && val2.value != ConstValue.VOLATILE) { + long value1 = ((Long) val1.value).longValue(); + long value2 = ((Long) val1.value).longValue(); + Integer newVal = new Integer(value1 == value2 ? 0 + : value1 < value2 ? -1 : 1); + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newVal); + constantInfos.put(instr, constInfo); + result = new ConstValue(newVal); + result.addConstantListener(constInfo); + val1.addConstantListener(result); + val2.addConstantListener(result); + } else + result = unknownValue[0]; + return info.poppush(4, result); + } + case opc_fcmpl: case opc_fcmpg: { + ConstValue val1 = info.getStack(2); + ConstValue val2 = info.getStack(1); + if (val1.value != ConstValue.VOLATILE + && val2.value != ConstValue.VOLATILE) { + float value1 = ((Float) val1.value).floatValue(); + float value2 = ((Float) val1.value).floatValue(); + Integer newVal = new Integer + (value1 == value2 ? 0 + : ( opcode == opc_fcmpg + ? (value1 < value2 ? -1 : 1) + : (value1 > value2 ? 1 : -1))); + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newVal); + constantInfos.put(instr, constInfo); + result = new ConstValue(newVal); + result.addConstantListener(constInfo); + val1.addConstantListener(result); + val2.addConstantListener(result); + } else + result = unknownValue[0]; + return info.poppush(2, result); + } + case opc_dcmpl: case opc_dcmpg: { + ConstValue val1 = info.getStack(4); + ConstValue val2 = info.getStack(2); + if (val1.value != ConstValue.VOLATILE + && val2.value != ConstValue.VOLATILE) { + double value1 = ((Double) val1.value).doubleValue(); + double value2 = ((Double) val1.value).doubleValue(); + Integer newVal = new Integer + (value1 == value2 ? 0 + : ( opcode == opc_dcmpg + ? (value1 < value2 ? -1 : 1) + : (value1 > value2 ? 1 : -1))); + ConstantInfo constInfo = new ConstantInfo(CONSTANT, newVal); + constantInfos.put(instr, constInfo); + result = new ConstValue(newVal); + result.addConstantListener(constInfo); + val1.addConstantListener(result); + val2.addConstantListener(result); + } else + result = unknownValue[0]; + return info.poppush(4, result); + } + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: + case opc_lookupswitch: + case opc_ireturn: case opc_lreturn: + case opc_freturn: case opc_dreturn: case opc_areturn: + case opc_athrow: + case opc_jsr: + case opc_ret: + case opc_goto: + case opc_return: + return info; + + case opc_putstatic: + case opc_putfield: { + final FieldIdentifier fi + = (FieldIdentifier) canonizeReference(instr); + Reference ref = instr.getReference(); + int size = TypeSignature.getTypeSize(ref.getType()); + if (fi != null && !fi.isNotConstant()) { + ConstValue stacktop = info.getStack(size); + Object fieldVal = fi.getConstant(); + if (fieldVal == null) + fieldVal = TypeSignature.getDefaultValue(ref.getType()); + if (stacktop.value == null ? fieldVal == null + : stacktop.value.equals(fieldVal)) { + stacktop.addConstantListener(new ConstantListener() { + public void constantChanged() { + fieldNotConstant(fi); + } + }); + } else { + fieldNotConstant(fi); + } + } + size += (opcode == opc_putstatic) ? 0 : 1; + return info.pop(size); + } + case opc_getstatic: + case opc_getfield: { + int size = (opcode == opc_getstatic) ? 0 : 1; + FieldIdentifier fi = (FieldIdentifier) canonizeReference(instr); + Reference ref = instr.getReference(); + int typesize = TypeSignature.getTypeSize(ref.getType()); + if (fi != null) { + if (fi.isNotConstant()) { + fi.setReachable(); + result = unknownValue[typesize - 1]; + } else { + Object obj = fi.getConstant(); + if (obj == null) + obj = TypeSignature.getDefaultValue(ref.getType()); + ConstantInfo constInfo = new ConstantInfo(CONSTANT, obj); + constantInfos.put(instr, constInfo); + result = new ConstValue(obj); + fi.addFieldListener(methodIdent); + ConstValue prev = (ConstValue) fieldDependencies.get(fi); + if (prev != null) + prev.addConstantListener(result); + else + fieldDependencies.put(fi, result); + } + } else + result = unknownValue[typesize - 1]; + return info.poppush(size, result); + } + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: { + canonizeReference(instr); + Reference ref = instr.getReference(); + boolean constant = true; + int size = 0; + Object cls = null; + String[] paramTypes + = TypeSignature.getParameterTypes(ref.getType()); + Object[] args = new Object[paramTypes.length]; + ConstValue clsValue = null; + ConstValue[] argValues = new ConstValue[paramTypes.length]; + + for (int i = paramTypes.length - 1; i >= 0; i--) { + size += TypeSignature.getTypeSize(paramTypes[i]); + Object value = (argValues[i] = info.getStack(size)).value; + if (value != ConstValue.VOLATILE) + args[i] = value; + else + constant = false; + } + + if (opcode != opc_invokestatic) { + size++; + clsValue = info.getStack(size); + cls = clsValue.value; + if (cls == ConstValue.VOLATILE || cls == null) + constant = false; + } + String retType = TypeSignature.getReturnType(ref.getType()); + if (retType.equals("V")) { + handleReference(ref, opcode == opc_invokevirtual + || opcode == opc_invokeinterface); + return info.pop(size); + } + if (constant && !runtime.isWhite(retType)) { + /* This is not a valid constant type */ + constant = false; + } + Object methodResult = null; + if (constant) { + try { + methodResult = runtime.invokeMethod + (ref, opcode != opc_invokespecial, cls, args); + } catch (InterpreterException ex) { + constant = false; + if (net.sf.jode.GlobalOptions.verboseLevel > 3) + GlobalOptions.err.println("Can't interpret "+ref+": " + + ex.getMessage()); + /* result is not constant */ + } catch (InvocationTargetException ex) { + constant = false; + if (net.sf.jode.GlobalOptions.verboseLevel > 3) + GlobalOptions.err.println("Method "+ref + +" throwed exception: " + + ex.getTargetException()); + /* method always throws exception ? */ + } + } + ConstValue returnVal; + if (!constant) { + handleReference(ref, opcode == opc_invokevirtual + || opcode == opc_invokeinterface); + int retsize = TypeSignature.getTypeSize(retType); + returnVal = unknownValue[retsize - 1]; + } else { + ConstantInfo constInfo = + new ConstantInfo(CONSTANT, methodResult); + constantInfos.put(instr, constInfo); + returnVal = new ConstValue(methodResult); + returnVal.addConstantListener(constInfo); + if (clsValue != null) + clsValue.addConstantListener(returnVal); + for (int i=0; i< argValues.length; i++) + argValues[i].addConstantListener(returnVal); + } + return info.poppush(size, returnVal); + } + + case opc_new: { + handleClass(instr.getClazzType()); + return info.poppush(0, unknownValue[0]); + } + case opc_arraylength: { + return info.poppush(1, unknownValue[0]); + } + case opc_checkcast: { + handleClass(instr.getClazzType()); + return info.pop(0); + } + case opc_instanceof: { + handleClass(instr.getClazzType()); + return info.poppush(1, unknownValue[0]); + } + case opc_monitorenter: + case opc_monitorexit: + return info.pop(1); + case opc_multianewarray: + handleClass(instr.getClazzType()); + return info.poppush(instr.getDimensions(), unknownValue[0]); + default: + throw new IllegalArgumentException("Invalid opcode "+opcode); + } + } + + public void analyze() { + StackLocalInfo info = before.copy(); + Handler[] handlers = block.getHandlers(); + + if (handlers.length > 0) { + ConstValue[] newStack = new ConstValue[info.stack.length]; + newStack[0] = unknownValue[0]; + StackLocalInfo catchInfo = + new StackLocalInfo(newStack, info.locals, 1); + + for (int i=0; i< handlers.length; i++) { + if (handlers[i].getType() != null) + Main.getClassBundle().reachableClass + (handlers[i].getType()); + + infos[handlers[i].getCatcher().getBlockNr()] + .mergeBefore(catchInfo, jsrInfo, usedLocals); + } + } + + Instruction[] instrs = block.getInstructions(); + for (int idx = 0 ; idx < instrs.length; idx++) { + Instruction instr = instrs[idx]; + info = handleOpcode(instr, info); + if (instr.isStore() && handlers.length > 0) { + int slot = instr.getLocalSlot(); + ConstValue newValue = info.locals[slot]; + for (int i=0; i< handlers.length; i++) { + infos[handlers[i].getCatcher().getBlockNr()] + .mergeOneLocal(slot, info.locals[slot]); + if (newValue.stackSize > 1) + infos[handlers[i].getCatcher().getBlockNr()] + .mergeOneLocal(slot+1, info.locals[slot+1]); + } + } + } + after = info; + propagateAfter(); + } + + public void dumpInfo(PrintWriter output) { + output.println("/-["+nr+"]-"+before); + if (constantFlow >= 0) + output.println("| constantFlow: "+constantFlow); + if (jsrInfo != null) + output.println("| used: "+usedLocals+" JSR: "+jsrInfo); + block.dumpCode(output); + output.println("\\-["+nr+"]-"+after); + } + + public String toString() { + return "BlockAnalyzer["+nr+"]"; + } + } + + + /** + * The TodoQueue is a linked list of BlockInfo + * + * There is only one TodoQueue, the modifiedQueue in analyzeCode + * + * The queue operations are in StackLocalInfo. + */ + static class TodoQueue { + BlockInfo first; + + public void enqueue(BlockInfo info) { + if (info.nextTodo == null) { + info.nextTodo = first; + first = info; + } + } + + public BlockInfo dequeue() { + BlockInfo result = first; + if (result != null) { + first = result.nextTodo; + result.nextTodo = null; + } + return result; + } + } + + public void fieldNotConstant(FieldIdentifier fi) { + ConstValue value = (ConstValue) fieldDependencies.remove(fi); + if (value != null) + value.constantChanged(); + fi.removeFieldListener(methodIdent); + fi.setNotConstant(); + } + + void handleReference(Reference ref, boolean isVirtual) { + Main.getClassBundle().reachableReference(ref, isVirtual); + } + + void handleClass(String clName) { + int i = 0; + while (i < clName.length() && clName.charAt(i) == '[') + i++; + if (i < clName.length() && clName.charAt(i) == 'L') { + clName = clName.substring(i+1, clName.length()-1); + Main.getClassBundle().reachableClass(clName); + } + } + + public ConstantAnalyzer() { + } + + + public void dumpBlockInfo(PrintWriter output) { + for (int i=0; i < infos.length; i++) + infos[i].dumpInfo(output); + } + + public void analyzeCode(MethodIdentifier methodIdent, BasicBlocks bb) { + Block[] blocks = bb.getBlocks(); + this.methodIdent = methodIdent; + this.bb = bb; + this.infos = new BlockInfo[blocks.length]; + this.fieldDependencies = new HashMap(); + + for (int i=0; i< infos.length; i++) + infos[i] = new BlockInfo(i, blocks[i]); + + Block startBlock = bb.getStartBlock(); + if (startBlock != null) { + MethodInfo minfo = bb.getMethodInfo(); + infos[startBlock.getBlockNr()].mergeBefore + (new StackLocalInfo(bb.getMaxStack(), bb.getMaxLocals(), + minfo.isStatic(), minfo.getType()), + null, null); + + BlockInfo info; + while ((info = modifiedQueue.dequeue()) != null) { +// dumpBlockInfo(GlobalOptions.err); +// GlobalOptions.err.println("Analyzing: "+info); + info.analyze(); + } + } + +// GlobalOptions.err.println("After Analyze"); +// dumpBlockInfo(GlobalOptions.err); + + BitSet reachableBlocks = new BitSet(); + for (int i=0; i< infos.length; i++) { + if (infos[i].isReachable()) + reachableBlocks.set(i); + } + bbInfos.put(bb, reachableBlocks); + + this.methodIdent = null; + this.bb = null; + this.infos = null; + this.fieldDependencies = null; + } + + public static void replaceWith(ArrayList newCode, Instruction instr, + Instruction replacement) { + switch(instr.getOpcode()) { + case opc_jsr: + newCode.add(Instruction.forOpcode(opc_ldc, (Object) null)); + break; + case opc_ldc: + case opc_ldc2_w: + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: + case opc_getstatic: + if (replacement != null) + newCode.add(replacement); + return; + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_ifnull: case opc_ifnonnull: + case opc_arraylength: + case opc_lookupswitch: + case opc_getfield: + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_ineg: case opc_fneg: + newCode.add(Instruction.forOpcode(opc_pop)); + break; + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_lcmp: + case opc_dcmpg: case opc_dcmpl: + case opc_ladd: case opc_dadd: + case opc_lsub: case opc_dsub: + case opc_lmul: case opc_dmul: + case opc_ldiv: case opc_ddiv: + case opc_lrem: case opc_drem: + case opc_land: case opc_lor : case opc_lxor: + newCode.add(Instruction.forOpcode(opc_pop2)); + /* fall through */ + case opc_fcmpg: case opc_fcmpl: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_d2i: case opc_d2l: case opc_d2f: + case opc_lneg: case opc_dneg: + case opc_iadd: case opc_fadd: + case opc_isub: case opc_fsub: + case opc_imul: case opc_fmul: + case opc_idiv: case opc_fdiv: + case opc_irem: case opc_frem: + case opc_iand: case opc_ior : case opc_ixor: + case opc_ishl: case opc_ishr: case opc_iushr: + case opc_iaload: case opc_laload: + case opc_faload: case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + newCode.add(Instruction.forOpcode(opc_pop2)); + break; + + case opc_lshl: case opc_lshr: case opc_lushr: + newCode.add(Instruction.forOpcode(opc_pop)); + newCode.add(Instruction.forOpcode(opc_pop2)); + break; + case opc_putstatic: + case opc_putfield: + if (TypeSignature + .getTypeSize(instr.getReference().getType()) == 2) { + newCode.add(Instruction.forOpcode(opc_pop2)); + if (instr.getOpcode() == opc_putfield) + newCode.add(Instruction.forOpcode(opc_pop)); + } else + newCode.add(Instruction.forOpcode(instr.getOpcode() + == opc_putfield + ? opc_pop2 : opc_pop)); + break; + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: { + Reference ref = instr.getReference(); + String[] pt = TypeSignature.getParameterTypes(ref.getType()); + int arg = 0; + + int i = pt.length; + while (i > 0) + newCode.add(Instruction.forOpcode(TypeSignature + .getTypeSize(pt[--i]) + + opc_pop - 1)); + + if (instr.getOpcode() != opc_invokestatic) + newCode.add(Instruction.forOpcode(opc_pop)); + break; + } + default: + throw new InternalError("Unexpected opcode"); + } + if (replacement != null) + newCode.add(replacement); + } + + public void transformCode(BasicBlocks bb) { + BitSet reachable = (BitSet) bbInfos.remove(bb); + Block[] blocks = bb.getBlocks(); + Handler[] handlers = bb.getExceptionHandlers(); + + Block newStartBlock = bb.getStartBlock(); + int newBlockCtr = 0; + int newHandlerCtr = 0; + next_handler: + for (int i = 0; i < handlers.length; i++) { + int start = handlers[i].getStart().getBlockNr(); + int end = handlers[i].getEnd().getBlockNr(); + while (!reachable.get(end)) { + if (start == end) + /* handler not reachable, check next one. */ + continue next_handler; + start++; + } + while (!reachable.get(end)) { + end--; + } + handlers[i].setStart(blocks[start]); + handlers[i].setEnd(blocks[start]); + /* Catcher is always reachable */ + handlers[newHandlerCtr++] = handlers[i]; + } + for (int i=0; i < blocks.length; i++) { + if (!reachable.get(i)) + continue; + blocks[newBlockCtr] = blocks[i]; + Instruction[] oldCode = blocks[i].getInstructions(); + Block[] succs = blocks[i].getSuccs(); + ArrayList newCode = new ArrayList(oldCode.length); + for (int idx = 0; idx < oldCode.length; idx++) { + Instruction instr = oldCode[idx]; + ConstantInfo info = (ConstantInfo) constantInfos.remove(instr); + if (info != null && (info.flags & CONSTANT) != 0) { + Instruction ldcInstr = Instruction.forOpcode + (info.constant instanceof Long + || info.constant instanceof Double + ? opc_ldc2_w : opc_ldc, info.constant); + if (GlobalOptions.verboseLevel > 2) + GlobalOptions.err.println + (bb + ": Replacing " + instr + + " with constant " + info.constant); + replaceWith(newCode, instr, ldcInstr); + } else if (info != null && (info.flags & CONSTANTFLOW) != 0) { + int succnr = ((Integer)info.constant).intValue(); + replaceWith(newCode, instr, null); + if (GlobalOptions.verboseLevel > 2) + GlobalOptions.err.println + (bb + ": Removing " + instr); + succs = new Block[] { succs[succnr] }; + } else { + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_nop: + break; + + case opc_putstatic: + case opc_putfield: { + Reference ref = instr.getReference(); + FieldIdentifier fi = (FieldIdentifier) + Main.getClassBundle().getIdentifier(ref); + if (fi != null + && (Main.stripping & Main.STRIP_UNREACH) != 0 + && !fi.isReachable()) { + replaceWith(newCode, instr, null); + break; + } + /* fall through */ + } + default: + newCode.add(instr); + } + } + } + blocks[i].setCode((Instruction[]) newCode.toArray(new Instruction[newCode.size()]), succs); + newBlockCtr++; + } + if (newBlockCtr < blocks.length) { + Block[] newBlocks = new Block[newBlockCtr]; + System.arraycopy(blocks, 0, newBlocks, 0, newBlockCtr); + blocks = newBlocks; + } + if (newHandlerCtr < handlers.length) { + Handler[] newHandlers = new Handler[newHandlerCtr]; + System.arraycopy(handlers, 0, newHandlers, 0, newHandlerCtr); + handlers = newHandlers; + } + bb.setBlocks(blocks, newStartBlock, handlers); + } +} diff --git a/jode/src/net/sf/jode/obfuscator/modules/IdentityRenamer.java b/jode/src/net/sf/jode/obfuscator/modules/IdentityRenamer.java new file mode 100644 index 0000000..9097d90 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/IdentityRenamer.java @@ -0,0 +1,50 @@ +/* IdentityRenamer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import net.sf.jode.obfuscator.Renamer; +import net.sf.jode.obfuscator.Identifier; +///#def COLLECTIONS java.util +import java.util.Iterator; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +public class IdentityRenamer implements Renamer { + public Iterator generateNames(Identifier ident) { + final String base = ident.getName(); + return new Iterator() { + int last = 0; + + public boolean hasNext() { + return true; + } + + public Object next() { + return (last++ == 0 ? base : base + last); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} + diff --git a/jode/src/net/sf/jode/obfuscator/modules/KeywordRenamer.java b/jode/src/net/sf/jode/obfuscator/modules/KeywordRenamer.java new file mode 100644 index 0000000..9fcecb7 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/KeywordRenamer.java @@ -0,0 +1,84 @@ +/* KeywordRenamer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import net.sf.jode.obfuscator.*; +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Iterator; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +public class KeywordRenamer implements Renamer, OptionHandler { + String keywords[]; + Renamer backup; + + public KeywordRenamer() { + keywords = new String[] { + "if", "else", "for", "while", "throw", "return", + "class", "interface", "implements", "extends", + "instanceof", "new", + "int", "boolean", "long", "float", "double", "short", + "public", "protected", "private", + "static", "synchronized", "strict", "transient", "abstract", + "volatile", "final", + /* Not really keywords, but very confusing anyway. */ + "Object", "String", "Thread", "Runnable", "StringBuffer", "Vector" + }; + backup = new StrongRenamer(); + } + + public void setOption(String option, Collection values) { + if (option.startsWith("keywords")) { + keywords = (String[]) values.toArray(new String[values.size()]); + } else if (option.startsWith("backup")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one backup is allowed"); + backup = (Renamer) values.iterator().next(); + } else + throw new IllegalArgumentException("Invalid option `"+option+"'"); + } + + public Iterator generateNames(final Identifier ident) { + return new Iterator() { + int pos = 0; + Iterator backing = null; + + public boolean hasNext() { + return true; + } + public Object next() { + if (pos < keywords.length) + return keywords[pos++]; + + if (backing == null) + backing = backup.generateNames(ident); + + return backing.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/jode/src/net/sf/jode/obfuscator/modules/LocalOptimizer.java b/jode/src/net/sf/jode/obfuscator/modules/LocalOptimizer.java new file mode 100644 index 0000000..4d185b0 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/LocalOptimizer.java @@ -0,0 +1,789 @@ +/* LocalOptimizer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import java.util.*; +import net.sf.jode.bytecode.*; +import net.sf.jode.obfuscator.*; +import net.sf.jode.GlobalOptions; + +///#def COLLECTIONS java.util +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +///#enddef + +/** + * This class takes some bytecode and tries to minimize the number + * of locals used. It will also remove unnecessary stores.
    + * + * This class can only work on verified code. There should also be no + * dead code, since the verifier doesn't check that dead code behaves + * okay.
    + * + * This is done in two phases. First we determine which locals are + * the same, and which locals have a overlapping life time. In the + * second phase we will then redistribute the locals with a coloring + * graph algorithm.
    + * + * The idea for the first phase is: For each read we follow the + * instruction flow backward to find the corresponding writes. We can + * also merge with another control flow that has a different read, in + * this case we merge with that read, too.
    + * + * The tricky part is the subroutine handling. We follow the local + * that is used in a ret and find the corresponding jsr target (there + * must be only one, if the verifier should accept this class). While + * we do this we remember in the info of the ret, which locals are + * used in that subroutine.
    + * + * When we know the jsr target<->ret correlation, we promote from the + * nextByAddr of every jsr the locals that are accessed by the + * subroutine to the corresponding ret and the others to the jsr. Also + * we will promote all reads from the jsr targets to the jsr.
    + * + * If you think this might be to complicated, keep in mind that jsr's + * are not only left by the ret instructions, but also "spontanously" + * (by not reading the return address again).
    + */ +public class LocalOptimizer implements Opcodes, CodeTransformer { + + /** + * This class keeps track for each local variables: + *
      + *
    • which name and type this local has + * (if there is a local variable table),
    • + *
    • which other locals must be the same,
    • + *
    • which other locals have an intersecting life time.
    • + *
    + */ + class LocalInfo { + LocalInfo shadow = null; + + public LocalInfo getReal() { + LocalInfo real = this; + while (real.shadow != null) + real = real.shadow; + return real; + } + + String name; + String type; + Vector conflictingLocals = new Vector(); + String id; // for debugging purposes only. + int size; + int newSlot = -1; + + LocalInfo() { + } + + void conflictsWith(LocalInfo l) { + if (shadow != null) { + getReal().conflictsWith(l); + } else { + l = l.getReal(); + if (!conflictingLocals.contains(l)) { + conflictingLocals.addElement(l); + l.conflictingLocals.addElement(this); + } + } + } + + void combineInto(LocalInfo l) { + if (shadow != null) { + getReal().combineInto(l); + return; + } + l = l.getReal(); + if (this == l) + return; + shadow = l; + if (l.name == null) { + l.name = name; + l.type = type; + } + if (id.compareTo(l.id) < 0) + l.id = id; + } + + void generateID(int blockNr, int instrNr) { + char[] space = new char[5]; + space[0] = (char) ('0' + blockNr / 10); + space[1] = (char) ('0' + blockNr % 10); + space[2] = (char) ('a' + instrNr / (26*26)); + space[3] = (char) ('a' + (instrNr / 26) % 26); + space[4] = (char) ('a' + instrNr % 26); + id = new String(space); + } + + public String toString() { + return id; + } + } + + private static class TodoQueue { + BlockInfo first = null; + BlockInfo last = null; + + public void add(BlockInfo info) { + if (info.nextTodo == null && info != last) { + /* only enqueue if not already on queue */ + info.nextTodo = first; + first = info; + if (first == null) + last = info; + } + } + + public boolean isEmpty() { + return first == null; + } + + public BlockInfo remove() { + BlockInfo result = first; + first = result.nextTodo; + result.nextTodo = null; + if (first == null) + last = null; + return result; + } + } + + BasicBlocks bb; + + TodoQueue changedInfos; + InstrInfo firstInfo; + LocalInfo[] paramLocals; + BlockInfo[] blockInfos; + int maxlocals; + + /** + * This class contains information for each basic block. + */ + class BlockInfo { + /** + * The next Instruction in the todo list; null, if this block + * info is not on todo list, or if it is the last one. + */ + BlockInfo nextTodo; + + /** + * The local infos for each Instruction. The index is the + * instruction number. + */ + LocalInfo[] instrLocals; + + /** + * The LocalInfo, whose values are read from a previous block. + * Index is the slot number. + */ + LocalInfo[] ins; + + /** + * The LocalInfo, written in this block. + * Index is the slot number. + */ + LocalInfo[] gens; + + /** + * The predecessors for this block. + */ + Collection preds = new ArrayList(); + + /** + * The tryBlocks, for which this block is the catcher. + */ + Collection tryBlocks = new ArrayList(); + + /** + * For each slot, this contains the InstrInfo of one of the + * next Instruction, that may read from that slot, without + * prior writing. + */ + InstrInfo[] nextReads; + + /** + * This only has a value for blocks countaining a ret. In + * that case this bitset contains all locals, that may be used + * between jsr and ret. + */ + BitSet usedBySub; + /** + * For each slot if get() is true, no instruction may read + * this slot, since it may contain different locals, depending + * on flow. + */ + LocalInfo[] lifeLocals; + /** + * If instruction is the destination of a jsr, this contains + * the single allowed ret instruction info, or null if there + * is no ret at all (or not yet detected). + */ + InstrInfo retInfo; + /** + * If this instruction is a ret, this contains the single + * allowed jsr target to which this ret belongs. + */ + BlockInfo jsrTargetInfo; + /** + * The underlying basic block. + */ + Block block; + + + public BlockInfo(int blockNr, Block block) { + this.block = block; + ins = new LocalInfo[bb.getMaxLocals()]; + gens = new LocalInfo[bb.getMaxLocals()]; + Instruction[] instrs = block.getInstructions(); + instrLocals = new LocalInfo[instrs.length]; + for (int instrNr = 0; instrNr < instrs.length; instrNr++) { + Instruction instr = instrs[instrNr]; + if (instr.hasLocal()) { + int slot = instr.getLocalSlot(); + LocalInfo local = new LocalInfo(); + instrLocals[instrNr] = local; + LocalVariableInfo lvi = instr.getLocalInfo(); + local.name = lvi.getName(); + local.type = lvi.getType(); + local.size = 1; + local.generateID(blockNr, instrNr); + switch (instr.getOpcode()) { + case opc_lload: case opc_dload: + local.size = 2; + /* fall through */ + case opc_iload: case opc_fload: case opc_aload: + case opc_iinc: + /* this is a load instruction */ + if (gens[slot] == null) { + ins[slot] = local; + gens[slot] = local; + changedInfos.add(this); + } else { + gens[slot].combineInto(local); + } + break; + + case opc_ret: + /* this is a ret instruction */ + usedBySub = new BitSet(); + if (gens[slot] == null) { + ins[slot] = local; + gens[slot] = local; + changedInfos.add(this); + } else { + gens[slot].combineInto(local); + } + break; + + case opc_lstore: case opc_dstore: + local.size = 2; + /* fall through */ + case opc_istore: case opc_fstore: case opc_astore: + gens[slot] = local; + break; + + default: + throw new InternalError + ("Illegal opcode for SlotInstruction"); + } + } + } + } + + + void promoteIn(int slot, LocalInfo local) { + if (gens[slot] == null) { + changedInfos.add(this); + gens[slot] = local; + ins[slot] = local; + } else { + gens[slot].combineInto(local); + } + } + + void promoteInForTry(int slot, LocalInfo local) { + if (ins[slot] == null) { + gens[slot] = local; + ins[slot] = local; + changedInfos.add(this); + } else + ins[slot].combineInto(local); + + if (gens[slot] != null) { + gens[slot].combineInto(local); + for (int i=0; i< instrLocals.length; i++) { + if (instrLocals[i] != null + && block.getInstructions()[i].getLocalSlot() == slot) + instrLocals[i].combineInto(local); + } + } + } + + public void promoteIns() { + for (int i=0; i < ins.length; i++) { + if (ins[i] != null) { + for (Iterator iter = preds.iterator(); iter.hasNext();) { + BlockInfo pred = (BlockInfo) iter.next(); + pred.promoteIn(i, ins[i]); + } + + for (Iterator iter = tryBlocks.iterator(); + iter.hasNext();) { + BlockInfo pred = (BlockInfo) iter.next(); + pred.promoteInForTry(i, ins[i]); + } + } + } +// if (prevInstr.getOpcode() == opc_jsr) { +// /* Prev instr is a jsr, promote reads to the +// * corresponding ret. +// */ +// InstrInfo jsrInfo = +// (InstrInfo) instrInfos.get(prevInstr.getSingleSucc()); +// if (jsrInfo.retInfo != null) { +// /* Now promote reads that are modified by the +// * subroutine to the ret, and those that are not +// * to the jsr instruction. +// */ +// promoteReads(info, jsrInfo.retInfo.instr, +// jsrInfo.retInfo.usedBySub, false); +// promoteReads(info, prevInstr, +// jsrInfo.retInfo.usedBySub, true); +// } +// } + } + + public void generateConflicts() { + LocalInfo[] active = (LocalInfo[]) ins.clone(); + Instruction[] instrs = block.getInstructions(); + for (int instrNr = 0; instrNr < instrs.length; instrNr++) { + Instruction instr = instrs[instrNr]; + if (instr.isStore()) { + /* This is a store. It conflicts with every local, which + * is active at this point. + */ + for (int i=0; i < maxlocals; i++) { + if (i != instr.getLocalSlot() + && active[i] != null) + instrLocals[instrNr].conflictsWith(active[i]); + + + if (info.nextInfo.nextReads[i] != null + && info.nextInfo.nextReads[i].jsrTargetInfo != null) { + Instruction[] jsrs = info.nextInfo.nextReads[i] + .jsrTargetInfo.instr.getPreds(); + for (int j=0; j< jsrs.length; j++) { + InstrInfo jsrInfo + = (InstrInfo) instrInfos.get(jsrs[j]); + for (int k=0; k < maxlocals; k++) { + if (!info.nextInfo.nextReads[i].usedBySub + .get(k) + && jsrInfo.nextReads[k] != null) + info.local.conflictsWith + (jsrInfo.nextReads[k].local); + } + } + } + } + } + } + } + } + + public LocalOptimizer() { + } + + + /** + * Merges the given vector to a new vector. Both vectors may + * be null in which case they are interpreted as empty vectors. + * The vectors will never changed, but the result may be one + * of the given vectors. + */ + Vector merge(Vector v1, Vector v2) { + if (v1 == null || v1.isEmpty()) + return v2; + if (v2 == null || v2.isEmpty()) + return v1; + Vector result = (Vector) v1.clone(); + Enumeration enumeration = v2.elements(); + while (enumeration.hasMoreElements()) { + Object elem = enumeration.nextElement(); + if (!result.contains(elem)) + result.addElement(elem); + } + return result; + } + + public void calcLocalInfo() { + maxlocals = bb.getMaxLocals(); + Block[] blocks = bb.getBlocks(); + + /* Initialize paramLocals */ + { + String methodType = bb.getMethodInfo().getType(); + int paramCount = (bb.getMethodInfo().isStatic() ? 0 : 1) + + TypeSignature.getArgumentSize(methodType); + paramLocals = new LocalInfo[paramCount]; + int slot = 0; + if (!bb.getMethodInfo().isStatic()) { + LocalInfo local = new LocalInfo(); + LocalVariableInfo lvi = bb.getParamInfo(slot); + local.type = "L" + (bb.getMethodInfo().getClazzInfo() + .getName().replace('.', '/'))+";"; + if (local.type.equals(lvi.getType())) + local.name = lvi.getName(); + local.size = 1; + local.id = " this"; + paramLocals[slot++] = local; + } + int pos = 1; + while (pos < methodType.length() + && methodType.charAt(pos) != ')') { + int start = pos; + pos = TypeSignature.skipType(methodType, pos); + + LocalInfo local = new LocalInfo(); + LocalVariableInfo lvi = bb.getParamInfo(slot); + local.type = methodType.substring(start, pos); + if (local.type.equals(lvi.getType())) + local.name = lvi.getName(); + local.size = TypeSignature.getTypeSize(local.type); + local.id = " parm"; + paramLocals[slot] = local; + slot += local.size; + } + } + + /* Initialize the InstrInfos and LocalInfos + */ + changedInfos = new TodoQueue(); + blockInfos = new BlockInfo[blocks.length]; + for (int i=0; i< blocks.length; i++) + blockInfos[i] = new BlockInfo(i, blocks[i]); + + for (int i=0; i< blocks.length; i++) { + int[] succs = blocks[i].getSuccs(); + for (int j=0; j< succs.length; j++) { + if (succs[j] >= 0) + blockInfos[succs[j]].preds.add(blockInfos[i]); + } + BasicBlocks.Handler[] handlers = blocks[i].getCatcher(); + for (int j=0; j< handlers.length; j++) { + blockInfos[handlers[j].getCatcher()] + .tryBlocks.add(blockInfos[i]); + } + } + + /* find out which locals are the same. + */ + while (!changedInfos.isEmpty()) { + BlockInfo info = changedInfos.remove(); + info.promoteIns(); + + if (instr.getPreds() != null) { + for (int i = 0; i < instr.getPreds().length; i++) { + Instruction predInstr = instr.getPreds()[i]; + if (instr.getPreds()[i].getOpcode() == opc_jsr) { + /* This is the target of a jsr instr. + */ + if (info.instr.getOpcode() != opc_astore) { + /* XXX Grrr, the bytecode verifier doesn't + * test if a jsr starts with astore. So + * it is possible to do something else + * before putting the ret address into a + * local. */ + throw new InternalError("Non standard jsr"); + } + InstrInfo retInfo = info.nextInfo.nextReads + [info.instr.getLocalSlot()]; + + if (retInfo != null) { + if (retInfo.instr.getOpcode() != opc_ret) + throw new InternalError + ("reading return address"); + + info.retInfo = retInfo; + retInfo.jsrTargetInfo = info; + + /* Now promote reads from the instruction + * after the jsr to the ret instruction if + * they are modified by the subroutine, + * and to the jsr instruction otherwise. + */ + Instruction nextInstr = predInstr.getNextByAddr(); + InstrInfo nextInfo + = (InstrInfo) instrInfos.get(nextInstr); + + promoteReads(nextInfo, retInfo.instr, + retInfo.usedBySub, false); + + promoteReads(nextInfo, predInstr, + retInfo.usedBySub, true); + } + } + promoteReads(info, instr.getPreds()[i]); + } + } + } + changedInfos = null; + + /* Now merge with the parameters + * The params should be the locals in firstInfo.nextReads + */ + int startBlock = bb.getStartBlock(); + if (startBlock >= 0) { + LocalInfo[] ins = blockInfos[startBlock].ins; + for (int i=0; i< paramLocals.length; i++) { + if (ins[i] != null) + paramLocals[i].combineInto(ins[i]); + } + } + } + + public void stripLocals() { + Block[] blocks = bb.getBlocks(); + for (int i = 0; i < blocks.length; i++) { + Instruction[] instrs = blocks[i].getInstructions(); + for (int j = 0; j < instrs.length; j++) { + Instruction instr = instrs[j]; + if (info.local != null + && info.local.usingBlocks.size() == 1) { + /* If this is a store, whose value is never read; it can + * be removed, i.e replaced by a pop. + */ + switch (instr.getOpcode()) { + case opc_istore: + case opc_fstore: + case opc_astore: + instrs[j] = Instruction.forOpcode(opc_pop); + break; + case opc_lstore: + case opc_dstore: + instrs[j] = Instruction.forOpcode(opc_pop2); + break; + default: + } + } + } + } + } + + void distributeLocals(Vector locals) { + if (locals.size() == 0) + return; + + /* Find the local with the least conflicts. */ + int min = Integer.MAX_VALUE; + LocalInfo bestLocal = null; + Enumeration enumeration = locals.elements(); + while (enumeration.hasMoreElements()) { + LocalInfo li = (LocalInfo) enumeration.nextElement(); + int conflicts = 0; + Enumeration conflenum = li.conflictingLocals.elements(); + while (conflenum.hasMoreElements()) { + if (((LocalInfo)conflenum.nextElement()).newSlot != -2) + conflicts++; + } + if (conflicts < min) { + min = conflicts; + bestLocal = li; + } + } + /* Mark the local as taken */ + locals.removeElement(bestLocal); + bestLocal.newSlot = -2; + /* Now distribute the remaining locals recursively. */ + distributeLocals(locals); + + /* Finally find a new slot */ + next_slot: + for (int slot = 0; ; slot++) { + Enumeration conflenum = bestLocal.conflictingLocals.elements(); + while (conflenum.hasMoreElements()) { + LocalInfo conflLocal = (LocalInfo)conflenum.nextElement(); + if (bestLocal.size == 2 && conflLocal.newSlot == slot+1) { + slot++; + continue next_slot; + } + if (conflLocal.size == 2 && conflLocal.newSlot+1 == slot) + continue next_slot; + if (conflLocal.newSlot == slot) { + if (conflLocal.size == 2) + slot++; + continue next_slot; + } + } + bestLocal.newSlot = slot; + break; + } + } + + public void distributeLocals() { + /* give locals new slots. This is a graph coloring + * algorithm (the optimal solution is NP complete, but this + * should be a good approximation). + */ + + /* first give the params the same slot as they had before. + */ + for (int i=0; i maxlocals) + maxlocals = info.local.newSlot + info.local.size; + info.instr.setLocalSlot(info.local.newSlot); + } + } + bc.setMaxLocals(maxlocals); + } + + private InstrInfo CONFLICT = new InstrInfo(); + + boolean promoteLifeLocals(LocalInfo[] newLife, InstrInfo nextInfo) { + if (nextInfo.lifeLocals == null) { + nextInfo.lifeLocals = (LocalInfo[]) newLife.clone(); + return true; + } + boolean changed = false; + for (int i=0; i< maxlocals; i++) { + LocalInfo local = nextInfo.lifeLocals[i]; + if (local == null) + /* A conflict has already happened, or this slot + * may not have been initialized. */ + continue; + + local = local.getReal(); + LocalInfo newLocal = newLife[i]; + if (newLocal != null) + newLocal = newLocal.getReal(); + if (local != newLocal) { + nextInfo.lifeLocals[i] = null; + changed = true; + } + } + return changed; + } + + public void dumpLocals() { + Vector locals = new Vector(); + for (int blockNr=0; blockNr < blockInfos.length; blockNr++) { + BlockInfo info = blockInfos[blockNr]; + GlobalOptions.err.print("ins: ["); + for (int i=0; i 0) + i.next(); + return (String) i.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public final Collection getCollection(Identifier ident) { + if (ident instanceof PackageIdentifier) + return packs; + else if (ident instanceof ClassIdentifier) + return clazzes; + else if (ident instanceof MethodIdentifier) + return methods; + else if (ident instanceof FieldIdentifier) + return fields; + else if (ident instanceof LocalIdentifier) + return locals; + else + throw new IllegalArgumentException(ident.getClass().getName()); + } + + public final void addIdentifierName(Identifier ident) { + getCollection(ident).add(ident.getName()); + } + + public Iterator generateNames(Identifier ident) { + return new NameGenerator(getCollection(ident)); + } +} + + diff --git a/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java b/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java new file mode 100644 index 0000000..bf0ad71 --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/RemovePopAnalyzer.java @@ -0,0 +1,423 @@ +/* RemovePopAnalyzer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import net.sf.jode.bytecode.*; +import net.sf.jode.obfuscator.*; +import net.sf.jode.GlobalOptions; + +import java.util.BitSet; +///#def COLLECTIONS java.util +import java.util.ListIterator; +import java.util.LinkedList; +import java.util.Stack; +///#enddef + +public class RemovePopAnalyzer implements CodeTransformer, Opcodes { + public RemovePopAnalyzer() { + } + + static class BlockInfo { + /* A bitset of stack entries at the beginning of the block, + * whose values should be never put put onto the stack. + * This array is shared with all other blocks that have + * a common predecessor. + */ + int[] poppedBefore; + + /* A bitset of instructions, that should be removed, i.e. their + * parameters should just get popped. + */ + int[] removedInstrs; + + ArrayList predecessors; + + BlockInfo(int[] popped, int[] removed) { + this.poppedEntries = popped; + this.removedInstrs = removed; + } + + boolean isPopped(int pos) { + return (poppedEntries[pos >> 5] & (1 << (pos & 31))) != 0; + } + + boolean isRemoved(int pos) { + return (removedInstrs[pos >> 5] & (1 << (pos & 31))) != 0; + } + } + + public BlockInfo analyzeBlock(Block block, BlockInfo oldInfo) { + } + + + /** + * This method propagates pops through a dup instruction, eventually + * generating new code and a new set of popped instructions. + * + * @param opcode the opcode of the dup instruction. + * @param newInstruction a list where the new instructions should + * be added to the front. + * @param stackDepth the stack depth after the dup is executed. + * @param poppedEntries the stack slots that should be popped at + * the end of this dup. + * @return The stack slots that should be popped at the start of + * the dup. + */ + byte movePopsThroughDup(int opcode, + List newInstructions, + int poppedEntries) { + int count = (opcode - opc_dup)/3+1; + int depth = (opcode - opc_dup)%3; + + /* Calculate which entries can be popped before this instruction, + * and update opcode. + */ + int newPopped = + (((poppedEntries + 1) << depth) - 1) & (poppedEntries >> count); + int mask = ((1 << count) - 1); + int poppedDeep = poppedEntries & mask; + int poppedTop = (poppedEntries >> (depth + count)) & mask; + boolean swapAfterDup = false; + boolean swapBeforeDup = false; + + for (int i = count+depth; i > depth; i--) { + if ((newPopped & (1 << i)) != 0) + depth--; + } + + // adjust the depth + for (int i = depth; i > 0; i--) { + if ((newPopped & (1 << i)) != 0) + depth--; + } + + // adjust the count and poppedDeep/3 + if ((poppedDeep & poppedTop & 1) != 0) { + count--; + poppedDeep >>= 1; + poppedTop >>= 1; + mask >>= 1; + } else if ((poppedDeep & poppedTop & 2) != 0) { + count--; + poppedDeep &= 1; + poppedTop &= 1; + mask &= 1; + } + + if (poppedDeep == mask + || (depth == 0 && poppedTop == mask)) { + // dup was not necessary + return newPopped; + } + + /* Now (poppedDeep & poppedTop) == 0 */ + + if (poppedTop > 0) { + /* Insert the pop for the top elements, we add + * the dup later in front of these instructions. + */ + if (poppedTop == 3) { + newInstructions.addFirst + (Instruction.forOpcode(opc_pop2)); + } else { + newInstructions.addFirst + (Instruction.forOpcode(opc_pop)); + if (count == 2 && poppedTop == 1) + swapAfterDup = true; + } + } + + if (poppedDeep != 0) { + if (poppedDeep == 2) { + /* We swap before and after dupping to get to + * poppedDeep = 1 case. + */ + swapAfterDup = !swapAfterDup; + swapBeforeDup = true; + } + /* The bottom most value is popped; decrease count + * and increase depth, so that it won't be created + * in the first place. + */ + depth++; + count--; + } + + /* Now all pops are resolved */ + /* Do a dup with count and depth now. */ + + if (swapAfterDup) + newInstructions.addFirst + (Instruction.forOpcode(opc_swap)); + + if (depth < 3) { + newInstructions.addFirst + (Instruction.forOpcode(opc_pop - 3 + + depth + 3 * count)); + } else { + // I hope that this will almost never happen. + // depth = 3, count = 1; + // Note that instructions are backwards. + newInstructions.addFirst + (Instruction.forOpcode(opc_pop2)); //DABCD< + newInstructions.addFirst + (Instruction.forOpcode(opc_dup2_x2)); //DABCDAB< + newInstructions.addFirst + (Instruction.forOpcode(opc_pop)); //DCDAB< + newInstructions.addFirst + (Instruction.forOpcode(opc_dup_x2)); //DCDABD< + newInstructions.addFirst + (Instruction.forOpcode(opc_pop)); //DCABD< + newInstructions.addFirst + (Instruction.forOpcode(opc_dup2_x2)); //DCABDC< + swappedBeforeDup = !swappedBeforeDup; //ABDC< + } + + if (swapBeforeDup) + newInstructions.addFirst + (Instruction.forOpcode(opc_swap)); + return newPopped; + } + + /** + * This method analyzes a block from end to start and removes the + * pop instructions together with their pushes. It propagates pops + * to the front removing various instructions on the fly, which may + * generate new pops for their operands and so on. + * + * @param block the block of code. + * @param poppedEntries the stack slots that should be popped at + * the end of this block. + * @return the stack slots that should be popped at the start of + * the block. + */ + BitSet movePopsToFront(Block block, BitSet poppedEntries) { + /* Calculate stack height at end of block. */ + Instruction[] oldInstrs = block.getInstructions(); + LinkedList newInstructions = new LinkedList(); + + int instrNr = oldInstrs.length; + int stackDepth = block.getStackHeight() + block.getStackDelta(); + while (instrNr > 0) { + Instruction instr = oldInstrs[--instrNr]; + if (instr.getOpcode() == opc_nop) + continue; + if (instr.getOpcode() == opc_pop) { + popsAtEnd.set(stackDepth++); + continue; + } + if (instr.getOpcode() == opc_pop2) { + popsAtEnd.set(stackDepth++); + popsAtEnd.set(stackDepth++); + continue; + } + + instr.getStackPopPush(poppush); + + /* Check if this instr pushes a popped Entry. */ + boolean push_a_popped = false; + boolean push_all_popped = true; + for (int j=0; j < poppush[1]; j++) { + if (poppedEntries.get(j)) + push_a_popped = true; + else + push_all_popped = false; + } + + if (!push_a_popped) { + // Normal case: + // add the instruction and adjust stack depth. + newInstructions.addFirst(instr); + stackDepth += poppush[0] - poppush[1]; + continue; + } + + /* We push an entry, that gets popped later */ + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_dup: + case opc_dup_x1: + case opc_dup_x2: + case opc_dup2: + case opc_dup2_x1: + case opc_dup2_x2: { + int popped = 0; + for (int j = poppush[1] ; j > 0; j--) { + popped <<= 1; + if (poppedEntries.get(--stackDepth)) { + popped |= 1; + poppedEntries.clear(stackDepth); + } + } + popped = movePopsThroughDup(opcode, newInstructions, + popped); + for (int j=0; j < poppush[1]; j++) { + if ((popped & 1) != 0) + poppedEntries.set(stackDepth); + stackDepth++; + popped >>=1; + } + break; + } + + case opc_swap: + if (!push_all_popped) { + // swap the popped status + if (poppedEntries.get(stackDepth - 1)) { + poppedEntries.clear(stackDepth - 1); + poppedEntries.set(stackDepth - 2); + } else { + poppedEntries.set(stackDepth - 1); + poppedEntries.clear(stackDepth - 2); + } + } + + case opc_ldc2_w: + case opc_lload: case opc_dload: + case opc_i2l: case opc_i2d: + case opc_f2l: case opc_f2d: + case opc_ldc: + case opc_iload: case opc_fload: case opc_aload: + case opc_new: + case opc_lneg: case opc_dneg: + case opc_l2d: case opc_d2l: + case opc_laload: case opc_daload: + case opc_ineg: case opc_fneg: + case opc_i2f: case opc_f2i: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_newarray: case opc_anewarray: + case opc_arraylength: + case opc_instanceof: + case opc_lshl: case opc_lshr: case opc_lushr: + case opc_iaload: case opc_faload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + case opc_iadd: case opc_fadd: + case opc_isub: case opc_fsub: + case opc_imul: case opc_fmul: + case opc_idiv: case opc_fdiv: + case opc_irem: case opc_frem: + case opc_iand: case opc_ior : case opc_ixor: + case opc_ishl: case opc_ishr: case opc_iushr: + case opc_fcmpl: case opc_fcmpg: + case opc_l2i: case opc_l2f: + case opc_d2i: case opc_d2f: + case opc_ladd: case opc_dadd: + case opc_lsub: case opc_dsub: + case opc_lmul: case opc_dmul: + case opc_ldiv: case opc_ddiv: + case opc_lrem: case opc_drem: + case opc_land: case opc_lor : case opc_lxor: + case opc_lcmp: + case opc_dcmpl: case opc_dcmpg: + case opc_getstatic: + case opc_getfield: + case opc_multianewarray: + + /* The simple instructions, that can be removed. */ + if (!push_all_popped) + throw new InternalError("pop half of a long"); + if (poppush[0] < poppush[1]) { + for (int j=0; j < poppush[0] - poppush[1]; j++) + poppedEntries.set(stackDepth++); + } else if (poppush[0] < poppush[1]) { + for (int j=0; j < poppush[0] - poppush[1]; j++) + poppedEntries.clear(--stackDepth); + } + + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_checkcast: + + /* These instructions can't be removed, since + * they have side effects. + */ + if (!push_all_popped) + throw new InternalError("pop half of a long"); + if (poppush[1] == 1) { + poppedEntries.clear(--stackDepth); + newInstructions + .addFirst(Instruction.forOpcode(opc_pop)); + } else { + poppedEntries.clear(--stackDepth); + poppedEntries.clear(--stackDepth); + newInstructions + .addFirst(Instruction.forOpcode(opc_pop2)); + } + newInstructions.addFirst(instr); + default: + throw new InternalError("Unexpected opcode!"); + } + } + blocks[i].setCode((Instruction[]) newInstructions + .toArray(new Instruction[newInstructions.size()]), + blocks[i].getSuccs()); + return poppedEntries; + } + + /** + * This method analyzes a block from start to end and inserts the + * pop instructions at the right places. It is used if a pop couldn't + * be removed for some reason. + * + * @param block the block of code. + * @param poppedEntries the stack slots that should be popped at + * the end of this block. + * @return the stack slots that should be popped at the start of + * the block. + */ + void movePopsToTail(Block block, BitSet poppedEntries) { + /* Calculate stack height at end of block. */ + Instruction[] oldInstrs = block.getInstructions(); + ArrayList newInstructions = new ArrayList(); + + int instrNr = oldInstrs.length; + int stackDepth = block.getStackHeight(); + for (instrNr = 0; instrNr < oldInstrs.length; instrNr++) { + while (poppedEntries.get(stackDepth-1)) { + poppedEntries.clear(--stackDepth); + /* XXX opc_pop2?*/ + newInstructions.add(Instruction.forOpcode(opc_pop)); + } + + Instruction instr = oldInstrs[--instrNr]; + instr.getStackPopPush(poppush); + /* XXX handle pops inside a opc_dup */ + } + block.setCode((Instruction[]) newInstructions + .toArray(new Instruction[newInstructions.size()]), + block.getSuccs()); + } + + public void transformCode(BasicBlocks bb) { + if (bb.getStartBlock() == null) + return; + + BlockInfo[] infos = calcBlockInfos(bb); + + int poppush[] = new int[2]; + boolean poppedEntries[] = new boolean[bb.getMaxStack()]; + Block[] blocks = bb.getBlocks(); + for (int i = 0; i < blocks.length; i++) { + blocks[i].setCode((Instruction[]) newInstructions + .toArray(oldInstrs), blocks[i].getSuccs()); + } + } +} diff --git a/jode/src/net/sf/jode/obfuscator/modules/SerializePreserver.java b/jode/src/net/sf/jode/obfuscator/modules/SerializePreserver.java new file mode 100644 index 0000000..d80919e --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/SerializePreserver.java @@ -0,0 +1,91 @@ +/* SerializePreserver Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import net.sf.jode.obfuscator.*; + +import java.lang.reflect.Modifier; +///#def COLLECTIONS java.util +import java.util.Collection; +///#enddef + +public class SerializePreserver implements IdentifierMatcher { + boolean onlySUID = true; + + public SerializePreserver() { + } + + public void setOption(String option, Collection values) { + if (option.equals("all")) { + onlySUID = false; + } else + throw new IllegalArgumentException("Invalid option `"+option+"'."); + } + + public final boolean matchesSub(Identifier ident, String name) { + if (ident instanceof PackageIdentifier) + return true; + if (ident instanceof ClassIdentifier) { + ClassIdentifier clazz = (ClassIdentifier) ident; + return (clazz.isSerializable() + && (!onlySUID || clazz.hasSUID())); + } + return false; + } + + public final boolean matches(Identifier ident) { + ClassIdentifier clazz; + if (ident instanceof ClassIdentifier) + clazz = (ClassIdentifier) ident; + else if (ident instanceof FieldIdentifier) + clazz = (ClassIdentifier) ident.getParent(); + else + return false; + + if (!clazz.isSerializable() + || (onlySUID && !clazz.hasSUID())) + return false; + + if (ident instanceof FieldIdentifier) { + FieldIdentifier field = (FieldIdentifier) ident; + if ((field.getModifiers() + & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) + return true; + if (ident.getName().equals("serialPersistentFields") + || ident.getName().equals("serialVersionUID")) + return true; + } else if (ident instanceof MethodIdentifier) { + if (ident.getName().equals("writeObject") + && ident.getType().equals("(Ljava.io.ObjectOutputStream)V")) + return true; + if (ident.getName().equals("readObject") + && ident.getType().equals("(Ljava.io.ObjectInputStream)V")) + return true; + } else if (ident instanceof ClassIdentifier) { + if (!clazz.hasSUID()) + clazz.addSUID(); + return true; + } + return false; + } + + public final String getNextComponent(Identifier ident) { + return null; + } +} diff --git a/jode/src/net/sf/jode/obfuscator/modules/SimpleAnalyzer.java b/jode/src/net/sf/jode/obfuscator/modules/SimpleAnalyzer.java new file mode 100644 index 0000000..fc9c2cb --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/SimpleAnalyzer.java @@ -0,0 +1,228 @@ +/* SimpleAnalyzer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import net.sf.jode.bytecode.Opcodes; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.BasicBlocks; +import net.sf.jode.bytecode.Block; +import net.sf.jode.bytecode.Handler; +import net.sf.jode.bytecode.Instruction; +import net.sf.jode.bytecode.Reference; +import net.sf.jode.bytecode.TypeSignature; +import net.sf.jode.obfuscator.Identifier; +import net.sf.jode.obfuscator.*; +import net.sf.jode.GlobalOptions; + +import java.io.IOException; +///#def COLLECTIONS java.util +import java.util.ArrayList; +import java.util.Iterator; +import java.util.ListIterator; +///#enddef + +public class SimpleAnalyzer implements CodeAnalyzer, Opcodes { + + private ClassInfo canonizeIfaceRef(ClassInfo clazz, Reference ref) { + while (clazz != null) { + if (clazz.findMethod(ref.getName(), ref.getType()) != null) + return clazz; + ClassInfo[] ifaces = clazz.getInterfaces(); + for (int i = 0; i < ifaces.length; i++) { + ClassInfo realClass = canonizeIfaceRef(ifaces[i], ref); + if (realClass != null) + return realClass; + } + clazz = clazz.getSuperclass(); + } + return null; + } + + protected Identifier canonizeReference(Instruction instr) { + Reference ref = instr.getReference(); + Identifier ident = Main.getClassBundle().getIdentifier(ref); + ClassPath classPath = Main.getClassBundle().getClassPath(); + String clName = ref.getClazz(); + String realClazzName; + if (ident != null) { + ClassIdentifier clazz = (ClassIdentifier)ident.getParent(); + realClazzName = "L" + (clazz.getFullName() + .replace('.', '/')) + ";"; + } else { + /* We have to look at the ClassInfo's instead, to + * point to the right method. + */ + ClassInfo clazz; + if (clName.charAt(0) == '[') { + /* Arrays don't define new methods (well clone(), + * but that can be ignored). + */ + clazz = classPath.getClassInfo("java.lang.Object"); + } else { + clazz = classPath.getClassInfo + (clName.substring(1, clName.length()-1) + .replace('/','.')); + } + try { + clazz.load(clazz.DECLARATIONS); + } catch (IOException ex) { + throw new RuntimeException("Can't get declarations of " + + clazz); + } + if (instr.getOpcode() == opc_invokeinterface) { + clazz = canonizeIfaceRef(clazz, ref); + } else if (instr.getOpcode() >= opc_invokevirtual) { + while (clazz != null + && clazz.findMethod(ref.getName(), + ref.getType()) == null) + clazz = clazz.getSuperclass(); + } else { + while (clazz != null + && clazz.findField(ref.getName(), + ref.getType()) == null) + clazz = clazz.getSuperclass(); + } + + if (clazz == null) { + GlobalOptions.err.println("WARNING: Can't find reference: " + +ref); + realClazzName = clName; + } else + realClazzName = "L" + clazz.getName().replace('.', '/') + ";"; + } + if (!realClazzName.equals(ref.getClazz())) { + ref = Reference.getReference(realClazzName, + ref.getName(), ref.getType()); + instr.setReference(ref); + } + return ident; + } + + + /** + * Reads the opcodes out of the code info and determine its + * references + * @return an enumeration of the references. + */ + public void analyzeCode(MethodIdentifier m, BasicBlocks bb) { + Block[] blocks = bb.getBlocks(); + for (int i=0; i < blocks.length; i++) { + Instruction[] instrs = blocks[i].getInstructions(); + for (int idx = 0; idx < instrs.length; idx++) { + int opcode = instrs[idx].getOpcode(); + switch (opcode) { + case opc_checkcast: + case opc_instanceof: + case opc_multianewarray: { + String clName = instrs[idx].getClazzType(); + int k = 0; + while (k < clName.length() && clName.charAt(k) == '[') + k++; + if (k < clName.length() && clName.charAt(k) == 'L') { + clName = clName.substring(k+1, clName.length()-1) + .replace('/','.'); + Main.getClassBundle().reachableClass(clName); + } + break; + } + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: + case opc_putstatic: + case opc_putfield: + m.setGlobalSideEffects(); + /* fall through */ + case opc_getstatic: + case opc_getfield: { + Identifier ident = canonizeReference(instrs[idx]); + if (ident != null) { + if (opcode == opc_putstatic + || opcode == opc_putfield) { + FieldIdentifier fi = (FieldIdentifier) ident; + if (!fi.isNotConstant()) + fi.setNotConstant(); + } else if (opcode == opc_invokevirtual || + opcode == opc_invokeinterface) { + ((ClassIdentifier) ident.getParent()) + .reachableReference + (instrs[idx].getReference(), true); + } else { + ident.setReachable(); + } + } + break; + } + } + } + } + + Handler[] handlers = bb.getExceptionHandlers(); + for (int i=0; i< handlers.length; i++) { + if (handlers[i].getType() != null) + Main.getClassBundle() + .reachableClass(handlers[i].getType()); + } + } + + public void transformCode(BasicBlocks bb) { + Block[] blocks = bb.getBlocks(); + for (int i=0; i < blocks.length; i++) { + Instruction[] instrs = blocks[i].getInstructions(); + ArrayList newCode = new ArrayList(); + Block[] newSuccs = blocks[i].getSuccs(); + for (int idx = 0; idx < instrs.length; idx++) { + int opcode = instrs[idx].getOpcode(); + if (opcode == opc_putstatic || opcode == opc_putfield) { + Reference ref = instrs[idx].getReference(); + FieldIdentifier fi = (FieldIdentifier) + Main.getClassBundle().getIdentifier(ref); + if (fi != null + && (Main.stripping & Main.STRIP_UNREACH) != 0 + && !fi.isReachable()) { + /* Replace instruction with pop opcodes. */ + int stacksize = + (opcode == Instruction.opc_putstatic) ? 0 : 1; + stacksize += TypeSignature.getTypeSize(ref.getType()); + switch (stacksize) { + case 1: + newCode.add(Instruction.forOpcode + (Instruction.opc_pop)); + continue; + case 2: + newCode.add(Instruction.forOpcode + (Instruction.opc_pop2)); + continue; + case 3: + newCode.add(Instruction.forOpcode + (Instruction.opc_pop2)); + newCode.add(Instruction.forOpcode + (Instruction.opc_pop)); + continue; + } + } + } + newCode.add(instrs[idx]); + } + blocks[i].setCode((Instruction []) newCode.toArray(instrs), + newSuccs); + } + } +} diff --git a/jode/src/net/sf/jode/obfuscator/modules/StrongRenamer.java b/jode/src/net/sf/jode/obfuscator/modules/StrongRenamer.java new file mode 100644 index 0000000..acfefef --- /dev/null +++ b/jode/src/net/sf/jode/obfuscator/modules/StrongRenamer.java @@ -0,0 +1,171 @@ +/* StrongRenamer Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.obfuscator.modules; +import net.sf.jode.obfuscator.*; +///#def COLLECTIONS java.util +import java.util.Collection; +import java.util.Iterator; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +public class StrongRenamer implements Renamer, OptionHandler { + static String[] idents = { + "Package", "Class", "Field", "Method", "Local" + }; + static String[] parts = { + "Start", "Part" + }; + String charsets[][]; + + String javaKeywords[] = { + "abstract", "default", "if", "private", "throw", "boolean", + "do", "implements", "protected", "throws", "break", "double", + "import", "public", "transient", "byte", "else", "instanceof", + "return", "try", "case", "extends", "int", "short", "void", + "catch", "final", "interface", "static", "volatile", "char", + "finally", "long", "super", "while", "class", "float", + "native", "switch", "const", "for", "new", "synchronized", + "continue", "goto", "package", "this", "strictfp", "null", + "true", "false" + }; + + public StrongRenamer() { + charsets = new String[idents.length][parts.length]; + for (int i=0; i< idents.length; i++) + for (int j=0; j< parts.length; j++) + charsets[i][j] = "abcdefghijklmnopqrstuvwxyz"; + } + + public void setOption(String option, Collection values) { + if (option.startsWith("charset")) { + Object value = values.iterator().next(); + if (values.size() != 1 || !(value instanceof String)) + throw new IllegalArgumentException + ("Only string parameter are supported."); + String set = (String) value; + String remOpt = option.substring("charset".length()); + int part = -1, ident = -1; + if (remOpt.length() > 0) { + for (int i=0; i < idents.length; i++) { + if (remOpt.startsWith(idents[i])) { + remOpt = remOpt.substring(idents[i].length()); + ident = i; + break; + } + } + } + if (remOpt.length() > 0) { + for (int j=0; j < parts.length; j++) { + if (remOpt.startsWith(parts[j])) { + remOpt = remOpt.substring(parts[j].length()); + part = j; + break; + } + } + } + if (remOpt.length() > 0) + throw new IllegalArgumentException("Invalid charset `" + +option+"'"); + for (int i = 0; i < idents.length; i++) { + if (ident >= 0 && ident != i) + continue; + for (int j = 0; j < parts.length; j++) { + if (part >= 0 && part != j) + continue; + charsets[i][j] = set; + } + } + } else + throw new IllegalArgumentException("Invalid option `" + +option+"'"); + } + + public Iterator generateNames(Identifier ident) { + int identType; + if (ident instanceof PackageIdentifier) + identType = 0; + else if (ident instanceof ClassIdentifier) + identType = 1; + else if (ident instanceof FieldIdentifier) + identType = 2; + else if (ident instanceof MethodIdentifier) + identType = 3; + else if (ident instanceof LocalIdentifier) + identType = 4; + else + throw new IllegalArgumentException(ident.getClass().getName()); + final String[] theCharset = charsets[identType]; + + return new Iterator() { + char[] name = null; + int headIndex; + + public boolean hasNext() { + return true; + } + public Object next() { + if (name == null) { + name = new char[] { theCharset[0].charAt(0) }; + headIndex = 0; + return new String(name); + } + next_name: + while (true) { + if (++headIndex < theCharset[0].length()) { + name[0] = theCharset[0].charAt(headIndex); + return new String(name); + } + headIndex = 0; + name[0] = theCharset[0].charAt(0); + + String charset = theCharset[1]; + for (int pos = 1; pos < name.length; pos++) { + int index = charset.indexOf(name[pos]) + 1; + if (index < charset.length()) { + name[pos] = charset.charAt(index); + return new String(name); + } + name[pos] = charset.charAt(0); + } + + name = new char[name.length+1]; + name[0] = theCharset[0].charAt(0); + char firstCont = theCharset[1].charAt(0); + for (int i=1; i 0) + prefix += "."; + + int lastDot = prefix.length(); + if (!wildcard.startsWith(prefix)) + return null; + + int nextDot = wildcard.indexOf('.', lastDot); + if (nextDot > 0 + && (nextDot <= firstStar || firstStar == -1)) + return wildcard.substring(lastDot, nextDot); + else if (firstStar == -1) + return wildcard.substring(lastDot); + else + return null; + } + + public boolean matchesSub(Identifier ident, String subident) { + String prefix = ident.getFullName(); + if (prefix.length() > 0) + prefix += "."; + if (subident != null) + prefix += subident; + if (firstStar == -1 || firstStar >= prefix.length()) + return wildcard.startsWith(prefix); + return prefix.startsWith(wildcard.substring(0, firstStar)); + } + + public boolean matches(Identifier ident) { + String test = ident.getFullName(); + if (firstStar == -1) { + if (wildcard.equals(test)) { + return true; + } + return false; + } + if (!test.startsWith(wildcard.substring(0, firstStar))) + return false; + + test = test.substring(firstStar); + int lastWild = firstStar; + int nextWild; + while ((nextWild = wildcard.indexOf('*', lastWild + 1)) != -1) { + String pattern = wildcard.substring(lastWild+1, nextWild); + while (!test.startsWith(pattern)) { + if (test.length() == 0) + return false; + test = test.substring(1); + } + test = test.substring(nextWild - lastWild - 1); + lastWild = nextWild; + } + + return test.endsWith(wildcard.substring(lastWild+1)); + } + + public String toString() { + return "Wildcard "+wildcard; + } +} diff --git a/jode/src/net/sf/jode/overview.html b/jode/src/net/sf/jode/overview.html new file mode 100644 index 0000000..4ad32b1 --- /dev/null +++ b/jode/src/net/sf/jode/overview.html @@ -0,0 +1,50 @@ + + + + +Jode API + + + +

    Jode is a set of package usefule for handling java class files. +Jode's main purpose is to do decompile and obfuscate classes. +Nonetheless it contains a lot of infrastructure that may be useful for +other packages working on java class files. Therefore I publish the +API here.

    + +

    Note: Jode is licensed under GNU GPL. This implies that if +you want to distribute a program that makes use this API you have to +do it under the conditions of the GNU GPL. As long as you don't +distribute it now restrictions apply. Otherwise you have to make all +your source open and allow the redistributions of it. Refer to the GNU GPL for the +details.

    + + +

    The best documented package is the {@link net.sf.jode.bytecode} +package to work on class files. Of course there are other bytecode +packages out there, but my bytecode package has some advantages. +

    + +

    The class {@link net.sf.jode.decompiler.Decompiler} can be used to +start the decompiler from your own java package. +

    + + diff --git a/jode/src/net/sf/jode/swingui/ClassPathDialog.java b/jode/src/net/sf/jode/swingui/ClassPathDialog.java new file mode 100644 index 0000000..e22216a --- /dev/null +++ b/jode/src/net/sf/jode/swingui/ClassPathDialog.java @@ -0,0 +1,274 @@ +/* ClassPathDialog Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.swingui; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.decompiler.Decompiler; +import net.sf.jode.decompiler.ProgressListener; + +///#def JAVAX_SWING javax.swing +import javax.swing.*; +import javax.swing.filechooser.*; +import javax.swing.event.*; +///#enddef + +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.BorderLayout; +import java.awt.event.*; +import java.awt.AWTEventMulticaster; +import java.io.File; + +public class ClassPathDialog { + JDialog dialog; + + JTextField editField; + boolean editFieldChanged = false; + JList pathList; + DefaultListModel pathListModel; + + ClassPath currentClassPath; + ActionListener actionListener = null; + + public ClassPathDialog(JFrame frame, String[] classPath) { + dialog = new JDialog(frame, Main.bundle.getString("cpdialog.title"), + false); + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); + panel.add(BorderLayout.NORTH, createEditPane()); + panel.add(BorderLayout.CENTER, createListPane()); + panel.add(BorderLayout.SOUTH, createOkayCancelPane()); + + dialog.getContentPane().add(BorderLayout.CENTER, panel); + dialog.pack(); + dialog.addWindowListener(new WindowAdapter() { + public void windowClosing() { + dialog.setVisible(false); + } + }); + + for (int i = 0; i < classPath.length; i++) + pathListModel.addElement(classPath[i]); + createNewClassPath(); + } + + public void showDialog() { + dialog.setVisible(true); + } + + public ClassPath getClassPath() { + return currentClassPath; + } + + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + ClassPath reflectClassPath = new ClassPath("reflection:"); + private void createNewClassPath() { + String[] paths = new String[pathListModel.getSize()]; + pathListModel.copyInto(paths); + currentClassPath = new ClassPath(paths, reflectClassPath); + if (actionListener != null) + actionListener.actionPerformed + (new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null)); + } + + void add() { + String entry = editField.getText(); + int index = pathListModel.getSize(); + if (pathList.isSelectionEmpty()) + pathListModel.addElement(entry); + else { + index = pathList.getLeadSelectionIndex() + 1; + pathListModel.add(index - 1, + editField.getText()); + } + pathList.setSelectedIndex(index); + editFieldChanged = false; + } + + static class JarFileFilter extends FileFilter { + public boolean accept(File f) { + if (f.isDirectory()) + return true; + String name = f.getName(); + int dot = name.lastIndexOf('.'); + if (dot >= 0) { + String ext = name.substring(dot+1); + if (ext.equals("jar") || ext.equals("zip")) + return true; + } + return false; + } + + public String getDescription() { + return Main.bundle.getString("browse.filter.description"); + } + } + + class BrowseListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String fileName = editField.getText(); + if (fileName.length() == 0) + fileName = null; + JFileChooser fileChooser = new JFileChooser(fileName); + fileChooser.setFileSelectionMode + (JFileChooser.FILES_AND_DIRECTORIES); + fileChooser.setDialogType(JFileChooser.CUSTOM_DIALOG); + fileChooser.setDialogTitle(Main.bundle.getString("browse.title")); + fileChooser.setFileFilter(new JarFileFilter()); + fileChooser.setApproveButtonText + (Main.bundle.getString("button.select")); + fileChooser.setApproveButtonMnemonic('s'); + if (fileChooser.showDialog(dialog, null) + == JFileChooser.APPROVE_OPTION) { + editField.setText(fileChooser.getSelectedFile().getPath()); + add(); + } + } + } + + private JPanel createEditPane() { + editField = new JTextField(); + + JButton browseButton = new JButton + (Main.bundle.getString("button.browse")); + browseButton.setMnemonic('b'); + browseButton.addActionListener(new BrowseListener()); + + JButton addButton = new JButton + (Main.bundle.getString("button.add")); + addButton.setMnemonic('d'); + JButton removeButton = new JButton + (Main.bundle.getString("button.remove")); + removeButton.setMnemonic('r'); + + ActionListener addListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + add(); + } + }; + addButton.addActionListener(addListener); + editField.addActionListener(addListener); + editField.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { + editFieldChanged = true; + } + public void insertUpdate(DocumentEvent e) { + editFieldChanged = true; + } + public void removeUpdate(DocumentEvent e) { + editFieldChanged = (editField.getText().length() > 0); + } + }); + + removeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (!pathList.isSelectionEmpty()) { + int index = pathList.getLeadSelectionIndex(); + pathListModel.remove(index); + if (index < pathListModel.getSize()) + pathList.setSelectedIndex(index); + } + } + }); + + JPanel editPane = new JPanel(); + editPane.setLayout(new BorderLayout()); + editPane.add(BorderLayout.EAST, browseButton); + editPane.add(BorderLayout.SOUTH, + createButtonPane(new JButton[] { addButton, + removeButton })); + editPane.add(BorderLayout.CENTER, editField); + return editPane; + } + + private JComponent createListPane() { + pathListModel = new DefaultListModel(); + pathList = new JList(pathListModel); + pathList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + JScrollPane listScroller = new JScrollPane(pathList); + listScroller.setMinimumSize(new Dimension(250, 80)); + listScroller.setAlignmentX(JScrollPane.LEFT_ALIGNMENT); + + pathList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + if (!pathList.isSelectionEmpty() + && !editFieldChanged) { + editField.setText((String) + pathList.getSelectedValue()); + editFieldChanged = false; + } + } + }); + return listScroller; + } + + private JPanel createOkayCancelPane() { + JButton okayButton = new JButton + (Main.bundle.getString("button.okay")); + okayButton.setMnemonic('o'); + okayButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + createNewClassPath(); + dialog.setVisible(false); + } + }); + JButton applyButton = new JButton + (Main.bundle.getString("button.apply")); + applyButton.setMnemonic('a'); + applyButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + createNewClassPath(); + } + }); + JButton cancelButton = new JButton + (Main.bundle.getString("button.cancel")); + cancelButton.setMnemonic('c'); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dialog.setVisible(false); + } + }); + return createButtonPane + (new JButton[] { okayButton, applyButton, cancelButton }); + } + + private JPanel createButtonPane(JButton[] buttons) { + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(buttons[0]); + for (int i=1; i < buttons.length; i++) { + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(buttons[i]); + } + buttonPane.add(Box.createHorizontalGlue()); + return buttonPane; + } +} diff --git a/jode/src/net/sf/jode/swingui/HierarchyTreeModel.java b/jode/src/net/sf/jode/swingui/HierarchyTreeModel.java new file mode 100644 index 0000000..ff92547 --- /dev/null +++ b/jode/src/net/sf/jode/swingui/HierarchyTreeModel.java @@ -0,0 +1,285 @@ +/* HierarchyTreeModel Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.swingui; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; + +///#def JAVAX_SWING javax.swing +import javax.swing.JProgressBar; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeModelEvent; +//#enddef + +///#def COLLECTIONEXTRA java.lang +import java.lang.Comparable; +///#enddef +///#def COLLECTIONS java.util +import java.util.TreeSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +///#enddef + +import java.io.IOException; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +public class HierarchyTreeModel implements TreeModel, Runnable { + static final int MAX_PACKAGE_LEVEL = 10; + TreeElement root = new TreeElement(""); + Set listeners = new HashSet(); + JProgressBar progressBar; + ClassPath classPath; + + class TreeElement implements Comparable { + String fullName; + TreeSet childs; + boolean inClassPath = false; + + public TreeElement(String fqn) { + this.fullName = fqn; + childs = new TreeSet(); + } + + public String getFullName() { + return fullName; + } + + public void addChild(TreeElement child) { + childs.add(child); + } + + public Collection getChilds() { + return childs; + } + + public String toString() { + return fullName; + } + + public int compareTo(Object o) { + TreeElement other = (TreeElement) o; + return fullName.compareTo(other.fullName); + } + + public boolean equals(Object o) { + return (o instanceof TreeElement) + && fullName.equals(((TreeElement)o).fullName); + } + + public int hashCode() { + return fullName.hashCode(); + } + } + + private TreeElement handleClass(HashMap classes, ClassInfo clazz) { + if (clazz == null) + return root; + + TreeElement elem = (TreeElement) classes.get(clazz); + if (elem != null) + return elem; + + elem = new TreeElement(clazz.getName()); + classes.put(clazz, elem); + + try { + clazz.load(ClassInfo.HIERARCHY); + } catch (IOException ex) { + clazz.guess(ClassInfo.HIERARCHY); + } + + if (!clazz.isInterface()) { + ClassInfo superClazz = clazz.getSuperclass(); + handleClass(classes, superClazz).addChild(elem); + } + ClassInfo[] ifaces = clazz.getInterfaces(); + for (int i=0; i < ifaces.length; i++) + handleClass(classes, ifaces[i]).addChild(elem); + if (ifaces.length == 0 && clazz.isInterface()) + root.addChild(elem); + return elem; + } + + public int readPackage(int depth, HashMap classes, String packageName, + int count) { + if (depth++ >= MAX_PACKAGE_LEVEL) + return count; + String prefix = packageName.length() == 0 ? "" : packageName + "."; + Enumeration enumeration = classPath.listClassesAndPackages(packageName); + while (enumeration.hasMoreElements()) { + //insert sorted and remove double elements; + String name = (String)enumeration.nextElement(); + String fqn = prefix + name; + if (classPath.isPackage(fqn)) { + count = readPackage(depth, classes, fqn, count); + } else { + TreeElement elem = handleClass + (classes, classPath.getClassInfo(fqn)); + if (progressBar != null) + progressBar.setValue(++count); + + elem.inClassPath = true; + } + } + return count; + } + + public int countClasses(int depth, String packageName) { + if (depth++ >= MAX_PACKAGE_LEVEL) + return 0; + int number = 0; + String prefix = packageName.length() == 0 ? "" : packageName + "."; + Enumeration enumeration = classPath.listClassesAndPackages(packageName); + while (enumeration.hasMoreElements()) { + //insert sorted and remove double elements; + String name = (String)enumeration.nextElement(); + String fqn = prefix + name; + if (classPath.isPackage(fqn)) { + number += countClasses(depth, fqn); + } else { + number++; + } + } + return number; + } + + public HierarchyTreeModel(ClassPath classPath) { + this.progressBar = null; + setClassPath(classPath); + } + + public HierarchyTreeModel(ClassPath classPath, + JProgressBar progressBar) { + this.progressBar = progressBar; + setClassPath(classPath); + } + + public void setClassPath(ClassPath classPath) { + this.classPath = classPath; + + Thread t = new Thread(this); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); + } + + public void run() { + if (progressBar != null) { + progressBar.setMinimum(0); + progressBar.setMaximum(countClasses(0, "")); + } + readPackage(0, new HashMap(), "", 0); + + TreeModelListener[] ls; + synchronized (listeners) { + ls = (TreeModelListener[]) + listeners.toArray(new TreeModelListener[listeners.size()]); + } + TreeModelEvent ev = new TreeModelEvent(this, new Object[] { root }); + for (int i=0; i< ls.length; i++) + ls[i].treeStructureChanged(ev); + } + + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } + public void valueForPathChanged(TreePath path, Object newValue) { + // we don't allow values + } + + public Object getChild(Object parent, int index) { + Iterator iter = ((TreeElement) parent).getChilds().iterator(); + for (int i=0; i< index; i++) + iter.next(); + return iter.next(); + } + + public int getChildCount(Object parent) { + return ((TreeElement) parent).getChilds().size(); + } + + public int getIndexOfChild(Object parent, Object child) { + Iterator iter = ((TreeElement) parent).getChilds().iterator(); + int i=0; + while (iter.next() != child) + i++; + return i; + } + + public Object getRoot() { + return root; + } + + public boolean isLeaf(Object node) { + return ((TreeElement) node).getChilds().isEmpty(); + } + + public boolean isValidClass(Object node) { + return ((TreeElement) node).inClassPath; + } + + public String getFullName(Object node) { + return ((TreeElement) node).getFullName(); + } + + public TreePath getPath(String fullName) { + if (fullName == null || fullName.length() == 0) + return new TreePath(root); + + int length = 1; + ClassInfo ci = classPath.getClassInfo(fullName); + while (ci != null) { + try { + ci.load(ClassInfo.HIERARCHY); + } catch (IOException ex) { + ci.guess(ClassInfo.HIERARCHY); + } + length++; + ci = ci.getSuperclass(); + } + + TreeElement[] path = new TreeElement[length]; + path[0] = root; + int nr = 0; + next_component: + while (nr < length-1) { + ci = classPath.getClassInfo(fullName); + for (int i=2; i < length - nr; i++) + ci = ci.getSuperclass(); + Iterator iter = path[nr].getChilds().iterator(); + while (iter.hasNext()) { + TreeElement te = (TreeElement) iter.next(); + if (te.getFullName().equals(ci.getName())) { + path[++nr] = te; + continue next_component; + } + } + return null; + } + return new TreePath(path); + } +} diff --git a/jode/src/net/sf/jode/swingui/Main.java b/jode/src/net/sf/jode/swingui/Main.java new file mode 100644 index 0000000..c1e2984 --- /dev/null +++ b/jode/src/net/sf/jode/swingui/Main.java @@ -0,0 +1,463 @@ +/* Main Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.swingui; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.decompiler.Decompiler; +import net.sf.jode.decompiler.ProgressListener; + +///#def JAVAX_SWING javax.swing +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.tree.*; +///#enddef + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.StringTokenizer; +import java.util.ResourceBundle; +import java.util.*; + +public class Main + implements ActionListener, Runnable, + TreeSelectionListener, TreeModelListener { + Decompiler decompiler; + JFrame frame; + JTree classTree; + JPanel statusLine; + PackagesTreeModel packModel; + HierarchyTreeModel hierModel; + JTextArea sourcecodeArea, errorArea; + Thread decompileThread; + String currentClassPath, lastClassName; + ClassPathDialog classPathDialog; + + JProgressBar progressBar; + private JMenuItem saveMenuItem; + + boolean hierarchyTree; + + public static ResourceBundle bundle; + + public Main(String[] classpath) { + decompiler = new Decompiler(); + frame = new JFrame(GlobalOptions.copyright); + classPathDialog = new ClassPathDialog(frame, classpath); + decompiler.setClassPath(classPathDialog.getClassPath()); + classPathDialog.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ClassPath classPath = classPathDialog.getClassPath(); + decompiler.setClassPath(classPath); + if (classTree != null) + classTree.clearSelection(); + if (packModel != null) + packModel.setClassPath(classPath); + if (hierModel != null && hierarchyTree) { + hierModel.setClassPath(classPath); + } else { + hierModel = null; + } + } + }); + + fillContentPane(frame.getContentPane()); + addMenu(frame); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + frame.pack(); + this.center(); + } + + public void center() { + //center window + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension frameSize = frame.getSize(); + + if (frameSize.height > screenSize.height) { + frameSize.height = screenSize.height; + } + + if (frameSize.width > screenSize.width) { + frameSize.width = screenSize.width; + } + + frame.setLocation((screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2); + } + + public void show() { + frame.setVisible(true); + } + + public void fillContentPane(Container contentPane) { + statusLine = new JPanel(); + hierarchyTree = false; + packModel = new PackagesTreeModel(getClassPath()); + hierModel = null; + packModel.addTreeModelListener(this); + Font monospaced = new Font("monospaced", Font.PLAIN, 12); + classTree = new JTree(packModel); + classTree.setRootVisible(false); + DefaultTreeSelectionModel selModel = new DefaultTreeSelectionModel(); + selModel.setSelectionMode(selModel.SINGLE_TREE_SELECTION); + classTree.setSelectionModel(selModel); + classTree.addTreeSelectionListener(this); + JScrollPane spClassTree = new JScrollPane(classTree); + sourcecodeArea = new JTextArea(20, 80); + sourcecodeArea.setEditable(false); + sourcecodeArea.setFont(monospaced); + JScrollPane spText = new JScrollPane(sourcecodeArea); + errorArea = new JTextArea(3, 80); + errorArea.setEditable(false); + errorArea.setFont(monospaced); + JScrollPane spError = new JScrollPane(errorArea); + + JSplitPane rightPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + spText, spError); + JSplitPane allPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, + spClassTree, rightPane); + contentPane.setLayout(new BorderLayout()); + contentPane.add(allPane, BorderLayout.CENTER); + contentPane.add(statusLine, BorderLayout.SOUTH); + progressBar = new JProgressBar(); + statusLine.add(progressBar); + rightPane.setDividerLocation(300); + rightPane.setDividerSize(4); + allPane.setDividerLocation(200); + allPane.setDividerSize(4); + decompiler.setErr(new PrintWriter + (new BufferedWriter(new AreaWriter(errorArea)), true)); + } + + public synchronized void valueChanged(TreeSelectionEvent e) { + if (decompileThread != null) + return; + TreePath path = e.getNewLeadSelectionPath(); + if (path == null) + return; + Object node = path.getLastPathComponent(); + if (node != null) { + if (hierarchyTree && hierModel.isValidClass(node)) + lastClassName = hierModel.getFullName(node); + else if (!hierarchyTree && packModel.isValidClass(node)) + lastClassName = packModel.getFullName(node); + else + return; + + startDecompiler(); + } + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == classTree) + startDecompiler(); + } + + public synchronized void startDecompiler() { + if (decompileThread == null) { + decompileThread = new Thread(this); + decompileThread.setPriority(Thread.MIN_PRIORITY); + + progressBar.setMinimum(0); + progressBar.setMaximum(1000); + progressBar.setString(bundle.getString("main.decompiling")); + progressBar.setStringPainted(true); + decompileThread.start(); + this.saveMenuItem.setEnabled(true); + } + } + + public class AreaWriter extends Writer { + boolean initialized = false; + boolean lastCR = false; + private JTextArea area; + + public AreaWriter(JTextArea a) { + area = a; + } + + public void write(char[] b, int off, int len) throws IOException { + /* Note that setText and append are thread safe! */ + if (!initialized) { + area.setText(""); + initialized = true; + } + String str = new String(b, off, len); + StringBuffer sb = new StringBuffer(len); + while (str != null && str.length() > 0) { + if (lastCR && str.charAt(0) == '\n') + str = str.substring(1); + int crIndex = str.indexOf('\r'); + if (crIndex >= 0) { + sb.append(str.substring(0, crIndex)); + sb.append("\n"); + str = str.substring(crIndex+1); + lastCR = true; + } else { + sb.append(str); + str = null; + } + } + area.append(sb.toString()); + } + + public void flush() { + } + + public void close() { + } + } + + public void run() { + errorArea.setText(""); + Writer writer = new BufferedWriter + (new AreaWriter(sourcecodeArea), 1024); + + ProgressListener progListener = new ProgressListener() + { + public void updateProgress(final double progress, + final String detail) { + SwingUtilities.invokeLater(new Runnable() + { + public void run() { + progressBar.setValue((int)(1000 * progress)); + progressBar.setString(detail); + } + }); + } + }; + try { + decompiler.decompile(lastClassName, writer, progListener); + } catch (Throwable t) { + try { + writer.write(bundle.getString("main.exception")); + PrintWriter pw = new PrintWriter(writer); + t.printStackTrace(pw); + pw.flush(); + } catch (IOException ex) { + /* Shouldn't happen, complain to stderr */ + ex.printStackTrace(); + } + } finally { + try { + writer.close(); + } catch (IOException ex) { + /* ignore */ + } + synchronized(this) { + decompileThread = null; + } + } + SwingUtilities.invokeLater(new Runnable() + { + public void run() { + progressBar.setValue(0); + progressBar.setString(""); + } + }); + } + + public void addMenu(JFrame frame) { + JMenuBar bar = new JMenuBar(); + JMenu menu; + JMenuItem item; + menu = new JMenu(bundle.getString("menu.file")); + menu.setMnemonic('f'); + + this.saveMenuItem = new JMenuItem(bundle.getString("menu.file.save")); + this.saveMenuItem.setMnemonic('s'); + this.saveMenuItem.setEnabled(false); + this.saveMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + JFileChooser chooser = new JFileChooser(); + + try { + if (JFileChooser.APPROVE_OPTION != chooser.showSaveDialog( + new Container())) { + return; //cancelled + } + } catch (Exception he) { + return; + } + + //save file + File saveFile = new File(chooser.getSelectedFile() + .getAbsoluteFile().toString()); + + FileOutputStream out; // declare a file output object + PrintStream p; // declare a print stream object + + try { + // Create a new file output stream + out = new FileOutputStream(saveFile); + + // Connect print stream to the output stream + p = new PrintStream(out); + + p.print(sourcecodeArea.getText()); + p.close(); + } catch (Exception e) { + errorArea.setText(bundle.getString("menu.file.save.ex")); + } + } + }); + menu.add(this.saveMenuItem); + + menu.add(new JSeparator()); + + item = new JMenuItem(bundle.getString("menu.file.gc")); + item.setMnemonic('c'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + System.gc(); + System.runFinalization(); + } + }); + menu.add(item); + item = new JMenuItem(bundle.getString("menu.file.exit")); + item.setMnemonic('x'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + System.exit(0); + } + }); + menu.add(item); + bar.add(menu); + menu = new JMenu(bundle.getString("menu.opt")); + menu.setMnemonic('o'); + final JCheckBoxMenuItem hierItem + = new JCheckBoxMenuItem(bundle.getString("menu.opt.hier"), + hierarchyTree); + hierItem.setMnemonic('h'); + hierItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + hierarchyTree = hierItem.isSelected(); + if (hierarchyTree && hierModel == null) { + hierModel = new HierarchyTreeModel(getClassPath(), + progressBar); + hierModel.addTreeModelListener(Main.this); + } + classTree.setModel(hierarchyTree + ? (TreeModel) hierModel : packModel); + if (lastClassName != null) { + TreePath lastPath = (hierarchyTree + ? hierModel.getPath(lastClassName) + : packModel.getPath(lastClassName)); + classTree.setSelectionPath(lastPath); + classTree.scrollPathToVisible(lastPath); + } + } + }); + menu.add(hierItem); + menu.add(new JSeparator()); + item = new JMenuItem(bundle.getString("menu.opt.cp")); + item.setMnemonic('c'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + classPathDialog.showDialog(); + } + }); + menu.add(item); + bar.add(menu); + frame.setJMenuBar(bar); + } + + public ClassPath getClassPath() { + return classPathDialog.getClassPath(); + } + + public void treeNodesChanged(TreeModelEvent e) { + reselect(); + } + + public void treeNodesInserted(TreeModelEvent e) { + reselect(); + } + + public void treeNodesRemoved(TreeModelEvent e) { + reselect(); + } + + public void treeStructureChanged(TreeModelEvent e) { + reselect(); + } + + public void reselect() { + if (lastClassName != null) { + TreePath lastPath = (hierarchyTree + ? hierModel.getPath(lastClassName) + : packModel.getPath(lastClassName)); + if (lastPath != null) { + classTree.setSelectionPath(lastPath); + classTree.scrollPathToVisible(lastPath); + } + } + } + + public static void usage() { + PrintWriter err = GlobalOptions.err; + int numUsage = Integer.parseInt(bundle.getString("usage.count")); + for (int i=0; i < numUsage ; i++) + err.println(bundle.getString("usage."+i)); + } + + public static void main(String[] params) { + bundle = ResourceBundle.getBundle("net.sf.jode.swingui.Resources"); + String cp = System.getProperty("java.class.path", ""); + cp = cp.replace(File.pathSeparatorChar, + Decompiler.altPathSeparatorChar); + String bootClassPath = System.getProperty("sun.boot.class.path"); + if (bootClassPath != null) + cp += Decompiler.altPathSeparatorChar + + bootClassPath.replace(File.pathSeparatorChar, + Decompiler.altPathSeparatorChar); + int i; + for (i = 0; i < params.length; i++) { + if (params[i].equals("--classpath") + || params[i].equals("--cp") + || params[i].equals("-c")) + cp = params[++i]; + else if (params[i].equals("--debug") + || params[i].equals("--D")) { + String arg = params[++i]; + GlobalOptions.setDebugging(arg); + } else if (params[i].startsWith("-")) { + if (!params[i].equals("--help") + && !params[i].equals("-h")) + System.err.println("Unknown option: "+params[i]); + usage(); + return; + } else + cp = params[i]; + } + StringTokenizer st = new StringTokenizer + (cp, ""+Decompiler.altPathSeparatorChar); + String[] splitcp = new String[st.countTokens()]; + for (i = 0; i< splitcp.length; i++) + splitcp[i] = st.nextToken(); + Main win = new Main(splitcp); + win.show(); + } +} diff --git a/jode/src/net/sf/jode/swingui/PackagesTreeModel.java b/jode/src/net/sf/jode/swingui/PackagesTreeModel.java new file mode 100644 index 0000000..e10e27e --- /dev/null +++ b/jode/src/net/sf/jode/swingui/PackagesTreeModel.java @@ -0,0 +1,217 @@ +/* PackagesTreeModel Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.swingui; +import net.sf.jode.decompiler.Options; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.ClassPath; + +///#def JAVAX_SWING javax.swing +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeModelEvent; +///#enddef + +///#def COLLECTIONEXTRA java.lang +import java.lang.Comparable; +///#enddef +///#def COLLECTIONS java.util +import java.util.Arrays; +import java.util.TreeSet; +import java.util.HashSet; +import java.util.Set; +import java.util.HashMap; +import java.util.Map; +///#enddef + + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +public class PackagesTreeModel implements TreeModel { + Map cachedChildrens = new HashMap(); + Main main; + ClassPath classPath; + + class TreeElement implements Comparable { + String fullName; + String name; + boolean leaf; + + public TreeElement(String prefix, String name, boolean isLeaf) { + this.fullName = prefix+name; + this.name = name; + this.leaf = isLeaf; + } + + public String getFullName() { + return fullName; + } + + public String getName() { + return name; + } + + public boolean isLeaf() { + return leaf; + } + + public String toString() { + return name; + } + + public int compareTo(Object o) { + TreeElement other = (TreeElement) o; + if (leaf != other.leaf) + // files come after directories + return leaf ? 1 : -1; + return fullName.compareTo(other.fullName); + } + + public boolean equals(Object o) { + return (o instanceof TreeElement) + && fullName.equals(((TreeElement)o).fullName); + } + + public int hashCode() { + return fullName.hashCode(); + } + } + + TreeElement root = new TreeElement("", "", false); + Set listeners = new HashSet(); + + public PackagesTreeModel(ClassPath classPath) { + this.classPath = classPath; + } + + public void setClassPath(ClassPath classPath) { + this.classPath = classPath; + + cachedChildrens.clear(); + TreeModelListener[] ls; + synchronized (listeners) { + ls = (TreeModelListener[]) + listeners.toArray(new TreeModelListener[listeners.size()]); + } + TreeModelEvent ev = new TreeModelEvent(this, new Object[] { root }); + for (int i=0; i< ls.length; i++) + ls[i].treeStructureChanged(ev); + } + + public TreeElement[] getChildrens(TreeElement parent) { + TreeElement[] result = + (TreeElement[]) cachedChildrens.get(parent); + if (result == null) { + TreeSet v = new TreeSet(); + String prefix = parent == root ? "" : parent.getFullName() + "."; + Enumeration enumeration + = classPath.listClassesAndPackages(parent.getFullName()); + while (enumeration.hasMoreElements()) { + //insert sorted and remove double elements; + String name = (String)enumeration.nextElement(); + String fqn = prefix + name; + boolean isClass = !classPath.isPackage(fqn); + + if (isClass + && Options.skipClass(classPath.getClassInfo(fqn))) + continue; + TreeElement newElem = new TreeElement(prefix, name, isClass); + v.add(newElem); + } + result = (TreeElement[]) v.toArray(new TreeElement[v.size()]); + cachedChildrens.put(parent, result); + } + return result; + } + + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } + public void valueForPathChanged(TreePath path, Object newValue) { + // we don't allow values + } + + public Object getChild(Object parent, int index) { + return getChildrens((TreeElement) parent)[index]; + } + + public int getChildCount(Object parent) { + return getChildrens((TreeElement) parent).length; + } + + public int getIndexOfChild(Object parent, Object child) { + TreeElement[] childrens = getChildrens((TreeElement) parent); + int i = Arrays.binarySearch(childrens, child); + if (i >= 0) + return i; + throw new NoSuchElementException + (((TreeElement)parent).getFullName() + "." + child); + } + + public Object getRoot() { + return root; + } + + public boolean isLeaf(Object node) { + return ((TreeElement) node).isLeaf(); + } + + public boolean isValidClass(Object node) { + return ((TreeElement) node).isLeaf(); + } + + public TreePath getPath(String fullName) { + if (fullName == null || fullName.length() == 0) + return new TreePath(root); + int pos = -1; + int length = 2; + while ((pos = fullName.indexOf('.', pos+1)) != -1) + length++; + TreeElement[] path = new TreeElement[length]; + path[0] = root; + int i = 0; + pos = -1; + next_component: + while (pos < fullName.length()) { + int start = pos+1; + pos = fullName.indexOf('.', start); + if (pos == -1) + pos = fullName.length(); + String component = fullName.substring(start, pos); + TreeElement[] childs = getChildrens(path[i]); + for (int j=0; j< childs.length; j++) { + if (childs[j].getName().equals(component)) { + path[++i] = childs[j]; + continue next_component; + } + } + return null; + } + return new TreePath(path); + } + + public String getFullName(Object node) { + return ((TreeElement) node).getFullName(); + } +} diff --git a/jode/src/net/sf/jode/type/ArrayType.java b/jode/src/net/sf/jode/type/ArrayType.java new file mode 100644 index 0000000..a825164 --- /dev/null +++ b/jode/src/net/sf/jode/type/ArrayType.java @@ -0,0 +1,208 @@ +/* ArrayType Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.bytecode.ClassInfo; +import java.util.Vector; +import java.io.IOException; + +/** + * This type represents an array type. + * + * @author Jochen Hoenicke + */ +public class ArrayType extends ClassType { + Type elementType; + + ArrayType(Type elementType) { + super(TC_ARRAY, elementType + "[]"); + this.elementType = elementType; + } + + public boolean isInterface() { + return false; + } + + public boolean isUnknown() { + if (elementType instanceof ClassType) + return ((ClassType) elementType).isUnknown(); + return false; + } + + public boolean isFinal() { + if (elementType instanceof ClassType) + return ((ClassType) elementType).isFinal(); + return false; + } + + public ClassType getSuperClass() { + return tObject; + } + + public ClassType[] getInterfaces() { + return arrayIfaces; + } + + public Type getElementType() { + return elementType; + } + + public Type getSuperType() { + if (elementType instanceof IntegerType) + return tRange(tObject, this); + else + return tRange(tObject, + (ReferenceType) tArray(elementType.getSuperType())); + } + + public Type getSubType() { + if (elementType instanceof IntegerType) + return this; + else + return tArray(elementType.getSubType()); + } + + public Type getHint() { + return tArray(elementType.getHint()); + } + + public Type getCanonic() { + return tArray(elementType.getCanonic()); + } + + public boolean isSuperTypeOf(Type type) { + if (type == tNull) + return true; + if (type instanceof ArrayType) + return elementType.isSuperTypeOf(((ArrayType) type).elementType); + return false; + } + + /** + * Create the type corresponding to the range from bottomType to this. + * @param bottomType the start point of the range + * @return the range type, or tError if not possible. + */ + public Type createRangeType(ReferenceType bottom) { + /* + * tArray(y), tArray(x) -> tArray( y.intersection(x) ) + * obj , tArray(x) -> + * iff tArray extends and implements obj + */ + if (bottom instanceof ArrayType) + return tArray(elementType.intersection + (((ArrayType)bottom).elementType)); + + return super.createRangeType(bottom); + } + + /** + * Returns the common sub type of this and type. + * @param type the other type. + * @return the common sub type. + */ + public Type getSpecializedType(Type type) { + /* + * tArray(x), iface -> tArray(x) iff tArray implements iface + * tArray(x), tArray(y) -> tArray(x.intersection(y)) + * tArray(x), other -> tError + */ + if (type.getTypeCode() == TC_RANGE) { + type = ((RangeType) type).getBottom(); + } + if (type == tNull) + return this; + if (type.getTypeCode() == TC_ARRAY) { + Type elType = elementType.intersection + (((ArrayType)type).elementType); + return elType != tError ? tArray(elType) : tError; + } + if (type.isSuperTypeOf(this)) + return this; + return tError; + } + + /** + * Returns the common super type of this and type. + * @param type the other type. + * @return the common super type. + */ + public Type getGeneralizedType(Type type) { + /* tArray(x), tNull -> tArray(x) + * tArray(x), tClass(y) -> common ifaces of tArray and tClass + * tArray(x), tArray(y) -> tArray(x.intersection(y)) or tObject + * tArray(x), other -> tError + */ + if (type.getTypeCode() == TC_RANGE) { + type = ((RangeType) type).getTop(); + } + if (type == tNull) + return this; + if (type.getTypeCode() == TC_ARRAY) { + Type elType = elementType.intersection + (((ArrayType)type).elementType); + if (elType != tError) + return tArray(elType); + return MultiClassType.create(arrayIfaces); + } + if (!(type instanceof ReferenceType)) + return tError; + + return ((ReferenceType)type).getGeneralizedType(this); + } + + /** + * Checks if this type represents a valid type instead of a list + * of minimum types. + */ + public boolean isValidType() { + return elementType.isValidType(); + } + + public String getTypeSignature() { + return "["+elementType.getTypeSignature(); + } + + public Class getTypeClass() throws ClassNotFoundException { + return Class.forName(getTypeSignature()); + } + + private static String pluralize(String singular) { + return singular + + ((singular.endsWith("s") || singular.endsWith("x") + || singular.endsWith("sh") || singular.endsWith("ch")) + ? "es" : "s"); + } + + public String getDefaultName() { + if (elementType instanceof ArrayType) + return elementType.getDefaultName(); + return pluralize(elementType.getDefaultName()); + } + + public boolean equals(Object o) { + if (o == this) + return true; + if (o instanceof ArrayType) { + ArrayType type = (ArrayType) o; + return type.elementType.equals(elementType); + } + return false; + } +} diff --git a/jode/src/net/sf/jode/type/ClassInfoType.java b/jode/src/net/sf/jode/type/ClassInfoType.java new file mode 100644 index 0000000..6b44337 --- /dev/null +++ b/jode/src/net/sf/jode/type/ClassInfoType.java @@ -0,0 +1,136 @@ +/* ClassInfoType Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.TypeSignature; +import net.sf.jode.GlobalOptions; + +import java.lang.reflect.Modifier; +import java.io.IOException; +import java.util.Vector; +import java.util.Stack; +import java.util.Hashtable; + +///#def COLLECTIONS java.util +import java.util.Map; +///#enddef + +/** + * This class is the type representing a class loaded from a ClassPath.

    + * + * @author Jochen Hoenicke */ +public class ClassInfoType extends ClassType { + ClassInfo clazz; + ClassType superClass = null; + ClassType[] interfaces = null; + + public ClassInfo getClazz() { + return clazz; + } + + public ClassInfoType(ClassInfo clazz, Type[] generics) { + super(TC_CLASS, clazz.getName()); + + this.clazz = clazz; + try { + clazz.load(ClassInfo.HIERARCHY); + } catch (IOException ex) { + clazz.guess(ClassInfo.HIERARCHY); + GlobalOptions.err.println + ("Can't get full class hierarchy for "+clazz+ + " types may be incorrect."); + GlobalOptions.err.println(ex.toString()); + } + + String signature = clazz.getSignature(); + + if (signature.length() == 0) { + /* This is only true for java.lang.Object, each other + * class needs at least a super class. + */ + return; + } + + genInstances = generics; + if (generics != null) { + /* parse generic names */ + String[] genNames; + if (signature.charAt(0) == '<') + genNames = TypeSignature.getGenericNames(signature); + + if (genNames == null) + throw new IllegalArgumentException + ("Generic parameters for non-generic class"); + if (generics.length != genNames.length) + throw new IllegalArgumentException + ("Wrong number of generic parameters"); + } + + signature = TypeSignature.mapGenerics(signature, getGenerics()); + } + + public boolean isUnknown() { + return clazz.isGuessed(); + } + + public boolean isFinal() { + return Modifier.isFinal(clazz.getModifiers()); + } + + public boolean isInterface() { + return clazz.isInterface(); + } + + public ClassType getSuperClass() { + if (clazz.isInterface()) + return null; + if (superClass == null) { + ClassInfo superInfo = clazz.getSuperclass(); + if (superInfo == null) + return null; + superClass = Type.tClass(superInfo); + } + return superClass; + } + + public ClassType[] getInterfaces() { + if (interfaces == null) { + ClassInfo[] ifaceInfos = clazz.getInterfaces(); + if (ifaceInfos.length == 0) + interfaces = EMPTY_IFACES; + else { + interfaces = new ClassType[ifaceInfos.length]; + for (int i=0; i < interfaces.length; i++) + interfaces[i] = Type.tClass(ifaceInfos[i]); + } + } + return interfaces; + } + + public ClassInfo getClassInfo() { + return clazz; + } + + public boolean equals(Object o) { + if (o instanceof ClassInfoType) + return ((ClassInfoType) o).clazz == clazz; + return super.equals(o); + } +} diff --git a/jode/src/net/sf/jode/type/ClassType.java b/jode/src/net/sf/jode/type/ClassType.java new file mode 100644 index 0000000..acf7cfc --- /dev/null +++ b/jode/src/net/sf/jode/type/ClassType.java @@ -0,0 +1,406 @@ +/* ClassType Copyright (C) 2000-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import java.util.Stack; +import java.util.Hashtable; +import java.util.Enumeration; +import java.io.IOException; + +///#def COLLECTIONS java.util +import java.util.Collections; +import java.util.Map; +///#enddef + +/** + * This class is the base class of all types representing a class type.

    + * + * @author Jochen Hoenicke + */ +public abstract class ClassType extends ReferenceType { + /** + * The full qualified class name in java syntax. + */ + protected String className; + protected String[] genericNames; + protected Type[] genericInstances; + + /* + * @invariant (genericNames == null) == (genericInstances == null) + * @invariant (genericNames != null) ==> + * (genericNames.length == genericInstances.length) + */ + + public String getClassName() { + return className; + } + + public Type getGeneric(String name) { + if (genericNames != null) { + for (int i = 0; i < genericNames.length; i++) + if (genericNames[i].equals(name)) + return genericInstances[i]; + } + return null; + } + + public ClassType(int typecode, String clazzName) { + super(typecode); + className = clazzName; + } + + public ClassType(int typecode, String clazzName, + String[] genNames, Type[] genTypes) { + super(typecode); + className = clazzName; + genericNames = genNames; + genericTypes = genTypes; + } + + /** + * Checks if this type represents an interface. + * @return true if this is an interface, false if this is a class or + * if unsure. + */ + public abstract boolean isUnknown(); + + /** + * Checks if this type represents an interface. + * @return true if this is an interface, false if this is a class or + * if unsure. + */ + public abstract boolean isInterface(); + + /** + * Checks if this type represents an interface. + * @return true if this is an interface, false if this is a class or + * if unsure. + */ + public abstract boolean isFinal(); + + /** + * Returns the reference type representing the super class, or null + * if this reference type represents java.lang.Object, or for interfaces. + * @return the super class' type. + */ + public abstract ClassType getSuperClass(); + + /** + * Returns the reference type representing the interfaces this class + * implements. This may of course be an empty array. + * @return the interfaces' types. + */ + public abstract ClassType[] getInterfaces(); + + /** + * Returns true, if all types in this type set are a super type of + * at least one type in the type set given as parameter. + */ + public boolean isSuperTypeOf(Type type) { + if (type == tNull) + return true; + if (type instanceof MultiClassType) + return ((MultiClassType)type).containsSubTypeOf(this); + if (!(type instanceof ClassType)) + return false; + if (this.equals(tObject)) + return true; + + ClassType ctype = (ClassType) type; + + if (isFinal()) + return ctype.equals(this); + + while (ctype != null) { + if (ctype.equals(this)) + return true; + + if (isInterface()) { + ClassType[] typeIfaces = ctype.getInterfaces(); + for (int i = 0; i < typeIfaces.length; i++) + if (isSuperTypeOf(typeIfaces[i])) + return true; + } + ctype = ctype.getSuperClass(); + } + return false; + } + + public boolean maybeSuperTypeOf(ClassType type) { + if (this.equals(tObject)) + return true; + + ClassType ctype = (ClassType) type; + + if (isFinal()) + return ctype.equals(this); + + while (ctype != null) { + if (ctype.equals(this)) + return true; + + if (ctype.isUnknown()) + return true; + + if (isInterface()) { + ClassType[] typeIfaces = ctype.getInterfaces(); + for (int i = 0; i < typeIfaces.length; i++) + if (isSuperTypeOf(typeIfaces[i])) + return true; + } + ctype = ctype.getSuperClass(); + } + return false; + } + + public Type getSubType() { + return tRange(this, tNull); + } + + public Type getHint() { + return this; + } + + public Type getCanonic() { + return this; + } + + /** + * Create the type corresponding to the range from bottomType to + * this. Checks if the given type range is not empty. This + * means, that this extends bottom.clazz and implements all + * interfaces in bottom. + * @param bottom the start point of the range + * @return the range type, or tError if range is empty. + */ + public Type createRangeType(ReferenceType bottomType) { + if (!bottomType.maybeSuperTypeOf(this)) + return tError; + + if (this.isSuperTypeOf(bottomType)) + /* bottomType contains a class equal to this. + */ + return this; + return tRange(bottomType, this); + } + + /** + * Returns the specialized type of this and type. + * We have two classes and multiple interfaces. The result + * should be the object that extends both objects + * and the union of all interfaces. + */ + public Type getSpecializedType(Type type) { + if (type instanceof RangeType) { + type = ((RangeType) type).getBottom(); + } + + /* Most times (almost always) one of the two classes is + * already more specialized. Optimize for this case. + */ + if (type.isSuperTypeOf(this)) + return this; + if (this.isSuperTypeOf(type)) + return type; + + if (type instanceof MultiClassType) + return ((MultiClassType) type).getSpecializedType(this); + + if (!(type instanceof ClassType)) + return tError; + ClassType other = (ClassType) type; + return MultiClassType.create(new ClassType[] {this, other}); + } + + /** + * Returns the generalized type of this and type, i.e. the common + * super type. The result should be the collection of classes and + * interfaces that are super class resp. super interfaces of both + * objects. We don't include their super classes and super interfaces + * though. + */ + public Type getGeneralizedType(Type type) { + int code = type.typecode; + if (code == TC_RANGE) { + type = ((RangeType) type).getTop(); + code = type.typecode; + } + if (code == TC_NULL) + return this; + + /* Often one of the two classes is already more generalized. + * Optimize for this case. + */ + if (type.isSuperTypeOf(this)) + return type; + if (this.isSuperTypeOf(type)) + return this; + + if (!(type instanceof ReferenceType)) + return tError; + + Stack classTypes = new Stack(); + classTypes.push(this); + return ((ReferenceType) type).findCommonClassTypes(classTypes); + } + + public String getTypeSignature() { + return "L" + className.replace('.', '/') + ";"; + } + + public Class getTypeClass() throws ClassNotFoundException { + return Class.forName(className); + } + + public String toString() + { + if (genInstances == null) + return className; + StringBuffer sb = new StringBuffer(className).append('<'); + String comma = ""; + for (int i = 0; i < genInstances.length; i++) { + sb.append(comma).append(genInstances[i].toString()); + } + sb.append('>'); + return sb.toString(); + } + + /** + * Checks if we need to cast to a middle type, before we can cast from + * fromType to this type. + * @return the middle type, or null if it is not necessary. + */ + public Type getCastHelper(Type fromType) { + if (isInterface() || fromType == tNull + || (fromType instanceof RangeType + && ((RangeType)fromType).getBottom() == tNull)) + return null; + Type hint = fromType.getHint(); + if (hint.isSuperTypeOf(this) + || (hint instanceof ClassType + && ((ClassType) hint).isInterface())) + return null; + return tObject; + } + + /** + * Checks if this type represents a valid type instead of a list + * of minimum types. + */ + public boolean isValidType() { + return true; + } + + /** + * Checks if this is a class or array type (but not a null type). + * @XXX remove this? + * @return true if this is a class or array type. + */ + public boolean isClassType() { + return true; + } + + public boolean containsClass(String clazzName) { + return clazzName.equals(className); + } + + private final static Hashtable keywords = new Hashtable(); + static { + keywords.put("abstract", Boolean.TRUE); + keywords.put("default", Boolean.TRUE); + keywords.put("if", Boolean.TRUE); + keywords.put("private", Boolean.TRUE); + keywords.put("throw", Boolean.TRUE); + keywords.put("boolean", Boolean.TRUE); + keywords.put("do", Boolean.TRUE); + keywords.put("implements", Boolean.TRUE); + keywords.put("protected", Boolean.TRUE); + keywords.put("throws", Boolean.TRUE); + keywords.put("break", Boolean.TRUE); + keywords.put("double", Boolean.TRUE); + keywords.put("import", Boolean.TRUE); + keywords.put("public", Boolean.TRUE); + keywords.put("transient", Boolean.TRUE); + keywords.put("byte", Boolean.TRUE); + keywords.put("else", Boolean.TRUE); + keywords.put("instanceof", Boolean.TRUE); + keywords.put("return", Boolean.TRUE); + keywords.put("try", Boolean.TRUE); + keywords.put("case", Boolean.TRUE); + keywords.put("extends", Boolean.TRUE); + keywords.put("int", Boolean.TRUE); + keywords.put("short", Boolean.TRUE); + keywords.put("void", Boolean.TRUE); + keywords.put("catch", Boolean.TRUE); + keywords.put("final", Boolean.TRUE); + keywords.put("interface", Boolean.TRUE); + keywords.put("static", Boolean.TRUE); + keywords.put("volatile", Boolean.TRUE); + keywords.put("char", Boolean.TRUE); + keywords.put("finally", Boolean.TRUE); + keywords.put("long", Boolean.TRUE); + keywords.put("super", Boolean.TRUE); + keywords.put("while", Boolean.TRUE); + keywords.put("class", Boolean.TRUE); + keywords.put("float", Boolean.TRUE); + keywords.put("native", Boolean.TRUE); + keywords.put("switch", Boolean.TRUE); + keywords.put("const", Boolean.TRUE); + keywords.put("for", Boolean.TRUE); + keywords.put("new", Boolean.TRUE); + keywords.put("synchronized", Boolean.TRUE); + keywords.put("continue", Boolean.TRUE); + keywords.put("goto", Boolean.TRUE); + keywords.put("package", Boolean.TRUE); + keywords.put("this", Boolean.TRUE); + keywords.put("strictfp", Boolean.TRUE); + keywords.put("null", Boolean.TRUE); + keywords.put("true", Boolean.TRUE); + keywords.put("false", Boolean.TRUE); + } + + /** + * Generates the default name, that is the `natural' choice for + * local of this type. + * @return the default name of a local of this type. + */ + public String getDefaultName() { + String name = className; + int dot = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$')); + if (dot >= 0) + name = name.substring(dot+1); + if (Character.isUpperCase(name.charAt(0))) { + name = name.toLowerCase(); + if (keywords.get(name) != null) + return "var_" + name; + return name; + } else + return "var_" + name; + } + + public int hashCode() { + return className.hashCode(); + } + + public boolean equals(Object o) { + if (o instanceof ClassType) + return ((ClassType) o).className.equals(className); + return false; + } +} diff --git a/jode/src/net/sf/jode/type/GenericParameterType.java b/jode/src/net/sf/jode/type/GenericParameterType.java new file mode 100644 index 0000000..139d447 --- /dev/null +++ b/jode/src/net/sf/jode/type/GenericParameterType.java @@ -0,0 +1,68 @@ +/* GenericParameterType Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: GenericParameterType.java,v 1.3 2004/06/01 08:46:10 hoenicke Exp $ + */ + +package net.sf.jode.type; +import net.sf.jode.bytecode.ClassInfo; +import java.util.Vector; +import java.util.Stack; +import java.util.Hashtable; +import java.io.IOException; + +/** + * This class represents the type of a system class, i.e. the classes + * from package java.lang, that need special handling, like + * Object, String, StringBuffer, etc. + * + * @author Jochen Hoenicke + */ +public class GenericParameterType extends ClassType { + ClassType superType; + ClassType[] ifacesTypes; + + /** + * @param className The name of this system class, must be interned. + */ + public GenericParameterType(String className, + ClassType superType, + ClassType[] ifacesTypes) { + super(TC_SYSCLASS, className); + this.superType = superType; + this.ifacesTypes = ifacesTypes; + } + + public boolean isInterface() { + return false; + } + + public boolean isFinal() { + return false; + } + + public boolean isUnknown() { + return false; + } + + public ClassType getSuperClass() { + return superType; + } + + public ClassType[] getInterfaces() { + return ifacesTypes; + } +} diff --git a/jode/src/net/sf/jode/type/IntegerType.java b/jode/src/net/sf/jode/type/IntegerType.java new file mode 100644 index 0000000..095e89a --- /dev/null +++ b/jode/src/net/sf/jode/type/IntegerType.java @@ -0,0 +1,289 @@ +/* IntegerType Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.GlobalOptions; + +/** + * This is a type class for 16 bit integral types. There are seven + * different types, namely int, char, short, byte, boolean, + * const short, const byte abbreviated I, C, S, B, Z, cS, + * cB. cB and cS specify constant + * ints whose value is in byte resp. short range. They may be + * converted to B resp. S, but sometimes need an explicit cast. + * + * @author Jochen Hoenicke + */ +public class IntegerType extends Type { + + /* Order does matter: + * First type that is possible (and hinted) will be taken. + */ + public static final int IT_Z = 0x01; + public static final int IT_I = 0x02; + public static final int IT_C = 0x04; + public static final int IT_S = 0x08; + public static final int IT_B = 0x10; + public static final int IT_cS = 0x20; + public static final int IT_cB = 0x40; + private static final int NUM_TYPES = 7; + + private static final int[] subTypes = { + /*Z*/ IT_Z, + /*I*/ IT_I|IT_C|IT_S|IT_B/*|IT_cS|IT_cB*/, /*C*/ IT_C, + /*S*/ IT_S|IT_B/*|IT_cS|IT_cB*/, /*B*/ IT_B/*|IT_cB*/, + /*cS*/IT_cS|IT_cB, /*cB*/IT_cB + }; + private static final int[] superTypes = { + /*Z*/ IT_Z, + /*I*/ IT_I, /*C*/ IT_I|IT_C, + /*S*/ IT_I|IT_S, /*B*/ IT_I|IT_S|IT_B, + /*cS*/IT_I|IT_C|IT_S|IT_cS, /*cB*/IT_I|IT_C|IT_S|IT_B|IT_cS|IT_cB + }; + private static final Type[] simpleTypes = { + new IntegerType(IT_Z), + new IntegerType(IT_I), new IntegerType(IT_C), + new IntegerType(IT_S), new IntegerType(IT_B), + new IntegerType(IT_cS), new IntegerType(IT_cB) + }; + private static final String[] typeNames = { + "Z","I","C","S","B","s","b" + }; + + int possTypes; + int hintTypes; + + /** + * Create a new type with the given type. + */ + public IntegerType(int types) { + this(types, types); + } + + public IntegerType(int types, int hints) { + super(TC_INTEGER); + possTypes = types; + hintTypes = hints; + } + + public Type getHint() { + int hint = possTypes & hintTypes; + if (hint == 0) + hint = possTypes; + int i = 0; + while ((hint & 1) == 0) { + hint >>= 1; + i++; + } + return simpleTypes[i]; + } + + public Type getCanonic() { + int types = possTypes; + int i = 0; + while ((types >>= 1) != 0) { + i++; + } + return simpleTypes[i]; + } + + private static int getSubTypes(int types) { + int result = 0; + for (int i=0; i < NUM_TYPES; i++) { + if (((1<= 0; ) + paramClasses[i] = parameterTypes[i].getTypeClass(); + return paramClasses; + } + + public Type getReturnType() { + return returnType; + } + + public Class getReturnClass() throws ClassNotFoundException { + return returnType.getTypeClass(); + } + + public String getTypeSignature() { + return signature; + } + + public String toString() { + return signature; + } +} diff --git a/jode/src/net/sf/jode/type/MultiClassType.java b/jode/src/net/sf/jode/type/MultiClassType.java new file mode 100644 index 0000000..db999db --- /dev/null +++ b/jode/src/net/sf/jode/type/MultiClassType.java @@ -0,0 +1,315 @@ +/* MultiClassType Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.bytecode.ClassInfo; +import java.util.Stack; +import java.util.Vector; +import java.io.IOException; + +/** + * This class represents a type aproximation, consisting of multiple + * interfaces and a class type.

    + * + * If this is the bottom boundary, this specifies, which class our + * type must extend and which interfaces it must implement. + * + * If this is the top boundary, this gives all interfaces and classes + * that may extend the type. I.e. at least one interface or class extends + * the searched type. + * + * @author Jochen Hoenicke */ +public class MultiClassType extends ReferenceType { + + ClassType classes[]; + + private MultiClassType(ClassType[] classes) { + super(TC_CLASSIFACE); + this.classes = classes; + } + + public static ReferenceType create(ClassType[] classes) { + if (classes.length == 0) + return tObject; + if (classes.length == 1) + return classes[0]; + return new MultiClassType(classes); + } + + public Type getSubType() { + /* We don't implement the set of types, that are castable to some + * of the given classes or interfaces. + */ + throw new InternalError + ("getSubType called on set of classes and interfaces!"); + } + + /** + * Returns true, iff this type implements all interfaces in type + * and extends all objects in type. + */ + public boolean isSuperTypeOf(Type type) { + for (int i = 0; i < classes.length; i++) + if (!classes[i].isSuperTypeOf(type)) + return false; + return true; + } + + /** + * Returns true, iff this type implements all interfaces in type + * and extends all objects in type. + */ + public boolean maybeSuperTypeOf(ClassType type) { + for (int i = 0; i < classes.length; i++) + if (!classes[i].maybeSuperTypeOf(type)) + return false; + return true; + } + + public Type getHint() { + return getCanonic(); + } + + public Type getCanonic() { + return classes[0]; + } + + /** + * Create the type corresponding to the range from bottomType to + * this. This removes all classes that doesn't extend all classes + * in bottom. If no class remains, this is a type error. + * @param bottom the start point of the range + * @return the range type, or tError if range is empty. + */ + public Type createRangeType(ReferenceType bottomType) { + ReferenceType topType; + /** + * Check if we fully implement the bottom type. + */ + int j; + for (j=0; j < classes.length; j++) { + if (!bottomType.maybeSuperTypeOf(classes[j])) + break; + } + + if (j == classes.length) + topType = this; + else { + /* Now we have at least one class, that doesn't implement + * bottomType, remove all such classes. + */ + ClassType[] topClasses = new ClassType[classes.length - 1]; + System.arraycopy(classes, 0, topClasses, 0, j); + int count = j; + for (j++; j < classes.length; j++) { + if (bottomType.maybeSuperTypeOf(classes[j])) + topClasses[count++] = classes[j]; + } + + if (count == 0) + return tError; + if (count < topClasses.length - 1) { + ClassType[] shortClasses = new ClassType[count]; + System.arraycopy(topClasses, 0, shortClasses, 0, count); + topClasses = shortClasses; + } + topType = create(topClasses); + } + if (topType.isSuperTypeOf(bottomType)) + /* This means that topType contains only classes that are also + * in bottomType. So topType is the whole range. + */ + return topType; + return tRange(bottomType, topType); + } + + boolean containsSubTypeOf(Type type) { + for (int i = 0; i < classes.length; i++) + if (type.isSuperTypeOf(classes[i])) + return true; + return false; + } + + /** + * Returns the specialized type of this and type. + * We simple unify the lists of classes, but simplify them, to remove + * all classes that are already subtypes of some other class in the + * other list. + */ + public Type getSpecializedType(Type type) { + if (type instanceof RangeType) + type = ((RangeType) type).getBottom(); + + /* Most times (almost always) one of the two types is + * already more specialized. Optimize for this case. + */ + if (type.isSuperTypeOf(this)) + return this; + if (this.isSuperTypeOf(type)) + return type; + + ClassType[] otherClasses; + if (type instanceof MultiClassType) { + otherClasses = ((MultiClassType) type).classes; + } else if (type instanceof ClassType) { + otherClasses = new ClassType[] { (ClassType) type }; + } else + return tError; + + /* The classes are simply the union of both classes set. But + * we can simplify this, if a class is implemented by another + * class in the other list, we can omit it. + */ + Vector destClasses = new Vector(); + big_loop_this: + for (int i=0; i< classes.length; i++) { + ClassType clazz = classes[i]; + if (!clazz.isSuperTypeOf(type)) { + /* This interface is not implemented by any of the other + * classes. Add it to the destClasses. + */ + destClasses.addElement(clazz); + } + } + big_loop_other: + for (int i=0; i< otherClasses.length; i++) { + ClassType clazz = otherClasses[i]; + if (!clazz.isSuperTypeOf(this)) { + /* This interface is not implemented by any of the other + * classes. Add it to the destClasses. + */ + destClasses.addElement(clazz); + } + } + + ClassType[] classArray = new ClassType[destClasses.size()]; + destClasses.copyInto(classArray); + return create(classArray); + } + + /** + * Returns the generalized type of this and type. We have two + * classes and multiple interfaces. The result should be the + * object that is the the super class of both objects and all + * interfaces, that one class or interface of each type + * implements. + */ + public Type getGeneralizedType(Type type) { + if (type instanceof RangeType) + type = ((RangeType) type).getTop(); + + /* Often one of the two classes is already more generalized. + * Optimize for this case. + */ + if (type.isSuperTypeOf(this)) + return type; + if (this.isSuperTypeOf(type)) + return this; + + if (!(type instanceof ReferenceType)) + return tError; + + Stack classTypes = new Stack(); + for (int i = 0; i < classes.length; i++) + classTypes.push(classes[i]); + return ((ReferenceType)type).findCommonClassTypes(classTypes); + } + + public String toString() + { + StringBuffer sb = new StringBuffer("{"); + String comma = ""; + for (int i=0; i< classes.length; i++) { + sb.append(comma).append(classes[i]); + comma = ", "; + } + return sb.append("}").toString(); + } + + public String getTypeSignature() { + return getCanonic().getTypeSignature(); + } + + public Class getTypeClass() throws ClassNotFoundException { + return getCanonic().getTypeClass(); + } + + /** + * Checks if we need to cast to a middle type, before we can cast from + * fromType to this type. + * @return the middle type, or null if it is not necessary. + */ + public Type getCastHelper(Type fromType) { + return getCanonic().getCastHelper(fromType); + } + + /** + * Checks if this type represents a valid type instead of a list + * of minimum types. + */ + public boolean isValidType() { + return false; + } + + /** + * Checks if this is a class or array type (but not a null type). + * @XXX remove this? + * @return true if this is a class or array type. + */ + public boolean isClassType() { + return true; + } + + /** + * Generates the default name, that is the `natural' choice for + * local of this type. + * @return the default name of a local of this type. + */ + public String getDefaultName() { + return getCanonic().getDefaultName(); + } + + public int hashCode() { + int hash = 0; + for (int i=0; i < classes.length; i++) { + hash ^= classes[i].hashCode(); + } + return hash; + } + + public boolean equals(Object o) { + if (o == this) + return true; + if (o instanceof MultiClassType) { + MultiClassType type = (MultiClassType) o; + if (type.classes.length == classes.length) { + big_loop: + for (int i=0; i< type.classes.length; i++) { + for (int j=0; j + * + * Question: Should we replace tUObject = tRange(tObject, tNull) by tNull? + * Question2: if not, should null have type tNull? + * + * @author Jochen Hoenicke + */ +public class NullType extends ReferenceType { + public NullType() { + super(TC_NULL); + } + + public Type getSubType() { + return this; + } + + public Type createRangeType(ReferenceType bottomType) { + return tRange(bottomType, this); + } + + /** + * Returns the generalized type of this and type. We have two + * classes and multiple interfaces. The result should be the + * object that is the the super class of both objects and all + * interfaces, that one class or interface of each type + * implements. + */ + public Type getGeneralizedType(Type type) { + if (type.typecode == TC_RANGE) + type = ((RangeType) type).getTop(); + if (type instanceof ReferenceType) + return type; + return tError; + } + + /** + * Returns the specialized type of this and type. + * We have two classes and multiple interfaces. The result + * should be the object that extends both objects + * and the union of all interfaces. + */ + public Type getSpecializedType(Type type) { + if (type.typecode == TC_RANGE) + type = ((RangeType) type).getBottom(); + if (type != tNull) + return tError; + return tNull; + } + + public String toString() { + return "tNull"; + } + + public Type findCommonClassTypes(Stack otherTypes) { + throw new UnsupportedOperationException(); + } +} diff --git a/jode/src/net/sf/jode/type/ParameterType.java b/jode/src/net/sf/jode/type/ParameterType.java new file mode 100644 index 0000000..3639175 --- /dev/null +++ b/jode/src/net/sf/jode/type/ParameterType.java @@ -0,0 +1,88 @@ +/* ParameterType Copyright (C) 2005 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: ParameterType.java,v 1.3 2004/06/01 08:46:10 hoenicke Exp $ + */ + +package net.sf.jode.type; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.GlobalOptions; + +import java.lang.reflect.Modifier; +import java.io.IOException; +import java.util.Vector; +import java.util.Stack; +import java.util.Hashtable; + +///#def COLLECTIONS java.util +import java.util.Map; +///#enddef + +/** + * This class represents the singleton set containing one parameter type. For + * example in the context of the class Enum<E extends + * Enum<E>> the identifier E denotes such a + * parameter type. It has the super class Enum<E> and + * implements no interfaces. + * + * @author Jochen Hoenicke + */ +public class ParameterType extends ClassType { + ClassType superClass; + ClassType[] interfaces; + + public ParameterType(String name, + ClassInfo superInfo, ClassInfo[] ifaceInfos) { + super(TC_PARAMETER, name); + + superClass = Type.tClass(superInfo); + if (ifaceInfos.length == 0) { + interfaces = EMPTY_IFACES; + } else { + interfaces = new ClassType[ifaceInfos.length]; + for (int i=0; i < interfaces.length; i++) + interfaces[i] = Type.tClass(ifaceInfos[i]); + } + } + + public boolean isUnknown() { + return false; + } + public boolean isFinal() { + return false; + } + public boolean isInterface() { + return false; + } + + public ClassType getSuperClass() { + return superClass; + } + + public ClassType[] getInterfaces() { + return interfaces; + } + + public boolean equals(Object o) { + if (o instanceof ParameterType) + return ((ParameterType) o).className == className; + return false; + } +} + + + + diff --git a/jode/src/net/sf/jode/type/RangeType.java b/jode/src/net/sf/jode/type/RangeType.java new file mode 100644 index 0000000..9d734e8 --- /dev/null +++ b/jode/src/net/sf/jode/type/RangeType.java @@ -0,0 +1,235 @@ +/* RangeType Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassInfo; +import java.util.Hashtable; + +/** + * This class represents a set of reference types. The set contains + * all types that are castable to all the bottom types by a widening + * cast and to which one of the top types can be casted to by a + * widening cast.
    + * + * For a totally unknown reference type bottomType is tObject and + * topType is tNull. The bottomType is always guaranteed to be not + * tNull. And all topTypes must be castable to all bottom types with + * a widening cast.
    + * + * To do intersection on range types, the reference types need three + * more operations: specialization, generalization and + * createRange.

    + * + * specialization chooses all common sub type of two types. It is + * used to find the bottom of the intersected interval.

    + * + * generalization chooses the common super type of two types. It + * is used to find the top of the intersected interval.

    + * + * When the new interval is created with createRangeType + * the bottom and top are adjusted so that they only consists of + * possible types. It then decides, if it needs a range type, or if + * the reference types already represents all types. + * + * @author Jochen Hoenicke + * @see ReferenceType + * @date 98/08/06 */ +public class RangeType extends Type { + /** + * The bottom type set. Each type in the set represented by + * this range type can be casted to all types in bottom type. + */ + final ReferenceType bottomType; + /** + * The top type set. For each type in this range type, there is a + * top type, that can be casted to this type. + */ + final ReferenceType topType; + + /** + * Create a new range type with the given bottom and top set. + */ + RangeType(ReferenceType bottomType, ReferenceType topType) { + super(TC_RANGE); + if (bottomType == tNull) + throw new InternalError("bottom is NULL"); + this.bottomType = bottomType; + this.topType = topType; + } + + /** + * Returns the bottom type set. All types in this range type can + * be casted to all bottom types by a widening cast. + * @return the bottom type set + */ + ReferenceType getBottom() { + return bottomType; + } + + /** + * Returns the top type set. For each type in this range type, + * there is a top type, that can be casted to this type. + * @return the top type set + */ + ReferenceType getTop() { + return topType; + } + + + /** + * Returns the hint type of this range type set. This returns the + * singleton set containing only the first top type, except if it + * is null and there is a unique bottom type, in which case it + * returns the bottom type. + * @return the hint type. + */ + public Type getHint() { + Type bottomHint = bottomType.getHint(); + Type topHint = topType.getHint(); + + if (topType == tNull && bottomType.equals(bottomHint)) + return bottomHint; + + return topHint; + } + + /** + * Returns the canonic type of this range type set. This returns the + * singleton set containing only the first top type. + * @return the canonic type. + */ + public Type getCanonic() { + return topType.getCanonic(); + } + + /** + * The set of super types of this type. This is the set of + * super types of the top type. + * @return the set of super types. + */ + public Type getSuperType() { + return topType.getSuperType(); + } + + /** + * The set of sub types of this type. This is the set of + * sub types of the bottom types. + * @return the set of super types. + */ + public Type getSubType() { + return tRange(bottomType, tNull); + } + + /** + * Checks if we need to cast to a middle type, before we can cast from + * fromType to this type. + * @return the middle type, or null if it is not necessary. + */ + public Type getCastHelper(Type fromType) { + return topType.getCastHelper(fromType); + } + + public String getTypeSignature() { + if (topType.isClassType() || !bottomType.isValidType()) + return topType.getTypeSignature(); + else + return bottomType.getTypeSignature(); + } + + public Class getTypeClass() throws ClassNotFoundException { + if (topType.isClassType() || !bottomType.isValidType()) + return topType.getTypeClass(); + else + return bottomType.getTypeClass(); + } + + public String toString() + { + return "<" + bottomType + "-" + topType + ">"; + } + + public String getDefaultName() { + throw new InternalError("getDefaultName() called on range"); + } + + public int hashCode() { + int hashcode = topType.hashCode(); + return (hashcode << 16 | hashcode >>> 16) ^ bottomType.hashCode(); + } + + public boolean equals(Object o) { + if (o instanceof RangeType) { + RangeType type = (RangeType) o; + return topType.equals(type.topType) + && bottomType.equals(type.bottomType); + } + return false; + } + + public boolean containsClass(ClassInfo clazz) { + ClassType clazzType = Type.tClass(clazz, null); + if (!bottomType.maybeSuperTypeOf(clazzType)) + return false; + if (topType == tNull) + return true; + if (topType instanceof ClassType) + return clazzType.maybeSuperTypeOf((ClassType) topType); + if (topType instanceof MultiClassType) { + ClassType[] classes = ((MultiClassType) topType).classes; + for (int i = 0; i < classes.length; i++) { + if (clazzType.maybeSuperTypeOf(classes[i])) + return true; + } + } + return false; + } + + /** + * Intersect this type with another type and return the new type. + * @param type the other type. + * @return the intersection, or tError, if a type conflict happens. + */ + public Type intersection(Type type) { + if (type == tError) + return type; + if (type == Type.tUnknown) + return this; + + Type top, bottom, result; + bottom = bottomType.getSpecializedType(type); + top = topType.getGeneralizedType(type); + if (top.equals(bottom)) + result = top; + else if (top instanceof ReferenceType + && bottom instanceof ReferenceType) + result = ((ReferenceType)top) + .createRangeType((ReferenceType)bottom); + else + result = tError; + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) { + GlobalOptions.err.println("intersecting "+ this +" and "+ type + + " to <" + bottom + "," + top + + "> to " + result); + } + return result; + } +} + diff --git a/jode/src/net/sf/jode/type/ReferenceType.java b/jode/src/net/sf/jode/type/ReferenceType.java new file mode 100644 index 0000000..ed9a99b --- /dev/null +++ b/jode/src/net/sf/jode/type/ReferenceType.java @@ -0,0 +1,210 @@ +/* ReferenceType Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassInfo; +import java.io.IOException; +import java.util.Stack; +import java.util.Vector; +import java.util.Enumeration; + +/** + * This is an abstract super class of all reference types. Reference + * types are NullType, MultiClassType, and ClassType with its sub types + * ClassInfoType, SystemClassType, and ArrayType.

    + * + * To do intersection on range types, the reference types need three + * more operations: specialization, generalization and + * createRange.

    + * + * specialization chooses all common sub type of two types. It is + * used to find the bottom of the intersected interval.

    + * + * generalization chooses the common super type of two types. It + * is used to find the top of the intersected interval.

    + * + * When the new interval is created with createRangeType + * the bottom and top are adjusted so that they only consists of + * possible types. It then decides, if it needs a range type, or if + * the reference types already represents all types. + * + * @author Jochen Hoenicke + */ +public abstract class ReferenceType extends Type { + public ReferenceType(int typecode) { + super(typecode); + } + + /** + * Returns the specialized type set of this and type. The result + * should be a type set, so that every type, extends all types in + * type and this, iff it extends all types in the resulting type + * set. + * @param type the other type. + * @return the specialized type. */ + public abstract Type getSpecializedType(Type type); + + /** + * Returns the generalized type set of this and type. The result + * should be a type set, so that every type, is extended/implemented + * by one type in this and one type in type, iff it is + * extended/implemented by one type in the resulting type set. + * @param type the other type. + * @return the generalized type + */ + public abstract Type getGeneralizedType(Type type); + + public Type findCommonClassTypes(Stack otherTypes) { + /* Consider each class and interface implemented by this. + * If any clazz or interface in other implements it, add it to + * the classes vector. Otherwise consider all sub interfaces. + */ + Vector classes = new Vector(); + + type_loop: + while (!otherTypes.isEmpty()) { + ClassType type = (ClassType) otherTypes.pop(); + if (type.equals(tObject)) + /* tObject is always implied. */ + continue type_loop; + + for (Enumeration enumeration = classes.elements(); + enumeration.hasMoreElements(); ) { + if (type.isSuperTypeOf((Type) enumeration.nextElement())) + /* We can skip this, as another class already + * implies it. */ + continue type_loop; + } + + if (type.isSuperTypeOf(this)) { + classes.addElement(type); + continue type_loop; + } + + /* This clazz/interface is not implemented by this object. + * Try its parents now. + */ + ClassType ifaces[] = type.getInterfaces(); + for (int i=0; i < ifaces.length; i++) + otherTypes.push(ifaces[i]); + ClassType superClass = type.getSuperClass(); + if (superClass != null) + otherTypes.push(superClass); + } + ClassType[] classArray = new ClassType[classes.size()]; + classes.copyInto(classArray); + return MultiClassType.create(classArray); + } + + /** + * Creates a range type set of this and bottom. The resulting type set + * contains all types, that extend all types in bottom and are extended + * by at least one type in this.
    + * Note that a RangeType will do this, but we normalize the bottom and + * top set. + * @param bottom the bottom type. + * @return the range type set. + */ + public abstract Type createRangeType(ReferenceType bottom); + + /** + * Tells if all otherIfaces, are implemented by at least one + * ifaces or by clazz. + * + * This is a useful function for generalizing/specializing interface + * types or arrays. + * + * If it can't find all classes in the hierarchy, it will catch this + * error and return false, i.e. it assumes that the class doesn't + * implement all interfaces. + * + * @param clazz The clazz, can be null. + * @param ifaces The ifaces. + * @param otherifaces The other ifaces, that must be implemented. + * @return true, if all otherIfaces are implemented, false if unsure or + * if not all otherIfaces are implemented. + */ + protected static boolean implementsAllIfaces(ClassInfo clazz, + ClassInfo[] ifaces, + ClassInfo[] otherIfaces) { + try { + big: + for (int i=0; i < otherIfaces.length; i++) { + ClassInfo iface = otherIfaces[i]; + if (clazz != null && iface.implementedBy(clazz)) + continue big; + for (int j=0; j < ifaces.length; j++) { + if (iface.implementedBy(ifaces[j])) + continue big; + } + return false; + } + return true; + } catch (IOException ex) { + /* Class Hierarchy can't be fully gotten. */ + return false; + } + } + + /** + * Returns true, if all types in this type are possibly super + * types of the given type. If we don't have the full hierarchy + * of this type, assume it is. + */ + public boolean maybeSuperTypeOf(ClassType type) { + return false; + } + + public Type getSuperType() { + return (this == tObject) ? tObject : tRange(tObject, this); + } + + public abstract Type getSubType(); + + /** + * Intersect this type with another type and return the new type. + * @param type the other type. + * @return the intersection, or tError, if a type conflict happens. + */ + public Type intersection(Type type) { + if (type == tError) + return type; + if (type == Type.tUnknown) + return this; + + Type newBottom = getSpecializedType(type); + Type newTop = getGeneralizedType(type); + Type result; + if (newTop.equals(newBottom)) + result = newTop; + else if (newTop instanceof ReferenceType + && newBottom instanceof ReferenceType) + result = ((ReferenceType) newTop) + .createRangeType((ReferenceType) newBottom); + else + result = tError; + + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) { + GlobalOptions.err.println("intersecting "+ this +" and "+ type + + " to " + result); + } + return result; + } +} diff --git a/jode/src/net/sf/jode/type/SystemClassType.java b/jode/src/net/sf/jode/type/SystemClassType.java new file mode 100644 index 0000000..d259adb --- /dev/null +++ b/jode/src/net/sf/jode/type/SystemClassType.java @@ -0,0 +1,72 @@ +/* SystemClassType Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.bytecode.ClassInfo; +import java.util.Vector; +import java.util.Stack; +import java.util.Hashtable; +import java.io.IOException; + +/** + * This class represents the type of a system class, i.e. the classes + * from package java.lang, that need special handling, like + * Object, String, StringBuffer, etc. + * + * @author Jochen Hoenicke + */ +public class SystemClassType extends ClassType { + ClassType superType; + ClassType[] ifacesTypes; + boolean isFinal, isInterface; + + /** + * @param className The name of this system class, must be interned. + */ + public SystemClassType(String className, + ClassType superType, + ClassType[] ifacesTypes, + boolean isFinal, boolean isInterface) { + super(TC_SYSCLASS, className); + this.superType = superType; + this.ifacesTypes = ifacesTypes; + this.isFinal = isFinal; + this.isInterface = isInterface; + } + + public boolean isInterface() { + return isInterface; + } + + public boolean isFinal() { + return isFinal; + } + + public boolean isUnknown() { + return false; + } + + public ClassType getSuperClass() { + return superType; + } + + public ClassType[] getInterfaces() { + return ifacesTypes; + } +} diff --git a/jode/src/net/sf/jode/type/Type.java b/jode/src/net/sf/jode/type/Type.java new file mode 100644 index 0000000..cb80a72 --- /dev/null +++ b/jode/src/net/sf/jode/type/Type.java @@ -0,0 +1,694 @@ +/* Type Copyright (C) 1998-2005 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.type; +import net.sf.jode.GlobalOptions; +import net.sf.jode.bytecode.ClassPath; +import net.sf.jode.bytecode.ClassInfo; +import net.sf.jode.bytecode.TypeSignature; +import net.sf.jode.util.UnifyHash; + +///#def COLLECTIONS java.util +import java.util.Collections; +import java.util.Map; +import java.util.Iterator; +import java.util.Arrays; +///#enddef + +/** + * This is my type class. It differs from java.lang.class, in that it + * represents a set of types. Since most times this set is infinite, it + * needs a special representation.
    + * + * The main operation on a type sets are tSuperType, tSubType and + * intersection. + * + * @author Jochen Hoenicke */ +public class Type { + public static final int TC_BOOLEAN = 0; + public static final int TC_BYTE = 1; + public static final int TC_CHAR = 2; + public static final int TC_SHORT = 3; + public static final int TC_INT = 4; + public static final int TC_LONG = 5; + public static final int TC_FLOAT = 6; + public static final int TC_DOUBLE = 7; + public static final int TC_NULL = 8; + public static final int TC_ARRAY = 9; + public static final int TC_CLASS = 10; + public static final int TC_VOID = 11; + public static final int TC_METHOD = 12; + public static final int TC_ERROR = 13; + public static final int TC_UNKNOWN = 101; + public static final int TC_RANGE = 103; + public static final int TC_INTEGER = 107; + public static final int TC_SYSCLASS = 108; + public static final int TC_CLASSIFACE = 109; + public static final int TC_PARAMETER = 110; + + private static final UnifyHash classHash = new UnifyHash(); + private static final UnifyHash arrayHash = new UnifyHash(); + private static final UnifyHash methodHash = new UnifyHash(); + + /** + * This type represents the singleton set containing the boolean type. + */ + public static final Type tBoolean = new IntegerType(IntegerType.IT_Z); + /** + * This type represents the singleton set containing the byte type. + */ + public static final Type tByte = new IntegerType(IntegerType.IT_B); + /** + * This type represents the singleton set containing the char type. + */ + public static final Type tChar = new IntegerType(IntegerType.IT_C); + /** + * This type represents the singleton set containing the short type. + */ + public static final Type tShort = new IntegerType(IntegerType.IT_S); + /** + * This type represents the singleton set containing the int type. + */ + public static final Type tInt = new IntegerType(IntegerType.IT_I); + /** + * This type represents the singleton set containing the long type. + */ + public static final Type tLong = new Type(TC_LONG); + /** + * This type represents the singleton set containing the float type. + */ + public static final Type tFloat = new Type(TC_FLOAT); + /** + * This type represents the singleton set containing the double type. + */ + public static final Type tDouble = new Type(TC_DOUBLE); + /** + * This type represents the void type. It is really not a type at + * all. + */ + public static final Type tVoid = new Type(TC_VOID); + /** + * This type represents the empty set, and probably means, that something + * has gone wrong. + */ + public static final Type tError = new Type(TC_ERROR); + /** + * This type represents the set of all possible types. + */ + public static final Type tUnknown = new Type(TC_UNKNOWN); + /** + * This type represents the set of all integer types, up to 32 bit. + */ + public static final Type tUInt = new IntegerType(IntegerType.IT_I + | IntegerType.IT_B + | IntegerType.IT_C + | IntegerType.IT_S); + /** + * This type represents the set of the boolean and int type. + */ + public static final Type tBoolInt = new IntegerType(IntegerType.IT_I + | IntegerType.IT_Z); + /** + * This type represents the set of boolean and all integer types, + * up to 32 bit. + */ + public static final Type tBoolUInt= new IntegerType(IntegerType.IT_I + | IntegerType.IT_B + | IntegerType.IT_C + | IntegerType.IT_S + | IntegerType.IT_Z); + /** + * This type represents the set of the boolean and byte type. + */ + public static final Type tBoolByte= new IntegerType(IntegerType.IT_B + | IntegerType.IT_Z); + + public final static ClassType[] EMPTY_IFACES = new ClassType[0]; + /** + * This type represents the singleton set containing + * java.lang.Object. + */ + public static final SystemClassType tObject = + tSystemClass("java.lang.Object", + null, EMPTY_IFACES, false, false); + /** + * This type represents the singleton set containing the special + * null type (the type of null). + */ + public static final ReferenceType tNull = new NullType(); + /** + * This type represents the set of all reference types, including + * class types, array types, interface types and the null type. + */ + public static final Type tUObject = tRange(tObject, tNull); + + /** + * This type represents the singleton set containing + * java.lang.Comparable. + */ + public static final SystemClassType tSerializable = + tSystemClass("java.io.Serializable", + null, EMPTY_IFACES, false, true); + /** + * This type represents the singleton set containing + * java.lang.Comparable. + */ + public static final SystemClassType tCloneable = + tSystemClass("java.lang.Cloneable", + null, EMPTY_IFACES, false, true); + + static final ClassType[] arrayIfaces = { + tCloneable, tSerializable + }; + + /** + * This type represents the singleton set containing + * java.lang.String. + */ + /*FIXME */ + public static final ClassType tString = + tClass(new ClassPath("reflection:"), "java/lang/String"); + public static final ClassType tStringBuffer = + tClass(new ClassPath("reflection:"), "java/lang/StringBuffer"); + public static final ClassType tStringBuilder = + tClass(new ClassPath("reflection:"), "java/lang/StringBuilder"); + + /** + * Generate the singleton set of the type represented by the given + * string. + * @param classpath the current classpath. + * @param type the type signature (or method signature). + * @return a singleton set containing the given type. + */ + public static final Type tType(ClassPath cp, String type) { + return tType(cp, type, Collections.EMPTY_MAP); + } + + /** + * Generate the singleton set of the type represented by the given + * string. + * @param classpath the current classpath. + * @param signature the type signature (or method signature). + * @param parameterMap the map from type variables to real types. + * @return a singleton set containing the given type. + */ + public static final Type tType(ClassPath cp, String signature, + Map parameterMap) { + if (signature == null || signature.length() == 0) + return tError; + switch(signature.charAt(0)) { + case 'Z': + return tBoolean; + case 'B': + return tByte; + case 'C': + return tChar; + case 'S': + return tShort; + case 'I': + return tInt; + case 'F': + return tFloat; + case 'J': + return tLong; + case 'D': + return tDouble; + case 'V': + return tVoid; + case '[': + return tArray(tType(cp, signature.substring(1))); + case 'L': { + int endIndex = signature.length()-1; + Type[] generics = null; + if (signature.charAt(endIndex) != ';') + return tError; + if (signature.charAt(endIndex-1) == '>') { + /* parse parameter types */ + int index = signature.indexOf('<'); + String[] genericNames = TypeSignature + .getArgumentTypes(signature.substring(index, endIndex)); + endIndex = index; + generics = new Type[genericNames.length]; + for (int i = 0; i < generics.length; i++) { + String name = genericNames[i]; + char c = name.charAt(0); + if (c == '*') + generics[i] = Type.tUObject; + else if (c == '+') + generics[i] = Type.tType(cp, name.substring(1)) + .getSubType(); + else if (c == '-') + generics[i] = Type.tType(cp, name.substring(1)) + .getSuperType(); + else + generics[i] = Type.tType(cp, name); + } + } + return tClass(cp, signature.substring(1, endIndex), generics); + } + case 'T': { + int index = signature.indexOf(';'); + if (index != signature.length()-1) + return tError; + Type type = (Type) parameterMap.get(signature.substring(1, index)); + if (type == null) + return tError; + return type; + } + } + throw new InternalError("Unknown type signature: "+signature); + } + + /** + * Generate the singleton set of the type represented by the given + * class name. + * @param className the full qualified name of the class. + * The packages may be separated by `.' or `/'. + * @return a singleton set containing the given type. + */ + public static final ClassType tClass(ClassPath classPath, + String className, + Type[] generics) { + return tClass(classPath.getClassInfo(className.replace('/','.')), + generics); + } + /** + * @deprecated + */ + public static final ClassType tClass(ClassPath classPath, + String className){ + return tClass(classPath, className, null); + } + + /** + * Generate the singleton set of the type represented by the given + * class name. + * @param clazzname the interned full qualified name of the class. + * The packages mus be separated by `.'. + * @return a singleton set containing the given type. + */ + public static final SystemClassType tSystemClass + (String clazzName, ClassType superClass, ClassType[] ifaces, + boolean isFinal, boolean isInterface) { + return new SystemClassType(clazzName, superClass, ifaces, + isFinal, isInterface); + } + + /** + * Generate the singleton set of the type represented by the given + * class info. + * @param clazzinfo the net.sf.jode.bytecode.ClassInfo. + * @return a singleton set containing the given type. + */ + public static final ClassType tClass(ClassInfo clazzinfo, + Type[] generics) { + int hash = clazzinfo.hashCode(); + if (generics != null) { + for (int i = 0; i < generics.length; i++) + hash = hash * 11 + generics[i].hashCode(); + } + Iterator iter = classHash.iterateHashCode(hash); + while (iter.hasNext()) { + ClassInfoType type = (ClassInfoType) iter.next(); + if (type.getClassInfo() == clazzinfo + && Arrays.equals(generics, type.genInstances)) + return type; + } + ClassInfoType type = new ClassInfoType(clazzinfo, generics); + classHash.put(hash, type); + return type; + } + + /** + * Generate the singleton set of the type represented by the given + * class info. + * @param clazzinfo the net.sf.jode.bytecode.ClassInfo. + * @return a singleton set containing the given type. + * deprecated This should be removed if generics work. + */ + public static final ClassType tClass(ClassInfo clazzinfo) { + return tClass(clazzinfo, null); + } + + /** + * Generate/look up the set of the array type whose element types + * are in the given type set. + * @param type the element types (which may be the empty set tError). + * @return the set of array types (which may be the empty set tError). + */ + public static final Type tArray(Type type) { + if (type == tError) + return type; + + int hash = type.hashCode(); + Iterator iter = arrayHash.iterateHashCode(hash); + while (iter.hasNext()) { + ArrayType arrType = (ArrayType) iter.next(); + if (arrType.getElementType().equals(type)) + return arrType; + } + ArrayType arrType = new ArrayType(type); + arrayHash.put(hash, arrType); + return arrType; + } + + /** + * Generate/look up the method type for the given signature + * @param signature the method decriptor. + * @return a method type (a singleton set). + */ + public static MethodType tMethod(ClassPath cp, String signature) { + int hash = signature.hashCode() + cp.hashCode(); + Iterator iter = methodHash.iterateHashCode(hash); + while (iter.hasNext()) { + MethodType methodType = (MethodType) iter.next(); + if (methodType.getTypeSignature().equals(signature) + && methodType.getClassPath().equals(cp)) + return methodType; + } + MethodType methodType = new MethodType(cp, signature); + methodHash.put(hash, methodType); + return methodType; + } + + /** + * Generate the range type from bottom to top. This should + * represent all reference types, that can be casted to bottom by + * a widening cast and where top can be casted to. You should not + * use this method directly; use tSubType, tSuperType and + * intersection instead, which is more general. + * @param bottom the bottom type. + * @param top the top type. + * @return the range type. + */ + public static final Type tRange(ReferenceType bottom, + ReferenceType top) { + return new RangeType(bottom, top); + } + + /** + * Generate the set of types, to which one of the types in type can + * be casted to by a widening cast. The following holds: + *

    • tSuperType(tObject) = tObject
    • + *
    • tSuperType(tError) = tError
    • + *
    • type.intersection(tSuperType(type)).equals(type) + * (this means type is a subset of tSuperType(type).
    • + *
    • tSuperType(tNull) = tUObject
    • + *
    • tSuperType(tChar) = {tChar, tInt }
    + * @param type a set of types. + * @return the super types of type. + */ + public static Type tSuperType(Type type) { + return type.getSuperType(); + } + + /** + * Generate the set of types, which can be casted to one of the + * types in type by a widening cast. The following holds: + *
    • tSubType(tObject) = tUObject
    • + *
    • tSubType(tError) = tError
    • + *
    • type.intersection(tSubType(type)).equals(type) + * (this means type is a subset of tSubType(type).
    • + *
    • tSubType(tNull) = tNull
    • + *
    • tSubType({tBoolean, tShort}) = { tBoolean, tByte, tShort }
    + * @param type a set of types. + * @return the sub types of type. + */ + public static Type tSubType(Type type) { + return type.getSubType(); + } + + /** + * The typecode of this type. This should be one of the TC_ constants. + */ + final int typecode; + + /** + * Create a new type with the given type code. + */ + protected Type(int tc) { + typecode = tc; + } + + /** + * The sub types of this type. + * @return tSubType(this). + */ + public Type getSubType() { + return this; + } + + /** + * The super types of this type. + * @return tSuperType(this). + */ + public Type getSuperType() { + return this; + } + + /** + * Returns the hint type of this type set. This returns the singleton + * set containing only the `most likely' type in this set. This doesn't + * work for tError or tUnknown, and may lead + * to errors for certain range types. + * @return the hint type. + */ + public Type getHint() { + return getCanonic(); + } + + /** + * Returns the canonic type of this type set. The intention is, to + * return for each expression the type, that the java compiler would + * assign to this expression. + * @return the canonic type. + */ + public Type getCanonic() { + return this; + } + + /** + * Returns the type code of this type. Don't use this; it is + * merily needed by the sub types (and the bytecode verifier, which + * has its own type merging methods). + * @return the type code of the type. + */ + public final int getTypeCode() { + return typecode; + } + + /** + * Returns the number of stack/local entries an object of this type + * occupies. + * @return 0 for tVoid, 2 for tDouble and tLong and + * 1 for every other type. + */ + public int stackSize() + { + switch(typecode) { + case TC_VOID: + return 0; + case TC_ERROR: + default: + return 1; + case TC_DOUBLE: + case TC_LONG: + return 2; + } + } + + /** + * Returns true, if all types in this type set are a super type of + * at least one type in the type set given as parameter. + */ + public boolean isSuperTypeOf(Type type) { + return this == type; + } + + /** + * Returns true, if all types in this type are possibly super + * types of the given type. If we don't have the full hierarchy + * of this type, assume it is. + */ + public boolean maybeSuperTypeOf(ClassType type) { + return isSuperTypeOf(type); + } + + /** + * Intersect this set of types with another type set and return the + * intersection. + * @param type the other type set. + * @return the intersection, tError, if the intersection is empty. + */ + public Type intersection(Type type) { + if (this == tError || type == tError) + return tError; + if (this == tUnknown) + return type; + if (type == tUnknown || this == type) + return this; + /* We have two different singleton sets now. + */ + if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_TYPES) != 0) + GlobalOptions.err.println("intersecting "+ this +" and "+ type + + " to "); + return tError; + } + + /** + * Checks if we need to cast to a middle type, before we can cast from + * fromType to this type. For example it is impossible to cast a + * String to a StringBuffer, but if we cast to Object in between this + * is allowed (it doesn't make much sense though). + * @return the middle type, or null if it is not necessary. + */ + public Type getCastHelper(Type fromType) { + return null; + } + + /** + * Checks if this type represents a valid singleton type. + */ + public boolean isValidType() { + return typecode <= TC_DOUBLE; + } + + /** + * Checks if this is a class or array type (but not a null type). + * @XXX remove this? + * @return true if this is a class or array type. + */ + public boolean isClassType() { + return false; + } + + /** + * Check if this type set and the other type set are not disjunct. + * @param type the other type set. + * @return true if this they aren't disjunct. + */ + public boolean isOfType(Type type) { + return this.intersection(type) != Type.tError; + } + + /** + * Check if this type set contains the given class. + * @param clazz the class to check. + * @return true if clazz is contained in this type. + */ + public boolean containsClass(ClassInfo clazz) { + return false; + } + + /** + * Generates the default name, that is the `natural' choice for + * local of this type. + * @return the default name of a local of this type. + */ + public String getDefaultName() { + switch (typecode) { + case TC_LONG: + return "l"; + case TC_FLOAT: + return "f"; + case TC_DOUBLE: + return "d"; + default: + return "local"; + } + } + + /** + * Generates the default value, that is the initial value of a field + * of this type. + * @return the default value of a field of this type. + */ + public Object getDefaultValue() { + switch (typecode) { + case TC_LONG: + return new Long(0); + case TC_FLOAT: + return new Float(0); + case TC_DOUBLE: + return new Double(0); + default: + return null; + } + } + + /** + * Returns the type signature of this type. You should only call + * this on singleton types. + * @return the type (or method) signature of this type. + */ + public String getTypeSignature() { + switch (typecode) { + case TC_LONG: + return "J"; + case TC_FLOAT: + return "F"; + case TC_DOUBLE: + return "D"; + default: + return "?"; + } + } + + /** + * Returns the java.lang.Class representing this type. You should + * only call this on singleton types. + * @return the Class object representing this type. + */ + public Class getTypeClass() throws ClassNotFoundException { + switch (typecode) { + case TC_LONG: + return Long.TYPE; + case TC_FLOAT: + return Float.TYPE; + case TC_DOUBLE: + return Double.TYPE; + default: + throw new InternalError("getTypeClass() called on illegal type"); + } + } + + /** + * Returns a string representation describing this type set. + * @return a string representation describing this type set. + */ + public String toString() { + switch (typecode) { + case TC_LONG: + return "long"; + case TC_FLOAT: + return "float"; + case TC_DOUBLE: + return "double"; + case TC_NULL: + return "null"; + case TC_VOID: + return "void"; + case TC_UNKNOWN: + return ""; + case TC_ERROR: + default: + return ""; + } + } +} diff --git a/jode/src/net/sf/jode/util/ArrayEnum.java b/jode/src/net/sf/jode/util/ArrayEnum.java new file mode 100644 index 0000000..f4e2647 --- /dev/null +++ b/jode/src/net/sf/jode/util/ArrayEnum.java @@ -0,0 +1,38 @@ +/* ArrayEnum Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.util; + +public class ArrayEnum implements java.util.Enumeration { + int index = 0; + int size; + Object[] array; + + public ArrayEnum(int size, Object[] array) { + this.size = size; + this.array = array; + } + + public boolean hasMoreElements() { + return index < size; + } + public Object nextElement() { + return array[index++]; + } +} diff --git a/jode/src/net/sf/jode/util/SimpleMap.java b/jode/src/net/sf/jode/util/SimpleMap.java new file mode 100644 index 0000000..fe9c1e0 --- /dev/null +++ b/jode/src/net/sf/jode/util/SimpleMap.java @@ -0,0 +1,98 @@ +/* SimpleMap Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.util; +///#def COLLECTIONS java.util +import java.util.AbstractMap; +import java.util.Map; +import java.util.Iterator; +import java.util.Set; +///#enddef + +/** + * This is a very simple map, using a set as backing. + * The default backing set is a simple set, but you can specify any other + * set of Map.Entry in the constructor. + */ +public class SimpleMap extends AbstractMap { + private Set backing; + + public SimpleMap() { + backing = new SimpleSet(); + } + + public SimpleMap(int initialCapacity) { + backing = new SimpleSet(initialCapacity); + } + + public SimpleMap(Set fromSet) { + backing = fromSet; + } + + public Set entrySet() { + return backing; + } + + public static class SimpleEntry implements Map.Entry { + Object key; + Object value; + + public SimpleEntry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object newValue) { + Object old = value; + value = newValue; + return old; + } + + public int hashCode() { + return key.hashCode() ^ value.hashCode(); + } + + public boolean equals(Object o) { + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry) o; + return key.equals(e.getKey()) && value.equals(e.getValue()); + } + return false; + } + } + + public Object put(Object key, Object value) { + for (Iterator i = backing.iterator(); + i.hasNext(); ) { + Map.Entry entry = (Map.Entry) i.next(); + if (key.equals(entry.getKey())) + return entry.setValue(value); + } + backing.add(new SimpleEntry(key, value)); + return null; + } +} diff --git a/jode/src/net/sf/jode/util/SimpleSet.java b/jode/src/net/sf/jode/util/SimpleSet.java new file mode 100644 index 0000000..2c758fb --- /dev/null +++ b/jode/src/net/sf/jode/util/SimpleSet.java @@ -0,0 +1,92 @@ +/* SimpleSet Copyright (C) 1998-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.util; +///#def COLLECTIONS java.util +import java.util.AbstractSet; +import java.util.Iterator; +///#enddef + +public class SimpleSet extends AbstractSet implements Cloneable +{ + Object[] elementObjects; + int count = 0; + + public SimpleSet() { + this(2); + } + + public SimpleSet(int initialSize) { + elementObjects = new Object[initialSize]; + } + + public int size() { + return count; + } + + public boolean add(Object element) { + if (element == null) + throw new NullPointerException(); + + for (int i=0; i< count; i++) { + if (element.equals(elementObjects[i])) + return false; + } + + if (count == elementObjects.length) { + Object[] newArray = new Object[(count+1)*3/2]; + System.arraycopy(elementObjects,0,newArray,0,count); + elementObjects = newArray; + } + elementObjects[count++] = element; + return true; + } + + public Object clone() { + try { + SimpleSet other = (SimpleSet) super.clone(); + other.elementObjects = (Object[]) elementObjects.clone(); + return other; + } catch (CloneNotSupportedException ex) { + throw new InternalError("Clone?"); + } + } + + public Iterator iterator() { + return new Iterator() { + int pos = 0; + + public boolean hasNext() { + return pos < count; + } + + public Object next() { + return elementObjects[pos++]; + } + + public void remove() { + if (pos < count) + System.arraycopy(elementObjects, pos, + elementObjects, pos-1, count - pos); + count--; + pos--; + } + }; + } +} diff --git a/jode/src/net/sf/jode/util/StringQuoter.java b/jode/src/net/sf/jode/util/StringQuoter.java new file mode 100644 index 0000000..8e46430 --- /dev/null +++ b/jode/src/net/sf/jode/util/StringQuoter.java @@ -0,0 +1,101 @@ +/* StringQuoter Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.util; +/** + * This is a simple class to quote a string or a char. It puts it in + * quotes (" resp. ') and prints special chars with the same syntax as + * strings and chars in java source codes. + */ +public class StringQuoter { + /** + * This is the static method, that quotes a string. + */ + public static String quote(String str) { + StringBuffer result = new StringBuffer("\""); + for (int i=0; i< str.length(); i++) { + char c; + switch (c = str.charAt(i)) { + case '\0': + result.append("\\0"); + break; + case '\t': + result.append("\\t"); + break; + case '\n': + result.append("\\n"); + break; + case '\r': + result.append("\\r"); + break; + case '\\': + result.append("\\\\"); + break; + case '\"': + result.append("\\\""); + break; + default: + if (c < 32) { + String oct = Integer.toOctalString(c); + result.append("\\000".substring(0, 4-oct.length())) + .append(oct); + } else if (c >= 32 && c < 127) + result.append(str.charAt(i)); + else { + String hex = Integer.toHexString(c); + result.append("\\u0000".substring(0, 6-hex.length())) + .append(hex); + } + } + } + return result.append("\"").toString(); + } + + /** + * This is the static method, that quotes a char. + */ + public static String quote(char c) { + switch (c) { + case '\0': + return "\'\\0\'"; + case '\t': + return "\'\\t\'"; + case '\n': + return "\'\\n\'"; + case '\r': + return "\'\\r\'"; + case '\\': + return "\'\\\\\'"; + case '\"': + return "\'\\\"\'"; + case '\'': + return "\'\\\'\'"; + } + if (c < 32) { + String oct = Integer.toOctalString(c); + return "\'\\000".substring(0, 5-oct.length())+oct+"\'"; + } + if (c >= 32 && c < 127) + return "\'"+c+"\'"; + else { + String hex = Integer.toHexString(c); + return "\'\\u0000".substring(0, 7-hex.length())+hex+"\'"; + } + } +} diff --git a/jode/src/net/sf/jode/util/UnifyHash.java b/jode/src/net/sf/jode/util/UnifyHash.java new file mode 100644 index 0000000..b9260d9 --- /dev/null +++ b/jode/src/net/sf/jode/util/UnifyHash.java @@ -0,0 +1,293 @@ +/* UnifyHash Copyright (C) 1999-2002 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; see the file COPYING.LESSER. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package net.sf.jode.util; +///#ifdef JDK12 +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; +///#endif + +///#def COLLECTIONS java.util +import java.util.Comparator; +import java.util.AbstractCollection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.ConcurrentModificationException; +///#enddef +///#def COLLECTIONEXTRA java.lang +import java.lang.UnsupportedOperationException; +///#enddef + +public class UnifyHash extends AbstractCollection { + /** + * the default capacity + */ + private static final int DEFAULT_CAPACITY = 11; + + /** the default load factor of a HashMap */ + private static final float DEFAULT_LOAD_FACTOR = 0.75F; + +///#ifdef JDK12 + private ReferenceQueue queue = new ReferenceQueue(); +///#endif + + static class Bucket +///#ifdef JDK12 + extends WeakReference +///#endif + { +///#ifdef JDK12 + public Bucket(Object o, ReferenceQueue q) { + super(o, q); + } +///#else +/// public Bucket(Object o) { +/// this.obj = o; +/// } +/// +/// Object obj; +/// +/// public Object get() { +/// return obj; +/// } +///#endif + + int hash; + Bucket next; + } + + private Bucket[] buckets; + int modCount = 0; + int size = 0; + int threshold; + float loadFactor; + + public UnifyHash(int initialCapacity, float loadFactor) { + this.loadFactor = loadFactor; + buckets = new Bucket[initialCapacity]; + threshold = (int) (loadFactor * initialCapacity); + } + + public UnifyHash(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + public UnifyHash() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + private void grow() { + Bucket[] oldBuckets = buckets; + int newCap = buckets.length * 2 + 1; + threshold = (int) (loadFactor * newCap); + buckets = new Bucket[newCap]; + for (int i = 0; i < oldBuckets.length; i++) { + Bucket nextBucket; + for (Bucket b = oldBuckets[i]; b != null; b = nextBucket) { + if (i != Math.abs(b.hash % oldBuckets.length)) + throw new RuntimeException(""+i+", hash: "+b.hash+", oldlength: "+oldBuckets.length); + int newSlot = Math.abs(b.hash % newCap); + nextBucket = b.next; + b.next = buckets[newSlot]; + buckets[newSlot] = b; + } + } + } + +///#ifdef JDK12 + public final void cleanUp() { + Bucket died; + while ((died = (Bucket)queue.poll()) != null) { + int diedSlot = Math.abs(died.hash % buckets.length); + if (buckets[diedSlot] == died) + buckets[diedSlot] = died.next; + else { + Bucket b = buckets[diedSlot]; + while (b.next != died) + b = b.next; + b.next = died.next; + } + size--; + } + } +///#endif + + + public int size() { + return size; + } + + public Iterator iterator() { +///#ifdef JDK12 + cleanUp(); +///#endif + + return new Iterator() { + private int bucket = 0; + private int known = modCount; + private Bucket nextBucket; + private Object nextVal; + + { + internalNext(); + } + + private void internalNext() { + while (true) { + while (nextBucket == null) { + if (bucket == buckets.length) + return; + nextBucket = buckets[bucket++]; + } + + nextVal = nextBucket.get(); + if (nextVal != null) + return; + + nextBucket = nextBucket.next; + } + } + + public boolean hasNext() { + return nextBucket != null; + } + + public Object next() { + if (known != modCount) + throw new ConcurrentModificationException(); + if (nextBucket == null) + throw new NoSuchElementException(); + Object result = nextVal; + nextBucket = nextBucket.next; + internalNext(); + return result; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public Iterator iterateHashCode(final int hash) { +///#ifdef JDK12 + cleanUp(); +///#endif + return new Iterator() { + private int known = modCount; + private boolean removeOk = false; + private Bucket removeBucket = null; + private Bucket prevBucket = null; + private Bucket nextBucket + = buckets[Math.abs(hash % buckets.length)]; + private Object nextVal; + + { + internalNext(); + } + + private void internalNext() { + while (nextBucket != null) { + if (nextBucket.hash == hash) { + nextVal = nextBucket.get(); + if (nextVal != null) + return; + } + prevBucket = nextBucket; + nextBucket = nextBucket.next; + } + } + + public boolean hasNext() { + return nextBucket != null; + } + + public Object next() { + if (known != modCount) + throw new ConcurrentModificationException(); + if (nextBucket == null) + throw new NoSuchElementException(); + Object result = nextVal; + removeBucket = prevBucket; + removeOk = true; + prevBucket = nextBucket; + nextBucket = nextBucket.next; + internalNext(); + return result; + } + + public void remove() { + if (known != modCount) + throw new ConcurrentModificationException(); + if (!removeOk) + throw new IllegalStateException(); + if (removeBucket == null) + buckets[Math.abs(hash % buckets.length)] + = buckets[Math.abs(hash % buckets.length)].next; + else + removeBucket.next = removeBucket.next.next; + known = ++modCount; + size--; + } + }; + } + + public void put(int hash, Object o) { + if (size++ > threshold) + grow(); + modCount++; + + int slot = Math.abs(hash % buckets.length); +///#ifdef JDK12 + Bucket b = new Bucket(o, queue); +///#else +/// Bucket b = new Bucket(o); +///#endif + b.hash = hash; + b.next = buckets[slot]; + buckets[slot] = b; + } + + public boolean remove(int hash, Object o) { + Iterator i = iterateHashCode(hash); + while (i.hasNext()) { + if (i.next() == o) { + i.remove(); + return true; + } + } + return false; + } + + public Object unify(Object o, int hash, Comparator comparator) { +///#ifdef JDK12 + cleanUp(); +///#endif + int slot = Math.abs(hash % buckets.length); + for (Bucket b = buckets[slot]; b != null; b = b.next) { + Object old = b.get(); + if (old != null && comparator.compare(o, old) == 0) + return old; + } + + put(hash, o); + return o; + } +} + diff --git a/jode/test/.cvsignore b/jode/test/.cvsignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/jode/test/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/jode/test/AnonymousClass.java b/jode/test/AnonymousClass.java new file mode 100644 index 0000000..07800b8 --- /dev/null +++ b/jode/test/AnonymousClass.java @@ -0,0 +1,204 @@ +/* AnonymousClass Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +import java.util.Vector; + +public class AnonymousClass { +///#ifndef JAVAC11 + // javac 1.1 is tooooooo broken + class Inner { + int var = 3; + + public void test() { + final long longVar = 5; + final double dblVar = 3; + + class Hello { + int var = (int) longVar; + + { + System.err.println("all constructors"); + } + + Hello() { + System.err.println("construct"); + } + Hello(String info) { + System.err.println("construct: "+info); + } +///#ifndef JAVAC12 +///#ifndef JIKES0 + Hello(int i) { + this("This can only be compiled correctly" + +" by a recent jikes"); + } +///#endif +///#endif + private void hello() { + this.hashCode(); + Inner.this.hashCode(); + AnonymousClass.this.hashCode(); + System.err.println("HelloWorld: "+dblVar); + } + }; + final Hello hi = new Hello(); + final Hello ho = new Hello("ho"); + final Object o = new Object() { + int blah = 5; + + { + System.err.println("Anonymous Constructor speaking"); + } + + Hello hii = hi; + + public String toString() { + this.hii.hello(); + hi.hello(); + return Integer.toHexString(AnonymousClass.this.hashCode() + +blah); + } + + { + System.err.println("Anonymous Constructor continues"); + } + + }; + Object p = new Object() { + public String toString() { + return o.toString(); + } + }; +///#ifndef JAVAC12 + Hello blah = new Hello("Hello World") { + public void hello() { + System.err.println("overwritten" + dblVar + hi); + } + }; +///#endif + + Inner blub = new AnonymousClass().new Inner("Inner param") { + public void test() { + System.err.println("overwritten"); + } + }; + + class Hi extends Inner { + public Hi() { + super("Hi World"); + } + } + +///#ifndef JAVAC12 + class Huhu extends Hello { + public Huhu(String str) { + super(str); + } + + public Huhu(int i) { + } + + public Huhu() { + super("What's up"); + } + } +///#endif + + Vector v = new Vector(hi.var, new Inner("blah").var) { + public String newMethod() { + return super.toString(); + } + }; + + Hi hu = new Hi(); + new Huhu(1); + + } + Inner (String str) { + } + } + + + public void test() { + class Hello { + int var = 4; + + Hello() { + System.err.println("construct"); + } + Hello(String info) { + System.err.println("construct: "+info); + } + + public void hello() { + this.hashCode(); + AnonymousClass.this.hashCode(); + System.err.println("HelloWorld"); + } + }; + final Hello hi = new Hello(); + final Hello ho = new Hello("ho"); + final Object o = new Object() { + int blah = 5; + + Hello hii = hi; + + public String toString() { + this.hii.hello(); + hi.hello(); + return Integer.toHexString(AnonymousClass.this.hashCode() + +blah); + } + }; + Object p = new Object() { + public String toString() { + return o.toString(); + } + }; +///#ifndef JAVAC12 + Hello blah = new Hello("Hello World") { + public void hello() { + System.err.println("overwritten"); + } + }; +///#endif + + Inner blub = new Inner("Inner param") { + public void test() { + System.err.println("overwritten"); + } + }; + + class Hi extends Inner { + public Hi() { + super("Hi World"); + } + } + + Vector v = new Vector(hi.var, new Inner("blah").var) { + public String newMethod() { + return super.toString(); + } + }; + + Hi hu = new Hi(); + + } +///#endif +} diff --git a/jode/test/AnonymousJavac.java b/jode/test/AnonymousJavac.java new file mode 100644 index 0000000..8e13423 --- /dev/null +++ b/jode/test/AnonymousJavac.java @@ -0,0 +1,181 @@ +/* AnonymousJavac Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +import java.util.Vector; + +public class AnonymousJavac { + class Inner { + int var = 3; + + public void test() { + final long longVar = 5; + final double dblVar = 3; + class Hello { + int var = (int) longVar; + + { + System.err.println("all constructors"); + } + + Hello() { + System.err.println("construct"); + } + Hello(String info) { + System.err.println("construct: "+info); + } + + private void hello() { + this.hashCode(); + Inner.this.hashCode(); + Inner.this.var = var; + AnonymousJavac.this.hashCode(); + System.err.println("HelloWorld: "+dblVar); + } + }; + final Hello hi = new Hello(); + final Hello ho = new Hello("ho"); + final Object o = new Object() { + int blah = 5; + Hello hii = hi; + Hello hoo = new Hello("hoo"); + + { + System.err.println("Anonymous Constructor speaking"); + } + + public String toString() { + this.hii.hello(); + hi.hello(); + return Integer.toHexString(AnonymousJavac.this.hashCode() + +blah); + } + + { + System.err.println("Anonymous Constructor continues"); + } + + }; + Object p = new Object() { + public String toString() { + return o.toString(); + } + }; + + Inner blub1 = new Inner("Inner param") { + Hello hii = hi; + + public void test() { + System.err.println("overwritten: "+hii+hi); + } + }; + + Inner blub2 = new AnonymousJavac().new Inner("Inner param") { + Hello hii = hi; + + public void test() { + System.err.println("overwritten: "+hii); + AnonymousJavac.this.hashCode(); + } + }; + + class Hi extends Inner { + public Hi() { + super("Hi World"); + } + } + + Vector v = new Vector(hi.var, var) { + Hello hii = hi; + public Object clone() { + return super.clone(); + } + }; + + Hi hu = new Hi(); + + } + Inner (String str) { + } + } + + + public void test() { + class Hello { + int var = 4; + + Hello() { + System.err.println("construct"); + } + Hello(String info) { + System.err.println("construct: "+info); + } + + public void hello() { + this.hashCode(); + AnonymousJavac.this.hashCode(); + System.err.println("HelloWorld"); + } + }; + final Hello hi = new Hello(); + final Hello ho = new Hello("ho"); + final Object o = new Object() { + int blah = 5; + Hello hii = hi; + + public String toString() { + this.hii.hello(); + hi.hello(); + return Integer.toHexString(AnonymousJavac.this.hashCode() + +blah); + } + }; + Object p = new Object() { + public String toString() { + return o.toString(); + } + }; + + Inner blub1 = new Inner("Inner param") { + public void test() { + System.err.println("overwritten"); + } + }; + + Inner blub2 = new AnonymousJavac().new Inner("Inner param") { + public void test() { + System.err.println("overwritten"); + } + }; + + class Hi extends Inner { + public Hi() { + super("Hi World"); + } + } + + Vector v = new Vector(hi.var, new Inner("Hi").var) { + public Object clone() { + return super.clone(); + } + }; + + Hi hu = new Hi(); + } +} diff --git a/jode/test/ArrayCloneTest.java b/jode/test/ArrayCloneTest.java new file mode 100644 index 0000000..f709d27 --- /dev/null +++ b/jode/test/ArrayCloneTest.java @@ -0,0 +1,28 @@ +/* ArrayCloneTest Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class ArrayCloneTest { + + public void test() { + int[] i = {4,3,2}; + int[] j = (int[]) i.clone(); + } + +} diff --git a/jode/test/ArrayTest.java b/jode/test/ArrayTest.java new file mode 100644 index 0000000..61f4722 --- /dev/null +++ b/jode/test/ArrayTest.java @@ -0,0 +1,92 @@ +/* ArrayTest Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +import java.io.*; +import java.lang.reflect.*; + +public class ArrayTest { + Serializable s; + Serializable[] u; + Cloneable c; + + public void test() { + int[] i = {4,3,2}; + i = new int[] {1, 2, 3}; + int[] j = new int[] {4,5,6}; + + int[][] k = {i,j}; + + u = k; + s = i; + c = i; + } + + public void typetest() { + int[] arr = null; + s = arr; + c = arr; + arr[0] = 3; + arr = arr != null ? arr : new int[4]; + } + + public static void main(String[] param) { + int[] arr = new int[4]; + Class cls = arr.getClass(); + System.err.println("int[].getClass() is: "+cls); + System.err.println("int[].getClass().getSuperclass() is: " + + cls.getSuperclass()); + Class[] ifaces = cls.getInterfaces(); + System.err.print("int[].getClass().getInterfaces() are: "); + for (int i = 0; i < ifaces.length; i++) { + if (i > 0) + System.err.print(", "); + System.err.print(ifaces[i]); + } + System.err.println(); + + Field[] fields = cls.getDeclaredFields(); + System.err.print("int[].getClass().getDeclaredFields() are: "); + for (int i = 0; i < fields.length; i++) { + if (i > 0) + System.err.print(", "); + System.err.print(fields[i]); + } + System.err.println(); + + Method[] methods = cls.getDeclaredMethods(); + System.err.print("int[].getClass().getDeclaredMethods() are: "); + for (int i = 0; i < methods.length; i++) { + if (i > 0) + System.err.print(", "); + System.err.print(methods[i]); + } + System.err.println(); + + Object o = arr; + System.err.println("arr instanceof Serializable: "+ + (o instanceof Serializable)); + System.err.println("arr instanceof Externalizable: "+ + (o instanceof Externalizable)); + System.err.println("arr instanceof Cloneable: "+ + (o instanceof Cloneable)); +// System.err.println("arr instanceof Comparable: "+ +// (o instanceof Comparable)); + } +} + diff --git a/jode/test/AssignOp.java b/jode/test/AssignOp.java new file mode 100644 index 0000000..680b7c5 --- /dev/null +++ b/jode/test/AssignOp.java @@ -0,0 +1,90 @@ +/* AssignOp Copyright (C) 1998-1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class AssignOp { + static short static_short; + static int static_int; + static double static_double; + static String static_String; + static long static_long; + + short obj_short; + int obj_int; + long obj_long; + double obj_double; + String obj_String; + + short[] arr_short; + int [] arr_int; + long[] arr_long; + double[] arr_double; + String[] arr_String; + + void assop() { + short local_short = 0; + int local_int = 0; + long local_long = 0; + double local_double = 1.0; + String local_String = null; + + local_short -= 25 * local_int; + static_short += 100 - local_int; + obj_short /= 0.1; + arr_short[local_int] >>= 25; + + local_long -= 15L; + static_long <<= local_int; + obj_long >>>= 3; + arr_long[4+local_int] *= obj_long - static_long; + + local_int |= 25 | local_int; + static_int <<= 3; + obj_int *= 17 + obj_int; + arr_int[local_int] /= (obj_int+=7); + + local_double /= 3.0; + static_double *= obj_int; + obj_double -= 25; + arr_double[local_int] /= (local_double+=7.0); + + local_String += "Hallo"; + static_String += "Hallo"; + obj_String += "Hallo"; + arr_String[0] += local_double + static_String + "Hallo" + obj_int; + } + + void prepost() { + int local_int= -1; + long local_long= 4; + + local_long = local_int++; + obj_long = ++obj_int; + arr_long[static_int] = static_long = (arr_long[--static_int] = (static_int--))+1; + } + + void iinc() { + int local_int = 0; + local_int += 5; + obj_int = (local_int -= 5); + + static_int = local_int++; + obj_int = --local_int; + } +} diff --git a/jode/test/Base.j b/jode/test/Base.j new file mode 100644 index 0000000..c6a3daa --- /dev/null +++ b/jode/test/Base.j @@ -0,0 +1,12 @@ +.class public Base +.super java/lang/Object + +.field public static test I +.field public test J + +.method ()V + aload_0 + invokespecial java/lang/Object/()V + return +.end method + \ No newline at end of file diff --git a/jode/test/Child.j b/jode/test/Child.j new file mode 100644 index 0000000..2b07e27 --- /dev/null +++ b/jode/test/Child.j @@ -0,0 +1,29 @@ +.class public Child +.super Base + +.field private test I + +.method ()V + .limit locals 1 + .limit stack 2 + aload_0 + invokespecial Base/()V + getstatic Base/test I + pop + aload_0 + getfield Base/test J + pop2 + aload_0 + getfield Child/test I + pop + aload_0 + getfield Child/test J + pop2 + return +.end method + +.method public static main([Ljava/lang/String;)V + .limit locals 1 + .limit stack 0 + return +.end method diff --git a/jode/test/ClassOpTest.java b/jode/test/ClassOpTest.java new file mode 100644 index 0000000..c487f40 --- /dev/null +++ b/jode/test/ClassOpTest.java @@ -0,0 +1,36 @@ +/* ClassOpTest Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class ClassOpTest { + static void test1() { + Class c1 = String.class; + Class c2 = Object.class; + if (ClassOpTest.class == null); + c1.getClass(); + } + + void test2() { + Class c2 = Object.class; + Class c3 = ClassOpTest.class; + Class c4 = int[].class; + Class c5 = Object[][].class; + Class c6 = int.class; + } +} diff --git a/jode/test/ConstantTypes.java b/jode/test/ConstantTypes.java new file mode 100644 index 0000000..2d20721 --- /dev/null +++ b/jode/test/ConstantTypes.java @@ -0,0 +1,71 @@ +/* ConstantTypes Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class ConstantTypes { + static boolean bool = true; + static byte b = (byte) 0x80; + static char c = '\u0080'; + static short s = (short)'\u8234'; + static int i = '\uffff'; + + static void intFunc(int i){} + static void shortFunc(short s){} + static void charFunc(char c){} + static void byteFunc(byte b){} + + static { + /* All casts are necessaray */ + intFunc(25); + shortFunc((short) 25); + charFunc((char) 25); + byteFunc((byte) 25); + intFunc('\u0019'); + shortFunc((short) '\u0019'); + charFunc('\u0019'); + byteFunc((byte) '\u0019'); + intFunc(b); + intFunc(c); + intFunc(s); + intFunc(i); + shortFunc(b); + shortFunc((short)c); + shortFunc(s); + shortFunc((short)i); + charFunc((char)b); + charFunc(c); + charFunc((char)s); + charFunc((char)i); + byteFunc(b); + byteFunc((byte)c); + byteFunc((byte)s); + byteFunc((byte)i); + b = 42; + c = 42; + s = 42; + i = 42; + i = c; + s = b; + i = s; + c = (char) s; + s = (short) c; + c = (char) b; + b = (byte) c; + } +} diff --git a/jode/test/CountOpcodes.java b/jode/test/CountOpcodes.java new file mode 100644 index 0000000..79a6d25 --- /dev/null +++ b/jode/test/CountOpcodes.java @@ -0,0 +1,99 @@ +import jode.bytecode.*; +import java.util.*; +import com.sun.java.util.collections.Iterator; + +public class CountOpcodes { + static int[] opcodeCount = new int[256]; + static int[] predsCount = new int[1024]; + static int[] succsCount = new int[1024]; + static Vector instructions = new Vector(73400); + + public static void handleBytecode(BytecodeInfo bc) { + for (Iterator i = bc.getInstructions().iterator(); i.hasNext();) { + Instruction instr = (Instruction) i.next(); + instructions.addElement(instr); + opcodeCount[instr.getOpcode()]++; + Instruction[] p = instr.getPreds(); + if (p == null) + predsCount[0]++; + else + predsCount[p.length]++; + + Instruction[] s = instr.getSuccs(); + if (s == null) + succsCount[0]++; + else + succsCount[s.length]++; + } + } + + public static void handlePackage(String pack) { + Enumeration subs = ClassInfo.getClassesAndPackages(pack); + while (subs.hasMoreElements()) { + String comp = (String) subs.nextElement(); + String full = pack + "." + comp; + if (ClassInfo.isPackage(full)) + handlePackage(full); + else { + ClassInfo clazz = ClassInfo.forName(full); + clazz.loadInfo(ClassInfo.FULLINFO); + MethodInfo[] ms = clazz.getMethods(); + for (int i=0; i < ms.length; i++) { + BytecodeInfo bc = ms[i].getBytecode(); + if (bc != null) + handleBytecode(bc); + } + } + } + } + + public static void main(String[] params) { + ClassInfo.setClassPath(params[0]); + Runtime runtime = Runtime.getRuntime(); + long free = runtime.freeMemory(); + long last; + do { + last = free; + runtime.gc(); + runtime.runFinalization(); + free = runtime.freeMemory(); + } while (free < last); + System.err.println("used before: "+(runtime.totalMemory()- free)); + long time = System.currentTimeMillis(); + handlePackage("com"); + System.err.println("Time used: "+(System.currentTimeMillis() - time)); + free = runtime.freeMemory(); + do { + last = free; + runtime.gc(); + runtime.runFinalization(); + free = runtime.freeMemory(); + } while (free < last); + System.err.println("used after: "+(runtime.totalMemory()- free)); + System.err.println("instruction count: "+instructions.size()); + for (int i=0; i< 256; i++) { + if (opcodeCount[i] > 0) + System.err.println("Opcode "+i+": \t ("+Opcodes.opcodeString[i]+")\t"+opcodeCount[i]); + } + int moreThanTwo = 0; + for (int i=0; i< predsCount.length; i++) { + if (predsCount[i] > 0) { + System.err.println("preds "+i+": \t"+predsCount[i]); + if (i>1) + moreThanTwo +=predsCount[i]; + } + } + System.err.println("preds >2: \t"+moreThanTwo); + + moreThanTwo = 0; + for (int i=0; i< succsCount.length; i++) { + if (succsCount[i] > 0) { + System.err.println("succs "+i+": \t"+succsCount[i]); + if (i>1) + moreThanTwo +=succsCount[i]; + } + } + System.err.println("succs >2: \t"+moreThanTwo); + } +} + diff --git a/jode/test/EvilTypes.j b/jode/test/EvilTypes.j new file mode 100644 index 0000000..0807d4b --- /dev/null +++ b/jode/test/EvilTypes.j @@ -0,0 +1,124 @@ +; This class converts between boolean and ints without type casts. +; You can't decompile this directly, the decompiler probably gives type errors. + +.class public EvilTypes +.super java/lang/Object + +.field public static runner Ljava/lang/Runnable; + +.method public static boolToInt(Z)I + .limit locals 1 + .limit stack 1 + iload_0 + ireturn +.end method + +.method public static intToBool(I)Z + .limit locals 1 + .limit stack 1 + iload_0 + ireturn +.end method + +.method public static overrideParam(I)[I + .limit locals 1 + .limit stack 1 + aconst_null + astore_0 + aload_0 + areturn +.end method + +.method public static test()V + .limit locals 2 + .limit stack 2 + iconst_1 + invokestatic EvilTypes/intToBool(I)Z + istore 0 + iconst_2 + istore 1 + iload 0 + iload 1 + ixor + pop + return +.end method + +.method private static useSerial(Ljava/io/Serializable;)V + .limit locals 1 + .limit stack 0 + return +.end method + +; This is a test where a type error occurs, because there is no Type +; that implements Cloneable, Serializable and is assignable form int +; array and java/lang/Date (though both objects are Cloneable and +; Serializable). We can't find any correct type for local 2. + +.method public static referenceCast(Ljava/util/Date;[I)Ljava/lang/Cloneable; + .limit locals 3 + .limit stack 2 + aload_0 + ifnonnull second + aload_0 + goto done +second: + aload_1 +done: + dup + astore_2 + invokestatic EvilTypes/useSerial(Ljava/io/Serializable;)V + aload_2 + areturn +.end method + +; This shows that the bytecode verifier doesn't catch every type error. +.method public static test(Ljava/lang/String;)Ljava/lang/Runnable; + .limit locals 1 + .limit stack 1 + aload_0 + areturn +.end method + +; The problem of this method is the type of local_0, the code is as +; follows: +; local_0 = null; +; for (;;) local_0 = local_0[0]; +; +; Since local_0 is used as an array, it must be of array type. Since +; local_0[0] is assigned to local_0, local_0[0] must be of array type, +; so local_0 must be of type array of array of something, and so on... + +.method public static infinitiveArray()V + .limit locals 1 + .limit stack 2 + aconst_null + astore_0 +loop: + aload_0 + iconst_0 + aaload + astore_0 + goto loop +.end method + +; This tests shows how lazy the type checking even with verify is: +; The type error produced in the test method, is first seen on the +; invokeinterface opcode. If there would be no invokeinterface it +; would never have been noticed. +; NOTE: An obfuscator may use this fact and replace every ocurrence +; of an interface or Object type with any other interface type. + +.method public static main([Ljava/lang/String;)V + .limit locals 1 + .limit stack 2 + aload_0 + iconst_0 + aaload + invokestatic EvilTypes/test(Ljava/lang/String;)Ljava/lang/Runnable; + putstatic EvilTypes/runner Ljava/lang/Runnable; + getstatic EvilTypes/runner Ljava/lang/Runnable; + invokeinterface java/lang/Runnable/run()V 1 + return +.end method + diff --git a/jode/test/Expressions.java b/jode/test/Expressions.java new file mode 100644 index 0000000..dfbeffd --- /dev/null +++ b/jode/test/Expressions.java @@ -0,0 +1,91 @@ +/* Expressions Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +public class Expressions { + double cd; + float cf; + long cl; + int ci; + char cc; + short cs; + byte cb; + boolean cz; + + void postIncDecExpressions() { + cd++; + cf++; + cl++; + ci++; + cs++; + cb++; + cc++; + cd--; + cf--; + cl--; + ci--; + cs--; + cb--; + cc--; + float f = 0.0F; + double d = 0.0; + long l = 0L; + f++; + f--; + d++; + d--; + l++; + l--; + } + + + void unary() { + short s = 25; + s = (short) ~s; + boolean b = !true; + s = b? s: cs; + char c= 25; + c = b ? c: cc; + } + + void shift() { + int i = 0; + long l =0; + l >>= i; + l >>= i; + i >>= i; + l = l << l; + l = l << i; + l = i << l; + l = i << i; + i = (int) (l << l); + i = (int) (l << i); + i = i << l; + i = i << i; + cl >>= ci; + ci <<= ci; + cl = cl << cl; + cl = cl << ci; + cl = ci << cl; + cl = ci << ci; + ci = (int) (cl << cl); + ci = (int) (cl << ci); + ci = ci << cl; + ci = ci << ci; + } +} diff --git a/jode/test/Flow.java b/jode/test/Flow.java new file mode 100644 index 0000000..b73ade5 --- /dev/null +++ b/jode/test/Flow.java @@ -0,0 +1,218 @@ +/* Flow Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public abstract class Flow { + int g; + int[] ain; + + void skip() { + for (int i=0; i<3; i++) { + if (g > 5) { + for (int j=0; j<5; j++) { + g++; + } + } + i--; + } + } + + /* This tests the while in a switch problem. + */ + public void switchWhileTest() { + int dir = g; + int x = 0; + int y = 0; + boolean done = false; + g = 5; + switch (dir) { + case 1: + while (!done) { + done = true; + if (g > 7) + g = g - 4; + x = g; + y = g; + if (x > 7) + x = x - 4; + for (int i=0; i<4; i++) { + for (int j=0; j<5; j++) { + if (ain[j] == x + i && ain[j] == y) + done = false; + } + } + } + for (int i=0; i<5; i++) { + ain[g] = x + i; + ain[g] = y; + g += 1; + } + break; + case 2: + while (!done) { + done = true; + x = g; + y = g; + if (y > 7) + y = y - 4; + for (int i=0; i<4; i++) { + for (int j=0; j<4; j++) { + if (ain[j] == x && ain[j] == y + i) + done = false; + } + } + } + for (int i = 0; i<4; i++) { + ain[g] = x; + ain[g] = y + i; + g += 1; + } + break; + case 3: // Same code as case 2 slightly optimized + big: + for (;;) { + x = g; + y = g; + if (y > 7) + y = y - 4; + for (int i=0; i<4; i++) { + for (int j=0; j<4; j++) { + if (ain[j] == x && ain[j] == y + i) + continue big; + } + } + break; + } + for (int i = 0; i<4; i++) { + ain[g] = x; + ain[g] = y + i; + g += 1; + } + break; + } + } + + /** + * This was an example where our flow analysis didn't find an + * elegant solution. The reason is, that we try to make + * while(true)-loops as small as possible (you can't see the real + * end of the loop, if it is breaked there like here). + * + * Look at the assembler code and you know why my Decompiler had + * problems with this. But the decompiler did produce compilable + * code which produces the same assembler code. + * + * The solution was, to make switches as big as possible, the whole + * analyze methods were overworked. + */ + void WhileTrueSwitch() { + int i = 1; + while (true) { + switch (i) { + case 0: + return; + case 1: + i = 5; + continue; + case 2: + i = 6; + continue; + case 3: + throw new RuntimeException(); + default: + i = 7; + return; + } + } + } + + abstract int test(); + + /** + * This tests shorts and empty ifs. Especially the no op ifs can + * be optimized to very unusual code. + */ + public void shortIf() { + while(g != 7) { + if (g == 5) + return; + else if (g != 4) + break; + else if (g == 2) + shortIf(); + else + return; + + if (g!= 7) + shortIf(); + else { + shortIf(); + return; + } + + if (g != 1) + break; + else if (g == 3) + shortIf(); + else + break; + + // javac optimizes this instruction to + // test(); + // jikes reproduces this statement as one would expect + if (g + 5 == test()) { + } + + // javac -O optimizes this to the following weired statements + // PUSH g; + // PUSH test(); + // POP2; + // This cannot be decompiled correctly, since the == is lost. + if (g == test()) + continue; + } + while(g == 3) { + // javac: + // PUSH test() == 4 || test() == 3 && test() == 2; + // POP; + if (test() == 4 || test() == 3 && test() == 2); + // javac -O: + // if (test() != 4 && test() == 3) { + // PUSH test()+test() - test(); + // PUSH g-4; + // POP2; + // } + if (test() == 4 || test() == 3 && test() == 2) + continue; + } + while (g==2) { + // javac: + // test(); + // test(); + // test(); + if ((long) (test() + test() - test()) == (long)(g-4)); + // javac -O: + // PUSH (long)(test() + test() - test()) <=> (long)(g-4) + // POP; + if ((long) (test() + test() - test()) == (long)(g-4)) + continue; + } + System.err.println("Hallo"); + } +} diff --git a/jode/test/For.java b/jode/test/For.java new file mode 100644 index 0000000..c8b95dd --- /dev/null +++ b/jode/test/For.java @@ -0,0 +1,35 @@ +/* For Copyright (C) 1998-1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class For { + + boolean nested() { + outer: + for (int i=0; i< 100; i++) { + for (int j=0; j< 100; j++) { + if (i < j) + continue outer; + } + return false; + } + return true; + } + +} diff --git a/jode/test/HintTypeTest.java b/jode/test/HintTypeTest.java new file mode 100644 index 0000000..5be1a6d --- /dev/null +++ b/jode/test/HintTypeTest.java @@ -0,0 +1,50 @@ +/* HintTypeTest Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +/** + * The primitive types can give some headaches. You almost never can say + * if a local variable is of type int, char, short etc.

    + * + * Most times this doesn't matter this much, but with int and character's + * this can get ugly.

    + * + * The solution is to give every variable a hint, which type it probably is. + * The hint reset, when the type is not possible. For integer types we try + * to set it to the smallest explicitly assigned type.

    + * + * Some operators will propagate this hint.

    + */ +public class HintTypeTest { + + public void charLocal() { + String s= "Hallo"; + for (byte i=0; i< s.length(); i++) { + char c = s.charAt(i); + if (c == 'H') + // The widening to int doesn't occur in byte code, but + // is necessary. This is really difficult. + System.err.println("H is "+(int)c); + else + System.err.println(""+c+" is "+(int)c); + } + } +} + + diff --git a/jode/test/IfCombine.java b/jode/test/IfCombine.java new file mode 100644 index 0000000..f4033db --- /dev/null +++ b/jode/test/IfCombine.java @@ -0,0 +1,33 @@ +/* IfCombine Copyright (C) 1998-1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class IfCombine { + boolean a,b,c; + int i,j,k; + + public void foo() { + if ( a && (b || c) && (i 0) + son = new NestMyself(d-1); +///#endif + } + } + new NestMyself(maxdepth); + } + +} diff --git a/jode/test/ObfuscateStrings.j b/jode/test/ObfuscateStrings.j new file mode 100644 index 0000000..47e17e3 --- /dev/null +++ b/jode/test/ObfuscateStrings.j @@ -0,0 +1,166 @@ +; This class contains a hand optimized (and hard to decompile) +; string obfuscating method. Maybe I will use it in the Obfuscator +; some day, but probably the decompiler will handle those string, too. + +.class public ObfuscateStrings +.super java/lang/Object + +.method private static obf(Ljava/lang/String;)Ljava/lang/String; + .limit locals 1 + .limit stack 7 + aload_0 + invokevirtual java/lang/String/toCharArray()[C + dup + iconst_0 + ldc 0x12345678 + goto firstloop + +loopstart: +; next pseudo random +; char array + dup_x1 + swap + iload_0 + swap + ldc 0x7fffffff + iand + +firstloop: +;stack content: +; char array +; char array copy +; current index +; current pseudo random + + ldc 1103515245 + imul + sipush 12345 + iadd + dup_x2 + sipush 0xff + iand + dup_x2 + pop + +;stack content: +; char array +; next pseudo random +; xor mask +; char array copy +; current index + + dup2_x1 + +;stack content: +; char array +; next pseudo random +; char array copy +; current index +; xor mask +; char array copy +; current index + + caload + ixor + +;stack content: +; char array +; next pseudo random +; char array copy +; current index +; new char + + swap + dup_x1 + istore_0 + iinc 0 1 + castore + +;stack content: +; char array +; next pseudo random +;locals: 1 = current index + + swap + dup + arraylength + iload_0 + if_icmpne loopstart + + new java/lang/String + dup_x2 + swap + invokespecial java/lang/String/([C)V + pop + areturn +.end method + +.method private static obf2(Ljava/lang/String;)Ljava/lang/String; + .limit locals 1 + .limit stack 8 + aload_0 + invokevirtual java/lang/String/toCharArray()[C + ldc 0x12345678 + istore_0 + iconst_0 + +loop: +; char array +; next index + + +;stack content: +; char array +; current index + dup2 + dup2 + caload + +; char array +; current index +; char array +; current index +; original char + + iload_0 + ldc 0x7fffffff + iand + + dup + ldc 1103515245 + imul + sipush 12345 + iadd + istore_0 + sipush 0xff + iand + ixor + +; char array +; current index +; char array +; current index +; new char + + castore + iconst_1 + iadd + +; char array +; next index + dup2 + swap + arraylength + if_icmplt loop + +; char array +; next index + pop + + new java/lang/String + dup_x1 + swap + invokespecial java/lang/String/([C)V + areturn +.end method + diff --git a/jode/test/OptimizeTest.java b/jode/test/OptimizeTest.java new file mode 100644 index 0000000..3a6789e --- /dev/null +++ b/jode/test/OptimizeTest.java @@ -0,0 +1,79 @@ +/* OptimizeTest Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class OptimizeTest { + + public final int getInlined(String str, int i) { + return str.charAt(i); + } + + public final int getInlined(String str, int i, OptimizeTest t) { + return str.charAt(i) + t.getInlined(str, i) + i; + } + + + public final int complexInline(String str, int i) { + System.err.println(""); + return str.charAt(i)+str.charAt(-i); + } + + public int notInlined(String str, int i, OptimizeTest t) { + return str.charAt(i) + t.getInlined(str, i); + } + + public final void longInline(String str, int i) { + str.replace('a','b'); + System.err.println(str.concat(String.valueOf(str.charAt(i)))); + } + + public int g; + + /** + * This is a really brutal test. It shows that side effects can + * make the handling of inlined methods really, really difficult. + */ + public final int sideInline(int a) { + return g++ + a; + } + + public void main(String[] param) { + OptimizeTest ot = new OptimizeTest(); + + System.err.println(ot.getInlined("abcde".replace('a','b'), param.length)); + System.err.println(ot.getInlined("Hallo", ot.notInlined(param[1], 10 - ot.getInlined(param[0], 0, new OptimizeTest()), ot))); + System.err.println(ot.complexInline("ollah", param.length)); + System.err.println("result: "+(g++ + sideInline(g) + g++) + "g: "+g); + longInline("Hallo", 3); + System.err.println("result:"+ + (g++ + InlineTest + .difficultSideInline(this, g) + + g++) + "g: "+g); + // This was a check which methods are inlined. The result: + // Only methods in the same package or in sub packages. +// System.err.println("result:"+ +// (g++ + inline.InlineTest +// .difficultSideInline(this, g) +// + g++) + "g: "+g); +// System.err.println("result:"+ +// (g++ + jode.InlineTest +// .difficultSideInline(this, g) +// + g++) + "g: "+g); + } +} diff --git a/jode/test/OptimizerTest.java b/jode/test/OptimizerTest.java new file mode 100644 index 0000000..9bb298f --- /dev/null +++ b/jode/test/OptimizerTest.java @@ -0,0 +1,58 @@ +/** + * This class should be optimized through the obfuscator: + * + *

    + *  java jode.Obfuscator --dest tmp.zip --preserve 'jode.test.OptimizerTest.test.(*)*' jode
    + * 
    + */ + +public class OptimizerTest { + String blah, blub; + Object o; + + private static String manipulateString(String input) { + char[] chars = input.toCharArray(); + char[] dests = new char[chars.length]; + dests[0] = chars[0]; + for (int i=1; i< chars.length; i++) { + if (chars[i] > chars[i-1]) { + dests[i] = (char) (chars[i] - chars[i-1]); + } else if (chars[i] == chars[i-1]) { + dests[i] = 0; + } else if (chars[i] < chars[i-1]) { + dests[i] = (char) (Character.MAX_VALUE + - (chars[i-1] - chars[i] - 1)); + } else + dests[i] = 'X'; + } + return new String(dests); + } + + public static String doubleCompare(double d1, double d2) { + return d1 + ((d1 > d2) ? " > " + : (d1 < d2) ? " < " + : (d1 == d2) ? " == " : " ?? ") + d2; + } + + public void test() { + System.err.println(manipulateString("ABCDEFGGFEDCBA")); + blah = manipulateString("hello world"); + o = new Object(); + blub = "Hallo"+manipulateString("ABCDEFGGFDECBA"); + System.err.println(blub); + + System.err.println(doubleCompare(0.0, 0.0)); + System.err.println(doubleCompare(0.0, 1.0)); + System.err.println(doubleCompare(0.0, -1.0)); + System.err.println(doubleCompare(0.0, Double.NaN)); + System.err.println(doubleCompare(Double.NaN, 0.0)); + System.err.println(doubleCompare(Double.NaN, Double.NaN)); + System.err.println(doubleCompare(Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY)); + + System.err.println(doubleCompare(Math.exp(1), 2.718281828459045)); + + System.err.println((int)Math.round(Math.exp(Math.log(5)*5))/ 625); + } +} + diff --git a/jode/test/PrivateHideTest.java b/jode/test/PrivateHideTest.java new file mode 100644 index 0000000..eaa7f95 --- /dev/null +++ b/jode/test/PrivateHideTest.java @@ -0,0 +1,89 @@ + +/** + * This class tests accesses to private methods and fields, that can be + * accessed via a cast. + * + * The test class has some problems, that no compiler will produce + * correct code. When all constructors are declared as private all + * compilers won't even compile it. + * + * I wish there would be a clear document saying what constructs are + * allowed and a compiler that would compile them all... + */ +public class PrivateHideTest { + + public static class Test { + private String id; + + Test(int val) { + id = "Test"+val; + } + + private void a(String descr) { + System.err.println(descr+" = "+id); + } + + class Mid extends Test { + private String id; + + Mid(int val) { + super(val); + id = "Mid"+val; + } + + private void a(String descr) { + System.err.println(descr+" = "+id); + } + + private Test getTestThis() { + return Test.this; + } + + class Inner extends Mid { + private String id; + + Inner(int val, Test midOuter) { + midOuter.super(val); + id = "Inner"+val; + } + + private void a(String descr) { + System.err.println(descr+" = "+id); + } + + private void methodTest() { + this.a("this"); + super.a("super"); + ((Mid) this).a("(Mid) this"); + ((Test) this).a("(Test) this"); + Mid.this.a("Mid.this"); + ((Test) Mid.this).a("(Test) Mid.this"); + Test.this.a("Test.this"); + ((Mid) this).getTestThis().a("((Mid) this).getTestThis()"); + Mid.this.getTestThis().a("Mid.this.getTestThis()"); + } + + + private void fieldTest() { + System.err.println("this = "+this.id); + System.err.println("(Mid) this = " + ((Mid) this).id); + System.err.println("(Test) this = " + ((Test) this).id); + System.err.println("Mid.this = " + Mid.this.id); + System.err.println("(Test) Mid.this = " + ((Test) Mid.this).id); + System.err.println("Test.this = " + Test.this.id); + System.err.println("((Mid) this).getTestThis() = " + ((Mid)this).getTestThis().id); + System.err.println("Mid.this.getTestThis() = " + Mid.this.getTestThis().id); + } + } + } + } + + public static void main(String[] argv) { + Test.Mid.Inner inner = + new Test(1).new Mid(2).new Inner(3, new Test(4).new Mid(5)); + inner.methodTest(); + System.err.println("--"); + inner.fieldTest(); + ((Test.Mid)inner).a("(Test.Mid) inner"); + } +} diff --git a/jode/test/RemovePopExample.j b/jode/test/RemovePopExample.j new file mode 100644 index 0000000..bd131e2 --- /dev/null +++ b/jode/test/RemovePopExample.j @@ -0,0 +1,70 @@ +.class public RemovePopExample +.super java/lang/Object + +.field private sng I +.field private dlb J +.field private obj Ljava/lang/Object; + +.method ()V + .limit locals 1 + .limit stack 1 + aload_0 + invokespecial java/lang/Object/()V + return +.end method + +.method singlePop()V + .limit locals 3 + .limit stack 20 + + iconst_0 + istore 0 + + iconst_0 + pop + dconst_0 + pop2 + + iload_0 + pop + + aconst_null + iconst_0 + iaload + pop + + iconst_0 + dup + pop + + dup + istore_0 + pop + + lconst_1 + iconst_0 + dup_x2 + lshl + lstore_1 + pop + + iload_0 + iconst_4 + dup_x1 + pop + pop2 + + iconst_4 + lload_1 + dup2 + pop2 + + dup2_x1 + lstore_1 + istore_0 + pop2 + + return + +.end method + diff --git a/jode/test/ResolveConflicts.java b/jode/test/ResolveConflicts.java new file mode 100644 index 0000000..c115dc3 --- /dev/null +++ b/jode/test/ResolveConflicts.java @@ -0,0 +1,144 @@ +/* Conflicts Copyright (C) 1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +/** + * This class tests name conflicts and their resolvation. Note that every + * name in this file should be the shortest possible name. + */ +public class ResolveConflicts +{ + public class Conflicts + { + int Conflicts; + + class Blah + { + Conflicts Inner; + + void Conflicts() { +///#ifndef JAVAC11 +///#ifndef JAVAC12 + Inner = ResolveConflicts.Conflicts.this; +///#endif +///#endif + } + } + + class Inner + { + int Conflicts; + Conflicts Inner; + + class Blah + extends Conflicts.Blah + { + int Blah; + + void Inner() { + this.Inner.Inner(); + this.Conflicts(); +///#ifndef JAVAC11 +///#ifndef JAVAC12 + ResolveConflicts.Conflicts.Inner.this.Inner.Inner(); + ResolveConflicts.Conflicts.Inner.this.Conflicts(); +///#endif +///#endif + } + + Blah() { + /* empty */ + } + + Blah(Conflicts Conflicts) { + Conflicts.super(); + } + } + + void Conflicts() { + int Conflicts = 4; + Conflicts(); + new Object() { + void Inner() { +///#ifndef JAVAC11 +///#ifndef JAVAC12 + ResolveConflicts.Conflicts.this.Inner(); +///#endif +///#endif + } + }; + this.Conflicts = Conflicts; + Inner(); +///#ifndef JAVAC11 +///#ifndef JAVAC12 + ResolveConflicts.Conflicts.this.Conflicts = this.Conflicts; +///#endif +///#endif + } + + Conflicts Conflicts(Inner Conflicts) { +///#ifndef JAVAC11 +///#ifndef JAVAC12 + ResolveConflicts.Conflicts Inner + = ResolveConflicts.Conflicts.this; +///#endif +///#endif + return ResolveConflicts.this.new Conflicts(); + } + } + + class Second + extends Conflicts.Inner.Blah + { + Inner Blah = new Inner(); + + class Inner extends Conflicts.Inner + { + } + + Conflicts.Inner create() { +///#ifndef JAVAC11 +///#ifndef JAVAC12 + ResolveConflicts.Conflicts.Inner inner + = ResolveConflicts.Conflicts.this.new Inner(); +///#endif +///#endif +///#ifdef JAVAC11 + return null; +///#else + return ResolveConflicts.this.new Conflicts().new Inner(); +///#endif + } + + Second(Conflicts.Inner Blah) { + Blah.super(); + } + } + + public void Inner() { + /* empty */ + } + + public Conflicts() { + int Conflicts = this.Conflicts; + Inner Inner = new Inner(); + Inner.Conflicts = 5; + new Inner().Conflicts(Inner).Inner(); + } + } +} diff --git a/jode/test/StackOps.j b/jode/test/StackOps.j new file mode 100644 index 0000000..92ed496 --- /dev/null +++ b/jode/test/StackOps.j @@ -0,0 +1,41 @@ +; This class contains evil stack operations, that make it very hard to +; produce correct code. + +.class public StackOps +.super java/lang/Object + +.method public static concatSwaped(ZLjava/lang/String;Ljava/lang/String;)Ljava/lang/String; + .limit locals 3 + .limit stack 3 + aload_1 + aload_2 + iload 0 + ifeq dontswap + swap +dontswap: + invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; + areturn +.end method + +.method public static dupTest(Ljava/lang/String;)Ljava/lang/String; + .limit locals 1 + .limit stack 2 + ; first a simple test that we can resolve + aload_0 + dup + invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; + ; now concat again. + dup + invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; + ; Now a more evil test. + aload_0 + swap + ifnull pushagain + dup + goto concat +pushagain: + aload_0 +concat: + invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; + areturn +.end method diff --git a/jode/test/TriadicExpr.java b/jode/test/TriadicExpr.java new file mode 100644 index 0000000..7252da2 --- /dev/null +++ b/jode/test/TriadicExpr.java @@ -0,0 +1,38 @@ +/* TriadicExpr Copyright (C) 1998-1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + + +public class TriadicExpr { + int a,b,c,d,e,f,g,h,i,j; + boolean bool; + + void test() { + if( (a< b ? bool : (a 3) { + try { + System.err.println(); + } finally { + if (simple() < 2) + break; + else if (simple() < 3) + foo(); + else + return; + } + System.out.println(); + } + System.err.println(); + } +} diff --git a/jode/test/Unreach.java b/jode/test/Unreach.java new file mode 100644 index 0000000..56fb487 --- /dev/null +++ b/jode/test/Unreach.java @@ -0,0 +1,32 @@ +/* Unreach Copyright (C) 1999 Dave Mugridge. + * + * $Id$ + */ + + +/* A test class submitted by dave@onekiwi.demon.co.uk */ +class Unreach +{ + static int j = 0; + final double[] d = {0.5, 0.4, 0.3}; // won't decompile + + public static final void m(int i) throws Exception { + switch (i) { + case 1: + j += 2; + for (;;) { + j += 3; + switch (j) { + case 2: + break; + default: + j += 4; + return; + } + } + // An unreachable break is inserted here + default: + j += 5; // decompiles as j = j + 1; -- not quite optimal + } + } +} diff --git a/jode/test/innerclasses/CheckPrivate.java b/jode/test/innerclasses/CheckPrivate.java new file mode 100644 index 0000000..93fa895 --- /dev/null +++ b/jode/test/innerclasses/CheckPrivate.java @@ -0,0 +1,40 @@ + + +public class CheckPrivate { + private class InnerClass { + private int field; + + private InnerClass(int j) { + field = j; + } + + private int method(int a) { + int old = field; + field = a; + return old; + } + + private void test() { + outerField = 4; + outerMethod(); + System.err.println(outerField); + new CheckPrivate(); + } + } + + private int outerField; + + private int outerMethod() { + return outerField; + } + + private CheckPrivate() { + InnerClass inner = new InnerClass(1); + inner.field = 3; + inner.method(inner.field); + } + + public static void main(String[] test) { + new CheckPrivate(); + } +} diff --git a/jode/test/innerclasses/CheckSuperRemove.java b/jode/test/innerclasses/CheckSuperRemove.java new file mode 100644 index 0000000..8c00b2b --- /dev/null +++ b/jode/test/innerclasses/CheckSuperRemove.java @@ -0,0 +1,28 @@ +public class CheckSuperRemove { + public class Inner { + public void test() { + class MyInner extends Inner { + } + MyInner myInner = new MyInner(); + Inner anonInner = new Inner() { + public void test() {} + }; + MyInner anonInner2 = new MyInner() { + public void test() {} + }; + } + } + + public class SubInner extends Inner { + public SubInner(int a) { + } + + public SubInner() { + } + } + + public static void main(String[] param) { + new CheckSuperRemove().new SubInner(); + new CheckSuperRemove().new SubInner(1); + } +} diff --git a/jode/test/removepop.jodescript b/jode/test/removepop.jodescript new file mode 100644 index 0000000..8134c5a --- /dev/null +++ b/jode/test/removepop.jodescript @@ -0,0 +1,7 @@ +classpath = "." +load = new WildCard { value = "RemovePopExample" } + +preserve = new WildCard { value = "*" } + +analyzer = new SimpleAnalyzer +post = new RemovePopAnalyzer diff --git a/jode/test/simpletests.sh b/jode/test/simpletests.sh new file mode 100755 index 0000000..a65d6d6 --- /dev/null +++ b/jode/test/simpletests.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +TEMP=`mktemp -d tmp.XXXXXX` + +if echo $JAVAC | grep jikes >/dev/null; then + compiler=JIKES; + version=`$JAVAC 2>&1 | grep Version | \ + perl -pe's/^.*Version \"?([0-9]+)\.([0-9]+).*$/\1/'` +elif echo $JAVAC | grep javac >/dev/null; then + compiler=JAVAC + version=`$JAVAC -J-version 2>&1 | grep version | \ + perl -pe's/^.*version \"?([0-9]+)\.([0-9]+).*$/\1\2/'` +else + compiler=UNKNOWN + version="" +fi + +echo "detected compiler $compiler" + +error="" + +EXPECT_FAIL="" + +for testclass in \ +ArrayCloneTest.java \ +ArrayTest.java \ +AssignOp.java \ +ClassOpTest.java \ +ConstantTypes.java \ +Expressions.java \ +Flow.java \ +For.java \ +HintTypeTest.java \ +IfCombine.java \ +LocalTypes.java \ +ResolveConflicts.java \ +TriadicExpr.java \ +TryCatch.java \ +Unreach.java \ +AnonymousClass.java \ +InnerClass.java \ +InnerCompat.java \ +NestedAnon.java +do + cp $srcdir/$testclass $TEMP + $PERL $top_srcdir/scripts/jcpp.pl -D$compiler -D$compiler$version \ + $TEMP/$testclass + CLASSPATH=$CLASSPATH:$CLASSLIB $JAVAC $JFLAGS -d $TEMP $TEMP/$testclass + CLASSPATH=$CLASSPATH:$CLASSLIB $JAVA jode.Decompiler \ + --classpath=$TEMP --dest=$TEMP ${testclass%.java} > $testclass.log 2>&1 + if ! CLASSPATH=$TEMP:$CLASSPATH $JAVAC $JFLAGS -d $TEMP $TEMP/$testclass >> $testclass.log 2>&1 ; then + cat $TEMP/$testclass >> $testclass.log + CLASSPATH=$TEMP:$CLASSPATH javap -c ${testclass%.java} >> $testclass.log + if ! echo $EXPECT_FAIL | grep $testclass >/dev/null ; then + error="$error $testclass"; + echo "FAIL: $testclass" + else + echo "EXPECTED FAIL: $testclass" + fi + else + echo "PASS: $testclass" + rm $testclass.log + fi + #rm -rf $TEMP/* +done + +rm -rf $TEMP; + +if [ -n "$error" ]; then + exit 1; +fi diff --git a/jode/test/src/net/sf/jode/bytecode/BasicBlockWriterTest.java b/jode/test/src/net/sf/jode/bytecode/BasicBlockWriterTest.java new file mode 100644 index 0000000..02235be --- /dev/null +++ b/jode/test/src/net/sf/jode/bytecode/BasicBlockWriterTest.java @@ -0,0 +1,226 @@ +package net.sf.jode.bytecode; +import junit.framework.*; +import java.io.*; + +public class BasicBlockWriterTest extends TestCase implements Opcodes { + public BasicBlockWriterTest(String name) { + super(name); + } + + GrowableConstantPool gcp = new GrowableConstantPool(); + Instruction[] someNops; + Instruction[] manyNops; + Instruction[] whileHead; + Instruction[] whileCond; + Instruction[] whileFoot; + + /** + * The whileHead block in bytecode + */ + private final static String whileHeadStr="\3="; + /** + * The whileCond block in bytecode, without the if_icmpeq instruction. + */ + private final static String whileCondStr="\34\33"; + /** + * The whileFoot block in bytecode. + */ + private final static String whileFootStr="\204\2\1"; + /** + * The someNops block in bytecode, without the if_icmpeq instruction. + */ + private final static String someNopsStr="\0\0"; + + private void assertCodeEquals(String message, + String expected, byte[] code) { + if (code.length != expected.length()) + fail(message); + for (int i = 0; i < code.length; i++) { + if (code[i] != (byte) expected.charAt(i)) + fail(message); + } + } + + public void setUp() { + Instruction nop = Instruction.forOpcode(opc_nop); + someNops = new Instruction[] { nop, nop }; + manyNops = new Instruction[35000]; + for (int i = 0; i < manyNops.length; i++) + manyNops[i] = nop; + + whileHead = new Instruction[] { + Instruction.forOpcode(opc_ldc, new Integer(0)), + Instruction.forOpcode(opc_istore, LocalVariableInfo.getInfo(2)), + }; + whileCond = new Instruction[] { + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(2)), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_if_icmpeq) + }; + whileFoot = new Instruction[] { + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(2), 1), + }; + } + + public byte[] write(BasicBlockWriter bbw) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + bbw.write(gcp, dos); + dos.close(); + return baos.toByteArray(); + } + + public void testEmpty() throws IOException { + BasicBlocks bb = new BasicBlocks(new MethodInfo("foo", "()V", 0)); + bb.setBlocks(new Block[0], null, new Handler[0]); + BasicBlockWriter bbw = new BasicBlockWriter(bb, gcp); + gcp.write(new DataOutputStream(new ByteArrayOutputStream())); + + assertCodeEquals("Code differs", + "\0\0\0\1\0\0\0\1" /* no stack, one local, length 1 */ + +"\261" /* opc_return */ + +"\0\0" /* no exception handlers */, + write(bbw)); + } + + public void testSimple() throws IOException { + Block bb1 = new Block(); + Block bb2 = new Block(); + bb1.setCode(someNops, new Block[] {bb2}); + bb2.setCode(someNops, new Block[] {null}); + BasicBlocks bb = new BasicBlocks(new MethodInfo("foo", "()V", 0)); + bb.setBlocks(new Block[] { bb1, bb2}, bb1, new Handler[0]); + BasicBlockWriter bbw = new BasicBlockWriter(bb, gcp); + gcp.write(new DataOutputStream(new ByteArrayOutputStream())); + + assertCodeEquals("Code differs", + "\0\0\0\1\0\0\0\5" /* no stack, one local, length 5 */ + +someNopsStr+someNopsStr+"\261" + +"\0\0" /* no exception handlers */, + write(bbw)); + } + + public void testWhile() throws IOException { + Block b1 = new Block(); + Block b2 = new Block(); + Block b3 = new Block(); + Block b4 = new Block(); + b1.setCode(whileHead, new Block[] { b2 }); + b2.setCode(whileCond, new Block[] { null, b3 }); + b3.setCode(someNops, new Block[] { b4 }); + b4.setCode(whileFoot, new Block[] { b2 }); + BasicBlocks bb = new BasicBlocks(new MethodInfo("a", "(I)V", 0)); + bb.setBlocks(new Block[] { b1, b2, b3, b4}, b1, new Handler[0]); + BasicBlockWriter bbw = new BasicBlockWriter(bb, gcp); + gcp.write(new DataOutputStream(new ByteArrayOutputStream())); + assertEquals(5, bbw.blockAddr.length); + assertEquals(0, bbw.blockAddr[0]); + assertEquals(2, bbw.blockAddr[1]); + assertEquals(7, bbw.blockAddr[2]); + assertEquals(9, bbw.blockAddr[3]); + assertEquals(16, bbw.blockAddr[4]); + + assertCodeEquals + ("Code differs", + "\0\2\0\3\0\0\0\20" + +whileHeadStr+whileCondStr+"\237\0\13" + +someNopsStr+whileFootStr+"\247\377\366"+"\261"+"\0\0", + write(bbw)); + } + + public void testTableSwitch() throws IOException { + Block b1 = new Block(); + Block b2 = new Block(); + Instruction[] switchBlock = new Instruction[] { + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_lookupswitch, new int[] { 1, 3, 5}) + }; + b1.setCode(switchBlock, new Block[] { b2, null, b1, null }); + b2.setCode(someNops, new Block[] { null }); + BasicBlocks bb = new BasicBlocks(new MethodInfo("s", "(I)V", 0)); + bb.setBlocks(new Block[] { b1, b2 }, b1, new Handler[0]); + BasicBlockWriter bbw = new BasicBlockWriter(bb, gcp); + gcp.write(new DataOutputStream(new ByteArrayOutputStream())); + assertEquals(3, bbw.blockAddr.length); + assertEquals(0, bbw.blockAddr[0]); + assertEquals(36, bbw.blockAddr[1]); + assertEquals(39, bbw.blockAddr[2]); + assertCodeEquals + ("Code differs", + "\0\1\0\2\0\0\0\47" + +"\33\252\0\0" /*iload_0 + tableswitch + align */ + +"\0\0\0\45\0\0\0\1\0\0\0\5" /* def, low, high */ + +"\0\0\0\43\0\0\0\45\0\0\0\45\0\0\0\45\377\377\377\377" + +someNopsStr+"\261"+"\0\0", + write(bbw)); + } + + public void testLookupSwitch() throws IOException { + Block b1 = new Block(); + Block b2 = new Block(); + Instruction[] switchBlock = new Instruction[] { + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_lookupswitch, new int[] { 1, 5, 7}) + }; + b1.setCode(switchBlock, new Block[] { b2, null, b1, null }); + b2.setCode(someNops, new Block[] { null }); + BasicBlocks bb = new BasicBlocks(new MethodInfo("s", "(I)V", 0)); + bb.setBlocks(new Block[] { b1, b2 }, b1, new Handler[0]); + BasicBlockWriter bbw = new BasicBlockWriter(bb, gcp); + gcp.write(new DataOutputStream(new ByteArrayOutputStream())); + assertEquals(3, bbw.blockAddr.length); + assertEquals(0, bbw.blockAddr[0]); + assertEquals(36, bbw.blockAddr[1]); + assertEquals(39, bbw.blockAddr[2]); + assertCodeEquals("Code differs", + "\0\1\0\2\0\0\0\47" + +"\33\253\0\0" /*iload_0 + lookupswitch + align */ + +"\0\0\0\45\0\0\0\3" /* def , nitem */ + +"\0\0\0\1\0\0\0\43" + +"\0\0\0\5\0\0\0\45" + +"\0\0\0\7\377\377\377\377" + +someNopsStr+"\261"+"\0\0", + write(bbw)); + } + + public void testException() throws IOException { + Block b1 = new Block(); + Block b2 = new Block(); + Instruction[] catchInstrs = new Instruction[] { + Instruction.forOpcode(opc_athrow) + }; + b1.setCode(someNops, new Block[] { null }); + b2.setCode(catchInstrs, new Block[0]); + BasicBlocks bb = new BasicBlocks(new MethodInfo("e", "()V", 0)); + Handler h = new Handler(b1, b1, b2, "java.lang.RuntimeException"); + bb.setBlocks(new Block[] { b1, b2 }, b1, new Handler[] {h}); + assertEquals(0, b1.blockNr); + assertEquals(1, b2.blockNr); + assertEquals(1, b1.catchers.length); + assertEquals(0, b2.catchers.length); + assertSame(h, b1.catchers[0]); + BasicBlockWriter bbw = new BasicBlockWriter(bb, gcp); + gcp.write(new DataOutputStream(new ByteArrayOutputStream())); + int cpoolEntry = gcp.putClassName("java.lang.RuntimeException"); + assertEquals(3, bbw.blockAddr.length); + assertEquals(0, bbw.blockAddr[0]); + assertEquals(3, bbw.blockAddr[1]); + assertEquals(4, bbw.blockAddr[2]); + assertCodeEquals("Code differs", + "\0\1\0\1\0\0\0\4" + + someNopsStr + "\261" + "\277" + + "\0\1\0\0\0\3\0\3\0"+(char)cpoolEntry, + write(bbw)); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new BasicBlockWriterTest("testEmpty")); + suite.addTest(new BasicBlockWriterTest("testSimple")); + suite.addTest(new BasicBlockWriterTest("testWhile")); + suite.addTest(new BasicBlockWriterTest("testTableSwitch")); + suite.addTest(new BasicBlockWriterTest("testLookupSwitch")); + suite.addTest(new BasicBlockWriterTest("testException")); + return suite; + } +} diff --git a/jode/test/src/net/sf/jode/bytecode/BasicBlocksTest.java b/jode/test/src/net/sf/jode/bytecode/BasicBlocksTest.java new file mode 100644 index 0000000..f39f174 --- /dev/null +++ b/jode/test/src/net/sf/jode/bytecode/BasicBlocksTest.java @@ -0,0 +1,47 @@ +package net.sf.jode.bytecode; +import junit.framework.*; +import java.io.*; + +public class BasicBlocksTest extends TestCase implements Opcodes { + public BasicBlocksTest(String name) { + super(name); + } + + public void testJsr() { + Block b0 = new Block(); + Block b1 = new Block(); + Block b2 = new Block(); + b0.setCode(new Instruction[] { + Instruction.forOpcode(opc_jsr) + }, new Block[] { b1, null }); + b1.setCode(new Instruction[] { + Instruction.forOpcode(opc_astore, LocalVariableInfo.getInfo(2)), + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(1), -1), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_ifeq) + }, new Block[] { b2, b0 }); + b2.setCode(new Instruction[] { + Instruction.forOpcode(opc_ret, LocalVariableInfo.getInfo(0)), + }, new Block[0]); + assertEquals("pop0", 0, b0.maxpop); + assertEquals("push0", 0, b0.maxpush); + assertEquals("delta0", 0, b0.delta); + assertEquals("pop1", 1, b1.maxpop); + assertEquals("push1", 0, b1.maxpush); + assertEquals("delta1", -1, b1.delta); + assertEquals("pop2", 0, b2.maxpop); + assertEquals("push2", 0, b2.maxpush); + assertEquals("delta2", 0, b2.delta); + BasicBlocks bb = new BasicBlocks(new MethodInfo("foo", "(I)V", 0)); + bb.setBlocks(new Block[] { b0, b1, b2 }, b0, new Handler[0]); + assertEquals("stack0", 0, b0.stackHeight); + assertEquals("stack1", 1, b1.stackHeight); + assertEquals("stack2", 0, b2.stackHeight); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new BasicBlocksTest("testJsr")); + return suite; + } +} diff --git a/jode/test/src/net/sf/jode/bytecode/BlockTest.java b/jode/test/src/net/sf/jode/bytecode/BlockTest.java new file mode 100644 index 0000000..f378b1a --- /dev/null +++ b/jode/test/src/net/sf/jode/bytecode/BlockTest.java @@ -0,0 +1,44 @@ +package net.sf.jode.bytecode; +import junit.framework.*; +import java.io.*; + +public class BlockTest extends TestCase implements Opcodes { + public BlockTest(String name) { + super(name); + } + + public void testJsr() { + Block b1 = new Block(); + Block b2 = new Block(); + Instruction jsr = Instruction.forOpcode(opc_jsr); + b1.setCode(new Instruction[] { jsr }, new Block[] { b2, null } ); + assertEquals("pop", 0, b1.maxpop); + assertEquals("push", 0, b1.maxpush); + assertEquals("delta", 0, b1.delta); + try { + b1.setCode(new Instruction[] { jsr }, new Block[] { b2 }); + fail("jsr must have two successors"); + } catch (IllegalArgumentException ex) { + } + try { + b1.setCode(new Instruction[] { jsr }, + new Block[] { null, b2 }); + fail("jsr succ mustn't be null"); + } catch (IllegalArgumentException ex) { + } + try { + b1.setCode(new Instruction[] { jsr, + Instruction.forOpcode(opc_nop) }, + new Block[] { null, b2 }); + fail("jsr must be last in block"); + } catch (IllegalArgumentException ex) { + } + + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new BlockTest("testJsr")); + return suite; + } +} diff --git a/jode/test/src/net/sf/jode/flow/TrExcTest.java b/jode/test/src/net/sf/jode/flow/TrExcTest.java new file mode 100644 index 0000000..f34f5ee --- /dev/null +++ b/jode/test/src/net/sf/jode/flow/TrExcTest.java @@ -0,0 +1,358 @@ +package net.sf.jode.flow; +import net.sf.jode.decompiler.LocalInfo; +import junit.framework.*; +import net.sf.jode.expr.*; +import net.sf.jode.type.Type; +import net.sf.jode.decompiler.TabbedPrintWriter; +import net.sf.jode.GlobalOptions; + +public class TrExcTest extends TestCase { + private static final boolean VERBOSE = false; + + public TrExcTest(String name) { + super (name); + } + + public void setUp() { + GlobalOptions.debuggingFlags |= GlobalOptions.DEBUG_CHECK; + if (VERBOSE) + GlobalOptions.debuggingFlags + |= GlobalOptions.DEBUG_ANALYZE | GlobalOptions.DEBUG_FLOW; + } + + FlowBlock[] createFlowBlocks(int n) { + FlowBlock[] flows = new FlowBlock[n]; + for (int i = 0; i < n; i++) + flows[i] = new FlowBlock(null, i, i > 0 ? flows[i-1] : null); + return flows; + } + + public void testSynchronized11() throws java.io.IOException { + FlowBlock[] flows = createFlowBlocks(5); + LocalInfo thisLocal = new LocalInfo(null, 0); + LocalInfo tmpLocal = new LocalInfo(null, 1); + LocalInfo tmp2Local = new LocalInfo(null, 2); + + /* Monitorenter */ + flows[0].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[0].appendBlock + (new InstructionBlock(new MonitorEnterOperator())); + flows[0].setSuccessors(new FlowBlock[] { flows[1] }); + + /* Synchronized Blocks */ + flows[1].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[1].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmpLocal))), tmpLocal); + flows[1].appendBlock(new JsrBlock()); + flows[1].setSuccessors(new FlowBlock[] { flows[4], flows[2] }); + + flows[2].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, tmpLocal)), tmpLocal); + flows[2].appendBlock + (new ReturnBlock(new NopOperator(Type.tUObject))); + flows[2].setSuccessors(new FlowBlock[] { FlowBlock.END_OF_METHOD }); + + /* Catch Exception Blocks */ + flows[3].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[3].appendBlock + (new InstructionBlock(new MonitorExitOperator())); + flows[3].appendBlock + (new ThrowBlock(new NopOperator(Type.tUObject))); + flows[3].setSuccessors(new FlowBlock[0]); + + /* monitorexit subroutine */ + flows[4].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmp2Local))), tmp2Local); + flows[4].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[4].appendBlock + (new InstructionBlock(new MonitorExitOperator())); + flows[4].appendReadBlock + (new RetBlock(tmp2Local), tmp2Local); + flows[4].setSuccessors(new FlowBlock[0]); + + flows[0].addStartPred(); + TransformExceptionHandlers exc = new TransformExceptionHandlers(flows); + exc.addHandler(flows[1],flows[2],flows[3], null); + exc.analyze(); + flows[0].analyze(); + flows[0].removeStartPred(); + if (VERBOSE) + flows[0].dumpSource(new TabbedPrintWriter(GlobalOptions.err)); + assertTrue(flows[0].getBlock() instanceof SynchronizedBlock); + assertTrue(flows[0].getBlock().getSubBlocks()[0] + instanceof ReturnBlock); + } + + public void testSynchronized13() throws java.io.IOException { + FlowBlock[] flows = createFlowBlocks(5); + LocalInfo thisLocal = new LocalInfo(null, 0); + LocalInfo tmpLocal = new LocalInfo(null, 1); + LocalInfo tmp2Local = new LocalInfo(null, 2); + + /* Monitorenter */ + flows[0].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[0].appendBlock + (new InstructionBlock(new MonitorEnterOperator())); + flows[0].setSuccessors(new FlowBlock[] { flows[1] }); + + /* Synchronized Blocks */ + flows[1].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[1].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmpLocal))), tmpLocal); + flows[1].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[1].appendBlock + (new InstructionBlock(new MonitorExitOperator())); + flows[1].setSuccessors(new FlowBlock[] { flows[2] }); + + flows[2].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, tmpLocal)), tmpLocal); + flows[2].appendBlock + (new ReturnBlock(new NopOperator(Type.tUObject))); + flows[2].setSuccessors(new FlowBlock[] { FlowBlock.END_OF_METHOD }); + + /* Catch Exception Blocks */ + flows[3].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[3].appendBlock + (new InstructionBlock(new MonitorExitOperator())); + flows[3].appendBlock + (new ThrowBlock(new NopOperator(Type.tUObject))); + flows[3].setSuccessors(new FlowBlock[0]); + + /* monitorexit subroutine */ + flows[4].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmp2Local))), tmp2Local); + flows[4].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[4].appendBlock + (new InstructionBlock(new MonitorExitOperator())); + flows[4].appendReadBlock + (new RetBlock(tmp2Local), tmp2Local); + flows[4].setSuccessors(new FlowBlock[0]); + + flows[0].addStartPred(); + TransformExceptionHandlers exc = new TransformExceptionHandlers(flows); + exc.addHandler(flows[1],flows[2],flows[3], null); + exc.analyze(); + flows[0].analyze(); + flows[0].removeStartPred(); + if (VERBOSE) + flows[0].dumpSource(new TabbedPrintWriter(GlobalOptions.err)); + assertTrue(flows[0].getBlock() instanceof SynchronizedBlock); + assertTrue(flows[0].getBlock().getSubBlocks()[0] + instanceof ReturnBlock); + } + + public void testSpecialFin() throws java.io.IOException { + FlowBlock[] flows = createFlowBlocks(3); + LocalInfo thisLocal = new LocalInfo(null, 0); + LocalInfo tmpLocal = new LocalInfo(null, 1); + LocalInfo tmp2Local = new LocalInfo(null, 2); + + /* Try Blocks */ + flows[0].setSuccessors(new FlowBlock[] { flows[2] }); + + /* Catch Exception Blocks */ + flows[1].appendBlock(new SpecialBlock(SpecialBlock.POP, 1, 0)); + flows[1].setSuccessors(new FlowBlock[] { flows[2] }); + + /* subroutine */ + flows[2].appendBlock(new DescriptionBlock("/*FINALLY*/")); + flows[2].setSuccessors(new FlowBlock[] { FlowBlock.END_OF_METHOD }); + + flows[0].addStartPred(); + TransformExceptionHandlers exc = new TransformExceptionHandlers(flows); + exc.addHandler(flows[0],flows[0],flows[1], null); + exc.analyze(); + flows[0].analyze(); + flows[0].removeStartPred(); + if (VERBOSE) + flows[0].dumpSource(new TabbedPrintWriter(GlobalOptions.err)); + assertTrue("Try", flows[0].getBlock() instanceof TryBlock); + assertTrue("Empty", flows[0].getBlock().getSubBlocks()[0] instanceof EmptyBlock); + assertTrue("Finally", flows[0].getBlock().getSubBlocks()[1] instanceof FinallyBlock); + assertTrue("Descr", flows[0].getBlock().getSubBlocks()[1].getSubBlocks()[0] instanceof DescriptionBlock); + } + + public void testSpecialFinTryLoops() throws java.io.IOException { + FlowBlock[] flows = createFlowBlocks(3); + LocalInfo thisLocal = new LocalInfo(null, 0); + LocalInfo tmpLocal = new LocalInfo(null, 1); + LocalInfo tmp2Local = new LocalInfo(null, 2); + + /* Try Blocks */ + flows[0].setSuccessors(new FlowBlock[] { flows[0] }); + + /* Catch Exception Blocks */ + flows[1].appendBlock(new SpecialBlock(SpecialBlock.POP, 1, 0)); + flows[1].setSuccessors(new FlowBlock[] { flows[2] }); + + /* subroutine */ + flows[2].appendBlock(new DescriptionBlock("/*FINALLY*/")); + flows[2].setSuccessors(new FlowBlock[] { FlowBlock.END_OF_METHOD }); + + flows[0].addStartPred(); + TransformExceptionHandlers exc = new TransformExceptionHandlers(flows); + exc.addHandler(flows[0],flows[0],flows[1], null); + exc.analyze(); + flows[0].analyze(); + flows[0].removeStartPred(); + if (VERBOSE) + flows[0].dumpSource(new TabbedPrintWriter(GlobalOptions.err)); + assertTrue("Try", flows[0].getBlock() instanceof TryBlock); + assertTrue("Loop", flows[0].getBlock().getSubBlocks()[0] instanceof LoopBlock); + assertTrue("Empty", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0] instanceof EmptyBlock); + assertTrue("Finally", flows[0].getBlock().getSubBlocks()[1] instanceof FinallyBlock); + assertTrue("Descr", flows[0].getBlock().getSubBlocks()[1].getSubBlocks()[0] instanceof DescriptionBlock); + } + + public void testFinBreaksJikes() throws java.io.IOException { + FlowBlock[] flows = createFlowBlocks(5); + LocalInfo thisLocal = new LocalInfo(null, 0); + LocalInfo tmpLocal = new LocalInfo(null, 1); + LocalInfo tmp2Local = new LocalInfo(null, 2); + + /* Try Blocks */ + flows[0].appendBlock(new JsrBlock()); + flows[0].setSuccessors(new FlowBlock[] { flows[3], flows[4] }); + + /* Catch Exception Blocks */ + flows[1].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmpLocal))), tmpLocal); + flows[1].appendBlock(new JsrBlock()); + flows[1].setSuccessors(new FlowBlock[] { flows[3], flows[2] }); + + flows[2].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, tmpLocal)), tmpLocal); + flows[2].appendBlock + (new ThrowBlock(new NopOperator(Type.tUObject))); + flows[2].setSuccessors(new FlowBlock[0]); + + /* subroutine */ + flows[3].appendBlock(new SpecialBlock(SpecialBlock.POP, 1, 0)); + flows[3].setSuccessors(new FlowBlock[] { flows[4] }); + + flows[4].appendBlock(new DescriptionBlock("/*HERE*/")); + flows[4].setSuccessors(new FlowBlock[] { FlowBlock.END_OF_METHOD }); + + flows[0].addStartPred(); + TransformExceptionHandlers exc = new TransformExceptionHandlers(flows); + exc.addHandler(flows[0],flows[0],flows[1], null); + exc.analyze(); + flows[0].analyze(); + flows[0].removeStartPred(); + if (VERBOSE) + flows[0].dumpSource(new TabbedPrintWriter(GlobalOptions.err)); + assertTrue("Sequ", flows[0].getBlock() instanceof SequentialBlock); + assertTrue("Loop", flows[0].getBlock().getSubBlocks()[0] instanceof LoopBlock); + assertTrue("Try", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0] instanceof TryBlock); + assertTrue("Empty", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[0] instanceof EmptyBlock); + assertTrue("Finally", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[1] instanceof FinallyBlock); + assertTrue("Break", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[1].getSubBlocks()[0] instanceof BreakBlock); + assertTrue("Descr", flows[0].getBlock().getSubBlocks()[1] + instanceof DescriptionBlock); + } + + public void testFinCondBreaks() throws java.io.IOException { + FlowBlock[] flows = createFlowBlocks(6); + LocalInfo thisLocal = new LocalInfo(null, 0); + LocalInfo tmpLocal = new LocalInfo(null, 1); + LocalInfo tmp2Local = new LocalInfo(null, 2); + + /* Try Blocks */ + flows[0].appendBlock(new JsrBlock()); + flows[0].setSuccessors(new FlowBlock[] { flows[3], flows[5] }); + + /* Catch Exception Blocks */ + flows[1].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmpLocal))), tmpLocal); + flows[1].appendBlock(new JsrBlock()); + flows[1].setSuccessors(new FlowBlock[] { flows[3], flows[2] }); + + flows[2].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, tmpLocal)), tmpLocal); + flows[2].appendBlock + (new ThrowBlock(new NopOperator(Type.tUObject))); + flows[2].setSuccessors(new FlowBlock[0]); + + /* subroutine */ + flows[3].appendWriteBlock + (new InstructionBlock + (new StoreInstruction + (new LocalStoreOperator(Type.tUObject, tmp2Local))), tmp2Local); + flows[3].appendReadBlock + (new InstructionBlock + (new LocalLoadOperator(Type.tUObject, null, thisLocal)), thisLocal); + flows[3].appendBlock(new ConditionalBlock + (new CompareUnaryOperator + (Type.tUObject, Operator.EQUALS_OP))); + flows[3].setSuccessors(new FlowBlock[] { flows[4], flows[5] }); + flows[4].appendReadBlock + (new RetBlock(tmp2Local), tmp2Local); + flows[4].setSuccessors(new FlowBlock[0]); + + flows[5].appendBlock(new DescriptionBlock("/*HERE*/")); + flows[5].setSuccessors(new FlowBlock[] { FlowBlock.END_OF_METHOD }); + + flows[0].addStartPred(); + TransformExceptionHandlers exc = new TransformExceptionHandlers(flows); + exc.addHandler(flows[0],flows[0],flows[1], null); + exc.analyze(); + flows[0].analyze(); + flows[0].removeStartPred(); + if (VERBOSE) + flows[0].dumpSource(new TabbedPrintWriter(GlobalOptions.err)); + assertTrue("Sequ", flows[0].getBlock() instanceof SequentialBlock); + assertTrue("Loop", flows[0].getBlock().getSubBlocks()[0] instanceof LoopBlock); + assertTrue("Try", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0] instanceof TryBlock); + assertTrue("Empty", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[0] instanceof EmptyBlock); + assertTrue("Finally", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[1] instanceof FinallyBlock); + assertTrue("If", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[1].getSubBlocks()[0] instanceof IfThenElseBlock); + assertTrue("Break", flows[0].getBlock().getSubBlocks()[0].getSubBlocks()[0].getSubBlocks()[1].getSubBlocks()[0].getSubBlocks()[0] instanceof BreakBlock); + assertTrue("Descr", flows[0].getBlock().getSubBlocks()[1] + instanceof DescriptionBlock); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new TrExcTest("testSynchronized11")); + suite.addTest(new TrExcTest("testSynchronized13")); + suite.addTest(new TrExcTest("testSpecialFin")); + suite.addTest(new TrExcTest("testSpecialFinTryLoops")); + suite.addTest(new TrExcTest("testFinBreaksJikes")); + suite.addTest(new TrExcTest("testFinCondBreaks")); + return suite; + } +} diff --git a/jode/test/src/net/sf/jode/obfuscator/modules/ConstAnaTest.java b/jode/test/src/net/sf/jode/obfuscator/modules/ConstAnaTest.java new file mode 100644 index 0000000..a9ec5bb --- /dev/null +++ b/jode/test/src/net/sf/jode/obfuscator/modules/ConstAnaTest.java @@ -0,0 +1,186 @@ +package net.sf.jode.obfuscator.modules; +import net.sf.jode.bytecode.*; +import net.sf.jode.GlobalOptions; +import junit.framework.*; +import java.util.BitSet; +import java.io.PrintWriter; + +public class ConstAnaTest extends TestCase implements Opcodes { + ConstantAnalyzer ca; + Instruction[] callJsrInstr; + BasicBlocks jsrMethod; + + public ConstAnaTest(String name) { + super(name); + } + + public void setUp() { + ca = new ConstantAnalyzer(); + createJsrMethod(); + } + + public void createJsrMethod() { + callJsrInstr = new Instruction[] { + Instruction.forOpcode(opc_jsr) + }; + + Block b0 = new Block(); + Block b1 = new Block(); + Block b2 = new Block(); + b0.setCode(callJsrInstr, new Block[] { b1, null }); + b1.setCode(new Instruction[] { + Instruction.forOpcode(opc_astore, LocalVariableInfo.getInfo(2)), + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(1), -1), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_ifeq) + }, new Block[] { b2, b0 }); + b2.setCode(new Instruction[] { + Instruction.forOpcode(opc_ret, LocalVariableInfo.getInfo(2)), + }, new Block[0]); + jsrMethod = new BasicBlocks(new MethodInfo("foo", "(I)V", 0)); + jsrMethod.setBlocks(new Block[] { b0, b1, b2 }, + b0, new Handler[0]); + } + + public void testSimple() throws Exception { + Block b0 = new Block(); + Block b1 = new Block(); + Block b2 = new Block(); + b0.setCode(new Instruction[] { + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(1), 1), + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(2), 1), + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(3), 1), + }, new Block[] { b1 }); + b1.setCode(new Instruction[] { + Instruction.forOpcode(opc_ldc, new Integer(0)), + Instruction.forOpcode(opc_istore, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_ldc, new Integer(4)), + Instruction.forOpcode(opc_istore, LocalVariableInfo.getInfo(2)), + Instruction.forOpcode(opc_ldc, new Integer(0)), + Instruction.forOpcode(opc_istore, LocalVariableInfo.getInfo(3)), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_ifeq) + }, new Block[] { b2, b0 }); + b2.setCode(new Instruction[] { + Instruction.forOpcode(opc_iinc, LocalVariableInfo.getInfo(1), 1), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(2)), + Instruction.forOpcode(opc_if_icmplt) + }, new Block[] { b2, null }); + BasicBlocks bb = new BasicBlocks(new MethodInfo("foo", "()V", 0)); + bb.setBlocks(new Block[] { b0, b1, b2 }, b1, new Handler[0]); + + ca.analyzeCode(null, bb); + + BitSet reachable = (BitSet) ca.bbInfos.get(bb); + assertEquals("Reachable set", + "{1, 2}", reachable.toString()); + assertEquals("constant flow", + ca.CONSTANTFLOW, + Class.forName("net.sf.jode.obfuscator.modules.ConstantAnalyzer$ConstantInfo") + .getDeclaredField("flags") + .getInt(ca.constantInfos.get(b1.getInstructions()[7]))); + ca.transformCode(bb); + + Block[] blocks = bb.getBlocks(); + assertEquals(2, blocks.length); + assertEquals(1, blocks[0].getSuccs().length); + assertEquals(blocks[1], blocks[0].getSuccs()[0]); + assertEquals(2, blocks[1].getSuccs().length); + assertEquals(blocks[1], blocks[1].getSuccs()[0]); + assertEquals(null, blocks[1].getSuccs()[1]); + } + + public void testJsr() throws Exception { + Block[] blocks = jsrMethod.getBlocks(); + + ca.analyzeCode(null, jsrMethod); + + BitSet reachable = (BitSet) ca.bbInfos.get(jsrMethod); + assertEquals("Reachable set", + "{0, 1, 2}", reachable.toString()); + ca.transformCode(jsrMethod); + + blocks = jsrMethod.getBlocks(); + assertEquals(3, blocks.length); + assertEquals(2, blocks[0].getSuccs().length); + assertEquals(blocks[1], blocks[0].getSuccs()[0]); + assertEquals(null, blocks[0].getSuccs()[1]); + assertEquals(2, blocks[1].getSuccs().length); + assertEquals(0, blocks[2].getSuccs().length); + } + + public void testNestedJsr() throws Exception { + Block b0 = new Block(); + Block b1 = new Block(); + Block b2 = new Block(); + Block b3 = new Block(); + Block b4 = new Block(); + Block b5 = new Block(); + Block b6 = new Block(); + Block b7 = new Block(); + Block b8 = new Block(); + Block b9 = new Block(); + + b0.setCode(new Instruction[] { + Instruction.forOpcode(opc_jsr) + }, new Block[] { b2, b1 }); + b1.setCode(new Instruction[] { + Instruction.forOpcode(opc_jsr) + }, new Block[] { b5, null }); + b2.setCode(new Instruction[] { + Instruction.forOpcode(opc_astore, LocalVariableInfo.getInfo(2)), + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_ifeq) + }, new Block[] { b3, b7 }); + b3.setCode(new Instruction[] { + Instruction.forOpcode(opc_jsr) + }, new Block[] { b5, b1 }); + b4.setCode(new Instruction[] { + Instruction.forOpcode(opc_ret, LocalVariableInfo.getInfo(2)), + }, new Block[0]); + b5.setCode(new Instruction[] { + Instruction.forOpcode(opc_astore, LocalVariableInfo.getInfo(3)), + Instruction.forOpcode(opc_ret, LocalVariableInfo.getInfo(3)), + }, new Block[0]); + b6.setCode(new Instruction[] { + Instruction.forOpcode(opc_astore, LocalVariableInfo.getInfo(3)) + }, new Block[] { b4 }); + b7.setCode(new Instruction[] { + Instruction.forOpcode(opc_iload, LocalVariableInfo.getInfo(1)), + Instruction.forOpcode(opc_ifeq) + }, new Block[] { b8, b4 }); + b8.setCode(new Instruction[] { + Instruction.forOpcode(opc_jsr) + }, new Block[] { b6, b9 }); + b9.setCode(new Instruction[0], new Block[] { b7 }); + + BasicBlocks bb = new BasicBlocks(new MethodInfo("foo", "(I)V", 0)); + bb.setBlocks(new Block[] { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 }, + b0, new Handler[0]); + ca.analyzeCode(null, bb); + + BitSet reachable = (BitSet) ca.bbInfos.get(bb); + assertEquals("Reachable set", + "{0, 1, 2, 3, 4, 5, 6, 7, 8}", reachable.toString()); + assertNotNull("Constant Flow", + ca.constantInfos.get(b8.getInstructions()[0])); + + ca.transformCode(bb); + + Block[] blocks = bb.getBlocks(); + assertEquals(9, blocks.length); + assertEquals(2, blocks[0].getSuccs().length); + assertEquals(2, blocks[1].getSuccs().length); + assertEquals(2, blocks[3].getSuccs().length); + assertEquals(1, blocks[8].getSuccs().length); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new ConstAnaTest("testSimple")); + suite.addTest(new ConstAnaTest("testJsr")); + suite.addTest(new ConstAnaTest("testNestedJsr")); + return suite; + } +}