From 7603a3d4c9ebf1e98a9f2aba53ae1140cdd34497 Mon Sep 17 00:00:00 2001 From: Hyunsik Choi Date: Sun, 24 Aug 2014 02:36:37 +0900 Subject: [PATCH] TAJO-906: Runtime code generation for evaluating expression trees. Closes #113 --- CHANGES | 3 + LICENSE | 31 + pom.xml | 52 +- tajo-algebra/pom.xml | 13 +- tajo-catalog/pom.xml | 12 +- tajo-catalog/tajo-catalog-client/pom.xml | 13 +- tajo-catalog/tajo-catalog-common/pom.xml | 13 +- .../org/apache/tajo/catalog/CatalogUtil.java | 2 +- .../org/apache/tajo/catalog/FunctionDesc.java | 2 +- tajo-catalog/tajo-catalog-drivers/pom.xml | 11 +- .../tajo-hcatalog/pom.xml | 19 +- tajo-catalog/tajo-catalog-server/pom.xml | 20 +- tajo-client/pom.xml | 14 +- tajo-common/pom.xml | 13 +- .../java/org/apache/tajo/SessionVars.java | 2 +- .../java/org/apache/tajo/conf/TajoConf.java | 2 +- .../java/org/apache/tajo/datum/CharDatum.java | 5 +- .../java/org/apache/tajo/datum/Datum.java | 4 + .../org/apache/tajo/datum/DatumFactory.java | 32 +- .../org/apache/tajo/datum/Inet4Datum.java | 5 + .../java/org/apache/tajo/datum/NullDatum.java | 5 + .../apache/tajo/util/CommonTestingUtil.java | 24 +- .../main/java/org/apache/tajo/util/Pair.java | 14 +- .../tajo/util/datetime/DateTimeUtil.java | 35 + tajo-core/pom.xml | 34 +- .../tajo/engine/codegen/CaseWhenEmitter.java | 210 ++ .../codegen/CaseWhenSwitchGenerator.java | 92 + .../tajo/engine/codegen/CodeGenUtils.java | 41 + .../tajo/engine/codegen/CompilationError.java | 31 + .../tajo/engine/codegen/EvalCodeEmitter.java | 27 + .../engine/codegen/EvalCodeGenContext.java | 223 ++ .../engine/codegen/EvalCodeGenerator.java | 840 ++++++ .../engine/codegen/ExecutorPreCompiler.java | 213 ++ .../TajoClassLoader.java} | 13 +- .../engine/codegen/TajoGeneratorAdapter.java | 953 ++++++ .../engine/codegen/VariablesPreBuilder.java | 83 + .../apache/tajo/engine/eval/BinaryEval.java | 1 + .../org/apache/tajo/engine/eval/EvalNode.java | 2 - .../apache/tajo/engine/eval/EvalTreeUtil.java | 16 +- .../org/apache/tajo/engine/eval/EvalType.java | 10 + .../apache/tajo/engine/eval/FunctionEval.java | 11 +- .../tajo/engine/eval/LikePredicateEval.java | 4 + .../eval/PatternMatchPredicateEval.java | 8 + .../engine/eval/SimilarToPredicateEval.java | 5 + .../engine/eval/SimpleEvalNodeVisitor.java | 3 - .../function/builtin/CoalesceBoolean.java | 4 +- .../engine/function/builtin/CoalesceDate.java | 3 +- .../function/builtin/CoalesceDouble.java | 3 +- .../engine/function/builtin/CoalesceLong.java | 3 +- .../function/builtin/CoalesceString.java | 3 +- .../engine/function/builtin/CoalesceTime.java | 3 +- .../function/builtin/CoalesceTimestamp.java | 3 +- .../tajo/engine/planner/ExprAnnotator.java | 15 +- .../engine/planner/PhysicalPlannerImpl.java | 4 + .../apache/tajo/engine/planner/Projector.java | 33 +- .../engine/planner/logical/HavingNode.java | 15 +- .../tajo/engine/planner/logical/ScanNode.java | 12 +- .../planner/logical/SelectableNode.java | 48 + .../engine/planner/logical/SelectionNode.java | 15 +- .../engine/planner/physical/BNLJoinExec.java | 9 +- .../planner/physical/BSTIndexScanExec.java | 2 +- .../BasicPhysicalExecutorVisitor.java | 2 + .../planner/physical/BinaryPhysicalExec.java | 2 + .../planner/physical/ExternalSortExec.java | 1 + .../physical/HashFullOuterJoinExec.java | 8 +- .../engine/planner/physical/HashJoinExec.java | 7 +- .../physical/HashLeftOuterJoinExec.java | 7 +- .../physical/HashLeftSemiJoinExec.java | 4 + .../physical/MergeFullOuterJoinExec.java | 7 +- .../planner/physical/MergeJoinExec.java | 7 +- .../engine/planner/physical/NLJoinExec.java | 2 +- .../planner/physical/NLLeftOuterJoinExec.java | 2 +- .../physical/PartitionMergeScanExec.java | 5 +- .../engine/planner/physical/PhysicalExec.java | 11 +- .../physical/PhysicalExecutorVisitor.java | 2 + .../planner/physical/PhysicalPlanUtil.java | 1 + .../planner/physical/ProjectionExec.java | 2 +- .../physical/RightOuterMergeJoinExec.java | 7 +- .../planner/physical/SelectionExec.java | 8 +- .../engine/planner/physical/SeqScanExec.java | 34 +- .../planner/physical/UnaryPhysicalExec.java | 5 + .../engine/query/QueryUnitRequestImpl.java | 2 +- .../org/apache/tajo/master/GlobalEngine.java | 14 +- .../tajo/master/LaunchTaskRunnersEvent.java | 45 + .../tajo/master/TajoContainerProxy.java | 10 +- .../org/apache/tajo/master/TajoMaster.java | 2 +- .../tajo/master/querymaster/QueryMaster.java | 1 - .../tajo/master/querymaster/SubQuery.java | 7 +- .../worker/ExecutionBlockSharedResource.java | 139 + .../tajo/worker/TajoResourceAllocator.java | 14 +- .../org/apache/tajo/worker/TajoWorker.java | 31 +- .../tajo/worker/TajoWorkerManagerService.java | 20 +- .../java/org/apache/tajo/worker/Task.java | 61 +- .../tajo/worker/TaskAttemptContext.java | 30 +- .../src/main/proto/TajoWorkerProtocol.proto | 3 + .../apache/tajo/LocalTajoTestingUtility.java | 6 +- .../org/apache/tajo/TajoTestingCluster.java | 8 +- .../engine/codegen/TestEvalCodeGenerator.java | 311 ++ .../engine/codegen/TestGeneratorAdapter.java | 41 + .../apache/tajo/engine/eval/ExprTestBase.java | 83 +- .../tajo/engine/eval/TestPredicates.java | 17 + .../tajo/engine/eval/TestSQLExpression.java | 24 +- .../function/TestConditionalExpressions.java | 123 +- .../TestPatternMatchingPredicates.java | 2 +- .../planner/physical/TestPhysicalPlanner.java | 6 +- .../org/apache/tajo/worker/TestFetcher.java | 13 +- .../TestTajoCli/testHelpSessionVars.result | 1 + tajo-dist/pom.xml | 22 + tajo-dist/src/main/bin/tajo | 4 +- tajo-docs/pom.xml | 18 + tajo-jdbc/pom.xml | 13 +- .../org/apache/tajo/jdbc/MetaDataTuple.java | 6 + tajo-maven-plugins/pom.xml | 12 + tajo-project/pom.xml | 23 +- tajo-rpc/pom.xml | 12 + tajo-storage/pom.xml | 18 +- .../org/apache/tajo/storage/FrameTuple.java | 6 + .../org/apache/tajo/storage/LazyTuple.java | 9 +- .../java/org/apache/tajo/storage/Tuple.java | 3 + .../java/org/apache/tajo/storage/VTuple.java | 6 + .../tajo/storage/parquet/ParquetAppender.java | 11 +- .../tajo/storage/parquet/TestReadWrite.java | 7 +- tajo-thirdparty/asm/pom.xml | 180 ++ .../org/objectweb/asm/AnnotationVisitor.java | 169 ++ .../org/objectweb/asm/AnnotationWriter.java | 318 ++ .../tajo/org/objectweb/asm/Attribute.java | 255 ++ .../tajo/org/objectweb/asm/ByteVector.java | 312 ++ .../tajo/org/objectweb/asm/ClassReader.java | 2202 ++++++++++++++ .../tajo/org/objectweb/asm/ClassVisitor.java | 286 ++ .../tajo/org/objectweb/asm/ClassWriter.java | 1706 +++++++++++ .../tajo/org/objectweb/asm/Context.java | 110 + .../apache/tajo/org/objectweb/asm/Edge.java | 75 + .../tajo/org/objectweb/asm/FieldVisitor.java | 121 + .../tajo/org/objectweb/asm/FieldWriter.java | 273 ++ .../apache/tajo/org/objectweb/asm/Frame.java | 1453 +++++++++ .../apache/tajo/org/objectweb/asm/Handle.java | 170 ++ .../tajo/org/objectweb/asm/Handler.java | 121 + .../apache/tajo/org/objectweb/asm/Item.java | 311 ++ .../apache/tajo/org/objectweb/asm/Label.java | 560 ++++ .../tajo/org/objectweb/asm/MethodVisitor.java | 662 ++++ .../tajo/org/objectweb/asm/MethodWriter.java | 2685 +++++++++++++++++ .../tajo/org/objectweb/asm/Opcodes.java | 358 +++ .../apache/tajo/org/objectweb/asm/Type.java | 895 ++++++ .../tajo/org/objectweb/asm/attrs/package.html | 66 + .../objectweb/asm/commons/AdviceAdapter.java | 625 ++++ .../asm/commons/AnalyzerAdapter.java | 920 ++++++ .../asm/commons/CodeSizeEvaluator.java | 217 ++ .../asm/commons/GeneratorAdapter.java | 1641 ++++++++++ .../asm/commons/InstructionAdapter.java | 1090 +++++++ .../asm/commons/JSRInlinerAdapter.java | 746 +++++ .../asm/commons/LocalVariablesSorter.java | 361 +++ .../org/objectweb/asm/commons/Method.java | 282 ++ .../org/objectweb/asm/commons/Remapper.java | 223 ++ .../commons/RemappingAnnotationAdapter.java | 79 + .../asm/commons/RemappingClassAdapter.java | 126 + .../asm/commons/RemappingFieldAdapter.java | 62 + .../asm/commons/RemappingMethodAdapter.java | 161 + .../commons/RemappingSignatureAdapter.java | 155 + .../asm/commons/SerialVersionUIDAdder.java | 533 ++++ .../objectweb/asm/commons/SimpleRemapper.java | 69 + .../asm/commons/StaticInitMerger.java | 96 + .../asm/commons/TableSwitchGenerator.java | 57 + .../asm/commons/TryCatchBlockSorter.java | 92 + .../org/objectweb/asm/commons/package.html | 66 + .../AnnotationConstantsCollector.java | 147 + .../optimizer/ClassConstantsCollector.java | 184 ++ .../asm/optimizer/ClassOptimizer.java | 167 + .../org/objectweb/asm/optimizer/Constant.java | 323 ++ .../objectweb/asm/optimizer/ConstantPool.java | 249 ++ .../optimizer/FieldConstantsCollector.java | 75 + .../objectweb/asm/optimizer/JarOptimizer.java | 234 ++ .../optimizer/MethodConstantsCollector.java | 161 + .../asm/optimizer/MethodOptimizer.java | 167 + .../objectweb/asm/optimizer/NameMapping.java | 114 + .../org/objectweb/asm/optimizer/Shrinker.java | 283 ++ .../asm/optimizer/jdk1.2.2_017.txt.gz | Bin 0 -> 113814 bytes .../asm/optimizer/jdk1.3.1_19.txt.gz | Bin 0 -> 128067 bytes .../optimizer/shrink-annotations.properties | 71 + .../asm/optimizer/shrink-frames.properties | 80 + .../asm/optimizer/shrink-resize.properties | 55 + .../optimizer/shrink-signatures.properties | 61 + .../asm/optimizer/shrink-writer.properties | 84 + .../objectweb/asm/optimizer/shrink.properties | 372 +++ .../tajo/org/objectweb/asm/package.html | 105 + .../asm/signature/SignatureReader.java | 228 ++ .../asm/signature/SignatureVisitor.java | 235 ++ .../asm/signature/SignatureWriter.java | 227 ++ .../org/objectweb/asm/signature/package.html | 54 + .../objectweb/asm/tree/AbstractInsnNode.java | 248 ++ .../objectweb/asm/tree/AnnotationNode.java | 225 ++ .../org/objectweb/asm/tree/ClassNode.java | 345 +++ .../org/objectweb/asm/tree/FieldInsnNode.java | 108 + .../org/objectweb/asm/tree/FieldNode.java | 243 ++ .../org/objectweb/asm/tree/FrameNode.java | 210 ++ .../org/objectweb/asm/tree/IincInsnNode.java | 82 + .../objectweb/asm/tree/InnerClassNode.java | 101 + .../tajo/org/objectweb/asm/tree/InsnList.java | 600 ++++ .../tajo/org/objectweb/asm/tree/InsnNode.java | 87 + .../org/objectweb/asm/tree/IntInsnNode.java | 87 + .../asm/tree/InvokeDynamicInsnNode.java | 100 + .../org/objectweb/asm/tree/JumpInsnNode.java | 95 + .../org/objectweb/asm/tree/LabelNode.java | 78 + .../org/objectweb/asm/tree/LdcInsnNode.java | 78 + .../objectweb/asm/tree/LineNumberNode.java | 84 + .../objectweb/asm/tree/LocalVariableNode.java | 112 + .../asm/tree/LookupSwitchInsnNode.java | 117 + .../objectweb/asm/tree/MethodInsnNode.java | 109 + .../org/objectweb/asm/tree/MethodNode.java | 597 ++++ .../asm/tree/MultiANewArrayInsnNode.java | 83 + .../asm/tree/TableSwitchInsnNode.java | 113 + .../objectweb/asm/tree/TryCatchBlockNode.java | 94 + .../org/objectweb/asm/tree/TypeInsnNode.java | 90 + .../org/objectweb/asm/tree/VarInsnNode.java | 93 + .../objectweb/asm/tree/analysis/Analyzer.java | 549 ++++ .../asm/tree/analysis/AnalyzerException.java | 61 + .../asm/tree/analysis/BasicInterpreter.java | 358 +++ .../asm/tree/analysis/BasicValue.java | 111 + .../asm/tree/analysis/BasicVerifier.java | 433 +++ .../objectweb/asm/tree/analysis/Frame.java | 729 +++++ .../asm/tree/analysis/Interpreter.java | 226 ++ .../asm/tree/analysis/SimpleVerifier.java | 320 ++ .../objectweb/asm/tree/analysis/SmallSet.java | 134 + .../asm/tree/analysis/SourceInterpreter.java | 198 ++ .../asm/tree/analysis/SourceValue.java | 97 + .../asm/tree/analysis/Subroutine.java | 90 + .../objectweb/asm/tree/analysis/Value.java | 45 + .../objectweb/asm/tree/analysis/package.html | 85 + .../tajo/org/objectweb/asm/tree/package.html | 210 ++ .../org/objectweb/asm/util/ASMifiable.java | 56 + .../tajo/org/objectweb/asm/util/ASMifier.java | 1151 +++++++ .../asm/util/CheckAnnotationAdapter.java | 136 + .../objectweb/asm/util/CheckClassAdapter.java | 914 ++++++ .../objectweb/asm/util/CheckFieldAdapter.java | 100 + .../asm/util/CheckMethodAdapter.java | 1397 +++++++++ .../asm/util/CheckSignatureAdapter.java | 330 ++ .../tajo/org/objectweb/asm/util/Printer.java | 500 +++ .../org/objectweb/asm/util/Textifiable.java | 56 + .../org/objectweb/asm/util/Textifier.java | 1200 ++++++++ .../asm/util/TraceAnnotationVisitor.java | 89 + .../objectweb/asm/util/TraceClassVisitor.java | 209 ++ .../objectweb/asm/util/TraceFieldVisitor.java | 76 + .../asm/util/TraceMethodVisitor.java | 223 ++ .../asm/util/TraceSignatureVisitor.java | 317 ++ .../tajo/org/objectweb/asm/util/package.html | 58 + .../objectweb/asm/xml/ASMContentHandler.java | 1317 ++++++++ .../tajo/org/objectweb/asm/xml/Processor.java | 1044 +++++++ .../org/objectweb/asm/xml/SAXAdapter.java | 89 + .../asm/xml/SAXAnnotationAdapter.java | 185 ++ .../objectweb/asm/xml/SAXClassAdapter.java | 324 ++ .../org/objectweb/asm/xml/SAXCodeAdapter.java | 362 +++ .../objectweb/asm/xml/SAXFieldAdapter.java | 63 + .../tajo/org/objectweb/asm/xml/asm-xml.dtd | 367 +++ .../tajo/org/objectweb/asm/xml/package.html | 114 + tajo-yarn-pullserver/pom.xml | 17 + .../java/org/apache/tajo/storage/Tuple.java | 27 +- 255 files changed, 47476 insertions(+), 284 deletions(-) create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenEmitter.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenSwitchGenerator.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/CodeGenUtils.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/CompilationError.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeEmitter.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenContext.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenerator.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java rename tajo-core/src/main/java/org/apache/tajo/engine/{planner/physical/PhysicalPlanningException.java => codegen/TajoClassLoader.java} (74%) create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoGeneratorAdapter.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/codegen/VariablesPreBuilder.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectableNode.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/master/LaunchTaskRunnersEvent.java create mode 100644 tajo-core/src/main/java/org/apache/tajo/worker/ExecutionBlockSharedResource.java create mode 100644 tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestEvalCodeGenerator.java create mode 100644 tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestGeneratorAdapter.java create mode 100644 tajo-thirdparty/asm/pom.xml create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationWriter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Attribute.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ByteVector.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassReader.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassWriter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Context.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Edge.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldWriter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Frame.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handle.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handler.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Item.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Label.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodWriter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Opcodes.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Type.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/attrs/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AdviceAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AnalyzerAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/CodeSizeEvaluator.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/GeneratorAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/InstructionAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/JSRInlinerAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/LocalVariablesSorter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/Method.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/Remapper.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/RemappingAnnotationAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/RemappingClassAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/RemappingFieldAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/RemappingMethodAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/RemappingSignatureAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/SerialVersionUIDAdder.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/SimpleRemapper.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/StaticInitMerger.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/TableSwitchGenerator.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/TryCatchBlockSorter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/AnnotationConstantsCollector.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/ClassConstantsCollector.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/ClassOptimizer.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/Constant.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/ConstantPool.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/FieldConstantsCollector.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/JarOptimizer.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/MethodConstantsCollector.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/MethodOptimizer.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/NameMapping.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/Shrinker.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/jdk1.2.2_017.txt.gz create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/jdk1.3.1_19.txt.gz create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/shrink-annotations.properties create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/shrink-frames.properties create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/shrink-resize.properties create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/shrink-signatures.properties create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/shrink-writer.properties create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/optimizer/shrink.properties create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/signature/SignatureReader.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/signature/SignatureVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/signature/SignatureWriter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/signature/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/AbstractInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/AnnotationNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/ClassNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/FieldInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/FieldNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/FrameNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/IincInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/InnerClassNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/InsnList.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/InsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/IntInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/InvokeDynamicInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/JumpInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/LabelNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/LdcInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/LineNumberNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/LocalVariableNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/LookupSwitchInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/MethodInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/MethodNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/MultiANewArrayInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/TableSwitchInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/TryCatchBlockNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/TypeInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/VarInsnNode.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/Analyzer.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/AnalyzerException.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/BasicInterpreter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/BasicValue.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/BasicVerifier.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/Frame.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/Interpreter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/SimpleVerifier.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/SmallSet.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/SourceInterpreter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/SourceValue.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/Subroutine.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/Value.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/analysis/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/tree/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/ASMifiable.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/ASMifier.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/CheckAnnotationAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/CheckClassAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/CheckFieldAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/CheckMethodAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/CheckSignatureAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/Printer.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/Textifiable.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/Textifier.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/TraceAnnotationVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/TraceClassVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/TraceFieldVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/TraceMethodVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/TraceSignatureVisitor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/util/package.html create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/ASMContentHandler.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/Processor.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/SAXAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/SAXAnnotationAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/SAXClassAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/SAXCodeAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/SAXFieldAdapter.java create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/asm-xml.dtd create mode 100644 tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/xml/package.html diff --git a/CHANGES b/CHANGES index f2798b4045..2b5f149e5a 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,9 @@ Release 0.9.0 - unreleased IMPROVEMENT + TAJO-906: Runtime code generation for evaluating expression trees. + (hyunsik) + TAJO-931: Output file can be punctuated depending on the file size. (hyunsik) diff --git a/LICENSE b/LICENSE index 86975d5c70..ca54d5ea39 100644 --- a/LICENSE +++ b/LICENSE @@ -340,3 +340,34 @@ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +----------------------------------------------------------------------------- +Copyright (c) 2000-2011 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pom.xml b/pom.xml index bf33c69305..d7361bde10 100644 --- a/pom.xml +++ b/pom.xml @@ -90,6 +90,7 @@ tajo-storage tajo-yarn-pullserver tajo-dist + tajo-thirdparty/asm @@ -178,9 +179,9 @@ - org.apache.maven.plugins - maven-pmd-plugin - 2.7.1 + org.apache.rat + apache-rat-plugin + 0.7 org.apache.maven.plugins @@ -336,51 +337,6 @@ org.apache.rat apache-rat-plugin - 0.8 - - - verify - - check - - - - - false - 0 - - CHANGES - **/workers - **/querymasters - **/*.sql - **/*.hiveql - **/*.schema - **/*.tbl - **/*.js - **/*.result - **/*.json - **/*.avsc - - **/target/** - **/*.log - - .git/** - .gitignore - *.patch - .idea/** - **/*.iml - **/*.ipr - **/.project - **/.classpath - **/.settings/** - atlassian-ide-plugin.xml - .reviewboardrc - **/sphinx_rtd_theme/**/* - - **/*.rst - **/resources/* - - diff --git a/tajo-algebra/pom.xml b/tajo-algebra/pom.xml index e8ff2bc41e..a49c212144 100644 --- a/tajo-algebra/pom.xml +++ b/tajo-algebra/pom.xml @@ -43,6 +43,18 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-jar-plugin @@ -79,7 +91,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-catalog/pom.xml b/tajo-catalog/pom.xml index e3ae9df84b..970959a9cc 100644 --- a/tajo-catalog/pom.xml +++ b/tajo-catalog/pom.xml @@ -44,10 +44,13 @@ + + org.apache.rat + apache-rat-plugin + org.apache.maven.plugins maven-surefire-report-plugin - 2.15 maven-deploy-plugin @@ -55,12 +58,6 @@ true - - org.apache.rat - apache-rat-plugin - - - @@ -163,7 +160,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-catalog/tajo-catalog-client/pom.xml b/tajo-catalog/tajo-catalog-client/pom.xml index 959eb4c18c..4db1c266c4 100644 --- a/tajo-catalog/tajo-catalog-client/pom.xml +++ b/tajo-catalog/tajo-catalog-client/pom.xml @@ -46,6 +46,18 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-antrun-plugin @@ -110,7 +122,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-catalog/tajo-catalog-common/pom.xml b/tajo-catalog/tajo-catalog-common/pom.xml index 34f2f4bb84..b82495fdbf 100644 --- a/tajo-catalog/tajo-catalog-common/pom.xml +++ b/tajo-catalog/tajo-catalog-common/pom.xml @@ -46,6 +46,18 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-antrun-plugin @@ -129,7 +141,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java index ae00084f58..a2d9796bc1 100644 --- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java +++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java @@ -481,7 +481,7 @@ public static boolean isMatchedFunction(List definedTypes, List getFuncClass() throws InternalException { + public Class getFuncClass() { return this.funcClass; } diff --git a/tajo-catalog/tajo-catalog-drivers/pom.xml b/tajo-catalog/tajo-catalog-drivers/pom.xml index b57967b274..2cb05eec6a 100644 --- a/tajo-catalog/tajo-catalog-drivers/pom.xml +++ b/tajo-catalog/tajo-catalog-drivers/pom.xml @@ -37,10 +37,18 @@ + + org.apache.rat + apache-rat-plugin + + + tajo-hcatalog/** + + + org.apache.maven.plugins maven-surefire-report-plugin - 2.15 maven-deploy-plugin @@ -68,7 +76,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-catalog/tajo-catalog-drivers/tajo-hcatalog/pom.xml b/tajo-catalog/tajo-catalog-drivers/tajo-hcatalog/pom.xml index bac9d27ce6..02ea16be0a 100644 --- a/tajo-catalog/tajo-catalog-drivers/tajo-hcatalog/pom.xml +++ b/tajo-catalog/tajo-catalog-drivers/tajo-hcatalog/pom.xml @@ -46,6 +46,23 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + derby.log + + + org.apache.maven.plugins maven-dependency-plugin @@ -69,7 +86,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 @@ -371,7 +387,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-catalog/tajo-catalog-server/pom.xml b/tajo-catalog/tajo-catalog-server/pom.xml index a6812fc42e..1e9e2c2336 100644 --- a/tajo-catalog/tajo-catalog-server/pom.xml +++ b/tajo-catalog/tajo-catalog-server/pom.xml @@ -46,6 +46,24 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + derby.log + src/main/resources/schemas/**/*.sql + + + org.apache.maven.plugins maven-antrun-plugin @@ -106,7 +124,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 @@ -217,7 +234,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-client/pom.xml b/tajo-client/pom.xml index 0002a681b6..1c109f594b 100644 --- a/tajo-client/pom.xml +++ b/tajo-client/pom.xml @@ -57,10 +57,21 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-surefire-plugin - 2.12.4 TRUE @@ -332,7 +343,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-common/pom.xml b/tajo-common/pom.xml index da2a7d0208..f1a8c4056e 100644 --- a/tajo-common/pom.xml +++ b/tajo-common/pom.xml @@ -99,6 +99,18 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-antrun-plugin @@ -319,7 +331,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-common/src/main/java/org/apache/tajo/SessionVars.java b/tajo-common/src/main/java/org/apache/tajo/SessionVars.java index 5503aaa37a..c32fd4383e 100644 --- a/tajo-common/src/main/java/org/apache/tajo/SessionVars.java +++ b/tajo-common/src/main/java/org/apache/tajo/SessionVars.java @@ -108,8 +108,8 @@ public enum SessionVars implements ConfigKey { HASH_GROUPBY_SIZE_LIMIT(ConfVars.$EXECUTOR_GROUPBY_INMEMORY_HASH_THRESHOLD, "limited size for hash groupby (mb)", DEFAULT), MAX_OUTPUT_FILE_SIZE(ConfVars.$MAX_OUTPUT_FILE_SIZE, "Maximum per-output file size (mb). 0 means infinite.", DEFAULT), - NULL_CHAR(ConfVars.$CSVFILE_NULL, "null char of text file output", DEFAULT), + CODEGEN(ConfVars.$CODEGEN, "Runtime code generation enabled (experiment)", DEFAULT), // Behavior Control --------------------------------------------------------- ARITHABORT(ConfVars.$BEHAVIOR_ARITHMETIC_ABORT, diff --git a/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java b/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java index 77c2363665..178d80e950 100644 --- a/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java +++ b/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java @@ -324,7 +324,7 @@ public static enum ConfVars implements ConfigKey { $EXECUTOR_GROUPBY_INMEMORY_HASH_THRESHOLD("tajo.executor.groupby.in-memory-hash-threshold-bytes", (long)256 * 1048576), $MAX_OUTPUT_FILE_SIZE("tajo.query.max-outfile-size-mb", 0), // zero means infinite - + $CODEGEN("tajo.executor.codegen.enabled", false), // Runtime code generation // Client ----------------------------------------------------------------- $CLIENT_SESSION_EXPIRY_TIME("tajo.client.session.expiry-time-sec", 3600), // default time is one hour. diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/CharDatum.java b/tajo-common/src/main/java/org/apache/tajo/datum/CharDatum.java index e6c4d94ccb..750930f0da 100644 --- a/tajo-common/src/main/java/org/apache/tajo/datum/CharDatum.java +++ b/tajo-common/src/main/java/org/apache/tajo/datum/CharDatum.java @@ -149,9 +149,8 @@ public Datum equalsTo(Datum datum) { public int compareTo(Datum datum) { switch (datum.type()) { case CHAR: - CharDatum other = (CharDatum) datum; - return UnsignedBytes.lexicographicalComparator().compare(bytes, other.bytes); - + case TEXT: + return UnsignedBytes.lexicographicalComparator().compare(bytes, datum.asByteArray()); case NULL_TYPE: return -1; diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/Datum.java b/tajo-common/src/main/java/org/apache/tajo/datum/Datum.java index 29d7a04341..442db71e91 100644 --- a/tajo-common/src/main/java/org/apache/tajo/datum/Datum.java +++ b/tajo-common/src/main/java/org/apache/tajo/datum/Datum.java @@ -53,6 +53,10 @@ public boolean isNull() { return false; } + public boolean isNotNull() { + return true; + } + public boolean asBool() { throw new InvalidCastException(type, Type.BOOLEAN); } diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java b/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java index 7d6ef186c0..17cfc7a9b8 100644 --- a/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java +++ b/tajo-common/src/main/java/org/apache/tajo/datum/DatumFactory.java @@ -271,17 +271,15 @@ public static DateDatum createDate(int year, int month, int day) { } public static DateDatum createDate(String dateStr) { - TimeMeta tm = DateTimeUtil.decodeDateTime(dateStr); - return new DateDatum(DateTimeUtil.date2j(tm.years, tm.monthOfYear, tm.dayOfMonth)); + return new DateDatum(DateTimeUtil.toJulianDate(dateStr)); } public static TimeDatum createTime(long instance) { return new TimeDatum(instance); } - public static TimeDatum createTime(String dateStr) { - TimeMeta tm = DateTimeUtil.decodeDateTime(dateStr); - return new TimeDatum(DateTimeUtil.toTime(tm)); + public static TimeDatum createTime(String timeStr) { + return new TimeDatum(DateTimeUtil.toJulianTime(timeStr)); } public static TimestampDatum createTimestmpDatumWithJavaMillis(long millis) { @@ -300,6 +298,11 @@ public static IntervalDatum createInterval(String intervalStr) { return new IntervalDatum(intervalStr); } + @SuppressWarnings("unused") + public static IntervalDatum createInterval(long interval) { + return new IntervalDatum(interval); + } + public static DateDatum createDate(Datum datum) { switch (datum.type()) { case INT4: @@ -333,11 +336,7 @@ public static TimeDatum createTime(Datum datum) { public static TimestampDatum createTimestamp(Datum datum) { switch (datum.type()) { case TEXT: - long timestamp = DateTimeUtil.toJulianTimestamp(datum.asChars()); - TimeMeta tm = new TimeMeta(); - DateTimeUtil.toJulianTimeMeta(timestamp, tm); - DateTimeUtil.toUTCTimezone(tm); - return new TimestampDatum(DateTimeUtil.toJulianTimestamp(tm)); + return parseTimestamp(datum.asChars()); case TIMESTAMP: return (TimestampDatum) datum; default: @@ -345,6 +344,15 @@ public static TimestampDatum createTimestamp(Datum datum) { } } + @SuppressWarnings("unused") + public static TimestampDatum createTimestamp(long julianTimestamp) { + return new TimestampDatum(julianTimestamp); + } + + public static TimestampDatum parseTimestamp(String str) { + return new TimestampDatum(DateTimeUtil.toJulianTimestampWithTZ(str)); + } + public static BlobDatum createBlob(byte[] val) { return new BlobDatum(val); } @@ -357,6 +365,10 @@ public static BlobDatum createBlob(String val) { return new BlobDatum(val.getBytes()); } + public static Inet4Datum createInet4(int encoded) { + return new Inet4Datum(encoded); + } + public static Inet4Datum createInet4(byte[] val) { return new Inet4Datum(val); } diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/Inet4Datum.java b/tajo-common/src/main/java/org/apache/tajo/datum/Inet4Datum.java index 08b76f08fe..1de81cd9f5 100644 --- a/tajo-common/src/main/java/org/apache/tajo/datum/Inet4Datum.java +++ b/tajo-common/src/main/java/org/apache/tajo/datum/Inet4Datum.java @@ -29,6 +29,11 @@ public class Inet4Datum extends Datum { private static final int size = 4; @Expose private final int address; + Inet4Datum(int encoded) { + super(Type.INET4); + this.address = encoded; + } + public Inet4Datum(String addr) { super(Type.INET4); String [] elems = addr.split("\\."); diff --git a/tajo-common/src/main/java/org/apache/tajo/datum/NullDatum.java b/tajo-common/src/main/java/org/apache/tajo/datum/NullDatum.java index 532e7cd71b..0007b52ea2 100644 --- a/tajo-common/src/main/java/org/apache/tajo/datum/NullDatum.java +++ b/tajo-common/src/main/java/org/apache/tajo/datum/NullDatum.java @@ -51,6 +51,11 @@ public boolean isNull() { return true; } + @Override + public boolean isNotNull() { + return false; + } + @Override public boolean asBool() { throw new InvalidCastException(Type.NULL_TYPE, Type.BOOLEAN); diff --git a/tajo-common/src/main/java/org/apache/tajo/util/CommonTestingUtil.java b/tajo-common/src/main/java/org/apache/tajo/util/CommonTestingUtil.java index 8dd8bfec99..e62cb12161 100644 --- a/tajo-common/src/main/java/org/apache/tajo/util/CommonTestingUtil.java +++ b/tajo-common/src/main/java/org/apache/tajo/util/CommonTestingUtil.java @@ -21,12 +21,34 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.tajo.ConfigKey; +import org.apache.tajo.OverridableConf; +import org.apache.tajo.SessionVars; +import org.apache.tajo.conf.TajoConf; import java.io.IOException; import java.util.UUID; public class CommonTestingUtil { - public static final String TAJO_TEST = "tajo.test"; + public static final String TAJO_TEST_KEY = "tajo.test.enabled"; + public static final String TAJO_TEST_TRUE = "true"; + private static OverridableConf userSessionVars; + + static { + System.setProperty(CommonTestingUtil.TAJO_TEST_KEY, CommonTestingUtil.TAJO_TEST_TRUE); + + userSessionVars = new OverridableConf(new TajoConf(), ConfigKey.ConfigType.SESSION); + for (SessionVars var : SessionVars.values()) { + String value = System.getProperty(var.keyname()); + if (value != null) { + userSessionVars.put(var, value); + } + } + } + + public static OverridableConf getSessionVarsForTest() { + return userSessionVars; + } /** * diff --git a/tajo-common/src/main/java/org/apache/tajo/util/Pair.java b/tajo-common/src/main/java/org/apache/tajo/util/Pair.java index 7b5f8adceb..e8852df438 100644 --- a/tajo-common/src/main/java/org/apache/tajo/util/Pair.java +++ b/tajo-common/src/main/java/org/apache/tajo/util/Pair.java @@ -18,6 +18,8 @@ package org.apache.tajo.util; +import com.google.common.base.Objects; + public class Pair { private T1 first; private T2 second; @@ -48,8 +50,18 @@ public String toString() { return first + "," + second; } + @Override + public boolean equals(Object obj) { + if (obj instanceof Pair) { + Pair other = (Pair) obj; + return first.equals(other.first) && second.equals(other.second); + } else { + return false; + } + } + @Override public int hashCode() { - return toString().hashCode(); + return Objects.hashCode(first, second); } } diff --git a/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java b/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java index 327b4237e2..9198f5bf43 100644 --- a/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java +++ b/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java @@ -663,6 +663,41 @@ public static long toJulianTimestamp(String str) { return toJulianTimestamp(tm); } + + /** + * Parse datetime string to julian time. + * The result is the local time basis. + * @param timestampStr + * @return + */ + public static long toJulianTimestampWithTZ(String timestampStr) { + long timestamp = DateTimeUtil.toJulianTimestamp(timestampStr); + TimeMeta tm = new TimeMeta(); + DateTimeUtil.toJulianTimeMeta(timestamp, tm); + DateTimeUtil.toUTCTimezone(tm); + return DateTimeUtil.toJulianTimestamp(tm); + } + + /** + * Parse datetime string to julian date. + * @param dateStr + * @return + */ + public static int toJulianDate(String dateStr) { + TimeMeta tm = DateTimeUtil.decodeDateTime(dateStr); + return DateTimeUtil.date2j(tm.years, tm.monthOfYear, tm.dayOfMonth); + } + + /** + * Parse datetime string to julian time. + * @param timeStr + * @return + */ + public static long toJulianTime(String timeStr) { + TimeMeta tm = DateTimeUtil.decodeDateTime(timeStr); + return DateTimeUtil.toTime(tm); + } + public static TimeMeta decodeDateTime(String str) { return decodeDateTime(str, MAXDATEFIELDS); } diff --git a/tajo-core/pom.xml b/tajo-core/pom.xml index 364c428aa5..d33b5983d6 100644 --- a/tajo-core/pom.xml +++ b/tajo-core/pom.xml @@ -59,10 +59,33 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + derby.log + benchmark/** + src/test/tpch/** + src/test/resources/dataset/** + src/test/resources/queries/** + src/test/resources/results/** + src/test/resources/results/** + src/main/resources/webapps/static/js/* + + + org.apache.maven.plugins maven-surefire-plugin - 2.12.4 TRUE @@ -192,11 +215,6 @@ - - org.apache.maven.plugins - maven-pmd-plugin - 2.7.1 - @@ -242,6 +260,10 @@ org.apache.tajo tajo-rpc + + org.apache.tajo + tajo-thirdparty-asm + org.apache.hadoop diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenEmitter.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenEmitter.java new file mode 100644 index 0000000000..16bd39688c --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenEmitter.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import com.google.common.base.Preconditions; +import org.apache.tajo.engine.eval.BinaryEval; +import org.apache.tajo.engine.eval.CaseWhenEval; +import org.apache.tajo.engine.eval.EvalNode; +import org.apache.tajo.engine.eval.EvalType; +import org.apache.tajo.org.objectweb.asm.Label; +import org.apache.tajo.org.objectweb.asm.Opcodes; + +import java.util.List; +import java.util.Stack; + +class CaseWhenEmitter implements EvalCodeEmitter { + public static final CaseWhenEmitter instance; + + static { + instance = new CaseWhenEmitter(); + } + + public static CaseWhenEmitter getInstance() { + return instance; + } + + @Override + public void emit(EvalCodeGenerator codeGen, EvalCodeGenContext context, CaseWhenEval caseWhen, + Stack stack) { + // TYPE 1: CASE WHEN x THEN c1 WHEN y THEN c2 WHEN c3 ELSE ... END; + EvalNode commonTerm = extractCommonTerm(caseWhen.getIfThenEvals()); + + if (commonTerm != null) { + int casesNum = caseWhen.getIfThenEvals().size(); + List ifThenList = caseWhen.getIfThenEvals(); + TajoGeneratorAdapter.SwitchCase[] cases = new TajoGeneratorAdapter.SwitchCase[casesNum]; + + for (int i = 0; i < caseWhen.getIfThenEvals().size(); i++) { + int key = getSwitchIndex(ifThenList.get(i).getCondition()); + EvalNode result = ifThenList.get(i).getResult(); + cases[i] = new TajoGeneratorAdapter.SwitchCase(key, result); + } + CaseWhenSwitchGenerator gen = new CaseWhenSwitchGenerator(codeGen, context, stack, cases, caseWhen.getElse()); + + stack.push(caseWhen); + + codeGen.visit(context, commonTerm, stack); + + Label ifNull = context.newLabel(); + Label endIf = context.newLabel(); + + context.emitNullityCheck(ifNull); + context.generatorAdapter.tableSwitch(gen.keys(), gen); + context.gotoLabel(endIf); + + codeGen.emitLabel(context, ifNull); + context.pop(commonTerm.getValueType()); + codeGen.visit(context, caseWhen.getElse(), stack); + + codeGen.emitLabel(context, endIf); + + stack.pop(); + } else { // TYPE 2: CASE WHEN x = c1 THEN r1 WHEN y = c2 THEN r2 ELSE ... END + int casesNum = caseWhen.getIfThenEvals().size(); + Label [] labels = new Label[casesNum - 1]; + + for (int i = 0; i < labels.length; i++) { + labels[i] = context.newLabel(); + } + + Label defaultLabel = context.newLabel(); + Label ifNull = context.newLabel(); + Label afterAll = context.newLabel(); + + // The following will generate code as follows. defaultLabel points to the last else clause. + // + // if (...) { + // ..... + // } else if (...) { + // .... + // } else if (...) { + // } ..... { + // } else { <- default label + // ... + // } + + stack.push(caseWhen); + for (int i = 0; i < casesNum; i++) { + CaseWhenEval.IfThenEval ifThenEval = caseWhen.getIfThenEvals().get(i); + + stack.push(caseWhen); + codeGen.visit(context, ifThenEval.getCondition(), stack); + int NULL_FLAG = context.istore(); + int CHILD = context.store(ifThenEval.getCondition().getValueType()); + + context.iload(NULL_FLAG); + context.emitNullityCheck(ifNull); + + context.pushBooleanOfThreeValuedLogic(true); + context.load(ifThenEval.getCondition().getValueType(), CHILD); + context.methodvisitor.visitJumpInsn(Opcodes.IF_ICMPNE, (casesNum - 1) < i ? labels[i] : defaultLabel); // false + + codeGen.visit(context, ifThenEval.getResult(), stack); + context.gotoLabel(afterAll); + stack.pop(); + + if (i < casesNum - 1) { + codeGen.emitLabel(context, labels[i]); // else if + } + } + stack.pop(); + + codeGen.emitLabel(context, defaultLabel); + if (caseWhen.hasElse()) { + stack.push(caseWhen); + codeGen.visit(context, caseWhen.getElse(), stack); + stack.pop(); + context.gotoLabel(afterAll); + } else { + context.gotoLabel(ifNull); + } + + codeGen.emitLabel(context, ifNull); + context.pushDummyValue(caseWhen.getValueType()); + context.pushNullFlag(false); + + codeGen.emitLabel(context, afterAll); + } + } + + private EvalNode extractCommonTerm(List ifThenEvals) { + EvalNode commonTerm = null; + + for (int i = 0; i < ifThenEvals.size(); i++) { + EvalNode predicate = ifThenEvals.get(i).getCondition(); + if (!checkIfSimplePredicate(predicate)) { + return null; + } + + BinaryEval bin = (BinaryEval) predicate; + + EvalNode baseTerm; + if (bin.getLeftExpr().getType() == EvalType.CONST) { + baseTerm = bin.getRightExpr(); + } else { + baseTerm = bin.getLeftExpr(); + } + + if (commonTerm == null) { + commonTerm = baseTerm; + } else { + if (!baseTerm.equals(commonTerm)) { + return null; + } + } + } + + return commonTerm; + } + + /** + * Simple predicate means one term is constant and comparator is equal. + * + * @param predicate Predicate to be checked + * @return True if the predicate is a simple form. + */ + private boolean checkIfSimplePredicate(EvalNode predicate) { + if (predicate.getType() == EvalType.EQUAL) { + BinaryEval binaryEval = (BinaryEval) predicate; + EvalNode lhs = binaryEval.getLeftExpr(); + EvalNode rhs = binaryEval.getRightExpr(); + + boolean simple = false; + simple |= rhs.getType() == EvalType.CONST && TajoGeneratorAdapter.isJVMInternalInt(rhs.getValueType()); + simple |= lhs.getType() == EvalType.CONST && TajoGeneratorAdapter.isJVMInternalInt(lhs.getValueType()); + return simple; + } else { + return false; + } + } + + private int getSwitchIndex(EvalNode predicate) { + Preconditions.checkArgument(checkIfSimplePredicate(predicate), + "This expression cannot be used for switch table: " + predicate); + + BinaryEval bin = (BinaryEval) predicate; + + if (bin.getLeftExpr().getType() == EvalType.CONST) { + return bin.getLeftExpr().eval(null, null).asInt4(); + } else { + return bin.getRightExpr().eval(null, null).asInt4(); + } + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenSwitchGenerator.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenSwitchGenerator.java new file mode 100644 index 0000000000..6f928ac168 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CaseWhenSwitchGenerator.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import com.google.common.collect.Maps; +import org.apache.tajo.engine.eval.EvalNode; +import org.apache.tajo.org.objectweb.asm.Label; + +import java.util.NavigableMap; +import java.util.Stack; + +class CaseWhenSwitchGenerator implements TajoGeneratorAdapter.SwitchCaseGenerator { + final private EvalCodeGenerator generator; + final private EvalCodeGenContext context; + final private Stack stack; + + final NavigableMap casesMap; + final EvalNode defaultEval; + + public CaseWhenSwitchGenerator(EvalCodeGenerator generator, EvalCodeGenContext context, Stack stack, + TajoGeneratorAdapter.SwitchCase[] cases, EvalNode defaultEval) { + this.generator = generator; + this.context = context; + this.stack = stack; + this.casesMap = Maps.newTreeMap(); + for (TajoGeneratorAdapter.SwitchCase switchCase : cases) { + this.casesMap.put(switchCase.key(), switchCase); + } + this.defaultEval = defaultEval; + } + + @Override + public int size() { + return casesMap.size(); + } + + @Override + public int min() { + return casesMap.firstEntry().getKey(); + } + + @Override + public int max() { + return casesMap.lastEntry().getKey(); + } + + @Override + public int key(int index) { + return casesMap.get(index).key(); + } + + @Override + public void generateCase(int key, Label end) { + generator.visit(context, casesMap.get(key).result(), stack); + context.gotoLabel(end); + } + + public int [] keys() { + int [] keys = new int[casesMap.size()]; + + int idx = 0; + for (int key : casesMap.keySet()) { + keys[idx++] = key; + } + return keys; + } + + public void generateDefault() { + if (defaultEval != null) { + generator.visit(context, defaultEval, stack); + } else { + context.pushNullOfThreeValuedLogic(); + context.pushNullFlag(false); + } + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CodeGenUtils.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CodeGenUtils.java new file mode 100644 index 0000000000..84a93fcfec --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CodeGenUtils.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import org.apache.tajo.org.objectweb.asm.ClassReader; +import org.apache.tajo.org.objectweb.asm.util.ASMifier; +import org.apache.tajo.org.objectweb.asm.util.TraceClassVisitor; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class CodeGenUtils { + private static final int FLAGS = ClassReader.SKIP_DEBUG; + + public static String disassemble(byte [] bytesCode) { + StringWriter strWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(strWriter); + + ClassReader cr; + cr = new ClassReader(bytesCode); + cr.accept(new TraceClassVisitor(null, new ASMifier(), writer), FLAGS); + + return strWriter.toString(); + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CompilationError.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CompilationError.java new file mode 100644 index 0000000000..27054deb1a --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/CompilationError.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import org.apache.tajo.engine.eval.EvalNode; + +public class CompilationError extends RuntimeException { + public CompilationError(String message) { + super(message); + } + + public CompilationError(EvalNode evalNode, Throwable t, byte [] clazz) { + super("Compilation Error: " + evalNode.toString() + "\n\nBYTES CODE DUMP:\n" + CodeGenUtils.disassemble(clazz), t); + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeEmitter.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeEmitter.java new file mode 100644 index 0000000000..d94c9071ba --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeEmitter.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import org.apache.tajo.engine.eval.EvalNode; + +import java.util.Stack; + +public interface EvalCodeEmitter { + void emit(EvalCodeGenerator gen, EvalCodeGenContext context, T caseWhen, Stack stack); +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenContext.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenContext.java new file mode 100644 index 0000000000..03c04023d8 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenContext.java @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import com.google.common.collect.Maps; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.common.TajoDataTypes; +import org.apache.tajo.datum.Datum; +import org.apache.tajo.datum.IntervalDatum; +import org.apache.tajo.engine.eval.*; +import org.apache.tajo.org.objectweb.asm.ClassWriter; +import org.apache.tajo.org.objectweb.asm.MethodVisitor; +import org.apache.tajo.org.objectweb.asm.Opcodes; +import org.apache.tajo.org.objectweb.asm.commons.GeneratorAdapter; +import org.apache.tajo.storage.Tuple; + +import java.util.Map; +import java.util.Stack; + +public class EvalCodeGenContext extends TajoGeneratorAdapter { + final String owner; + final Schema schema; + final ClassWriter classWriter; + final EvalNode evalNode; + final Map symbols; + int seqId = 0; + + public EvalCodeGenContext(String className, Schema schema, ClassWriter classWriter, EvalNode evalNode) { + this.owner = className; + this.classWriter = classWriter; + this.schema = schema; + this.evalNode = evalNode; + this.symbols = Maps.newHashMap(); + + emitClassDefinition(); + emitMemberFields(); + classWriter.visitEnd(); + emitConstructor(); + + String methodName = "eval"; + String methodDesc = TajoGeneratorAdapter.getMethodDescription(Datum.class, new Class[]{Schema.class, Tuple.class}); + MethodVisitor evalMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDesc, null, null); + evalMethod.visitCode(); + this.methodvisitor = evalMethod; + generatorAdapter = new GeneratorAdapter(this.methodvisitor, access, methodDesc, methodDesc); + } + + public void emitClassDefinition() { + classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, this.owner, null, + TajoGeneratorAdapter.getInternalName(EvalNode.class), null); + } + + public void emitMemberFields() { + classWriter.visitField(Opcodes.ACC_PRIVATE, "schema", + "L" + TajoGeneratorAdapter.getInternalName(Schema.class) + ";", null, null); + + VariablesPreBuilder builder = new VariablesPreBuilder(); + builder.visit(this, evalNode, new Stack()); + } + + public static void emitCreateSchema(TajoGeneratorAdapter adapter, MethodVisitor mv, Schema schema) { + mv.visitLdcInsn(schema.toJson()); + adapter.invokeStatic(EvalCodeGenerator.class, "createSchema", Schema.class, new Class[] {String.class}); + } + + public static void emitCreateEval(TajoGeneratorAdapter adapter, MethodVisitor mv, EvalNode evalNode) { + mv.visitLdcInsn(evalNode.toJson()); + adapter.invokeStatic(EvalCodeGenerator.class, "createEval", EvalNode.class, new Class[] {String.class}); + } + + public static void emitConstEval(TajoGeneratorAdapter adapter, MethodVisitor mv, ConstEval evalNode) { + mv.visitLdcInsn(evalNode.toJson()); + adapter.invokeStatic(EvalCodeGenerator.class, "createConstEval", ConstEval.class, new Class[] {String.class}); + } + + public static void emitRowConstantEval(TajoGeneratorAdapter adapter, MethodVisitor mv, RowConstantEval evalNode) { + mv.visitLdcInsn(evalNode.toJson()); + adapter.invokeStatic(EvalCodeGenerator.class, "createRowConstantEval", RowConstantEval.class, + new Class[] {String.class}); + } + + public void emitConstructor() { + // constructor method + MethodVisitor initMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + initMethod.visitCode(); + initMethod.visitVarInsn(Opcodes.ALOAD, 0); + initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, TajoGeneratorAdapter.getInternalName(EvalNode.class), "", + "()V"); + + TajoGeneratorAdapter consAdapter = new TajoGeneratorAdapter(Opcodes.ACC_PUBLIC, initMethod, "", "()V"); + + // == this.schema = schema; + if (schema != null) { + consAdapter.aload(0); + emitCreateSchema(consAdapter, initMethod, schema); + initMethod.visitFieldInsn(Opcodes.PUTFIELD, this.owner, "schema", getDescription(Schema.class)); + } + + for (Map.Entry entry : symbols.entrySet()) { + if (entry.getKey().getType() == EvalType.CONST) { + ConstEval constEval = (ConstEval) entry.getKey(); + + if (constEval.getValueType().getType() == TajoDataTypes.Type.INTERVAL) { + IntervalDatum datum = (IntervalDatum) constEval.getValue(); + + final String internalName = TajoGeneratorAdapter.getInternalName(IntervalDatum.class); + + initMethod.visitTypeInsn(Opcodes.NEW, internalName); + consAdapter.dup(); + initMethod.visitLdcInsn(datum.getMonths()); + initMethod.visitLdcInsn(datum.getMilliSeconds()); + initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, internalName, "", "(IJ)V"); + int INTERVAL_DATUM = consAdapter.astore(); + + consAdapter.aload(0); + consAdapter.aload(INTERVAL_DATUM); + initMethod.visitFieldInsn(Opcodes.PUTFIELD, this.owner, entry.getValue(), + "L" + TajoGeneratorAdapter.getInternalName(IntervalDatum.class) + ";"); + } + + } else if (entry.getKey().getType() == EvalType.IN) { + InEval inEval = (InEval) entry.getKey(); + + final String internalName = getInternalName(InEval.class); + initMethod.visitTypeInsn(Opcodes.NEW, internalName); + consAdapter.dup(); + emitCreateEval(consAdapter, initMethod, inEval.getLeftExpr()); + emitRowConstantEval(consAdapter, initMethod, (RowConstantEval) inEval.getRightExpr()); + consAdapter.push(inEval.isNot()); + consAdapter.invokeSpecial(InEval.class, "", void.class, + new Class [] {EvalNode.class, RowConstantEval.class, boolean.class}); + int IN_PREDICATE_EVAL = consAdapter.astore(); + + consAdapter.aload(0); + consAdapter.aload(IN_PREDICATE_EVAL); + initMethod.visitFieldInsn(Opcodes.PUTFIELD, this.owner, entry.getValue(), getDescription(InEval.class)); + + } else if (EvalType.isStringPatternMatchOperator(entry.getKey().getType())) { + PatternMatchPredicateEval patternPredicate = (PatternMatchPredicateEval) entry.getKey(); + + Class clazz = EvalCodeGenerator.getStringPatternEvalClass(entry.getKey().getType()); + final String internalName = TajoGeneratorAdapter.getInternalName(clazz); + + initMethod.visitTypeInsn(Opcodes.NEW, internalName); + consAdapter.dup(); + consAdapter.push(patternPredicate.isNot()); + emitCreateEval(consAdapter, initMethod, patternPredicate.getLeftExpr()); + emitConstEval(consAdapter, initMethod, (ConstEval) patternPredicate.getRightExpr()); + consAdapter.push(patternPredicate.isCaseInsensitive()); + consAdapter.invokeSpecial(clazz, "", void.class, + new Class [] {boolean.class, EvalNode.class, ConstEval.class, boolean.class}); + + int PatternEval = consAdapter.astore(); + + consAdapter.aload(0); + consAdapter.aload(PatternEval); + initMethod.visitFieldInsn(Opcodes.PUTFIELD, this.owner, entry.getValue(), getDescription(clazz)); + + } else if (entry.getKey().getType() == EvalType.FUNCTION) { + GeneralFunctionEval function = (GeneralFunctionEval) entry.getKey(); + final String internalName = TajoGeneratorAdapter.getInternalName(function.getFuncDesc().getFuncClass()); + + // new and initialization of function + initMethod.visitTypeInsn(Opcodes.NEW, internalName); + consAdapter.dup(); + initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, internalName, "", "()V"); + int FUNCTION = consAdapter.astore(); + + // commParam + int paramNum = function.getArgs().length; + initMethod.visitLdcInsn(paramNum); + consAdapter.newArray(FunctionEval.ParamType.class); + final int PARAM_TYPE_ARRAY = consAdapter.astore(); + FunctionEval.ParamType[] paramTypes = EvalCodeGenerator.getParamTypes(function.getArgs()); + for (int paramIdx = 0; paramIdx < paramTypes.length; paramIdx++) { + consAdapter.aload(PARAM_TYPE_ARRAY); + consAdapter.methodvisitor.visitLdcInsn(paramIdx); + consAdapter.methodvisitor.visitFieldInsn(Opcodes.GETSTATIC, TajoGeneratorAdapter.getInternalName(FunctionEval.ParamType.class), + paramTypes[paramIdx].name(), TajoGeneratorAdapter.getDescription(FunctionEval.ParamType.class)); + consAdapter.methodvisitor.visitInsn(Opcodes.AASTORE); + } + + initMethod.visitVarInsn(Opcodes.ALOAD, FUNCTION); + consAdapter.aload(PARAM_TYPE_ARRAY); + consAdapter.invokeVirtual(function.getFuncDesc().getFuncClass(), "init", void.class, new Class[] {FunctionEval.ParamType[].class}); + + initMethod.visitVarInsn(Opcodes.ALOAD, 0); + initMethod.visitVarInsn(Opcodes.ALOAD, FUNCTION); + initMethod.visitFieldInsn(Opcodes.PUTFIELD, this.owner, entry.getValue(), + "L" + TajoGeneratorAdapter.getInternalName(function.getFuncDesc().getFuncClass()) + ";"); + + } + } + + initMethod.visitInsn(Opcodes.RETURN); + initMethod.visitMaxs(1, 1); + initMethod.visitEnd(); + } + + public void emitReturn() { + convertToDatum(evalNode.getValueType(), true); + methodvisitor.visitInsn(Opcodes.ARETURN); + methodvisitor.visitMaxs(0, 0); + methodvisitor.visitEnd(); + classWriter.visitEnd(); + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenerator.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenerator.java new file mode 100644 index 0000000000..c57f923cb8 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/EvalCodeGenerator.java @@ -0,0 +1,840 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import org.apache.tajo.catalog.Column; +import org.apache.tajo.catalog.FunctionDesc; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.common.TajoDataTypes; +import org.apache.tajo.datum.Datum; +import org.apache.tajo.datum.IntervalDatum; +import org.apache.tajo.datum.ProtobufDatum; +import org.apache.tajo.engine.eval.*; +import org.apache.tajo.engine.json.CoreGsonHelper; +import org.apache.tajo.org.objectweb.asm.*; +import org.apache.tajo.storage.Tuple; +import org.apache.tajo.storage.VTuple; + +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.util.Stack; + +import static org.apache.tajo.common.TajoDataTypes.DataType; +import static org.apache.tajo.engine.codegen.TajoGeneratorAdapter.*; +import static org.apache.tajo.engine.eval.FunctionEval.ParamType; + +public class EvalCodeGenerator extends SimpleEvalNodeVisitor { + + public static final byte UNKNOWN = 0; + public static final byte TRUE = 1; + public static final byte FALSE = 2; + + /** 0 - UNKNOWN, 1 - TRUE, 2 - FALSE */ + @SuppressWarnings("unused") + public static final byte [] THREE_VALUES = new byte[] {UNKNOWN, TRUE, FALSE}; + @SuppressWarnings("unused") + public static final byte [] NOT_LOGIC = new byte[] {UNKNOWN, FALSE, TRUE}; + @SuppressWarnings("unused") + public static final byte [][] AND_LOGIC = new byte [][] { + // unknown true false + new byte [] {UNKNOWN, UNKNOWN, FALSE}, // unknown + new byte [] {UNKNOWN, TRUE, FALSE}, // true + new byte [] {FALSE, FALSE, FALSE} // false + }; + @SuppressWarnings("unused") + public static final byte [][] OR_LOGIC = new byte [][] { + // unknown true false + new byte [] {UNKNOWN, TRUE, UNKNOWN}, // unknown + new byte [] {TRUE, TRUE, TRUE}, // true + new byte [] {UNKNOWN, TRUE, FALSE} // false + }; + + private final TajoClassLoader classLoader; + static int classSeq = 1; + + public EvalCodeGenerator(TajoClassLoader classLoader) { + this.classLoader = classLoader; + } + + public EvalNode compile(Schema schema, EvalNode expr) throws CompilationError { + + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + String className = EvalCodeGenerator.class.getPackage().getName() + ".CompiledEval" + classSeq++; + EvalCodeGenContext context = new EvalCodeGenContext(TajoGeneratorAdapter.getInternalName(className), + schema, classWriter, expr); + visit(context, expr, new Stack()); + context.emitReturn(); + + Class aClass = classLoader.defineClass(className, classWriter.toByteArray()); + + Constructor constructor; + EvalNode compiledEval; + + try { + constructor = aClass.getConstructor(); + compiledEval = (EvalNode) constructor.newInstance(); + } catch (Throwable t) { + throw new CompilationError(expr, t, classWriter.toByteArray()); + } + return compiledEval; + } + + private void printOut(EvalCodeGenContext context, String message) { + context.methodvisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + context.push(message); + context.invokeVirtual(PrintStream.class, "println", void.class, new Class[]{String.class}); + } + + public EvalNode visitBinaryEval(EvalCodeGenContext context, Stack stack, BinaryEval binaryEval) { + if (EvalType.isLogicalOperator(binaryEval.getType())) { + return visitAndOrEval(context, binaryEval, stack); + } else if (EvalType.isArithmeticOperator(binaryEval.getType())) { + return visitArithmeticEval(context, binaryEval, stack); + } else if (EvalType.isComparisonOperator(binaryEval.getType())) { + return visitComparisonEval(context, binaryEval, stack); + } else if (binaryEval.getType() == EvalType.CONCATENATE) { + return visitStringConcat(context, binaryEval, stack); + } else if (binaryEval.getType() == EvalType.LIKE || binaryEval.getType() == EvalType.SIMILAR_TO + || binaryEval.getType() == EvalType.REGEX) { + return visitStringPatternMatch(context, binaryEval, stack); + } else if (binaryEval.getType() == EvalType.IN) { + return visitInPredicate(context, binaryEval, stack); + } else { + stack.push(binaryEval); + visit(context, binaryEval.getLeftExpr(), stack); + visit(context, binaryEval.getRightExpr(), stack); + stack.pop(); + return binaryEval; + } + } + + public EvalNode visitUnaryEval(EvalCodeGenContext context, Stack stack, UnaryEval unary) { + stack.push(unary); + if (unary.getType() == EvalType.CAST) { + visitCast(context, stack, (CastEval) unary); + + } else if (unary.getType() == EvalType.NOT) { + + visit(context, unary.getChild(), stack); + context.methodvisitor.visitVarInsn(Opcodes.ISTORE, 9); + context.methodvisitor.visitVarInsn(Opcodes.ISTORE, 10); + + Label ifNull = new Label(); + Label endIf = new Label(); + + context.emitNullityCheck(ifNull, 9); + + context.methodvisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(EvalCodeGenerator.class), + "NOT_LOGIC", "[B"); + context.methodvisitor.visitVarInsn(Opcodes.ILOAD, 10); + context.methodvisitor.visitInsn(Opcodes.BALOAD); + context.pushNullFlag(true); + emitGotoLabel(context, endIf); + + emitLabel(context, ifNull); + context.pushDummyValue(unary.getValueType()); + context.pushNullFlag(false); + + emitLabel(context, endIf); + + } else if (unary.getType() == EvalType.IS_NULL) { + return visitIsNull(context, (IsNullEval) unary, stack); + + + } else if (unary.getType() == EvalType.SIGNED) { + visit(context, unary.getChild(), stack); + + Label ifNull = new Label(); + Label endIf = new Label(); + + context.emitNullityCheck(ifNull); + + SignedEval signed = (SignedEval) unary; + switch (signed.getValueType().getType()) { + case BOOLEAN: + case CHAR: + case INT1: + case INT2: + case INT4: context.methodvisitor.visitInsn(Opcodes.INEG); break; + case INT8: context.methodvisitor.visitInsn(Opcodes.LNEG); break; + case FLOAT4: context.methodvisitor.visitInsn(Opcodes.FNEG); break; + case FLOAT8: context.methodvisitor.visitInsn(Opcodes.DNEG); break; + default: throw new InvalidEvalException(unary.getType() + " operation to " + signed.getChild() + " is invalid."); + } + + context.pushNullFlag(true); + emitGotoLabel(context, endIf); + + emitLabel(context, ifNull); + context.pushNullFlag(false); + + emitLabel(context, endIf); + + } else { + super.visit(context, unary, stack); + } + stack.pop(); + return unary; + } + + public EvalNode visitBetween(EvalCodeGenContext context, BetweenPredicateEval between, Stack stack) { + EvalNode predicand = between.getPredicand(); + EvalNode begin = between.getBegin(); + EvalNode end = between.getEnd(); + + stack.push(between); + + visit(context, predicand, stack); + final int PREDICAND_NULLFLAG = context.istore(); + final int PREDICAND = context.store(predicand.getValueType()); + + visit(context, begin, stack); + final int BEGIN_NULLFLAG = context.istore(); + final int BEGIN = context.store(begin.getValueType()); + + visit(context, end, stack); // < end, right_nullflag + final int END_NULLFLAG = context.istore(); + final int END = context.store(end.getValueType()); // < + + stack.pop(); + + Label ifNullCommon = new Label(); + Label ifNotMatched = new Label(); + + Label afterEnd = new Label(); + + + context.emitNullityCheck(ifNullCommon, PREDICAND_NULLFLAG, BEGIN_NULLFLAG, END_NULLFLAG); + + if (between.isSymmetric()) { + Label ifFirstMatchFailed = new Label(); + Label ifSecondMatchFailed = new Label(); + Label secondCheck = new Label(); + Label finalDisjunctive = new Label(); + + ////////////////////////////////////////////////////////////////////////////////////////// + // second check + ////////////////////////////////////////////////////////////////////////////////////////// + + // predicand <= begin + context.load(begin.getValueType(), BEGIN); + context.load(predicand.getValueType(), PREDICAND); + context.ifCmp(predicand.getValueType(), EvalType.LEQ, ifFirstMatchFailed); + + // end <= predicand + context.load(end.getValueType(), END); + context.load(predicand.getValueType(), PREDICAND); + // inverse the operator GEQ -> LTH + context.ifCmp(predicand.getValueType(), EvalType.GEQ, ifFirstMatchFailed); + + context.push(true); + emitGotoLabel(context, secondCheck); + + emitLabel(context, ifFirstMatchFailed); + context.push(false); + + ////////////////////////////////////////////////////////////////////////////////////////// + // second check + ////////////////////////////////////////////////////////////////////////////////////////// + emitLabel(context, secondCheck); + + // predicand <= end + context.load(end.getValueType(), END); + context.load(predicand.getValueType(), PREDICAND); + + // inverse the operator LEQ -> GTH + context.ifCmp(predicand.getValueType(), EvalType.LEQ, ifSecondMatchFailed); + + // end <= predicand + context.load(begin.getValueType(), BEGIN); + context.load(predicand.getValueType(), PREDICAND); + // inverse the operator GEQ -> LTH + context.ifCmp(predicand.getValueType(), EvalType.GEQ, ifSecondMatchFailed); + + context.push(true); + emitGotoLabel(context, finalDisjunctive); + + emitLabel(context, ifSecondMatchFailed); + context.push(false); + + emitLabel(context, finalDisjunctive); + context.methodvisitor.visitInsn(Opcodes.IOR); + context.methodvisitor.visitJumpInsn(Opcodes.IFEQ, ifNotMatched); + } else { + // predicand <= begin + context.load(begin.getValueType(), BEGIN); + context.load(predicand.getValueType(), PREDICAND); + context.ifCmp(predicand.getValueType(), EvalType.LEQ, ifNotMatched); + + // end <= predicand + context.load(end.getValueType(), END); + context.load(predicand.getValueType(), PREDICAND); + context.ifCmp(predicand.getValueType(), EvalType.GEQ, ifNotMatched); + } + + // IF MATCHED + context.pushBooleanOfThreeValuedLogic(between.isNot() ? false : true); + context.pushNullFlag(true); + emitGotoLabel(context, afterEnd); + + emitLabel(context, ifNotMatched); // IF NOT MATCHED + context.pushBooleanOfThreeValuedLogic(between.isNot() ? true : false); + context.pushNullFlag(true); + emitGotoLabel(context, afterEnd); + + emitLabel(context, ifNullCommon); // IF NULL + context.pushNullOfThreeValuedLogic(); + context.pushNullFlag(false); + + emitLabel(context, afterEnd); + + return between; + } + + private void emitGotoLabel(EvalCodeGenContext context, Label label) { + context.methodvisitor.visitJumpInsn(Opcodes.GOTO, label); + } + + void emitLabel(EvalCodeGenContext context, Label label) { + context.methodvisitor.visitLabel(label); + } + + public EvalNode visitCast(EvalCodeGenContext context, Stack stack, CastEval cast) { + DataType srcType = cast.getOperand().getValueType(); + DataType targetType = cast.getValueType(); + + if (srcType.equals(targetType)) { + visit(context, cast.getChild(), stack); + return cast; + } + + visit(context, cast.getChild(), stack); + + Label ifNull = new Label(); + Label afterEnd = new Label(); + context.emitNullityCheck(ifNull); + + context.castInsn(srcType, targetType); + context.pushNullFlag(true); + emitGotoLabel(context, afterEnd); + + emitLabel(context, ifNull); + context.pop(srcType); + context.pushDummyValue(targetType); + context.pushNullFlag(false); + printOut(context, "endIfNull"); + + emitLabel(context, afterEnd); + return cast; + } + + public EvalNode visitField(EvalCodeGenContext context, Stack stack, FieldEval field) { + + if (field.getValueType().getType() == TajoDataTypes.Type.NULL_TYPE) { + context.pushNullOfThreeValuedLogic(); + context.pushNullFlag(false); + } else { + + Column columnRef = field.getColumnRef(); + int fieldIdx; + if (columnRef.hasQualifier()) { + fieldIdx = context.schema.getColumnId(columnRef.getQualifiedName()); + } else { + fieldIdx = context.schema.getColumnIdByName(columnRef.getSimpleName()); + } + + context.methodvisitor.visitVarInsn(Opcodes.ALOAD, 2); + context.push(fieldIdx); + context.invokeInterface(Tuple.class, "isNull", boolean.class, new Class [] {int.class}); + + context.push(true); + + Label ifNull = new Label(); + Label afterAll = new Label(); + context.methodvisitor.visitJumpInsn(Opcodes.IF_ICMPEQ, ifNull); + + String methodName = null; + Class returnType = null; + Class [] paramTypes = null; + switch (field.getValueType().getType()) { + case BOOLEAN: + methodName = "getByte"; + returnType = byte.class; + paramTypes = new Class[] {int.class}; + break; + case CHAR: { + methodName = "getText"; + returnType = String.class; + paramTypes = new Class[] {int.class}; + break; + } + case INT1: + case INT2: + case INT4: + case DATE: + case INET4: + methodName = "getInt4"; + returnType = int.class; + paramTypes = new Class [] {int.class}; + break; + case INT8: + case TIMESTAMP: + case TIME: + methodName = "getInt8"; + returnType = long.class; + paramTypes = new Class [] {int.class}; + break; + case FLOAT4: + methodName = "getFloat4"; + returnType = float.class; + paramTypes = new Class [] {int.class}; + break; + case FLOAT8: + methodName = "getFloat8"; + returnType = double.class; + paramTypes = new Class [] {int.class}; + break; + case TEXT: + methodName = "getText"; + returnType = String.class; + paramTypes = new Class [] {int.class}; + break; + case INTERVAL: + methodName = "getInterval"; + returnType = IntervalDatum.class; + paramTypes = new Class [] {int.class}; + break; + case PROTOBUF: + methodName = "getProtobufDatum"; + returnType = ProtobufDatum.class; + paramTypes = new Class [] {int.class}; + break; + default: + throw new InvalidEvalException(field.getValueType() + " is not supported yet"); + } + + context.methodvisitor.visitVarInsn(Opcodes.ALOAD, 2); + context.push(fieldIdx); + context.invokeInterface(Tuple.class, methodName, returnType, paramTypes); + + context.pushNullFlag(true); // not null + context.methodvisitor.visitJumpInsn(Opcodes.GOTO, afterAll); + + context.methodvisitor.visitLabel(ifNull); + context.pushDummyValue(field.getValueType()); + context.pushNullFlag(false); + + context.methodvisitor.visitLabel(afterAll); + } + return field; + } + + public EvalNode visitAndOrEval(EvalCodeGenContext context, BinaryEval evalNode, Stack stack) { + + stack.push(evalNode); + visit(context, evalNode.getLeftExpr(), stack); + context.pop(); + int LHS = context.istore(); + + visit(context, evalNode.getRightExpr(), stack); + context.pop(); + int RHS = context.istore(); + stack.pop(); + + if (evalNode.getType() == EvalType.AND) { + context.methodvisitor.visitFieldInsn(Opcodes.GETSTATIC, + org.apache.tajo.org.objectweb.asm.Type.getInternalName(EvalCodeGenerator.class), "AND_LOGIC", "[[B"); + } else if (evalNode.getType() == EvalType.OR) { + context.methodvisitor.visitFieldInsn(Opcodes.GETSTATIC, + org.apache.tajo.org.objectweb.asm.Type.getInternalName(EvalCodeGenerator.class), "OR_LOGIC", "[[B"); + } else { + throw new CompilationError("visitAndOrEval() cannot generate the code at " + evalNode); + } + context.load(evalNode.getLeftExpr().getValueType(), LHS); + context.methodvisitor.visitInsn(Opcodes.AALOAD); + context.load(evalNode.getRightExpr().getValueType(), RHS); + context.methodvisitor.visitInsn(Opcodes.BALOAD); // get three valued logic number from the AND/OR_LOGIC array + context.methodvisitor.visitInsn(Opcodes.DUP); // three valued logic number x 2, three valued logic number can be null flag. + + return evalNode; + } + + public static int store(EvalCodeGenContext context, DataType type, int idx) { + switch (type.getType()) { + case NULL_TYPE: + case BOOLEAN: + case CHAR: + case INT1: + case INT2: + case INT4: + context.methodvisitor.visitVarInsn(Opcodes.ISTORE, idx); + break; + case INT8: context.methodvisitor.visitVarInsn(Opcodes.LSTORE, idx); break; + case FLOAT4: context.methodvisitor.visitVarInsn(Opcodes.FSTORE, idx); break; + case FLOAT8: context.methodvisitor.visitVarInsn(Opcodes.DSTORE, idx); break; + default: context.methodvisitor.visitVarInsn(Opcodes.ASTORE, idx); break; + } + + return idx + TajoGeneratorAdapter.getWordSize(type); + } + + public EvalNode visitArithmeticEval(EvalCodeGenContext context, BinaryEval evalNode, Stack stack) { + stack.push(evalNode); + visit(context, evalNode.getLeftExpr(), stack); // < left_child, push nullflag + int LHS_NULLFLAG = context.istore(); + int LHS = context.store(evalNode.getLeftExpr().getValueType()); + + visit(context, evalNode.getRightExpr(), stack); // < left_child, right_child, nullflag + int RHS_NULLFLAG = context.istore(); + int RHS = context.store(evalNode.getRightExpr().getValueType()); + stack.pop(); + + Label ifNull = new Label(); + Label afterEnd = new Label(); + + context.emitNullityCheck(ifNull, LHS_NULLFLAG, RHS_NULLFLAG); + + context.load(evalNode.getLeftExpr().getValueType(), LHS); + context.load(evalNode.getRightExpr().getValueType(), RHS); + + int opCode = TajoGeneratorAdapter.getOpCode(evalNode.getType(), evalNode.getValueType()); + context.methodvisitor.visitInsn(opCode); + + context.pushNullFlag(true); + emitGotoLabel(context, afterEnd); + + emitLabel(context, ifNull); + context.pushDummyValue(evalNode.getValueType()); + context.pushNullFlag(false); + + emitLabel(context, afterEnd); + + return evalNode; + } + + public EvalNode visitComparisonEval(EvalCodeGenContext context, BinaryEval evalNode, Stack stack) + throws CompilationError { + + DataType lhsType = evalNode.getLeftExpr().getValueType(); + DataType rhsType = evalNode.getRightExpr().getValueType(); + + if (lhsType.getType() == TajoDataTypes.Type.NULL_TYPE || rhsType.getType() == TajoDataTypes.Type.NULL_TYPE) { + context.pushNullOfThreeValuedLogic(); + context.pushNullFlag(false); + } else { + stack.push(evalNode); + visit(context, evalNode.getLeftExpr(), stack); // < lhs, l_null + final int LHS_NULLFLAG = context.istore(); + int LHS = context.store(evalNode.getLeftExpr().getValueType()); // < + + visit(context, evalNode.getRightExpr(), stack); // < rhs, r_nullflag + final int RHS_NULLFLAG = context.istore(); + final int RHS = context.store(evalNode.getRightExpr().getValueType()); // < + stack.pop(); + + Label ifNull = new Label(); + Label ifNotMatched = new Label(); + Label afterEnd = new Label(); + + context.emitNullityCheck(ifNull, LHS_NULLFLAG, RHS_NULLFLAG); + + context.load(evalNode.getLeftExpr().getValueType(), LHS); // < lhs + context.load(evalNode.getRightExpr().getValueType(), RHS); // < lhs, rhs + + context.ifCmp(evalNode.getLeftExpr().getValueType(), evalNode.getType(), ifNotMatched); + + context.pushBooleanOfThreeValuedLogic(true); + context.pushNullFlag(true); + context.methodvisitor.visitJumpInsn(Opcodes.GOTO, afterEnd); + + context.methodvisitor.visitLabel(ifNotMatched); + context.pushBooleanOfThreeValuedLogic(false); + context.pushNullFlag(true); + context.methodvisitor.visitJumpInsn(Opcodes.GOTO, afterEnd); + + context.methodvisitor.visitLabel(ifNull); + context.pushNullOfThreeValuedLogic(); + context.pushNullFlag(false); + + context.methodvisitor.visitLabel(afterEnd); + } + + return evalNode; + } + + public EvalNode visitStringConcat(EvalCodeGenContext context, BinaryEval evalNode, Stack stack) + throws CompilationError { + + stack.push(evalNode); + + visit(context, evalNode.getLeftExpr(), stack); // < lhs, l_null + final int LHS_NULLFLAG = context.istore(); // < lhs + final int LHS = context.store(evalNode.getLeftExpr().getValueType()); + + visit(context, evalNode.getRightExpr(), stack); // < rhs, r_nullflag + int RHS_NULLFLAG = context.istore(); + int RHS = context.store(evalNode.getRightExpr().getValueType()); // < + stack.pop(); + + Label ifNull = new Label(); + Label afterEnd = new Label(); + + context.emitNullityCheck(ifNull, LHS_NULLFLAG, RHS_NULLFLAG); + + context.load(evalNode.getLeftExpr().getValueType(), LHS); // < lhs + context.load(evalNode.getRightExpr().getValueType(), RHS); // < lhs, rhs + + context.invokeVirtual(String.class, "concat", String.class, new Class[] {String.class}); + context.pushNullFlag(true); + context.methodvisitor.visitJumpInsn(Opcodes.GOTO, afterEnd); + + context.methodvisitor.visitLabel(ifNull); + context.pushDummyValue(evalNode.getValueType()); + context.pushNullFlag(false); + + context.methodvisitor.visitLabel(afterEnd); + + return evalNode; + } + + public EvalNode visitIsNull(EvalCodeGenContext context, IsNullEval isNullEval, Stack stack) { + + visit(context, isNullEval.getChild(), stack); + + Label ifNull = new Label(); + Label endIf = new Label(); + + context.emitNullityCheck(ifNull); + + context.pop(isNullEval.getChild().getValueType()); + context.pushBooleanOfThreeValuedLogic(isNullEval.isNot() ? true : false); + context.methodvisitor.visitJumpInsn(Opcodes.GOTO, endIf); + + context.methodvisitor.visitLabel(ifNull); + context.pop(isNullEval.getChild().getValueType()); + context.pushBooleanOfThreeValuedLogic(isNullEval.isNot() ? false : true); + + emitLabel(context, endIf); + context.methodvisitor.visitInsn(Opcodes.ICONST_1); // NOT NULL + + return isNullEval; + } + + + @Override + public EvalNode visitConst(EvalCodeGenContext context, ConstEval constEval, Stack stack) { + switch (constEval.getValueType().getType()) { + case NULL_TYPE: + + if (stack.isEmpty()) { + context.pushNullOfThreeValuedLogic(); + } else { + EvalNode parentNode = stack.peek(); + + if (parentNode instanceof BinaryEval) { + BinaryEval parent = (BinaryEval) stack.peek(); + if (parent.getLeftExpr() == constEval) { + context.pushDummyValue(parent.getRightExpr().getValueType()); + } else { + context.pushDummyValue(parent.getLeftExpr().getValueType()); + } + } else if (parentNode instanceof CaseWhenEval) { + CaseWhenEval caseWhen = (CaseWhenEval) parentNode; + context.pushDummyValue(caseWhen.getValueType()); + } else { + throw new CompilationError("Cannot find matched type in the stack: " + constEval); + } + } + break; + case BOOLEAN: + context.push(constEval.getValue().asInt4()); + break; + + case INT1: + case INT2: + case INT4: + case DATE: + context.push(constEval.getValue().asInt4()); + break; + case INT8: + case TIMESTAMP: + case TIME: + context.push(constEval.getValue().asInt8()); + break; + case FLOAT4: + context.push(constEval.getValue().asFloat4()); + break; + case FLOAT8: + context.push(constEval.getValue().asFloat8()); + break; + case CHAR: + case TEXT: + context.push(constEval.getValue().asChars()); + break; + case INTERVAL: + // load pre-stored variable. + emitGetField(context, context.owner, context.symbols.get(constEval), IntervalDatum.class); + break; + default: + throw new UnsupportedOperationException(constEval.getValueType().getType().name() + + " const type is not supported"); + } + + context.pushNullFlag(constEval.getValueType().getType() != TajoDataTypes.Type.NULL_TYPE); + return constEval; + } + + public static ParamType [] getParamTypes(EvalNode [] arguments) { + ParamType[] paramTypes = new ParamType[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + if (arguments[i].getType() == EvalType.CONST) { + if (arguments[i].getValueType().getType() == TajoDataTypes.Type.NULL_TYPE) { + paramTypes[i] = ParamType.NULL; + } else { + paramTypes[i] = ParamType.CONSTANT; + } + } else { + paramTypes[i] = ParamType.VARIABLE; + } + } + return paramTypes; + } + + @Override + public EvalNode visitFuncCall(EvalCodeGenContext context, FunctionEval func, Stack stack) { + int paramNum = func.getArgs().length; + context.push(paramNum); + context.newArray(Datum.class); // new Datum[paramNum] + final int DATUM_ARRAY = context.astore(); + + stack.push(func); + EvalNode [] params = func.getArgs(); + for (int paramIdx = 0; paramIdx < func.getArgs().length; paramIdx++) { + context.aload(DATUM_ARRAY); // array ref + context.methodvisitor.visitLdcInsn(paramIdx); // array idx + visit(context, params[paramIdx], stack); + context.convertToDatum(params[paramIdx].getValueType(), true); // value + context.methodvisitor.visitInsn(Opcodes.AASTORE); + } + stack.pop(); + + context.methodvisitor.visitTypeInsn(Opcodes.NEW, TajoGeneratorAdapter.getInternalName(VTuple.class)); + context.methodvisitor.visitInsn(Opcodes.DUP); + context.aload(DATUM_ARRAY); + context.newInstance(VTuple.class, new Class[]{Datum[].class}); // new VTuple(datum []) + context.methodvisitor.visitTypeInsn(Opcodes.CHECKCAST, TajoGeneratorAdapter.getInternalName(Tuple.class)); // cast to Tuple + final int TUPLE = context.astore(); + + FunctionDesc desc = func.getFuncDesc(); + + String fieldName = context.symbols.get(func); + String funcDescName = "L" + TajoGeneratorAdapter.getInternalName(desc.getFuncClass()) + ";"; + + context.aload(0); + context.methodvisitor.visitFieldInsn(Opcodes.GETFIELD, context.owner, fieldName, funcDescName); + context.aload(TUPLE); + context.invokeVirtual(desc.getFuncClass(), "eval", Datum.class, new Class[] {Tuple.class}); + + context.convertToPrimitive(func.getValueType()); + return func; + } + + public EvalNode visitInPredicate(EvalCodeGenContext context, EvalNode patternEval, Stack stack) { + String fieldName = context.symbols.get(patternEval); + emitGetField(context, context.owner, fieldName, InEval.class); + if (context.schema != null) { + emitGetField(context, context.owner, "schema", Schema.class); + } else { + context.methodvisitor.visitInsn(Opcodes.ACONST_NULL); + } + context.aload(2); // tuple + context.invokeVirtual(InEval.class, "eval", Datum.class, new Class[]{Schema.class, Tuple.class}); + context.convertToPrimitive(patternEval.getValueType()); + + return patternEval; + } + + protected EvalNode visitStringPatternMatch(EvalCodeGenContext context, EvalNode patternEval, Stack stack) { + Class clazz = getStringPatternEvalClass(patternEval.getType()); + String fieldName = context.symbols.get(patternEval); + emitGetField(context, context.owner, fieldName, clazz); + if (context.schema != null) { + emitGetField(context, context.owner, "schema", Schema.class); + } else { + context.methodvisitor.visitInsn(Opcodes.ACONST_NULL); + } + context.aload(2); // tuple + context.invokeVirtual(clazz, "eval", Datum.class, new Class[]{Schema.class, Tuple.class}); + context.convertToPrimitive(patternEval.getValueType()); + + return patternEval; + } + + protected static void emitGetField(EvalCodeGenContext context, String owner, String fieldName, Class clazz) { + context.aload(0); + context.methodvisitor.visitFieldInsn(Opcodes.GETFIELD, owner, fieldName, getDescription(clazz)); + } + + public static Class getStringPatternEvalClass(EvalType type) { + if (type == EvalType.LIKE) { + return LikePredicateEval.class; + } else if (type == EvalType.SIMILAR_TO) { + return SimilarToPredicateEval.class; + } else { + return RegexPredicateEval.class; + } + } + + @SuppressWarnings("unused") + public static EvalNode createEval(String json) { + return CoreGsonHelper.fromJson(json, EvalNode.class); + } + + @SuppressWarnings("unused") + public static ConstEval createConstEval(String json) { + return (ConstEval) CoreGsonHelper.fromJson(json, EvalNode.class); + } + + @SuppressWarnings("unused") + public static RowConstantEval createRowConstantEval(String json) { + return (RowConstantEval) CoreGsonHelper.fromJson(json, EvalNode.class); + } + + @SuppressWarnings("unused") + public static Schema createSchema(String json) { + return CoreGsonHelper.fromJson(json, Schema.class); + } + + @Override + protected EvalNode visitCaseWhen(EvalCodeGenContext context, CaseWhenEval caseWhen, Stack stack) { + CaseWhenEmitter.getInstance().emit(this, context, caseWhen, stack); + return caseWhen; + } + + @Override + protected EvalNode visitIfThen(EvalCodeGenContext context, CaseWhenEval.IfThenEval evalNode, Stack stack) { + stack.push(evalNode); + visit(context, evalNode.getCondition(), stack); + visit(context, evalNode.getResult(), stack); + stack.pop(); + return evalNode; + } + +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java new file mode 100644 index 0000000000..54d857b8f4 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import com.google.common.collect.Maps; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.engine.eval.EvalNode; +import org.apache.tajo.engine.planner.*; +import org.apache.tajo.engine.planner.logical.*; +import org.apache.tajo.util.Pair; + +import java.util.Collections; +import java.util.Map; +import java.util.Stack; + +public class ExecutorPreCompiler extends BasicLogicalPlanVisitor { + private static final Log LOG = LogFactory.getLog(ExecutorPreCompiler.class); + + private static final ExecutorPreCompiler instance; + + static { + instance = new ExecutorPreCompiler(); + } + + public static void compile(CompilationContext context, LogicalNode node) throws PlanningException { + instance.visit(context, null, null, node, new Stack()); + context.compiledEval = Collections.unmodifiableMap(context.compiledEval); + } + + public static Map, EvalNode> compile(TajoClassLoader classLoader, LogicalNode node) + throws PlanningException { + CompilationContext context = new CompilationContext(classLoader); + instance.visit(context, null, null, node, new Stack()); + return context.compiledEval; + } + + public static class CompilationContext { + private final EvalCodeGenerator compiler; + private Map, EvalNode> compiledEval; + + public CompilationContext(TajoClassLoader classLoader) { + this.compiler = new EvalCodeGenerator(classLoader); + this.compiledEval = Maps.newHashMap(); + } + + public EvalCodeGenerator getCompiler() { + return compiler; + } + + public Map, EvalNode> getPrecompiedEvals() { + return compiledEval; + } + } + + private static void compileIfAbsent(CompilationContext context, Schema schema, EvalNode eval) { + Pair key = new Pair(schema, eval); + if (!context.compiledEval.containsKey(key)) { + try { + EvalNode compiled = context.compiler.compile(schema, eval); + context.compiledEval.put(key, compiled); + + } catch (Throwable t) { + // If any compilation error occurs, it works in a fallback mode. This mode just uses EvalNode objects + // instead of a compiled EvalNode. + context.compiledEval.put(key, eval); + LOG.warn(t); + } + } + } + + private static void compileProjectableNode(CompilationContext context, Schema schema, Projectable node) { + Target [] targets; + if (node.hasTargets()) { + targets = node.getTargets(); + } else { + targets = PlannerUtil.schemaToTargets(node.getOutSchema()); + } + + for (Target target : targets) { + compileIfAbsent(context, schema, target.getEvalTree()); + } + } + + private static void compileSelectableNode(CompilationContext context, Schema schema, SelectableNode node) { + if (node.hasQual()) { + compileIfAbsent(context, schema, node.getQual()); + } + } + + @Override + public LogicalNode visitProjection(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + ProjectionNode node, Stack stack) throws PlanningException { + super.visitProjection(context, plan, block, node, stack); + + compileProjectableNode(context, node.getInSchema(), node); + + return node; + } + + @Override + public LogicalNode visitHaving(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + HavingNode node, Stack stack) throws PlanningException { + super.visitHaving(context, plan, block, node, stack); + + compileSelectableNode(context, node.getInSchema(), node); + + return node; + } + + @Override + public LogicalNode visitGroupBy(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + GroupbyNode node, Stack stack) throws PlanningException { + super.visitGroupBy(context, plan, block, node, stack); + + compileProjectableNode(context, node.getInSchema(), node); + + return node; + } + + @Override + public LogicalNode visitWindowAgg(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + WindowAggNode node, Stack stack) throws PlanningException { + super.visitWindowAgg(context, plan, block, node, stack); + + compileProjectableNode(context, node.getInSchema(), node); + + return node; + } + + public LogicalNode visitDistinct(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + DistinctGroupbyNode node, Stack stack) throws PlanningException { + super.visitDistinct(context, plan, block, node, stack); + + compileProjectableNode(context, node.getInSchema(), node); + return node; + } + + @Override + public LogicalNode visitFilter(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + SelectionNode node, Stack stack) throws PlanningException { + super.visitFilter(context, plan, block, node, stack); + + compileSelectableNode(context, node.getInSchema(), node); + + return node; + } + + @Override + public LogicalNode visitJoin(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + JoinNode node, Stack stack) throws PlanningException { + super.visitJoin(context, plan, block, node, stack); + + compileProjectableNode(context, node.getInSchema(), node); + + if (node.hasJoinQual()) { + compileIfAbsent(context, node.getInSchema(), node.getJoinQual()); + } + + return node; + } + + @Override + public LogicalNode visitTableSubQuery(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + TableSubQueryNode node, Stack stack) throws PlanningException { + stack.push(node); + visit(context, plan, null, node.getSubQuery(), stack); + stack.pop(); + + if (node.hasTargets()) { + for (Target target : node.getTargets()) { + compileIfAbsent(context, node.getTableSchema(), target.getEvalTree()); + } + } + + return node; + } + + @Override + public LogicalNode visitPartitionedTableScan(CompilationContext context, LogicalPlan plan, + LogicalPlan.QueryBlock block, PartitionedTableScanNode node, + Stack stack) throws PlanningException { + visitScan(context, plan, block, node, stack); + return node; + } + + @Override + public LogicalNode visitScan(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, + ScanNode node, Stack stack) throws PlanningException { + + compileProjectableNode(context, node.getInSchema(), node); + compileSelectableNode(context, node.getInSchema(), node); + + return node; + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanningException.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoClassLoader.java similarity index 74% rename from tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanningException.java rename to tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoClassLoader.java index 62add1e343..16be3b0d24 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanningException.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoClassLoader.java @@ -16,16 +16,15 @@ * limitations under the License. */ -package org.apache.tajo.engine.planner.physical; +package org.apache.tajo.engine.codegen; -import java.io.IOException; +public class TajoClassLoader extends ClassLoader { -public class PhysicalPlanningException extends IOException { - public PhysicalPlanningException(String message) { - super(message); + public Class defineClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); } - public PhysicalPlanningException(Exception ioe) { - super(ioe); + public void clean() throws Throwable { + super.finalize(); } } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoGeneratorAdapter.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoGeneratorAdapter.java new file mode 100644 index 0000000000..6fac1a8b01 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/TajoGeneratorAdapter.java @@ -0,0 +1,953 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.apache.tajo.common.TajoDataTypes; +import org.apache.tajo.datum.*; +import org.apache.tajo.engine.eval.EvalNode; +import org.apache.tajo.engine.eval.EvalType; +import org.apache.tajo.exception.InvalidCastException; +import org.apache.tajo.exception.UnsupportedException; +import org.apache.tajo.util.TUtil; +import org.apache.tajo.util.datetime.DateTimeUtil; +import org.apache.tajo.org.objectweb.asm.Label; +import org.apache.tajo.org.objectweb.asm.MethodVisitor; +import org.apache.tajo.org.objectweb.asm.Opcodes; +import org.apache.tajo.org.objectweb.asm.Type; +import org.apache.tajo.org.objectweb.asm.commons.GeneratorAdapter; +import org.apache.tajo.org.objectweb.asm.commons.TableSwitchGenerator; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.tajo.common.TajoDataTypes.Type.*; + +class TajoGeneratorAdapter { + + public static final Map> OpCodesMap = Maps.newHashMap(); + + static { + TUtil.putToNestedMap(OpCodesMap, EvalType.PLUS, INT1, Opcodes.IADD); + TUtil.putToNestedMap(OpCodesMap, EvalType.PLUS, INT2, Opcodes.IADD); + TUtil.putToNestedMap(OpCodesMap, EvalType.PLUS, INT4, Opcodes.IADD); + TUtil.putToNestedMap(OpCodesMap, EvalType.PLUS, INT8, Opcodes.LADD); + TUtil.putToNestedMap(OpCodesMap, EvalType.PLUS, FLOAT4, Opcodes.FADD); + TUtil.putToNestedMap(OpCodesMap, EvalType.PLUS, FLOAT8, Opcodes.DADD); + + TUtil.putToNestedMap(OpCodesMap, EvalType.MINUS, INT1, Opcodes.ISUB); + TUtil.putToNestedMap(OpCodesMap, EvalType.MINUS, INT2, Opcodes.ISUB); + TUtil.putToNestedMap(OpCodesMap, EvalType.MINUS, INT4, Opcodes.ISUB); + TUtil.putToNestedMap(OpCodesMap, EvalType.MINUS, INT8, Opcodes.LSUB); + TUtil.putToNestedMap(OpCodesMap, EvalType.MINUS, FLOAT4, Opcodes.FSUB); + TUtil.putToNestedMap(OpCodesMap, EvalType.MINUS, FLOAT8, Opcodes.DSUB); + + TUtil.putToNestedMap(OpCodesMap, EvalType.MULTIPLY, INT1, Opcodes.IMUL); + TUtil.putToNestedMap(OpCodesMap, EvalType.MULTIPLY, INT2, Opcodes.IMUL); + TUtil.putToNestedMap(OpCodesMap, EvalType.MULTIPLY, INT4, Opcodes.IMUL); + TUtil.putToNestedMap(OpCodesMap, EvalType.MULTIPLY, INT8, Opcodes.LMUL); + TUtil.putToNestedMap(OpCodesMap, EvalType.MULTIPLY, FLOAT4, Opcodes.FMUL); + TUtil.putToNestedMap(OpCodesMap, EvalType.MULTIPLY, FLOAT8, Opcodes.DMUL); + + TUtil.putToNestedMap(OpCodesMap, EvalType.DIVIDE, INT1, Opcodes.IDIV); + TUtil.putToNestedMap(OpCodesMap, EvalType.DIVIDE, INT2, Opcodes.IDIV); + TUtil.putToNestedMap(OpCodesMap, EvalType.DIVIDE, INT4, Opcodes.IDIV); + TUtil.putToNestedMap(OpCodesMap, EvalType.DIVIDE, INT8, Opcodes.LDIV); + TUtil.putToNestedMap(OpCodesMap, EvalType.DIVIDE, FLOAT4, Opcodes.FDIV); + TUtil.putToNestedMap(OpCodesMap, EvalType.DIVIDE, FLOAT8, Opcodes.DDIV); + + TUtil.putToNestedMap(OpCodesMap, EvalType.MODULAR, INT1, Opcodes.IREM); + TUtil.putToNestedMap(OpCodesMap, EvalType.MODULAR, INT2, Opcodes.IREM); + TUtil.putToNestedMap(OpCodesMap, EvalType.MODULAR, INT4, Opcodes.IREM); + TUtil.putToNestedMap(OpCodesMap, EvalType.MODULAR, INT8, Opcodes.LREM); + TUtil.putToNestedMap(OpCodesMap, EvalType.MODULAR, FLOAT4, Opcodes.FREM); + TUtil.putToNestedMap(OpCodesMap, EvalType.MODULAR, FLOAT8, Opcodes.DREM); + + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_AND, INT1, Opcodes.IAND); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_AND, INT2, Opcodes.IAND); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_AND, INT4, Opcodes.IAND); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_AND, INT8, Opcodes.LAND); + + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_OR, INT1, Opcodes.IOR); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_OR, INT2, Opcodes.IOR); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_OR, INT4, Opcodes.IOR); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_OR, INT8, Opcodes.LOR); + + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_XOR, INT1, Opcodes.IXOR); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_XOR, INT2, Opcodes.IXOR); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_XOR, INT4, Opcodes.IXOR); + TUtil.putToNestedMap(OpCodesMap, EvalType.BIT_XOR, INT8, Opcodes.LXOR); + + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, INT1, Opcodes.IF_ICMPEQ); + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, INT2, Opcodes.IF_ICMPEQ); + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, INT4, Opcodes.IF_ICMPEQ); + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, FLOAT8, Opcodes.DCMPG); + TUtil.putToNestedMap(OpCodesMap, EvalType.EQUAL, TEXT, Opcodes.IF_ACMPNE); + + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, INT1, Opcodes.IF_ICMPNE); + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, INT2, Opcodes.IF_ICMPNE); + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, INT4, Opcodes.IF_ICMPNE); + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, FLOAT8, Opcodes.DCMPG); + TUtil.putToNestedMap(OpCodesMap, EvalType.NOT_EQUAL, TEXT, Opcodes.IF_ACMPNE); + + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT1, Opcodes.IF_ICMPLT); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT2, Opcodes.IF_ICMPLT); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT4, Opcodes.IF_ICMPLT); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, FLOAT8, Opcodes.DCMPG); + + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT1, Opcodes.IF_ICMPLT); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT2, Opcodes.IF_ICMPLT); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT4, Opcodes.IF_ICMPLT); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.LTH, FLOAT8, Opcodes.DCMPG); + + TUtil.putToNestedMap(OpCodesMap, EvalType.LEQ, INT1, Opcodes.IF_ICMPLE); + TUtil.putToNestedMap(OpCodesMap, EvalType.LEQ, INT2, Opcodes.IF_ICMPLE); + TUtil.putToNestedMap(OpCodesMap, EvalType.LEQ, INT4, Opcodes.IF_ICMPLE); + TUtil.putToNestedMap(OpCodesMap, EvalType.LEQ, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.LEQ, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.LEQ, FLOAT8, Opcodes.DCMPG); + + TUtil.putToNestedMap(OpCodesMap, EvalType.GTH, INT1, Opcodes.IF_ICMPGT); + TUtil.putToNestedMap(OpCodesMap, EvalType.GTH, INT2, Opcodes.IF_ICMPGT); + TUtil.putToNestedMap(OpCodesMap, EvalType.GTH, INT4, Opcodes.IF_ICMPGT); + TUtil.putToNestedMap(OpCodesMap, EvalType.GTH, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.GTH, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.GTH, FLOAT8, Opcodes.DCMPG); + + TUtil.putToNestedMap(OpCodesMap, EvalType.GEQ, INT1, Opcodes.IF_ICMPGE); + TUtil.putToNestedMap(OpCodesMap, EvalType.GEQ, INT2, Opcodes.IF_ICMPGE); + TUtil.putToNestedMap(OpCodesMap, EvalType.GEQ, INT4, Opcodes.IF_ICMPGE); + TUtil.putToNestedMap(OpCodesMap, EvalType.GEQ, INT8, Opcodes.LCMP); + TUtil.putToNestedMap(OpCodesMap, EvalType.GEQ, FLOAT4, Opcodes.FCMPL); + TUtil.putToNestedMap(OpCodesMap, EvalType.GEQ, FLOAT8, Opcodes.DCMPG); + } + + protected int access; + protected MethodVisitor methodvisitor; + protected GeneratorAdapter generatorAdapter; + + public TajoGeneratorAdapter() {} + + public TajoGeneratorAdapter(int access, MethodVisitor methodVisitor, String name, String desc) { + this.access = access; + this.methodvisitor = methodVisitor; + generatorAdapter = new GeneratorAdapter(methodVisitor, access, name, desc); + } + + public static boolean isJVMInternalInt(TajoDataTypes.DataType dataType) { + TajoDataTypes.Type type = dataType.getType(); + return type == BOOLEAN || type == INT1 || type == INT2 || type == INT4 || type== INET4; + } + + public static int getWordSize(TajoDataTypes.DataType type) { + if (type.getType() == INT8 || type.getType() == FLOAT8 || type.getType() == TIMESTAMP || type.getType() == TIME) { + return 2; + } else { + return 1; + } + } + + public void push(final boolean value) { + methodvisitor.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0); + } + + public void push(final int value) { + if (value >= -1 && value <= 5) { + methodvisitor.visitInsn(Opcodes.ICONST_0 + value); + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + methodvisitor.visitIntInsn(Opcodes.BIPUSH, value); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + methodvisitor.visitIntInsn(Opcodes.SIPUSH, value); + } else { + methodvisitor.visitLdcInsn(new Integer(value)); + } + } + + public void push(final long value) { + if (value == 0L || value == 1L) { + methodvisitor.visitInsn(Opcodes.LCONST_0 + (int) value); + } else { + methodvisitor.visitLdcInsn(new Long(value)); + } + } + + public void push(final float value) { + int bits = Float.floatToIntBits(value); + if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 + methodvisitor.visitInsn(Opcodes.FCONST_0 + (int) value); + } else { + methodvisitor.visitLdcInsn(new Float(value)); + } + } + + public void push(final double value) { + long bits = Double.doubleToLongBits(value); + if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d + methodvisitor.visitInsn(Opcodes.DCONST_0 + (int) value); + } else { + methodvisitor.visitLdcInsn(new Double(value)); + } + } + + public void push(final String value) { + Preconditions.checkNotNull(value); + methodvisitor.visitLdcInsn(value); + } + + public void ifCmp(TajoDataTypes.DataType dataType, EvalType evalType, Label elseLabel) { + + if (isJVMInternalInt(dataType)) { + switch (evalType) { + case EQUAL: + methodvisitor.visitJumpInsn(Opcodes.IF_ICMPNE, elseLabel); + break; + case NOT_EQUAL: + methodvisitor.visitJumpInsn(Opcodes.IF_ICMPEQ, elseLabel); + break; + case LTH: + methodvisitor.visitJumpInsn(Opcodes.IF_ICMPGE, elseLabel); + break; + case LEQ: + methodvisitor.visitJumpInsn(Opcodes.IF_ICMPGT, elseLabel); + break; + case GTH: + methodvisitor.visitJumpInsn(Opcodes.IF_ICMPLE, elseLabel); + break; + case GEQ: + methodvisitor.visitJumpInsn(Opcodes.IF_ICMPLT, elseLabel); + break; + default: + throw new CompilationError("Unknown comparison operator: " + evalType.name()); + } + } else { + + if (dataType.getType() == TEXT) { + invokeVirtual(String.class, "compareTo", int.class, new Class[]{String.class}); + } else { + int opCode = TajoGeneratorAdapter.getOpCode(evalType, dataType); + methodvisitor.visitInsn(opCode); + } + + switch (evalType) { + case EQUAL: + methodvisitor.visitJumpInsn(Opcodes.IFNE, elseLabel); + break; + case NOT_EQUAL: + methodvisitor.visitJumpInsn(Opcodes.IFEQ, elseLabel); + break; + case LTH: + methodvisitor.visitJumpInsn(Opcodes.IFGE, elseLabel); + break; + case LEQ: + methodvisitor.visitJumpInsn(Opcodes.IFGT, elseLabel); + break; + case GTH: + methodvisitor.visitJumpInsn(Opcodes.IFLE, elseLabel); + break; + case GEQ: + methodvisitor.visitJumpInsn(Opcodes.IFLT, elseLabel); + break; + default: + throw new CompilationError("Unknown comparison operator: " + evalType.name()); + } + } + } + + public void load(TajoDataTypes.DataType dataType, int idx) { + switch (dataType.getType()) { + case NULL_TYPE: + case BOOLEAN: + case CHAR: + case INT1: + case INT2: + case INT4: + case INET4: + methodvisitor.visitVarInsn(Opcodes.ILOAD, idx); + break; + case INT8: + methodvisitor.visitVarInsn(Opcodes.LLOAD, idx); + break; + case FLOAT4: + methodvisitor.visitVarInsn(Opcodes.FLOAD, idx); + break; + case FLOAT8: + methodvisitor.visitVarInsn(Opcodes.DLOAD, idx); + break; + case TEXT: + case INTERVAL: + case PROTOBUF: + methodvisitor.visitVarInsn(Opcodes.ALOAD, idx); + break; + default: + throw new CompilationError("Unknown data type: " + dataType.getType().name()); + } + } + + public static String getDescription(Class clazz) { + if (clazz == null) { + return ""; + } else if (clazz == void.class) { + return "V"; + } else if (clazz == boolean.class) { + return "Z"; + } else if (clazz == char.class) { + return "C"; + } else if (clazz == byte.class) { + return "B"; + } else if (clazz == short.class) { + return "S"; + } else if (clazz == int.class) { + return "I"; + } else if (clazz == long.class) { + return "J"; + } else if (clazz == float.class) { + return "F"; + } else if (clazz == double.class) { + return "D"; + } else if (clazz.isArray()) { + return "[" + getDescription(clazz.getComponentType()); + } else { + return "L" + getInternalName(clazz) + ";"; + } + } + + public static String getMethodDescription(Class returnType, Class [] argumentTypes) { + StringBuilder builder = new StringBuilder(); + builder.append("("); + if (argumentTypes != null) { + for (Class argType : argumentTypes) { + builder.append(getDescription(argType)); + } + } + builder.append(")"); + + builder.append(getDescription(returnType)); + return builder.toString(); + } + + public Label newLabel() { + return new Label(); + } + + public void gotoLabel(Label label) { + methodvisitor.visitJumpInsn(Opcodes.GOTO, label); + } + + public void pushBooleanOfThreeValuedLogic(boolean value) { + push(value ? 1 : 2); // TRUE or FALSE + } + + public void pushNullOfThreeValuedLogic() { + push(0); // NULL of three valued logic + } + + public void pushNullFlag(boolean trueIfNotNull) { + push(trueIfNotNull ? true : false); + } + + public void emitNullityCheck(Label ifNull) { + methodvisitor.visitJumpInsn(Opcodes.IFEQ, ifNull); + } + + /** + * If at least one of all local variables corresponding to varIds is null, jump the label. + * + * @param ifNull The label to jump + * @param varIds A list of variable Ids. + */ + public void emitNullityCheck(Label ifNull, int ... varIds) { + // TODO - ANDing can be reduced if we interleave IAND into a sequence of ILOAD instructions. + for (int varId : varIds) { + methodvisitor.visitVarInsn(Opcodes.ILOAD, varId); + } + if (varIds.length > 1) { + for (int i = 0; i < varIds.length - 1; i++) { + methodvisitor.visitInsn(Opcodes.IAND); + } + } + emitNullityCheck(ifNull); + } + + public void pushDummyValue(TajoDataTypes.DataType type) { + if (type.getType() == NULL_TYPE) { + pushNullOfThreeValuedLogic(); + } else if (isJVMInternalInt(type) || type.getType() == DATE) { + push(0); + } else if (type.getType() == TajoDataTypes.Type.INT8 || type.getType() == TIMESTAMP || type.getType() == TIME) { + push(0L); + } else if (type.getType() == TajoDataTypes.Type.FLOAT8) { + push(0.0d); + } else if (type.getType() == TajoDataTypes.Type.FLOAT4) { + push(0.0f); + } else if (type.getType() == TajoDataTypes.Type.CHAR || type.getType() == TajoDataTypes.Type.TEXT) { + push(""); + } else if (type.getType() == INTERVAL || type.getType() == PROTOBUF) { + invokeStatic(NullDatum.class, "get", NullDatum.class, new Class[]{}); + } else { + assert false; + } + } + + public void newInstance(Class owner, Class [] paramTypes) { + methodvisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, getInternalName(owner), "", + getMethodDescription(void.class, paramTypes)); + } + + public void invokeSpecial(Class owner, String methodName, Class returnType, Class [] paramTypes) { + methodvisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, getInternalName(owner), methodName, + getMethodDescription(returnType, paramTypes)); + } + + public void invokeStatic(Class owner, String methodName, Class returnType, Class [] paramTypes) { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, getInternalName(owner), methodName, + getMethodDescription(returnType, paramTypes)); + } + + public void invokeVirtual(Class owner, String methodName, Class returnType, Class [] paramTypes) { + methodvisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(owner), methodName, + getMethodDescription(returnType, paramTypes)); + } + + public void invokeInterface(Class owner, String methodName, Class returnType, Class [] paramTypes) { + methodvisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(owner), methodName, + getMethodDescription(returnType, paramTypes)); + } + + public static boolean isPrimitiveOpCode(EvalType evalType, TajoDataTypes.DataType returnType) { + return TUtil.containsInNestedMap(OpCodesMap, evalType, returnType.getType()); + } + + public static int getOpCode(EvalType evalType, TajoDataTypes.DataType returnType) { + if (!isPrimitiveOpCode(evalType, returnType)) { + throw new CompilationError("No Such OpCode for " + evalType + " returning " + returnType.getType().name()); + } + return TUtil.getFromNestedMap(OpCodesMap, evalType, returnType.getType()); + } + + public void castInsn(TajoDataTypes.DataType srcType, TajoDataTypes.DataType targetType) { + TajoDataTypes.Type srcRawType = srcType.getType(); + TajoDataTypes.Type targetRawType = targetType.getType(); + switch(srcRawType) { + case BOOLEAN: + case CHAR: { + if (srcType.hasLength() && srcType.getLength() == 1) { + switch (targetType.getType()) { + case CHAR: + case INT1: + case INT2: + case INT4: break; + case INT8: methodvisitor.visitInsn(Opcodes.I2L); break; + case FLOAT4: methodvisitor.visitInsn(Opcodes.I2F); break; + case FLOAT8: methodvisitor.visitInsn(Opcodes.I2D); break; + case TEXT: emitStringValueOfChar(); break; + default: + throw new InvalidCastException(srcType, targetType); + } + } else { + switch (targetRawType) { + case CHAR: + case INT1: + case INT2: + case INT4: emitParseInt4(); break; + case INT8: emitParseInt8(); break; + case FLOAT4: emitParseFloat4(); break; + case FLOAT8: emitParseFloat8(); break; + case TEXT: break; + default: throw new InvalidCastException(srcType, targetType); + } + } + break; + } + case INT1: + case INT2: + case INT4: + switch (targetType.getType()) { + case CHAR: + case INT1: methodvisitor.visitInsn(Opcodes.I2C); break; + case INT2: methodvisitor.visitInsn(Opcodes.I2S); break; + case INT4: return; + case INT8: methodvisitor.visitInsn(Opcodes.I2L); break; + case FLOAT4: methodvisitor.visitInsn(Opcodes.I2F); break; + case FLOAT8: methodvisitor.visitInsn(Opcodes.I2D); break; + case TEXT: emitStringValueOfInt4(); break; + default: throw new InvalidCastException(srcType, targetType); + } + break; + case INT8: + switch (targetRawType) { + case CHAR: + case INT1: + case INT2: + case INT4: methodvisitor.visitInsn(Opcodes.L2I); break; + case INT8: return; + case FLOAT4: methodvisitor.visitInsn(Opcodes.L2F); break; + case FLOAT8: methodvisitor.visitInsn(Opcodes.L2D); break; + case TEXT: emitStringValueOfInt8(); break; + default: throw new InvalidCastException(srcType, targetType); + } + break; + case FLOAT4: + switch (targetRawType) { + case CHAR: + case INT1: + case INT2: + case INT4: methodvisitor.visitInsn(Opcodes.F2I); break; + case INT8: methodvisitor.visitInsn(Opcodes.F2L); break; + case FLOAT4: return; + case FLOAT8: methodvisitor.visitInsn(Opcodes.F2D); break; + case TEXT: emitStringValueOfFloat4(); break; + default: throw new InvalidCastException(srcType, targetType); + } + break; + case FLOAT8: + switch (targetRawType) { + case CHAR: + case INT1: + case INT2: + case INT4: methodvisitor.visitInsn(Opcodes.D2I); break; + case INT8: methodvisitor.visitInsn(Opcodes.D2L); break; + case FLOAT4: methodvisitor.visitInsn(Opcodes.D2F); break; + case FLOAT8: return; + case TEXT: emitStringValueOfFloat8(); break; + default: throw new InvalidCastException(srcType, targetType); + } + break; + case TEXT: + switch (targetRawType) { + case CHAR: + case INT1: + case INT2: + case INT4: emitParseInt4(); break; + case INT8: emitParseInt8(); break; + case FLOAT4: emitParseFloat4(); break; + case FLOAT8: emitParseFloat8(); break; + case TEXT: break; + case TIMESTAMP: { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(DateTimeUtil.class), + "toJulianTimestampWithTZ", "(L" + Type.getInternalName(String.class) + ";)J"); + break; + } + case DATE: { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(DateTimeUtil.class), + "toJulianDate", "(L" + Type.getInternalName(String.class) + ";)I"); + break; + } + case TIME: { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(DateTimeUtil.class), + "toJulianTime", "(L" + Type.getInternalName(String.class) + ";)J"); + break; + } + default: throw new InvalidCastException(srcType, targetType); + } + break; + default: throw new InvalidCastException(srcType, targetType); + } + } + + public static String getInternalName(String className) { + return className.replace('.', '/'); + } + + public static String getInternalName(Class clazz) { + return clazz.getName().replace('.', '/'); + } + + public void convertToPrimitive(TajoDataTypes.DataType type) { + + Label ifNull = new Label(); + Label afterAll = new Label(); + + // datum + int datum = astore(); + + aload(datum); + invokeVirtual(Datum.class, "isNotNull", boolean.class, new Class [] {}); + methodvisitor.visitJumpInsn(Opcodes.IFEQ, ifNull); // datum + + aload(datum); + switch (type.getType()) { + case BOOLEAN: + case INT1: + case INT2: + invokeVirtual(Datum.class, "asInt2", short.class, new Class[] {}); + break; + case INT4: + case DATE: + invokeVirtual(Datum.class, "asInt4", int.class, new Class[] {}); + break; + case INT8: + case TIMESTAMP: + case TIME: + invokeVirtual(Datum.class, "asInt8", long.class, new Class[] {}); + break; + case FLOAT4: + invokeVirtual(Datum.class, "asFloat4", float.class, new Class[] {}); + break; + case FLOAT8: + invokeVirtual(Datum.class, "asFloat8", double.class, new Class[] {}); + break; + case CHAR: + case TEXT: + invokeVirtual(Datum.class, "asChars", String.class, new Class[]{}); + break; + default: + throw new UnsupportedException("Unsupported type: " + type); + } + + pushNullFlag(true); + gotoLabel(afterAll); + + methodvisitor.visitLabel(ifNull); + pushDummyValue(type); + pushNullFlag(false); + + methodvisitor.visitLabel(afterAll); + } + + public void convertToDatum(TajoDataTypes.DataType type, boolean castToDatum) { + String convertMethod; + Class returnType; + Class [] paramTypes; + switch (type.getType()) { + case NULL_TYPE: + pop(); // pop null flag + pop(type); // pop null datum + invokeStatic(NullDatum.class, "get", NullDatum.class, new Class[] {}); + if (castToDatum) { + methodvisitor.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(Datum.class)); + } + return; + + case BOOLEAN: + convertMethod = "createBool"; + returnType = Datum.class; + paramTypes = new Class[] {int.class}; + break; + case CHAR: + convertMethod = "createChar"; + returnType = CharDatum.class; + paramTypes = new Class[] {String.class}; + break; + case INT1: + case INT2: + convertMethod = "createInt2"; + returnType = Int2Datum.class; + paramTypes = new Class[] {short.class}; + break; + case INT4: + convertMethod = "createInt4"; + returnType = Int4Datum.class; + paramTypes = new Class[] {int.class}; + break; + case INT8: + convertMethod = "createInt8"; + returnType = Int8Datum.class; + paramTypes = new Class[] {long.class}; + break; + case FLOAT4: + convertMethod = "createFloat4"; + returnType = Float4Datum.class; + paramTypes = new Class[] {float.class}; + break; + case FLOAT8: + convertMethod = "createFloat8"; + returnType = Float8Datum.class; + paramTypes = new Class[] {double.class}; + break; + case TEXT: + convertMethod = "createText"; + returnType = TextDatum.class; + paramTypes = new Class[] {String.class}; + break; + case TIMESTAMP: + convertMethod = "createTimestamp"; + returnType = TimestampDatum.class; + paramTypes = new Class[] {long.class}; + break; + case DATE: + convertMethod = "createDate"; + returnType = DateDatum.class; + paramTypes = new Class[] {int.class}; + break; + case TIME: + convertMethod = "createTime"; + returnType = TimeDatum.class; + paramTypes = new Class[] {long.class}; + break; + case INTERVAL: + case PROTOBUF: + convertMethod = null; + returnType = null; + paramTypes = null; + break; + case INET4: + convertMethod = "createInet4"; + returnType = Inet4Datum.class; + paramTypes = new Class[] {int.class}; + break; + default: + throw new RuntimeException("Unsupported type: " + type.getType().name()); + } + + Label ifNull = new Label(); + Label afterAll = new Label(); + + emitNullityCheck(ifNull); + if (convertMethod != null) { + invokeStatic(DatumFactory.class, convertMethod, returnType, paramTypes); + } + methodvisitor.visitJumpInsn(Opcodes.GOTO, afterAll); + + methodvisitor.visitLabel(ifNull); + pop(type); + invokeStatic(NullDatum.class, "get", NullDatum.class, null); + + methodvisitor.visitLabel(afterAll); + if (castToDatum) { + methodvisitor.visitTypeInsn(Opcodes.CHECKCAST, TajoGeneratorAdapter.getInternalName(Datum.class)); + } + } + + public void pop(TajoDataTypes.DataType type) { + if (getWordSize(type) == 2) { + methodvisitor.visitInsn(Opcodes.POP2); + } else { + methodvisitor.visitInsn(Opcodes.POP); + } + } + + public void emitStringValueOfChar() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), + "valueOf", "(C)L" + Type.getInternalName(String.class) + ";"); + } + + public void emitStringValueOfInt4() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), + "valueOf", "(I)L" + Type.getInternalName(String.class) + ";"); + } + + public void emitStringValueOfInt8() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), + "valueOf", "(J)L" + Type.getInternalName(String.class) + ";"); + } + + public void emitStringValueOfFloat4() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), + "valueOf", "(F)L" + Type.getInternalName(String.class) + ";"); + } + + public void emitStringValueOfFloat8() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), + "valueOf", "(D)L" + Type.getInternalName(String.class) + ";"); + } + + public void emitParseInt4() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Integer.class), + "parseInt", "(L" + Type.getInternalName(String.class) + ";)I"); + } + + public void emitParseInt8() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Long.class), + "parseLong", "(L" + Type.getInternalName(String.class) + ";)J"); + } + + public void emitParseFloat4() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Float.class), + "parseFloat", "(L" + Type.getInternalName(String.class) + ";)F"); + } + + public void emitParseFloat8() { + methodvisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Double.class), + "parseDouble", "(L" + Type.getInternalName(String.class) + ";)D"); + } + + public void newArray(final Class clazz) { + int typeCode; + if (clazz == boolean.class) { + typeCode = Opcodes.T_BOOLEAN; + } else if (clazz == char.class) { + typeCode = Opcodes.T_CHAR; + } else if (clazz == byte.class) { + typeCode = Opcodes.T_BYTE; + } else if (clazz == short.class) { + typeCode = Opcodes.T_SHORT; + } else if (clazz == int.class) { + typeCode = Opcodes.T_INT; + } else if (clazz == long.class) { + typeCode = Opcodes.T_LONG; + } else if (clazz == float.class) { + typeCode = Opcodes.T_FLOAT; + } else if (clazz == double.class) { + typeCode = Opcodes.T_DOUBLE; + } else { + methodvisitor.visitTypeInsn(Opcodes.ANEWARRAY, getInternalName(clazz)); + return; + } + + methodvisitor.visitIntInsn(Opcodes.NEWARRAY, typeCode); + } + + private int nextVarId = 3; + + private Map localVariablesMap = new HashMap(); + + public void astore(String name) { + if (localVariablesMap.containsKey(name)) { + int varId = localVariablesMap.get(name); + methodvisitor.visitVarInsn(Opcodes.ASTORE, varId); + } else { + int varId = nextVarId++; + methodvisitor.visitVarInsn(Opcodes.ASTORE, varId); + localVariablesMap.put(name, varId); + } + } + + public int astore() { + int varId = getCurVarIdAndIncrease(); + methodvisitor.visitVarInsn(Opcodes.ASTORE, varId); + return varId; + } + + public void astore(int varId) { + methodvisitor.visitVarInsn(Opcodes.ASTORE, varId); + } + + public void aload(String name) { + if (localVariablesMap.containsKey(name)) { + int varId = localVariablesMap.get(name); + methodvisitor.visitVarInsn(Opcodes.ALOAD, varId); + } else { + throw new RuntimeException("No such variable name: " + name); + } + } + + public void aload(int varId) { + methodvisitor.visitVarInsn(Opcodes.ALOAD, varId); + } + + public void dup() { + methodvisitor.visitInsn(Opcodes.DUP); + } + + public void pop() { + methodvisitor.visitInsn(Opcodes.POP); + } + + public void pop2() { + methodvisitor.visitInsn(Opcodes.POP2); + } + + public int istore() { + int varId = getCurVarIdAndIncrease(); + methodvisitor.visitVarInsn(Opcodes.ISTORE, varId); + return varId; + } + + public void iload(int varId) { + methodvisitor.visitVarInsn(Opcodes.ILOAD, varId); + } + + private int getCurVarIdAndIncrease() { + int varId = nextVarId++; + return varId; + } + + private int getCurVarIdAndIncrease(TajoDataTypes.DataType type) { + int varId = nextVarId; + nextVarId += getWordSize(type); + return varId; + } + + public int store(TajoDataTypes.DataType type) { + int varId = nextVarId; + nextVarId += TajoGeneratorAdapter.getWordSize(type); + + switch (type.getType()) { + case NULL_TYPE: + case BOOLEAN: + case CHAR: + case INT1: + case INT2: + case INT4: + case INET4: + methodvisitor.visitVarInsn(Opcodes.ISTORE, varId); + break; + case TIME: + case TIMESTAMP: + case INT8: + methodvisitor.visitVarInsn(Opcodes.LSTORE, varId); + break; + case FLOAT4: + methodvisitor.visitVarInsn(Opcodes.FSTORE, varId); + break; + case FLOAT8: + methodvisitor.visitVarInsn(Opcodes.DSTORE, varId); + break; + case INTERVAL: + case TEXT: + methodvisitor.visitVarInsn(Opcodes.ASTORE, varId); + break; + default: + throw new CompilationError("Unknown data type: " + type.getType().name()); + } + + return varId; + } + + public static interface SwitchCaseGenerator extends TableSwitchGenerator { + int size(); + int min(); + int max(); + int key(int index); + void generateCase(int index, Label end); + void generateDefault(); + } + + public static class SwitchCase implements Comparable { + private final int index; + private final EvalNode thanResult; + + public SwitchCase(int index, EvalNode thanResult) { + this.index = index; + this.thanResult = thanResult; + } + + public int key() { + return index; + } + + public EvalNode result() { + return thanResult; + } + + @Override + public int compareTo(SwitchCase o) { + return index - o.index; + } + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/VariablesPreBuilder.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/VariablesPreBuilder.java new file mode 100644 index 0000000000..9f50bb5ed5 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/VariablesPreBuilder.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import org.apache.tajo.common.TajoDataTypes; +import org.apache.tajo.datum.IntervalDatum; +import org.apache.tajo.engine.eval.*; +import org.apache.tajo.org.objectweb.asm.Opcodes; + +import java.util.Stack; + +class VariablesPreBuilder extends SimpleEvalNodeVisitor { + + public EvalNode visitBinaryEval(EvalCodeGenContext context, Stack stack, BinaryEval binaryEval) { + super.visitBinaryEval(context, stack, binaryEval); + + if (EvalType.isStringPatternMatchOperator(binaryEval.getType())) { + if (!context.symbols.containsKey(binaryEval)) { + String fieldName = binaryEval.getType().name() + "_" + context.seqId++; + context.symbols.put(binaryEval, fieldName); + + Class clazz = EvalCodeGenerator.getStringPatternEvalClass(binaryEval.getType()); + context.classWriter.visitField(Opcodes.ACC_PRIVATE, fieldName, + "L" + TajoGeneratorAdapter.getInternalName(clazz) + ";", null, null); + } + } else if (binaryEval.getType() == EvalType.IN) { + if (!context.symbols.containsKey(binaryEval)) { + String fieldName = binaryEval.getType().name() + "_" + context.seqId++; + context.symbols.put(binaryEval, fieldName); + + context.classWriter.visitField(Opcodes.ACC_PRIVATE, fieldName, + "L" + TajoGeneratorAdapter.getInternalName(InEval.class) + ";", null, null); + } + } + + return binaryEval; + } + + @Override + public EvalNode visitConst(EvalCodeGenContext context, ConstEval constEval, Stack stack) { + + if (constEval.getValueType().getType() == TajoDataTypes.Type.INTERVAL) { + if (!context.symbols.containsKey(constEval)) { + String fieldName = constEval.getValueType().getType().name() + "_" + context.seqId++; + context.symbols.put(constEval, fieldName); + + context.classWriter.visitField(Opcodes.ACC_PRIVATE, fieldName, + "L" + TajoGeneratorAdapter.getInternalName(IntervalDatum.class) + ";", null, null); + } + } + return constEval; + } + + @Override + public EvalNode visitFuncCall(EvalCodeGenContext context, FunctionEval function, Stack stack) { + super.visitFuncCall(context, function, stack); + + if (!context.symbols.containsKey(function)) { + String fieldName = function.getFuncDesc().getSignature() + "_" + context.seqId++; + context.symbols.put(function, fieldName); + context.classWriter.visitField(Opcodes.ACC_PRIVATE, fieldName, + "L" + TajoGeneratorAdapter.getInternalName(function.getFuncDesc().getFuncClass()) + ";", null, null); + } + + return function; + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/BinaryEval.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/BinaryEval.java index 4c94f4a2d2..d7473e9323 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/BinaryEval.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/BinaryEval.java @@ -35,6 +35,7 @@ public class BinaryEval extends EvalNode implements Cloneable { @Expose protected EvalNode leftExpr; @Expose protected EvalNode rightExpr; + @Expose protected DataType returnType; protected BinaryEval(EvalType type) { super(type); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalNode.java index 48ab5167ec..754f8885ce 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalNode.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalNode.java @@ -32,7 +32,6 @@ */ public abstract class EvalNode implements Cloneable, GsonObject { @Expose protected EvalType type; - @Expose protected DataType returnType = null; public EvalNode() { } @@ -70,7 +69,6 @@ public String toJson() { public Object clone() throws CloneNotSupportedException { EvalNode evalNode = (EvalNode) super.clone(); evalNode.type = type; - evalNode.returnType = returnType; return evalNode; } } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalTreeUtil.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalTreeUtil.java index 0035636ef7..457f651783 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalTreeUtil.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalTreeUtil.java @@ -43,12 +43,18 @@ public static void changeColumnRef(EvalNode node, String oldName, String newName node.postOrder(new ChangeColumnRefVisitor(oldName, newName)); } - public static void replace(EvalNode expr, EvalNode targetExpr, EvalNode tobeReplaced) { + public static int replace(EvalNode expr, EvalNode targetExpr, EvalNode tobeReplaced) { EvalReplaceVisitor replacer = new EvalReplaceVisitor(targetExpr, tobeReplaced); - replacer.visitChild(null, expr, new Stack()); + ReplaceContext context = new ReplaceContext(); + replacer.visitChild(context, expr, new Stack()); + return context.countOfReplaces; } - public static class EvalReplaceVisitor extends BasicEvalNodeVisitor { + private static class ReplaceContext { + int countOfReplaces = 0; + } + + public static class EvalReplaceVisitor extends BasicEvalNodeVisitor { private EvalNode target; private EvalNode tobeReplaced; @@ -58,10 +64,12 @@ public EvalReplaceVisitor(EvalNode target, EvalNode tobeReplaced) { } @Override - public EvalNode visitChild(EvalNode context, EvalNode evalNode, Stack stack) { + public EvalNode visitChild(ReplaceContext context, EvalNode evalNode, Stack stack) { super.visitChild(context, evalNode, stack); if (evalNode.equals(target)) { + context.countOfReplaces++; + EvalNode parent = stack.peek(); if (parent instanceof BetweenPredicateEval) { diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalType.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalType.java index 500928a869..d5335102e1 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalType.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/EvalType.java @@ -152,6 +152,16 @@ public static boolean isFunction(EvalType type) { return match; } + public static boolean isStringPatternMatchOperator(EvalType type) { + boolean match = false; + + match |= type == LIKE; + match |= type == SIMILAR_TO; + match |= type == REGEX; + + return match; + } + public String getOperatorName() { return operatorName != null ? operatorName : name(); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/FunctionEval.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/FunctionEval.java index 9447ef26b6..5d358f7650 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/FunctionEval.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/FunctionEval.java @@ -47,6 +47,10 @@ public FunctionEval(EvalType type, FunctionDesc funcDesc, EvalNode[] argEvals) { this.argEvals = argEvals; } + public FunctionDesc getFuncDesc() { + return funcDesc; + } + public ParamType [] getParamType() { ParamType [] paramTypes = new ParamType[argEvals.length]; for (int i = 0; i < argEvals.length; i++) { @@ -93,6 +97,7 @@ public EvalNode getChild(int idx) { return argEvals[idx]; } + public DataType getValueType() { return this.funcDesc.getReturnType(); } @@ -105,10 +110,6 @@ public String getName() { return funcDesc.getSignature(); } - public FunctionDesc getFuncDesc() { - return this.funcDesc; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -117,7 +118,7 @@ public String toString() { if(i+1 < argEvals.length) sb.append(","); } - return funcDesc.getSignature() + "(" + (isDistinct() ? " distinct" : "") + sb+")"; + return funcDesc.getSignature() + "(" + (isDistinct() ? " distinct " : "") + sb+")"; } @Override diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/LikePredicateEval.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/LikePredicateEval.java index bfac33ea98..535677f771 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/LikePredicateEval.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/LikePredicateEval.java @@ -25,6 +25,10 @@ public class LikePredicateEval extends PatternMatchPredicateEval { + public LikePredicateEval(boolean not, EvalNode field, ConstEval pattern) { + super(EvalType.LIKE, not, field, pattern, false); + } + public LikePredicateEval(boolean not, EvalNode field, ConstEval pattern, boolean caseSensitive) { super(EvalType.LIKE, not, field, pattern, caseSensitive); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/PatternMatchPredicateEval.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/PatternMatchPredicateEval.java index 8d78b0b139..0a8e800c5c 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/PatternMatchPredicateEval.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/PatternMatchPredicateEval.java @@ -55,6 +55,14 @@ public PatternMatchPredicateEval(EvalType evalType, boolean not, EvalNode field, abstract void compile(String pattern) throws PatternSyntaxException; + public boolean isNot() { + return not; + } + + public boolean isCaseInsensitive() { + return caseInsensitive; + } + @Override public DataType getValueType() { return RES_TYPE; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimilarToPredicateEval.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimilarToPredicateEval.java index 9ac0e62f20..a690759647 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimilarToPredicateEval.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimilarToPredicateEval.java @@ -24,6 +24,11 @@ public class SimilarToPredicateEval extends PatternMatchPredicateEval { private static final String SIMILARTO_ESCAPE_SPATIAL_CHARACTERS = "([.])"; + public SimilarToPredicateEval(boolean not, EvalNode field, ConstEval pattern, + @SuppressWarnings("unused") boolean isCaseSensitive) { + super(EvalType.SIMILAR_TO, not, field, pattern, false); + } + public SimilarToPredicateEval(boolean not, EvalNode field, ConstEval pattern) { super(EvalType.SIMILAR_TO, not, field, pattern); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimpleEvalNodeVisitor.java b/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimpleEvalNodeVisitor.java index 15e34de6fb..15b628bb01 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimpleEvalNodeVisitor.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/eval/SimpleEvalNodeVisitor.java @@ -60,9 +60,6 @@ public EvalNode visit(CONTEXT context, EvalNode evalNode, Stack stack) case IF_THEN: result = visitIfThen(context, (CaseWhenEval.IfThenEval) evalNode, stack); break; - case IN: - result = visitInPredicate(context, (InEval) evalNode, stack); - break; // Functions case FUNCTION: diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceBoolean.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceBoolean.java index 8c714c5eff..05640c9581 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceBoolean.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceBoolean.java @@ -32,12 +32,12 @@ example = "> SELECT coalesce(null, null, true);\n" + "true", returnType = Type.BOOLEAN, - paramTypes = {@ParamTypes(paramTypes = {Type.BOOLEAN, TajoDataTypes.Type.BOOLEAN_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.BOOLEAN, TajoDataTypes.Type.BOOLEAN_ARRAY})} ) public class CoalesceBoolean extends Coalesce { public CoalesceBoolean() { super(new Column[] { - new Column("column", TajoDataTypes.Type.BOOLEAN), + new Column("param", TajoDataTypes.Type.BOOLEAN), new Column("params", TajoDataTypes.Type.BOOLEAN_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDate.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDate.java index 23f8f0c545..35df518077 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDate.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDate.java @@ -32,12 +32,11 @@ example = "> SELECT coalesce(null, null, date '2014-01-01');\n" + "2014-01-01", returnType = Type.DATE, - paramTypes = {@ParamTypes(paramTypes = {Type.DATE, Type.DATE_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {Type.DATE_ARRAY})} ) public class CoalesceDate extends Coalesce { public CoalesceDate() { super(new Column[] { - new Column("column", TajoDataTypes.Type.DATE), new Column("params", TajoDataTypes.Type.DATE_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDouble.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDouble.java index 3e94150a45..47c363f11c 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDouble.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceDouble.java @@ -31,12 +31,11 @@ example = "> SELECT coalesce(null, null, 10.0);\n" + "10.0", returnType = TajoDataTypes.Type.FLOAT8, - paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.FLOAT8, TajoDataTypes.Type.FLOAT8_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.FLOAT8_ARRAY})} ) public class CoalesceDouble extends Coalesce { public CoalesceDouble() { super(new Column[] { - new Column("column", TajoDataTypes.Type.FLOAT8), new Column("params", TajoDataTypes.Type.FLOAT8_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceLong.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceLong.java index 5d552550c8..f975615aa5 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceLong.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceLong.java @@ -31,13 +31,12 @@ example = "> SELECT coalesce(null, null, 10);\n" + "10", returnType = TajoDataTypes.Type.INT8, - paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.INT8, TajoDataTypes.Type.INT8_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.INT8_ARRAY})} ) public class CoalesceLong extends Coalesce { public CoalesceLong() { super(new Column[] { - new Column("column", TajoDataTypes.Type.INT8), new Column("params", TajoDataTypes.Type.INT8_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceString.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceString.java index 50e4786512..6441e00521 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceString.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceString.java @@ -31,13 +31,12 @@ example = "> SELECT coalesce(null, null, 'default');\n" + "default", returnType = TajoDataTypes.Type.TEXT, - paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.TEXT, TajoDataTypes.Type.TEXT_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {TajoDataTypes.Type.TEXT_ARRAY})} ) public class CoalesceString extends Coalesce { public CoalesceString() { super(new Column[] { - new Column("column", TajoDataTypes.Type.TEXT), new Column("params", TajoDataTypes.Type.TEXT_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTime.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTime.java index 01bb6ded5f..56cfe32b01 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTime.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTime.java @@ -32,12 +32,11 @@ example = "> SELECT coalesce(null, null, time '12:10:00');\n" + "12:10:00", returnType = Type.TIME, - paramTypes = {@ParamTypes(paramTypes = {Type.TIME, Type.TIME_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {Type.TIME_ARRAY})} ) public class CoalesceTime extends Coalesce { public CoalesceTime() { super(new Column[] { - new Column("column", TajoDataTypes.Type.TIME), new Column("params", TajoDataTypes.Type.TIME_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTimestamp.java b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTimestamp.java index 2609717ec9..ec02e46123 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTimestamp.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/function/builtin/CoalesceTimestamp.java @@ -32,12 +32,11 @@ example = "> SELECT coalesce(null, null, timestamp '2014-01-01');\n" + "2014-01-01 00:00:00", returnType = Type.TIMESTAMP, - paramTypes = {@ParamTypes(paramTypes = {Type.TIMESTAMP, Type.TIMESTAMP_ARRAY})} + paramTypes = {@ParamTypes(paramTypes = {Type.TIMESTAMP_ARRAY})} ) public class CoalesceTimestamp extends Coalesce { public CoalesceTimestamp() { super(new Column[] { - new Column("column", TajoDataTypes.Type.TIMESTAMP), new Column("params", TajoDataTypes.Type.TIMESTAMP_ARRAY), }); } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java index 6a3af98b09..574e32c3d4 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/ExprAnnotator.java @@ -439,11 +439,18 @@ public EvalNode visitRegexpPredicate(Context ctx, Stack stack, PatternMatc @Override public EvalNode visitConcatenate(Context ctx, Stack stack, BinaryOperator expr) throws PlanningException { stack.push(expr); - EvalNode left = visit(ctx, stack, expr.getLeft()); - EvalNode right = visit(ctx, stack, expr.getRight()); + EvalNode lhs = visit(ctx, stack, expr.getLeft()); + EvalNode rhs = visit(ctx, stack, expr.getRight()); stack.pop(); - return new BinaryEval(EvalType.CONCATENATE, left, right); + if (lhs.getValueType().getType() != Type.TEXT) { + lhs = convertType(lhs, CatalogUtil.newSimpleDataType(Type.TEXT)); + } + if (rhs.getValueType().getType() != Type.TEXT) { + rhs = convertType(rhs, CatalogUtil.newSimpleDataType(Type.TEXT)); + } + + return new BinaryEval(EvalType.CONCATENATE, lhs, rhs); } private EvalNode visitPatternMatchPredicate(Context ctx, Stack stack, PatternMatchPredicate expr) @@ -599,7 +606,7 @@ public EvalNode visitFunction(Context ctx, Stack stack, FunctionExpr expr) // trying the implicit type conversion between actual parameter types and the definition types. if (CatalogUtil.checkIfVariableLengthParamDefinition(TUtil.newList(funcDesc.getParamTypes()))) { - DataType lastDataType = null; + DataType lastDataType = funcDesc.getParamTypes()[0]; for (int i = 0; i < givenArgs.length; i++) { if (i < (funcDesc.getParamTypes().length - 1)) { // variable length lastDataType = funcDesc.getParamTypes()[i]; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java index e34548ce54..2730202444 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java @@ -55,6 +55,7 @@ import org.apache.tajo.worker.TaskAttemptContext; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Stack; @@ -147,6 +148,7 @@ private PhysicalExec createPlanRecursive(TaskAttemptContext ctx, LogicalNode log stack.push(selNode); leftExec = createPlanRecursive(ctx, selNode.getChild(), stack); stack.pop(); + return new SelectionExec(ctx, selNode, leftExec); case PROJECTION: @@ -154,6 +156,7 @@ private PhysicalExec createPlanRecursive(TaskAttemptContext ctx, LogicalNode log stack.push(prjNode); leftExec = createPlanRecursive(ctx, prjNode.getChild(), stack); stack.pop(); + return new ProjectionExec(ctx, prjNode, leftExec); case TABLE_SUBQUERY: { @@ -210,6 +213,7 @@ private PhysicalExec createPlanRecursive(TaskAttemptContext ctx, LogicalNode log leftExec = createPlanRecursive(ctx, joinNode.getLeftChild(), stack); rightExec = createPlanRecursive(ctx, joinNode.getRightChild(), stack); stack.pop(); + return createJoinPlan(ctx, joinNode, leftExec, rightExec); case UNION: diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/Projector.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/Projector.java index 161d39b365..d8499d0696 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/Projector.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/Projector.java @@ -18,34 +18,49 @@ package org.apache.tajo.engine.planner; +import org.apache.tajo.SessionVars; import org.apache.tajo.catalog.Schema; import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.storage.Tuple; +import org.apache.tajo.worker.TaskAttemptContext; public class Projector { + private final TaskAttemptContext context; private final Schema inSchema; + private final Target [] targets; // for projection private final int targetNum; private final EvalNode[] evals; - public Projector(Schema inSchema, Schema outSchema, Target [] targets) { + public Projector(TaskAttemptContext context, Schema inSchema, Schema outSchema, Target [] targets) { + this.context = context; this.inSchema = inSchema; if (targets == null) { - targets = PlannerUtil.schemaToTargets(outSchema); + this.targets = PlannerUtil.schemaToTargets(outSchema); + } else { + this.targets = targets; } - this.targetNum = targets.length; + + this.targetNum = this.targets.length; evals = new EvalNode[targetNum]; - for (int i = 0; i < targetNum; i++) { - evals[i] = targets[i].getEvalTree(); + + if (context.getQueryContext().getBool(SessionVars.CODEGEN)) { + EvalNode eval; + for (int i = 0; i < targetNum; i++) { + eval = this.targets[i].getEvalTree(); + evals[i] = context.getPrecompiledEval(inSchema, eval); + } + } else { + for (int i = 0; i < targetNum; i++) { + evals[i] = this.targets[i].getEvalTree(); + } } } public void eval(Tuple in, Tuple out) { - if (targetNum > 0) { - for (int i = 0; i < evals.length; i++) { - out.put(i, evals[i].eval(inSchema, in)); - } + for (int i = 0; i < evals.length; i++) { + out.put(i, evals[i].eval(inSchema, in)); } } } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/HavingNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/HavingNode.java index 6c45868d40..aa6d597abc 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/HavingNode.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/HavingNode.java @@ -22,21 +22,26 @@ import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.planner.PlanString; -public class HavingNode extends UnaryNode implements Cloneable { +public class HavingNode extends UnaryNode implements SelectableNode, Cloneable { @Expose private EvalNode qual; public HavingNode(int pid) { super(pid, NodeType.HAVING); } - public EvalNode getQual() { - return this.qual; - } + @Override + public boolean hasQual() { + return true; + } - public void setQual(EvalNode qual) { + public void setQual(EvalNode qual) { this.qual = qual; } + public EvalNode getQual() { + return this.qual; + } + @Override public boolean equals(Object obj) { if (obj instanceof HavingNode) { diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/ScanNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/ScanNode.java index 9582deec26..8d28e6e83d 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/ScanNode.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/ScanNode.java @@ -30,7 +30,7 @@ import org.apache.tajo.engine.utils.SchemaUtil; import org.apache.tajo.util.TUtil; -public class ScanNode extends RelationNode implements Projectable, Cloneable { +public class ScanNode extends RelationNode implements Projectable, SelectableNode, Cloneable { @Expose protected TableDesc tableDesc; @Expose protected String alias; @Expose protected Schema logicalSchema; @@ -101,6 +101,7 @@ public String getCanonicalName() { } } + @Override public Schema getTableSchema() { return logicalSchema; } @@ -108,15 +109,18 @@ public Schema getTableSchema() { public Schema getPhysicalSchema() { return getInSchema(); } - + + @Override public boolean hasQual() { return qual != null; } - + + @Override public EvalNode getQual() { return this.qual; } - + + @Override public void setQual(EvalNode evalTree) { this.qual = evalTree; } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectableNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectableNode.java new file mode 100644 index 0000000000..7082f4bca4 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectableNode.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.planner.logical; + +import org.apache.tajo.engine.eval.EvalNode; + +/** + * An interface for logical node which is able to filter tuples. + */ +public interface SelectableNode { + + /** + * Checking if it has filter condition + * + * @return True if it has filter condition. Otherwise, it will return false. + */ + public boolean hasQual(); + + /** + * Set a filter condition. + * + * @param eval EvalNode resulting in a boolean result. + */ + public void setQual(EvalNode eval); + + /** + * Get a filter condition + * + * @return Filter Condition + */ + public EvalNode getQual(); +} diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectionNode.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectionNode.java index b8a96801e6..3bbbd82691 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectionNode.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/logical/SelectionNode.java @@ -22,21 +22,26 @@ import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.planner.PlanString; -public class SelectionNode extends UnaryNode implements Cloneable { +public class SelectionNode extends UnaryNode implements SelectableNode, Cloneable { @Expose private EvalNode qual; public SelectionNode(int pid) { super(pid, NodeType.SELECTION); } - public EvalNode getQual() { - return this.qual; - } + @Override + public boolean hasQual() { + return true; + } - public void setQual(EvalNode qual) { + public void setQual(EvalNode qual) { this.qual = qual; } + public EvalNode getQual() { + return this.qual; + } + @Override public PlanString getPlanString() { PlanString planStr = new PlanString(this); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BNLJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BNLJoinExec.java index 60a7c1979f..91cefa15e3 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BNLJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BNLJoinExec.java @@ -79,13 +79,20 @@ public BNLJoinExec(final TaskAttemptContext context, final JoinNode plan, plan.setTargets(PlannerUtil.schemaToTargets(outSchema)); } - projector = new Projector(inSchema, outSchema, plan.getTargets()); + projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); outputTuple = new VTuple(outSchema.size()); } + @Override + protected void compile() { + if (hasJoinQual) { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + } + public JoinNode getPlan() { return plan; } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java index 35de7076b2..f831525583 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java @@ -59,7 +59,7 @@ public BSTIndexScanExec(TaskAttemptContext context, this.fileScanner = StorageManagerFactory.getSeekableScanner(context.getConf(), scanNode.getTableDesc().getMeta(), scanNode.getInSchema(), fragment, outSchema); this.fileScanner.init(); - this.projector = new Projector(inSchema, outSchema, scanNode.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, scanNode.getTargets()); this.reader = new BSTIndex(sm.getFileSystem().getConf()). getIndexReader(fileName, keySchema, comparator); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BasicPhysicalExecutorVisitor.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BasicPhysicalExecutorVisitor.java index f6f3e52e37..42611b05bd 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BasicPhysicalExecutorVisitor.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BasicPhysicalExecutorVisitor.java @@ -18,6 +18,8 @@ package org.apache.tajo.engine.planner.physical; +import org.apache.tajo.engine.planner.PhysicalPlanningException; + import java.util.Stack; public class BasicPhysicalExecutorVisitor implements PhysicalExecutorVisitor { diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BinaryPhysicalExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BinaryPhysicalExec.java index 628c18cd70..03ec396314 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BinaryPhysicalExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BinaryPhysicalExec.java @@ -52,6 +52,8 @@ public void init() throws IOException { leftChild.init(); rightChild.init(); progress = 0.0f; + + super.init(); } @Override diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ExternalSortExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ExternalSortExec.java index 621552794b..700e34daa0 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ExternalSortExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ExternalSortExec.java @@ -34,6 +34,7 @@ import org.apache.tajo.catalog.proto.CatalogProtos.StoreType; import org.apache.tajo.catalog.statistics.TableStats; import org.apache.tajo.conf.TajoConf.ConfVars; +import org.apache.tajo.engine.planner.PhysicalPlanningException; import org.apache.tajo.engine.planner.logical.SortNode; import org.apache.tajo.storage.*; import org.apache.tajo.storage.Scanner; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashFullOuterJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashFullOuterJoinExec.java index 65ebe2fd26..9dabbb3edf 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashFullOuterJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashFullOuterJoinExec.java @@ -19,6 +19,7 @@ package org.apache.tajo.engine.planner.physical; import org.apache.tajo.catalog.Column; +import org.apache.tajo.engine.codegen.CompilationError; import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.planner.PlannerUtil; import org.apache.tajo.engine.planner.Projector; @@ -91,7 +92,7 @@ public HashFullOuterJoinExec(TaskAttemptContext context, JoinNode plan, Physical } // for projection - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); @@ -102,6 +103,11 @@ public HashFullOuterJoinExec(TaskAttemptContext context, JoinNode plan, Physical rightNumCols = inner.getSchema().size(); } + @Override + protected void compile() throws CompilationError { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + protected void getKeyLeftTuple(final Tuple outerTuple, Tuple keyTuple) { for (int i = 0; i < leftKeyList.length; i++) { keyTuple.put(i, outerTuple.get(leftKeyList[i])); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashJoinExec.java index a5e9df0b45..426a7a19e2 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashJoinExec.java @@ -82,7 +82,7 @@ public HashJoinExec(TaskAttemptContext context, JoinNode plan, PhysicalExec left } // for projection - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); @@ -90,6 +90,11 @@ public HashJoinExec(TaskAttemptContext context, JoinNode plan, PhysicalExec left leftKeyTuple = new VTuple(leftKeyList.length); } + @Override + protected void compile() { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + protected void getKeyLeftTuple(final Tuple outerTuple, Tuple keyTuple) { for (int i = 0; i < leftKeyList.length; i++) { keyTuple.put(i, outerTuple.get(leftKeyList[i])); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftOuterJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftOuterJoinExec.java index ac8b28fb05..b752db5c23 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftOuterJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftOuterJoinExec.java @@ -109,7 +109,7 @@ public HashLeftOuterJoinExec(TaskAttemptContext context, JoinNode plan, Physical } // for projection - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); @@ -119,6 +119,11 @@ public HashLeftOuterJoinExec(TaskAttemptContext context, JoinNode plan, Physical rightNumCols = rightChild.getSchema().size(); } + @Override + protected void compile() { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + protected void getKeyLeftTuple(final Tuple outerTuple, Tuple keyTuple) { for (int i = 0; i < leftKeyList.length; i++) { keyTuple.put(i, outerTuple.get(leftKeyList[i])); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftSemiJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftSemiJoinExec.java index 4fbb5e42c3..4fdd03ae62 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftSemiJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/HashLeftSemiJoinExec.java @@ -45,6 +45,10 @@ public HashLeftSemiJoinExec(TaskAttemptContext context, JoinNode plan, PhysicalE } } + protected void compile() { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + /** * The End of Tuple (EOT) condition is true only when no more tuple in the left relation (on disk). * next() method finds the first unmatched tuple from both tables. diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeFullOuterJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeFullOuterJoinExec.java index ff1f7b3ff3..e1cc6a8359 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeFullOuterJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeFullOuterJoinExec.java @@ -88,7 +88,7 @@ public MergeFullOuterJoinExec(TaskAttemptContext context, JoinNode plan, Physica plan.getJoinQual(), leftChild.getSchema(), rightChild.getSchema()); // for projection - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); @@ -98,6 +98,11 @@ public MergeFullOuterJoinExec(TaskAttemptContext context, JoinNode plan, Physica rightNumCols = rightChild.getSchema().size(); } + @Override + protected void compile() { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + public JoinNode getPlan(){ return this.joinNode; } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeJoinExec.java index 470e1c9756..bbfe973860 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/MergeJoinExec.java @@ -84,13 +84,18 @@ public MergeJoinExec(TaskAttemptContext context, JoinNode plan, PhysicalExec out this.innerIterator = innerTupleSlots.iterator(); // for projection - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); outTuple = new VTuple(outSchema.size()); } + @Override + protected void compile() { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + public JoinNode getPlan(){ return this.joinNode; } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLJoinExec.java index 6e5900e0eb..dc061ed935 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLJoinExec.java @@ -54,7 +54,7 @@ public NLJoinExec(TaskAttemptContext context, JoinNode plan, PhysicalExec outer, } // for projection - projector = new Projector(inSchema, outSchema, plan.getTargets()); + projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join needNewOuter = true; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLLeftOuterJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLLeftOuterJoinExec.java index 5c17c40903..37ef7dfed8 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLLeftOuterJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/NLLeftOuterJoinExec.java @@ -57,7 +57,7 @@ public NLLeftOuterJoinExec(TaskAttemptContext context, JoinNode plan, PhysicalEx } // for projection - projector = new Projector(inSchema, outSchema, plan.getTargets()); + projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join needNextRightTuple = true; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PartitionMergeScanExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PartitionMergeScanExec.java index 9fa5b76a53..d87a30debc 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PartitionMergeScanExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PartitionMergeScanExec.java @@ -62,8 +62,9 @@ public PartitionMergeScanExec(TaskAttemptContext context, AbstractStorageManager public void init() throws IOException { for (CatalogProtos.FragmentProto fragment : fragments) { - scanners.add(new SeqScanExec(context, sm, (ScanNode) PlannerUtil.clone(null, plan), - new CatalogProtos.FragmentProto[] {fragment})); + SeqScanExec scanExec = new SeqScanExec(context, sm, (ScanNode) PlannerUtil.clone(null, plan), + new CatalogProtos.FragmentProto[] {fragment}); + scanners.add(scanExec); } progress = 0.0f; rescan(); diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExec.java index e30a10bb9a..31cfc4d359 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExec.java @@ -20,9 +20,11 @@ import org.apache.commons.logging.Log; import org.apache.hadoop.fs.Path; +import org.apache.tajo.SessionVars; import org.apache.tajo.catalog.Schema; import org.apache.tajo.catalog.SchemaObject; import org.apache.tajo.catalog.statistics.TableStats; +import org.apache.tajo.engine.codegen.CompilationError; import org.apache.tajo.storage.Tuple; import org.apache.tajo.worker.TaskAttemptContext; @@ -47,7 +49,14 @@ public final Schema getSchema() { return outSchema; } - public abstract void init() throws IOException; + public void init() throws IOException { + if (context.getQueryContext().getBool(SessionVars.CODEGEN)) { + this.compile(); + } + } + + protected void compile() throws CompilationError { + } public abstract Tuple next() throws IOException; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExecutorVisitor.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExecutorVisitor.java index 738db62efa..505b599ad0 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExecutorVisitor.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalExecutorVisitor.java @@ -18,6 +18,8 @@ package org.apache.tajo.engine.planner.physical; +import org.apache.tajo.engine.planner.PhysicalPlanningException; + import java.util.Stack; public interface PhysicalExecutorVisitor { diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanUtil.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanUtil.java index 0909c76f8e..2f55cf7901 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanUtil.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/PhysicalPlanUtil.java @@ -20,6 +20,7 @@ import org.apache.tajo.SessionVars; import org.apache.tajo.catalog.TableMeta; +import org.apache.tajo.engine.planner.PhysicalPlanningException; import org.apache.tajo.engine.planner.logical.NodeType; import org.apache.tajo.engine.planner.logical.PersistentStoreNode; import org.apache.tajo.engine.query.QueryContext; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java index ee6ef1da94..89cd75af94 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java @@ -46,7 +46,7 @@ public void init() throws IOException { super.init(); this.outTuple = new VTuple(outSchema.size()); - this.projector = new Projector(inSchema, outSchema, this.plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, this.plan.getTargets()); } @Override diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/RightOuterMergeJoinExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/RightOuterMergeJoinExec.java index 365fc228c8..5d4dad5b55 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/RightOuterMergeJoinExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/RightOuterMergeJoinExec.java @@ -87,7 +87,7 @@ public RightOuterMergeJoinExec(TaskAttemptContext context, JoinNode plan, Physic plan.getJoinQual(), outer.getSchema(), inner.getSchema()); // for projection - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); // for join frameTuple = new FrameTuple(); @@ -96,6 +96,11 @@ public RightOuterMergeJoinExec(TaskAttemptContext context, JoinNode plan, Physic leftNumCols = outer.getSchema().size(); } + @Override + protected void compile() { + joinQual = context.getPrecompiledEval(inSchema, joinQual); + } + public JoinNode getPlan() { return this.joinNode; } diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SelectionExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SelectionExec.java index 2e676e9b29..5ae9a8f289 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SelectionExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SelectionExec.java @@ -18,6 +18,7 @@ package org.apache.tajo.engine.planner.physical; +import org.apache.tajo.engine.codegen.CompilationError; import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.planner.logical.SelectionNode; import org.apache.tajo.storage.Tuple; @@ -26,7 +27,7 @@ import java.io.IOException; public class SelectionExec extends UnaryPhysicalExec { - private final EvalNode qual; + private EvalNode qual; public SelectionExec(TaskAttemptContext context, SelectionNode plan, @@ -35,6 +36,11 @@ public SelectionExec(TaskAttemptContext context, this.qual = plan.getQual(); } + @Override + public void compile() throws CompilationError { + qual = context.getPrecompiledEval(inSchema, qual); + } + @Override public Tuple next() throws IOException { Tuple tuple; diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SeqScanExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SeqScanExec.java index 507cb6c2a1..2f0c12fc7f 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SeqScanExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/SeqScanExec.java @@ -26,6 +26,7 @@ import org.apache.tajo.catalog.proto.CatalogProtos.FragmentProto; import org.apache.tajo.catalog.statistics.TableStats; import org.apache.tajo.datum.Datum; +import org.apache.tajo.engine.codegen.CompilationError; import org.apache.tajo.engine.eval.ConstEval; import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.eval.EvalTreeUtil; @@ -66,8 +67,8 @@ public class SeqScanExec extends PhysicalExec { private boolean cacheRead = false; - public SeqScanExec(TaskAttemptContext context, AbstractStorageManager sm, - ScanNode plan, CatalogProtos.FragmentProto [] fragments) throws IOException { + public SeqScanExec(TaskAttemptContext context, AbstractStorageManager sm, ScanNode plan, + CatalogProtos.FragmentProto [] fragments) throws IOException { super(context, plan.getInSchema(), plan.getOutSchema()); this.plan = plan; @@ -87,6 +88,12 @@ public SeqScanExec(TaskAttemptContext context, AbstractStorageManager sm, cacheKey = new TupleCacheKey( context.getTaskId().getQueryUnitId().getExecutionBlockId().toString(), plan.getTableName(), pathNameKey); } + + if (fragments != null + && plan.getTableDesc().hasPartition() + && plan.getTableDesc().getPartitionMethod().getPartitionType() == CatalogProtos.PartitionType.COLUMN) { + rewriteColumnPartitionedTableSchema(); + } } /** @@ -120,12 +127,16 @@ private void rewriteColumnPartitionedTableSchema() throws IOException { Datum datum = targetExpr.eval(columnPartitionSchema, partitionRow); ConstEval constExpr = new ConstEval(datum); - for (Target target : plan.getTargets()) { + + for (int i = 0; i < plan.getTargets().length; i++) { + Target target = plan.getTargets()[i]; + if (target.getEvalTree().equals(targetExpr)) { if (!target.hasAlias()) { target.setAlias(target.getEvalTree().getName()); } target.setExpr(constExpr); + } else { EvalTreeUtil.replace(target.getEvalTree(), targetExpr, constExpr); } @@ -140,12 +151,6 @@ private void rewriteColumnPartitionedTableSchema() throws IOException { public void init() throws IOException { Schema projected; - if (fragments != null - && plan.getTableDesc().hasPartition() - && plan.getTableDesc().getPartitionMethod().getPartitionType() == CatalogProtos.PartitionType.COLUMN) { - rewriteColumnPartitionedTableSchema(); - } - if (plan.hasTargets()) { projected = new Schema(); Set columnSet = new HashSet(); @@ -193,10 +198,19 @@ public void init() throws IOException { } else { initScanner(projected); } + + super.init(); + } + + @Override + protected void compile() throws CompilationError { + if (plan.hasQual()) { + qual = context.getPrecompiledEval(inSchema, qual); + } } private void initScanner(Schema projected) throws IOException { - this.projector = new Projector(inSchema, outSchema, plan.getTargets()); + this.projector = new Projector(context, inSchema, outSchema, plan.getTargets()); if (fragments != null) { if (fragments.length > 1) { this.scanner = new MergeScanner(context.getConf(), plan.getPhysicalSchema(), plan.getTableDesc().getMeta(), diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/UnaryPhysicalExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/UnaryPhysicalExec.java index ab67d7ba42..2c63ea6f52 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/UnaryPhysicalExec.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/UnaryPhysicalExec.java @@ -46,13 +46,17 @@ public void setChild(PhysicalExec child){ this.child = child; } + @Override public void init() throws IOException { progress = 0.0f; if (child != null) { child.init(); } + + super.init(); } + @Override public void rescan() throws IOException { progress = 0.0f; if (child != null) { @@ -60,6 +64,7 @@ public void rescan() throws IOException { } } + @Override public void close() throws IOException { progress = 1.0f; if (child != null) { diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/query/QueryUnitRequestImpl.java b/tajo-core/src/main/java/org/apache/tajo/engine/query/QueryUnitRequestImpl.java index 56df48d557..ef82427b43 100644 --- a/tajo-core/src/main/java/org/apache/tajo/engine/query/QueryUnitRequestImpl.java +++ b/tajo-core/src/main/java/org/apache/tajo/engine/query/QueryUnitRequestImpl.java @@ -161,7 +161,7 @@ public String getSerializedData() { this.serializedData = p.getSerializedData(); return this.serializedData; } - + public boolean isInterQuery() { QueryUnitRequestProtoOrBuilder p = viaProto ? proto : builder; if (interQuery != null) { diff --git a/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java b/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java index fc1be9b5b7..2c62d42861 100644 --- a/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java +++ b/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java @@ -60,6 +60,7 @@ import org.apache.tajo.master.querymaster.QueryMasterTask; import org.apache.tajo.master.session.Session; import org.apache.tajo.storage.*; +import org.apache.tajo.util.CommonTestingUtil; import org.apache.tajo.worker.TaskAttemptContext; import java.io.IOException; @@ -115,9 +116,20 @@ public void stop() { super.stop(); } + private QueryContext createQueryContext(Session session) { + QueryContext newQueryContext = new QueryContext(context.getConf(), session); + + String tajoTest = System.getProperty(CommonTestingUtil.TAJO_TEST_KEY); + if (tajoTest != null && tajoTest.equalsIgnoreCase(CommonTestingUtil.TAJO_TEST_TRUE)) { + newQueryContext.putAll(CommonTestingUtil.getSessionVarsForTest()); + } + + return newQueryContext; + } + public SubmitQueryResponse executeQuery(Session session, String query, boolean isJson) { LOG.info("Query: " + query); - QueryContext queryContext = new QueryContext(context.getConf(), session); + QueryContext queryContext = createQueryContext(session); Expr planningContext; try { diff --git a/tajo-core/src/main/java/org/apache/tajo/master/LaunchTaskRunnersEvent.java b/tajo-core/src/main/java/org/apache/tajo/master/LaunchTaskRunnersEvent.java new file mode 100644 index 0000000000..9a4a01d922 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/master/LaunchTaskRunnersEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.master; + +import org.apache.hadoop.yarn.api.records.Container; +import org.apache.tajo.ExecutionBlockId; +import org.apache.tajo.engine.query.QueryContext; + +import java.util.Collection; + +public class LaunchTaskRunnersEvent extends TaskRunnerGroupEvent { + private final QueryContext queryContext; + private final String planJson; + + public LaunchTaskRunnersEvent(ExecutionBlockId executionBlockId, + Collection containers, QueryContext queryContext, String planJson) { + super(EventType.CONTAINER_REMOTE_LAUNCH, executionBlockId, containers); + this.queryContext = queryContext; + this.planJson = planJson; + } + + public QueryContext getQueryContext() { + return queryContext; + } + + public String getPlanJson() { + return planJson; + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/master/TajoContainerProxy.java b/tajo-core/src/main/java/org/apache/tajo/master/TajoContainerProxy.java index cb7861c08b..f3e4b724d4 100644 --- a/tajo-core/src/main/java/org/apache/tajo/master/TajoContainerProxy.java +++ b/tajo-core/src/main/java/org/apache/tajo/master/TajoContainerProxy.java @@ -27,6 +27,7 @@ import org.apache.tajo.ExecutionBlockId; import org.apache.tajo.QueryUnitAttemptId; import org.apache.tajo.conf.TajoConf; +import org.apache.tajo.engine.query.QueryContext; import org.apache.tajo.ipc.TajoMasterProtocol; import org.apache.tajo.ipc.TajoWorkerProtocol; import org.apache.tajo.master.querymaster.QueryMasterTask; @@ -42,10 +43,15 @@ import java.util.List; public class TajoContainerProxy extends ContainerProxy { + private final QueryContext queryContext; + private final String planJson; + public TajoContainerProxy(QueryMasterTask.QueryMasterTaskContext context, Configuration conf, Container container, - ExecutionBlockId executionBlockId) { + QueryContext queryContext, ExecutionBlockId executionBlockId, String planJson) { super(context, conf, executionBlockId, container); + this.queryContext = queryContext; + this.planJson = planJson; } @Override @@ -101,6 +107,8 @@ private void assignExecutionBlock(ExecutionBlockId executionBlockId, Container c .setNodeId(container.getNodeId().toString()) .setContainerId(container.getId().toString()) .setQueryOutputPath(context.getStagingDir().toString()) + .setQueryContext(queryContext.getProto()) + .setPlanJson(planJson) .build(); tajoWorkerRpcClient.executeExecutionBlock(null, request, NullCallback.get()); diff --git a/tajo-core/src/main/java/org/apache/tajo/master/TajoMaster.java b/tajo-core/src/main/java/org/apache/tajo/master/TajoMaster.java index 4e51460bf7..8d4a6a3b7b 100644 --- a/tajo-core/src/main/java/org/apache/tajo/master/TajoMaster.java +++ b/tajo-core/src/main/java/org/apache/tajo/master/TajoMaster.java @@ -214,7 +214,7 @@ private void initResourceManager() throws Exception { } private void initWebServer() throws Exception { - if (!systemConf.get(CommonTestingUtil.TAJO_TEST, "FALSE").equalsIgnoreCase("TRUE")) { + if (!systemConf.get(CommonTestingUtil.TAJO_TEST_KEY, "FALSE").equalsIgnoreCase("TRUE")) { InetSocketAddress address = systemConf.getSocketAddrVar(ConfVars.TAJO_MASTER_INFO_ADDRESS); webServer = StaticHttpServer.getInstance(this ,"admin", address.getHostName(), address.getPort(), true, null, context.getConf(), null); diff --git a/tajo-core/src/main/java/org/apache/tajo/master/querymaster/QueryMaster.java b/tajo-core/src/main/java/org/apache/tajo/master/querymaster/QueryMaster.java index deadd39d3a..3a868020ff 100644 --- a/tajo-core/src/main/java/org/apache/tajo/master/querymaster/QueryMaster.java +++ b/tajo-core/src/main/java/org/apache/tajo/master/querymaster/QueryMaster.java @@ -190,7 +190,6 @@ protected void cleanupExecutionBlock(List ex builder.addAllExecutionBlockId(Lists.newArrayList(executionBlockIds)); TajoWorkerProtocol.ExecutionBlockListProto executionBlockListProto = builder.build(); - List intermediateEntries = new ArrayList(); for (TajoMasterProtocol.WorkerResourceProto worker : workers) { try { if (worker.getPeerRpcPort() == 0) continue; diff --git a/tajo-core/src/main/java/org/apache/tajo/master/querymaster/SubQuery.java b/tajo-core/src/main/java/org/apache/tajo/master/querymaster/SubQuery.java index 40c54065e2..87da175817 100644 --- a/tajo-core/src/main/java/org/apache/tajo/master/querymaster/SubQuery.java +++ b/tajo-core/src/main/java/org/apache/tajo/master/querymaster/SubQuery.java @@ -42,6 +42,8 @@ import org.apache.tajo.catalog.statistics.StatisticsUtil; import org.apache.tajo.catalog.statistics.TableStats; import org.apache.tajo.conf.TajoConf; +import org.apache.tajo.engine.json.CoreGsonHelper; +import org.apache.tajo.engine.plan.proto.PlanProto; import org.apache.tajo.engine.planner.PlannerUtil; import org.apache.tajo.engine.planner.global.DataChannel; import org.apache.tajo.engine.planner.global.ExecutionBlock; @@ -1034,8 +1036,9 @@ public void transition(SubQuery subQuery, SubQueryEvent event) { } LOG.info("SubQuery (" + subQuery.getId() + ") has " + subQuery.containers.size() + " containers!"); subQuery.eventHandler.handle( - new TaskRunnerGroupEvent(EventType.CONTAINER_REMOTE_LAUNCH, - subQuery.getId(), allocationEvent.getAllocatedContainer()) + new LaunchTaskRunnersEvent(subQuery.getId(), allocationEvent.getAllocatedContainer(), + subQuery.getContext().getQueryContext(), + CoreGsonHelper.toJson(subQuery.getBlock().getPlan(), LogicalNode.class)) ); subQuery.eventHandler.handle(new SubQueryEvent(subQuery.getId(), SubQueryEventType.SQ_START)); diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/ExecutionBlockSharedResource.java b/tajo-core/src/main/java/org/apache/tajo/worker/ExecutionBlockSharedResource.java new file mode 100644 index 0000000000..a70fbfd823 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/worker/ExecutionBlockSharedResource.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.worker; + +import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tajo.SessionVars; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.engine.codegen.ExecutorPreCompiler; +import org.apache.tajo.engine.codegen.TajoClassLoader; +import org.apache.tajo.engine.eval.EvalNode; +import org.apache.tajo.engine.json.CoreGsonHelper; +import org.apache.tajo.engine.planner.PlanningException; +import org.apache.tajo.engine.planner.logical.LogicalNode; +import org.apache.tajo.engine.query.QueryContext; +import org.apache.tajo.util.Pair; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ExecutionBlockSharedResource { + private static Log LOG = LogFactory.getLog(ExecutionBlockSharedResource.class); + private AtomicBoolean initializing = new AtomicBoolean(false); + private volatile Boolean resourceInitSuccess = new Boolean(false); + private CountDownLatch initializedResourceLatch = new CountDownLatch(1); + + // Query + private QueryContext context; + + // Resources + private TajoClassLoader classLoader; + private ExecutorPreCompiler.CompilationContext compilationContext; + private LogicalNode plan; + private boolean codeGenEnabled = false; + + public void initialize(final QueryContext context, final String planJson) throws InterruptedException { + + if (!initializing.getAndSet(true)) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + ExecutionBlockSharedResource.this.context = context; + initPlan(planJson); + initCodeGeneration(); + resourceInitSuccess = true; + } catch (Throwable t) { + LOG.error(t); + LOG.error(ExceptionUtils.getStackTrace(t)); + } finally { + initializedResourceLatch.countDown(); + } + } + }); + thread.run(); + thread.join(); + + if (!resourceInitSuccess) { + throw new RuntimeException("Resource cannot be initialized"); + } + } + } + + private void initPlan(String planJson) { + plan = CoreGsonHelper.fromJson(planJson, LogicalNode.class); + } + + private void initCodeGeneration() throws PlanningException { + if (context.getBool(SessionVars.CODEGEN)) { + codeGenEnabled = true; + classLoader = new TajoClassLoader(); + compilationContext = new ExecutorPreCompiler.CompilationContext(classLoader); + ExecutorPreCompiler.compile(compilationContext, plan); + } + } + + public boolean awaitInitializedResource() throws InterruptedException { + initializedResourceLatch.await(); + return resourceInitSuccess; + } + + public LogicalNode getPlan() { + return this.plan; + } + + public EvalNode compileEval(Schema schema, EvalNode eval) { + return compilationContext.getCompiler().compile(schema, eval); + } + + public EvalNode getPreCompiledEval(Schema schema, EvalNode eval) { + if (codeGenEnabled) { + + Pair key = new Pair(schema, eval); + if (compilationContext.getPrecompiedEvals().containsKey(key)) { + return compilationContext.getPrecompiedEvals().get(key); + } else { + try { + LOG.warn(eval.toString() + " does not exists. Immediately compile it: " + eval); + return compileEval(schema, eval); + } catch (Throwable t) { + LOG.warn(t); + return eval; + } + } + } else { + throw new IllegalStateException("CodeGen is disabled"); + } + } + + public void release() { + compilationContext = null; + + if (classLoader != null) { + try { + classLoader.clean(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + classLoader = null; + } + } +} diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TajoResourceAllocator.java b/tajo-core/src/main/java/org/apache/tajo/worker/TajoResourceAllocator.java index c7e513d8a4..aaff69c8f5 100644 --- a/tajo-core/src/main/java/org/apache/tajo/worker/TajoResourceAllocator.java +++ b/tajo-core/src/main/java/org/apache/tajo/worker/TajoResourceAllocator.java @@ -31,10 +31,7 @@ import org.apache.tajo.ExecutionBlockId; import org.apache.tajo.conf.TajoConf; import org.apache.tajo.ipc.TajoMasterProtocol; -import org.apache.tajo.master.ContainerProxy; -import org.apache.tajo.master.TajoContainerProxy; -import org.apache.tajo.master.TaskRunnerGroupEvent; -import org.apache.tajo.master.TaskRunnerLauncher; +import org.apache.tajo.master.*; import org.apache.tajo.master.event.ContainerAllocationEvent; import org.apache.tajo.master.event.ContainerAllocatorEventType; import org.apache.tajo.master.event.SubQueryContainerAllocationEvent; @@ -143,19 +140,20 @@ class TajoTaskRunnerLauncher implements TaskRunnerLauncher { @Override public void handle(TaskRunnerGroupEvent event) { if (event.getType() == TaskRunnerGroupEvent.EventType.CONTAINER_REMOTE_LAUNCH) { - launchTaskRunners(event.getExecutionBlockId(), event.getContainers()); + LaunchTaskRunnersEvent launchEvent = (LaunchTaskRunnersEvent) event; + launchTaskRunners(launchEvent); } else if (event.getType() == TaskRunnerGroupEvent.EventType.CONTAINER_REMOTE_CLEANUP) { stopContainers(event.getContainers()); } } } - private void launchTaskRunners(ExecutionBlockId executionBlockId, Collection containers) { + private void launchTaskRunners(LaunchTaskRunnersEvent event) { // Query in standby mode doesn't need launch Worker. // But, Assign ExecutionBlock to assigned tajo worker - for(Container eachContainer: containers) { + for(Container eachContainer: event.getContainers()) { TajoContainerProxy containerProxy = new TajoContainerProxy(queryTaskContext, tajoConf, - eachContainer, executionBlockId); + eachContainer, event.getQueryContext(), event.getExecutionBlockId(), event.getPlanJson()); executorService.submit(new LaunchRunner(eachContainer.getId(), containerProxy)); } } diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorker.java b/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorker.java index d8d09e1833..f76176d693 100644 --- a/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorker.java +++ b/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorker.java @@ -28,12 +28,14 @@ import org.apache.hadoop.fs.shell.PathData; import org.apache.hadoop.service.CompositeService; import org.apache.hadoop.yarn.util.RackResolver; +import org.apache.tajo.ExecutionBlockId; import org.apache.tajo.QueryId; import org.apache.tajo.TajoConstants; import org.apache.tajo.TajoProtos; import org.apache.tajo.catalog.CatalogClient; import org.apache.tajo.catalog.CatalogService; import org.apache.tajo.conf.TajoConf; +import org.apache.tajo.engine.query.QueryContext; import org.apache.tajo.ipc.TajoMasterProtocol; import org.apache.tajo.master.ha.TajoMasterInfo; import org.apache.tajo.master.querymaster.QueryMaster; @@ -55,6 +57,7 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -207,7 +210,7 @@ public void init(Configuration conf) { addService(pullService); } - if (!systemConf.get(CommonTestingUtil.TAJO_TEST, "FALSE").equalsIgnoreCase("TRUE")) { + if (!systemConf.get(CommonTestingUtil.TAJO_TEST_KEY, "FALSE").equalsIgnoreCase("TRUE")) { try { httpPort = systemConf.getSocketAddrVar(ConfVars.WORKER_INFO_ADDRESS).getPort(); if(queryMasterMode && !taskRunnerMode) { @@ -349,6 +352,9 @@ public void stop() { } public class WorkerContext { + private ConcurrentHashMap sharedResourceMap = + new ConcurrentHashMap(); + public QueryMaster getQueryMaster() { if(queryMasterManagerService == null) { return null; @@ -356,6 +362,10 @@ public QueryMaster getQueryMaster() { return queryMasterManagerService.getQueryMaster(); } + public TajoConf getConf() { + return systemConf; + } + public TajoWorkerManagerService getTajoWorkerManagerService() { return tajoWorkerManagerService; } @@ -398,6 +408,25 @@ public void stopWorker(boolean force) { } } + public void initSharedResource(QueryContext queryContext, ExecutionBlockId blockId, String planJson) + throws InterruptedException { + + if (!sharedResourceMap.containsKey(blockId)) { + ExecutionBlockSharedResource resource = new ExecutionBlockSharedResource(); + if (sharedResourceMap.putIfAbsent(blockId, resource) == null) { + resource.initialize(queryContext, planJson); + } + } + } + + public ExecutionBlockSharedResource getSharedResource(ExecutionBlockId blockId) { + return sharedResourceMap.get(blockId); + } + + public void releaseSharedResource(ExecutionBlockId blockId) { + sharedResourceMap.remove(blockId).release(); + } + protected void cleanup(String strPath) { if(deletionService == null) return; diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerManagerService.java b/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerManagerService.java index c5f14468cf..fa116c3695 100644 --- a/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerManagerService.java +++ b/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerManagerService.java @@ -25,16 +25,14 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.service.CompositeService; -import org.apache.tajo.ExecutionBlockId; -import org.apache.tajo.QueryId; -import org.apache.tajo.QueryUnitAttemptId; -import org.apache.tajo.TajoIdProtos; +import org.apache.tajo.*; import org.apache.tajo.conf.TajoConf; +import org.apache.tajo.engine.query.QueryContext; import org.apache.tajo.ipc.TajoWorkerProtocol; -import org.apache.tajo.ipc.TajoWorkerProtocol.ExecutionBlockReport; import org.apache.tajo.rpc.AsyncRpcServer; import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos; import org.apache.tajo.util.NetUtils; +import org.apache.tajo.util.TajoIdUtils; import java.net.InetSocketAddress; @@ -118,7 +116,12 @@ public void executeExecutionBlock(RpcController controller, TajoWorkerProtocol.RunExecutionBlockRequestProto request, RpcCallback done) { workerContext.getWorkerSystemMetrics().counter("query", "executedExecutionBlocksNum").inc(); + try { + workerContext.initSharedResource( + new QueryContext(workerContext.getConf(), request.getQueryContext()), + TajoIdUtils.createExecutionBlockId(request.getExecutionBlockId()), request.getPlanJson()); + String[] params = new String[7]; params[0] = "standby"; //mode(never used) params[1] = request.getExecutionBlockId(); @@ -132,8 +135,8 @@ public void executeExecutionBlock(RpcController controller, params[6] = request.getQueryOutputPath(); workerContext.getTaskRunnerManager().startTask(params); done.run(TajoWorker.TRUE_PROTO); - } catch (Exception e) { - LOG.error(e.getMessage(), e); + } catch (Throwable t) { + LOG.error(t.getMessage(), t); done.run(TajoWorker.FALSE_PROTO); } } @@ -163,6 +166,9 @@ public void cleanupExecutionBlocks(RpcController controller, workerContext.cleanup(inputDir); String outputDir = TaskRunner.getBaseOutputDir(new ExecutionBlockId(executionBlockIdProto)).toString(); workerContext.cleanup(outputDir); + + // Release shared resources + workerContext.releaseSharedResource(new ExecutionBlockId(executionBlockIdProto)); } done.run(TajoWorker.TRUE_PROTO); } diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/Task.java b/tajo-core/src/main/java/org/apache/tajo/worker/Task.java index 1881685dfa..d0665ae469 100644 --- a/tajo-core/src/main/java/org/apache/tajo/worker/Task.java +++ b/tajo-core/src/main/java/org/apache/tajo/worker/Task.java @@ -157,6 +157,20 @@ public Task(QueryUnitAttemptId taskId, this.reporter = new Reporter(taskId, masterProxy); this.reporter.startCommunicationThread(); + + // resource intiailization + boolean resourceInitialized = false; + try { + resourceInitialized = context.getSharedResource().awaitInitializedResource(); + } catch (InterruptedException e) { + LOG.error("Failed Resource Initialization", e); + } finally { + if (!resourceInitialized) { + setState(TaskAttemptState.TA_FAILED); + return; + } + } + plan = CoreGsonHelper.fromJson(request.getSerializedData(), LogicalNode.class); LogicalNode [] scanNode = PlannerUtil.findAllNodes(plan, NodeType.SCAN); if (scanNode != null) { @@ -218,25 +232,27 @@ public Task(QueryUnitAttemptId taskId, } public void init() throws IOException { - // initialize a task temporal dir - localFS.mkdirs(taskDir); - - if (request.getFetches().size() > 0) { - inputTableBaseDir = localFS.makeQualified( - lDirAllocator.getLocalPathForWrite( - getTaskAttemptDir(context.getTaskId()).toString(), systemConf)); - localFS.mkdirs(inputTableBaseDir); - Path tableDir; - for (String inputTable : context.getInputTables()) { - tableDir = new Path(inputTableBaseDir, inputTable); - if (!localFS.exists(tableDir)) { - LOG.info("the directory is created " + tableDir.toUri()); - localFS.mkdirs(tableDir); + if (context.getState() == TaskAttemptState.TA_PENDING) { + // initialize a task temporal dir + localFS.mkdirs(taskDir); + + if (request.getFetches().size() > 0) { + inputTableBaseDir = localFS.makeQualified( + lDirAllocator.getLocalPathForWrite( + getTaskAttemptDir(context.getTaskId()).toString(), systemConf)); + localFS.mkdirs(inputTableBaseDir); + Path tableDir; + for (String inputTable : context.getInputTables()) { + tableDir = new Path(inputTableBaseDir, inputTable); + if (!localFS.exists(tableDir)) { + LOG.info("the directory is created " + tableDir.toUri()); + localFS.mkdirs(tableDir); + } } } + // for localizing the intermediate data + localize(request); } - // for localizing the intermediate data - localize(request); } public QueryUnitAttemptId getTaskId() { @@ -426,14 +442,21 @@ public void run() { while(!killed && executor.next() != null) { } - this.executor.close(); - reloadInputStats(); - this.executor = null; } catch (Exception e) { error = e ; LOG.error(e.getMessage(), e); aborted = true; } finally { + if (executor != null) { + try { + executor.close(); + reloadInputStats(); + } catch (IOException e) { + e.printStackTrace(); + } + this.executor = null; + } + context.setProgress(1.0f); taskRunnerContext.completedTasksNum.incrementAndGet(); context.getHashShuffleAppenderManager().finalizeTask(taskId); diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java b/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java index 6cb4bd7bef..d27fd6ded4 100644 --- a/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java +++ b/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java @@ -26,8 +26,10 @@ import org.apache.hadoop.fs.Path; import org.apache.tajo.QueryUnitAttemptId; import org.apache.tajo.TajoProtos.TaskAttemptState; +import org.apache.tajo.catalog.Schema; import org.apache.tajo.catalog.statistics.TableStats; import org.apache.tajo.conf.TajoConf; +import org.apache.tajo.engine.eval.EvalNode; import org.apache.tajo.engine.planner.enforce.Enforcer; import org.apache.tajo.engine.planner.global.DataChannel; import org.apache.tajo.engine.query.QueryContext; @@ -75,16 +77,23 @@ public class TaskAttemptContext { private Enforcer enforcer; private QueryContext queryContext; private WorkerContext workerContext; + private ExecutionBlockSharedResource sharedResource; /** a output volume for each partition */ private Map partitionOutputVolume; private HashShuffleAppenderManager hashShuffleAppenderManager; - public TaskAttemptContext(QueryContext queryContext, final WorkerContext workerContext, final QueryUnitAttemptId queryId, + public TaskAttemptContext(QueryContext queryContext, final WorkerContext workerContext, + final QueryUnitAttemptId queryId, final FragmentProto[] fragments, final Path workDir) { this.queryContext = queryContext; - this.workerContext = workerContext; + + if (workerContext != null) { // For unit tests + this.workerContext = workerContext; + this.sharedResource = workerContext.getSharedResource(queryId.getQueryUnitId().getExecutionBlockId()); + } + this.queryId = queryId; if (fragments != null) { @@ -154,6 +163,23 @@ public Enforcer getEnforcer() { return this.enforcer; } + public ExecutionBlockSharedResource getSharedResource() { + return sharedResource; + } + + public EvalNode compileEval(Schema schema, EvalNode eval) { + return sharedResource.compileEval(schema, eval); + } + + public EvalNode getPrecompiledEval(Schema schema, EvalNode eval) { + if (sharedResource != null) { + return sharedResource.getPreCompiledEval(schema, eval); + } else { + LOG.debug("Shared resource is not initialized. It is NORMAL in unit tests"); + return eval; + } + } + public boolean hasResultStats() { return resultStats != null; } diff --git a/tajo-core/src/main/proto/TajoWorkerProtocol.proto b/tajo-core/src/main/proto/TajoWorkerProtocol.proto index cdb1438a23..e100c48125 100644 --- a/tajo-core/src/main/proto/TajoWorkerProtocol.proto +++ b/tajo-core/src/main/proto/TajoWorkerProtocol.proto @@ -207,6 +207,9 @@ message RunExecutionBlockRequestProto { required string nodeId = 4; required string containerId = 5; optional string queryOutputPath = 6; + + required KeyValueSetProto queryContext = 7; + required string planJson = 8; } message ExecutionBlockListProto { diff --git a/tajo-core/src/test/java/org/apache/tajo/LocalTajoTestingUtility.java b/tajo-core/src/test/java/org/apache/tajo/LocalTajoTestingUtility.java index 271ba702dd..4d9ca67cc7 100644 --- a/tajo-core/src/test/java/org/apache/tajo/LocalTajoTestingUtility.java +++ b/tajo-core/src/test/java/org/apache/tajo/LocalTajoTestingUtility.java @@ -33,6 +33,7 @@ import org.apache.tajo.engine.planner.global.MasterPlan; import org.apache.tajo.engine.query.QueryContext; import org.apache.tajo.master.session.Session; +import org.apache.tajo.util.CommonTestingUtil; import org.apache.tajo.util.KeyValueSet; import org.apache.tajo.util.TajoIdUtils; @@ -66,12 +67,15 @@ public static QueryUnitAttemptId newQueryUnitAttemptId() { public static QueryUnitAttemptId newQueryUnitAttemptId(MasterPlan plan) { return QueryIdFactory.newQueryUnitAttemptId(QueryIdFactory.newQueryUnitId(plan.newExecutionBlockId()), 0); } + public static Session createDummySession() { return new Session(UUID.randomUUID().toString(), dummyUserInfo.getUserName(), TajoConstants.DEFAULT_DATABASE_NAME); } public static QueryContext createDummyContext(TajoConf conf) { - return new QueryContext(conf, createDummySession()); + QueryContext context = new QueryContext(conf, createDummySession()); + context.putAll(CommonTestingUtil.getSessionVarsForTest().getAllKeyValus()); + return context; } /** diff --git a/tajo-core/src/test/java/org/apache/tajo/TajoTestingCluster.java b/tajo-core/src/test/java/org/apache/tajo/TajoTestingCluster.java index 7b87112426..346fa69043 100644 --- a/tajo-core/src/test/java/org/apache/tajo/TajoTestingCluster.java +++ b/tajo-core/src/test/java/org/apache/tajo/TajoTestingCluster.java @@ -92,9 +92,16 @@ public TajoTestingCluster() { public TajoTestingCluster(boolean masterHaEMode) { this.conf = new TajoConf(); this.conf.setBoolVar(ConfVars.TAJO_MASTER_HA_ENABLE, masterHaEMode); + + setTestingFlagProperties(); initPropertiesAndConfigs(); } + void setTestingFlagProperties() { + System.setProperty(CommonTestingUtil.TAJO_TEST_KEY, CommonTestingUtil.TAJO_TEST_TRUE); + conf.set(CommonTestingUtil.TAJO_TEST_KEY, CommonTestingUtil.TAJO_TEST_TRUE); + } + void initPropertiesAndConfigs() { if (System.getProperty(ConfVars.RESOURCE_MANAGER_CLASS.varname) != null) { String testResourceManager = System.getProperty(ConfVars.RESOURCE_MANAGER_CLASS.varname); @@ -106,7 +113,6 @@ void initPropertiesAndConfigs() { this.standbyWorkerMode = conf.getVar(ConfVars.RESOURCE_MANAGER_CLASS) .indexOf(TajoWorkerResourceManager.class.getName()) >= 0; - conf.set(CommonTestingUtil.TAJO_TEST, "TRUE"); } public TajoConf getConfiguration() { diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestEvalCodeGenerator.java b/tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestEvalCodeGenerator.java new file mode 100644 index 0000000000..d86081af8a --- /dev/null +++ b/tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestEvalCodeGenerator.java @@ -0,0 +1,311 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + + +import org.apache.tajo.catalog.CatalogUtil; +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.common.TajoDataTypes; +import org.apache.tajo.engine.eval.ExprTestBase; +import org.junit.Test; + +import java.io.IOException; + +public class TestEvalCodeGenerator extends ExprTestBase { + private static Schema schema; + static { + schema = new Schema(); + schema.addColumn("col0", TajoDataTypes.Type.INT1); + schema.addColumn("col1", TajoDataTypes.Type.INT2); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + schema.addColumn("col3", TajoDataTypes.Type.INT8); + schema.addColumn("col4", TajoDataTypes.Type.FLOAT4); + schema.addColumn("col5", TajoDataTypes.Type.FLOAT8); + schema.addColumn("col6", TajoDataTypes.Type.TEXT); + schema.addColumn("col7", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 3)); + schema.addColumn("col8", TajoDataTypes.Type.BOOLEAN); + schema.addColumn("nullable", TajoDataTypes.Type.NULL_TYPE); + } + + @Test + public void testArithmetic() throws IOException { + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1+1;", new String [] {"2"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5", "select col1 + col2 from table1;", new String [] {"3"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5", "select col1 + col3 from table1;", new String [] {"4"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5", "select col1 + col4 from table1;", new String [] {"5.5"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5", "select col1 + col5 from table1;", new String [] {"6.5"}); + } + + @Test + public void testGetField() throws IOException { + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6", "select col1 from table1;", new String [] {"1"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6", "select col2 from table1;", new String [] {"2"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6", "select col3 from table1;", new String [] {"3"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6", "select col4 from table1;", new String [] {"4.5"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6", "select col5 from table1;", new String [] {"5.5"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6", "select col6 from table1;", new String [] {"F6"}); + testEval(schema, "table1", "0,1,2,3,4.5,5.5,F6,abc,t", "select col8 from table1;", new String [] {"t"}); + } + + @Test + public void testNullHandling() throws IOException { + schema = new Schema(); + schema.addColumn("col0", TajoDataTypes.Type.INT1); + schema.addColumn("col1", TajoDataTypes.Type.INT2); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + schema.addColumn("col3", TajoDataTypes.Type.INT8); + schema.addColumn("col4", TajoDataTypes.Type.FLOAT4); + schema.addColumn("col5", TajoDataTypes.Type.FLOAT8); + schema.addColumn("col6", TajoDataTypes.Type.TEXT); + schema.addColumn("col7", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 1)); + schema.addColumn("col8", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 3)); + schema.addColumn("col9", TajoDataTypes.Type.BOOLEAN); + schema.addColumn("nullable", TajoDataTypes.Type.NULL_TYPE); + + testEval(schema, "table1", ",1,2,3,4.5,6.5,F6,abc,abc,t", "select col0 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,,2,3,4.5,6.5,F6,abc,abc,t,", "select col1 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,,3,4.5,6.5,F6,abc,abc,t,", "select col2 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,,4.5,6.5,F6,abc,abc,t,", "select col3 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,,6.5,F6,abc,abc,t,", "select col4 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,,F6,abc,abc,t,", "select col5 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,,abc,abc,t,", "select col6 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,,abc,t,", "select col7 is null from table1;", new String[]{"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,abc,,t,", "select col8 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,abc,abc,,", "select col9 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,abc,abc,t,", "select nullable is null from table1;", new String [] {"t"}); + + testEval(schema, "table1", ",1,2,3,4.5,6.5,F6,abc,abc,t", "select col0 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,,2,3,4.5,6.5,F6,abc,abc,t,", "select col1 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,,3,4.5,6.5,F6,abc,abc,t,", "select col2 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,,4.5,6.5,F6,abc,abc,t,", "select col3 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,,6.5,F6,abc,abc,t,", "select col4 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,,F6,abc,abc,t,", "select col5 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,,abc,abc,t,", "select col6 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,,abc,t,", "select col7 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,abc,,t,", "select col8 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,abc,abc,,", "select col9 is not null from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5,F6,abc,abc,t,", "select nullable is not null from table1;", new String [] {"f"}); + } + + @Test + public void testComparison() throws IOException { + Schema inetSchema = new Schema(); + inetSchema.addColumn("addr1", TajoDataTypes.Type.INET4); + inetSchema.addColumn("addr2", TajoDataTypes.Type.INET4); + + testSimpleEval("select (1 > null AND false)", new String[] {"f"}); // unknown - false -> false + testSimpleEval("select (1::int8 > null) is null", new String[] {"t"}); + + testSimpleEval("select 1 = null;", new String [] {""}); + testSimpleEval("select 1 <> null;", new String [] {""}); + testSimpleEval("select 1 > null;", new String [] {""}); + testSimpleEval("select 1 >= null;", new String [] {""}); + testSimpleEval("select 1 < null;", new String [] {""}); + testSimpleEval("select 1 <= null;", new String [] {""}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 = col1 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 = col2 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 = col3 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 = col4 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 = col5 from table1;", new String [] {"f"}); + + testEval(inetSchema, "table1", "192.168.0.1,192.168.0.1", "select addr1 = addr2 from table1;", new String[]{"t"}); + testEval(inetSchema, "table1", "192.168.0.1,192.168.0.2", "select addr1 = addr2 from table1;", new String[]{"f"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <> col1 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <> col2 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <> col3 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <> col4 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <> col5 from table1;", new String [] {"t"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 < col1 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 < col2 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 < col3 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 < col4 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 1 < col5 from table1;", new String [] {"t"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <= col1 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <= col2 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <= col3 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <= col4 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 <= col5 from table1;", new String [] {"t"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 > col1 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 > col2 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 > col3 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 > col4 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 > col5 from table1;", new String [] {"f"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 >= col1 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 >= col2 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 >= col3 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 >= col4 from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 3 >= col5 from table1;", new String [] {"f"}); + } + + @Test + public void testBetweenAsymmetric() throws IOException { + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.INT4); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + testEval(schema, "table1", "0,", "select col1 between 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "1,", "select col1 between 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "2,", "select col1 between 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "3,", "select col1 between 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "4,", "select col1 between 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "5,", "select (col2 between 1 and 3) is null from table1", new String[]{"t"}); + + testEval(schema, "table1", "0,", "select col1 between 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "1,", "select col1 between 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "2,", "select col1 between 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "3,", "select col1 between 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "4,", "select col1 between 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "5,", "select (col2 between 3 and 1) is null from table1", new String[]{"t"}); + + testEval(schema, "table1", "0,", "select col1 not between 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "1,", "select col1 not between 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "2,", "select col1 not between 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "3,", "select col1 not between 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "4,", "select col1 not between 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "5,", "select (col2 not between 1 and 3) is null from table1", new String[]{"t"}); + + testEval(schema, "table1", "0,", "select col1 not between 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "1,", "select col1 not between 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "2,", "select col1 not between 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "3,", "select col1 not between 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "4,", "select col1 not between 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "5,", "select (col2 not between 3 and 1) is null from table1", new String[]{"t"}); + } + + @Test + public void testBetweenSymmetric() throws IOException { + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.INT4); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + testEval(schema, "table1", "0,", "select col1 between symmetric 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "1,", "select col1 between symmetric 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "2,", "select col1 between symmetric 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "3,", "select col1 between symmetric 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "4,", "select col1 between symmetric 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "5,", "select (col2 between symmetric 1 and 3) is null from table1", new String[]{"t"}); + + testEval(schema, "table1", "0,", "select col1 not between symmetric 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "1,", "select col1 not between symmetric 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "2,", "select col1 not between symmetric 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "3,", "select col1 not between symmetric 1 and 3 from table1", new String[]{"f"}); + testEval(schema, "table1", "4,", "select col1 not between symmetric 1 and 3 from table1", new String[]{"t"}); + testEval(schema, "table1", "5,", "select (col2 not between symmetric 1 and 3) is null from table1", new String[]{"t"}); + + testEval(schema, "table1", "0,", "select col1 between symmetric 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "1,", "select col1 between symmetric 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "2,", "select col1 between symmetric 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "3,", "select col1 between symmetric 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "4,", "select col1 between symmetric 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "5,", "select (col2 between symmetric 3 and 1) is null from table1", new String[]{"t"}); + + testEval(schema, "table1", "0,", "select col1 not between symmetric 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "1,", "select col1 not between symmetric 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "2,", "select col1 not between symmetric 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "3,", "select col1 not between symmetric 3 and 1 from table1", new String[]{"f"}); + testEval(schema, "table1", "4,", "select col1 not between symmetric 3 and 1 from table1", new String[]{"t"}); + testEval(schema, "table1", "5,", "select (col2 not between symmetric 3 and 1) is null from table1", + new String[]{"t"}); + } + + @Test + public void testUnary() throws IOException { + schema = new Schema(); + schema.addColumn("col0", TajoDataTypes.Type.INT1); + schema.addColumn("col1", TajoDataTypes.Type.INT2); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + schema.addColumn("col3", TajoDataTypes.Type.INT8); + schema.addColumn("col4", TajoDataTypes.Type.FLOAT4); + schema.addColumn("col5", TajoDataTypes.Type.FLOAT8); + schema.addColumn("col6", TajoDataTypes.Type.TEXT); + schema.addColumn("col7", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 3)); + schema.addColumn("col8", TajoDataTypes.Type.BOOLEAN); + + + // sign test + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select +col1 from table1;", new String [] {"1"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select +col2 from table1;", new String [] {"2"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select +col3 from table1;", new String [] {"3"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select +col4 from table1;", new String [] {"4.1"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select +col5 from table1;", new String [] {"5.1"}); + + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select -col1 from table1;", new String [] {"-1"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select -col2 from table1;", new String [] {"-2"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select -col3 from table1;", new String [] {"-3"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select -col4 from table1;", new String [] {"-4.1"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select -col5 from table1;", new String [] {"-5.1"}); + + // not test + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select col8 from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select NOT (col8) from table1;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,t", "select NOT(NOT (col8)) from table1;", new String [] {"t"}); + + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,", "select col8 is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,", "select (NOT (col8)) is null from table1;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7,", "select (NOT(NOT (col8))) is null from table1;", new String [] {"t"}); + } + + @Test + public void testAndOr() throws IOException { + testSimpleEval("select true or (false or false) or false;", new String[] {"t"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select true and true;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select true and false;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select false and true;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select false and false;", new String [] {"f"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select true or true;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select true or false;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select false or true;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select false or false;", new String [] {"f"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select (true and true) and false;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select (true and false) and true;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select (false and true) and true;", new String [] {"f"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select (1 < 2) and true;", new String [] {"t"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select (1 < 2) and false;", new String [] {"f"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select (1 < 2) or false;", new String [] {"t"}); + } + + @Test + public void testFunction() throws IOException { + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select upper('abc');", new String [] {"ABC"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select upper('bbc');", new String [] {"BBC"}); + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select upper('chs');", new String [] {"CHS"}); + + testSimpleEval("select ltrim('xxtrim', 'xx') ", new String[]{"trim"}); + } + + @Test + public void testStringConcat() throws IOException { + testSimpleEval("select length('123456') as col1 ", new String[]{"6"}); + + testEval(schema, "table1", "0,1,2,3,4.5,6.5", "select 'abc' || 'bbc'", new String [] {"abcbbc"}); + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.TEXT); + schema.addColumn("col2", TajoDataTypes.Type.TEXT); + testEval(schema, "table1", " trim, abc", "select ltrim(col1) || ltrim(col2) from table1", + new String[]{"trimabc"}); + } +} diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestGeneratorAdapter.java b/tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestGeneratorAdapter.java new file mode 100644 index 0000000000..9976d390b7 --- /dev/null +++ b/tajo-core/src/test/java/org/apache/tajo/engine/codegen/TestGeneratorAdapter.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.codegen; + +import org.apache.tajo.catalog.Schema; +import org.apache.tajo.datum.Datum; +import org.apache.tajo.storage.Tuple; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +public class TestGeneratorAdapter { + @Test + public void testGetDescription() throws Exception { + assertEquals("I", TajoGeneratorAdapter.getDescription(int.class)); + assertEquals("Ljava/lang/String;", TajoGeneratorAdapter.getDescription(String.class)); + } + + @Test + public void getMethodDescription() throws Exception { + assertEquals("(Lorg/apache/tajo/catalog/Schema;Lorg/apache/tajo/storage/Tuple;)Lorg/apache/tajo/datum/Datum;", + TajoGeneratorAdapter.getMethodDescription(Datum.class, new Class[]{Schema.class, Tuple.class})); + } +} diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java b/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java index 3bee4f3d7e..4cbddddd39 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java @@ -19,6 +19,8 @@ package org.apache.tajo.engine.eval; import org.apache.tajo.LocalTajoTestingUtility; +import org.apache.tajo.OverridableConf; +import org.apache.tajo.SessionVars; import org.apache.tajo.TajoTestingCluster; import org.apache.tajo.algebra.Expr; import org.apache.tajo.catalog.*; @@ -29,6 +31,8 @@ import org.apache.tajo.common.TajoDataTypes.Type; import org.apache.tajo.conf.TajoConf; import org.apache.tajo.datum.*; +import org.apache.tajo.engine.codegen.EvalCodeGenerator; +import org.apache.tajo.engine.codegen.TajoClassLoader; import org.apache.tajo.engine.json.CoreGsonHelper; import org.apache.tajo.engine.parser.SQLAnalyzer; import org.apache.tajo.engine.plan.EvalTreeProtoDeserializer; @@ -38,7 +42,6 @@ import org.apache.tajo.engine.query.QueryContext; import org.apache.tajo.engine.utils.SchemaUtil; import org.apache.tajo.master.TajoMaster; -import org.apache.tajo.master.session.Session; import org.apache.tajo.storage.LazyTuple; import org.apache.tajo.storage.Tuple; import org.apache.tajo.storage.VTuple; @@ -54,12 +57,11 @@ import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME; import static org.apache.tajo.TajoConstants.DEFAULT_TABLESPACE_NAME; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class ExprTestBase { private static TajoTestingCluster util; + private static TajoConf conf; private static CatalogService cat; private static SQLAnalyzer analyzer; private static PreLogicalPlanVerifier preLogicalPlanVerifier; @@ -71,9 +73,13 @@ public static String getUserTimeZoneDisplay() { return DateTimeUtil.getTimeZoneDisplayTime(TajoConf.getCurrentTimeZone()); } + public ExprTestBase() { + } + @BeforeClass public static void setUp() throws Exception { util = new TajoTestingCluster(); + conf = util.getConfiguration(); util.startCatalogCluster(); cat = util.getMiniCatalogCluster().getCatalog(); cat.createTablespace(DEFAULT_TABLESPACE_NAME, "hdfs://localhost:1234/warehouse"); @@ -100,20 +106,22 @@ private static void assertJsonSerDer(EvalNode expr) { assertEquals(expr, fromJson); } + public TajoConf getConf() { + return new TajoConf(conf); + } + /** * verify query syntax and get raw targets. * + * @param context QueryContext * @param query a query for execution * @param condition this parameter means whether it is for success case or is not for failure case. * @return * @throws PlanningException */ - private static Target[] getRawTargets(String query, boolean condition) throws PlanningException, + private static Target[] getRawTargets(QueryContext context, String query, boolean condition) throws PlanningException, InvalidStatementException { - Session session = LocalTajoTestingUtility.createDummySession(); - QueryContext context = new QueryContext(util.getConfiguration(), session); - List parsedResults = SimpleParser.parseScript(query); if (parsedResults.size() > 1) { throw new RuntimeException("this query includes two or more statements."); @@ -162,17 +170,38 @@ public void testSimpleEval(String query, String [] expected) throws IOException } public void testSimpleEval(String query, String [] expected, boolean condition) throws IOException { - testEval(null, null, null, query, expected, ',', condition); + testEval(null, null, null, null, query, expected, ',', condition); } public void testEval(Schema schema, String tableName, String csvTuple, String query, String [] expected) throws IOException { - testEval(schema, tableName != null ? CatalogUtil.normalizeIdentifier(tableName) : null, csvTuple, query, + testEval(null, schema, tableName != null ? CatalogUtil.normalizeIdentifier(tableName) : null, csvTuple, query, expected, ',', true); } - public void testEval(Schema schema, String tableName, String csvTuple, String query, String [] expected, - char delimiter, boolean condition) throws IOException { + public void testEval(OverridableConf overideConf, Schema schema, String tableName, String csvTuple, String query, + String [] expected) + throws IOException { + testEval(overideConf, schema, tableName != null ? CatalogUtil.normalizeIdentifier(tableName) : null, csvTuple, + query, expected, ',', true); + } + + public void testEval(Schema schema, String tableName, String csvTuple, String query, + String [] expected, char delimiter, boolean condition) throws IOException { + testEval(null, schema, tableName != null ? CatalogUtil.normalizeIdentifier(tableName) : null, csvTuple, + query, expected, delimiter, condition); + } + + public void testEval(OverridableConf overideConf, Schema schema, String tableName, String csvTuple, String query, + String [] expected, char delimiter, boolean condition) throws IOException { + QueryContext context; + if (overideConf == null) { + context = LocalTajoTestingUtility.createDummyContext(conf); + } else { + context = LocalTajoTestingUtility.createDummyContext(conf); + context.putAll(overideConf); + } + LazyTuple lazyTuple; VTuple vtuple = null; String qualifiedTableName = @@ -192,8 +221,16 @@ public void testEval(Schema schema, String tableName, String csvTuple, String qu new LazyTuple(inputSchema, BytesUtils.splitPreserveAllTokens(csvTuple.getBytes(), delimiter, targetIdx),0); vtuple = new VTuple(inputSchema.size()); for (int i = 0; i < inputSchema.size(); i++) { + // If null value occurs, null datum is manually inserted to an input tuple. - if (lazyTuple.get(i) instanceof TextDatum && lazyTuple.get(i).asChars().equals("")) { + boolean nullDatum; + Datum datum = lazyTuple.get(i); + nullDatum = (datum instanceof TextDatum || datum instanceof CharDatum); + nullDatum = nullDatum && datum.asChars().equals("") || + datum.asChars().equals(context.get(SessionVars.NULL_CHAR)); + nullDatum |= datum.isNull(); + + if (nullDatum) { vtuple.put(i, NullDatum.get()); } else { vtuple.put(i, lazyTuple.get(i)); @@ -205,15 +242,33 @@ public void testEval(Schema schema, String tableName, String csvTuple, String qu Target [] targets; + TajoClassLoader classLoader = new TajoClassLoader(); + try { - targets = getRawTargets(query, condition); + targets = getRawTargets(context, query, condition); + + EvalCodeGenerator codegen = null; + if (context.getBool(SessionVars.CODEGEN)) { + codegen = new EvalCodeGenerator(classLoader); + } Tuple outTuple = new VTuple(targets.length); for (int i = 0; i < targets.length; i++) { EvalNode eval = targets[i].getEvalTree(); + + if (context.getBool(SessionVars.CODEGEN)) { + eval = codegen.compile(inputSchema, eval); + } + outTuple.put(i, eval.eval(inputSchema, vtuple)); } + try { + classLoader.clean(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + for (int i = 0; i < expected.length; i++) { Datum datum = outTuple.get(i); String outTupleAsChars; diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestPredicates.java b/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestPredicates.java index 7811e692a7..79a287b3f8 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestPredicates.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestPredicates.java @@ -18,7 +18,9 @@ package org.apache.tajo.engine.eval; +import org.apache.tajo.catalog.CatalogUtil; import org.apache.tajo.catalog.Schema; +import org.apache.tajo.common.TajoDataTypes; import org.junit.Test; import java.io.IOException; @@ -109,6 +111,21 @@ public void testParenthesizedValues() throws IOException { @Test public void testComparisonEqual() throws IOException { + + + Schema schema = new Schema(); + schema.addColumn("col0", TajoDataTypes.Type.INT1); + schema.addColumn("col1", TajoDataTypes.Type.INT2); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + schema.addColumn("col3", TajoDataTypes.Type.INT8); + schema.addColumn("col4", TajoDataTypes.Type.FLOAT4); + schema.addColumn("col5", TajoDataTypes.Type.FLOAT8); + schema.addColumn("col6", TajoDataTypes.Type.TEXT); + schema.addColumn("col7", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 3)); + schema.addColumn("nullable", TajoDataTypes.Type.INT4); + + testEval(schema, "t1", "0,1,2,3,4.1,5.1,cmp,asm,", "SELECT col6 = 'cmp' from t1", new String [] {"t"}); + Schema schema1 = new Schema(); schema1.addColumn("col1", INT4); schema1.addColumn("col2", INT4); diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestSQLExpression.java b/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestSQLExpression.java index af084d9d8a..6c9892a704 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestSQLExpression.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/eval/TestSQLExpression.java @@ -31,7 +31,7 @@ import java.io.IOException; import java.util.TimeZone; -import static org.apache.tajo.common.TajoDataTypes.Type.TEXT; +import static org.apache.tajo.common.TajoDataTypes.Type.*; import static org.junit.Assert.fail; public class TestSQLExpression extends ExprTestBase { @@ -93,16 +93,16 @@ public void testSQLStandardCast() throws IOException { @Test public void testExplicitCast() throws IOException { Schema schema = new Schema(); - schema.addColumn("col0", TajoDataTypes.Type.INT1); - schema.addColumn("col1", TajoDataTypes.Type.INT2); - schema.addColumn("col2", TajoDataTypes.Type.INT4); - schema.addColumn("col3", TajoDataTypes.Type.INT8); - schema.addColumn("col4", TajoDataTypes.Type.FLOAT4); - schema.addColumn("col5", TajoDataTypes.Type.FLOAT8); - schema.addColumn("col6", TajoDataTypes.Type.TEXT); + schema.addColumn("col0", INT1); + schema.addColumn("col1", INT2); + schema.addColumn("col2", INT4); + schema.addColumn("col3", INT8); + schema.addColumn("col4", FLOAT4); + schema.addColumn("col5", FLOAT8); + schema.addColumn("col6", TEXT); schema.addColumn("col7", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 3)); - testSimpleEval("select cast (1 as char)", new String[] {"1"}); + testSimpleEval("select cast (1 as char)", new String[]{"1"}); testSimpleEval("select cast (119 as char)", new String[] {"1"}); testEval(schema, "table1", "0,1,2,3,4.1,5.1,6,7", "select col0::int1 from table1;", new String [] {"0"}); @@ -866,6 +866,7 @@ public void testCastFromTable() throws IOException { Schema schema = new Schema(); schema.addColumn("col1", TEXT); schema.addColumn("col2", TEXT); + testEval(schema, "table1", "123,234", "select cast(col1 as float) as b, cast(col2 as float) as a from table1", new String[]{"123.0", "234.0"}); testEval(schema, "table1", "123,234", "select col1::float, col2::float from table1", @@ -879,7 +880,8 @@ public void testCastFromTable() throws IOException { new String[]{timestamp.asChars(TajoConf.getCurrentTimeZone(), true), "234.0"} ); - testSimpleEval("select '1980-04-01 01:50:01'::timestamp;", new String[]{timestamp.asChars(TajoConf.getCurrentTimeZone(), true)}); + testSimpleEval("select '1980-04-01 01:50:01'::timestamp;", + new String[]{timestamp.asChars(TajoConf.getCurrentTimeZone(), true)}); testSimpleEval("select '1980-04-01 01:50:01'::timestamp::text", new String[]{"1980-04-01 01:50:01"}); testSimpleEval("select (cast ('99999'::int8 as text))::int4 + 1", new String[]{"100000"}); @@ -901,6 +903,8 @@ public void testBooleanLiteral() throws IOException { @Test public void testNullComparisons() throws IOException { + testSimpleEval("select (1 > null) is null", new String[] {"t"}); + testSimpleEval("select null is null", new String[] {"t"}); testSimpleEval("select null is not null", new String[] {"f"}); diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/function/TestConditionalExpressions.java b/tajo-core/src/test/java/org/apache/tajo/engine/function/TestConditionalExpressions.java index ece34e7155..54c8722493 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/function/TestConditionalExpressions.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/function/TestConditionalExpressions.java @@ -18,19 +18,134 @@ package org.apache.tajo.engine.function; +import org.apache.tajo.catalog.CatalogUtil; +import org.apache.tajo.catalog.Schema; import org.apache.tajo.catalog.exception.NoSuchFunctionException; +import org.apache.tajo.common.TajoDataTypes; import org.apache.tajo.engine.eval.ExprTestBase; import org.junit.Test; +import java.io.IOException; + import static org.junit.Assert.fail; public class TestConditionalExpressions extends ExprTestBase { + @Test + public void testCaseWhens1() throws IOException { + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.INT1); + schema.addColumn("col2", TajoDataTypes.Type.INT2); + schema.addColumn("col3", TajoDataTypes.Type.INT4); + schema.addColumn("col4", TajoDataTypes.Type.INT8); + schema.addColumn("col5", TajoDataTypes.Type.FLOAT4); + schema.addColumn("col6", TajoDataTypes.Type.FLOAT8); + schema.addColumn("col7", TajoDataTypes.Type.TEXT); + schema.addColumn("col8", CatalogUtil.newDataType(TajoDataTypes.Type.CHAR, "", 3)); + schema.addColumn("col9", TajoDataTypes.Type.INT4); + + testEval(schema, "table1", "1,2,3,4,5.0,6.0,text,abc,", + "select case when col1 between 1 and 3 then 10 else 100 end from table1;", + new String [] {"10"}); + testEval(schema, "table1", "1,2,3,4,5.0,6.0,text,abc,", + "select case when col1 > 1 then 10 when col1 > 2 then 20 else 100 end from table1;", + new String [] {"100"}); + testEval(schema, "table1", "1,2,3,4,5.0,6.0,text,abc,", + "select case col1 when 1 then 10 when 2 then 20 else 100 end from table1;", + new String [] {"10"}); + testEval(schema, "table1", "1,2,3,4,5.0,6.0,text,abc,", + "select case col9 when 1 then 10 when 2 then 20 else 100 end is null from table1;", + new String [] {"f"}); + } + + @Test + public void testCaseWhensWithNullReturn() throws IOException { + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.TEXT); + schema.addColumn("col2", TajoDataTypes.Type.TEXT); + + testEval(schema, "table1", "str1,str2", + "SELECT CASE WHEN col1 IS NOT NULL THEN col2 ELSE NULL END FROM table1", + new String[]{"str2"}); + testEval(schema, "table1", ",str2", + "SELECT CASE WHEN col1 IS NOT NULL THEN col2 ELSE NULL END FROM table1", + new String[]{""}); + } + + @Test + public void testCaseWhensWithCommonExpression() throws IOException { + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.INT4); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + schema.addColumn("col3", TajoDataTypes.Type.INT4); + + testEval(schema, "table1", "1,2,3", + "SELECT CASE WHEN col1 = 1 THEN 1 WHEN col1 = 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"1"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE WHEN col2 = 1 THEN 1 WHEN col2 = 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"2"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE WHEN col3 = 1 THEN 1 WHEN col3 = 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"3"}); + + testEval(schema, "table1", "1,2,3", + "SELECT CASE col1 WHEN 1 THEN 1 WHEN 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"1"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col2 WHEN 1 THEN 1 WHEN 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"2"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col3 WHEN 1 THEN 1 WHEN 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"3"}); + + testEval(schema, "table1", "1,2,3", + "SELECT CASE col1 WHEN 1 THEN 'aaa' WHEN 2 THEN 'bbb' ELSE 'ccc' END FROM table1", + new String [] {"aaa"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col2 WHEN 1 THEN 'aaa' WHEN 2 THEN 'bbb' ELSE 'ccc' END FROM table1", + new String [] {"bbb"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col3 WHEN 1 THEN 'aaa' WHEN 2 THEN 'bbb' ELSE 'ccc' END FROM table1", + new String [] {"ccc"}); + } + + @Test + public void testCaseWhensWithCommonExpressionAndNull() throws IOException { + Schema schema = new Schema(); + schema.addColumn("col1", TajoDataTypes.Type.INT4); + schema.addColumn("col2", TajoDataTypes.Type.INT4); + schema.addColumn("col3", TajoDataTypes.Type.INT4); + + testEval(schema, "table1", "1,2,3", + "SELECT CASE col1 WHEN 1 THEN NULL WHEN 2 THEN 2 ELSE 3 END FROM table1", + new String [] {""}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col2 WHEN 1 THEN NULL WHEN 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"2"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col3 WHEN 1 THEN NULL WHEN 2 THEN 2 ELSE 3 END FROM table1", + new String [] {"3"}); + + testEval(schema, "table1", "1,2,3", + "SELECT CASE col1 WHEN 1 THEN 1 WHEN 2 THEN 2 ELSE NULL END FROM table1", + new String [] {"1"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col2 WHEN 1 THEN NULL WHEN 2 THEN 2 ELSE NULL END FROM table1", + new String [] {"2"}); + testEval(schema, "table1", "1,2,3", + "SELECT CASE col3 WHEN 1 THEN NULL WHEN 2 THEN 2 ELSE NULL END FROM table1", + new String [] {""}); + } + @Test public void testCoalesceText() throws Exception { + testSimpleEval("select coalesce('value1', 'value2');", new String[]{"value1"}); testSimpleEval("select coalesce(null, 'value2');", new String[]{"value2"}); testSimpleEval("select coalesce(null, null, 'value3');", new String[]{"value3"}); testSimpleEval("select coalesce('value1', null, 'value3');", new String[]{"value1"}); testSimpleEval("select coalesce(null, 'value2', 'value3');", new String[]{"value2"}); + testSimpleEval("select coalesce('value1');", new String[]{"value1"}); + testSimpleEval("select coalesce(null);", new String[]{""}); //no matched function try { @@ -43,10 +158,13 @@ public void testCoalesceText() throws Exception { @Test public void testCoalesceLong() throws Exception { + testSimpleEval("select coalesce(1, 2);", new String[]{"1"}); testSimpleEval("select coalesce(null, 2);", new String[]{"2"}); testSimpleEval("select coalesce(null, null, 3);", new String[]{"3"}); testSimpleEval("select coalesce(1, null, 3);", new String[]{"1"}); testSimpleEval("select coalesce(null, 2, 3);", new String[]{"2"}); + testSimpleEval("select coalesce(1);", new String[]{"1"}); + testSimpleEval("select coalesce(null);", new String[]{""}); //no matched function try { @@ -59,17 +177,20 @@ public void testCoalesceLong() throws Exception { @Test public void testCoalesceDouble() throws Exception { + testSimpleEval("select coalesce(1.0, 2.0);", new String[]{"1.0"}); testSimpleEval("select coalesce(null, 2.0);", new String[]{"2.0"}); testSimpleEval("select coalesce(null, null, 3.0);", new String[]{"3.0"}); testSimpleEval("select coalesce(1.0, null, 3.0);", new String[]{"1.0"}); testSimpleEval("select coalesce(null, 2.0, 3.0);", new String[]{"2.0"}); + testSimpleEval("select coalesce(1.0);", new String[]{"1.0"}); + testSimpleEval("select coalesce(null);", new String[]{""}); //no matched function try { testSimpleEval("select coalesce('value1', null, 3.0);", new String[]{"1.0"}); fail("coalesce(TEXT, NULL, FLOAT8) not defined. So should throw exception."); } catch (NoSuchFunctionException e) { - //success + // success } try { diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/function/TestPatternMatchingPredicates.java b/tajo-core/src/test/java/org/apache/tajo/engine/function/TestPatternMatchingPredicates.java index 7d8eba4a96..8aae26d9c6 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/function/TestPatternMatchingPredicates.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/function/TestPatternMatchingPredicates.java @@ -35,7 +35,7 @@ public void testLike() throws IOException { // test for null values testEval(schema, "table1", ",", "select col1 like 'a%' from table1", new String[]{""}); - //testSimpleEval("select null like 'a%'", new String[]{""}); + testSimpleEval("select null like 'a%'", new String[]{""}); testEval(schema, "table1", "abc", "select col1 like '%c' from table1", new String[]{"t"}); testEval(schema, "table1", "abc", "select col1 like 'a%' from table1", new String[]{"t"}); diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java index 191db85e7b..5d809f8a6d 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java @@ -914,7 +914,7 @@ public final void testGroupByWithNullValue() throws IOException, PlanningExcepti } @Test - public final void testUnionPlan() throws IOException, PlanningException { + public final void testUnionPlan() throws IOException, PlanningException, CloneNotSupportedException { FileFragment[] frags = StorageManager.splitNG(conf, "default.employee", employee.getMeta(), employee.getPath(), Integer.MAX_VALUE); Path workDir = CommonTestingUtil.getTestDir("target/test-data/testUnionPlan"); @@ -927,8 +927,8 @@ public final void testUnionPlan() throws IOException, PlanningException { LogicalNode rootNode = optimizer.optimize(plan); LogicalRootNode root = (LogicalRootNode) rootNode; UnionNode union = plan.createNode(UnionNode.class); - union.setLeftChild(root.getChild()); - union.setRightChild(root.getChild()); + union.setLeftChild((LogicalNode) root.getChild().clone()); + union.setRightChild((LogicalNode) root.getChild().clone()); root.setChild(union); PhysicalPlanner phyPlanner = new PhysicalPlannerImpl(conf,sm); diff --git a/tajo-core/src/test/java/org/apache/tajo/worker/TestFetcher.java b/tajo-core/src/test/java/org/apache/tajo/worker/TestFetcher.java index eb63c271d9..95c06bb924 100644 --- a/tajo-core/src/test/java/org/apache/tajo/worker/TestFetcher.java +++ b/tajo-core/src/test/java/org/apache/tajo/worker/TestFetcher.java @@ -84,7 +84,7 @@ public void testGet() throws IOException { String params = String.format("qid=%s&sid=%s&p=%s&type=%s", queryId, sid, partId, "h"); Path inputPath = new Path(dataPath); - FSDataOutputStream stream = LocalFileSystem.get(conf).create(inputPath, true); + FSDataOutputStream stream = FileSystem.getLocal(conf).create(inputPath, true); for (int i = 0; i < 100; i++) { String data = ""+rnd.nextInt(); stream.write(data.getBytes()); @@ -124,7 +124,7 @@ public void testStatus() throws Exception { String dataPath = INPUT_DIR + queryId.toString() + "/output"+ "/" + sid + "/" +ta + "/output/" + partId; String params = String.format("qid=%s&sid=%s&p=%s&type=%s&ta=%s", queryId, sid, partId, "h", ta); - FSDataOutputStream stream = LocalFileSystem.get(conf).create(new Path(dataPath), true); + FSDataOutputStream stream = FileSystem.getLocal(conf).create(new Path(dataPath), true); for (int i = 0; i < 100; i++) { String data = ""+rnd.nextInt(); stream.write(data.getBytes()); @@ -152,11 +152,12 @@ public void testNoContentFetch() throws Exception { String params = String.format("qid=%s&sid=%s&p=%s&type=%s&ta=%s", queryId, sid, partId, "h", ta); Path inputPath = new Path(dataPath); - if(LocalFileSystem.get(conf).exists(inputPath)){ - LocalFileSystem.get(conf).delete(new Path(dataPath), true); + FileSystem fs = FileSystem.getLocal(conf); + if(fs.exists(inputPath)){ + fs.delete(new Path(dataPath), true); } - FSDataOutputStream stream = LocalFileSystem.get(conf).create(new Path(dataPath).getParent(), true); + FSDataOutputStream stream = FileSystem.getLocal(conf).create(new Path(dataPath).getParent(), true); stream.close(); URI uri = URI.create("http://127.0.0.1:" + pullServerService.getPort() + "/?" + params); @@ -182,7 +183,7 @@ public void testFailureStatus() throws Exception { String shuffleType = "x"; String params = String.format("qid=%s&sid=%s&p=%s&type=%s&ta=%s", queryId, sid, partId, shuffleType, ta); - FSDataOutputStream stream = LocalFileSystem.get(conf).create(new Path(dataPath), true); + FSDataOutputStream stream = FileSystem.getLocal(conf).create(new Path(dataPath), true); for (int i = 0; i < 100; i++) { String data = params + rnd.nextInt(); diff --git a/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result b/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result index c294407abd..0b5e28f5f8 100644 --- a/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result +++ b/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result @@ -32,5 +32,6 @@ Available Session Variables: \set HASH_GROUPBY_SIZE_LIMIT [long value] - limited size for hash groupby (mb) \set MAX_OUTPUT_FILE_SIZE [int value] - Maximum per-output file size (mb). 0 means infinite. \set NULL_CHAR [text value] - null char of text file output +\set CODEGEN [true or false] - Runtime code generation enabled (experiment) \set ARITHABORT [true or false] - If true, a running query will be terminated when an overflow or divide-by-zero occurs. \set DEBUG_ENABLED [true or false] - (debug only) debug mode enabled \ No newline at end of file diff --git a/tajo-dist/pom.xml b/tajo-dist/pom.xml index d8930105e2..a0cbe9f8bd 100644 --- a/tajo-dist/pom.xml +++ b/tajo-dist/pom.xml @@ -32,6 +32,28 @@ Tajo Distribution jar + + + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + src/main/conf/workers + + + + + + org.apache.tajo diff --git a/tajo-dist/src/main/bin/tajo b/tajo-dist/src/main/bin/tajo index 4382a7abed..f579864cfb 100755 --- a/tajo-dist/src/main/bin/tajo +++ b/tajo-dist/src/main/bin/tajo @@ -180,8 +180,8 @@ fi # Allow alternate conf dir location. HADOOP_CONF_DIR="${HADOOP_CONF_DIR:-$HADOOP_HOME/etc/hadoop}" -# CLASSPATH initially contains $HADOOP_CONF_DIR -CLASSPATH="${HADOOP_CONF_DIR}" +# CLASSPATH initially contains $HADOOP_CONF_DIR and tools.jar +CLASSPATH="${HADOOP_CONF_DIR}:${JAVA_HOME}/lib/tools.jar" ############################################################################## # Hadoop Version Checking Section End diff --git a/tajo-docs/pom.xml b/tajo-docs/pom.xml index 5ca89c5330..5e14273c38 100644 --- a/tajo-docs/pom.xml +++ b/tajo-docs/pom.xml @@ -28,6 +28,24 @@ limitations under the License. + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + BUILDING + src/main/sphinx/**/*.rst + + + org.tomdz.maven sphinx-maven-plugin diff --git a/tajo-jdbc/pom.xml b/tajo-jdbc/pom.xml index dcca6b117a..1708f5af82 100644 --- a/tajo-jdbc/pom.xml +++ b/tajo-jdbc/pom.xml @@ -56,6 +56,18 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-dependency-plugin @@ -198,7 +210,6 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 diff --git a/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/MetaDataTuple.java b/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/MetaDataTuple.java index 04338ca170..5dae67e0e9 100644 --- a/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/MetaDataTuple.java +++ b/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/MetaDataTuple.java @@ -18,6 +18,7 @@ import org.apache.tajo.datum.Datum; import org.apache.tajo.datum.NullDatum; +import org.apache.tajo.datum.ProtobufDatum; import org.apache.tajo.exception.UnsupportedException; import org.apache.tajo.storage.Tuple; @@ -139,6 +140,11 @@ public String getText(int fieldId) { return values.get(fieldId).toString(); } + @Override + public ProtobufDatum getProtobufDatum(int fieldId) { + throw new UnsupportedException(); + } + @Override public char[] getUnicodeChars(int fieldId) { return values.get(fieldId).asUnicodeChars(); diff --git a/tajo-maven-plugins/pom.xml b/tajo-maven-plugins/pom.xml index acb338d74c..43ff9e074b 100644 --- a/tajo-maven-plugins/pom.xml +++ b/tajo-maven-plugins/pom.xml @@ -55,6 +55,18 @@ + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-plugin-plugin diff --git a/tajo-project/pom.xml b/tajo-project/pom.xml index af8a361ef9..fe5366ee98 100644 --- a/tajo-project/pom.xml +++ b/tajo-project/pom.xml @@ -391,11 +391,6 @@ maven-antrun-plugin 1.6 - - org.apache.maven.plugins - maven-pmd-plugin - 2.7.1 - org.apache.maven.plugins maven-site-plugin @@ -596,6 +591,18 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-enforcer-plugin @@ -758,6 +765,12 @@ ${tajo.version} test-jar + + org.apache.tajo + tajo-thirdparty-asm + ${tajo.version} + jar + org.apache.hadoop diff --git a/tajo-rpc/pom.xml b/tajo-rpc/pom.xml index 22a6b4afa1..06e24b9af4 100644 --- a/tajo-rpc/pom.xml +++ b/tajo-rpc/pom.xml @@ -41,6 +41,18 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + org.apache.maven.plugins maven-jar-plugin diff --git a/tajo-storage/pom.xml b/tajo-storage/pom.xml index 7035b62819..6f80ea50be 100644 --- a/tajo-storage/pom.xml +++ b/tajo-storage/pom.xml @@ -60,10 +60,26 @@ ${project.build.sourceEncoding} + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + src/test/resources/testVariousTypes.avsc + + + org.apache.maven.plugins maven-surefire-plugin - 2.12.4 TRUE diff --git a/tajo-storage/src/main/java/org/apache/tajo/storage/FrameTuple.java b/tajo-storage/src/main/java/org/apache/tajo/storage/FrameTuple.java index 1376a05656..e0f8a2e485 100644 --- a/tajo-storage/src/main/java/org/apache/tajo/storage/FrameTuple.java +++ b/tajo-storage/src/main/java/org/apache/tajo/storage/FrameTuple.java @@ -24,6 +24,7 @@ import com.google.common.base.Preconditions; import org.apache.tajo.datum.Datum; import org.apache.tajo.datum.NullDatum; +import org.apache.tajo.datum.ProtobufDatum; import org.apache.tajo.exception.UnsupportedException; /** @@ -170,6 +171,11 @@ public String getText(int fieldId) { return get(fieldId).asChars(); } + @Override + public ProtobufDatum getProtobufDatum(int fieldId) { + return (ProtobufDatum) get(fieldId); + } + @Override public char [] getUnicodeChars(int fieldId) { return get(fieldId).asUnicodeChars(); diff --git a/tajo-storage/src/main/java/org/apache/tajo/storage/LazyTuple.java b/tajo-storage/src/main/java/org/apache/tajo/storage/LazyTuple.java index d8dca0ed9c..167e4a8f44 100644 --- a/tajo-storage/src/main/java/org/apache/tajo/storage/LazyTuple.java +++ b/tajo-storage/src/main/java/org/apache/tajo/storage/LazyTuple.java @@ -21,6 +21,8 @@ import org.apache.tajo.catalog.Schema; import org.apache.tajo.datum.Datum; import org.apache.tajo.datum.NullDatum; +import org.apache.tajo.datum.ProtobufDatum; +import org.apache.tajo.exception.UnsupportedException; import java.util.Arrays; @@ -183,7 +185,7 @@ public float getFloat4(int fieldId) { @Override public double getFloat8(int fieldId) { - return get(fieldId).asInt8(); + return get(fieldId).asFloat8(); } @Override @@ -191,6 +193,11 @@ public String getText(int fieldId) { return get(fieldId).asChars(); } + @Override + public ProtobufDatum getProtobufDatum(int fieldId) { + throw new UnsupportedException(); + } + @Override public char[] getUnicodeChars(int fieldId) { return get(fieldId).asUnicodeChars(); diff --git a/tajo-storage/src/main/java/org/apache/tajo/storage/Tuple.java b/tajo-storage/src/main/java/org/apache/tajo/storage/Tuple.java index 5a173f7532..c18317150f 100644 --- a/tajo-storage/src/main/java/org/apache/tajo/storage/Tuple.java +++ b/tajo-storage/src/main/java/org/apache/tajo/storage/Tuple.java @@ -19,6 +19,7 @@ package org.apache.tajo.storage; import org.apache.tajo.datum.Datum; +import org.apache.tajo.datum.ProtobufDatum; public interface Tuple extends Cloneable { @@ -64,6 +65,8 @@ public interface Tuple extends Cloneable { public String getText(int fieldId); + public ProtobufDatum getProtobufDatum(int fieldId); + public char [] getUnicodeChars(int fieldId); public Tuple clone() throws CloneNotSupportedException; diff --git a/tajo-storage/src/main/java/org/apache/tajo/storage/VTuple.java b/tajo-storage/src/main/java/org/apache/tajo/storage/VTuple.java index 326fb6d645..4fb35f957c 100644 --- a/tajo-storage/src/main/java/org/apache/tajo/storage/VTuple.java +++ b/tajo-storage/src/main/java/org/apache/tajo/storage/VTuple.java @@ -22,6 +22,7 @@ import org.apache.tajo.datum.Datum; import org.apache.tajo.datum.Inet4Datum; import org.apache.tajo.datum.NullDatum; +import org.apache.tajo.datum.ProtobufDatum; import org.apache.tajo.exception.UnimplementedException; import java.net.InetAddress; @@ -172,6 +173,11 @@ public String getText(int fieldId) { return values[fieldId].asChars(); } + @Override + public ProtobufDatum getProtobufDatum(int fieldId) { + return (ProtobufDatum) values[fieldId]; + } + @Override public char[] getUnicodeChars(int fieldId) { return values[fieldId].asUnicodeChars(); diff --git a/tajo-storage/src/main/java/org/apache/tajo/storage/parquet/ParquetAppender.java b/tajo-storage/src/main/java/org/apache/tajo/storage/parquet/ParquetAppender.java index ff9e43c9b6..3a3bb57359 100644 --- a/tajo-storage/src/main/java/org/apache/tajo/storage/parquet/ParquetAppender.java +++ b/tajo-storage/src/main/java/org/apache/tajo/storage/parquet/ParquetAppender.java @@ -18,6 +18,7 @@ package org.apache.tajo.storage.parquet; +import org.apache.tajo.storage.StorageConstants; import parquet.hadoop.ParquetOutputFormat; import parquet.hadoop.metadata.CompressionCodecName; @@ -56,15 +57,15 @@ public ParquetAppender(Configuration conf, Schema schema, TableMeta meta, Path path) throws IOException { super(conf, schema, meta, path); this.blockSize = Integer.parseInt( - meta.getOption(ParquetOutputFormat.BLOCK_SIZE)); + meta.getOption(ParquetOutputFormat.BLOCK_SIZE, StorageConstants.PARQUET_DEFAULT_BLOCK_SIZE)); this.pageSize = Integer.parseInt( - meta.getOption(ParquetOutputFormat.PAGE_SIZE)); + meta.getOption(ParquetOutputFormat.PAGE_SIZE, StorageConstants.PARQUET_DEFAULT_PAGE_SIZE)); this.compressionCodecName = CompressionCodecName.fromConf( - meta.getOption(ParquetOutputFormat.COMPRESSION)); + meta.getOption(ParquetOutputFormat.COMPRESSION, StorageConstants.PARQUET_DEFAULT_COMPRESSION_CODEC_NAME)); this.enableDictionary = Boolean.parseBoolean( - meta.getOption(ParquetOutputFormat.ENABLE_DICTIONARY)); + meta.getOption(ParquetOutputFormat.ENABLE_DICTIONARY, StorageConstants.PARQUET_DEFAULT_IS_DICTIONARY_ENABLED)); this.validating = Boolean.parseBoolean( - meta.getOption(ParquetOutputFormat.VALIDATION)); + meta.getOption(ParquetOutputFormat.VALIDATION, StorageConstants.PARQUET_DEFAULT_IS_VALIDATION_ENABLED)); } /** diff --git a/tajo-storage/src/test/java/org/apache/tajo/storage/parquet/TestReadWrite.java b/tajo-storage/src/test/java/org/apache/tajo/storage/parquet/TestReadWrite.java index 1cfba86c19..0a01dc43ca 100644 --- a/tajo-storage/src/test/java/org/apache/tajo/storage/parquet/TestReadWrite.java +++ b/tajo-storage/src/test/java/org/apache/tajo/storage/parquet/TestReadWrite.java @@ -25,6 +25,8 @@ import java.util.ArrayList; import java.util.List; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.LocalFileSystem; import org.apache.hadoop.fs.Path; import org.apache.tajo.catalog.Column; import org.apache.tajo.catalog.Schema; @@ -48,7 +50,10 @@ private Path createTmpFile() throws IOException { File tmp = File.createTempFile(getClass().getSimpleName(), ".tmp"); tmp.deleteOnExit(); tmp.delete(); - return new Path(tmp.getPath()); + + // it prevents accessing HDFS namenode of TajoTestingCluster. + LocalFileSystem localFS = LocalFileSystem.getLocal(new Configuration()); + return localFS.makeQualified(new Path(tmp.getPath())); } private Schema createAllTypesSchema() { diff --git a/tajo-thirdparty/asm/pom.xml b/tajo-thirdparty/asm/pom.xml new file mode 100644 index 0000000000..5464d49819 --- /dev/null +++ b/tajo-thirdparty/asm/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + tajo-project + org.apache.tajo + 0.9.0-SNAPSHOT + ../../tajo-project + + + tajo-thirdparty-asm + ASM (thirdparty) + ASM is a Java bytecode manipulation framework. + jar + + + UTF-8 + UTF-8 + + + + + apache.snapshots + http://repository.apache.org/snapshots + + true + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + ${project.build.sourceEncoding} + + + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + + src/main/java/org/apache/tajo/org/objectweb/asm/** + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + + + + + + + + + + docs + + false + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + module-javadocs + package + + jar + + + ${project.build.directory} + + + + + + + + + dist + + false + + tar|rpm|deb + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + dist + package + + run + + + + + run() { + echo "\$ ${@}" + "${@}" + res=$? + if [ $res != 0 ]; then + echo + echo "Failed!" + echo + exit $res + fi + } + + ROOT=`cd ${basedir}/..;pwd` + echo + echo "Current directory `pwd`" + echo + run rm -rf ${project.artifactId}-${project.version} + run mkdir ${project.artifactId}-${project.version} + run cd ${project.artifactId}-${project.version} + run cp -r ${basedir}/target/${project.artifactId}-${project.version}*.jar . + echo + echo "Tajo Common dist layout available at: ${project.build.directory}/${project.artifactId}-${project.version}" + echo + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + + + + diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationVisitor.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationVisitor.java new file mode 100644 index 0000000000..c63618c05c --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationVisitor.java @@ -0,0 +1,169 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A visitor to visit a Java annotation. The methods of this class must be + * called in the following order: ( visit | visitEnum | + * visitAnnotation | visitArray )* visitEnd. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public abstract class AnnotationVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The annotation visitor to which this visitor must delegate method calls. + * May be null. + */ + protected AnnotationVisitor av; + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public AnnotationVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param av + * the annotation visitor to which this visitor must delegate + * method calls. May be null. + */ + public AnnotationVisitor(final int api, final AnnotationVisitor av) { + if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + } + this.api = api; + this.av = av; + } + + /** + * Visits a primitive value of the annotation. + * + * @param name + * the value name. + * @param value + * the actual value, whose type must be {@link Byte}, + * {@link Boolean}, {@link Character}, {@link Short}, + * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type} or OBJECT or ARRAY sort. This + * value can also be an array of byte, boolean, short, char, int, + * long, float or double values (this is equivalent to using + * {@link #visitArray visitArray} and visiting each array element + * in turn, but is more convenient). + */ + public void visit(String name, Object value) { + if (av != null) { + av.visit(name, value); + } + } + + /** + * Visits an enumeration value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the enumeration class. + * @param value + * the actual enumeration value. + */ + public void visitEnum(String name, String desc, String value) { + if (av != null) { + av.visitEnum(name, desc, value); + } + } + + /** + * Visits a nested annotation value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or + * null if this visitor is not interested in visiting this + * nested annotation. The nested annotation value must be fully + * visited before calling other methods on this annotation + * visitor. + */ + public AnnotationVisitor visitAnnotation(String name, String desc) { + if (av != null) { + return av.visitAnnotation(name, desc); + } + return null; + } + + /** + * Visits an array value of the annotation. Note that arrays of primitive + * types (such as byte, boolean, short, char, int, long, float or double) + * can be passed as value to {@link #visit visit}. This is what + * {@link ClassReader} does. + * + * @param name + * the value name. + * @return a visitor to visit the actual array value elements, or + * null if this visitor is not interested in visiting these + * values. The 'name' parameters passed to the methods of this + * visitor are ignored. All the array values must be visited + * before calling other methods on this annotation visitor. + */ + public AnnotationVisitor visitArray(String name) { + if (av != null) { + return av.visitArray(name); + } + return null; + } + + /** + * Visits the end of the annotation. + */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationWriter.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationWriter.java new file mode 100644 index 0000000000..69f90c60e4 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/AnnotationWriter.java @@ -0,0 +1,318 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * An {@link AnnotationVisitor} that generates annotations in bytecode form. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter extends AnnotationVisitor { + + /** + * The class writer to which this annotation must be added. + */ + private final ClassWriter cw; + + /** + * The number of values in this annotation. + */ + private int size; + + /** + * true if values are named, false otherwise. Annotation + * writers used for annotation default and annotation arrays use unnamed + * values. + */ + private final boolean named; + + /** + * The annotation values in bytecode form. This byte vector only contains + * the values themselves, i.e. the number of values must be stored as a + * unsigned short just before these bytes. + */ + private final ByteVector bv; + + /** + * The byte vector to be used to store the number of values of this + * annotation. See {@link #bv}. + */ + private final ByteVector parent; + + /** + * Where the number of values of this annotation must be stored in + * {@link #parent}. + */ + private final int offset; + + /** + * Next annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter next; + + /** + * Previous annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter prev; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param cw + * the class writer to which this annotation must be added. + * @param named + * true if values are named, false otherwise. + * @param bv + * where the annotation values must be stored. + * @param parent + * where the number of annotation values must be stored. + * @param offset + * where in parent the number of annotation values must + * be stored. + */ + AnnotationWriter(final ClassWriter cw, final boolean named, + final ByteVector bv, final ByteVector parent, final int offset) { + super(Opcodes.ASM4); + this.cw = cw; + this.named = named; + this.bv = bv; + this.parent = parent; + this.offset = offset; + } + + // ------------------------------------------------------------------------ + // Implementation of the AnnotationVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + if (value instanceof String) { + bv.put12('s', cw.newUTF8((String) value)); + } else if (value instanceof Byte) { + bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int v = ((Boolean) value).booleanValue() ? 1 : 0; + bv.put12('Z', cw.newInteger(v).index); + } else if (value instanceof Character) { + bv.put12('C', cw.newInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + bv.put12('S', cw.newInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + bv.put12('c', cw.newUTF8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] v = (byte[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('B', cw.newInteger(v[i]).index); + } + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] v = (short[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('S', cw.newInteger(v[i]).index); + } + } else if (value instanceof char[]) { + char[] v = (char[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('C', cw.newInteger(v[i]).index); + } + } else if (value instanceof int[]) { + int[] v = (int[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('I', cw.newInteger(v[i]).index); + } + } else if (value instanceof long[]) { + long[] v = (long[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('J', cw.newLong(v[i]).index); + } + } else if (value instanceof float[]) { + float[] v = (float[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('F', cw.newFloat(v[i]).index); + } + } else if (value instanceof double[]) { + double[] v = (double[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('D', cw.newDouble(v[i]).index); + } + } else { + Item i = cw.newConstItem(value); + bv.put12(".s.IFJDCS".charAt(i.type), i.index); + } + } + + @Override + public void visitEnum(final String name, final String desc, + final String value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation(final String name, + final String desc) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag and type, and reserve space for values count + bv.put12('@', cw.newUTF8(desc)).putShort(0); + return new AnnotationWriter(cw, true, bv, bv, bv.length - 2); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag, and reserve space for array size + bv.put12('[', 0); + return new AnnotationWriter(cw, false, bv, bv, bv.length - 2); + } + + @Override + public void visitEnd() { + if (parent != null) { + byte[] data = parent.data; + data[offset] = (byte) (size >>> 8); + data[offset + 1] = (byte) size; + } + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this annotation writer list. + * + * @return the size of this annotation writer list. + */ + int getSize() { + int size = 0; + AnnotationWriter aw = this; + while (aw != null) { + size += aw.bv.length; + aw = aw.next; + } + return size; + } + + /** + * Puts the annotations of this annotation writer list into the given byte + * vector. + * + * @param out + * where the annotations must be put. + */ + void put(final ByteVector out) { + int n = 0; + int size = 2; + AnnotationWriter aw = this; + AnnotationWriter last = null; + while (aw != null) { + ++n; + size += aw.bv.length; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putInt(size); + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + + /** + * Puts the given annotation lists into the given byte vector. + * + * @param panns + * an array of annotation writer lists. + * @param off + * index of the first annotation to be written. + * @param out + * where the annotations must be put. + */ + static void put(final AnnotationWriter[] panns, final int off, + final ByteVector out) { + int size = 1 + 2 * (panns.length - off); + for (int i = off; i < panns.length; ++i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + out.putInt(size).putByte(panns.length - off); + for (int i = off; i < panns.length; ++i) { + AnnotationWriter aw = panns[i]; + AnnotationWriter last = null; + int n = 0; + while (aw != null) { + ++n; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Attribute.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Attribute.java new file mode 100644 index 0000000000..1f50ab66d6 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Attribute.java @@ -0,0 +1,255 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A non standard class, field, method or code attribute. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** + * The type of this attribute. + */ + public final String type; + + /** + * The raw value of this attribute, used only for unknown attributes. + */ + byte[] value; + + /** + * The next attribute in this attribute list. May be null. + */ + Attribute next; + + /** + * Constructs a new empty attribute. + * + * @param type + * the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns true if this type of attribute is unknown. The default + * implementation of this method always returns true. + * + * @return true if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns true if this type of attribute is a code attribute. + * + * @return true if this type of attribute is a code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or null if + * this attribute is not a code attribute that contains labels. + */ + protected Label[] getLabels() { + return null; + } + + /** + * Reads a {@link #type type} attribute. This method must return a + * new {@link Attribute} object, of type {@link #type type}, + * corresponding to the len bytes starting at the given offset, in + * the given class reader. + * + * @param cr + * the class that contains the attribute to be read. + * @param off + * index of the first byte of the attribute's content in + * {@link ClassReader#b cr.b}. The 6 attribute header bytes, + * containing the type and the length of the attribute, are not + * taken into account here. + * @param len + * the length of the attribute's content. + * @param buf + * buffer to be used to call {@link ClassReader#readUTF8 + * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass} + * or {@link ClassReader#readConst readConst}. + * @param codeOff + * index of the first byte of code's attribute content in + * {@link ClassReader#b cr.b}, or -1 if the attribute to be read + * is not a code attribute. The 6 attribute header bytes, + * containing the type and the length of the attribute, are not + * taken into account here. + * @param labels + * the labels of the method's code, or null if the + * attribute to be read is not a code attribute. + * @return a new {@link Attribute} object corresponding to the given + * bytes. + */ + protected Attribute read(final ClassReader cr, final int off, + final int len, final char[] buf, final int codeOff, + final Label[] labels) { + Attribute attr = new Attribute(type); + attr.value = new byte[len]; + System.arraycopy(cr.b, off, attr.value, 0, len); + return attr; + } + + /** + * Returns the byte array form of this attribute. + * + * @param cw + * the class to which this attribute must be added. This + * parameter can be used to add to the constant pool of this + * class the items that corresponds to this attribute. + * @param code + * the bytecode of the method corresponding to this code + * attribute, or null if this attribute is not a code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to this + * code attribute, or null if this attribute is not a + * code attribute. + * @param maxStack + * the maximum stack size of the method corresponding to this + * code attribute, or -1 if this attribute is not a code + * attribute. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to this code attribute, or -1 if this attribute + * is not a code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write(final ClassWriter cw, final byte[] code, + final int len, final int maxStack, final int maxLocals) { + ByteVector v = new ByteVector(); + v.data = value; + v.length = value.length; + return v; + } + + /** + * Returns the length of the attribute list that begins with this attribute. + * + * @return the length of the attribute list that begins with this attribute. + */ + final int getCount() { + int count = 0; + Attribute attr = this; + while (attr != null) { + count += 1; + attr = attr.next; + } + return count; + } + + /** + * Returns the size of all the attributes in this attribute list. + * + * @param cw + * the class writer to be used to convert the attributes into + * byte arrays, with the {@link #write write} method. + * @param code + * the bytecode of the method corresponding to these code + * attributes, or null if these attributes are not code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to + * these code attributes, or null if these attributes + * are not code attributes. + * @param maxStack + * the maximum stack size of the method corresponding to these + * code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these + * attributes are not code attributes. + * @return the size of all the attributes in this attribute list. This size + * includes the size of the attribute headers. + */ + final int getSize(final ClassWriter cw, final byte[] code, final int len, + final int maxStack, final int maxLocals) { + Attribute attr = this; + int size = 0; + while (attr != null) { + cw.newUTF8(attr.type); + size += attr.write(cw, code, len, maxStack, maxLocals).length + 6; + attr = attr.next; + } + return size; + } + + /** + * Writes all the attributes of this attribute list in the given byte + * vector. + * + * @param cw + * the class writer to be used to convert the attributes into + * byte arrays, with the {@link #write write} method. + * @param code + * the bytecode of the method corresponding to these code + * attributes, or null if these attributes are not code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to + * these code attributes, or null if these attributes + * are not code attributes. + * @param maxStack + * the maximum stack size of the method corresponding to these + * code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these + * attributes are not code attributes. + * @param out + * where the attributes must be written. + */ + final void put(final ClassWriter cw, final byte[] code, final int len, + final int maxStack, final int maxLocals, final ByteVector out) { + Attribute attr = this; + while (attr != null) { + ByteVector b = attr.write(cw, code, len, maxStack, maxLocals); + out.putShort(cw.newUTF8(attr.type)).putInt(b.length); + out.putByteArray(b.data, 0, b.length); + attr = attr.next; + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ByteVector.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ByteVector.java new file mode 100644 index 0000000000..229e3909ae --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ByteVector.java @@ -0,0 +1,312 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to + * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** + * The content of this vector. + */ + byte[] data; + + /** + * Actual number of bytes in this vector. + */ + int length; + + /** + * Constructs a new {@link ByteVector ByteVector} with a default initial + * size. + */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector ByteVector} with the given initial + * size. + * + * @param initialSize + * the initial size of the byte vector to be constructed. + */ + public ByteVector(final int initialSize) { + data = new byte[initialSize]; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b + * a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int b) { + int length = this.length; + if (length + 1 > data.length) { + enlarge(1); + } + data[length++] = (byte) b; + this.length = length; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b1 + * a byte. + * @param b2 + * another byte. + * @return this byte vector. + */ + ByteVector put11(final int b1, final int b2) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) b1; + data[length++] = (byte) b2; + this.length = length; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param s + * a short. + * @return this byte vector. + */ + public ByteVector putShort(final int s) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b + * a byte. + * @param s + * a short. + * @return this byte vector. + */ + ByteVector put12(final int b, final int s) { + int length = this.length; + if (length + 3 > data.length) { + enlarge(3); + } + byte[] data = this.data; + data[length++] = (byte) b; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param i + * an int. + * @return this byte vector. + */ + public ByteVector putInt(final int i) { + int length = this.length; + if (length + 4 > data.length) { + enlarge(4); + } + byte[] data = this.data; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param l + * a long. + * @return this byte vector. + */ + public ByteVector putLong(final long l) { + int length = this.length; + if (length + 8 > data.length) { + enlarge(8); + } + byte[] data = this.data; + int i = (int) (l >>> 32); + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + i = (int) l; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param s + * a String whose UTF8 encoded length must be less than 65536. + * @return this byte vector. + */ + public ByteVector putUTF8(final String s) { + int charLength = s.length(); + if (charLength > 65535) { + throw new IllegalArgumentException(); + } + int len = length; + if (len + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] data = this.data; + // optimistic algorithm: instead of computing the byte length and then + // serializing the string (which requires two loops), we assume the byte + // length is equal to char length (which is the most frequent case), and + // we start serializing the string right away. During the serialization, + // if we find that this assumption is wrong, we continue with the + // general method. + data[len++] = (byte) (charLength >>> 8); + data[len++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char c = s.charAt(i); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else { + int byteLength = i; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + byteLength++; + } else if (c > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + if (byteLength > 65535) { + throw new IllegalArgumentException(); + } + data[length] = (byte) (byteLength >>> 8); + data[length + 1] = (byte) byteLength; + if (length + 2 + byteLength > data.length) { + length = len; + enlarge(2 + byteLength); + data = this.data; + } + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else if (c > '\u07FF') { + data[len++] = (byte) (0xE0 | c >> 12 & 0xF); + data[len++] = (byte) (0x80 | c >> 6 & 0x3F); + data[len++] = (byte) (0x80 | c & 0x3F); + } else { + data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); + data[len++] = (byte) (0x80 | c & 0x3F); + } + } + break; + } + } + length = len; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b + * an array of bytes. May be null to put len + * null bytes into this byte vector. + * @param off + * index of the fist byte of b that must be copied. + * @param len + * number of bytes of b that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray(final byte[] b, final int off, final int len) { + if (length + len > data.length) { + enlarge(len); + } + if (b != null) { + System.arraycopy(b, off, data, length, len); + } + length += len; + return this; + } + + /** + * Enlarge this byte vector so that it can receive n more bytes. + * + * @param size + * number of additional bytes that this byte vector should be + * able to receive. + */ + private void enlarge(final int size) { + int length1 = 2 * data.length; + int length2 = length + size; + byte[] newData = new byte[length1 > length2 ? length1 : length2]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassReader.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassReader.java new file mode 100644 index 0000000000..ed6ac9d59e --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassReader.java @@ -0,0 +1,2202 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A Java class parser to make a {@link ClassVisitor} visit an existing class. + * This class parses a byte array conforming to the Java class file format and + * calls the appropriate visit methods of a given class visitor for each field, + * method and bytecode instruction encountered. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * True to enable signatures support. + */ + static final boolean SIGNATURES = true; + + /** + * True to enable annotations support. + */ + static final boolean ANNOTATIONS = true; + + /** + * True to enable stack map frames support. + */ + static final boolean FRAMES = true; + + /** + * True to enable bytecode writing support. + */ + static final boolean WRITER = true; + + /** + * True to enable JSR_W and GOTO_W support. + */ + static final boolean RESIZE = true; + + /** + * Flag to skip method code. If this class is set CODE + * attribute won't be visited. This can be used, for example, to retrieve + * annotations for methods and method parameters. + */ + public static final int SKIP_CODE = 1; + + /** + * Flag to skip the debug information in the class. If this flag is set the + * debug information of the class is not visited, i.e. the + * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be + * called. + */ + public static final int SKIP_DEBUG = 2; + + /** + * Flag to skip the stack map frames in the class. If this flag is set the + * stack map frames of the class is not visited, i.e. the + * {@link MethodVisitor#visitFrame visitFrame} method will not be called. + * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is + * used: it avoids visiting frames that will be ignored and recomputed from + * scratch in the class writer. + */ + public static final int SKIP_FRAMES = 4; + + /** + * Flag to expand the stack map frames. By default stack map frames are + * visited in their original format (i.e. "expanded" for classes whose + * version is less than V1_6, and "compressed" for the other classes). If + * this flag is set, stack map frames are always visited in expanded format + * (this option adds a decompression/recompression step in ClassReader and + * ClassWriter which degrades performances quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * The class to be parsed. The content of this array must not be + * modified. This field is intended for {@link Attribute} sub classes, and + * is normally not needed by class generators or adapters. + */ + public final byte[] b; + + /** + * The start index of each constant pool item in {@link #b b}, plus one. The + * one byte offset skips the constant pool item tag that indicates its type. + */ + private final int[] items; + + /** + * The String objects corresponding to the CONSTANT_Utf8 items. This cache + * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, + * which GREATLY improves performances (by a factor 2 to 3). This caching + * strategy could be extended to all constant pool items, but its benefit + * would not be so great for these items (because they are much less + * expensive to parse than CONSTANT_Utf8 items). + */ + private final String[] strings; + + /** + * Maximum length of the strings contained in the constant pool of the + * class. + */ + private final int maxStringLength; + + /** + * Start index of the class header information (access, name...) in + * {@link #b b}. + */ + public final int header; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b + * the bytecode of the class to be read. + */ + public ClassReader(final byte[] b) { + this(b, 0, b.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b + * the bytecode of the class to be read. + * @param off + * the start offset of the class data. + * @param len + * the length of the class data. + */ + public ClassReader(final byte[] b, final int off, final int len) { + this.b = b; + // checks the class version + if (readShort(off + 6) > Opcodes.V1_7) { + throw new IllegalArgumentException(); + } + // parses the constant pool + items = new int[readUnsignedShort(off + 8)]; + int n = items.length; + strings = new String[n]; + int max = 0; + int index = off + 10; + for (int i = 1; i < n; ++i) { + items[i] = index + 1; + int size; + switch (b[index]) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + case ClassWriter.INT: + case ClassWriter.FLOAT: + case ClassWriter.NAME_TYPE: + case ClassWriter.INDY: + size = 5; + break; + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + size = 9; + ++i; + break; + case ClassWriter.UTF8: + size = 3 + readUnsignedShort(index + 1); + if (size > max) { + max = size; + } + break; + case ClassWriter.HANDLE: + size = 4; + break; + // case ClassWriter.CLASS: + // case ClassWriter.STR: + // case ClassWriter.MTYPE + default: + size = 3; + break; + } + index += size; + } + maxStringLength = max; + // the class header information starts just after the constant pool + header = index; + } + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may + * not reflect Deprecated and Synthetic flags when bytecode is before 1.5 + * and those flags are represented by attributes. + * + * @return the class access flags + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the internal class name + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, the + * super class is {@link Object}. + * + * @return the internal name of super class, or null for + * {@link Object} class. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the array of internal names for all implemented interfaces or + * null. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + int index = header + 6; + int n = readUnsignedShort(index); + String[] interfaces = new String[n]; + if (n > 0) { + char[] buf = new char[maxStringLength]; + for (int i = 0; i < n; ++i) { + index += 2; + interfaces[i] = readClass(index, buf); + } + } + return interfaces; + } + + /** + * Copies the constant pool data into the given {@link ClassWriter}. Should + * be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter + * the {@link ClassWriter} to copy constant pool into. + */ + void copyPool(final ClassWriter classWriter) { + char[] buf = new char[maxStringLength]; + int ll = items.length; + Item[] items2 = new Item[ll]; + for (int i = 1; i < ll; i++) { + int index = items[i]; + int tag = b[index - 1]; + Item item = new Item(i); + int nameType; + switch (tag) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + nameType = items[readUnsignedShort(index + 2)]; + item.set(tag, readClass(index, buf), readUTF8(nameType, buf), + readUTF8(nameType + 2, buf)); + break; + case ClassWriter.INT: + item.set(readInt(index)); + break; + case ClassWriter.FLOAT: + item.set(Float.intBitsToFloat(readInt(index))); + break; + case ClassWriter.NAME_TYPE: + item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf), + null); + break; + case ClassWriter.LONG: + item.set(readLong(index)); + ++i; + break; + case ClassWriter.DOUBLE: + item.set(Double.longBitsToDouble(readLong(index))); + ++i; + break; + case ClassWriter.UTF8: { + String s = strings[i]; + if (s == null) { + index = items[i]; + s = strings[i] = readUTF(index + 2, + readUnsignedShort(index), buf); + } + item.set(tag, s, null, null); + break; + } + case ClassWriter.HANDLE: { + int fieldOrMethodRef = items[readUnsignedShort(index + 1)]; + nameType = items[readUnsignedShort(fieldOrMethodRef + 2)]; + item.set(ClassWriter.HANDLE_BASE + readByte(index), + readClass(fieldOrMethodRef, buf), + readUTF8(nameType, buf), readUTF8(nameType + 2, buf)); + break; + } + case ClassWriter.INDY: + if (classWriter.bootstrapMethods == null) { + copyBootstrapMethods(classWriter, items2, buf); + } + nameType = items[readUnsignedShort(index + 2)]; + item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf), + readUnsignedShort(index)); + break; + // case ClassWriter.STR: + // case ClassWriter.CLASS: + // case ClassWriter.MTYPE + default: + item.set(tag, readUTF8(index, buf), null, null); + break; + } + + int index2 = item.hashCode % items2.length; + item.next = items2[index2]; + items2[index2] = item; + } + + int off = items[1] - 1; + classWriter.pool.putByteArray(b, off, header - off); + classWriter.items = items2; + classWriter.threshold = (int) (0.75d * ll); + classWriter.index = ll; + } + + /** + * Copies the bootstrap method data into the given {@link ClassWriter}. + * Should be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter + * the {@link ClassWriter} to copy bootstrap methods into. + */ + private void copyBootstrapMethods(final ClassWriter classWriter, + final Item[] items, final char[] c) { + // finds the "BootstrapMethods" attribute + int u = getAttributes(); + boolean found = false; + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + if ("BootstrapMethods".equals(attrName)) { + found = true; + break; + } + u += 6 + readInt(u + 4); + } + if (!found) { + return; + } + // copies the bootstrap methods in the class writer + int boostrapMethodCount = readUnsignedShort(u + 8); + for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) { + int position = v - u - 10; + int hashCode = readConst(readUnsignedShort(v), c).hashCode(); + for (int k = readUnsignedShort(v + 2); k > 0; --k) { + hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode(); + v += 2; + } + v += 4; + Item item = new Item(j); + item.set(position, hashCode & 0x7FFFFFFF); + int index = item.hashCode % items.length; + item.next = items[index]; + items[index] = item; + } + int attrSize = readInt(u + 4); + ByteVector bootstrapMethods = new ByteVector(attrSize + 62); + bootstrapMethods.putByteArray(b, u + 10, attrSize - 2); + classWriter.bootstrapMethodsCount = boostrapMethodCount; + classWriter.bootstrapMethods = bootstrapMethods; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param is + * an input stream from which to read the class. + * @throws IOException + * if a problem occurs during reading. + */ + public ClassReader(final InputStream is) throws IOException { + this(readClass(is, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param name + * the binary qualified name of the class to be read. + * @throws IOException + * if an exception occurs during reading. + */ + public ClassReader(final String name) throws IOException { + this(readClass( + ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + + ".class"), true)); + } + + /** + * Reads the bytecode of a class. + * + * @param is + * an input stream from which to read the class. + * @param close + * true to close the input stream after reading. + * @return the bytecode read from the given input stream. + * @throws IOException + * if a problem occurs during reading. + */ + private static byte[] readClass(final InputStream is, boolean close) + throws IOException { + if (is == null) { + throw new IOException("Class not found"); + } + try { + byte[] b = new byte[is.available()]; + int len = 0; + while (true) { + int n = is.read(b, len, b.length - len); + if (n == -1) { + if (len < b.length) { + byte[] c = new byte[len]; + System.arraycopy(b, 0, c, 0, len); + b = c; + } + return b; + } + len += n; + if (len == b.length) { + int last = is.read(); + if (last < 0) { + return b; + } + byte[] c = new byte[b.length + 1000]; + System.arraycopy(b, 0, c, 0, len); + c[len++] = (byte) last; + b = c; + } + } + } finally { + if (close) { + is.close(); + } + } + } + + // ------------------------------------------------------------------------ + // Public methods + // ------------------------------------------------------------------------ + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader} + * . This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor + * the visitor that must visit this class. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} + * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, final int flags) { + accept(classVisitor, new Attribute[0], flags); + } + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor + * the visitor that must visit this class. + * @param attrs + * prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to + * the type of one the prototypes will not be parsed: its byte + * array value will be passed unchanged to the ClassWriter. + * This may corrupt it if this value contains references to + * the constant pool, or has syntactic or semantic links with a + * class element that has been transformed by a class adapter + * between the reader and the writer. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} + * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, + final Attribute[] attrs, final int flags) { + int u = header; // current offset in the class file + char[] c = new char[maxStringLength]; // buffer used to read strings + + Context context = new Context(); + context.attrs = attrs; + context.flags = flags; + context.buffer = c; + + // reads the class declaration + int access = readUnsignedShort(u); + String name = readClass(u + 2, c); + String superClass = readClass(u + 4, c); + String[] interfaces = new String[readUnsignedShort(u + 6)]; + u += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(u, c); + u += 2; + } + + // reads the class attributes + String signature = null; + String sourceFile = null; + String sourceDebug = null; + String enclosingOwner = null; + String enclosingName = null; + String enclosingDesc = null; + int anns = 0; + int ianns = 0; + int innerClasses = 0; + Attribute attributes = null; + + u = getAttributes(); + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("SourceFile".equals(attrName)) { + sourceFile = readUTF8(u + 8, c); + } else if ("InnerClasses".equals(attrName)) { + innerClasses = u + 8; + } else if ("EnclosingMethod".equals(attrName)) { + enclosingOwner = readClass(u + 8, c); + int item = readUnsignedShort(u + 10); + if (item != 0) { + enclosingName = readUTF8(items[item], c); + enclosingDesc = readUTF8(items[item] + 2, c); + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if ("SourceDebugExtension".equals(attrName)) { + int len = readInt(u + 4); + sourceDebug = readUTF(u + 8, len, new char[len]); + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if ("BootstrapMethods".equals(attrName)) { + int[] bootstrapMethods = new int[readUnsignedShort(u + 8)]; + for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) { + bootstrapMethods[j] = v; + v += 2 + readUnsignedShort(v + 2) << 1; + } + context.bootstrapMethods = bootstrapMethods; + } else { + Attribute attr = readAttribute(attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + + // visits the class declaration + classVisitor.visit(readInt(items[1] - 7), access, name, signature, + superClass, interfaces); + + // visits the source and debug info + if ((flags & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebug != null)) { + classVisitor.visitSource(sourceFile, sourceDebug); + } + + // visits the outer class + if (enclosingOwner != null) { + classVisitor.visitOuterClass(enclosingOwner, enclosingName, + enclosingDesc); + } + + // visits the class annotations + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitAnnotation(readUTF8(v, c), false)); + } + } + + // visits the attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + classVisitor.visitAttribute(attributes); + attributes = attr; + } + + // visits the inner classes + if (innerClasses != 0) { + int v = innerClasses + 2; + for (int i = readUnsignedShort(innerClasses); i > 0; --i) { + classVisitor.visitInnerClass(readClass(v, c), + readClass(v + 2, c), readUTF8(v + 4, c), + readUnsignedShort(v + 6)); + v += 8; + } + } + + // visits the fields and methods + u = header + 10 + 2 * interfaces.length; + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + u = readField(classVisitor, context, u); + } + u += 2; + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + u = readMethod(classVisitor, context, u); + } + + // visits the end of the class + classVisitor.visitEnd(); + } + + /** + * Reads a field and makes the given visitor visit it. + * + * @param classVisitor + * the visitor that must visit the field. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the field in the class file. + * @return the offset of the first byte following the field in the class. + */ + private int readField(final ClassVisitor classVisitor, + final Context context, int u) { + // reads the field declaration + char[] c = context.buffer; + int access = readUnsignedShort(u); + String name = readUTF8(u + 2, c); + String desc = readUTF8(u + 4, c); + u += 6; + + // reads the field attributes + String signature = null; + int anns = 0; + int ianns = 0; + Object value = null; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("ConstantValue".equals(attrName)) { + int item = readUnsignedShort(u + 8); + value = item == 0 ? null : readConst(item, c); + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else { + Attribute attr = readAttribute(context.attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // visits the field declaration + FieldVisitor fv = classVisitor.visitField(access, name, desc, + signature, value); + if (fv == null) { + return u; + } + + // visits the field annotations + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + fv.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + fv.visitAnnotation(readUTF8(v, c), false)); + } + } + + // visits the field attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + fv.visitAttribute(attributes); + attributes = attr; + } + + // visits the end of the field + fv.visitEnd(); + + return u; + } + + /** + * Reads a method and makes the given visitor visit it. + * + * @param classVisitor + * the visitor that must visit the method. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the method in the class file. + * @return the offset of the first byte following the method in the class. + */ + private int readMethod(final ClassVisitor classVisitor, + final Context context, int u) { + // reads the method declaration + char[] c = context.buffer; + int access = readUnsignedShort(u); + String name = readUTF8(u + 2, c); + String desc = readUTF8(u + 4, c); + u += 6; + + // reads the method attributes + int code = 0; + int exception = 0; + String[] exceptions = null; + String signature = null; + int anns = 0; + int ianns = 0; + int dann = 0; + int mpanns = 0; + int impanns = 0; + int firstAttribute = u; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("Code".equals(attrName)) { + if ((context.flags & SKIP_CODE) == 0) { + code = u + 8; + } + } else if ("Exceptions".equals(attrName)) { + exceptions = new String[readUnsignedShort(u + 8)]; + exception = u + 10; + for (int j = 0; j < exceptions.length; ++j) { + exceptions[j] = readClass(exception, c); + exception += 2; + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { + dann = u + 8; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleParameterAnnotations".equals(attrName)) { + mpanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleParameterAnnotations".equals(attrName)) { + impanns = u + 8; + } else { + Attribute attr = readAttribute(context.attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // visits the method declaration + MethodVisitor mv = classVisitor.visitMethod(access, name, desc, + signature, exceptions); + if (mv == null) { + return u; + } + + /* + * if the returned MethodVisitor is in fact a MethodWriter, it means + * there is no method adapter between the reader and the writer. If, in + * addition, the writer's constant pool was copied from this reader + * (mw.cw.cr == this), and the signature and exceptions of the method + * have not been changed, then it is possible to skip all visit events + * and just copy the original code of the method to the writer (the + * access, name and descriptor can have been changed, this is not + * important since they are not copied as is from the reader). + */ + if (WRITER && mv instanceof MethodWriter) { + MethodWriter mw = (MethodWriter) mv; + if (mw.cw.cr == this && signature == mw.signature) { + boolean sameExceptions = false; + if (exceptions == null) { + sameExceptions = mw.exceptionCount == 0; + } else if (exceptions.length == mw.exceptionCount) { + sameExceptions = true; + for (int j = exceptions.length - 1; j >= 0; --j) { + exception -= 2; + if (mw.exceptions[j] != readUnsignedShort(exception)) { + sameExceptions = false; + break; + } + } + } + if (sameExceptions) { + /* + * we do not copy directly the code into MethodWriter to + * save a byte array copy operation. The real copy will be + * done in ClassWriter.toByteArray(). + */ + mw.classReaderOffset = firstAttribute; + mw.classReaderLength = u - firstAttribute; + return u; + } + } + } + + // visits the method annotations + if (ANNOTATIONS && dann != 0) { + AnnotationVisitor dv = mv.visitAnnotationDefault(); + readAnnotationValue(dann, c, null, dv); + if (dv != null) { + dv.visitEnd(); + } + } + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + mv.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + mv.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && mpanns != 0) { + readParameterAnnotations(mpanns, desc, c, true, mv); + } + if (ANNOTATIONS && impanns != 0) { + readParameterAnnotations(impanns, desc, c, false, mv); + } + + // visits the method attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + mv.visitAttribute(attributes); + attributes = attr; + } + + // visits the method code + if (code != 0) { + context.access = access; + context.name = name; + context.desc = desc; + mv.visitCode(); + readCode(mv, context, code); + } + + // visits the end of the method + mv.visitEnd(); + + return u; + } + + /** + * Reads the bytecode of a method and makes the given visitor visit it. + * + * @param mv + * the visitor that must visit the method's code. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the code attribute in the class file. + */ + private void readCode(final MethodVisitor mv, final Context context, int u) { + // reads the header + byte[] b = this.b; + char[] c = context.buffer; + int maxStack = readUnsignedShort(u); + int maxLocals = readUnsignedShort(u + 2); + int codeLength = readInt(u + 4); + u += 8; + + // reads the bytecode to find the labels + int codeStart = u; + int codeEnd = u + codeLength; + Label[] labels = new Label[codeLength + 2]; + readLabel(codeLength + 1, labels); + while (u < codeEnd) { + int offset = u - codeStart; + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + readLabel(offset + readShort(u + 1), labels); + u += 3; + break; + case ClassWriter.LABELW_INSN: + readLabel(offset + readInt(u + 1), labels); + u += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + readLabel(offset + readInt(u), labels); + for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) { + readLabel(offset + readInt(u + 12), labels); + u += 4; + } + u += 12; + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + readLabel(offset + readInt(u), labels); + for (int i = readInt(u + 4); i > 0; --i) { + readLabel(offset + readInt(u + 12), labels); + u += 8; + } + u += 8; + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case MANA_INSN: + default: + u += 4; + break; + } + } + + // reads the try catch entries to find the labels, and also visits them + for (int i = readUnsignedShort(u); i > 0; --i) { + Label start = readLabel(readUnsignedShort(u + 2), labels); + Label end = readLabel(readUnsignedShort(u + 4), labels); + Label handler = readLabel(readUnsignedShort(u + 6), labels); + String type = readUTF8(items[readUnsignedShort(u + 8)], c); + mv.visitTryCatchBlock(start, end, handler, type); + u += 8; + } + u += 2; + + // reads the code attributes + int varTable = 0; + int varTypeTable = 0; + boolean zip = true; + boolean unzip = (context.flags & EXPAND_FRAMES) != 0; + int stackMap = 0; + int stackMapSize = 0; + int frameCount = 0; + Context frame = null; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + if ("LocalVariableTable".equals(attrName)) { + if ((context.flags & SKIP_DEBUG) == 0) { + varTable = u + 8; + for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { + int label = readUnsignedShort(v + 10); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + label += readUnsignedShort(v + 12); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + v += 10; + } + } + } else if ("LocalVariableTypeTable".equals(attrName)) { + varTypeTable = u + 8; + } else if ("LineNumberTable".equals(attrName)) { + if ((context.flags & SKIP_DEBUG) == 0) { + for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { + int label = readUnsignedShort(v + 10); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + labels[label].line = readUnsignedShort(v + 12); + v += 4; + } + } + } else if (FRAMES && "StackMapTable".equals(attrName)) { + if ((context.flags & SKIP_FRAMES) == 0) { + stackMap = u + 10; + stackMapSize = readInt(u + 4); + frameCount = readUnsignedShort(u + 8); + } + /* + * here we do not extract the labels corresponding to the + * attribute content. This would require a full parsing of the + * attribute, which would need to be repeated in the second + * phase (see below). Instead the content of the attribute is + * read one frame at a time (i.e. after a frame has been + * visited, the next frame is read), and the labels it contains + * are also extracted one frame at a time. Thanks to the + * ordering of frames, having only a "one frame lookahead" is + * not a problem, i.e. it is not possible to see an offset + * smaller than the offset of the current insn and for which no + * Label exist. + */ + /* + * This is not true for UNINITIALIZED type offsets. We solve + * this by parsing the stack map table without a full decoding + * (see below). + */ + } else if (FRAMES && "StackMap".equals(attrName)) { + if ((context.flags & SKIP_FRAMES) == 0) { + zip = false; + stackMap = u + 10; + stackMapSize = readInt(u + 4); + frameCount = readUnsignedShort(u + 8); + } + /* + * IMPORTANT! here we assume that the frames are ordered, as in + * the StackMapTable attribute, although this is not guaranteed + * by the attribute format. + */ + } else { + for (int j = 0; j < context.attrs.length; ++j) { + if (context.attrs[j].type.equals(attrName)) { + Attribute attr = context.attrs[j].read(this, u + 8, + readInt(u + 4), c, codeStart - 8, labels); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // generates the first (implicit) stack map frame + if (FRAMES && stackMap != 0) { + /* + * for the first explicit frame the offset is not offset_delta + 1 + * but only offset_delta; setting the implicit frame offset to -1 + * allow the use of the "offset_delta + 1" rule in all cases + */ + frame = context; + frame.offset = -1; + frame.mode = 0; + frame.localCount = 0; + frame.localDiff = 0; + frame.stackCount = 0; + frame.local = new Object[maxLocals]; + frame.stack = new Object[maxStack]; + if (unzip) { + getImplicitFrame(context); + } + /* + * Finds labels for UNINITIALIZED frame types. Instead of decoding + * each element of the stack map table, we look for 3 consecutive + * bytes that "look like" an UNINITIALIZED type (tag 8, offset + * within code bounds, NEW instruction at this offset). We may find + * false positives (i.e. not real UNINITIALIZED types), but this + * should be rare, and the only consequence will be the creation of + * an unneeded label. This is better than creating a label for each + * NEW instruction, and faster than fully decoding the whole stack + * map table. + */ + for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) { + if (b[i] == 8) { // UNINITIALIZED FRAME TYPE + int v = readUnsignedShort(i + 1); + if (v >= 0 && v < codeLength) { + if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) { + readLabel(v, labels); + } + } + } + } + } + + // visits the instructions + u = codeStart; + while (u < codeEnd) { + int offset = u - codeStart; + + // visits the label and line number for this offset, if any + Label l = labels[offset]; + if (l != null) { + mv.visitLabel(l); + if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) { + mv.visitLineNumber(l.line, l); + } + } + + // visits the frame for this offset, if any + while (FRAMES && frame != null + && (frame.offset == offset || frame.offset == -1)) { + // if there is a frame for this offset, makes the visitor visit + // it, and reads the next frame if there is one. + if (frame.offset != -1) { + if (!zip || unzip) { + mv.visitFrame(Opcodes.F_NEW, frame.localCount, + frame.local, frame.stackCount, frame.stack); + } else { + mv.visitFrame(frame.mode, frame.localDiff, frame.local, + frame.stackCount, frame.stack); + } + } + if (frameCount > 0) { + stackMap = readFrame(stackMap, zip, unzip, labels, frame); + --frameCount; + } else { + frame = null; + } + } + + // visits the instruction at this offset + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + mv.visitInsn(opcode); + u += 1; + break; + case ClassWriter.IMPLVAR_INSN: + if (opcode > Opcodes.ISTORE) { + opcode -= 59; // ISTORE_0 + mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), + opcode & 0x3); + } else { + opcode -= 26; // ILOAD_0 + mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + } + u += 1; + break; + case ClassWriter.LABEL_INSN: + mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]); + u += 3; + break; + case ClassWriter.LABELW_INSN: + mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); + u += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4)); + u += 6; + } else { + mv.visitVarInsn(opcode, readUnsignedShort(u + 2)); + u += 4; + } + break; + case ClassWriter.TABL_INSN: { + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + int label = offset + readInt(u); + int min = readInt(u + 4); + int max = readInt(u + 8); + Label[] table = new Label[max - min + 1]; + u += 12; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[offset + readInt(u)]; + u += 4; + } + mv.visitTableSwitchInsn(min, max, labels[label], table); + break; + } + case ClassWriter.LOOK_INSN: { + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + int label = offset + readInt(u); + int len = readInt(u + 4); + int[] keys = new int[len]; + Label[] values = new Label[len]; + u += 8; + for (int i = 0; i < len; ++i) { + keys[i] = readInt(u); + values[i] = labels[offset + readInt(u + 4)]; + u += 8; + } + mv.visitLookupSwitchInsn(labels[label], keys, values); + break; + } + case ClassWriter.VAR_INSN: + mv.visitVarInsn(opcode, b[u + 1] & 0xFF); + u += 2; + break; + case ClassWriter.SBYTE_INSN: + mv.visitIntInsn(opcode, b[u + 1]); + u += 2; + break; + case ClassWriter.SHORT_INSN: + mv.visitIntInsn(opcode, readShort(u + 1)); + u += 3; + break; + case ClassWriter.LDC_INSN: + mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c)); + u += 2; + break; + case ClassWriter.LDCW_INSN: + mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c)); + u += 3; + break; + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.ITFMETH_INSN: { + int cpIndex = items[readUnsignedShort(u + 1)]; + String iowner = readClass(cpIndex, c); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + if (opcode < Opcodes.INVOKEVIRTUAL) { + mv.visitFieldInsn(opcode, iowner, iname, idesc); + } else { + mv.visitMethodInsn(opcode, iowner, iname, idesc); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + u += 5; + } else { + u += 3; + } + break; + } + case ClassWriter.INDYMETH_INSN: { + int cpIndex = items[readUnsignedShort(u + 1)]; + int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)]; + Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c); + int bsmArgCount = readUnsignedShort(bsmIndex + 2); + Object[] bsmArgs = new Object[bsmArgCount]; + bsmIndex += 4; + for (int i = 0; i < bsmArgCount; i++) { + bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c); + bsmIndex += 2; + } + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs); + u += 5; + break; + } + case ClassWriter.TYPE_INSN: + mv.visitTypeInsn(opcode, readClass(u + 1, c)); + u += 3; + break; + case ClassWriter.IINC_INSN: + mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]); + u += 3; + break; + // case MANA_INSN: + default: + mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF); + u += 4; + break; + } + } + if (labels[codeLength] != null) { + mv.visitLabel(labels[codeLength]); + } + + // visits the local variable tables + if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) { + int[] typeTable = null; + if (varTypeTable != 0) { + u = varTypeTable + 2; + typeTable = new int[readUnsignedShort(varTypeTable) * 3]; + for (int i = typeTable.length; i > 0;) { + typeTable[--i] = u + 6; // signature + typeTable[--i] = readUnsignedShort(u + 8); // index + typeTable[--i] = readUnsignedShort(u); // start + u += 10; + } + } + u = varTable + 2; + for (int i = readUnsignedShort(varTable); i > 0; --i) { + int start = readUnsignedShort(u); + int length = readUnsignedShort(u + 2); + int index = readUnsignedShort(u + 8); + String vsignature = null; + if (typeTable != null) { + for (int j = 0; j < typeTable.length; j += 3) { + if (typeTable[j] == start && typeTable[j + 1] == index) { + vsignature = readUTF8(typeTable[j + 2], c); + break; + } + } + } + mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c), + vsignature, labels[start], labels[start + length], + index); + u += 10; + } + } + + // visits the code attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + mv.visitAttribute(attributes); + attributes = attr; + } + + // visits the max stack and max locals values + mv.visitMaxs(maxStack, maxLocals); + } + + /** + * Reads parameter annotations and makes the given visitor visit them. + * + * @param v + * start offset in {@link #b b} of the annotations to be read. + * @param desc + * the method descriptor. + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param visible + * true if the annotations to be read are visible at + * runtime. + * @param mv + * the visitor that must visit the annotations. + */ + private void readParameterAnnotations(int v, final String desc, + final char[] buf, final boolean visible, final MethodVisitor mv) { + int i; + int n = b[v++] & 0xFF; + // workaround for a bug in javac (javac compiler generates a parameter + // annotation array whose size is equal to the number of parameters in + // the Java source file, while it should generate an array whose size is + // equal to the number of parameters in the method descriptor - which + // includes the synthetic parameters added by the compiler). This work- + // around supposes that the synthetic parameters are the first ones. + int synthetics = Type.getArgumentTypes(desc).length - n; + AnnotationVisitor av; + for (i = 0; i < synthetics; ++i) { + // virtual annotation to detect synthetic parameters in MethodWriter + av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false); + if (av != null) { + av.visitEnd(); + } + } + for (; i < n + synthetics; ++i) { + int j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible); + v = readAnnotationValues(v + 2, buf, true, av); + } + } + } + + /** + * Reads the values of an annotation and makes the given visitor visit them. + * + * @param v + * the start offset in {@link #b b} of the values to be read + * (including the unsigned short that gives the number of + * values). + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param named + * if the annotation values are named or not. + * @param av + * the visitor that must visit the values. + * @return the end offset of the annotation values. + */ + private int readAnnotationValues(int v, final char[] buf, + final boolean named, final AnnotationVisitor av) { + int i = readUnsignedShort(v); + v += 2; + if (named) { + for (; i > 0; --i) { + v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av); + } + } else { + for (; i > 0; --i) { + v = readAnnotationValue(v, buf, null, av); + } + } + if (av != null) { + av.visitEnd(); + } + return v; + } + + /** + * Reads a value of an annotation and makes the given visitor visit it. + * + * @param v + * the start offset in {@link #b b} of the value to be read + * (not including the value name constant pool index). + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param name + * the name of the value to be read. + * @param av + * the visitor that must visit the value. + * @return the end offset of the annotation value. + */ + private int readAnnotationValue(int v, final char[] buf, final String name, + final AnnotationVisitor av) { + int i; + if (av == null) { + switch (b[v] & 0xFF) { + case 'e': // enum_const_value + return v + 5; + case '@': // annotation_value + return readAnnotationValues(v + 3, buf, true, null); + case '[': // array_value + return readAnnotationValues(v + 1, buf, false, null); + default: + return v + 3; + } + } + switch (b[v++] & 0xFF) { + case 'I': // pointer to CONSTANT_Integer + case 'J': // pointer to CONSTANT_Long + case 'F': // pointer to CONSTANT_Float + case 'D': // pointer to CONSTANT_Double + av.visit(name, readConst(readUnsignedShort(v), buf)); + v += 2; + break; + case 'B': // pointer to CONSTANT_Byte + av.visit(name, + new Byte((byte) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'Z': // pointer to CONSTANT_Boolean + av.visit(name, + readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE + : Boolean.TRUE); + v += 2; + break; + case 'S': // pointer to CONSTANT_Short + av.visit(name, new Short( + (short) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'C': // pointer to CONSTANT_Char + av.visit(name, new Character( + (char) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 's': // pointer to CONSTANT_Utf8 + av.visit(name, readUTF8(v, buf)); + v += 2; + break; + case 'e': // enum_const_value + av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf)); + v += 4; + break; + case 'c': // class_info + av.visit(name, Type.getType(readUTF8(v, buf))); + v += 2; + break; + case '@': // annotation_value + v = readAnnotationValues(v + 2, buf, true, + av.visitAnnotation(name, readUTF8(v, buf))); + break; + case '[': // array_value + int size = readUnsignedShort(v); + v += 2; + if (size == 0) { + return readAnnotationValues(v - 2, buf, false, + av.visitArray(name)); + } + switch (this.b[v++] & 0xFF) { + case 'B': + byte[] bv = new byte[size]; + for (i = 0; i < size; i++) { + bv[i] = (byte) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, bv); + --v; + break; + case 'Z': + boolean[] zv = new boolean[size]; + for (i = 0; i < size; i++) { + zv[i] = readInt(items[readUnsignedShort(v)]) != 0; + v += 3; + } + av.visit(name, zv); + --v; + break; + case 'S': + short[] sv = new short[size]; + for (i = 0; i < size; i++) { + sv[i] = (short) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, sv); + --v; + break; + case 'C': + char[] cv = new char[size]; + for (i = 0; i < size; i++) { + cv[i] = (char) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, cv); + --v; + break; + case 'I': + int[] iv = new int[size]; + for (i = 0; i < size; i++) { + iv[i] = readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, iv); + --v; + break; + case 'J': + long[] lv = new long[size]; + for (i = 0; i < size; i++) { + lv[i] = readLong(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, lv); + --v; + break; + case 'F': + float[] fv = new float[size]; + for (i = 0; i < size; i++) { + fv[i] = Float + .intBitsToFloat(readInt(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, fv); + --v; + break; + case 'D': + double[] dv = new double[size]; + for (i = 0; i < size; i++) { + dv[i] = Double + .longBitsToDouble(readLong(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, dv); + --v; + break; + default: + v = readAnnotationValues(v - 3, buf, false, av.visitArray(name)); + } + } + return v; + } + + /** + * Computes the implicit frame of the method currently being parsed (as + * defined in the given {@link Context}) and stores it in the given context. + * + * @param frame + * information about the class being parsed. + */ + private void getImplicitFrame(final Context frame) { + String desc = frame.desc; + Object[] locals = frame.local; + int local = 0; + if ((frame.access & Opcodes.ACC_STATIC) == 0) { + if ("".equals(frame.name)) { + locals[local++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[local++] = readClass(header + 2, frame.buffer); + } + } + int i = 1; + loop: while (true) { + int j = i; + switch (desc.charAt(i++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[local++] = Opcodes.INTEGER; + break; + case 'F': + locals[local++] = Opcodes.FLOAT; + break; + case 'J': + locals[local++] = Opcodes.LONG; + break; + case 'D': + locals[local++] = Opcodes.DOUBLE; + break; + case '[': + while (desc.charAt(i) == '[') { + ++i; + } + if (desc.charAt(i) == 'L') { + ++i; + while (desc.charAt(i) != ';') { + ++i; + } + } + locals[local++] = desc.substring(j, ++i); + break; + case 'L': + while (desc.charAt(i) != ';') { + ++i; + } + locals[local++] = desc.substring(j + 1, i++); + break; + default: + break loop; + } + } + frame.localCount = local; + } + + /** + * Reads a stack map frame and stores the result in the given + * {@link Context} object. + * + * @param stackMap + * the start offset of a stack map frame in the class file. + * @param zip + * if the stack map frame at stackMap is compressed or not. + * @param unzip + * if the stack map frame must be uncompressed. + * @param labels + * the labels of the method currently being parsed, indexed by + * their offset. A new label for the parsed stack map frame is + * stored in this array if it does not already exist. + * @param frame + * where the parsed stack map frame must be stored. + * @return the offset of the first byte following the parsed frame. + */ + private int readFrame(int stackMap, boolean zip, boolean unzip, + Label[] labels, Context frame) { + char[] c = frame.buffer; + int tag; + int delta; + if (zip) { + tag = b[stackMap++] & 0xFF; + } else { + tag = MethodWriter.FULL_FRAME; + frame.offset = -1; + } + frame.localDiff = 0; + if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) { + delta = tag; + frame.mode = Opcodes.F_SAME; + frame.stackCount = 0; + } else if (tag < MethodWriter.RESERVED) { + delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; + stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); + frame.mode = Opcodes.F_SAME1; + frame.stackCount = 1; + } else { + delta = readUnsignedShort(stackMap); + stackMap += 2; + if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); + frame.mode = Opcodes.F_SAME1; + frame.stackCount = 1; + } else if (tag >= MethodWriter.CHOP_FRAME + && tag < MethodWriter.SAME_FRAME_EXTENDED) { + frame.mode = Opcodes.F_CHOP; + frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag; + frame.localCount -= frame.localDiff; + frame.stackCount = 0; + } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) { + frame.mode = Opcodes.F_SAME; + frame.stackCount = 0; + } else if (tag < MethodWriter.FULL_FRAME) { + int local = unzip ? frame.localCount : 0; + for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) { + stackMap = readFrameType(frame.local, local++, stackMap, c, + labels); + } + frame.mode = Opcodes.F_APPEND; + frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED; + frame.localCount += frame.localDiff; + frame.stackCount = 0; + } else { // if (tag == FULL_FRAME) { + frame.mode = Opcodes.F_FULL; + int n = readUnsignedShort(stackMap); + stackMap += 2; + frame.localDiff = n; + frame.localCount = n; + for (int local = 0; n > 0; n--) { + stackMap = readFrameType(frame.local, local++, stackMap, c, + labels); + } + n = readUnsignedShort(stackMap); + stackMap += 2; + frame.stackCount = n; + for (int stack = 0; n > 0; n--) { + stackMap = readFrameType(frame.stack, stack++, stackMap, c, + labels); + } + } + } + frame.offset += delta + 1; + readLabel(frame.offset, labels); + return stackMap; + } + + /** + * Reads a stack map frame type and stores it at the given index in the + * given array. + * + * @param frame + * the array where the parsed type must be stored. + * @param index + * the index in 'frame' where the parsed type must be stored. + * @param v + * the start offset of the stack map frame type to read. + * @param buf + * a buffer to read strings. + * @param labels + * the labels of the method currently being parsed, indexed by + * their offset. If the parsed type is an Uninitialized type, a + * new label for the corresponding NEW instruction is stored in + * this array if it does not already exist. + * @return the offset of the first byte after the parsed type. + */ + private int readFrameType(final Object[] frame, final int index, int v, + final char[] buf, final Label[] labels) { + int type = b[v++] & 0xFF; + switch (type) { + case 0: + frame[index] = Opcodes.TOP; + break; + case 1: + frame[index] = Opcodes.INTEGER; + break; + case 2: + frame[index] = Opcodes.FLOAT; + break; + case 3: + frame[index] = Opcodes.DOUBLE; + break; + case 4: + frame[index] = Opcodes.LONG; + break; + case 5: + frame[index] = Opcodes.NULL; + break; + case 6: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case 7: // Object + frame[index] = readClass(v, buf); + v += 2; + break; + default: // Uninitialized + frame[index] = readLabel(readUnsignedShort(v), labels); + v += 2; + } + return v; + } + + /** + * Returns the label corresponding to the given offset. The default + * implementation of this method creates a label for the given offset if it + * has not been already created. + * + * @param offset + * a bytecode offset in a method. + * @param labels + * the already created labels, indexed by their offset. If a + * label already exists for offset this method must not create a + * new one. Otherwise it must store the new label in this array. + * @return a non null Label, which must be equal to labels[offset]. + */ + protected Label readLabel(int offset, Label[] labels) { + if (labels[offset] == null) { + labels[offset] = new Label(); + } + return labels[offset]; + } + + /** + * Returns the start index of the attribute_info structure of this class. + * + * @return the start index of the attribute_info structure of this class. + */ + private int getAttributes() { + // skips the header + int u = header + 8 + readUnsignedShort(header + 6) * 2; + // skips fields and methods + for (int i = readUnsignedShort(u); i > 0; --i) { + for (int j = readUnsignedShort(u + 8); j > 0; --j) { + u += 6 + readInt(u + 12); + } + u += 8; + } + u += 2; + for (int i = readUnsignedShort(u); i > 0; --i) { + for (int j = readUnsignedShort(u + 8); j > 0; --j) { + u += 6 + readInt(u + 12); + } + u += 8; + } + // the attribute_info structure starts just after the methods + return u + 2; + } + + /** + * Reads an attribute in {@link #b b}. + * + * @param attrs + * prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to + * the type of one the prototypes is ignored (i.e. an empty + * {@link Attribute} instance is returned). + * @param type + * the type of the attribute. + * @param off + * index of the first byte of the attribute's content in + * {@link #b b}. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into + * account here (they have already been read). + * @param len + * the length of the attribute's content. + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param codeOff + * index of the first byte of code's attribute content in + * {@link #b b}, or -1 if the attribute to be read is not a code + * attribute. The 6 attribute header bytes, containing the type + * and the length of the attribute, are not taken into account + * here. + * @param labels + * the labels of the method's code, or null if the + * attribute to be read is not a code attribute. + * @return the attribute that has been read, or null to skip this + * attribute. + */ + private Attribute readAttribute(final Attribute[] attrs, final String type, + final int off, final int len, final char[] buf, final int codeOff, + final Label[] labels) { + for (int i = 0; i < attrs.length; ++i) { + if (attrs[i].type.equals(type)) { + return attrs[i].read(this, off, len, buf, codeOff, labels); + } + } + return new Attribute(type).read(this, off, len, null, -1, null); + } + + // ------------------------------------------------------------------------ + // Utility methods: low level parsing + // ------------------------------------------------------------------------ + + /** + * Returns the number of constant pool items in {@link #b b}. + * + * @return the number of constant pool items in {@link #b b}. + */ + public int getItemCount() { + return items.length; + } + + /** + * Returns the start index of the constant pool item in {@link #b b}, plus + * one. This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param item + * the index a constant pool item. + * @return the start index of the constant pool item in {@link #b b}, plus + * one. + */ + public int getItem(final int item) { + return items[item]; + } + + /** + * Returns the maximum length of the strings contained in the constant pool + * of the class. + * + * @return the maximum length of the strings contained in the constant pool + * of the class. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readByte(final int index) { + return b[index] & 0xFF; + } + + /** + * Reads an unsigned short value in {@link #b b}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readUnsignedShort(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in {@link #b b}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public short readShort(final int index) { + byte[] b = this.b; + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readInt(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Reads a signed long value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public long readLong(final int index) { + long l1 = readInt(index); + long l0 = readInt(index + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads an UTF8 string constant pool item in {@link #b b}. This method + * is intended for {@link Attribute} sub classes, and is normally not needed + * by class generators or adapters. + * + * @param index + * the start index of an unsigned short value in {@link #b b}, + * whose value is the index of an UTF8 constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 item. + */ + public String readUTF8(int index, final char[] buf) { + int item = readUnsignedShort(index); + if (index == 0 || item == 0) { + return null; + } + String s = strings[item]; + if (s != null) { + return s; + } + index = items[item]; + return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); + } + + /** + * Reads UTF8 string in {@link #b b}. + * + * @param index + * start offset of the UTF8 string to be read. + * @param utfLen + * length of the UTF8 string to be read. + * @param buf + * buffer to be used to read the string. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(int index, final int utfLen, final char[] buf) { + int endIndex = index + utfLen; + byte[] b = this.b; + int strLen = 0; + int c; + int st = 0; + char cc = 0; + while (index < endIndex) { + c = b[index++]; + switch (st) { + case 0: + c = c & 0xFF; + if (c < 0x80) { // 0xxxxxxx + buf[strLen++] = (char) c; + } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx + cc = (char) (c & 0x1F); + st = 1; + } else { // 1110 xxxx 10xx xxxx 10xx xxxx + cc = (char) (c & 0x0F); + st = 2; + } + break; + + case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char + buf[strLen++] = (char) ((cc << 6) | (c & 0x3F)); + st = 0; + break; + + case 2: // byte 2 of 3-byte char + cc = (char) ((cc << 6) | (c & 0x3F)); + st = 1; + break; + } + } + return new String(buf, 0, strLen); + } + + /** + * Reads a class constant pool item in {@link #b b}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param index + * the start index of an unsigned short value in {@link #b b}, + * whose value is the index of a class constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified class item. + */ + public String readClass(final int index, final char[] buf) { + // computes the start index of the CONSTANT_Class item in b + // and reads the CONSTANT_Utf8 item designated by + // the first two bytes of this CONSTANT_Class item + return readUTF8(items[readUnsignedShort(index)], buf); + } + + /** + * Reads a numeric or string constant pool item in {@link #b b}. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param item + * the index of a constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, + * {@link String}, {@link Type} or {@link Handle} corresponding to + * the given constant pool item. + */ + public Object readConst(final int item, final char[] buf) { + int index = items[item]; + switch (b[index - 1]) { + case ClassWriter.INT: + return new Integer(readInt(index)); + case ClassWriter.FLOAT: + return new Float(Float.intBitsToFloat(readInt(index))); + case ClassWriter.LONG: + return new Long(readLong(index)); + case ClassWriter.DOUBLE: + return new Double(Double.longBitsToDouble(readLong(index))); + case ClassWriter.CLASS: + return Type.getObjectType(readUTF8(index, buf)); + case ClassWriter.STR: + return readUTF8(index, buf); + case ClassWriter.MTYPE: + return Type.getMethodType(readUTF8(index, buf)); + default: // case ClassWriter.HANDLE_BASE + [1..9]: + int tag = readByte(index); + int[] items = this.items; + int cpIndex = items[readUnsignedShort(index + 1)]; + String owner = readClass(cpIndex, buf); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String name = readUTF8(cpIndex, buf); + String desc = readUTF8(cpIndex + 2, buf); + return new Handle(tag, owner, name, desc); + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassVisitor.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassVisitor.java new file mode 100644 index 0000000000..1608132b98 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassVisitor.java @@ -0,0 +1,286 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A visitor to visit a Java class. The methods of this class must be called in + * the following order: visit [ visitSource ] [ + * visitOuterClass ] ( visitAnnotation | + * visitAttribute )* ( visitInnerClass | visitField | + * visitMethod )* visitEnd. + * + * @author Eric Bruneton + */ +public abstract class ClassVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The class visitor to which this visitor must delegate method calls. May + * be null. + */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param cv + * the class visitor to which this visitor must delegate method + * calls. May be null. + */ + public ClassVisitor(final int api, final ClassVisitor cv) { + if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + } + this.api = api; + this.cv = cv; + } + + /** + * Visits the header of the class. + * + * @param version + * the class version. + * @param access + * the class's access flags (see {@link Opcodes}). This parameter + * also indicates if the class is deprecated. + * @param name + * the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * @param signature + * the signature of this class. May be null if the class + * is not a generic one, and does not extend or implement generic + * classes or interfaces. + * @param superName + * the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For + * interfaces, the super class is {@link Object}. May be + * null, but only for the {@link Object} class. + * @param interfaces + * the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * null. + */ + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } + + /** + * Visits the source of the class. + * + * @param source + * the name of the source file from which the class was compiled. + * May be null. + * @param debug + * additional debug information to compute the correspondance + * between source and compiled elements of the class. May be + * null. + */ + public void visitSource(String source, String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } + + /** + * Visits the enclosing class of the class. This method must be called only + * if the class has an enclosing class. + * + * @param owner + * internal name of the enclosing class of the class. + * @param name + * the name of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + * @param desc + * the descriptor of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + */ + public void visitOuterClass(String owner, String name, String desc) { + if (cv != null) { + cv.visitOuterClass(owner, name, desc); + } + } + + /** + * Visits an annotation of the class. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (cv != null) { + return cv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the class. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (cv != null) { + cv.visitAttribute(attr); + } + } + + /** + * Visits information about an inner class. This inner class is not + * necessarily a member of the class being visited. + * + * @param name + * the internal name of an inner class (see + * {@link Type#getInternalName() getInternalName}). + * @param outerName + * the internal name of the class to which the inner class + * belongs (see {@link Type#getInternalName() getInternalName}). + * May be null for not member classes. + * @param innerName + * the (simple) name of the inner class inside its enclosing + * class. May be null for anonymous inner classes. + * @param access + * the access flags of the inner class as originally declared in + * the enclosing class. + */ + public void visitInnerClass(String name, String outerName, + String innerName, int access) { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * Visits a field of the class. + * + * @param access + * the field's access flags (see {@link Opcodes}). This parameter + * also indicates if the field is synthetic and/or deprecated. + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type Type}). + * @param signature + * the field's signature. May be null if the field's + * type does not use generic types. + * @param value + * the field's initial value. This parameter, which may be + * null if the field does not have an initial value, + * must be an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String} (for int, + * float, long or String fields + * respectively). This parameter is only used for static + * fields. Its value is ignored for non static fields, which + * must be initialized through bytecode instructions in + * constructors or methods. + * @return a visitor to visit field annotations and attributes, or + * null if this class visitor is not interested in visiting + * these annotations and attributes. + */ + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + if (cv != null) { + return cv.visitField(access, name, desc, signature, value); + } + return null; + } + + /** + * Visits a method of the class. This method must return a new + * {@link MethodVisitor} instance (or null) each time it is called, + * i.e., it should not return a previously returned visitor. + * + * @param access + * the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param signature + * the method's signature. May be null if the method + * parameters, return type and exceptions do not use generic + * types. + * @param exceptions + * the internal names of the method's exception classes (see + * {@link Type#getInternalName() getInternalName}). May be + * null. + * @return an object to visit the byte code of the method, or null + * if this class visitor is not interested in visiting the code of + * this method. + */ + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + if (cv != null) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + return null; + } + + /** + * Visits the end of the class. This method, which is the last one to be + * called, is used to inform the visitor that all the fields and methods of + * the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassWriter.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassWriter.java new file mode 100644 index 0000000000..af4c719833 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/ClassWriter.java @@ -0,0 +1,1706 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A {@link ClassVisitor} that generates classes in bytecode form. More + * precisely this visitor generates a byte array conforming to the Java class + * file format. It can be used alone, to generate a Java class "from scratch", + * or with one or more {@link ClassReader ClassReader} and adapter class visitor + * to generate a modified class from one or more existing Java classes. + * + * @author Eric Bruneton + */ +public class ClassWriter extends ClassVisitor { + + /** + * Flag to automatically compute the maximum stack size and the maximum + * number of local variables of methods. If this flag is set, then the + * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the + * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod} + * method will be ignored, and computed automatically from the signature and + * the bytecode of each method. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * Flag to automatically compute the stack map frames of methods from + * scratch. If this flag is set, then the calls to the + * {@link MethodVisitor#visitFrame} method are ignored, and the stack map + * frames are recomputed from the methods bytecode. The arguments of the + * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and + * recomputed from the bytecode. In other words, computeFrames implies + * computeMaxs. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + /** + * Pseudo access flag to distinguish between the synthetic attribute and the + * synthetic access flag. + */ + static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; + + /** + * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC. + */ + static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE + / Opcodes.ACC_SYNTHETIC; + + /** + * The type of instructions without any argument. + */ + static final int NOARG_INSN = 0; + + /** + * The type of instructions with an signed byte argument. + */ + static final int SBYTE_INSN = 1; + + /** + * The type of instructions with an signed short argument. + */ + static final int SHORT_INSN = 2; + + /** + * The type of instructions with a local variable index argument. + */ + static final int VAR_INSN = 3; + + /** + * The type of instructions with an implicit local variable index argument. + */ + static final int IMPLVAR_INSN = 4; + + /** + * The type of instructions with a type descriptor argument. + */ + static final int TYPE_INSN = 5; + + /** + * The type of field and method invocations instructions. + */ + static final int FIELDORMETH_INSN = 6; + + /** + * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. + */ + static final int ITFMETH_INSN = 7; + + /** + * The type of the INVOKEDYNAMIC instruction. + */ + static final int INDYMETH_INSN = 8; + + /** + * The type of instructions with a 2 bytes bytecode offset label. + */ + static final int LABEL_INSN = 9; + + /** + * The type of instructions with a 4 bytes bytecode offset label. + */ + static final int LABELW_INSN = 10; + + /** + * The type of the LDC instruction. + */ + static final int LDC_INSN = 11; + + /** + * The type of the LDC_W and LDC2_W instructions. + */ + static final int LDCW_INSN = 12; + + /** + * The type of the IINC instruction. + */ + static final int IINC_INSN = 13; + + /** + * The type of the TABLESWITCH instruction. + */ + static final int TABL_INSN = 14; + + /** + * The type of the LOOKUPSWITCH instruction. + */ + static final int LOOK_INSN = 15; + + /** + * The type of the MULTIANEWARRAY instruction. + */ + static final int MANA_INSN = 16; + + /** + * The type of the WIDE instruction. + */ + static final int WIDE_INSN = 17; + + /** + * The instruction types of all JVM opcodes. + */ + static final byte[] TYPE; + + /** + * The type of CONSTANT_Class constant pool items. + */ + static final int CLASS = 7; + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + static final int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + static final int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + static final int IMETH = 11; + + /** + * The type of CONSTANT_String constant pool items. + */ + static final int STR = 8; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + static final int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + static final int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + static final int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + static final int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + static final int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + static final int UTF8 = 1; + + /** + * The type of CONSTANT_MethodType constant pool items. + */ + static final int MTYPE = 16; + + /** + * The type of CONSTANT_MethodHandle constant pool items. + */ + static final int HANDLE = 15; + + /** + * The type of CONSTANT_InvokeDynamic constant pool items. + */ + static final int INDY = 18; + + /** + * The base value for all CONSTANT_MethodHandle constant pool items. + * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9 + * different items. + */ + static final int HANDLE_BASE = 20; + + /** + * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_NORMAL = 30; + + /** + * Uninitialized type Item stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. + */ + static final int TYPE_UNINIT = 31; + + /** + * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_MERGED = 32; + + /** + * The type of BootstrapMethods items. These items are stored in a special + * class attribute named BootstrapMethods and not in the constant pool. + */ + static final int BSM = 33; + + /** + * The class reader from which this class writer was constructed, if any. + */ + ClassReader cr; + + /** + * Minor and major version numbers of the class to be generated. + */ + int version; + + /** + * Index of the next item to be added in the constant pool. + */ + int index; + + /** + * The constant pool of this class. + */ + final ByteVector pool; + + /** + * The constant pool's hash table data. + */ + Item[] items; + + /** + * The threshold of the constant pool's hash table. + */ + int threshold; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key2; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key3; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key4; + + /** + * A type table used to temporarily store internal names that will not + * necessarily be stored in the constant pool. This type table is used by + * the control flow and data flow analysis algorithm used to compute stack + * map frames from scratch. This array associates to each index i + * the Item whose index is i. All Item objects stored in this array + * are also stored in the {@link #items} hash table. These two arrays allow + * to retrieve an Item from its index or, conversely, to get the index of an + * Item from its value. Each Item stores an internal name in its + * {@link Item#strVal1} field. + */ + Item[] typeTable; + + /** + * Number of elements in the {@link #typeTable} array. + */ + private short typeCount; + + /** + * The access flags of this class. + */ + private int access; + + /** + * The constant pool item that contains the internal name of this class. + */ + private int name; + + /** + * The internal name of this class. + */ + String thisName; + + /** + * The constant pool item that contains the signature of this class. + */ + private int signature; + + /** + * The constant pool item that contains the internal name of the super class + * of this class. + */ + private int superName; + + /** + * Number of interfaces implemented or extended by this class or interface. + */ + private int interfaceCount; + + /** + * The interfaces implemented or extended by this class or interface. More + * precisely, this array contains the indexes of the constant pool items + * that contain the internal names of these interfaces. + */ + private int[] interfaces; + + /** + * The index of the constant pool item that contains the name of the source + * file from which this class was compiled. + */ + private int sourceFile; + + /** + * The SourceDebug attribute of this class. + */ + private ByteVector sourceDebug; + + /** + * The constant pool item that contains the name of the enclosing class of + * this class. + */ + private int enclosingMethodOwner; + + /** + * The constant pool item that contains the name and descriptor of the + * enclosing method of this class. + */ + private int enclosingMethod; + + /** + * The runtime visible annotations of this class. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this class. + */ + private AnnotationWriter ianns; + + /** + * The non standard attributes of this class. + */ + private Attribute attrs; + + /** + * The number of entries in the InnerClasses attribute. + */ + private int innerClassesCount; + + /** + * The InnerClasses attribute. + */ + private ByteVector innerClasses; + + /** + * The number of entries in the BootstrapMethods attribute. + */ + int bootstrapMethodsCount; + + /** + * The BootstrapMethods attribute. + */ + ByteVector bootstrapMethods; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the first element of this + * list. + */ + FieldWriter firstField; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the last element of this + * list. + */ + FieldWriter lastField; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the first element of + * this list. + */ + MethodWriter firstMethod; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the last element of this + * list. + */ + MethodWriter lastMethod; + + /** + * true if the maximum stack size and number of local variables + * must be automatically computed. + */ + private boolean computeMaxs; + + /** + * true if the stack map frames must be recomputed from scratch. + */ + private boolean computeFrames; + + /** + * true if the stack map tables of this class are invalid. The + * {@link MethodWriter#resizeInstructions} method cannot transform existing + * stack map tables, and so produces potentially invalid classes when it is + * executed. In this case the class is reread and rewritten with the + * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize + * stack map tables when this option is used). + */ + boolean invalidFrames; + + // ------------------------------------------------------------------------ + // Static initializer + // ------------------------------------------------------------------------ + + /** + * Computes the instruction types of JVM opcodes. + */ + static { + int i; + byte[] b = new byte[220]; + String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + for (i = 0; i < b.length; ++i) { + b[i] = (byte) (s.charAt(i) - 'A'); + } + TYPE = b; + + // code to generate the above string + // + // // SBYTE_INSN instructions + // b[Constants.NEWARRAY] = SBYTE_INSN; + // b[Constants.BIPUSH] = SBYTE_INSN; + // + // // SHORT_INSN instructions + // b[Constants.SIPUSH] = SHORT_INSN; + // + // // (IMPL)VAR_INSN instructions + // b[Constants.RET] = VAR_INSN; + // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3 + // b[i] = IMPLVAR_INSN; + // } + // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3 + // b[i] = IMPLVAR_INSN; + // } + // + // // TYPE_INSN instructions + // b[Constants.NEW] = TYPE_INSN; + // b[Constants.ANEWARRAY] = TYPE_INSN; + // b[Constants.CHECKCAST] = TYPE_INSN; + // b[Constants.INSTANCEOF] = TYPE_INSN; + // + // // (Set)FIELDORMETH_INSN instructions + // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) { + // b[i] = FIELDORMETH_INSN; + // } + // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN; + // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN; + // + // // LABEL(W)_INSN instructions + // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) { + // b[i] = LABEL_INSN; + // } + // b[Constants.IFNULL] = LABEL_INSN; + // b[Constants.IFNONNULL] = LABEL_INSN; + // b[200] = LABELW_INSN; // GOTO_W + // b[201] = LABELW_INSN; // JSR_W + // // temporary opcodes used internally by ASM - see Label and + // MethodWriter + // for (i = 202; i < 220; ++i) { + // b[i] = LABEL_INSN; + // } + // + // // LDC(_W) instructions + // b[Constants.LDC] = LDC_INSN; + // b[19] = LDCW_INSN; // LDC_W + // b[20] = LDCW_INSN; // LDC2_W + // + // // special instructions + // b[Constants.IINC] = IINC_INSN; + // b[Constants.TABLESWITCH] = TABL_INSN; + // b[Constants.LOOKUPSWITCH] = LOOK_INSN; + // b[Constants.MULTIANEWARRAY] = MANA_INSN; + // b[196] = WIDE_INSN; // WIDE + // + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('A' + b[i])); + // } + // System.err.println(); + } + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #COMPUTE_MAXS}, + * {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + super(Opcodes.ASM4); + index = 1; + pool = new ByteVector(); + items = new Item[256]; + threshold = (int) (0.75d * items.length); + key = new Item(); + key2 = new Item(); + key3 = new Item(); + key4 = new Item(); + this.computeMaxs = (flags & COMPUTE_MAXS) != 0; + this.computeFrames = (flags & COMPUTE_FRAMES) != 0; + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for + * "mostly add" bytecode transformations. These optimizations are the + * following: + * + *
    + *
  • The constant pool from the original class is copied as is in the new + * class, which saves time. New constant pool entries will be added at the + * end if necessary, but unused constant pool entries won't be + * removed.
  • + *
  • Methods that are not transformed are copied as is in the new class, + * directly from the original class bytecode (i.e. without emitting visit + * events for all the method instructions), which saves a lot of + * time. Untransformed methods are detected by the fact that the + * {@link ClassReader} receives {@link MethodVisitor} objects that come from + * a {@link ClassWriter} (and not from any other {@link ClassVisitor} + * instance).
  • + *
+ * + * @param classReader + * the {@link ClassReader} used to read the original class. It + * will be used to copy the entire constant pool from the + * original class and also to copy other fragments of original + * bytecode where applicable. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. These option flags do not affect methods + * that are copied as is in the new class. This means that the + * maximum stack size nor the stack frames will be computed for + * these methods. See {@link #COMPUTE_MAXS}, + * {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + this(flags); + classReader.copyPool(this); + this.cr = classReader; + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public final void visit(final int version, final int access, + final String name, final String signature, final String superName, + final String[] interfaces) { + this.version = version; + this.access = access; + this.name = newClass(name); + thisName = name; + if (ClassReader.SIGNATURES && signature != null) { + this.signature = newUTF8(signature); + } + this.superName = superName == null ? 0 : newClass(superName); + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = newClass(interfaces[i]); + } + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFile = newUTF8(file); + } + if (debug != null) { + sourceDebug = new ByteVector().putUTF8(debug); + } + } + + @Override + public final void visitOuterClass(final String owner, final String name, + final String desc) { + enclosingMethodOwner = newClass(owner); + if (name != null && desc != null) { + enclosingMethod = newNameType(name, desc); + } + } + + @Override + public final AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public final void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public final void visitInnerClass(final String name, + final String outerName, final String innerName, final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + ++innerClassesCount; + innerClasses.putShort(name == null ? 0 : newClass(name)); + innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); + innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); + innerClasses.putShort(access); + } + + @Override + public final FieldVisitor visitField(final int access, final String name, + final String desc, final String signature, final Object value) { + return new FieldWriter(this, access, name, desc, signature, value); + } + + @Override + public final MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, final String[] exceptions) { + return new MethodWriter(this, access, name, desc, signature, + exceptions, computeMaxs, computeFrames); + } + + @Override + public final void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Other public methods + // ------------------------------------------------------------------------ + + /** + * Returns the bytecode of the class that was build with this class writer. + * + * @return the bytecode of the class that was build with this class writer. + */ + public byte[] toByteArray() { + if (index > 0xFFFF) { + throw new RuntimeException("Class file too large!"); + } + // computes the real size of the bytecode of this class + int size = 24 + 2 * interfaceCount; + int nbFields = 0; + FieldWriter fb = firstField; + while (fb != null) { + ++nbFields; + size += fb.getSize(); + fb = (FieldWriter) fb.fv; + } + int nbMethods = 0; + MethodWriter mb = firstMethod; + while (mb != null) { + ++nbMethods; + size += mb.getSize(); + mb = (MethodWriter) mb.mv; + } + int attributeCount = 0; + if (bootstrapMethods != null) { + // we put it as first attribute in order to improve a bit + // ClassReader.copyBootstrapMethods + ++attributeCount; + size += 8 + bootstrapMethods.length; + newUTF8("BootstrapMethods"); + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + size += 8; + newUTF8("Signature"); + } + if (sourceFile != 0) { + ++attributeCount; + size += 8; + newUTF8("SourceFile"); + } + if (sourceDebug != null) { + ++attributeCount; + size += sourceDebug.length + 4; + newUTF8("SourceDebugExtension"); + } + if (enclosingMethodOwner != 0) { + ++attributeCount; + size += 10; + newUTF8("EnclosingMethod"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + size += 6; + newUTF8("Deprecated"); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((version & 0xFFFF) < Opcodes.V1_5 + || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + size += 6; + newUTF8("Synthetic"); + } + } + if (innerClasses != null) { + ++attributeCount; + size += 8 + innerClasses.length; + newUTF8("InnerClasses"); + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + size += 8 + anns.getSize(); + newUTF8("RuntimeVisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + size += 8 + ianns.getSize(); + newUTF8("RuntimeInvisibleAnnotations"); + } + if (attrs != null) { + attributeCount += attrs.getCount(); + size += attrs.getSize(this, null, 0, -1, -1); + } + size += pool.length; + // allocates a byte vector of this size, in order to avoid unnecessary + // arraycopy operations in the ByteVector.enlarge() method + ByteVector out = new ByteVector(size); + out.putInt(0xCAFEBABE).putInt(version); + out.putShort(index).putByteArray(pool.data, 0, pool.length); + int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE + | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC); + out.putShort(access & ~mask).putShort(name).putShort(superName); + out.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + out.putShort(interfaces[i]); + } + out.putShort(nbFields); + fb = firstField; + while (fb != null) { + fb.put(out); + fb = (FieldWriter) fb.fv; + } + out.putShort(nbMethods); + mb = firstMethod; + while (mb != null) { + mb.put(out); + mb = (MethodWriter) mb.mv; + } + out.putShort(attributeCount); + if (bootstrapMethods != null) { + out.putShort(newUTF8("BootstrapMethods")); + out.putInt(bootstrapMethods.length + 2).putShort( + bootstrapMethodsCount); + out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); + } + if (sourceFile != 0) { + out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); + } + if (sourceDebug != null) { + int len = sourceDebug.length - 2; + out.putShort(newUTF8("SourceDebugExtension")).putInt(len); + out.putByteArray(sourceDebug.data, 2, len); + } + if (enclosingMethodOwner != 0) { + out.putShort(newUTF8("EnclosingMethod")).putInt(4); + out.putShort(enclosingMethodOwner).putShort(enclosingMethod); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(newUTF8("Deprecated")).putInt(0); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((version & 0xFFFF) < Opcodes.V1_5 + || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(newUTF8("Synthetic")).putInt(0); + } + } + if (innerClasses != null) { + out.putShort(newUTF8("InnerClasses")); + out.putInt(innerClasses.length + 2).putShort(innerClassesCount); + out.putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (attrs != null) { + attrs.put(this, null, 0, -1, -1, out); + } + if (invalidFrames) { + anns = null; + ianns = null; + attrs = null; + innerClassesCount = 0; + innerClasses = null; + bootstrapMethodsCount = 0; + bootstrapMethods = null; + firstField = null; + lastField = null; + firstMethod = null; + lastMethod = null; + computeMaxs = false; + computeFrames = true; + invalidFrames = false; + new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES); + return toByteArray(); + } + return out.data; + } + + // ------------------------------------------------------------------------ + // Utility methods: constant pool management + // ------------------------------------------------------------------------ + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * + * @param cst + * the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double}, a {@link String} or a + * {@link Type}. + * @return a new or already existing constant item with the given value. + */ + Item newConstItem(final Object cst) { + if (cst instanceof Integer) { + int val = ((Integer) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Byte) { + int val = ((Byte) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Character) { + int val = ((Character) cst).charValue(); + return newInteger(val); + } else if (cst instanceof Short) { + int val = ((Short) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Boolean) { + int val = ((Boolean) cst).booleanValue() ? 1 : 0; + return newInteger(val); + } else if (cst instanceof Float) { + float val = ((Float) cst).floatValue(); + return newFloat(val); + } else if (cst instanceof Long) { + long val = ((Long) cst).longValue(); + return newLong(val); + } else if (cst instanceof Double) { + double val = ((Double) cst).doubleValue(); + return newDouble(val); + } else if (cst instanceof String) { + return newString((String) cst); + } else if (cst instanceof Type) { + Type t = (Type) cst; + int s = t.getSort(); + if (s == Type.OBJECT) { + return newClassItem(t.getInternalName()); + } else if (s == Type.METHOD) { + return newMethodTypeItem(t.getDescriptor()); + } else { // s == primitive type or array + return newClassItem(t.getDescriptor()); + } + } else if (cst instanceof Handle) { + Handle h = (Handle) cst; + return newHandleItem(h.tag, h.owner, h.name, h.desc); + } else { + throw new IllegalArgumentException("value " + cst); + } + } + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param cst + * the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the + * given value. + */ + public int newConst(final Object cst) { + return newConstItem(cst).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param value + * the String value. + * @return the index of a new or already existing UTF8 item. + */ + public int newUTF8(final String value) { + key.set(UTF8, value, null, null); + Item result = get(key); + if (result == null) { + pool.putByte(UTF8).putUTF8(value); + result = new Item(index++, key); + put(result); + } + return result.index; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param value + * the internal name of the class. + * @return a new or already existing class reference item. + */ + Item newClassItem(final String value) { + key2.set(CLASS, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(CLASS, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param value + * the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return newClassItem(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param methodDesc + * method descriptor of the method type. + * @return a new or already existing method type reference item. + */ + Item newMethodTypeItem(final String methodDesc) { + key2.set(MTYPE, methodDesc, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(MTYPE, newUTF8(methodDesc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param methodDesc + * method descriptor of the method type. + * @return the index of a new or already existing method type reference + * item. + */ + public int newMethodType(final String methodDesc) { + return newMethodTypeItem(methodDesc).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @return a new or an already existing method type reference item. + */ + Item newHandleItem(final int tag, final String owner, final String name, + final String desc) { + key4.set(HANDLE_BASE + tag, owner, name, desc); + Item result = get(key4); + if (result == null) { + if (tag <= Opcodes.H_PUTSTATIC) { + put112(HANDLE, tag, newField(owner, name, desc)); + } else { + put112(HANDLE, + tag, + newMethod(owner, name, desc, + tag == Opcodes.H_INVOKEINTERFACE)); + } + result = new Item(index++, key4); + put(result); + } + return result; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @return the index of a new or already existing method type reference + * item. + */ + public int newHandle(final int tag, final String owner, final String name, + final String desc) { + return newHandleItem(tag, owner, name, desc).index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param name + * name of the invoked method. + * @param desc + * descriptor of the invoke method. + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. + * + * @return a new or an already existing invokedynamic type reference item. + */ + Item newInvokeDynamicItem(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + // cache for performance + ByteVector bootstrapMethods = this.bootstrapMethods; + if (bootstrapMethods == null) { + bootstrapMethods = this.bootstrapMethods = new ByteVector(); + } + + int position = bootstrapMethods.length; // record current position + + int hashCode = bsm.hashCode(); + bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name, + bsm.desc)); + + int argsLength = bsmArgs.length; + bootstrapMethods.putShort(argsLength); + + for (int i = 0; i < argsLength; i++) { + Object bsmArg = bsmArgs[i]; + hashCode ^= bsmArg.hashCode(); + bootstrapMethods.putShort(newConst(bsmArg)); + } + + byte[] data = bootstrapMethods.data; + int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments) + hashCode &= 0x7FFFFFFF; + Item result = items[hashCode % items.length]; + loop: while (result != null) { + if (result.type != BSM || result.hashCode != hashCode) { + result = result.next; + continue; + } + + // because the data encode the size of the argument + // we don't need to test if these size are equals + int resultPosition = result.intVal; + for (int p = 0; p < length; p++) { + if (data[position + p] != data[resultPosition + p]) { + result = result.next; + continue loop; + } + } + break; + } + + int bootstrapMethodIndex; + if (result != null) { + bootstrapMethodIndex = result.index; + bootstrapMethods.length = position; // revert to old position + } else { + bootstrapMethodIndex = bootstrapMethodsCount++; + result = new Item(bootstrapMethodIndex); + result.set(position, hashCode); + put(result); + } + + // now, create the InvokeDynamic constant + key3.set(name, desc, bootstrapMethodIndex); + result = get(key3); + if (result == null) { + put122(INDY, bootstrapMethodIndex, newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param name + * name of the invoked method. + * @param desc + * descriptor of the invoke method. + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. + * + * @return the index of a new or already existing invokedynamic reference + * item. + */ + public int newInvokeDynamic(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner + * the internal name of the field's owner class. + * @param name + * the field's name. + * @param desc + * the field's descriptor. + * @return a new or already existing field reference item. + */ + Item newFieldItem(final String owner, final String name, final String desc) { + key3.set(FIELD, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(FIELD, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param owner + * the internal name of the field's owner class. + * @param name + * the field's name. + * @param desc + * the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String desc) { + return newFieldItem(owner, name, desc).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner + * the internal name of the method's owner class. + * @param name + * the method's name. + * @param desc + * the method's descriptor. + * @param itf + * true if owner is an interface. + * @return a new or already existing method reference item. + */ + Item newMethodItem(final String owner, final String name, + final String desc, final boolean itf) { + int type = itf ? IMETH : METH; + key3.set(type, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(type, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param owner + * the internal name of the method's owner class. + * @param name + * the method's name. + * @param desc + * the method's descriptor. + * @param itf + * true if owner is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod(final String owner, final String name, + final String desc, final boolean itf) { + return newMethodItem(owner, name, desc, itf).index; + } + + /** + * Adds an integer to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param value + * the int value. + * @return a new or already existing int item. + */ + Item newInteger(final int value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(INT).putInt(value); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a float to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the float value. + * @return a new or already existing float item. + */ + Item newFloat(final float value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(FLOAT).putInt(key.intVal); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a long to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the long value. + * @return a new or already existing long item. + */ + Item newLong(final long value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(LONG).putLong(value); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a double to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the double value. + * @return a new or already existing double item. + */ + Item newDouble(final double value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(DOUBLE).putLong(key.longVal); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a string to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the String value. + * @return a new or already existing string item. + */ + private Item newString(final String value) { + key2.set(STR, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(STR, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param name + * a name. + * @param desc + * a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String desc) { + return newNameTypeItem(name, desc).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param name + * a name. + * @param desc + * a type descriptor. + * @return a new or already existing name and type item. + */ + Item newNameTypeItem(final String name, final String desc) { + key2.set(NAME_TYPE, name, desc, null); + Item result = get(key2); + if (result == null) { + put122(NAME_TYPE, newUTF8(name), newUTF8(desc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds the given internal name to {@link #typeTable} and returns its index. + * Does nothing if the type table already contains this internal name. + * + * @param type + * the internal name to be added to the type table. + * @return the index of this internal name in the type table. + */ + int addType(final String type) { + key.set(TYPE_NORMAL, type, null, null); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given "uninitialized" type to {@link #typeTable} and returns its + * index. This method is used for UNINITIALIZED types, made of an internal + * name and a bytecode offset. + * + * @param type + * the internal name to be added to the type table. + * @param offset + * the bytecode offset of the NEW instruction that created this + * UNINITIALIZED type value. + * @return the index of this internal name in the type table. + */ + int addUninitializedType(final String type, final int offset) { + key.type = TYPE_UNINIT; + key.intVal = offset; + key.strVal1 = type; + key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given Item to {@link #typeTable}. + * + * @param item + * the value to be added to the type table. + * @return the added Item, which a new Item instance with the same value as + * the given Item. + */ + private Item addType(final Item item) { + ++typeCount; + Item result = new Item(typeCount, key); + put(result); + if (typeTable == null) { + typeTable = new Item[16]; + } + if (typeCount == typeTable.length) { + Item[] newTable = new Item[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); + typeTable = newTable; + } + typeTable[typeCount] = result; + return result; + } + + /** + * Returns the index of the common super type of the two given types. This + * method calls {@link #getCommonSuperClass} and caches the result in the + * {@link #items} hash table to speedup future calls with the same + * parameters. + * + * @param type1 + * index of an internal name in {@link #typeTable}. + * @param type2 + * index of an internal name in {@link #typeTable}. + * @return the index of the common super type of the two given types. + */ + int getMergedType(final int type1, final int type2) { + key2.type = TYPE_MERGED; + key2.longVal = type1 | (((long) type2) << 32); + key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2); + Item result = get(key2); + if (result == null) { + String t = typeTable[type1].strVal1; + String u = typeTable[type2].strVal1; + key2.intVal = addType(getCommonSuperClass(t, u)); + result = new Item((short) 0, key2); + put(result); + } + return result.intVal; + } + + /** + * Returns the common super type of the two given types. The default + * implementation of this method loads the two given classes and uses + * the java.lang.Class methods to find the common super class. It can be + * overridden to compute this common super type in other ways, in particular + * without actually loading any class, or to take into account the class + * that is currently being generated by this ClassWriter, which can of + * course not be loaded since it is under construction. + * + * @param type1 + * the internal name of a class. + * @param type2 + * the internal name of another class. + * @return the internal name of the common super class of the two given + * classes. + */ + protected String getCommonSuperClass(final String type1, final String type2) { + Class c, d; + ClassLoader classLoader = getClass().getClassLoader(); + try { + c = Class.forName(type1.replace('/', '.'), false, classLoader); + d = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + + /** + * Returns the constant pool's hash table item which is equal to the given + * item. + * + * @param key + * a constant pool item. + * @return the constant pool's hash table item which is equal to the given + * item, or null if there is no such item. + */ + private Item get(final Item key) { + Item i = items[key.hashCode % items.length]; + while (i != null && (i.type != key.type || !key.isEqualTo(i))) { + i = i.next; + } + return i; + } + + /** + * Puts the given item in the constant pool's hash table. The hash table + * must not already contains this item. + * + * @param i + * the item to be added to the constant pool's hash table. + */ + private void put(final Item i) { + if (index + typeCount > threshold) { + int ll = items.length; + int nl = ll * 2 + 1; + Item[] newItems = new Item[nl]; + for (int l = ll - 1; l >= 0; --l) { + Item j = items[l]; + while (j != null) { + int index = j.hashCode % newItems.length; + Item k = j.next; + j.next = newItems[index]; + newItems[index] = j; + j = k; + } + } + items = newItems; + threshold = (int) (nl * 0.75); + } + int index = i.hashCode % items.length; + i.next = items[index]; + items[index] = i; + } + + /** + * Puts one byte and two shorts into the constant pool. + * + * @param b + * a byte. + * @param s1 + * a short. + * @param s2 + * another short. + */ + private void put122(final int b, final int s1, final int s2) { + pool.put12(b, s1).putShort(s2); + } + + /** + * Puts two bytes and one short into the constant pool. + * + * @param b1 + * a byte. + * @param b2 + * another byte. + * @param s + * a short. + */ + private void put112(final int b1, final int b2, final int s) { + pool.put11(b1, b2).putShort(s); + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Context.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Context.java new file mode 100644 index 0000000000..cd0606b79f --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Context.java @@ -0,0 +1,110 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.apache.tajo.org.objectweb.asm; + +/** + * Information about a class being parsed in a {@link ClassReader}. + * + * @author Eric Bruneton + */ +class Context { + + /** + * Prototypes of the attributes that must be parsed for this class. + */ + Attribute[] attrs; + + /** + * The {@link ClassReader} option flags for the parsing of this class. + */ + int flags; + + /** + * The buffer used to read strings. + */ + char[] buffer; + + /** + * The start index of each bootstrap method. + */ + int[] bootstrapMethods; + + /** + * The access flags of the method currently being parsed. + */ + int access; + + /** + * The name of the method currently being parsed. + */ + String name; + + /** + * The descriptor of the method currently being parsed. + */ + String desc; + + /** + * The offset of the latest stack map frame that has been parsed. + */ + int offset; + + /** + * The encoding of the latest stack map frame that has been parsed. + */ + int mode; + + /** + * The number of locals in the latest stack map frame that has been parsed. + */ + int localCount; + + /** + * The number locals in the latest stack map frame that has been parsed, + * minus the number of locals in the previous frame. + */ + int localDiff; + + /** + * The local values of the latest stack map frame that has been parsed. + */ + Object[] local; + + /** + * The stack size of the latest stack map frame that has been parsed. + */ + int stackCount; + + /** + * The stack values of the latest stack map frame that has been parsed. + */ + Object[] stack; +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Edge.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Edge.java new file mode 100644 index 0000000000..f9fb90a9ac --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Edge.java @@ -0,0 +1,75 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * An edge in the control flow graph of a method body. See {@link Label Label}. + * + * @author Eric Bruneton + */ +class Edge { + + /** + * Denotes a normal control flow graph edge. + */ + static final int NORMAL = 0; + + /** + * Denotes a control flow graph edge corresponding to an exception handler. + * More precisely any {@link Edge} whose {@link #info} is strictly positive + * corresponds to an exception handler. The actual value of {@link #info} is + * the index, in the {@link ClassWriter} type table, of the exception that + * is catched. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. If + * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative) + * stack size in the basic block from which this edge originates. This size + * is equal to the stack size at the "jump" instruction to which this edge + * corresponds, relatively to the stack size at the beginning of the + * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used, + * this field is the kind of this control flow graph edge (i.e. NORMAL or + * EXCEPTION). + */ + int info; + + /** + * The successor block of the basic block from which this edge originates. + */ + Label successor; + + /** + * The next edge in the list of successors of the originating basic block. + * See {@link Label#successors successors}. + */ + Edge next; +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldVisitor.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldVisitor.java new file mode 100644 index 0000000000..f4fe216ff8 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldVisitor.java @@ -0,0 +1,121 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A visitor to visit a Java field. The methods of this class must be called in + * the following order: ( visitAnnotation | visitAttribute )* + * visitEnd. + * + * @author Eric Bruneton + */ +public abstract class FieldVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The field visitor to which this visitor must delegate method calls. May + * be null. + */ + protected FieldVisitor fv; + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param fv + * the field visitor to which this visitor must delegate method + * calls. May be null. + */ + public FieldVisitor(final int api, final FieldVisitor fv) { + if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + } + this.api = api; + this.fv = fv; + } + + /** + * Visits an annotation of the field. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (fv != null) { + return fv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the field. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (fv != null) { + fv.visitAttribute(attr); + } + } + + /** + * Visits the end of the field. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldWriter.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldWriter.java new file mode 100644 index 0000000000..f3437e3427 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/FieldWriter.java @@ -0,0 +1,273 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * An {@link FieldVisitor} that generates Java fields in bytecode form. + * + * @author Eric Bruneton + */ +final class FieldWriter extends FieldVisitor { + + /** + * The class writer to which this field must be added. + */ + private final ClassWriter cw; + + /** + * Access flags of this field. + */ + private final int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * field. + */ + private final int desc; + + /** + * The index of the constant pool item that contains the signature of this + * field. + */ + private int signature; + + /** + * The index of the constant pool item that contains the constant value of + * this field. + */ + private int value; + + /** + * The runtime visible annotations of this field. May be null. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this field. May be null. + */ + private AnnotationWriter ianns; + + /** + * The non standard attributes of this field. May be null. + */ + private Attribute attrs; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link FieldWriter}. + * + * @param cw + * the class writer to which this field must be added. + * @param access + * the field's access flags (see {@link Opcodes}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type}). + * @param signature + * the field's signature. May be null. + * @param value + * the field's constant value. May be null. + */ + FieldWriter(final ClassWriter cw, final int access, final String name, + final String desc, final String signature, final Object value) { + super(Opcodes.ASM4); + if (cw.firstField == null) { + cw.firstField = this; + } else { + cw.lastField.fv = this; + } + cw.lastField = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + if (ClassReader.SIGNATURES && signature != null) { + this.signature = cw.newUTF8(signature); + } + if (value != null) { + this.value = cw.newConstItem(value).index; + } + } + + // ------------------------------------------------------------------------ + // Implementation of the FieldVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this field. + * + * @return the size of this field. + */ + int getSize() { + int size = 8; + if (value != 0) { + cw.newUTF8("ConstantValue"); + size += 8; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + cw.newUTF8("Synthetic"); + size += 6; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != 0) { + cw.newUTF8("Signature"); + size += 8; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the content of this field into the given byte vector. + * + * @param out + * where the content of this field must be put. + */ + void put(final ByteVector out) { + final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; + int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); + out.putShort(access & ~mask).putShort(name).putShort(desc); + int attributeCount = 0; + if (value != 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (value != 0) { + out.putShort(cw.newUTF8("ConstantValue")); + out.putInt(2).putShort(value); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(cw.newUTF8("Signature")); + out.putInt(2).putShort(signature); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Frame.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Frame.java new file mode 100644 index 0000000000..73d00d94fb --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Frame.java @@ -0,0 +1,1453 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * Information about the input and output stack map frames of a basic block. + * + * @author Eric Bruneton + */ +final class Frame { + + /* + * Frames are computed in a two steps process: during the visit of each + * instruction, the state of the frame at the end of current basic block is + * updated by simulating the action of the instruction on the previous state + * of this so called "output frame". In visitMaxs, a fix point algorithm is + * used to compute the "input frame" of each basic block, i.e. the stack map + * frame at the beginning of the basic block, starting from the input frame + * of the first basic block (which is computed from the method descriptor), + * and by using the previously computed output frames to compute the input + * state of the other blocks. + * + * All output and input frames are stored as arrays of integers. Reference + * and array types are represented by an index into a type table (which is + * not the same as the constant pool of the class, in order to avoid adding + * unnecessary constants in the pool - not all computed frames will end up + * being stored in the stack map table). This allows very fast type + * comparisons. + * + * Output stack map frames are computed relatively to the input frame of the + * basic block, which is not yet known when output frames are computed. It + * is therefore necessary to be able to represent abstract types such as + * "the type at position x in the input frame locals" or "the type at + * position x from the top of the input frame stack" or even "the type at + * position x in the input frame, with y more (or less) array dimensions". + * This explains the rather complicated type format used in output frames. + * + * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a + * signed number of array dimensions (from -8 to 7). KIND is either BASE, + * LOCAL or STACK. BASE is used for types that are not relative to the input + * frame. LOCAL is used for types that are relative to the input local + * variable types. STACK is used for types that are relative to the input + * stack types. VALUE depends on KIND. For LOCAL types, it is an index in + * the input local variable types. For STACK types, it is a position + * relatively to the top of input frame stack. For BASE types, it is either + * one of the constants defined in FrameVisitor, or for OBJECT and + * UNINITIALIZED types, a tag and an index in the type table. + * + * Output frames can contain types of any kind and with a positive or + * negative dimension (and even unassigned types, represented by 0 - which + * does not correspond to any valid type value). Input frames can only + * contain BASE types of positive or null dimension. In all cases the type + * table contains only internal type names (array type descriptors are + * forbidden - dimensions must be represented through the DIM field). + * + * The LONG and DOUBLE types are always represented by using two slots (LONG + * + TOP or DOUBLE + TOP), for local variable types as well as in the + * operand stack. This is necessary to be able to simulate DUPx_y + * instructions, whose effect would be dependent on the actual type values + * if types were always represented by a single slot in the stack (and this + * is not possible, since actual type values are not always known - cf LOCAL + * and STACK type kinds). + */ + + /** + * Mask to get the dimension of a frame type. This dimension is a signed + * integer between -8 and 7. + */ + static final int DIM = 0xF0000000; + + /** + * Constant to be added to a type to get a type with one more dimension. + */ + static final int ARRAY_OF = 0x10000000; + + /** + * Constant to be added to a type to get a type with one less dimension. + */ + static final int ELEMENT_OF = 0xF0000000; + + /** + * Mask to get the kind of a frame type. + * + * @see #BASE + * @see #LOCAL + * @see #STACK + */ + static final int KIND = 0xF000000; + + /** + * Flag used for LOCAL and STACK types. Indicates that if this type happens + * to be a long or double type (during the computations of input frames), + * then it must be set to TOP because the second word of this value has been + * reused to store other data in the basic block. Hence the first word no + * longer stores a valid long or double value. + */ + static final int TOP_IF_LONG_OR_DOUBLE = 0x800000; + + /** + * Mask to get the value of a frame type. + */ + static final int VALUE = 0x7FFFFF; + + /** + * Mask to get the kind of base types. + */ + static final int BASE_KIND = 0xFF00000; + + /** + * Mask to get the value of base types. + */ + static final int BASE_VALUE = 0xFFFFF; + + /** + * Kind of the types that are not relative to an input stack map frame. + */ + static final int BASE = 0x1000000; + + /** + * Base kind of the base reference types. The BASE_VALUE of such types is an + * index into the type table. + */ + static final int OBJECT = BASE | 0x700000; + + /** + * Base kind of the uninitialized base types. The BASE_VALUE of such types + * in an index into the type table (the Item at that index contains both an + * instruction offset and an internal class name). + */ + static final int UNINITIALIZED = BASE | 0x800000; + + /** + * Kind of the types that are relative to the local variable types of an + * input stack map frame. The value of such types is a local variable index. + */ + private static final int LOCAL = 0x2000000; + + /** + * Kind of the the types that are relative to the stack of an input stack + * map frame. The value of such types is a position relatively to the top of + * this stack. + */ + private static final int STACK = 0x3000000; + + /** + * The TOP type. This is a BASE type. + */ + static final int TOP = BASE | 0; + + /** + * The BOOLEAN type. This is a BASE type mainly used for array types. + */ + static final int BOOLEAN = BASE | 9; + + /** + * The BYTE type. This is a BASE type mainly used for array types. + */ + static final int BYTE = BASE | 10; + + /** + * The CHAR type. This is a BASE type mainly used for array types. + */ + static final int CHAR = BASE | 11; + + /** + * The SHORT type. This is a BASE type mainly used for array types. + */ + static final int SHORT = BASE | 12; + + /** + * The INTEGER type. This is a BASE type. + */ + static final int INTEGER = BASE | 1; + + /** + * The FLOAT type. This is a BASE type. + */ + static final int FLOAT = BASE | 2; + + /** + * The DOUBLE type. This is a BASE type. + */ + static final int DOUBLE = BASE | 3; + + /** + * The LONG type. This is a BASE type. + */ + static final int LONG = BASE | 4; + + /** + * The NULL type. This is a BASE type. + */ + static final int NULL = BASE | 5; + + /** + * The UNINITIALIZED_THIS type. This is a BASE type. + */ + static final int UNINITIALIZED_THIS = BASE | 6; + + /** + * The stack size variation corresponding to each JVM instruction. This + * stack variation is equal to the size of the values produced by an + * instruction, minus the size of the values consumed by this instruction. + */ + static final int[] SIZE; + + /** + * Computes the stack size variation corresponding to each JVM instruction. + */ + static { + int i; + int[] b = new int[202]; + String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" + + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" + + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" + + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; + for (i = 0; i < b.length; ++i) { + b[i] = s.charAt(i) - 'E'; + } + SIZE = b; + + // code to generate the above string + // + // int NA = 0; // not applicable (unused opcode or variable size opcode) + // + // b = new int[] { + // 0, //NOP, // visitInsn + // 1, //ACONST_NULL, // - + // 1, //ICONST_M1, // - + // 1, //ICONST_0, // - + // 1, //ICONST_1, // - + // 1, //ICONST_2, // - + // 1, //ICONST_3, // - + // 1, //ICONST_4, // - + // 1, //ICONST_5, // - + // 2, //LCONST_0, // - + // 2, //LCONST_1, // - + // 1, //FCONST_0, // - + // 1, //FCONST_1, // - + // 1, //FCONST_2, // - + // 2, //DCONST_0, // - + // 2, //DCONST_1, // - + // 1, //BIPUSH, // visitIntInsn + // 1, //SIPUSH, // - + // 1, //LDC, // visitLdcInsn + // NA, //LDC_W, // - + // NA, //LDC2_W, // - + // 1, //ILOAD, // visitVarInsn + // 2, //LLOAD, // - + // 1, //FLOAD, // - + // 2, //DLOAD, // - + // 1, //ALOAD, // - + // NA, //ILOAD_0, // - + // NA, //ILOAD_1, // - + // NA, //ILOAD_2, // - + // NA, //ILOAD_3, // - + // NA, //LLOAD_0, // - + // NA, //LLOAD_1, // - + // NA, //LLOAD_2, // - + // NA, //LLOAD_3, // - + // NA, //FLOAD_0, // - + // NA, //FLOAD_1, // - + // NA, //FLOAD_2, // - + // NA, //FLOAD_3, // - + // NA, //DLOAD_0, // - + // NA, //DLOAD_1, // - + // NA, //DLOAD_2, // - + // NA, //DLOAD_3, // - + // NA, //ALOAD_0, // - + // NA, //ALOAD_1, // - + // NA, //ALOAD_2, // - + // NA, //ALOAD_3, // - + // -1, //IALOAD, // visitInsn + // 0, //LALOAD, // - + // -1, //FALOAD, // - + // 0, //DALOAD, // - + // -1, //AALOAD, // - + // -1, //BALOAD, // - + // -1, //CALOAD, // - + // -1, //SALOAD, // - + // -1, //ISTORE, // visitVarInsn + // -2, //LSTORE, // - + // -1, //FSTORE, // - + // -2, //DSTORE, // - + // -1, //ASTORE, // - + // NA, //ISTORE_0, // - + // NA, //ISTORE_1, // - + // NA, //ISTORE_2, // - + // NA, //ISTORE_3, // - + // NA, //LSTORE_0, // - + // NA, //LSTORE_1, // - + // NA, //LSTORE_2, // - + // NA, //LSTORE_3, // - + // NA, //FSTORE_0, // - + // NA, //FSTORE_1, // - + // NA, //FSTORE_2, // - + // NA, //FSTORE_3, // - + // NA, //DSTORE_0, // - + // NA, //DSTORE_1, // - + // NA, //DSTORE_2, // - + // NA, //DSTORE_3, // - + // NA, //ASTORE_0, // - + // NA, //ASTORE_1, // - + // NA, //ASTORE_2, // - + // NA, //ASTORE_3, // - + // -3, //IASTORE, // visitInsn + // -4, //LASTORE, // - + // -3, //FASTORE, // - + // -4, //DASTORE, // - + // -3, //AASTORE, // - + // -3, //BASTORE, // - + // -3, //CASTORE, // - + // -3, //SASTORE, // - + // -1, //POP, // - + // -2, //POP2, // - + // 1, //DUP, // - + // 1, //DUP_X1, // - + // 1, //DUP_X2, // - + // 2, //DUP2, // - + // 2, //DUP2_X1, // - + // 2, //DUP2_X2, // - + // 0, //SWAP, // - + // -1, //IADD, // - + // -2, //LADD, // - + // -1, //FADD, // - + // -2, //DADD, // - + // -1, //ISUB, // - + // -2, //LSUB, // - + // -1, //FSUB, // - + // -2, //DSUB, // - + // -1, //IMUL, // - + // -2, //LMUL, // - + // -1, //FMUL, // - + // -2, //DMUL, // - + // -1, //IDIV, // - + // -2, //LDIV, // - + // -1, //FDIV, // - + // -2, //DDIV, // - + // -1, //IREM, // - + // -2, //LREM, // - + // -1, //FREM, // - + // -2, //DREM, // - + // 0, //INEG, // - + // 0, //LNEG, // - + // 0, //FNEG, // - + // 0, //DNEG, // - + // -1, //ISHL, // - + // -1, //LSHL, // - + // -1, //ISHR, // - + // -1, //LSHR, // - + // -1, //IUSHR, // - + // -1, //LUSHR, // - + // -1, //IAND, // - + // -2, //LAND, // - + // -1, //IOR, // - + // -2, //LOR, // - + // -1, //IXOR, // - + // -2, //LXOR, // - + // 0, //IINC, // visitIincInsn + // 1, //I2L, // visitInsn + // 0, //I2F, // - + // 1, //I2D, // - + // -1, //L2I, // - + // -1, //L2F, // - + // 0, //L2D, // - + // 0, //F2I, // - + // 1, //F2L, // - + // 1, //F2D, // - + // -1, //D2I, // - + // 0, //D2L, // - + // -1, //D2F, // - + // 0, //I2B, // - + // 0, //I2C, // - + // 0, //I2S, // - + // -3, //LCMP, // - + // -1, //FCMPL, // - + // -1, //FCMPG, // - + // -3, //DCMPL, // - + // -3, //DCMPG, // - + // -1, //IFEQ, // visitJumpInsn + // -1, //IFNE, // - + // -1, //IFLT, // - + // -1, //IFGE, // - + // -1, //IFGT, // - + // -1, //IFLE, // - + // -2, //IF_ICMPEQ, // - + // -2, //IF_ICMPNE, // - + // -2, //IF_ICMPLT, // - + // -2, //IF_ICMPGE, // - + // -2, //IF_ICMPGT, // - + // -2, //IF_ICMPLE, // - + // -2, //IF_ACMPEQ, // - + // -2, //IF_ACMPNE, // - + // 0, //GOTO, // - + // 1, //JSR, // - + // 0, //RET, // visitVarInsn + // -1, //TABLESWITCH, // visiTableSwitchInsn + // -1, //LOOKUPSWITCH, // visitLookupSwitch + // -1, //IRETURN, // visitInsn + // -2, //LRETURN, // - + // -1, //FRETURN, // - + // -2, //DRETURN, // - + // -1, //ARETURN, // - + // 0, //RETURN, // - + // NA, //GETSTATIC, // visitFieldInsn + // NA, //PUTSTATIC, // - + // NA, //GETFIELD, // - + // NA, //PUTFIELD, // - + // NA, //INVOKEVIRTUAL, // visitMethodInsn + // NA, //INVOKESPECIAL, // - + // NA, //INVOKESTATIC, // - + // NA, //INVOKEINTERFACE, // - + // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn + // 1, //NEW, // visitTypeInsn + // 0, //NEWARRAY, // visitIntInsn + // 0, //ANEWARRAY, // visitTypeInsn + // 0, //ARRAYLENGTH, // visitInsn + // NA, //ATHROW, // - + // 0, //CHECKCAST, // visitTypeInsn + // 0, //INSTANCEOF, // - + // -1, //MONITORENTER, // visitInsn + // -1, //MONITOREXIT, // - + // NA, //WIDE, // NOT VISITED + // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn + // -1, //IFNULL, // visitJumpInsn + // -1, //IFNONNULL, // - + // NA, //GOTO_W, // - + // NA, //JSR_W, // - + // }; + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('E' + b[i])); + // } + // System.err.println(); + } + + /** + * The label (i.e. basic block) to which these input and output stack map + * frames correspond. + */ + Label owner; + + /** + * The input stack map frame locals. + */ + int[] inputLocals; + + /** + * The input stack map frame stack. + */ + int[] inputStack; + + /** + * The output stack map frame locals. + */ + private int[] outputLocals; + + /** + * The output stack map frame stack. + */ + private int[] outputStack; + + /** + * Relative size of the output stack. The exact semantics of this field + * depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the size of + * the output stack relatively to the top of the input stack. + * + * When the stack map frames are completely computed, this field is the + * actual number of types in {@link #outputStack}. + */ + private int outputStackTop; + + /** + * Number of types that are initialized in the basic block. + * + * @see #initializations + */ + private int initializationCount; + + /** + * The types that are initialized in the basic block. A constructor + * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace + * every occurence of this type in the local variables and in the + * operand stack. This cannot be done during the first phase of the + * algorithm since, during this phase, the local variables and the operand + * stack are not completely computed. It is therefore necessary to store the + * types on which constructors are invoked in the basic block, in order to + * do this replacement during the second phase of the algorithm, where the + * frames are fully computed. Note that this array can contain types that + * are relative to input locals or to the input stack (see below for the + * description of the algorithm). + */ + private int[] initializations; + + /** + * Returns the output frame local variable type at the given index. + * + * @param local + * the index of the local that must be returned. + * @return the output frame local variable type at the given index. + */ + private int get(final int local) { + if (outputLocals == null || local >= outputLocals.length) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + return LOCAL | local; + } else { + int type = outputLocals[local]; + if (type == 0) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + type = outputLocals[local] = LOCAL | local; + } + return type; + } + } + + /** + * Sets the output frame local variable type at the given index. + * + * @param local + * the index of the local that must be set. + * @param type + * the value of the local that must be set. + */ + private void set(final int local, final int type) { + // creates and/or resizes the output local variables array if necessary + if (outputLocals == null) { + outputLocals = new int[10]; + } + int n = outputLocals.length; + if (local >= n) { + int[] t = new int[Math.max(local + 1, 2 * n)]; + System.arraycopy(outputLocals, 0, t, 0, n); + outputLocals = t; + } + // sets the local variable + outputLocals[local] = type; + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param type + * the type that must be pushed. + */ + private void push(final int type) { + // creates and/or resizes the output stack array if necessary + if (outputStack == null) { + outputStack = new int[10]; + } + int n = outputStack.length; + if (outputStackTop >= n) { + int[] t = new int[Math.max(outputStackTop + 1, 2 * n)]; + System.arraycopy(outputStack, 0, t, 0, n); + outputStack = t; + } + // pushes the type on the output stack + outputStack[outputStackTop++] = type; + // updates the maximun height reached by the output stack, if needed + int top = owner.inputStackTop + outputStackTop; + if (top > owner.outputStackMax) { + owner.outputStackMax = top; + } + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param desc + * the descriptor of the type to be pushed. Can also be a method + * descriptor (in this case this method pushes its return type + * onto the output frame stack). + */ + private void push(final ClassWriter cw, final String desc) { + int type = type(cw, desc); + if (type != 0) { + push(type); + if (type == LONG || type == DOUBLE) { + push(TOP); + } + } + } + + /** + * Returns the int encoding of the given type. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param desc + * a type descriptor. + * @return the int encoding of the given type. + */ + private static int type(final ClassWriter cw, final String desc) { + String t; + int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; + switch (desc.charAt(index)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + // stores the internal name, not the descriptor! + t = desc.substring(index + 1, desc.length() - 1); + return OBJECT | cw.addType(t); + // case '[': + default: + // extracts the dimensions and the element type + int data; + int dims = index + 1; + while (desc.charAt(dims) == '[') { + ++dims; + } + switch (desc.charAt(dims)) { + case 'Z': + data = BOOLEAN; + break; + case 'C': + data = CHAR; + break; + case 'B': + data = BYTE; + break; + case 'S': + data = SHORT; + break; + case 'I': + data = INTEGER; + break; + case 'F': + data = FLOAT; + break; + case 'J': + data = LONG; + break; + case 'D': + data = DOUBLE; + break; + // case 'L': + default: + // stores the internal name, not the descriptor + t = desc.substring(dims + 1, desc.length() - 1); + data = OBJECT | cw.addType(t); + } + return (dims - index) << 28 | data; + } + } + + /** + * Pops a type from the output frame stack and returns its value. + * + * @return the type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // if the output frame stack is empty, pops from the input stack + return STACK | -(--owner.inputStackTop); + } + } + + /** + * Pops the given number of types from the output frame stack. + * + * @param elements + * the number of types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // if the number of elements to be popped is greater than the number + // of elements in the output stack, clear it, and pops the remaining + // elements from the input stack. + owner.inputStackTop -= elements - outputStackTop; + outputStackTop = 0; + } + } + + /** + * Pops a type from the output frame stack. + * + * @param desc + * the descriptor of the type to be popped. Can also be a method + * descriptor (in this case this method pops the types + * corresponding to the method arguments). + */ + private void pop(final String desc) { + char c = desc.charAt(0); + if (c == '(') { + pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1); + } else if (c == 'J' || c == 'D') { + pop(2); + } else { + pop(1); + } + } + + /** + * Adds a new type to the list of types on which a constructor is invoked in + * the basic block. + * + * @param var + * a type on a which a constructor is invoked. + */ + private void init(final int var) { + // creates and/or resizes the initializations array if necessary + if (initializations == null) { + initializations = new int[2]; + } + int n = initializations.length; + if (initializationCount >= n) { + int[] t = new int[Math.max(initializationCount + 1, 2 * n)]; + System.arraycopy(initializations, 0, t, 0, n); + initializations = t; + } + // stores the type to be initialized + initializations[initializationCount++] = var; + } + + /** + * Replaces the given type with the appropriate type if it is one of the + * types on which a constructor is invoked in the basic block. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param t + * a type + * @return t or, if t is one of the types on which a constructor is invoked + * in the basic block, the type corresponding to this constructor. + */ + private int init(final ClassWriter cw, final int t) { + int s; + if (t == UNINITIALIZED_THIS) { + s = OBJECT | cw.addType(cw.thisName); + } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) { + String type = cw.typeTable[t & BASE_VALUE].strVal1; + s = OBJECT | cw.addType(type); + } else { + return t; + } + for (int j = 0; j < initializationCount; ++j) { + int u = initializations[j]; + int dim = u & DIM; + int kind = u & KIND; + if (kind == LOCAL) { + u = dim + inputLocals[u & VALUE]; + } else if (kind == STACK) { + u = dim + inputStack[inputStack.length - (u & VALUE)]; + } + if (t == u) { + return s; + } + } + return t; + } + + /** + * Initializes the input frame of the first basic block from the method + * descriptor. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param access + * the access flags of the method to which this label belongs. + * @param args + * the formal parameter types of this method. + * @param maxLocals + * the maximum number of local variables of this method. + */ + void initInputFrame(final ClassWriter cw, final int access, + final Type[] args, final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int i = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) { + inputLocals[i++] = OBJECT | cw.addType(cw.thisName); + } else { + inputLocals[i++] = UNINITIALIZED_THIS; + } + } + for (int j = 0; j < args.length; ++j) { + int t = type(cw, args[j].getDescriptor()); + inputLocals[i++] = t; + if (t == LONG || t == DOUBLE) { + inputLocals[i++] = TOP; + } + } + while (i < maxLocals) { + inputLocals[i++] = TOP; + } + } + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode + * the opcode of the instruction. + * @param arg + * the operand of the instruction, if any. + * @param cw + * the class writer to which this label belongs. + * @param item + * the operand of the instructions, if any. + */ + void execute(final int opcode, final int arg, final ClassWriter cw, + final Item item) { + int t1, t2, t3, t4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (item.type) { + case ClassWriter.INT: + push(INTEGER); + break; + case ClassWriter.LONG: + push(LONG); + push(TOP); + break; + case ClassWriter.FLOAT: + push(FLOAT); + break; + case ClassWriter.DOUBLE: + push(DOUBLE); + push(TOP); + break; + case ClassWriter.CLASS: + push(OBJECT | cw.addType("java/lang/Class")); + break; + case ClassWriter.STR: + push(OBJECT | cw.addType("java/lang/String")); + break; + case ClassWriter.MTYPE: + push(OBJECT | cw.addType("java/lang/invoke/MethodType")); + break; + // case ClassWriter.HANDLE_BASE + [1..9]: + default: + push(OBJECT | cw.addType("java/lang/invoke/MethodHandle")); + } + break; + case Opcodes.ALOAD: + push(get(arg)); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(INTEGER); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + pop(2); + push(FLOAT); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + t1 = pop(); + push(ELEMENT_OF + t1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + t1 = pop(); + set(arg, t1); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + t1 = pop(); + set(arg, t1); + set(arg + 1, TOP); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + t1 = pop(); + push(t1); + push(t1); + break; + case Opcodes.DUP_X1: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2: + t1 = pop(); + t2 = pop(); + push(t2); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X1: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t2); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + t4 = pop(); + push(t2); + push(t1); + push(t4); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.SWAP: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + break; + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + set(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new RuntimeException( + "JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(cw, item.strVal3); + break; + case Opcodes.PUTSTATIC: + pop(item.strVal3); + break; + case Opcodes.GETFIELD: + pop(1); + push(cw, item.strVal3); + break; + case Opcodes.PUTFIELD: + pop(item.strVal3); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(item.strVal3); + if (opcode != Opcodes.INVOKESTATIC) { + t1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL + && item.strVal2.charAt(0) == '<') { + init(t1); + } + } + push(cw, item.strVal3); + break; + case Opcodes.INVOKEDYNAMIC: + pop(item.strVal2); + push(cw, item.strVal2); + break; + case Opcodes.NEW: + push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + // case Opcodes.T_LONG: + default: + push(ARRAY_OF | LONG); + break; + } + break; + case Opcodes.ANEWARRAY: + String s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, '[' + s); + } else { + push(ARRAY_OF | OBJECT | cw.addType(s)); + } + break; + case Opcodes.CHECKCAST: + s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, s); + } else { + push(OBJECT | cw.addType(s)); + } + break; + // case Opcodes.MULTIANEWARRAY: + default: + pop(arg); + push(cw, item.strVal1); + break; + } + } + + /** + * Merges the input frame of the given basic block with the input and output + * frames of this basic block. Returns true if the input frame of + * the given label has been changed by this operation. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param frame + * the basic block whose input frame must be updated. + * @param edge + * the kind of the {@link Edge} between this label and 'label'. + * See {@link Edge#info}. + * @return true if the input frame of the given label has been + * changed by this operation. + */ + boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + boolean changed = false; + int i, s, dim, kind, t; + + int nLocal = inputLocals.length; + int nStack = inputStack.length; + if (frame.inputLocals == null) { + frame.inputLocals = new int[nLocal]; + changed = true; + } + + for (i = 0; i < nLocal; ++i) { + if (outputLocals != null && i < outputLocals.length) { + s = outputLocals[i]; + if (s == 0) { + t = inputLocals[i]; + } else { + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 + && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + } + } else { + t = inputLocals[i]; + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputLocals, i); + } + + if (edge > 0) { + for (i = 0; i < nLocal; ++i) { + t = inputLocals[i]; + changed |= merge(cw, t, frame.inputLocals, i); + } + if (frame.inputStack == null) { + frame.inputStack = new int[1]; + changed = true; + } + changed |= merge(cw, edge, frame.inputStack, 0); + return changed; + } + + int nInputStack = inputStack.length + owner.inputStackTop; + if (frame.inputStack == null) { + frame.inputStack = new int[nInputStack + outputStackTop]; + changed = true; + } + + for (i = 0; i < nInputStack; ++i) { + t = inputStack[i]; + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, i); + } + for (i = 0; i < outputStackTop; ++i) { + s = outputStack[i]; + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 + && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, nInputStack + i); + } + return changed; + } + + /** + * Merges the type at the given index in the given type array with the given + * type. Returns true if the type array has been modified by this + * operation. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param t + * the type with which the type array element must be merged. + * @param types + * an array of types. + * @param index + * the index of the type that must be merged in 'types'. + * @return true if the type array has been modified by this + * operation. + */ + private static boolean merge(final ClassWriter cw, int t, + final int[] types, final int index) { + int u = types[index]; + if (u == t) { + // if the types are equal, merge(u,t)=u, so there is no change + return false; + } + if ((t & ~DIM) == NULL) { + if (u == NULL) { + return false; + } + t = NULL; + } + if (u == 0) { + // if types[index] has never been assigned, merge(u,t)=t + types[index] = t; + return true; + } + int v; + if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) { + // if u is a reference type of any dimension + if (t == NULL) { + // if t is the NULL type, merge(u,t)=u, so there is no change + return false; + } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { + if ((u & BASE_KIND) == OBJECT) { + // if t is also a reference type, and if u and t have the + // same dimension merge(u,t) = dim(t) | common parent of the + // element types of u and t + v = (t & DIM) | OBJECT + | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); + } else { + // if u and t are array types, but not with the same element + // type, merge(u,t)=java/lang/Object + v = OBJECT | cw.addType("java/lang/Object"); + } + } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { + // if t is any other reference or array type, + // merge(u,t)=java/lang/Object + v = OBJECT | cw.addType("java/lang/Object"); + } else { + // if t is any other type, merge(u,t)=TOP + v = TOP; + } + } else if (u == NULL) { + // if u is the NULL type, merge(u,t)=t, + // or TOP if t is not a reference type + v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP; + } else { + // if u is any other type, merge(u,t)=TOP whatever t + v = TOP; + } + if (u != v) { + types[index] = v; + return true; + } + return false; + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handle.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handle.java new file mode 100644 index 0000000000..0a91c3a4b9 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handle.java @@ -0,0 +1,170 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.apache.tajo.org.objectweb.asm; + +/** + * A reference to a field or a method. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public final class Handle { + + /** + * The kind of field or method designated by this Handle. Should be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + final int tag; + + /** + * The internal name of the class that owns the field or method designated + * by this handle. + */ + final String owner; + + /** + * The name of the field or method designated by this handle. + */ + final String name; + + /** + * The descriptor of the field or method designated by this handle. + */ + final String desc; + + /** + * Constructs a new field or method handle. + * + * @param tag + * the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the class that owns the field or method + * designated by this handle. + * @param name + * the name of the field or method designated by this handle. + * @param desc + * the descriptor of the field or method designated by this + * handle. + */ + public Handle(int tag, String owner, String name, String desc) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.desc = desc; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the class that owns the field or method + * designated by this handle. + * + * @return the internal name of the class that owns the field or method + * designated by this handle. + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return desc; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Handle)) { + return false; + } + Handle h = (Handle) obj; + return tag == h.tag && owner.equals(h.owner) && name.equals(h.name) + && desc.equals(h.desc); + } + + @Override + public int hashCode() { + return tag + owner.hashCode() * name.hashCode() * desc.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual + * representation is: + * + *
+     * owner '.' name desc ' ' '(' tag ')'
+     * 
+ * + * . As this format is unambiguous, it can be parsed if necessary. + */ + @Override + public String toString() { + return owner + '.' + name + desc + " (" + tag + ')'; + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handler.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handler.java new file mode 100644 index 0000000000..da2af36c0a --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Handler.java @@ -0,0 +1,121 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * Information about an exception handler block. + * + * @author Eric Bruneton + */ +class Handler { + + /** + * Beginning of the exception handler's scope (inclusive). + */ + Label start; + + /** + * End of the exception handler's scope (exclusive). + */ + Label end; + + /** + * Beginning of the exception handler's code. + */ + Label handler; + + /** + * Internal name of the type of exceptions handled by this handler, or + * null to catch any exceptions. + */ + String desc; + + /** + * Constant pool index of the internal name of the type of exceptions + * handled by this handler, or 0 to catch any exceptions. + */ + int type; + + /** + * Next exception handler block info. + */ + Handler next; + + /** + * Removes the range between start and end from the given exception + * handlers. + * + * @param h + * an exception handler list. + * @param start + * the start of the range to be removed. + * @param end + * the end of the range to be removed. Maybe null. + * @return the exception handler list with the start-end range removed. + */ + static Handler remove(Handler h, Label start, Label end) { + if (h == null) { + return null; + } else { + h.next = remove(h.next, start, end); + } + int hstart = h.start.position; + int hend = h.end.position; + int s = start.position; + int e = end == null ? Integer.MAX_VALUE : end.position; + // if [hstart,hend[ and [s,e[ intervals intersect... + if (s < hend && e > hstart) { + if (s <= hstart) { + if (e >= hend) { + // [hstart,hend[ fully included in [s,e[, h removed + h = h.next; + } else { + // [hstart,hend[ minus [s,e[ = [e,hend[ + h.start = end; + } + } else if (e >= hend) { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + h.end = start; + } else { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[ + Handler g = new Handler(); + g.start = end; + g.end = h.end; + g.handler = h.handler; + g.desc = h.desc; + g.type = h.type; + g.next = h.next; + h.end = start; + h.next = g; + } + } + return h; + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Item.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Item.java new file mode 100644 index 0000000000..58a24d30f5 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Item.java @@ -0,0 +1,311 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A constant pool item. Constant pool items can be created with the 'newXXX' + * methods in the {@link ClassWriter} class. + * + * @author Eric Bruneton + */ +final class Item { + + /** + * Index of this item in the constant pool. + */ + int index; + + /** + * Type of this constant pool item. A single class is used to represent all + * constant pool item types, in order to minimize the bytecode size of this + * package. The value of this field is one of {@link ClassWriter#INT}, + * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT}, + * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8}, + * {@link ClassWriter#STR}, {@link ClassWriter#CLASS}, + * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD}, + * {@link ClassWriter#METH}, {@link ClassWriter#IMETH}, + * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}. + * + * MethodHandle constant 9 variations are stored using a range of 9 values + * from {@link ClassWriter#HANDLE_BASE} + 1 to + * {@link ClassWriter#HANDLE_BASE} + 9. + * + * Special Item types are used for Items that are stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. These special item types are + * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and + * {@link ClassWriter#TYPE_MERGED}. + */ + int type; + + /** + * Value of this item, for an integer item. + */ + int intVal; + + /** + * Value of this item, for a long item. + */ + long longVal; + + /** + * First part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal1; + + /** + * Second part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal2; + + /** + * Third part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal3; + + /** + * The hash code value of this constant pool item. + */ + int hashCode; + + /** + * Link to another constant pool item, used for collision lists in the + * constant pool's hash table. + */ + Item next; + + /** + * Constructs an uninitialized {@link Item}. + */ + Item() { + } + + /** + * Constructs an uninitialized {@link Item} for constant pool element at + * given position. + * + * @param index + * index of the item to be constructed. + */ + Item(final int index) { + this.index = index; + } + + /** + * Constructs a copy of the given item. + * + * @param index + * index of the item to be constructed. + * @param i + * the item that must be copied into the item to be constructed. + */ + Item(final int index, final Item i) { + this.index = index; + type = i.type; + intVal = i.intVal; + longVal = i.longVal; + strVal1 = i.strVal1; + strVal2 = i.strVal2; + strVal3 = i.strVal3; + hashCode = i.hashCode; + } + + /** + * Sets this item to an integer item. + * + * @param intVal + * the value of this item. + */ + void set(final int intVal) { + this.type = ClassWriter.INT; + this.intVal = intVal; + this.hashCode = 0x7FFFFFFF & (type + intVal); + } + + /** + * Sets this item to a long item. + * + * @param longVal + * the value of this item. + */ + void set(final long longVal) { + this.type = ClassWriter.LONG; + this.longVal = longVal; + this.hashCode = 0x7FFFFFFF & (type + (int) longVal); + } + + /** + * Sets this item to a float item. + * + * @param floatVal + * the value of this item. + */ + void set(final float floatVal) { + this.type = ClassWriter.FLOAT; + this.intVal = Float.floatToRawIntBits(floatVal); + this.hashCode = 0x7FFFFFFF & (type + (int) floatVal); + } + + /** + * Sets this item to a double item. + * + * @param doubleVal + * the value of this item. + */ + void set(final double doubleVal) { + this.type = ClassWriter.DOUBLE; + this.longVal = Double.doubleToRawLongBits(doubleVal); + this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal); + } + + /** + * Sets this item to an item that do not hold a primitive value. + * + * @param type + * the type of this item. + * @param strVal1 + * first part of the value of this item. + * @param strVal2 + * second part of the value of this item. + * @param strVal3 + * third part of the value of this item. + */ + void set(final int type, final String strVal1, final String strVal2, + final String strVal3) { + this.type = type; + this.strVal1 = strVal1; + this.strVal2 = strVal2; + this.strVal3 = strVal3; + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); + return; + case ClassWriter.NAME_TYPE: { + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode()); + return; + } + // ClassWriter.FIELD: + // ClassWriter.METH: + // ClassWriter.IMETH: + // ClassWriter.HANDLE_BASE + 1..9 + default: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode() * strVal3.hashCode()); + } + } + + /** + * Sets the item to an InvokeDynamic item. + * + * @param name + * invokedynamic's name. + * @param desc + * invokedynamic's desc. + * @param bsmIndex + * zero based index into the class attribute BootrapMethods. + */ + void set(String name, String desc, int bsmIndex) { + this.type = ClassWriter.INDY; + this.longVal = bsmIndex; + this.strVal1 = name; + this.strVal2 = desc; + this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex + * strVal1.hashCode() * strVal2.hashCode()); + } + + /** + * Sets the item to a BootstrapMethod item. + * + * @param position + * position in byte in the class attribute BootrapMethods. + * @param hashCode + * hashcode of the item. This hashcode is processed from the + * hashcode of the bootstrap method and the hashcode of all + * bootstrap arguments. + */ + void set(int position, int hashCode) { + this.type = ClassWriter.BSM; + this.intVal = position; + this.hashCode = hashCode; + } + + /** + * Indicates if the given item is equal to this one. This method assumes + * that the two items have the same {@link #type}. + * + * @param i + * the item to be compared to this one. Both items must have the + * same {@link #type}. + * @return true if the given item if equal to this one, + * false otherwise. + */ + boolean isEqualTo(final Item i) { + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + return i.strVal1.equals(strVal1); + case ClassWriter.TYPE_MERGED: + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + return i.longVal == longVal; + case ClassWriter.INT: + case ClassWriter.FLOAT: + return i.intVal == intVal; + case ClassWriter.TYPE_UNINIT: + return i.intVal == intVal && i.strVal1.equals(strVal1); + case ClassWriter.NAME_TYPE: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2); + case ClassWriter.INDY: { + return i.longVal == longVal && i.strVal1.equals(strVal1) + && i.strVal2.equals(strVal2); + } + // case ClassWriter.FIELD: + // case ClassWriter.METH: + // case ClassWriter.IMETH: + // case ClassWriter.HANDLE_BASE + 1..9 + default: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2) + && i.strVal3.equals(strVal3); + } + } + +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Label.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Label.java new file mode 100644 index 0000000000..bf061ba7fb --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Label.java @@ -0,0 +1,560 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A label represents a position in the bytecode of a method. Labels are used + * for jump, goto, and switch instructions, and for try catch blocks. A label + * designates the instruction that is just after. Note however that there + * can be other elements between a label and the instruction it designates (such + * as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * Indicates if this label is only used for debug attributes. Such a label + * is not the start of a basic block, the target of a jump instruction, or + * an exception handler. It can be safely ignored in control flow graph + * analysis algorithms (for optimization purposes). + */ + static final int DEBUG = 1; + + /** + * Indicates if the position of this label is known. + */ + static final int RESOLVED = 2; + + /** + * Indicates if this label has been updated, after instruction resizing. + */ + static final int RESIZED = 4; + + /** + * Indicates if this basic block has been pushed in the basic block stack. + * See {@link MethodWriter#visitMaxs visitMaxs}. + */ + static final int PUSHED = 8; + + /** + * Indicates if this label is the target of a jump instruction, or the start + * of an exception handler. + */ + static final int TARGET = 16; + + /** + * Indicates if a stack map frame must be stored for this label. + */ + static final int STORE = 32; + + /** + * Indicates if this label corresponds to a reachable basic block. + */ + static final int REACHABLE = 64; + + /** + * Indicates if this basic block ends with a JSR instruction. + */ + static final int JSR = 128; + + /** + * Indicates if this basic block ends with a RET instruction. + */ + static final int RET = 256; + + /** + * Indicates if this basic block is the start of a subroutine. + */ + static final int SUBROUTINE = 512; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(null, ...) call. + */ + static final int VISITED = 1024; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(!null, ...) call. + */ + static final int VISITED2 = 2048; + + /** + * Field used to associate user information to a label. Warning: this field + * is used by the ASM tree package. In order to use it with the ASM tree + * package you must override the + * {@link org.apache.tajo.org.objectweb.asm.tree.MethodNode#getLabelNode} method. + */ + public Object info; + + /** + * Flags that indicate the status of this label. + * + * @see #DEBUG + * @see #RESOLVED + * @see #RESIZED + * @see #PUSHED + * @see #TARGET + * @see #STORE + * @see #REACHABLE + * @see #JSR + * @see #RET + */ + int status; + + /** + * The line number corresponding to this label, if known. + */ + int line; + + /** + * The position of this label in the code, if known. + */ + int position; + + /** + * Number of forward references to this label, times two. + */ + private int referenceCount; + + /** + * Informations about forward references. Each forward reference is + * described by two consecutive integers in this array: the first one is the + * position of the first byte of the bytecode instruction that contains the + * forward reference, while the second is the position of the first byte of + * the forward reference itself. In fact the sign of the first integer + * indicates if this reference uses 2 or 4 bytes, and its absolute value + * gives the position of the bytecode instruction. This array is also used + * as a bitset to store the subroutines to which a basic block belongs. This + * information is needed in {@linked MethodWriter#visitMaxs}, after all + * forward references have been resolved. Hence the same array can be used + * for both purposes without problems. + */ + private int[] srcAndRefPositions; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow and data flow graph analysis algorithms (used + * to compute the maximum stack size or the stack map frames). A control + * flow graph contains one node per "basic block", and one edge per "jump" + * from one basic block to another. Each node (i.e., each basic block) is + * represented by the Label object that corresponds to the first instruction + * of this basic block. Each node also stores the list of its successors in + * the graph, as a linked list of Edge objects. + * + * The control flow analysis algorithms used to compute the maximum stack + * size or the stack map frames are similar and use two steps. The first + * step, during the visit of each instruction, builds information about the + * state of the local variables and the operand stack at the end of each + * basic block, called the "output frame", relatively to the frame + * state at the beginning of the basic block, which is called the "input + * frame", and which is unknown during this step. The second step, in + * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes + * information about the input frame of each basic block, from the input + * state of the first basic block (known from the method signature), and by + * the using the previously computed relative output frames. + * + * The algorithm used to compute the maximum stack size only computes the + * relative output and absolute input stack heights, while the algorithm + * used to compute stack map frames computes relative output frames and + * absolute input frames. + */ + + /** + * Start of the output stack relatively to the input stack. The exact + * semantics of this field depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the number of + * elements in the input stack. + * + * When the stack map frames are completely computed, this field is the + * offset of the first output stack element relatively to the top of the + * input stack. This offset is always negative or null. A null offset means + * that the output stack must be appended to the input stack. A -n offset + * means that the first n output stack elements must replace the top n input + * stack elements, and that the other elements must be appended to the input + * stack. + */ + int inputStackTop; + + /** + * Maximum height reached by the output stack, relatively to the top of the + * input stack. This maximum is always positive or null. + */ + int outputStackMax; + + /** + * Information about the input and output stack map frames of this basic + * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES} + * option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited. This linked + * list does not include labels used for debug info only. If + * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it + * does not contain successive labels that denote the same bytecode position + * (in this case only the first label appears in this list). + */ + Label successor; + + /** + * The successors of this node in the control flow graph. These successors + * are stored in a linked list of {@link Edge Edge} objects, linked to each + * other by their {@link Edge#next} field. + */ + Edge successors; + + /** + * The next basic block in the basic block stack. This stack is used in the + * main loop of the fix point algorithm used in the second step of the + * control flow analysis algorithms. It is also used in + * {@link #visitSubroutine} to avoid using a recursive method. + * + * @see MethodWriter#visitMaxs + */ + Label next; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new label. + */ + public Label() { + } + + // ------------------------------------------------------------------------ + // Methods to compute offsets and to manage forward references + // ------------------------------------------------------------------------ + + /** + * Returns the offset corresponding to this label. This offset is computed + * from the start of the method's bytecode. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @return the offset corresponding to this label. + * @throws IllegalStateException + * if this label is not resolved yet. + */ + public int getOffset() { + if ((status & RESOLVED) == 0) { + throw new IllegalStateException( + "Label offset position has not been resolved yet"); + } + return position; + } + + /** + * Puts a reference to this label in the bytecode of a method. If the + * position of the label is known, the offset is computed and written + * directly. Otherwise, a null offset is written and a new forward reference + * is declared for this label. + * + * @param owner + * the code writer that calls this method. + * @param out + * the bytecode of the method. + * @param source + * the position of first byte of the bytecode instruction that + * contains this label. + * @param wideOffset + * true if the reference must be stored in 4 bytes, or + * false if it must be stored with 2 bytes. + * @throws IllegalArgumentException + * if this label has not been created by the given code writer. + */ + void put(final MethodWriter owner, final ByteVector out, final int source, + final boolean wideOffset) { + if ((status & RESOLVED) == 0) { + if (wideOffset) { + addReference(-1 - source, out.length); + out.putInt(-1); + } else { + addReference(source, out.length); + out.putShort(-1); + } + } else { + if (wideOffset) { + out.putInt(position - source); + } else { + out.putShort(position - source); + } + } + } + + /** + * Adds a forward reference to this label. This method must be called only + * for a true forward reference, i.e. only if this label is not resolved + * yet. For backward references, the offset of the reference can be, and + * must be, computed and stored directly. + * + * @param sourcePosition + * the position of the referencing instruction. This position + * will be used to compute the offset of this forward reference. + * @param referencePosition + * the position where the offset for this forward reference must + * be stored. + */ + private void addReference(final int sourcePosition, + final int referencePosition) { + if (srcAndRefPositions == null) { + srcAndRefPositions = new int[6]; + } + if (referenceCount >= srcAndRefPositions.length) { + int[] a = new int[srcAndRefPositions.length + 6]; + System.arraycopy(srcAndRefPositions, 0, a, 0, + srcAndRefPositions.length); + srcAndRefPositions = a; + } + srcAndRefPositions[referenceCount++] = sourcePosition; + srcAndRefPositions[referenceCount++] = referencePosition; + } + + /** + * Resolves all forward references to this label. This method must be called + * when this label is added to the bytecode of the method, i.e. when its + * position becomes known. This method fills in the blanks that where left + * in the bytecode by each forward reference previously added to this label. + * + * @param owner + * the code writer that calls this method. + * @param position + * the position of this label in the bytecode. + * @param data + * the bytecode of the method. + * @return true if a blank that was left for this label was to + * small to store the offset. In such a case the corresponding jump + * instruction is replaced with a pseudo instruction (using unused + * opcodes) using an unsigned two bytes offset. These pseudo + * instructions will need to be replaced with true instructions with + * wider offsets (4 bytes instead of 2). This is done in + * {@link MethodWriter#resizeInstructions}. + * @throws IllegalArgumentException + * if this label has already been resolved, or if it has not + * been created by the given code writer. + */ + boolean resolve(final MethodWriter owner, final int position, + final byte[] data) { + boolean needUpdate = false; + this.status |= RESOLVED; + this.position = position; + int i = 0; + while (i < referenceCount) { + int source = srcAndRefPositions[i++]; + int reference = srcAndRefPositions[i++]; + int offset; + if (source >= 0) { + offset = position - source; + if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { + /* + * changes the opcode of the jump instruction, in order to + * be able to find it later (see resizeInstructions in + * MethodWriter). These temporary opcodes are similar to + * jump instruction opcodes, except that the 2 bytes offset + * is unsigned (and can therefore represent values from 0 to + * 65535, which is sufficient since the size of a method is + * limited to 65535 bytes). + */ + int opcode = data[reference - 1] & 0xFF; + if (opcode <= Opcodes.JSR) { + // changes IFEQ ... JSR to opcodes 202 to 217 + data[reference - 1] = (byte) (opcode + 49); + } else { + // changes IFNULL and IFNONNULL to opcodes 218 and 219 + data[reference - 1] = (byte) (opcode + 20); + } + needUpdate = true; + } + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } else { + offset = position + source + 1; + data[reference++] = (byte) (offset >>> 24); + data[reference++] = (byte) (offset >>> 16); + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } + } + return needUpdate; + } + + /** + * Returns the first label of the series to which this label belongs. For an + * isolated label or for the first label in a series of successive labels, + * this method returns the label itself. For other labels it returns the + * first label of the series. + * + * @return the first label of the series to which this label belongs. + */ + Label getFirst() { + return !ClassReader.FRAMES || frame == null ? this : frame.owner; + } + + // ------------------------------------------------------------------------ + // Methods related to subroutines + // ------------------------------------------------------------------------ + + /** + * Returns true is this basic block belongs to the given subroutine. + * + * @param id + * a subroutine id. + * @return true is this basic block belongs to the given subroutine. + */ + boolean inSubroutine(final long id) { + if ((status & Label.VISITED) != 0) { + return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0; + } + return false; + } + + /** + * Returns true if this basic block and the given one belong to a common + * subroutine. + * + * @param block + * another basic block. + * @return true if this basic block and the given one belong to a common + * subroutine. + */ + boolean inSameSubroutine(final Label block) { + if ((status & VISITED) == 0 || (block.status & VISITED) == 0) { + return false; + } + for (int i = 0; i < srcAndRefPositions.length; ++i) { + if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) { + return true; + } + } + return false; + } + + /** + * Marks this basic block as belonging to the given subroutine. + * + * @param id + * a subroutine id. + * @param nbSubroutines + * the total number of subroutines in the method. + */ + void addToSubroutine(final long id, final int nbSubroutines) { + if ((status & VISITED) == 0) { + status |= VISITED; + srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1]; + } + srcAndRefPositions[(int) (id >>> 32)] |= (int) id; + } + + /** + * Finds the basic blocks that belong to a given subroutine, and marks these + * blocks as belonging to this subroutine. This method follows the control + * flow graph to find all the blocks that are reachable from the current + * block WITHOUT following any JSR target. + * + * @param JSR + * a JSR block that jumps to this subroutine. If this JSR is not + * null it is added to the successor of the RET blocks found in + * the subroutine. + * @param id + * the id of this subroutine. + * @param nbSubroutines + * the total number of subroutines in the method. + */ + void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) { + // user managed stack of labels, to avoid using a recursive method + // (recursivity can lead to stack overflow with very large methods) + Label stack = this; + while (stack != null) { + // removes a label l from the stack + Label l = stack; + stack = l.next; + l.next = null; + + if (JSR != null) { + if ((l.status & VISITED2) != 0) { + continue; + } + l.status |= VISITED2; + // adds JSR to the successors of l, if it is a RET block + if ((l.status & RET) != 0) { + if (!l.inSameSubroutine(JSR)) { + Edge e = new Edge(); + e.info = l.inputStackTop; + e.successor = JSR.successors.successor; + e.next = l.successors; + l.successors = e; + } + } + } else { + // if the l block already belongs to subroutine 'id', continue + if (l.inSubroutine(id)) { + continue; + } + // marks the l block as belonging to subroutine 'id' + l.addToSubroutine(id, nbSubroutines); + } + // pushes each successor of l on the stack, except JSR targets + Edge e = l.successors; + while (e != null) { + // if the l block is a JSR block, then 'l.successors.next' leads + // to the JSR target (see {@link #visitJumpInsn}) and must + // therefore not be followed + if ((l.status & Label.JSR) == 0 || e != l.successors.next) { + // pushes e.successor on the stack if it not already added + if (e.successor.next == null) { + e.successor.next = stack; + stack = e.successor; + } + } + e = e.next; + } + } + } + + // ------------------------------------------------------------------------ + // Overriden Object methods + // ------------------------------------------------------------------------ + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodVisitor.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodVisitor.java new file mode 100644 index 0000000000..1d6758e96b --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodVisitor.java @@ -0,0 +1,662 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A visitor to visit a Java method. The methods of this class must be called in + * the following order: [ visitAnnotationDefault ] ( + * visitAnnotation | visitParameterAnnotation | + * visitAttribute )* [ visitCode ( visitFrame | + * visitXInsn
| visitLabel | + * visitTryCatchBlock | visitLocalVariable | + * visitLineNumber )* visitMaxs ] visitEnd. In + * addition, the visitXInsn
and visitLabel methods + * must be called in the sequential order of the bytecode instructions of the + * visited code, visitTryCatchBlock must be called before the + * labels passed as arguments have been visited, and the + * visitLocalVariable and visitLineNumber methods must be + * called after the labels passed as arguments have been visited. + * + * @author Eric Bruneton + */ +public abstract class MethodVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The method visitor to which this visitor must delegate method calls. May + * be null. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param mv + * the method visitor to which this visitor must delegate method + * calls. May be null. + */ + public MethodVisitor(final int api, final MethodVisitor mv) { + if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + } + this.api = api; + this.mv = mv; + } + + // ------------------------------------------------------------------------- + // Annotations and non standard attributes + // ------------------------------------------------------------------------- + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this + * annotation interface method, or null if this visitor is + * not interested in visiting this default value. The 'name' + * parameters passed to the methods of this annotation visitor are + * ignored. Moreover, exacly one visit method must be called on this + * annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (mv != null) { + return mv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter + * the parameter index. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, boolean visible) { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (mv != null) { + mv.visitAttribute(attr); + } + } + + /** + * Starts the visit of the method's code, if any (i.e. non abstract method). + */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack + * elements. This method must(*) be called just before any + * instruction i that follows an unconditional branch instruction + * such as GOTO or THROW, that is the target of a jump instruction, or that + * starts an exception handler block. The visited types must describe the + * values of the local variables and of the operand stack elements just + * before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or + * equal to {@link Opcodes#V1_6 V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in + * compressed form (all frames must use the same format, i.e. you must not + * mix expanded and compressed frames within a single method): + *
    + *
  • In expanded form, all frames must have the F_NEW type.
  • + *
  • In compressed form, frames are basically "deltas" from the state of + * the previous frame: + *
      + *
    • {@link Opcodes#F_SAME} representing frame with exactly the same + * locals as the previous frame and with the empty stack.
    • + *
    • {@link Opcodes#F_SAME1} representing frame with exactly the same + * locals as the previous frame and with single value on the stack ( + * nStack is 1 and stack[0] contains value for the + * type of the stack item).
    • + *
    • {@link Opcodes#F_APPEND} representing frame with current locals are + * the same as the locals in the previous frame, except that additional + * locals are defined (nLocal is 1, 2 or 3 and + * local elements contains values representing added types).
    • + *
    • {@link Opcodes#F_CHOP} representing frame with current locals are the + * same as the locals in the previous frame, except that the last 1-3 locals + * are absent and with the empty stack (nLocals is 1, 2 or 3).
    • + *
    • {@link Opcodes#F_FULL} representing complete frame data.
    • + *
    + *

+ * In both cases the first frame, corresponding to the method's parameters + * and access flags, is implicit and must not be visited. Also, it is + * illegal to visit two or more frames for the same code location (i.e., at + * least one instruction must be visited between two calls to visitFrame). + * + * @param type + * the type of this stack map frame. Must be + * {@link Opcodes#F_NEW} for expanded frames, or + * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for + * compressed frames. + * @param nLocal + * the number of local variables in the visited frame. + * @param local + * the local variable types in this frame. This array must not be + * modified. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param nStack + * the number of operand stack elements in the visited frame. + * @param stack + * the operand stack types in this frame. This array must not be + * modified. Its content has the same format as the "local" + * array. + * @throws IllegalStateException + * if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a + * Opcodes#F_SAME frame, in which case it is silently ignored). + */ + public void visitFrame(int type, int nLocal, Object[] local, int nStack, + Object[] stack) { + if (mv != null) { + mv.visitFrame(type, nLocal, local, nStack, stack); + } + } + + // ------------------------------------------------------------------------- + // Normal instructions + // ------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either 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, IALOAD, + * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, + * 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, I2L, I2F, I2D, + * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, + * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, + * or MONITOREXIT. + */ + public void visitInsn(int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either BIPUSH, SIPUSH or NEWARRAY. + * @param operand + * the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between + * Byte.MIN_VALUE and Byte.MAX_VALUE.
+ * When opcode is SIPUSH, operand value should be between + * Short.MIN_VALUE and Short.MAX_VALUE.
+ * When opcode is NEWARRAY, operand value should be one of + * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, + * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, + * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, + * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(int opcode, int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an + * instruction that loads or stores the value of a local variable. + * + * @param opcode + * the opcode of the local variable instruction to be visited. + * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, + * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var + * the operand of the instruction to be visited. This operand is + * the index of a local variable. + */ + public void visitVarInsn(int opcode, int var) { + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that + * takes the internal name of a class as parameter. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type + * the operand of the instruction to be visited. This operand + * must be the internal name of an object or array class (see + * {@link Type#getInternalName() getInternalName}). + */ + public void visitTypeInsn(int opcode, String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that + * loads or stores the value of a field of an object. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner + * the internal name of the field's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type Type}). + */ + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, desc); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + */ + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double}, {@link String}, {@link Type} or {@link Handle} + * value. This method is allowed to modify the content of the + * array so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + if (mv != null) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may + * jump to another instruction. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, + * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label + * the operand of the instruction to be visited. This operand is + * a label that designates the instruction to which the jump + * instruction may jump. + */ + public void visitJumpInsn(int opcode, Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited + * just after it. + * + * @param label + * a {@link Label Label} object. + */ + public void visitLabel(Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ------------------------------------------------------------------------- + // Special instructions + // ------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in + * future versions of the Java Virtual Machine. To easily detect new + * constant types, implementations of this method should check for + * unexpected constant types, like this: + * + *
+     * if (cst instanceof Integer) {
+     *     // ...
+     * } else if (cst instanceof Float) {
+     *     // ...
+     * } else if (cst instanceof Long) {
+     *     // ...
+     * } else if (cst instanceof Double) {
+     *     // ...
+     * } else if (cst instanceof String) {
+     *     // ...
+     * } else if (cst instanceof Type) {
+     *     int sort = ((Type) cst).getSort();
+     *     if (sort == Type.OBJECT) {
+     *         // ...
+     *     } else if (sort == Type.ARRAY) {
+     *         // ...
+     *     } else if (sort == Type.METHOD) {
+     *         // ...
+     *     } else {
+     *         // throw an exception
+     *     }
+     * } else if (cst instanceof Handle) {
+     *     // ...
+     * } else {
+     *     // throw an exception
+     * }
+     * 
+ * + * @param cst + * the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double}, a {@link String}, a {@link Type} of OBJECT or + * ARRAY sort for .class constants, for classes whose + * version is 49.0, a {@link Type} of METHOD sort or a + * {@link Handle} for MethodType and MethodHandle constants, for + * classes whose version is 51.0. + */ + public void visitLdcInsn(Object cst) { + if (mv != null) { + mv.visitLdcInsn(cst); + } + } + + /** + * Visits an IINC instruction. + * + * @param var + * index of the local variable to be incremented. + * @param increment + * amount to increment the local variable by. + */ + public void visitIincInsn(int var, int increment) { + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min + * the minimum key value. + * @param max + * the maximum key value. + * @param dflt + * beginning of the default handler block. + * @param labels + * beginnings of the handler blocks. labels[i] is the + * beginning of the handler block for the min + i key. + */ + public void visitTableSwitchInsn(int min, int max, Label dflt, + Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt + * beginning of the default handler block. + * @param keys + * the values of the keys. + * @param labels + * beginnings of the handler blocks. labels[i] is the + * beginning of the handler block for the keys[i] key. + */ + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param desc + * an array type descriptor (see {@link Type Type}). + * @param dims + * number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(String desc, int dims) { + if (mv != null) { + mv.visitMultiANewArrayInsn(desc, dims); + } + } + + // ------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start + * beginning of the exception handler's scope (inclusive). + * @param end + * end of the exception handler's scope (exclusive). + * @param handler + * beginning of the exception handler's code. + * @param type + * internal name of the type of exceptions handled by the + * handler, or null to catch any exceptions (for + * "finally" blocks). + * @throws IllegalArgumentException + * if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel visitLabel} method). + */ + public void visitTryCatchBlock(Label start, Label end, Label handler, + String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits a local variable declaration. + * + * @param name + * the name of a local variable. + * @param desc + * the type descriptor of this local variable. + * @param signature + * the type signature of this local variable. May be + * null if the local variable type does not use generic + * types. + * @param start + * the first instruction corresponding to the scope of this local + * variable (inclusive). + * @param end + * the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index + * the local variable's index. + * @throws IllegalArgumentException + * if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). + */ + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + if (mv != null) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + /** + * Visits a line number declaration. + * + * @param line + * a line number. This number refers to the source file from + * which the class was compiled. + * @param start + * the first instruction corresponding to this line number. + * @throws IllegalArgumentException + * if start has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). + */ + public void visitLineNumber(int line, Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables + * of the method. + * + * @param maxStack + * maximum stack size of the method. + * @param maxLocals + * maximum number of local variables for the method. + */ + public void visitMaxs(int maxStack, int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodWriter.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodWriter.java new file mode 100644 index 0000000000..290cf971b8 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/MethodWriter.java @@ -0,0 +1,2685 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * A {@link MethodVisitor} that generates methods in bytecode form. Each visit + * method of this class appends the bytecode corresponding to the visited + * instruction to a byte vector, in the order these methods are called. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +class MethodWriter extends MethodVisitor { + + /** + * Pseudo access flag used to denote constructors. + */ + static final int ACC_CONSTRUCTOR = 0x80000; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. + */ + static final int SAME_FRAME = 0; // to 63 (0-3f) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1 + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f) + + /** + * Reserved for future use + */ + static final int RESERVED = 128; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1. Offset is bigger then 63; + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that the k last locals are absent. The value of k is given + * by the formula 251-frame_type. + */ + static final int CHOP_FRAME = 248; // to 250 (f8-fA) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. Offset is bigger then 63; + */ + static final int SAME_FRAME_EXTENDED = 251; // fb + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that k additional locals are defined. The value of k is + * given by the formula frame_type-251. + */ + static final int APPEND_FRAME = 252; // to 254 // fc-fe + + /** + * Full frame + */ + static final int FULL_FRAME = 255; // ff + + /** + * Indicates that the stack map frames must be recomputed from scratch. In + * this case the maximum stack size and number of local variables is also + * recomputed from scratch. + * + * @see #compute + */ + private static final int FRAMES = 0; + + /** + * Indicates that the maximum stack size and number of local variables must + * be automatically computed. + * + * @see #compute + */ + private static final int MAXS = 1; + + /** + * Indicates that nothing must be automatically computed. + * + * @see #compute + */ + private static final int NOTHING = 2; + + /** + * The class writer to which this method must be added. + */ + final ClassWriter cw; + + /** + * Access flags of this method. + */ + private int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * method. + */ + private final int desc; + + /** + * The descriptor of this method. + */ + private final String descriptor; + + /** + * The signature of this method. + */ + String signature; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in cw.cr. More + * precisely, this field gives the index of the first byte to copied from + * cw.cr.b. + */ + int classReaderOffset; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in cw.cr. More + * precisely, this field gives the number of bytes to copied from + * cw.cr.b. + */ + int classReaderLength; + + /** + * Number of exceptions that can be thrown by this method. + */ + int exceptionCount; + + /** + * The exceptions that can be thrown by this method. More precisely, this + * array contains the indexes of the constant pool items that contain the + * internal names of these exception classes. + */ + int[] exceptions; + + /** + * The annotation default attribute of this method. May be null. + */ + private ByteVector annd; + + /** + * The runtime visible annotations of this method. May be null. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this method. May be null. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible parameter annotations of this method. May be + * null. + */ + private AnnotationWriter[] panns; + + /** + * The runtime invisible parameter annotations of this method. May be + * null. + */ + private AnnotationWriter[] ipanns; + + /** + * The number of synthetic parameters of this method. + */ + private int synthetics; + + /** + * The non standard attributes of the method. + */ + private Attribute attrs; + + /** + * The bytecode of this method. + */ + private ByteVector code = new ByteVector(); + + /** + * Maximum stack size of this method. + */ + private int maxStack; + + /** + * Maximum number of local variables for this method. + */ + private int maxLocals; + + /** + * Number of local variables in the current stack map frame. + */ + private int currentLocals; + + /** + * Number of stack map frames in the StackMapTable attribute. + */ + private int frameCount; + + /** + * The StackMapTable attribute. + */ + private ByteVector stackMap; + + /** + * The offset of the last frame that was written in the StackMapTable + * attribute. + */ + private int previousFrameOffset; + + /** + * The last frame that was written in the StackMapTable attribute. + * + * @see #frame + */ + private int[] previousFrame; + + /** + * The current stack map frame. The first element contains the offset of the + * instruction to which the frame corresponds, the second element is the + * number of locals and the third one is the number of stack elements. The + * local variables start at index 3 and are followed by the operand stack + * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = + * nStack, frame[3] = nLocal. All types are encoded as integers, with the + * same format as the one used in {@link Label}, but limited to BASE types. + */ + private int[] frame; + + /** + * Number of elements in the exception handler list. + */ + private int handlerCount; + + /** + * The first element in the exception handler list. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list. + */ + private Handler lastHandler; + + /** + * Number of entries in the LocalVariableTable attribute. + */ + private int localVarCount; + + /** + * The LocalVariableTable attribute. + */ + private ByteVector localVar; + + /** + * Number of entries in the LocalVariableTypeTable attribute. + */ + private int localVarTypeCount; + + /** + * The LocalVariableTypeTable attribute. + */ + private ByteVector localVarType; + + /** + * Number of entries in the LineNumberTable attribute. + */ + private int lineNumberCount; + + /** + * The LineNumberTable attribute. + */ + private ByteVector lineNumber; + + /** + * The non standard attributes of the method's code. + */ + private Attribute cattrs; + + /** + * Indicates if some jump instructions are too small and need to be resized. + */ + private boolean resize; + + /** + * The number of subroutines in this method. + */ + private int subroutines; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow graph analysis algorithm (used to compute the + * maximum stack size). A control flow graph contains one node per "basic + * block", and one edge per "jump" from one basic block to another. Each + * node (i.e., each basic block) is represented by the Label object that + * corresponds to the first instruction of this basic block. Each node also + * stores the list of its successors in the graph, as a linked list of Edge + * objects. + */ + + /** + * Indicates what must be automatically computed. + * + * @see #FRAMES + * @see #MAXS + * @see #NOTHING + */ + private final int compute; + + /** + * A list of labels. This list is the list of basic blocks in the method, + * i.e. a list of Label objects linked to each other by their + * {@link Label#successor} field, in the order they are visited by + * {@link MethodVisitor#visitLabel}, and starting with the first basic + * block. + */ + private Label labels; + + /** + * The previous basic block. + */ + private Label previousBlock; + + /** + * The current basic block. + */ + private Label currentBlock; + + /** + * The (relative) stack size after the last visited instruction. This size + * is relative to the beginning of the current basic block, i.e., the true + * stack size after the last visited instruction is equal to the + * {@link Label#inputStackTop beginStackSize} of the current basic block + * plus stackSize. + */ + private int stackSize; + + /** + * The (relative) maximum stack size after the last visited instruction. + * This size is relative to the beginning of the current basic block, i.e., + * the true maximum stack size after the last visited instruction is equal + * to the {@link Label#inputStackTop beginStackSize} of the current basic + * block plus stackSize. + */ + private int maxStackSize; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link MethodWriter}. + * + * @param cw + * the class writer in which the method must be added. + * @param access + * the method's access flags (see {@link Opcodes}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type}). + * @param signature + * the method's signature. May be null. + * @param exceptions + * the internal names of the method's exceptions. May be + * null. + * @param computeMaxs + * true if the maximum stack size and number of local + * variables must be automatically computed. + * @param computeFrames + * true if the stack map tables must be recomputed from + * scratch. + */ + MethodWriter(final ClassWriter cw, final int access, final String name, + final String desc, final String signature, + final String[] exceptions, final boolean computeMaxs, + final boolean computeFrames) { + super(Opcodes.ASM4); + if (cw.firstMethod == null) { + cw.firstMethod = this; + } else { + cw.lastMethod.mv = this; + } + cw.lastMethod = this; + this.cw = cw; + this.access = access; + if ("".equals(name)) { + this.access |= ACC_CONSTRUCTOR; + } + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + this.descriptor = desc; + if (ClassReader.SIGNATURES) { + this.signature = signature; + } + if (exceptions != null && exceptions.length > 0) { + exceptionCount = exceptions.length; + this.exceptions = new int[exceptionCount]; + for (int i = 0; i < exceptionCount; ++i) { + this.exceptions[i] = cw.newClass(exceptions[i]); + } + } + this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); + if (computeMaxs || computeFrames) { + // updates maxLocals + int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --size; + } + maxLocals = size; + currentLocals = size; + // creates and visits the label for the first basic block + labels = new Label(); + labels.status |= Label.PUSHED; + visitLabel(labels); + } + } + + // ------------------------------------------------------------------------ + // Implementation of the MethodVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotationDefault() { + if (!ClassReader.ANNOTATIONS) { + return null; + } + annd = new ByteVector(); + return new AnnotationWriter(cw, false, annd, null, 0); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, + final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + if ("Ljava/lang/Synthetic;".equals(desc)) { + // workaround for a bug in javac with synthetic parameters + // see ClassReader.readParameterAnnotations + synthetics = Math.max(synthetics, parameter + 1); + return new AnnotationWriter(cw, false, bv, null, 0); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + if (panns == null) { + panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = panns[parameter]; + panns[parameter] = aw; + } else { + if (ipanns == null) { + ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = ipanns[parameter]; + ipanns[parameter] = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attr.isCodeAttribute()) { + attr.next = cattrs; + cattrs = attr; + } else { + attr.next = attrs; + attrs = attr; + } + } + + @Override + public void visitCode() { + } + + @Override + public void visitFrame(final int type, final int nLocal, + final Object[] local, final int nStack, final Object[] stack) { + if (!ClassReader.FRAMES || compute == FRAMES) { + return; + } + + if (type == Opcodes.F_NEW) { + if (previousFrame == null) { + visitImplicitFirstFrame(); + } + currentLocals = nLocal; + int frameIndex = startFrame(code.length, nLocal, nStack); + for (int i = 0; i < nLocal; ++i) { + if (local[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) local[i]); + } else if (local[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) local[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) local[i]).position); + } + } + for (int i = 0; i < nStack; ++i) { + if (stack[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) stack[i]); + } else if (stack[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) stack[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) stack[i]).position); + } + } + endFrame(); + } else { + int delta; + if (stackMap == null) { + stackMap = new ByteVector(); + delta = code.length; + } else { + delta = code.length - previousFrameOffset - 1; + if (delta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = nLocal; + stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + stackMap.putShort(nStack); + for (int i = 0; i < nStack; ++i) { + writeFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta); + break; + case Opcodes.F_SAME: + if (delta < 64) { + stackMap.putByte(delta); + } else { + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + } + break; + case Opcodes.F_SAME1: + if (delta < 64) { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + } else { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + } + writeFrameType(stack[0]); + break; + } + + previousFrameOffset = code.length; + ++frameCount; + } + + maxStack = Math.max(maxStack, nStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + // adds the instruction to the bytecode of the method + code.putByte(opcode); + // update currentBlock + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + } else { + // updates current and max stack sizes + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + // if opcode == ATHROW or xRETURN, ends current block (no successor) + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) + || opcode == Opcodes.ATHROW) { + noSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // updates current and max stack sizes only for NEWARRAY + // (stack size variation = 0 for BIPUSH or SIPUSH) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, var, null, null); + } else { + // updates current and max stack sizes + if (opcode == Opcodes.RET) { + // no stack change, but end of current block (no successor) + currentBlock.status |= Label.RET; + // save 'stackSize' here for future use + // (see {@link #findSubroutineSuccessors}) + currentBlock.inputStackTop = stackSize; + noSuccessor(); + } else { // xLOAD or xSTORE + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + } + if (compute != NOTHING) { + // updates max locals + int n; + if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { + n = var + 2; + } else { + n = var + 1; + } + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if (var < 4 && opcode != Opcodes.RET) { + int opt; + if (opcode < Opcodes.ISTORE) { + /* ILOAD_0 */ + opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var; + } else { + /* ISTORE_0 */ + opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var; + } + code.putByte(opt); + } else if (var >= 256) { + code.putByte(196 /* WIDE */).put12(opcode, var); + } else { + code.put11(opcode, var); + } + if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) { + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + Item i = cw.newClassItem(type); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, code.length, cw, i); + } else if (opcode == Opcodes.NEW) { + // updates current and max stack sizes only if opcode == NEW + // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, + final String name, final String desc) { + Item i = cw.newFieldItem(owner, name, desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + int size; + // computes the stack size variation + char c = desc.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = stackSize + (c == 'D' || c == 'J' ? 1 : 0); + break; + // case Constants.PUTFIELD: + default: + size = stackSize + (c == 'D' || c == 'J' ? -3 : -2); + break; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc) { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + Item i = cw.newMethodItem(owner, name, desc, itf); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + } else { + size = stackSize - (argSize >> 2) + (argSize & 0x03); + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (itf) { + if (argSize == 0) { + argSize = Type.getArgumentsAndReturnSizes(desc); + i.intVal = argSize; + } + code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); + } else { + code.put12(opcode, i.index); + } + } + + @Override + public void visitInvokeDynamicInsn(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.INVOKEDYNAMIC, i.index); + code.putShort(0); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + Label nextInsn = null; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + // 'label' is the target of a jump instruction + label.getFirst().status |= Label.TARGET; + // adds 'label' as a successor of this basic block + addSuccessor(Edge.NORMAL, label); + if (opcode != Opcodes.GOTO) { + // creates a Label for the next basic block + nextInsn = new Label(); + } + } else { + if (opcode == Opcodes.JSR) { + if ((label.status & Label.SUBROUTINE) == 0) { + label.status |= Label.SUBROUTINE; + ++subroutines; + } + currentBlock.status |= Label.JSR; + addSuccessor(stackSize + 1, label); + // creates a Label for the next basic block + nextInsn = new Label(); + /* + * note that, by construction in this method, a JSR block + * has at least two successors in the control flow graph: + * the first one leads the next instruction after the JSR, + * while the second one leads to the JSR target. + */ + } else { + // updates current stack size (max stack size unchanged + // because stack size variation always negative in this + // case) + stackSize += Frame.SIZE[opcode]; + addSuccessor(stackSize, label); + } + } + } + // adds the instruction to the bytecode of the method + if ((label.status & Label.RESOLVED) != 0 + && label.position - code.length < Short.MIN_VALUE) { + /* + * case of a backward jump with an offset < -32768. In this case we + * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx + * with IFNOTxxx GOTO_W , where IFNOTxxx is the + * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where + * designates the instruction just after the GOTO_W. + */ + if (opcode == Opcodes.GOTO) { + code.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + code.putByte(201); // JSR_W + } else { + // if the IF instruction is transformed into IFNOT GOTO_W the + // next instruction becomes the target of the IFNOT instruction + if (nextInsn != null) { + nextInsn.status |= Label.TARGET; + } + code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + code.putShort(8); // jump offset + code.putByte(200); // GOTO_W + } + label.put(this, code, code.length - 1, true); + } else { + /* + * case of a backward jump with an offset >= -32768, or of a forward + * jump with, of course, an unknown offset. In these cases we store + * the offset in 2 bytes (which will be increased in + * resizeInstructions, if needed). + */ + code.putByte(opcode); + label.put(this, code, code.length - 1, false); + } + if (currentBlock != null) { + if (nextInsn != null) { + // if the jump instruction is not a GOTO, the next instruction + // is also a successor of this instruction. Calling visitLabel + // adds the label of this next instruction as a successor of the + // current block, and starts a new basic block + visitLabel(nextInsn); + } + if (opcode == Opcodes.GOTO) { + noSuccessor(); + } + } + } + + @Override + public void visitLabel(final Label label) { + // resolves previous forward references to label, if any + resize |= label.resolve(this, code.length, code.data); + // updates currentBlock + if ((label.status & Label.DEBUG) != 0) { + return; + } + if (compute == FRAMES) { + if (currentBlock != null) { + if (label.position == currentBlock.position) { + // successive labels, do not start a new basic block + currentBlock.status |= (label.status & Label.TARGET); + label.frame = currentBlock.frame; + return; + } + // ends current block (with one new successor) + addSuccessor(Edge.NORMAL, label); + } + // begins a new current block + currentBlock = label; + if (label.frame == null) { + label.frame = new Frame(); + label.frame.owner = label; + } + // updates the basic block list + if (previousBlock != null) { + if (label.position == previousBlock.position) { + previousBlock.status |= (label.status & Label.TARGET); + label.frame = previousBlock.frame; + currentBlock = previousBlock; + return; + } + previousBlock.successor = label; + } + previousBlock = label; + } else if (compute == MAXS) { + if (currentBlock != null) { + // ends current block (with one new successor) + currentBlock.outputStackMax = maxStackSize; + addSuccessor(stackSize, label); + } + // begins a new current block + currentBlock = label; + // resets the relative current and max stack sizes + stackSize = 0; + maxStackSize = 0; + // updates the basic block list + if (previousBlock != null) { + previousBlock.successor = label; + } + previousBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object cst) { + Item i = cw.newConstItem(cst); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); + } else { + int size; + // computes the stack size variation + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + size = stackSize + 2; + } else { + size = stackSize + 1; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + int index = i.index; + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + code.put12(20 /* LDC2_W */, index); + } else if (index >= 256) { + code.put12(19 /* LDC_W */, index); + } else { + code.put11(Opcodes.LDC, index); + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.IINC, var, null, null); + } + } + if (compute != NOTHING) { + // updates max locals + int n = var + 1; + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if ((var > 255) || (increment > 127) || (increment < -128)) { + code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var) + .putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, + final Label dflt, final Label... labels) { + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.TABLESWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(min).putInt(max); + for (int i = 0; i < labels.length; ++i) { + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, + final Label[] labels) { + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.LOOKUPSWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // adds current block successors + addSuccessor(Edge.NORMAL, dflt); + dflt.getFirst().status |= Label.TARGET; + for (int i = 0; i < labels.length; ++i) { + addSuccessor(Edge.NORMAL, labels[i]); + labels[i].getFirst().status |= Label.TARGET; + } + } else { + // updates current stack size (max stack size unchanged) + --stackSize; + // adds current block successors + addSuccessor(stackSize, dflt); + for (int i = 0; i < labels.length; ++i) { + addSuccessor(stackSize, labels[i]); + } + } + // ends current block + noSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + Item i = cw.newClassItem(desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); + } else { + // updates current stack size (max stack size unchanged because + // stack size variation always negative or null) + stackSize += 1 - dims; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, + final Label handler, final String type) { + ++handlerCount; + Handler h = new Handler(); + h.start = start; + h.end = end; + h.handler = handler; + h.desc = type; + h.type = type != null ? cw.newClass(type) : 0; + if (lastHandler == null) { + firstHandler = h; + } else { + lastHandler.next = h; + } + lastHandler = h; + } + + @Override + public void visitLocalVariable(final String name, final String desc, + final String signature, final Label start, final Label end, + final int index) { + if (signature != null) { + if (localVarType == null) { + localVarType = new ByteVector(); + } + ++localVarTypeCount; + localVarType.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature)) + .putShort(index); + } + if (localVar == null) { + localVar = new ByteVector(); + } + ++localVarCount; + localVar.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc)) + .putShort(index); + if (compute != NOTHING) { + // updates max locals + char c = desc.charAt(0); + int n = index + (c == 'J' || c == 'D' ? 2 : 1); + if (n > maxLocals) { + maxLocals = n; + } + } + } + + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumber == null) { + lineNumber = new ByteVector(); + } + ++lineNumberCount; + lineNumber.putShort(start.position); + lineNumber.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (ClassReader.FRAMES && compute == FRAMES) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start.getFirst(); + Label h = handler.handler.getFirst(); + Label e = handler.end.getFirst(); + // computes the kind of the edges to 'h' + String t = handler.desc == null ? "java/lang/Throwable" + : handler.desc; + int kind = Frame.OBJECT | cw.addType(t); + // h is an exception handler + h.status |= Label.TARGET; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = kind; + b.successor = h; + // adds it to the successors of 'l' + b.next = l.successors; + l.successors = b; + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + // creates and visits the first (implicit) frame + Frame f = labels.frame; + Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, this.maxLocals); + visitFrame(f); + + /* + * fix point algorithm: mark the first basic block as 'changed' + * (i.e. put it in the 'changed' list) and, while there are changed + * basic blocks, choose one, mark it as unchanged, and update its + * successors (which can be changed in the process). + */ + int max = 0; + Label changed = labels; + while (changed != null) { + // removes a basic block from the list of changed basic blocks + Label l = changed; + changed = changed.next; + l.next = null; + f = l.frame; + // a reachable jump target must be stored in the stack map + if ((l.status & Label.TARGET) != 0) { + l.status |= Label.STORE; + } + // all visited labels are reachable, by definition + l.status |= Label.REACHABLE; + // updates the (absolute) maximum stack size + int blockMax = f.inputStack.length + l.outputStackMax; + if (blockMax > max) { + max = blockMax; + } + // updates the successors of the current basic block + Edge e = l.successors; + while (e != null) { + Label n = e.successor.getFirst(); + boolean change = f.merge(cw, n.frame, e.info); + if (change && n.next == null) { + // if n has changed and is not already in the 'changed' + // list, adds it to this list + n.next = changed; + changed = n; + } + e = e.next; + } + } + + // visits all the frames that must be stored in the stack map + Label l = labels; + while (l != null) { + f = l.frame; + if ((l.status & Label.STORE) != 0) { + visitFrame(f); + } + if ((l.status & Label.REACHABLE) == 0) { + // finds start and end of dead basic block + Label k = l.successor; + int start = l.position; + int end = (k == null ? code.length : k.position) - 1; + // if non empty basic block + if (end >= start) { + max = Math.max(max, 1); + // replaces instructions with NOP ... NOP ATHROW + for (int i = start; i < end; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[end] = (byte) Opcodes.ATHROW; + // emits a frame for this unreachable block + int frameIndex = startFrame(start, 0, 1); + frame[frameIndex] = Frame.OBJECT + | cw.addType("java/lang/Throwable"); + endFrame(); + // removes the start-end range from the exception + // handlers + firstHandler = Handler.remove(firstHandler, l, k); + } + } + l = l.successor; + } + + handler = firstHandler; + handlerCount = 0; + while (handler != null) { + handlerCount += 1; + handler = handler.next; + } + + this.maxStack = max; + } else if (compute == MAXS) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start; + Label h = handler.handler; + Label e = handler.end; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = Edge.EXCEPTION; + b.successor = h; + // adds it to the successors of 'l' + if ((l.status & Label.JSR) == 0) { + b.next = l.successors; + l.successors = b; + } else { + // if l is a JSR block, adds b after the first two edges + // to preserve the hypothesis about JSR block successors + // order (see {@link #visitJumpInsn}) + b.next = l.successors.next.next; + l.successors.next.next = b; + } + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + if (subroutines > 0) { + // completes the control flow graph with the RET successors + /* + * first step: finds the subroutines. This step determines, for + * each basic block, to which subroutine(s) it belongs. + */ + // finds the basic blocks that belong to the "main" subroutine + int id = 0; + labels.visitSubroutine(null, 1, subroutines); + // finds the basic blocks that belong to the real subroutines + Label l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + // if this subroutine has not been visited yet... + if ((subroutine.status & Label.VISITED) == 0) { + // ...assigns it a new id and finds its basic blocks + id += 1; + subroutine.visitSubroutine(null, (id / 32L) << 32 + | (1L << (id % 32)), subroutines); + } + } + l = l.successor; + } + // second step: finds the successors of RET blocks + l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + Label L = labels; + while (L != null) { + L.status &= ~Label.VISITED2; + L = L.successor; + } + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + subroutine.visitSubroutine(l, 0, subroutines); + } + l = l.successor; + } + } + + /* + * control flow analysis algorithm: while the block stack is not + * empty, pop a block from this stack, update the max stack size, + * compute the true (non relative) begin stack size of the + * successors of this block, and push these successors onto the + * stack (unless they have already been pushed onto the stack). + * Note: by hypothesis, the {@link Label#inputStackTop} of the + * blocks in the block stack are the true (non relative) beginning + * stack sizes of these blocks. + */ + int max = 0; + Label stack = labels; + while (stack != null) { + // pops a block from the stack + Label l = stack; + stack = stack.next; + // computes the true (non relative) max stack size of this block + int start = l.inputStackTop; + int blockMax = start + l.outputStackMax; + // updates the global max stack size + if (blockMax > max) { + max = blockMax; + } + // analyzes the successors of the block + Edge b = l.successors; + if ((l.status & Label.JSR) != 0) { + // ignores the first edge of JSR blocks (virtual successor) + b = b.next; + } + while (b != null) { + l = b.successor; + // if this successor has not already been pushed... + if ((l.status & Label.PUSHED) == 0) { + // computes its true beginning stack size... + l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start + + b.info; + // ...and pushes it onto the stack + l.status |= Label.PUSHED; + l.next = stack; + stack = l; + } + b = b.next; + } + } + this.maxStack = Math.max(maxStack, max); + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods: control flow analysis algorithm + // ------------------------------------------------------------------------ + + /** + * Adds a successor to the {@link #currentBlock currentBlock} block. + * + * @param info + * information about the control flow edge to be added. + * @param successor + * the successor block to be added to the current block. + */ + private void addSuccessor(final int info, final Label successor) { + // creates and initializes an Edge object... + Edge b = new Edge(); + b.info = info; + b.successor = successor; + // ...and adds it to the successor list of the currentBlock block + b.next = currentBlock.successors; + currentBlock.successors = b; + } + + /** + * Ends the current basic block. This method must be used in the case where + * the current basic block does not have any successor. + */ + private void noSuccessor() { + if (compute == FRAMES) { + Label l = new Label(); + l.frame = new Frame(); + l.frame.owner = l; + l.resolve(this, code.length, code.data); + previousBlock.successor = l; + previousBlock = l; + } else { + currentBlock.outputStackMax = maxStackSize; + } + currentBlock = null; + } + + // ------------------------------------------------------------------------ + // Utility methods: stack map frames + // ------------------------------------------------------------------------ + + /** + * Visits a frame that has been computed from scratch. + * + * @param f + * the frame that must be visited. + */ + private void visitFrame(final Frame f) { + int i, t; + int nTop = 0; + int nLocal = 0; + int nStack = 0; + int[] locals = f.inputLocals; + int[] stacks = f.inputStack; + // computes the number of locals (ignores TOP types that are just after + // a LONG or a DOUBLE, and all trailing TOP types) + for (i = 0; i < locals.length; ++i) { + t = locals[i]; + if (t == Frame.TOP) { + ++nTop; + } else { + nLocal += nTop + 1; + nTop = 0; + } + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // computes the stack size (ignores TOP types that are just after + // a LONG or a DOUBLE) + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + ++nStack; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // visits the frame and its content + int frameIndex = startFrame(f.owner.position, nLocal, nStack); + for (i = 0; nLocal > 0; ++i, --nLocal) { + t = locals[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + endFrame(); + } + + /** + * Visit the implicit first frame of this method. + */ + private void visitImplicitFirstFrame() { + // There can be at most descriptor.length() + 1 locals + int frameIndex = startFrame(0, descriptor.length() + 1, 0); + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & ACC_CONSTRUCTOR) == 0) { + frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName); + } else { + frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS; + } + } + int i = 1; + loop: while (true) { + int j = i; + switch (descriptor.charAt(i++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + frame[frameIndex++] = 1; // Opcodes.INTEGER; + break; + case 'F': + frame[frameIndex++] = 2; // Opcodes.FLOAT; + break; + case 'J': + frame[frameIndex++] = 4; // Opcodes.LONG; + break; + case 'D': + frame[frameIndex++] = 3; // Opcodes.DOUBLE; + break; + case '[': + while (descriptor.charAt(i) == '[') { + ++i; + } + if (descriptor.charAt(i) == 'L') { + ++i; + while (descriptor.charAt(i) != ';') { + ++i; + } + } + frame[frameIndex++] = Frame.OBJECT + | cw.addType(descriptor.substring(j, ++i)); + break; + case 'L': + while (descriptor.charAt(i) != ';') { + ++i; + } + frame[frameIndex++] = Frame.OBJECT + | cw.addType(descriptor.substring(j + 1, i++)); + break; + default: + break loop; + } + } + frame[1] = frameIndex - 3; + endFrame(); + } + + /** + * Starts the visit of a stack map frame. + * + * @param offset + * the offset of the instruction to which the frame corresponds. + * @param nLocal + * the number of local variables in the frame. + * @param nStack + * the number of stack elements in the frame. + * @return the index of the next element to be written in this frame. + */ + private int startFrame(final int offset, final int nLocal, final int nStack) { + int n = 3 + nLocal + nStack; + if (frame == null || frame.length < n) { + frame = new int[n]; + } + frame[0] = offset; + frame[1] = nLocal; + frame[2] = nStack; + return 3; + } + + /** + * Checks if the visit of the current frame {@link #frame} is finished, and + * if yes, write it in the StackMapTable attribute. + */ + private void endFrame() { + if (previousFrame != null) { // do not write the first frame + if (stackMap == null) { + stackMap = new ByteVector(); + } + writeFrame(); + ++frameCount; + } + previousFrame = frame; + frame = null; + } + + /** + * Compress and writes the current frame {@link #frame} in the StackMapTable + * attribute. + */ + private void writeFrame() { + int clocalsSize = frame[1]; + int cstackSize = frame[2]; + if ((cw.version & 0xFFFF) < Opcodes.V1_6) { + stackMap.putShort(frame[0]).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + return; + } + int localsSize = previousFrame[1]; + int type = FULL_FRAME; + int k = 0; + int delta; + if (frameCount == 0) { + delta = frame[0]; + } else { + delta = frame[0] - previousFrame[0] - 1; + } + if (cstackSize == 0) { + k = clocalsSize - localsSize; + switch (k) { + case -3: + case -2: + case -1: + type = CHOP_FRAME; + localsSize = clocalsSize; + break; + case 0: + type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = APPEND_FRAME; + break; + } + } else if (clocalsSize == localsSize && cstackSize == 1) { + type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME + : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != FULL_FRAME) { + // verify if locals are the same + int l = 3; + for (int j = 0; j < localsSize; j++) { + if (frame[l] != previousFrame[l]) { + type = FULL_FRAME; + break; + } + l++; + } + } + switch (type) { + case SAME_FRAME: + stackMap.putByte(delta); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort( + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_FRAME_EXTENDED: + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + break; + case CHOP_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + break; + case APPEND_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + writeFrameTypes(3 + localsSize, 3 + clocalsSize); + break; + // case FULL_FRAME: + default: + stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + } + } + + /** + * Writes some types of the current frame {@link #frame} into the + * StackMapTableAttribute. This method converts types from the format used + * in {@link Label} to the format used in StackMapTable attributes. In + * particular, it converts type table indexes to constant pool indexes. + * + * @param start + * index of the first type in {@link #frame} to write. + * @param end + * index of last type in {@link #frame} to write (exclusive). + */ + private void writeFrameTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + int t = frame[i]; + int d = t & Frame.DIM; + if (d == 0) { + int v = t & Frame.BASE_VALUE; + switch (t & Frame.BASE_KIND) { + case Frame.OBJECT: + stackMap.putByte(7).putShort( + cw.newClass(cw.typeTable[v].strVal1)); + break; + case Frame.UNINITIALIZED: + stackMap.putByte(8).putShort(cw.typeTable[v].intVal); + break; + default: + stackMap.putByte(v); + } + } else { + StringBuffer buf = new StringBuffer(); + d >>= 28; + while (d-- > 0) { + buf.append('['); + } + if ((t & Frame.BASE_KIND) == Frame.OBJECT) { + buf.append('L'); + buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + buf.append(';'); + } else { + switch (t & 0xF) { + case 1: + buf.append('I'); + break; + case 2: + buf.append('F'); + break; + case 3: + buf.append('D'); + break; + case 9: + buf.append('Z'); + break; + case 10: + buf.append('B'); + break; + case 11: + buf.append('C'); + break; + case 12: + buf.append('S'); + break; + default: + buf.append('J'); + } + } + stackMap.putByte(7).putShort(cw.newClass(buf.toString())); + } + } + } + + private void writeFrameType(final Object type) { + if (type instanceof String) { + stackMap.putByte(7).putShort(cw.newClass((String) type)); + } else if (type instanceof Integer) { + stackMap.putByte(((Integer) type).intValue()); + } else { + stackMap.putByte(8).putShort(((Label) type).position); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: dump bytecode array + // ------------------------------------------------------------------------ + + /** + * Returns the size of the bytecode of this method. + * + * @return the size of the bytecode of this method. + */ + final int getSize() { + if (classReaderOffset != 0) { + return 6 + classReaderLength; + } + if (resize) { + // replaces the temporary jump opcodes introduced by Label.resolve. + if (ClassReader.RESIZE) { + resizeInstructions(); + } else { + throw new RuntimeException("Method code too large!"); + } + } + int size = 8; + if (code.length > 0) { + if (code.length > 65536) { + throw new RuntimeException("Method code too large!"); + } + cw.newUTF8("Code"); + size += 18 + code.length + 8 * handlerCount; + if (localVar != null) { + cw.newUTF8("LocalVariableTable"); + size += 8 + localVar.length; + } + if (localVarType != null) { + cw.newUTF8("LocalVariableTypeTable"); + size += 8 + localVarType.length; + } + if (lineNumber != null) { + cw.newUTF8("LineNumberTable"); + size += 8 + lineNumber.length; + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + cw.newUTF8(zip ? "StackMapTable" : "StackMap"); + size += 8 + stackMap.length; + } + if (cattrs != null) { + size += cattrs.getSize(cw, code.data, code.length, maxStack, + maxLocals); + } + } + if (exceptionCount > 0) { + cw.newUTF8("Exceptions"); + size += 8 + 2 * exceptionCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + cw.newUTF8("Synthetic"); + size += 6; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != null) { + cw.newUTF8("Signature"); + cw.newUTF8(signature); + size += 8; + } + if (ClassReader.ANNOTATIONS && annd != null) { + cw.newUTF8("AnnotationDefault"); + size += 6 + annd.length; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && panns != null) { + cw.newUTF8("RuntimeVisibleParameterAnnotations"); + size += 7 + 2 * (panns.length - synthetics); + for (int i = panns.length - 1; i >= synthetics; --i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + cw.newUTF8("RuntimeInvisibleParameterAnnotations"); + size += 7 + 2 * (ipanns.length - synthetics); + for (int i = ipanns.length - 1; i >= synthetics; --i) { + size += ipanns[i] == null ? 0 : ipanns[i].getSize(); + } + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the bytecode of this method in the given byte vector. + * + * @param out + * the byte vector into which the bytecode of this method must be + * copied. + */ + final void put(final ByteVector out) { + final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; + int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); + out.putShort(access & ~mask).putShort(name).putShort(desc); + if (classReaderOffset != 0) { + out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength); + return; + } + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (exceptionCount > 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && annd != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && panns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (code.length > 0) { + int size = 12 + code.length + 8 * handlerCount; + if (localVar != null) { + size += 8 + localVar.length; + } + if (localVarType != null) { + size += 8 + localVarType.length; + } + if (lineNumber != null) { + size += 8 + lineNumber.length; + } + if (stackMap != null) { + size += 8 + stackMap.length; + } + if (cattrs != null) { + size += cattrs.getSize(cw, code.data, code.length, maxStack, + maxLocals); + } + out.putShort(cw.newUTF8("Code")).putInt(size); + out.putShort(maxStack).putShort(maxLocals); + out.putInt(code.length).putByteArray(code.data, 0, code.length); + out.putShort(handlerCount); + if (handlerCount > 0) { + Handler h = firstHandler; + while (h != null) { + out.putShort(h.start.position).putShort(h.end.position) + .putShort(h.handler.position).putShort(h.type); + h = h.next; + } + } + attributeCount = 0; + if (localVar != null) { + ++attributeCount; + } + if (localVarType != null) { + ++attributeCount; + } + if (lineNumber != null) { + ++attributeCount; + } + if (stackMap != null) { + ++attributeCount; + } + if (cattrs != null) { + attributeCount += cattrs.getCount(); + } + out.putShort(attributeCount); + if (localVar != null) { + out.putShort(cw.newUTF8("LocalVariableTable")); + out.putInt(localVar.length + 2).putShort(localVarCount); + out.putByteArray(localVar.data, 0, localVar.length); + } + if (localVarType != null) { + out.putShort(cw.newUTF8("LocalVariableTypeTable")); + out.putInt(localVarType.length + 2).putShort(localVarTypeCount); + out.putByteArray(localVarType.data, 0, localVarType.length); + } + if (lineNumber != null) { + out.putShort(cw.newUTF8("LineNumberTable")); + out.putInt(lineNumber.length + 2).putShort(lineNumberCount); + out.putByteArray(lineNumber.data, 0, lineNumber.length); + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap")); + out.putInt(stackMap.length + 2).putShort(frameCount); + out.putByteArray(stackMap.data, 0, stackMap.length); + } + if (cattrs != null) { + cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); + } + } + if (exceptionCount > 0) { + out.putShort(cw.newUTF8("Exceptions")).putInt( + 2 * exceptionCount + 2); + out.putShort(exceptionCount); + for (int i = 0; i < exceptionCount; ++i) { + out.putShort(exceptions[i]); + } + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != null) { + out.putShort(cw.newUTF8("Signature")).putInt(2) + .putShort(cw.newUTF8(signature)); + } + if (ClassReader.ANNOTATIONS && annd != null) { + out.putShort(cw.newUTF8("AnnotationDefault")); + out.putInt(annd.length); + out.putByteArray(annd.data, 0, annd.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && panns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); + AnnotationWriter.put(panns, synthetics, out); + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations")); + AnnotationWriter.put(ipanns, synthetics, out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) + // ------------------------------------------------------------------------ + + /** + * Resizes and replaces the temporary instructions inserted by + * {@link Label#resolve} for wide forward jumps, while keeping jump offsets + * and instruction addresses consistent. This may require to resize other + * existing instructions, or even to introduce new instructions: for + * example, increasing the size of an instruction by 2 at the middle of a + * method can increases the offset of an IFEQ instruction from 32766 to + * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W + * 32765. This, in turn, may require to increase the size of another jump + * instruction, and so on... All these operations are handled automatically + * by this method. + *

+ * This method must be called after all the method that is being built + * has been visited. In particular, the {@link Label Label} objects used + * to construct the method are no longer valid after this method has been + * called. + */ + private void resizeInstructions() { + byte[] b = code.data; // bytecode of the method + int u, v, label; // indexes in b + int i, j; // loop indexes + /* + * 1st step: As explained above, resizing an instruction may require to + * resize another one, which may require to resize yet another one, and + * so on. The first step of the algorithm consists in finding all the + * instructions that need to be resized, without modifying the code. + * This is done by the following "fix point" algorithm: + * + * Parse the code to find the jump instructions whose offset will need + * more than 2 bytes to be stored (the future offset is computed from + * the current offset and from the number of bytes that will be inserted + * or removed between the source and target instructions). For each such + * instruction, adds an entry in (a copy of) the indexes and sizes + * arrays (if this has not already been done in a previous iteration!). + * + * If at least one entry has been added during the previous step, go + * back to the beginning, otherwise stop. + * + * In fact the real algorithm is complicated by the fact that the size + * of TABLESWITCH and LOOKUPSWITCH instructions depends on their + * position in the bytecode (because of padding). In order to ensure the + * convergence of the algorithm, the number of bytes to be added or + * removed from these instructions is over estimated during the previous + * loop, and computed exactly only after the loop is finished (this + * requires another pass to parse the bytecode of the method). + */ + int[] allIndexes = new int[0]; // copy of indexes + int[] allSizes = new int[0]; // copy of sizes + boolean[] resize; // instructions to be resized + int newOffset; // future offset of a jump instruction + + resize = new boolean[code.length]; + + // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done + int state = 3; + do { + if (state == 3) { + state = 2; + } + u = 0; + while (u < b.length) { + int opcode = b[u] & 0xFF; // opcode of current instruction + int insert = 0; // bytes to be added after this instruction + + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // converts temporary opcodes 202 to 217, 218 and + // 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (newOffset < Short.MIN_VALUE + || newOffset > Short.MAX_VALUE) { + if (!resize[u]) { + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // two additional bytes will be required to + // replace this GOTO or JSR instruction with + // a GOTO_W or a JSR_W + insert = 2; + } else { + // five additional bytes will be required to + // replace this IFxxx instruction with + // IFNOTxxx GOTO_W , where IFNOTxxx + // is the "opposite" opcode of IFxxx (i.e., + // IFNE for IFEQ) and where designates + // the instruction just after the GOTO_W. + insert = 5; + } + resize[u] = true; + } + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + u += 5; + break; + case ClassWriter.TABL_INSN: + if (state == 1) { + // true number of bytes to be added (or removed) + // from this instruction = (future number of padding + // bytes - current number of padding byte) - + // previously over estimated variation = + // = ((3 - newOffset%4) - (3 - u%4)) - u%4 + // = (-newOffset%4 + u%4) - u%4 + // = -(newOffset & 3) + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // over estimation of the number of bytes to be + // added to this instruction = 3 - current number + // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; + break; + case ClassWriter.LOOK_INSN: + if (state == 1) { + // like TABL_INSN + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // like TABL_INSN + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 8 * readInt(b, u + 4) + 8; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case ClassWriter.MANA_INSN: + default: + u += 4; + break; + } + if (insert != 0) { + // adds a new (u, insert) entry in the allIndexes and + // allSizes arrays + int[] newIndexes = new int[allIndexes.length + 1]; + int[] newSizes = new int[allSizes.length + 1]; + System.arraycopy(allIndexes, 0, newIndexes, 0, + allIndexes.length); + System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); + newIndexes[allIndexes.length] = u; + newSizes[allSizes.length] = insert; + allIndexes = newIndexes; + allSizes = newSizes; + if (insert > 0) { + state = 3; + } + } + } + if (state < 3) { + --state; + } + } while (state != 0); + + // 2nd step: + // copies the bytecode of the method into a new bytevector, updates the + // offsets, and inserts (or removes) bytes as requested. + + ByteVector newCode = new ByteVector(code.length); + + u = 0; + while (u < code.length) { + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + newCode.putByte(opcode); + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (resize[u]) { + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // with IFNOTxxx GOTO_W , where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO) { + newCode.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + newCode.putByte(201); // JSR_W + } else { + newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + newCode.putShort(8); // jump offset + newCode.putByte(200); // GOTO_W + // newOffset now computed from start of GOTO_W + newOffset -= 3; + } + newCode.putInt(newOffset); + } else { + newCode.putByte(opcode); + newCode.putShort(newOffset); + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + label = u + readInt(b, u + 1); + newOffset = getNewOffset(allIndexes, allSizes, u, label); + newCode.putByte(opcode); + newCode.putInt(newOffset); + u += 5; + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.TABLESWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + j = readInt(b, u) - j + 1; + u += 4; + newCode.putInt(readInt(b, u - 4)); + for (; j > 0; --j) { + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.LOOKUPSWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + for (; j > 0; --j) { + newCode.putInt(readInt(b, u)); + u += 4; + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + newCode.putByteArray(b, u, 6); + u += 6; + } else { + newCode.putByteArray(b, u, 4); + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + newCode.putByteArray(b, u, 2); + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + newCode.putByteArray(b, u, 3); + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + newCode.putByteArray(b, u, 5); + u += 5; + break; + // case MANA_INSN: + default: + newCode.putByteArray(b, u, 4); + u += 4; + break; + } + } + + // recomputes the stack map frames + if (frameCount > 0) { + if (compute == FRAMES) { + frameCount = 0; + stackMap = null; + previousFrame = null; + frame = null; + Frame f = new Frame(); + f.owner = labels; + Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, maxLocals); + visitFrame(f); + Label l = labels; + while (l != null) { + /* + * here we need the original label position. getNewOffset + * must therefore never have been called for this label. + */ + u = l.position - 3; + if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) { + getNewOffset(allIndexes, allSizes, l); + // TODO update offsets in UNINITIALIZED values + visitFrame(l.frame); + } + l = l.successor; + } + } else { + /* + * Resizing an existing stack map frame table is really hard. + * Not only the table must be parsed to update the offets, but + * new frames may be needed for jump instructions that were + * inserted by this method. And updating the offsets or + * inserting frames can change the format of the following + * frames, in case of packed frames. In practice the whole table + * must be recomputed. For this the frames are marked as + * potentially invalid. This will cause the whole class to be + * reread and rewritten with the COMPUTE_FRAMES option (see the + * ClassWriter.toByteArray method). This is not very efficient + * but is much easier and requires much less code than any other + * method I can think of. + */ + cw.invalidFrames = true; + } + } + // updates the exception handler block labels + Handler h = firstHandler; + while (h != null) { + getNewOffset(allIndexes, allSizes, h.start); + getNewOffset(allIndexes, allSizes, h.end); + getNewOffset(allIndexes, allSizes, h.handler); + h = h.next; + } + // updates the instructions addresses in the + // local var and line number tables + for (i = 0; i < 2; ++i) { + ByteVector bv = i == 0 ? localVar : localVarType; + if (bv != null) { + b = bv.data; + u = 0; + while (u < bv.length) { + label = readUnsignedShort(b, u); + newOffset = getNewOffset(allIndexes, allSizes, 0, label); + writeShort(b, u, newOffset); + label += readUnsignedShort(b, u + 2); + newOffset = getNewOffset(allIndexes, allSizes, 0, label) + - newOffset; + writeShort(b, u + 2, newOffset); + u += 10; + } + } + } + if (lineNumber != null) { + b = lineNumber.data; + u = 0; + while (u < lineNumber.length) { + writeShort( + b, + u, + getNewOffset(allIndexes, allSizes, 0, + readUnsignedShort(b, u))); + u += 4; + } + } + // updates the labels of the other attributes + Attribute attr = cattrs; + while (attr != null) { + Label[] labels = attr.getLabels(); + if (labels != null) { + for (i = labels.length - 1; i >= 0; --i) { + getNewOffset(allIndexes, allSizes, labels[i]); + } + } + attr = attr.next; + } + + // replaces old bytecodes with new ones + code = newCode; + } + + /** + * Reads an unsigned short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static int readUnsignedShort(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static short readShort(final byte[] b, final int index) { + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static int readInt(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Writes a short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * where the first byte of the short value must be written. + * @param s + * the value to be written in the given byte array. + */ + static void writeShort(final byte[] b, final int index, final int s) { + b[index] = (byte) (s >>> 8); + b[index + 1] = (byte) s; + } + + /** + * Computes the future value of a bytecode offset. + *

+ * Note: it is possible to have several entries for the same instruction in + * the indexes and sizes: two entries (index=a,size=b) and + * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b'). + * + * @param indexes + * current positions of the instructions to be resized. Each + * instruction must be designated by the index of its last + * byte, plus one (or, in other words, by the index of the + * first byte of the next instruction). + * @param sizes + * the number of bytes to be added to the above + * instructions. More precisely, for each i < len, + * sizes[i] bytes will be added at the end of the + * instruction designated by indexes[i] or, if + * sizes[i] is negative, the last | + * sizes[i]| bytes of the instruction will be removed + * (the instruction size must not become negative or + * null). + * @param begin + * index of the first byte of the source instruction. + * @param end + * index of the first byte of the target instruction. + * @return the future value of the given bytecode offset. + */ + static int getNewOffset(final int[] indexes, final int[] sizes, + final int begin, final int end) { + int offset = end - begin; + for (int i = 0; i < indexes.length; ++i) { + if (begin < indexes[i] && indexes[i] <= end) { + // forward jump + offset += sizes[i]; + } else if (end < indexes[i] && indexes[i] <= begin) { + // backward jump + offset -= sizes[i]; + } + } + return offset; + } + + /** + * Updates the offset of the given label. + * + * @param indexes + * current positions of the instructions to be resized. Each + * instruction must be designated by the index of its last + * byte, plus one (or, in other words, by the index of the + * first byte of the next instruction). + * @param sizes + * the number of bytes to be added to the above + * instructions. More precisely, for each i < len, + * sizes[i] bytes will be added at the end of the + * instruction designated by indexes[i] or, if + * sizes[i] is negative, the last | + * sizes[i]| bytes of the instruction will be removed + * (the instruction size must not become negative or + * null). + * @param label + * the label whose offset must be updated. + */ + static void getNewOffset(final int[] indexes, final int[] sizes, + final Label label) { + if ((label.status & Label.RESIZED) == 0) { + label.position = getNewOffset(indexes, sizes, 0, label.position); + label.status |= Label.RESIZED; + } + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Opcodes.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Opcodes.java new file mode 100644 index 0000000000..4462d8105d --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Opcodes.java @@ -0,0 +1,358 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +/** + * Defines the JVM opcodes, access flags and array type codes. This interface + * does not define all the JVM opcodes because some opcodes are automatically + * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced + * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n + * opcodes are therefore not defined in this interface. Likewise for LDC, + * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and + * JSR_W. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public interface Opcodes { + + // ASM API versions + + int ASM4 = 4 << 16 | 0 << 8 | 0; + + // versions + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + + // access flags + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + + // ASM specific pseudo access flags + + int ACC_DEPRECATED = 0x20000; // class, field, method + + // types for NEWARRAY + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // tags for Handle + + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; + + // stack map frame types + + /** + * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}. + */ + int F_NEW = -1; + + /** + * Represents a compressed frame with complete frame data. + */ + int F_FULL = 0; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that additional 1-3 locals are defined, and + * with an empty stack. + */ + int F_APPEND = 1; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that the last 1-3 locals are absent and with + * an empty stack. + */ + int F_CHOP = 2; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with a single value on the stack. + */ + int F_SAME1 = 4; + + Integer TOP = new Integer(0); + Integer INTEGER = new Integer(1); + Integer FLOAT = new Integer(2); + Integer DOUBLE = new Integer(3); + Integer LONG = new Integer(4); + Integer NULL = new Integer(5); + Integer UNINITIALIZED_THIS = new Integer(6); + + // opcodes // visit method (- = idem) + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + // int LDC_W = 19; // - + // int LDC2_W = 20; // - + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + // int ILOAD_0 = 26; // - + // int ILOAD_1 = 27; // - + // int ILOAD_2 = 28; // - + // int ILOAD_3 = 29; // - + // int LLOAD_0 = 30; // - + // int LLOAD_1 = 31; // - + // int LLOAD_2 = 32; // - + // int LLOAD_3 = 33; // - + // int FLOAD_0 = 34; // - + // int FLOAD_1 = 35; // - + // int FLOAD_2 = 36; // - + // int FLOAD_3 = 37; // - + // int DLOAD_0 = 38; // - + // int DLOAD_1 = 39; // - + // int DLOAD_2 = 40; // - + // int DLOAD_3 = 41; // - + // int ALOAD_0 = 42; // - + // int ALOAD_1 = 43; // - + // int ALOAD_2 = 44; // - + // int ALOAD_3 = 45; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + // int ISTORE_0 = 59; // - + // int ISTORE_1 = 60; // - + // int ISTORE_2 = 61; // - + // int ISTORE_3 = 62; // - + // int LSTORE_0 = 63; // - + // int LSTORE_1 = 64; // - + // int LSTORE_2 = 65; // - + // int LSTORE_3 = 66; // - + // int FSTORE_0 = 67; // - + // int FSTORE_1 = 68; // - + // int FSTORE_2 = 69; // - + // int FSTORE_3 = 70; // - + // int DSTORE_0 = 71; // - + // int DSTORE_1 = 72; // - + // int DSTORE_2 = 73; // - + // int DSTORE_3 = 74; // - + // int ASTORE_0 = 75; // - + // int ASTORE_1 = 76; // - + // int ASTORE_2 = 77; // - + // int ASTORE_3 = 78; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + // int WIDE = 196; // NOT VISITED + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - + // int GOTO_W = 200; // - + // int JSR_W = 201; // - +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Type.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Type.java new file mode 100644 index 0000000000..38f78f5086 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/Type.java @@ -0,0 +1,895 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java field or method type. This class can be used to make it easier to + * manipulate type and method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +public class Type { + + /** + * The sort of the void type. See {@link #getSort getSort}. + */ + public static final int VOID = 0; + + /** + * The sort of the boolean type. See {@link #getSort getSort}. + */ + public static final int BOOLEAN = 1; + + /** + * The sort of the char type. See {@link #getSort getSort}. + */ + public static final int CHAR = 2; + + /** + * The sort of the byte type. See {@link #getSort getSort}. + */ + public static final int BYTE = 3; + + /** + * The sort of the short type. See {@link #getSort getSort}. + */ + public static final int SHORT = 4; + + /** + * The sort of the int type. See {@link #getSort getSort}. + */ + public static final int INT = 5; + + /** + * The sort of the float type. See {@link #getSort getSort}. + */ + public static final int FLOAT = 6; + + /** + * The sort of the long type. See {@link #getSort getSort}. + */ + public static final int LONG = 7; + + /** + * The sort of the double type. See {@link #getSort getSort}. + */ + public static final int DOUBLE = 8; + + /** + * The sort of array reference types. See {@link #getSort getSort}. + */ + public static final int ARRAY = 9; + + /** + * The sort of object reference types. See {@link #getSort getSort}. + */ + public static final int OBJECT = 10; + + /** + * The sort of method types. See {@link #getSort getSort}. + */ + public static final int METHOD = 11; + + /** + * The void type. + */ + public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24) + | (5 << 16) | (0 << 8) | 0, 1); + + /** + * The boolean type. + */ + public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The char type. + */ + public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) + | (0 << 16) | (6 << 8) | 1, 1); + + /** + * The byte type. + */ + public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The short type. + */ + public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) + | (0 << 16) | (7 << 8) | 1, 1); + + /** + * The int type. + */ + public static final Type INT_TYPE = new Type(INT, null, ('I' << 24) + | (0 << 16) | (0 << 8) | 1, 1); + + /** + * The float type. + */ + public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) + | (2 << 16) | (2 << 8) | 1, 1); + + /** + * The long type. + */ + public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24) + | (1 << 16) | (1 << 8) | 2, 1); + + /** + * The double type. + */ + public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) + | (3 << 16) | (3 << 8) | 2, 1); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * The sort of this Java type. + */ + private final int sort; + + /** + * A buffer containing the internal name of this Java type. This field is + * only used for reference types. + */ + private final char[] buf; + + /** + * The offset of the internal name of this Java type in {@link #buf buf} or, + * for primitive types, the size, descriptor and getOpcode offsets for this + * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset + * for IALOAD or IASTORE, byte 3 the offset for all other instructions). + */ + private final int off; + + /** + * The length of the internal name of this Java type. + */ + private final int len; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a reference type. + * + * @param sort + * the sort of the reference type to be constructed. + * @param buf + * a buffer containing the descriptor of the previous type. + * @param off + * the offset of this descriptor in the previous buffer. + * @param len + * the length of this descriptor. + */ + private Type(final int sort, final char[] buf, final int off, final int len) { + this.sort = sort; + this.buf = buf; + this.off = off; + this.len = len; + } + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param typeDescriptor + * a field or method type descriptor. + * @return the Java type corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getType(typeDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java type corresponding to the given internal name. + * + * @param internalName + * an internal name. + * @return the Java type corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + char[] buf = internalName.toCharArray(); + return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length); + } + + /** + * Returns the Java type corresponding to the given method descriptor. + * Equivalent to Type.getType(methodDescriptor). + * + * @param methodDescriptor + * a method descriptor. + * @return the Java type corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return getType(methodDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java method type corresponding to the given argument and + * return types. + * + * @param returnType + * the return type of the method. + * @param argumentTypes + * the argument types of the method. + * @return the Java type corresponding to the given argument and return + * types. + */ + public static Type getMethodType(final Type returnType, + final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the Java type corresponding to the given class. + * + * @param c + * a class. + * @return the Java type corresponding to the given class. + */ + public static Type getType(final Class c) { + if (c.isPrimitive()) { + if (c == Integer.TYPE) { + return INT_TYPE; + } else if (c == Void.TYPE) { + return VOID_TYPE; + } else if (c == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (c == Byte.TYPE) { + return BYTE_TYPE; + } else if (c == Character.TYPE) { + return CHAR_TYPE; + } else if (c == Short.TYPE) { + return SHORT_TYPE; + } else if (c == Double.TYPE) { + return DOUBLE_TYPE; + } else if (c == Float.TYPE) { + return FLOAT_TYPE; + } else /* if (c == Long.TYPE) */{ + return LONG_TYPE; + } + } else { + return getType(getDescriptor(c)); + } + } + + /** + * Returns the Java method type corresponding to the given constructor. + * + * @param c + * a {@link Constructor Constructor} object. + * @return the Java method type corresponding to the given constructor. + */ + public static Type getType(final Constructor c) { + return getType(getConstructorDescriptor(c)); + } + + /** + * Returns the Java method type corresponding to the given method. + * + * @param m + * a {@link Method Method} object. + * @return the Java method type corresponding to the given method. + */ + public static Type getType(final Method m) { + return getType(getMethodDescriptor(m)); + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java types corresponding to the argument types of the given + * method descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + int off = 1; + int size = 0; + while (true) { + char car = buf[off++]; + if (car == ')') { + break; + } else if (car == 'L') { + while (buf[off++] != ';') { + } + ++size; + } else if (car != '[') { + ++size; + } + } + Type[] args = new Type[size]; + off = 1; + size = 0; + while (buf[off] != ')') { + args[size] = getType(buf, off); + off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); + size += 1; + } + return args; + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method. + * + * @param method + * a method. + * @return the Java types corresponding to the argument types of the given + * method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the Java type corresponding to the return type of the given + * method descriptor. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java type corresponding to the return type of the given + * method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + return getType(buf, methodDescriptor.indexOf(')') + 1); + } + + /** + * Returns the Java type corresponding to the return type of the given + * method. + * + * @param method + * a method. + * @return the Java type corresponding to the return type of the given + * method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param desc + * the descriptor of a method. + * @return the size of the arguments of the method (plus one for the + * implicit this argument), argSize, and the size of its return + * value, retSize, packed into a single int i = + * (argSize << 2) | retSize (argSize is therefore equal to + * i >> 2, and retSize to i & 0x03). + */ + public static int getArgumentsAndReturnSizes(final String desc) { + int n = 1; + int c = 1; + while (true) { + char car = desc.charAt(c++); + if (car == ')') { + car = desc.charAt(c); + return n << 2 + | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1)); + } else if (car == 'L') { + while (desc.charAt(c++) != ';') { + } + n += 1; + } else if (car == '[') { + while ((car = desc.charAt(c)) == '[') { + ++c; + } + if (car == 'D' || car == 'J') { + n -= 1; + } + } else if (car == 'D' || car == 'J') { + n += 2; + } else { + n += 1; + } + } + } + + /** + * Returns the Java type corresponding to the given type descriptor. For + * method descriptors, buf is supposed to contain nothing more than the + * descriptor itself. + * + * @param buf + * a buffer containing a type descriptor. + * @param off + * the offset of this descriptor in the previous buffer. + * @return the Java type corresponding to the given type descriptor. + */ + private static Type getType(final char[] buf, final int off) { + int len; + switch (buf[off]) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + len = 1; + while (buf[off + len] == '[') { + ++len; + } + if (buf[off + len] == 'L') { + ++len; + while (buf[off + len] != ';') { + ++len; + } + } + return new Type(ARRAY, buf, off, len + 1); + case 'L': + len = 1; + while (buf[off + len] != ';') { + ++len; + } + return new Type(OBJECT, buf, off + 1, len - 1); + // case '(': + default: + return new Type(METHOD, buf, off, buf.length - off); + } + } + + // ------------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------------ + + /** + * Returns the sort of this Java type. + * + * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR}, + * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT}, + * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE}, + * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD + * METHOD}. + */ + public int getSort() { + return sort; + } + + /** + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int i = 1; + while (buf[off + i] == '[') { + ++i; + } + return i; + } + + /** + * Returns the type of the elements of this array type. This method should + * only be used for an array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + return getType(buf, off + getDimensions()); + } + + /** + * Returns the binary name of the class corresponding to this type. This + * method must not be used on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuffer b = new StringBuffer(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + b.append("[]"); + } + return b.toString(); + case OBJECT: + return new String(buf, off, len).replace('/', '.'); + default: + return null; + } + } + + /** + * Returns the internal name of the class corresponding to this object or + * array type. The internal name of a class is its fully qualified name (as + * returned by Class.getName(), where '.' are replaced by '/'. This method + * should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return new String(buf, off, len); + } + + /** + * Returns the argument types of methods of this type. This method should + * only be used for method types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * Returns the return type of methods of this type. This method should only + * be used for method types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the size of the arguments and of the return value of methods of + * this type. This method should only be used for method types. + * + * @return the size of the arguments (plus one for the implicit this + * argument), argSize, and the size of the return value, retSize, + * packed into a single int i = (argSize << 2) | retSize + * (argSize is therefore equal to i >> 2, and retSize to + * i & 0x03). + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /** + * Returns the descriptor corresponding to this Java type. + * + * @return the descriptor corresponding to this Java type. + */ + public String getDescriptor() { + StringBuffer buf = new StringBuffer(); + getDescriptor(buf); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given argument and return + * types. + * + * @param returnType + * the return type of the method. + * @param argumentTypes + * the argument types of the method. + * @return the descriptor corresponding to the given argument and return + * types. + */ + public static String getMethodDescriptor(final Type returnType, + final Type... argumentTypes) { + StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < argumentTypes.length; ++i) { + argumentTypes[i].getDescriptor(buf); + } + buf.append(')'); + returnType.getDescriptor(buf); + return buf.toString(); + } + + /** + * Appends the descriptor corresponding to this Java type to the given + * string buffer. + * + * @param buf + * the string buffer to which the descriptor must be appended. + */ + private void getDescriptor(final StringBuffer buf) { + if (this.buf == null) { + // descriptor is in byte 3 of 'off' for primitive types (buf == + // null) + buf.append((char) ((off & 0xFF000000) >>> 24)); + } else if (sort == OBJECT) { + buf.append('L'); + buf.append(this.buf, off, len); + buf.append(';'); + } else { // sort == ARRAY || sort == METHOD + buf.append(this.buf, off, len); + } + } + + // ------------------------------------------------------------------------ + // Direct conversion from classes to type descriptors, + // without intermediate Type objects + // ------------------------------------------------------------------------ + + /** + * Returns the internal name of the given class. The internal name of a + * class is its fully qualified name, as returned by Class.getName(), where + * '.' are replaced by '/'. + * + * @param c + * an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class c) { + return c.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to the given Java type. + * + * @param c + * an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class c) { + StringBuffer buf = new StringBuffer(); + getDescriptor(buf, c); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param c + * a {@link Constructor Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor c) { + Class[] parameters = c.getParameterTypes(); + StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + return buf.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param m + * a {@link Method Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method m) { + Class[] parameters = m.getParameterTypes(); + StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + buf.append(')'); + getDescriptor(buf, m.getReturnType()); + return buf.toString(); + } + + /** + * Appends the descriptor of the given class to the given string buffer. + * + * @param buf + * the string buffer to which the descriptor must be appended. + * @param c + * the class whose descriptor must be computed. + */ + private static void getDescriptor(final StringBuffer buf, final Class c) { + Class d = c; + while (true) { + if (d.isPrimitive()) { + char car; + if (d == Integer.TYPE) { + car = 'I'; + } else if (d == Void.TYPE) { + car = 'V'; + } else if (d == Boolean.TYPE) { + car = 'Z'; + } else if (d == Byte.TYPE) { + car = 'B'; + } else if (d == Character.TYPE) { + car = 'C'; + } else if (d == Short.TYPE) { + car = 'S'; + } else if (d == Double.TYPE) { + car = 'D'; + } else if (d == Float.TYPE) { + car = 'F'; + } else /* if (d == Long.TYPE) */{ + car = 'J'; + } + buf.append(car); + return; + } else if (d.isArray()) { + buf.append('['); + d = d.getComponentType(); + } else { + buf.append('L'); + String name = d.getName(); + int len = name.length(); + for (int i = 0; i < len; ++i) { + char car = name.charAt(i); + buf.append(car == '.' ? '/' : car); + } + buf.append(';'); + return; + } + } + } + + // ------------------------------------------------------------------------ + // Corresponding size and opcodes + // ------------------------------------------------------------------------ + + /** + * Returns the size of values of this type. This method must not be used for + * method types. + * + * @return the size of values of this type, i.e., 2 for long and + * double, 0 for void and 1 otherwise. + */ + public int getSize() { + // the size is in byte 0 of 'off' for primitive types (buf == null) + return buf == null ? (off & 0xFF) : 1; + } + + /** + * Returns a JVM instruction opcode adapted to this Java type. This method + * must not be used for method types. + * + * @param opcode + * a JVM instruction opcode. This opcode must be one of ILOAD, + * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, + * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to + * this Java type. For example, if this type is float and + * opcode is IRETURN, this method returns FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4); + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4); + } + } + + // ------------------------------------------------------------------------ + // Equals, hashCode and toString + // ------------------------------------------------------------------------ + + /** + * Tests if the given object is equal to this type. + * + * @param o + * the object to be compared to this type. + * @return true if the given object is equal to this type. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Type)) { + return false; + } + Type t = (Type) o; + if (sort != t.sort) { + return false; + } + if (sort >= ARRAY) { + if (len != t.len) { + return false; + } + for (int i = off, j = t.off, end = i + len; i < end; i++, j++) { + if (buf[i] != t.buf[j]) { + return false; + } + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hc = 13 * sort; + if (sort >= ARRAY) { + for (int i = off, end = i + len; i < end; i++) { + hc = 17 * (hc + buf[i]); + } + } + return hc; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/attrs/package.html b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/attrs/package.html new file mode 100644 index 0000000000..0ffbd0727b --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/attrs/package.html @@ -0,0 +1,66 @@ + + + + + +Provides an implementation for optional class, field and method attributes. + +

+ +By default ASM strips optional attributes, in order to keep them in +the bytecode that is being readed you should pass an array of required attribute +instances to {@link org.objectweb.asm.ClassReader#accept(org.objectweb.asm.ClassVisitor, org.objectweb.asm.Attribute[], boolean) ClassReader.accept()} method. +In order to add custom attributes to the manually constructed bytecode concrete +subclasses of the {@link org.objectweb.asm.Attribute Attribute} can be passed to +the visitAttribute methods of the +{@link org.objectweb.asm.ClassVisitor ClassVisitor}, +{@link org.objectweb.asm.FieldVisitor FieldVisitor} and +{@link org.objectweb.asm.MethodVisitor MethodVisitor} interfaces. + +@since ASM 1.4.1 + + diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AdviceAdapter.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AdviceAdapter.java new file mode 100644 index 0000000000..72e0cc0465 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AdviceAdapter.java @@ -0,0 +1,625 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm.commons; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.tajo.org.objectweb.asm.Label; +import org.apache.tajo.org.objectweb.asm.Type; +import org.apache.tajo.org.objectweb.asm.Handle; +import org.apache.tajo.org.objectweb.asm.MethodVisitor; +import org.apache.tajo.org.objectweb.asm.Opcodes; + +/** + * A {@link org.apache.tajo.org.objectweb.asm.MethodVisitor} to insert before, after and around + * advices in methods and constructors. + *

+ * The behavior for constructors is like this: + *

    + * + *
  1. as long as the INVOKESPECIAL for the object initialization has not been + * reached, every bytecode instruction is dispatched in the ctor code visitor
  2. + * + *
  3. when this one is reached, it is only added in the ctor code visitor and a + * JP invoke is added
  4. + * + *
  5. after that, only the other code visitor receives the instructions
  6. + * + *
+ * + * @author Eugene Kuleshov + * @author Eric Bruneton + */ +public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { + + private static final Object THIS = new Object(); + + private static final Object OTHER = new Object(); + + protected int methodAccess; + + protected String methodDesc; + + private boolean constructor; + + private boolean superInitialized; + + private List stackFrame; + + private Map> branches; + + /** + * Creates a new {@link AdviceAdapter}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param mv + * the method visitor to which this adapter delegates calls. + * @param access + * the method's access flags (see {@link Opcodes}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link org.apache.tajo.org.objectweb.asm.Type Type}). + */ + protected AdviceAdapter(final int api, final MethodVisitor mv, + final int access, final String name, final String desc) { + super(api, mv, access, name, desc); + methodAccess = access; + methodDesc = desc; + constructor = "".equals(name); + } + + @Override + public void visitCode() { + mv.visitCode(); + if (constructor) { + stackFrame = new ArrayList(); + branches = new HashMap>(); + } else { + superInitialized = true; + onMethodEnter(); + } + } + + @Override + public void visitLabel(final Label label) { + mv.visitLabel(label); + if (constructor && branches != null) { + List frame = branches.get(label); + if (frame != null) { + stackFrame = frame; + branches.remove(label); + } + } + } + + @Override + public void visitInsn(final int opcode) { + if (constructor) { + int s; + switch (opcode) { + case RETURN: // empty stack + onMethodExit(opcode); + break; + case IRETURN: // 1 before n/a after + case FRETURN: // 1 before n/a after + case ARETURN: // 1 before n/a after + case ATHROW: // 1 before n/a after + popValue(); + onMethodExit(opcode); + break; + case LRETURN: // 2 before n/a after + case DRETURN: // 2 before n/a after + popValue(); + popValue(); + onMethodExit(opcode); + break; + case NOP: + case LALOAD: // remove 2 add 2 + case DALOAD: // remove 2 add 2 + case LNEG: + case DNEG: + case FNEG: + case INEG: + case L2D: + case D2L: + case F2I: + case I2B: + case I2C: + case I2S: + case I2F: + case ARRAYLENGTH: + break; + case ACONST_NULL: + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + case FCONST_0: + case FCONST_1: + case FCONST_2: + case F2L: // 1 before 2 after + case F2D: + case I2L: + case I2D: + pushValue(OTHER); + break; + case LCONST_0: + case LCONST_1: + case DCONST_0: + case DCONST_1: + pushValue(OTHER); + pushValue(OTHER); + break; + case IALOAD: // remove 2 add 1 + case FALOAD: // remove 2 add 1 + case AALOAD: // remove 2 add 1 + case BALOAD: // remove 2 add 1 + case CALOAD: // remove 2 add 1 + case SALOAD: // remove 2 add 1 + case POP: + case IADD: + case FADD: + case ISUB: + case LSHL: // 3 before 2 after + case LSHR: // 3 before 2 after + case LUSHR: // 3 before 2 after + case L2I: // 2 before 1 after + case L2F: // 2 before 1 after + case D2I: // 2 before 1 after + case D2F: // 2 before 1 after + case FSUB: + case FMUL: + case FDIV: + case FREM: + case FCMPL: // 2 before 1 after + case FCMPG: // 2 before 1 after + case IMUL: + case IDIV: + case IREM: + case ISHL: + case ISHR: + case IUSHR: + case IAND: + case IOR: + case IXOR: + case MONITORENTER: + case MONITOREXIT: + popValue(); + break; + case POP2: + case LSUB: + case LMUL: + case LDIV: + case LREM: + case LADD: + case LAND: + case LOR: + case LXOR: + case DADD: + case DMUL: + case DSUB: + case DDIV: + case DREM: + popValue(); + popValue(); + break; + case IASTORE: + case FASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + case LCMP: // 4 before 1 after + case DCMPL: + case DCMPG: + popValue(); + popValue(); + popValue(); + break; + case LASTORE: + case DASTORE: + popValue(); + popValue(); + popValue(); + popValue(); + break; + case DUP: + pushValue(peekValue()); + break; + case DUP_X1: + s = stackFrame.size(); + stackFrame.add(s - 2, stackFrame.get(s - 1)); + break; + case DUP_X2: + s = stackFrame.size(); + stackFrame.add(s - 3, stackFrame.get(s - 1)); + break; + case DUP2: + s = stackFrame.size(); + stackFrame.add(s - 2, stackFrame.get(s - 1)); + stackFrame.add(s - 2, stackFrame.get(s - 1)); + break; + case DUP2_X1: + s = stackFrame.size(); + stackFrame.add(s - 3, stackFrame.get(s - 1)); + stackFrame.add(s - 3, stackFrame.get(s - 1)); + break; + case DUP2_X2: + s = stackFrame.size(); + stackFrame.add(s - 4, stackFrame.get(s - 1)); + stackFrame.add(s - 4, stackFrame.get(s - 1)); + break; + case SWAP: + s = stackFrame.size(); + stackFrame.add(s - 2, stackFrame.get(s - 1)); + stackFrame.remove(s); + break; + } + } else { + switch (opcode) { + case RETURN: + case IRETURN: + case FRETURN: + case ARETURN: + case LRETURN: + case DRETURN: + case ATHROW: + onMethodExit(opcode); + break; + } + } + mv.visitInsn(opcode); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + super.visitVarInsn(opcode, var); + if (constructor) { + switch (opcode) { + case ILOAD: + case FLOAD: + pushValue(OTHER); + break; + case LLOAD: + case DLOAD: + pushValue(OTHER); + pushValue(OTHER); + break; + case ALOAD: + pushValue(var == 0 ? THIS : OTHER); + break; + case ASTORE: + case ISTORE: + case FSTORE: + popValue(); + break; + case LSTORE: + case DSTORE: + popValue(); + popValue(); + break; + } + } + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, + final String name, final String desc) { + mv.visitFieldInsn(opcode, owner, name, desc); + if (constructor) { + char c = desc.charAt(0); + boolean longOrDouble = c == 'J' || c == 'D'; + switch (opcode) { + case GETSTATIC: + pushValue(OTHER); + if (longOrDouble) { + pushValue(OTHER); + } + break; + case PUTSTATIC: + popValue(); + if (longOrDouble) { + popValue(); + } + break; + case PUTFIELD: + popValue(); + if (longOrDouble) { + popValue(); + popValue(); + } + break; + // case GETFIELD: + default: + if (longOrDouble) { + pushValue(OTHER); + } + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + mv.visitIntInsn(opcode, operand); + if (constructor && opcode != NEWARRAY) { + pushValue(OTHER); + } + } + + @Override + public void visitLdcInsn(final Object cst) { + mv.visitLdcInsn(cst); + if (constructor) { + pushValue(OTHER); + if (cst instanceof Double || cst instanceof Long) { + pushValue(OTHER); + } + } + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + mv.visitMultiANewArrayInsn(desc, dims); + if (constructor) { + for (int i = 0; i < dims; i++) { + popValue(); + } + pushValue(OTHER); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + mv.visitTypeInsn(opcode, type); + // ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack + if (constructor && opcode == NEW) { + pushValue(OTHER); + } + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc) { + mv.visitMethodInsn(opcode, owner, name, desc); + if (constructor) { + Type[] types = Type.getArgumentTypes(desc); + for (int i = 0; i < types.length; i++) { + popValue(); + if (types[i].getSize() == 2) { + popValue(); + } + } + switch (opcode) { + // case INVOKESTATIC: + // break; + case INVOKEINTERFACE: + case INVOKEVIRTUAL: + popValue(); // objectref + break; + case INVOKESPECIAL: + Object type = popValue(); // objectref + if (type == THIS && !superInitialized) { + onMethodEnter(); + superInitialized = true; + // once super has been initialized it is no longer + // necessary to keep track of stack state + constructor = false; + } + break; + } + + Type returnType = Type.getReturnType(desc); + if (returnType != Type.VOID_TYPE) { + pushValue(OTHER); + if (returnType.getSize() == 2) { + pushValue(OTHER); + } + } + } + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + if (constructor) { + Type[] types = Type.getArgumentTypes(desc); + for (int i = 0; i < types.length; i++) { + popValue(); + if (types[i].getSize() == 2) { + popValue(); + } + } + + Type returnType = Type.getReturnType(desc); + if (returnType != Type.VOID_TYPE) { + pushValue(OTHER); + if (returnType.getSize() == 2) { + pushValue(OTHER); + } + } + } + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + mv.visitJumpInsn(opcode, label); + if (constructor) { + switch (opcode) { + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case IFNULL: + case IFNONNULL: + popValue(); + break; + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + popValue(); + popValue(); + break; + case JSR: + pushValue(OTHER); + break; + } + addBranch(label); + } + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, + final Label[] labels) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + if (constructor) { + popValue(); + addBranches(dflt, labels); + } + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, + final Label dflt, final Label... labels) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + if (constructor) { + popValue(); + addBranches(dflt, labels); + } + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, + String type) { + super.visitTryCatchBlock(start, end, handler, type); + if (constructor && !branches.containsKey(handler)) { + List stackFrame = new ArrayList(); + stackFrame.add(OTHER); + branches.put(handler, stackFrame); + } + } + + private void addBranches(final Label dflt, final Label[] labels) { + addBranch(dflt); + for (int i = 0; i < labels.length; i++) { + addBranch(labels[i]); + } + } + + private void addBranch(final Label label) { + if (branches.containsKey(label)) { + return; + } + branches.put(label, new ArrayList(stackFrame)); + } + + private Object popValue() { + return stackFrame.remove(stackFrame.size() - 1); + } + + private Object peekValue() { + return stackFrame.get(stackFrame.size() - 1); + } + + private void pushValue(final Object o) { + stackFrame.add(o); + } + + /** + * Called at the beginning of the method or after super class class call in + * the constructor.
+ *
+ * + * Custom code can use or change all the local variables, but should not + * change state of the stack. + */ + protected void onMethodEnter() { + } + + /** + * Called before explicit exit from the method using either return or throw. + * Top element on the stack contains the return value or exception instance. + * For example: + * + *
+     *   public void onMethodExit(int opcode) {
+     *     if(opcode==RETURN) {
+     *         visitInsn(ACONST_NULL);
+     *     } else if(opcode==ARETURN || opcode==ATHROW) {
+     *         dup();
+     *     } else {
+     *         if(opcode==LRETURN || opcode==DRETURN) {
+     *             dup2();
+     *         } else {
+     *             dup();
+     *         }
+     *         box(Type.getReturnType(this.methodDesc));
+     *     }
+     *     visitIntInsn(SIPUSH, opcode);
+     *     visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V");
+     *   }
+     * 
+     *   // an actual call back method
+     *   public static void onExit(Object param, int opcode) {
+     *     ...
+     * 
+ * + *
+ *
+ * + * Custom code can use or change all the local variables, but should not + * change state of the stack. + * + * @param opcode + * one of the RETURN, IRETURN, FRETURN, ARETURN, LRETURN, DRETURN + * or ATHROW + * + */ + protected void onMethodExit(int opcode) { + } + + // TODO onException, onMethodCall +} diff --git a/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AnalyzerAdapter.java b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AnalyzerAdapter.java new file mode 100644 index 0000000000..ef8ef4d447 --- /dev/null +++ b/tajo-thirdparty/asm/src/main/java/org/apache/tajo/org/objectweb/asm/commons/AnalyzerAdapter.java @@ -0,0 +1,920 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.apache.tajo.org.objectweb.asm.commons; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.tajo.org.objectweb.asm.Label; +import org.apache.tajo.org.objectweb.asm.Type; +import org.apache.tajo.org.objectweb.asm.Handle; +import org.apache.tajo.org.objectweb.asm.MethodVisitor; +import org.apache.tajo.org.objectweb.asm.Opcodes; + +/** + * A {@link MethodVisitor} that keeps track of stack map frame changes between + * {@link #visitFrame(int, int, Object[], int, Object[]) visitFrame} calls. This + * adapter must be used with the + * {@link org.apache.tajo.org.objectweb.asm.ClassReader#EXPAND_FRAMES} option. Each + * visitX instruction delegates to the next visitor in the chain, if any, + * and then simulates the effect of this instruction on the stack map frame, + * represented by {@link #locals} and {@link #stack}. The next visitor in the + * chain can get the state of the stack map frame before each instruction + * by reading the value of these fields in its visitX methods (this + * requires a reference to the AnalyzerAdapter that is before it in the chain). + * If this adapter is used with a class that does not contain stack map table + * attributes (i.e., pre Java 6 classes) then this adapter may not be able to + * compute the stack map frame for each instruction. In this case no exception + * is thrown but the {@link #locals} and {@link #stack} fields will be null for + * these instructions. + * + * @author Eric Bruneton + */ +public class AnalyzerAdapter extends MethodVisitor { + + /** + * List of the local variable slots for current execution + * frame. Primitive types are represented by {@link Opcodes#TOP}, + * {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by + * two elements, the second one being TOP). Reference types are represented + * by String objects (representing internal names), and uninitialized types + * by Label objects (this label designates the NEW instruction that created + * this uninitialized value). This field is null for unreachable + * instructions. + */ + public List locals; + + /** + * List of the operand stack slots for current execution frame. + * Primitive types are represented by {@link Opcodes#TOP}, + * {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by + * two elements, the second one being TOP). Reference types are represented + * by String objects (representing internal names), and uninitialized types + * by Label objects (this label designates the NEW instruction that created + * this uninitialized value). This field is null for unreachable + * instructions. + */ + public List stack; + + /** + * The labels that designate the next instruction to be visited. May be + * null. + */ + private List