--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry excluding="**/.svn/*" kind="src" path="src"/>\r
+ <classpathentry excluding="**/.svn/*" kind="src" path="tests"/>\r
+ <classpathentry excluding="**/.svn/*" kind="src" path="example"/>\r
+ <classpathentry kind="lib" path="lib/hamcrest-all-1.3.jar" sourcepath="lib/hamcrest-all-1.3-sources.jar"/>\r
+ <classpathentry kind="lib" path="lib/trove-3.0.2.jar" sourcepath="lib/trove-3.0.2-src.jar"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>\r
+ <classpathentry kind="lib" path="lib/asm-debug-all-5.0.3.jar" sourcepath="lib/asm-all-5.0.3-src.zip"/>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>soba</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ <nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+eclipse.preferences.version=1
+encoding//src/soba/milanova/outerdata/ReadOrWrite.java=SJIS
+encoding/<project>=UTF-8
--- /dev/null
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8\r
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve\r
+org.eclipse.jdt.core.compiler.compliance=1.8\r
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate\r
+org.eclipse.jdt.core.compiler.debug.localVariable=generate\r
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.8\r
--- /dev/null
+\r
+The SOBA project is distributed under the MIT License.\r
+\r
+SOBA is dependent on three third party libraries: ASM, Hamcrest, and GNU Trove.\r
+ - ASM License is included in this file. \r
+ - Hamcrest is distributed under the BSD 3-Clause License.\r
+ - GNU Trove is distributed under the LGPL License.\r
+\r
+----------------------------------------------------------------------------\r
+The MIT License (MIT)\r
+\r
+Copyright (c) 2015 Takashi Ishio and Tomomi Hatano (Osaka University)\r
+\r
+Permission is hereby granted, free of charge, to any person obtaining a copy\r
+of this software and associated documentation files (the "Software"), to deal\r
+in the Software without restriction, including without limitation the rights\r
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+copies of the Software, and to permit persons to whom the Software is\r
+furnished to do so, subject to the following conditions:\r
+\r
+The above copyright notice and this permission notice shall be included in\r
+all copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+THE SOFTWARE.\r
+----------------------------------------------------------------------------\r
+\r
+\r
+\r
+ASM License\r
+----------------------------------------------------------------------------\r
+Copyright (c) 2000-2011 INRIA, France Telecom\r
+All rights reserved.\r
+\r
+Redistribution and use in source and binary forms, with or without\r
+modification, are permitted provided that the following conditions\r
+are met:\r
+\r
+1. Redistributions of source code must retain the above copyright\r
+ notice, this list of conditions and the following disclaimer.\r
+\r
+2. Redistributions in binary form must reproduce the above copyright\r
+ notice, this list of conditions and the following disclaimer in the\r
+ documentation and/or other materials provided with the distribution.\r
+\r
+3. Neither the name of the copyright holders nor the names of its\r
+ contributors may be used to endorse or promote products derived from\r
+ this software without specific prior written permission.\r
+\r
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\r
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\r
+THE POSSIBILITY OF SUCH DAMAGE.\r
+----------------------------------------------------------------------------\r
--- /dev/null
+
+# SOBA テクニカルノート
+
+## SOBA が持つ機能
+
+ * 指定ディレクトリ,あるいは JAR ファイルからクラス情報を読み込む機能.読み込まれたデータは JavaProgram オブジェクトとして表現されます.
+ * 読み込んだクラス群が持つメソッドに対して,バイトコードの命令をノードとした制御フローグラフ,制御依存関係,データ依存関係を取得することができます.
+ * Class Hierarchy Analysis と Variable Type Analysis を使った動的束縛の解決
+
+
+## 解析時の注意点
+
+### バイトコードだけからでは,ソースコード位置を特定できないメソッドが存在する
+
+各メソッドのソースコード上での定義位置は `LineNumberTable` から知ることができるが,
+命令が1つもなく,戻り値が void 型であるようなメソッドは,命令がないために行番号も持たない.
+そのため,バイトコードだけからでは,メソッドの宣言位置を特定できない.
+
+
+
+### メソッドの実装が1つも存在しなければ動的束縛の解決結果は空になる
+
+CHA や VTA を使って「動的束縛の解決後」の呼び出し関係を取り出す場合は,
+メソッドの定義が存在するように,うまくクラスパスを設定しなくてはならない.
+対応するメソッドの定義が検索範囲に1つも含まれていない場合,
+動的束縛の解決結果(実際に呼ばれうるメソッドの集合)は空集合となる.
+呼び出し関係をグラフ化する場合などには,呼び出しが「なかったことになる」ため,注意が必要である.
+
+
+### Generic Types はまじめに扱っていない
+
+メソッドの引数に関する Generic Types は,何も考えず型パラメータの中身を文字列展開している.
+クラスの型パラメータ自体がそのままメソッドの引数に出ている可能性があるので,「すべて実際の型」とは考えてはいけない.
+
+ASM での Generics の使い方は,
+[IBM dW: クラスワーキング・ツールキット: ASMとジェネリックス](http://www.ibm.com/developerworks/jp/java/library/j-cwt02076/)と,
+[ASM API JavaDoc: SignatureVisitor](http://www.docjar.com/docs/api/org/objectweb/asm/signature/SignatureVisitor.html)クラスの内容を参照するとよい.
+
+
+### 異なるソースコードが同一バイトコードになる例が存在する
+
+`if (X && Y) {` と `if (X) { if (Y) {` という2つの記述方法は,前者だと2つの条件分岐命令の間に LABEL ノードが入らず,後者は入るために区別可能.
+
+`if ((X || !Y) && (!X || Y)) {` のように条件分岐が複雑になる(IF式中に合流点が生じる)と,
+OR で判定された結果が合流するための LABEL ノードが条件分岐命令間に生成されてしまい,IF文2つを入れ子で記述した場合とまったく同一のバイトコードになる.
+
+
+### INVOKEDYNAMIC の解析はできない
+
+ASM の機能としてはサポートされているが,SOBA でこの情報を活用できるようにはしていない.
+
--- /dev/null
+package soba.example.dump;\r
+\r
+import soba.core.ClassHierarchy;\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.CallSite;\r
+import soba.util.files.ClasspathUtil;\r
+\r
+public class DumpMethodCall {\r
+\r
+ public static void main(String[] args) {\r
+ long count = 0;\r
+ long t = System.currentTimeMillis();\r
+ int classCount = 0;\r
+ int methodCount = 0;\r
+ JavaProgram program = new JavaProgram(ClasspathUtil.getClassList(args));\r
+ ClassHierarchy ch = program.getClassHierarchy();\r
+ \r
+ for (ClassInfo c: program.getClasses()) {\r
+ classCount++;\r
+ for (MethodInfo m: c.getMethods()) {\r
+ methodCount++;\r
+ System.out.println(m.toLongString());\r
+ for (CallSite cs: m.getCallSites()) {\r
+ MethodInfo[] callees = ch.resolveCall(cs);\r
+ if (callees.length > 0) {\r
+ for (MethodInfo callee: callees) {\r
+ //System.out.println(" [inside] " + callee.toLongString());\r
+ count++;\r
+ }\r
+ } else {\r
+ //System.out.println(" [outside] " + cs.toString());\r
+ count++;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ System.out.println(classCount + " classes");\r
+ System.out.println(methodCount + " methods");\r
+ System.out.println(count + " method calls");\r
+ System.out.println((System.currentTimeMillis() -t ) + "ms");\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.example.dump;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.CallSite;\r
+import soba.core.vta.CallResolver;\r
+import soba.util.files.ClasspathUtil;\r
+\r
+public class DumpMethodCallUsingVTA {\r
+\r
+ public static void main(String[] args) {\r
+ JavaProgram program = new JavaProgram(ClasspathUtil.getClassList(args));\r
+ CallResolver resolver = CallResolver.getVTA(program);\r
+ \r
+ for (ClassInfo c: program.getClasses()) {\r
+ for (MethodInfo m: c.getMethods()) {\r
+ System.out.println(m.toLongString());\r
+ for (CallSite cs: m.getCallSites()) {\r
+ MethodInfo[] callees = resolver.resolveCall(cs);\r
+ if (callees.length > 0) {\r
+ for (MethodInfo callee: callees) {\r
+ System.out.println(" [inside] " + callee.toLongString());\r
+ }\r
+ } else {\r
+ System.out.println(" [outside] " + cs.toString());\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Queue;\r
+import java.util.Set;\r
+import java.util.Stack;\r
+\r
+import soba.core.method.CallSite;\r
+import soba.core.method.FieldAccess;\r
+\r
+/**\r
+ * This class represents a class hierarchy.\r
+ */\r
+public class ClassHierarchy implements IDynamicBindingResolver {\r
+\r
+ private boolean frozen;\r
+ \r
+ private static final String JAVA_LANG_OBJECT = "java" + ClassInfo.PACKAGE_SEPARATOR + "lang" + ClassInfo.PACKAGE_SEPARATOR + "Object"; \r
+ \r
+ private Map<String, ClassInfo> entries; // type -> a method list of the type\r
+ private Map<String, String> parentClass; // type -> its super type \r
+ private Map<String, List<String>> parentInterfaces; // type -> its interfaces\r
+ private Map<String, Set<String>> subtypes; // type -> a set of sub types \r
+ private Set<String> requestedClasses; // a set of type names that are queried but not found\r
+\r
+ private static List<String> EMPTY = Collections.unmodifiableList(new ArrayList<String>(0)); \r
+ \r
+ /**\r
+ * Creates a new <code>ClassHierarchy</code> instance.\r
+ */\r
+ public ClassHierarchy() {\r
+ frozen = false;\r
+ \r
+ subtypes = new HashMap<String, Set<String>>();\r
+ parentClass = new HashMap<String, String>();\r
+ parentInterfaces = new HashMap<String, List<String>>();\r
+ entries = new HashMap<String, ClassInfo>();\r
+\r
+ requestedClasses = new HashSet<String>();\r
+\r
+ }\r
+ \r
+ /**\r
+ * Resolves dynamic binding of a method invocation. \r
+ * @param cs is a method invocation\r
+ * @return an array of <code>MethodInfo</code> objects representing methods \r
+ * that might be executed by the invocation.\r
+ * The return value is an empty array if no method declaration \r
+ * matched to the invocation.\r
+ */\r
+ @Override\r
+ public MethodInfo[] resolveCall(CallSite cs) {\r
+ return resolveCall(cs.getClassName(), cs.getMethodName(), cs.getDescriptor(), !cs.isStaticOrSpecial());\r
+ }\r
+ \r
+ /**\r
+ * Resolves dynamic binding of a method invocation.\r
+ * @param className is a class name\r
+ * @param methodName is a method name\r
+ * @param methodDesc is a method descriptor\r
+ * @param dynamic is true if the invoked method is bound dynamically\r
+ * @return an array of <code>MethodInfo</code> objects representing methods \r
+ * that might be executed by the invocation.\r
+ * The return value is an empty array if no method declaration \r
+ * matched to the invocation.\r
+ */\r
+ public MethodInfo[] resolveCall(String className, String methodName, String methodDesc, boolean dynamic) {\r
+ if (!dynamic) {\r
+ MethodInfo m = resolveSpecialCall(className, methodName, methodDesc);\r
+ return (m == null) ? new MethodInfo[0] : new MethodInfo[] {m};\r
+ } else {\r
+ return resolveDynamicCall(className, methodName, methodDesc);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Resolve dynamic binding of a virtual method call.\r
+ * Please note that this method does not care about the detail of the method definition.\r
+ * If a static method or an undefined private method is specified, this method may return incorrect result.\r
+ * @param className specifies a class representing a receiver type.\r
+ * @param methodName specifies a method name.\r
+ * @param methodDesc specifies a method descriptor (without generics information). \r
+ * @return MethodInfo object representing the specified method.\r
+ * The return value may be an emtpy if the method is not found.\r
+ */\r
+ private MethodInfo[] resolveDynamicCall(String className, String methodName, String methodDesc) {\r
+ assert className != null && methodName != null && methodDesc != null : "Parameters cannot be null."; \r
+ String targetTypeName = className;\r
+ \r
+ // Find the declaration of the called method.\r
+ MethodInfo topDecl = findDeclaration(className, methodName, methodDesc);\r
+ if (topDecl == null) return new MethodInfo[0]; // Not found\r
+ \r
+ List<MethodInfo> result = new ArrayList<>(16);\r
+ if (topDecl.hasMethodBody()) result.add(topDecl);\r
+ \r
+ \r
+ // We explicitly avoid array types, because arrays are not included in ClassHierarchy.\r
+ if (!isArrayType(targetTypeName)) {\r
+ // Find all implementation of the same method signature in subclasses.\r
+ Set<String> checkedClasses = new HashSet<String>();\r
+ Stack<String> classes = new Stack<String>();\r
+ classes.add(targetTypeName);\r
+ while (!classes.empty()) { \r
+ String currentClass = classes.pop();\r
+ \r
+ // skip the visited classes\r
+ if (checkedClasses.contains(currentClass)) {\r
+ continue;\r
+ }\r
+ checkedClasses.add(currentClass);\r
+ \r
+ ClassInfo currentClassInfo = getClassInfo(currentClass);\r
+ if (currentClassInfo != null) {\r
+ MethodInfo m = currentClassInfo.findMethod(methodName, methodDesc);\r
+ if (m != null) {\r
+ if (m.hasMethodBody() && (m != topDecl)) { \r
+ // m overrides the target class.\r
+ result.add(m); \r
+ }\r
+ }\r
+ \r
+ if ((m == null) || m.isOverridable()) { \r
+ // the method may be overridden by subclasses.\r
+ for (String c: getSubtypes(currentClass)) {\r
+ if (m != null && m.isPackagePrivate()) {\r
+ // A package-private method can be overridden by only classes in the same package.\r
+ if (isSamePackage(c, currentClass)) {\r
+ classes.push(c);\r
+ }\r
+ } else {\r
+ // Other methods can be overridden by sub-types.\r
+ classes.push(c);\r
+ }\r
+ }\r
+ }\r
+ } else {\r
+ // Skip a class that is not included in the class hierarchy.\r
+ }\r
+ }\r
+ \r
+ }\r
+ \r
+ MethodInfo[] resultArray = new MethodInfo[result.size()];\r
+ for (int i=0; i<result.size(); ++i) {\r
+ resultArray[i] = result.get(i);\r
+ }\r
+ return resultArray;\r
+ }\r
+ \r
+ /**\r
+ * Resolve binding of a static/constructor call.\r
+ * @param className specifies a class representing a receiver type.\r
+ * @param methodName specifies a method name.\r
+ * @param methodDesc specifies a method descriptor (without generics information). \r
+ * @return MethodInfo object representing the specified method.\r
+ * The return value may be null if the method is not found.\r
+ */\r
+ public MethodInfo resolveSpecialCall(String className, String methodName, String methodDesc) {\r
+ MethodInfo m = findDeclaration(className, methodName, methodDesc);\r
+ if (m != null) {\r
+ return m;\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Finds method declaration specified by typeName and signature.\r
+ * @param typeName is fully qualified domain name of a class/interface.\r
+ * @param signatureId identifies a method by its name and parameters. \r
+ * To obtain a method signature, use MethodUtil.getMethodSignature.\r
+ * @return MethodDecl object of the nearest ancestor (including the class specified by typeName) \r
+ * If no ancestor classes declare the method,\r
+ * MethodDecl comes from interfaces who declares the method.\r
+ * @throws ClassNotFoundException is thrown if typeName or its ancestors are not found in class hierarchy.\r
+ * @throws NoSuchMethodException is thrown if method is not found in ancestor classes and interfaces.\r
+ */\r
+ private MethodInfo findDeclaration(String className, String methodName, String methodDesc) {\r
+ // Find the nearest ancestor class implements the method\r
+ String currentClass = className;\r
+ while (currentClass != null) {\r
+ ClassInfo currentClassInfo = getClassInfo(currentClass);\r
+ if (currentClassInfo != null) {\r
+ MethodInfo m = currentClassInfo.findMethod(methodName, methodDesc);\r
+ if (m != null) {\r
+ return m;\r
+ } else {\r
+ currentClass = getSuperClass(currentClass);\r
+ }\r
+ } else {\r
+ if (isArrayType(currentClass)) { \r
+ currentClass = getSuperClass(currentClass);\r
+ continue;\r
+ }\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ // Search all interfaces\r
+ LinkedList<String> worklist = new LinkedList<String>();\r
+ currentClass = className;\r
+ while (currentClass != null) {\r
+ worklist.addAll(getSuperInterfaces(currentClass));\r
+ currentClass = getSuperClass(currentClass);\r
+ }\r
+ // Find a method declaration in the interfaces\r
+ while (!worklist.isEmpty()) {\r
+ String interfaceName = worklist.pollFirst();\r
+ ClassInfo currentClassInfo = getClassInfo(interfaceName);\r
+ if (currentClassInfo != null) {\r
+ MethodInfo m = currentClassInfo.findMethod(methodName, methodDesc);\r
+ if (m != null) {\r
+ return m;\r
+ } else {\r
+ // An interface may extend another interface.\r
+ worklist.addAll(getSuperInterfaces(interfaceName));\r
+ }\r
+ } else {\r
+ if (isArrayType(interfaceName)) { \r
+ // ignore array types\r
+ continue;\r
+ }\r
+ // Skip an interfaceName that is not included in the class hierarchy.\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ // Method not found at all\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * Finds a accessed <code>FieldInfo</code> object.\r
+ * @param access specifies a field access instruction.\r
+ * @return a <code>FieldInfo</code> object.\r
+ */\r
+ public FieldInfo resolveField(FieldAccess access) {\r
+ String owner = resolveFieldOwner(access);\r
+ return getClassInfo(owner).findField(access.getFieldName(), access.getDescriptor());\r
+ }\r
+\r
+ /**\r
+ * Finds a class which has a field to be accessed. \r
+ * @param access specifies a field access instruction.\r
+ * @return the name of a class which has a specified field.\r
+ */\r
+ private String resolveFieldOwner(FieldAccess access) {\r
+ if (access.isStatic()) {\r
+ return resolveStaticFieldOwner(access.getClassName(), access.getFieldName(), access.getDescriptor());\r
+ } else {\r
+ return resolveInstanceFieldOwner(access.getClassName(), access.getFieldName(), access.getDescriptor());\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Finds a class which has an instance field to be accessed. \r
+ * @param className specifies a class name in a field access instruction.\r
+ * The class may inherit a field from its parent.\r
+ * @param fieldName \r
+ * @param fieldDesc\r
+ * @return a class name that defines the field specified by the arguments.\r
+ * The method may return null if an owner is not found.\r
+ */\r
+ public String resolveInstanceFieldOwner(String className, String fieldName, String fieldDesc) {\r
+ String current = className;\r
+ while (current != null) {\r
+ ClassInfo c = getClassInfo(current);\r
+ if (c != null) {\r
+ if (c.findField(fieldName, fieldDesc) != null) {\r
+ return current;\r
+ } else {\r
+ current = c.getSuperClass();\r
+ }\r
+ } else {\r
+ // If a class is not registered, stop to resolve the owner.\r
+ current = null;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * Finds a class which has a static field to be accessed. \r
+ * @param className \r
+ * @param fieldName\r
+ * @param fieldDesc\r
+ * @return a class name.\r
+ * @see JVM Specification Section 5.4.3.2.\r
+ * If two public fields are accessible from the specified className and fieldName,\r
+ * javac reports the ambiguous field reference as an error.\r
+ */\r
+ public String resolveStaticFieldOwner(String className, String fieldName, String fieldDesc) {\r
+ // Search the current class\r
+ ClassInfo c = getClassInfo(className);\r
+ if (c == null) return null;\r
+ \r
+ if (c != null) {\r
+ if (c.findField(fieldName, fieldDesc) != null) {\r
+ return className;\r
+ }\r
+ }\r
+\r
+ // If not defined, search interface\r
+ Stack<String> worklist = new Stack<String>();\r
+ worklist.push(className);\r
+ while (!worklist.isEmpty()) {\r
+ String current = worklist.pop();\r
+ c = getClassInfo(current);\r
+ if (c != null) {\r
+ if (c.findField(fieldName, fieldDesc) != null) {\r
+ return current;\r
+ } else {\r
+ if (c.getInterfaces() != null) {\r
+ worklist.addAll(c.getInterfaces());\r
+ }\r
+ }\r
+ } else {\r
+ // If c is not a registered class, ignore it. \r
+ }\r
+ }\r
+ \r
+ // Recursively search a super class\r
+ c = getClassInfo(className);\r
+ if (c.getSuperClass() != null) return resolveStaticFieldOwner(c.getSuperClass(), fieldName, fieldDesc);\r
+ else return null;\r
+ }\r
+ \r
+ /**\r
+ * Prevents further modifications to the object.\r
+ */\r
+ public void freeze() {\r
+ assert !frozen: "ClassHierarchy is already frozen."; \r
+ frozen = true;\r
+ }\r
+ \r
+ /**\r
+ * @return true if this object is frozen. \r
+ */\r
+ public boolean isFrozen() {\r
+ return frozen;\r
+ }\r
+ \r
+ /**\r
+ * @return a set of class names which are requested \r
+ * by client methods, but not involved in this class hierarchy.\r
+ */\r
+ public Set<String> getRequestedClasses() {\r
+ return requestedClasses;\r
+ }\r
+ \r
+ /**\r
+ * @param className \r
+ * @return a <code>ClassInfo</code> object specified by the class name.\r
+ */\r
+ public ClassInfo getClassInfo(String className) {\r
+ ClassInfo c = entries.get(className);\r
+ if (c == null) {\r
+ requestedClasses.add(className); \r
+ }\r
+ return c;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of registered classes. \r
+ */\r
+ public int getClassCount() {\r
+ return entries.size();\r
+ }\r
+\r
+ /**\r
+ * @return the registered class names. \r
+ */\r
+ public Iterable<String> getClasses() {\r
+ return entries.keySet();\r
+ }\r
+ \r
+ /**\r
+ * Compare package names for the specified two types.\r
+ * @param typeName1 specifies a type to be compared.\r
+ * The type name must be registered to the class hierarchy. \r
+ * @param typeName2 also specifies a type to be comapred.\r
+ * @return true if the specified types belong to the same package.\r
+ */\r
+ public boolean isSamePackage(String typeName1, String typeName2) {\r
+ ClassInfo c1 = entries.get(typeName1);\r
+ ClassInfo c2 = entries.get(typeName2);\r
+ \r
+ if (c1 == null) requestedClasses.add(typeName1);\r
+ if (c2 == null) requestedClasses.add(typeName2);\r
+ \r
+ return (c1 != null)&&(c2 != null)&&(c1.getPackageName().equals(c2.getPackageName()));\r
+ }\r
+ \r
+ /**\r
+ * @param className specifies a class.\r
+ * @return a super class name for the specified class.\r
+ * This method returns null for "java/lang/Object". \r
+ * "java.lang.Object" is returned for an interface and an array type.\r
+ */\r
+ public String getSuperClass(String className) { \r
+ if (isArrayType(className)) return JAVA_LANG_OBJECT;\r
+ else {\r
+ if (!parentClass.containsKey(className)) {\r
+ requestedClasses.add(className);\r
+ }\r
+ return parentClass.get(className);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @param className specifies a fully qualified class name. \r
+ * @return interfaces implemented by the specified class.\r
+ * If the class has no interfaces, this method returns an empty collection.\r
+ * If the class is an interface and it extends another interface B,\r
+ * B is regarded as a super-interface of the class.\r
+ * If the specified class has no interfaces, \r
+ * the result is an empty colleciton.\r
+ */\r
+ public Collection<String> getSuperInterfaces(String className) {\r
+ if (isArrayType(className)) return EMPTY;\r
+ else if (parentInterfaces.containsKey(className)) { \r
+ return parentInterfaces.get(className);\r
+ } else {\r
+ if (!entries.containsKey(className)) requestedClasses.add(className);\r
+ return EMPTY;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * List all direct and transitive super-types of a specified type. \r
+ * @param className specifies a fully qualified class name. \r
+ */\r
+ public Collection<String> listAllSuperTypes(String className) {\r
+ if (!entries.containsKey(className)) requestedClasses.add(className);\r
+\r
+ Set<String> classes = new HashSet<String>();\r
+ Queue<String> worklist = new LinkedList<String>();\r
+ worklist.add(className);\r
+ while (!worklist.isEmpty()) {\r
+ String name = worklist.poll();\r
+ String superClass = getSuperClass(name);\r
+ if (superClass != null && !classes.contains(superClass)) {\r
+ classes.add(superClass);\r
+ worklist.add(superClass);\r
+ }\r
+ for (String s: getSuperInterfaces(name)) {\r
+ if (s != null && !classes.contains(s)) {\r
+ classes.add(s);\r
+ worklist.add(s);\r
+ }\r
+ }\r
+ }\r
+ return classes;\r
+ }\r
+ \r
+ /**\r
+ * @param typeName specifies a type name.\r
+ * @return true if the type name represents array types.\r
+ */\r
+ public boolean isArrayType(String typeName) { \r
+ return typeName.endsWith("[]");\r
+ }\r
+\r
+ /**\r
+ * @return a collection of classes which extend/implement \r
+ * the specified type.\r
+ * The result may be an empty collection.\r
+ */\r
+ public Collection<String> getSubtypes(String typeName) {\r
+ if (!entries.containsKey(typeName)) requestedClasses.add(typeName);\r
+\r
+ if (subtypes.containsKey(typeName)) { \r
+ return subtypes.get(typeName);\r
+ } else {\r
+ return EMPTY;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @param typeNames\r
+ * @return a collection of all the sub-types for the specified types.\r
+ */\r
+ public Collection<String> getAllSubtypes(Iterable<String> typeNames) {\r
+ Stack<String> worklist = new Stack<String>();\r
+ for (String t: typeNames) {\r
+ worklist.push(t);\r
+ }\r
+ \r
+ HashSet<String> visited = new HashSet<String>();\r
+ while (!worklist.isEmpty()) {\r
+ String t = worklist.pop();\r
+ if (visited.contains(t)) continue;\r
+ \r
+ visited.add(t);\r
+ worklist.addAll(getSubtypes(t));\r
+ }\r
+ return visited;\r
+ }\r
+\r
+ /**\r
+ * This method registers a class info object to the hierarchy.\r
+ * This method calls registerSuperClass, registerSubtype and registerInterfaces. \r
+ * @param c is a registered <code>ClassInfo</code> object.\r
+ */\r
+ public void registerClass(ClassInfo c) {\r
+ if (frozen) {\r
+ throw new FrozenHierarchyException();\r
+ }\r
+ entries.put(c.getClassName(), c);\r
+ registerSuperClass(c.getClassName(), c.getSuperClass());\r
+ registerSubtype(c.getClassName(), c.getSuperClass());\r
+ registerInterfaces(c.getClassName(), c.getInterfaces());\r
+ for (String interfaceName: c.getInterfaces()) {\r
+ registerSubtype(c.getClassName(), interfaceName);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * This method allows developers to manually modify the class hierarchy.\r
+ * @param current specifies a class name. \r
+ * @param parent specifies the super class name of the current class.\r
+ */\r
+ public void registerSuperClass(String current, String parent) {\r
+ if (frozen) {\r
+ throw new FrozenHierarchyException();\r
+ }\r
+ parentClass.put(current, parent);\r
+ \r
+ }\r
+\r
+ /**\r
+ * This method allows developers to manually modify the type hierarchy.\r
+ * A subtype of a class is a subclass.\r
+ * A subtype of an interface is an implementation class. \r
+ * @param typeName\r
+ * @param parentTypeName\r
+ */\r
+ public void registerSubtype(String typeName, String parentTypeName) {\r
+ if (frozen) {\r
+ throw new FrozenHierarchyException();\r
+ }\r
+ if (subtypes.containsKey(parentTypeName)) {\r
+ subtypes.get(parentTypeName).add(typeName);\r
+ } else {\r
+ HashSet<String> types = new HashSet<String>();\r
+ types.add(typeName);\r
+ subtypes.put(parentTypeName, types);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * This method allows developers to manually modify the type hierarchy.\r
+ * @param current\r
+ * @param interfaces\r
+ */\r
+ public void registerInterfaces(String current, List<String> interfaces) {\r
+ if (frozen) {\r
+ throw new FrozenHierarchyException();\r
+ }\r
+ parentInterfaces.put(current, interfaces);\r
+ }\r
+ \r
+ public class FrozenHierarchyException extends RuntimeException {\r
+ private static final long serialVersionUID = -8288161390304221032L;\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.ClassReader;\r
+import org.objectweb.asm.MethodVisitor;\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.commons.JSRInlinerAdapter;\r
+import org.objectweb.asm.tree.ClassNode;\r
+import org.objectweb.asm.tree.FieldNode;\r
+\r
+/**\r
+ * This class represents a java class.\r
+ */\r
+public class ClassInfo {\r
+\r
+ public static final String PACKAGE_SEPARATOR ="/";\r
+ private static final String DEFAULT_PACKAGE = "$default$";\r
+ public static final String LIBRARY_LABEL = "$library$";\r
+ \r
+ private String fileName;\r
+ private String sourceFileName;\r
+ private String packageName;\r
+ private String className;\r
+ private String md5hash;\r
+ private String label;\r
+ private List<MethodInfo> methods = new ArrayList<>();\r
+ private List<FieldInfo> fields = new ArrayList<>();\r
+ \r
+ private String superclassName;\r
+ private List<String> interfaceNames;\r
+ \r
+ /**\r
+ * Creates a new <code>ClassInfo</code> instance from a binary stream.\r
+ * @param fileName\r
+ * @param binaryStream specifies a stream of Java bytecode.\r
+ * @param loaderLabel specifies a label indicating a location/category for a class.\r
+ * @throws IOException\r
+ */\r
+ public ClassInfo(String fileName, InputStream binaryStream, String loaderLabel) throws IOException {\r
+ this(fileName, binaryStream);\r
+ this.label = loaderLabel;\r
+ }\r
+\r
+ /**\r
+ * Creates a new <code>ClassInfo</code> instance from a binary stream.\r
+ * @param fileName\r
+ * @param binaryStream specifies a stream of Java bytecode. \r
+ * Please note that the stream is not closed by the method; \r
+ * the callers must close the stream by themselves.\r
+ */\r
+ public ClassInfo(String fileName, InputStream binaryStream) throws IOException {\r
+ this.fileName = fileName; \r
+ byte[] bytes = readToEnd(binaryStream);\r
+ ClassReader cr1;\r
+ try {\r
+ cr1 = new ClassReader(bytes) {\r
+ /**\r
+ * This extension reduces the number of allocated strings.\r
+ * When reading SOBA and GNU Trove class files, 52MB of 72MB strings can be discarded.\r
+ */\r
+ @Override\r
+ public String readUTF8(int index, char[] buf) {\r
+ return super.readUTF8(index, buf);\r
+ }\r
+ \r
+ };\r
+ } catch (ArrayIndexOutOfBoundsException e) {\r
+ throw new ClassReadFailureException("ASM ClassReader cannot parse the bytecode. " + fileName + " " + e.getLocalizedMessage());\r
+ }\r
+ ClassNode classNode = new ClassNode(Opcodes.ASM5) {\r
+ @Override\r
+ public MethodVisitor visitMethod(int access, String name,\r
+ String desc, String signature, String[] exceptions) {\r
+ return new JSRInlinerAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc, signature, exceptions);\r
+ }\r
+ };\r
+ cr1.accept(classNode, 0);\r
+ this.className = classNode.name;\r
+\r
+ int pkgIndex = className.lastIndexOf(PACKAGE_SEPARATOR);\r
+ if (pkgIndex >= 0) {\r
+ packageName = className.substring(0, pkgIndex);\r
+ } else {\r
+ packageName = DEFAULT_PACKAGE;\r
+ }\r
+ \r
+ if (classNode.sourceFile != null) {\r
+ this.sourceFileName = getClassDirPath() + File.separator + classNode.sourceFile;\r
+ } else {\r
+ this.sourceFileName = null;\r
+ }\r
+\r
+ this.md5hash = MD5.getMD5(bytes);\r
+\r
+ classNode.methods.forEach(m -> methods.add(new MethodInfo(this, m)));\r
+ \r
+ for (int i=0; i<classNode.fields.size(); ++i) {\r
+ fields.add(new FieldInfo(this, (FieldNode)classNode.fields.get(i)));\r
+ }\r
+ superclassName = classNode.superName;\r
+ interfaceNames = new ArrayList<String>(classNode.interfaces.size());\r
+ for (int i=0; i<classNode.interfaces.size(); ++i) {\r
+ interfaceNames.add((String)classNode.interfaces.get(i));\r
+ }\r
+ }\r
+\r
+ private static byte[] readToEnd(InputStream stream) throws IOException {\r
+ byte[] buf = new byte[4096];\r
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();\r
+ int n;\r
+ while ((n = stream.read(buf, 0, buf.length)) > 0) {\r
+ buffer.write(buf, 0, n);\r
+ }\r
+ return buffer.toByteArray();\r
+ }\r
+\r
+ public static ClassInfo createLibraryClass(String fileName, InputStream binaryStream) throws IOException {\r
+ ClassInfo c = new ClassInfo(fileName, binaryStream);\r
+ c.label = LIBRARY_LABEL;\r
+ return c;\r
+ }\r
+ \r
+ /**\r
+ * @return a package name.\r
+ * A package name is separated by "/".\r
+ */\r
+ public String getPackageName() {\r
+ return packageName;\r
+ }\r
+\r
+ /**\r
+ * @return a class name with its package name.\r
+ * The class name is an internal representation; \r
+ * a package name is separated by "/".\r
+ */\r
+ public String getClassName() {\r
+ return className;\r
+ }\r
+\r
+ /**\r
+ * @return a super class name with its package name.\r
+ */\r
+ public String getSuperClass() {\r
+ return superclassName;\r
+ }\r
+ \r
+ /**\r
+ * @return a list of interface names this class implements.\r
+ */\r
+ public List<String> getInterfaces() {\r
+ return interfaceNames;\r
+ }\r
+ \r
+ /**\r
+ * @return a MD5 hash value.\r
+ */\r
+ public String getHash() {\r
+ return md5hash;\r
+ }\r
+\r
+ /**\r
+ * @return a directory path which has this class file.\r
+ */\r
+ public String getClassDirPath() {\r
+ return packageName.replace('/', File.separatorChar); \r
+ }\r
+\r
+ /**\r
+ * @return the file name whose containing the class data.\r
+ * If the class file is contained in a ZIP/JAR file,\r
+ * the resultant path incidates the zip file and an internal file path in the archive.\r
+ */\r
+ public String getClassFileName() {\r
+ return fileName;\r
+ }\r
+ \r
+ /**\r
+ * @return a label attached to this class.\r
+ * This is expected to distinguish a library class or an application class.\r
+ */\r
+ public String getLabel() {\r
+ return label;\r
+ }\r
+\r
+ /**\r
+ * @return true if this class is a library\r
+ */\r
+ public boolean isLibrary() {\r
+ return getLabel() == LIBRARY_LABEL;\r
+ }\r
+ \r
+ /**\r
+ * @return a Java source file name.\r
+ * The file name is a relative path.\r
+ * The method may return null. \r
+ */\r
+ public String getSourceFileName() {\r
+ return sourceFileName;\r
+ }\r
+\r
+ /**\r
+ * @return the number of methods declared in this class.\r
+ */\r
+ public int getMethodCount() {\r
+ return methods.size();\r
+ }\r
+\r
+ /**\r
+ * @param methodIndex\r
+ * @return a <code>MethodInfo</code> object specified by its index.\r
+ */\r
+ public MethodInfo getMethod(int methodIndex) {\r
+ return methods.get(methodIndex);\r
+ }\r
+\r
+ /**\r
+ * @return a list of <code>MethodInfo</code> objects declared in this class.\r
+ */\r
+ public List<MethodInfo> getMethods() {\r
+ return methods;\r
+ }\r
+\r
+ /**\r
+ * @param methodName is a method name.\r
+ * @param methodDesc is a method descriptor without generics information.\r
+ * @return a <code>MethodInfo</code> object if the class declares the specified method. \r
+ */\r
+ public MethodInfo findMethod(String methodName, String methodDesc) {\r
+ for (MethodInfo m: methods) {\r
+ if (m.getMethodName().equals(methodName) &&\r
+ m.getDescriptor().equals(methodDesc)) {\r
+ return m;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of fields declared in this class.\r
+ */\r
+ public int getFieldCount() {\r
+ return fields.size();\r
+ }\r
+\r
+ /**\r
+ * @param fieldIndex\r
+ * @return a <code>FieldInfo</code> object specified by its index.\r
+ */\r
+ public FieldInfo getField(int fieldIndex) {\r
+ return fields.get(fieldIndex);\r
+ }\r
+\r
+ /**\r
+ * @return a list of <code>FieldInfo</code> objects declared in this class.\r
+ */\r
+ public List<FieldInfo> getFields() {\r
+ return fields;\r
+ }\r
+ \r
+ /**\r
+ * @param fieldName is a field name.\r
+ * @param fieldDesc is a field descriptor without generics information.\r
+ * @return a <code>FieldInfo</code> object if the class declares the specified field.\r
+ */\r
+ public FieldInfo findField(String fieldName, String fieldDesc) {\r
+ for (FieldInfo f: fields) {\r
+ if (f.getFieldName().equals(fieldName) &&\r
+ f.getDescriptor().equals(fieldDesc)) {\r
+ return f;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import java.io.IOException;\r
+\r
+public class ClassReadFailureException extends IOException {\r
+\r
+ private static final long serialVersionUID = -5653771195879228138L;\r
+\r
+ public ClassReadFailureException(String message) {\r
+ super(message);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import org.objectweb.asm.tree.FieldNode;\r
+\r
+import soba.core.signature.TypeResolver;\r
+\r
+/**\r
+ * This class represents a field variable.\r
+ */\r
+public class FieldInfo {\r
+\r
+ private ClassInfo owner;\r
+ private FieldNode fieldNode;\r
+ private String typeName;\r
+ \r
+ /**\r
+ * Creates a new <code>FieldInfo</code> instance.\r
+ * @param owner is a <code>ClassInfo</code> object which declares this field.\r
+ * @param fieldNode\r
+ */\r
+ public FieldInfo(ClassInfo owner, FieldNode fieldNode) {\r
+ this.owner = owner;\r
+ this.fieldNode = fieldNode;\r
+ this.typeName = null;\r
+ }\r
+ \r
+ /**\r
+ * @return the package name who has the field.\r
+ */\r
+ public String getPackageName() {\r
+ return owner.getPackageName();\r
+ }\r
+ \r
+ /**\r
+ * @return the class name who has the field.\r
+ */\r
+ public String getClassName() {\r
+ return owner.getClassName();\r
+ }\r
+ \r
+ /**\r
+ * @return the field name.\r
+ */\r
+ public String getFieldName() {\r
+ return fieldNode.name;\r
+ }\r
+ \r
+ /**\r
+ * @return the descriptor of the field.\r
+ */\r
+ public String getDescriptor() {\r
+ return fieldNode.desc;\r
+ }\r
+ \r
+ /**\r
+ * @return the type name of the field.\r
+ */\r
+ public String getFieldTypeName() {\r
+ if (typeName == null) {\r
+ if (fieldNode.signature != null) {\r
+ typeName = TypeResolver.getTypeName(fieldNode.signature);\r
+ } else {\r
+ typeName = TypeResolver.getTypeName(fieldNode.desc);\r
+ }\r
+ }\r
+ return typeName;\r
+ }\r
+ \r
+ @Override\r
+ public String toString() {\r
+ StringBuilder builder = new StringBuilder();\r
+ builder.append(getClassName());\r
+ builder.append(".");\r
+ builder.append(getFieldName());\r
+ builder.append(": ");\r
+ builder.append(getFieldTypeName());\r
+ return builder.toString();\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+public interface IClassFilter {\r
+\r
+ /**\r
+ * This method determines whether JavaProgram load the class.\r
+ * @param dataName indicates a class file name (maybe in a zip file).\r
+ * @return true to make JavaProgram load the class.\r
+ */\r
+ public boolean loadClass(String dataName);\r
+ \r
+ /**\r
+ * This method determines whether JavaProgram registers the class \r
+ * to a class hierarchy object. \r
+ * @param c is a loaded ClassInfo object.\r
+ * @return true to make JavaProgram include the ClassInfo object.\r
+ */\r
+ public boolean acceptClass(ClassInfo c);\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import soba.core.method.CallSite;\r
+\r
+public interface IDynamicBindingResolver {\r
+\r
+ /**\r
+ * Resolves the dynamic binding of a method invocation.\r
+ * @param cs is a method invocation.\r
+ * @return an array of the methods which may be invoked.\r
+ */\r
+ public MethodInfo[] resolveCall(CallSite cs);\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.stream.Collectors;\r
+\r
+import soba.util.files.IClassList;\r
+import soba.util.files.IClassListCallback;\r
+\r
+\r
+/**\r
+ * This class represents a Java program.\r
+ */\r
+public class JavaProgram {\r
+ \r
+ private Map<String, ClassInfo> classes;\r
+ private ClassHierarchy classHierarchy;\r
+ private List<ClassInfo> loaded;\r
+ private List<ClassInfo> duplicated;\r
+ private List<String> filtered;\r
+ private List<ErrorMessage> errors;\r
+\r
+ /**\r
+ * Creates a new <code>JavaProgram</code> instance.\r
+ * @param lists\r
+ */\r
+ public JavaProgram(final IClassList[] lists) {\r
+ this(lists, null);\r
+ }\r
+ \r
+ /**\r
+ * Creates a new <code>JavaProgram</code> instance specifying classes to be analyzed.\r
+ * @param fileEnumeraters\r
+ * @param filter specifies classes to be analyzed.\r
+ */\r
+ public JavaProgram(final IClassList[] lists, final IClassFilter filter) {\r
+ classes = new HashMap<String, ClassInfo>(65536);\r
+ errors = new ArrayList<ErrorMessage>(1024);\r
+ loaded = new ArrayList<ClassInfo>(65536);\r
+ duplicated = new ArrayList<ClassInfo>(1024);\r
+ filtered = new ArrayList<String>(1024);\r
+ classHierarchy = new ClassHierarchy();\r
+ \r
+ for (final IClassList list: lists) {\r
+ if (list == null) continue;\r
+ \r
+ list.process(new IClassListCallback() {\r
+ \r
+ @Override\r
+ public boolean reportError(String name, Exception e) {\r
+ errors.add(new ErrorMessage(name, e));\r
+ return false;\r
+ }\r
+ \r
+ @Override\r
+ public void process(String name, InputStream stream) throws IOException {\r
+ if (filter == null || filter.loadClass(name)) {\r
+ ClassInfo c = new ClassInfo(name, stream, list.getLabel());\r
+ if (filter == null || filter.acceptClass(c)) {\r
+ if (!classes.containsKey(c.getClassName())) {\r
+ classes.put(c.getClassName(), c);\r
+ loaded.add(c);\r
+ classHierarchy.registerClass(c);\r
+ } else {\r
+ duplicated.add(c);\r
+ }\r
+ } else {\r
+ filtered.add(name);\r
+ }\r
+ } else {\r
+ filtered.add(name);\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public boolean isTarget(String name) {\r
+ return name.endsWith(".class");\r
+ }\r
+ });\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return a list of loaded <code>ClassInfo</code> objects.\r
+ */\r
+ public List<ClassInfo> getClasses() {\r
+ return loaded;\r
+ }\r
+ \r
+ /**\r
+ * @return a list of filtered classes.\r
+ */\r
+ public List<String> getFiltered() {\r
+ return filtered;\r
+ }\r
+ \r
+ /**\r
+ * @return a list of duplicated <code>ClassInfo</code> objects.\r
+ * If the analyzed files contain classes whose names are same\r
+ * (with their package names), this method returns a non-empty list. \r
+ */\r
+ public List<ClassInfo> getDuplicated() {\r
+ return duplicated;\r
+ }\r
+\r
+ /**\r
+ * @return a list of library classes.\r
+ */\r
+ public List<ClassInfo> getLibraryClasses() {\r
+ return classes.values().stream()\r
+ .filter(c -> c.isLibrary())\r
+ .collect(Collectors.toList());\r
+ }\r
+ \r
+ /**\r
+ * @param className is a class name including its package name.\r
+ * @return a <code>ClassInfo</code> object specified by its name.\r
+ */\r
+ public ClassInfo getClassInfo(String className) {\r
+ return classes.get(className);\r
+ }\r
+ \r
+ /**\r
+ * @return a <code>ClassHierarchy</code> object which has hierarchy information of the analyzed classes.\r
+ */\r
+ public ClassHierarchy getClassHierarchy() {\r
+ return classHierarchy;\r
+ }\r
+ \r
+ /**\r
+ * @return a list of error messages.\r
+ */\r
+ public List<ErrorMessage> getErrors() {\r
+ return errors;\r
+ }\r
+ \r
+ public static class ErrorMessage { \r
+ private String dataName;\r
+ private Exception exception;\r
+ public ErrorMessage(String name, Exception e) {\r
+ this.dataName = name;\r
+ this.exception = e;\r
+ }\r
+ public String getDataName() {\r
+ return dataName;\r
+ }\r
+ public Exception getException() {\r
+ return exception;\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+\r
+public class MD5 {\r
+\r
+ public static String getMD5(byte[] bytearray) {\r
+ try {\r
+ MessageDigest digest = MessageDigest.getInstance("MD5");\r
+ byte[] hash = digest.digest(bytearray);\r
+ return getString(hash);\r
+ } catch (NoSuchAlgorithmException e) {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ private static String getString(byte[] bytearray) {\r
+ StringBuilder b = new StringBuilder();\r
+ for (int i=0; i<bytearray.length; ++i) {\r
+ String s = "0" + Integer.toHexString(bytearray[i]);\r
+ b.append(s.substring(s.length()-2));\r
+ }\r
+ return b.toString();\r
+ }\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+import gnu.trove.set.TIntSet;\r
+import gnu.trove.set.hash.TIntHashSet;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.FieldInsnNode;\r
+import org.objectweb.asm.tree.LineNumberNode;\r
+import org.objectweb.asm.tree.LocalVariableNode;\r
+import org.objectweb.asm.tree.MethodInsnNode;\r
+import org.objectweb.asm.tree.MethodNode;\r
+import org.objectweb.asm.tree.analysis.AnalyzerException;\r
+\r
+import soba.core.method.CallSite;\r
+import soba.core.method.ControlDependence;\r
+import soba.core.method.DataDependence;\r
+import soba.core.method.FieldAccess;\r
+import soba.core.method.OpcodeString;\r
+import soba.core.method.asm.DataFlowAnalyzer;\r
+import soba.core.method.asm.DataFlowInterpreter;\r
+import soba.core.signature.MethodSignatureReader;\r
+import soba.util.ObjectIdMap;\r
+import soba.util.graph.DirectedGraph;\r
+\r
+/**\r
+ * This class represents a java method.\r
+ */\r
+public class MethodInfo {\r
+ \r
+ private ClassInfo ownerClass;\r
+ private MethodNode method;\r
+\r
+ private String returnType;\r
+ private String[] paramTypes;\r
+ private int[] paramIndex;\r
+ private int paramCount;\r
+ private boolean[] paramGeneric;\r
+ \r
+ private int[] lines;\r
+ private int maxLine;\r
+ private int minLine;\r
+ \r
+ private DataFlowAnalyzer analyzer;\r
+ private DataDependence dataDependence;\r
+\r
+ /**\r
+ * Creates a new <code>MethodInfo</code> instance.\r
+ * @param owner is a <code>ClassInfo</code> object which declares this method.\r
+ * @param method\r
+ */\r
+ public MethodInfo(ClassInfo owner, MethodNode method) {\r
+ this.ownerClass = owner;\r
+ this.method = method;\r
+ }\r
+ \r
+ /**\r
+ * @return the package name who has the method.\r
+ */\r
+ public String getPackageName() {\r
+ return ownerClass.getPackageName();\r
+ }\r
+\r
+ /**\r
+ * @return the class name who has the method.\r
+ */\r
+ public String getClassName() {\r
+ return ownerClass.getClassName();\r
+ }\r
+\r
+ /**\r
+ * @return a method name\r
+ */\r
+ public String getMethodName() {\r
+ return method.name;\r
+ }\r
+ \r
+ /**\r
+ * @return the descriptor of the method.\r
+ */\r
+ public String getDescriptor() {\r
+ return method.desc;\r
+ }\r
+ \r
+ /**\r
+ * @return a descriptor including generics information.\r
+ */\r
+ public String getGenericsSignature() {\r
+ return method.signature;\r
+ }\r
+\r
+ /**\r
+ * @return true if this method has a method body.\r
+ */\r
+ public boolean hasMethodBody() {\r
+ return (method.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if this is a library method.\r
+ */\r
+ public boolean isLibrary() {\r
+ return ownerClass.isLibrary();\r
+ }\r
+ \r
+ /**\r
+ * @return true if this method is declared as a static method.\r
+ */\r
+ public boolean isStatic() {\r
+ return (method.access & Opcodes.ACC_STATIC) != 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if this method is automatically generated by the compiler. \r
+ */\r
+ public boolean isSynthetic() {\r
+ return (method.access & Opcodes.ACC_SYNTHETIC) != 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if this method is declared as a public method.\r
+ */\r
+ public boolean isPublic() {\r
+ return (method.access & Opcodes.ACC_PUBLIC) != 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if this method is declared as a protected method.\r
+ */\r
+ public boolean isProtected() {\r
+ return (method.access & Opcodes.ACC_PROTECTED) != 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if this method is declared as a private method.\r
+ */\r
+ public boolean isPrivate() {\r
+ return (method.access & Opcodes.ACC_PRIVATE) != 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if this method can be accessed by the same package only.\r
+ */\r
+ public boolean isPackagePrivate() {\r
+ return (method.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0;\r
+ }\r
+ \r
+ /**\r
+ * @return true if the method may be overridden by a subclass.\r
+ * In other words, the method is a non-final, non-private instance method. \r
+ */\r
+ public boolean isOverridable() {\r
+ return (method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC)) == 0;\r
+ }\r
+\r
+ /**\r
+ * @return the number of bytecode instructions in this method.\r
+ */\r
+ public int getInstructionCount() {\r
+ return method.instructions.size();\r
+ }\r
+ \r
+ /**\r
+ * @param instructionIndex\r
+ * @return AbstractInsnNode of instructionIndex\r
+ */\r
+ public AbstractInsnNode getAbstractInsnNode(int instructionIndex) {\r
+ return method.instructions.get(instructionIndex);\r
+ }\r
+ \r
+ /**\r
+ * @return the return value type.\r
+ * The method may return a generic type name such as "T". \r
+ */\r
+ public String getReturnType() {\r
+ extractParametersIfNecessary();\r
+ return returnType;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of parameters of this method.\r
+ */\r
+ public int getParamCount() {\r
+ extractParametersIfNecessary();\r
+ return paramCount;\r
+ }\r
+ \r
+ /**\r
+ * @return the index value of a receiver object.\r
+ */\r
+ public int getReceiverObjectParamIndex() {\r
+ assert !isStatic();\r
+ return 0; // "this" is always the first argument even if a method is invoked for an inner/anonymous class.\r
+ }\r
+ \r
+ /**\r
+ * This method translates "N-th" parameter to an index value for the local variable table.\r
+ * This method is required because a double-word (long, double) variable \r
+ * occupies two words in a local variable table. \r
+ * @param paramIndex specifies the position of a parameter.\r
+ * E.g. 0, 1, 2, ... indicate the first, the second, the third, ... parameters. \r
+ * @return index value to accesss local variable table.\r
+ */\r
+ public int getVariableTableIndexOfParamAt(int index) {\r
+ return this.paramIndex[index];\r
+ }\r
+ \r
+ /**\r
+ * This method is reverse of getVariableTableIndexOfParamAt.\r
+ * @return a position of the parameter corresponding to an index value for the local variable table.\r
+ */\r
+ public int getParameterOrderingNumber(int localVarialbleIndex) {\r
+ for (int p = 0; p < this.paramCount; p++) {\r
+ if (paramIndex[p] == localVarialbleIndex) {\r
+ return p;\r
+ }\r
+ }\r
+ throw new IllegalArgumentException("getParameterOrderingNumber:" + localVarialbleIndex + " is not Parameter");\r
+ }\r
+ \r
+ /**\r
+ * @param localVarialbleIndex specifies an index value for the local variable table.\r
+ * @return true if a local variable specified by the index value is a parameter of this method.\r
+ */\r
+ public boolean isParameterOrderingNumber(int localVarialbleIndex) {\r
+ for (int p = 0; p < this.paramCount; p++) {\r
+ if (paramIndex[p] == localVarialbleIndex) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * @param index specifies the position of a parameter.\r
+ * @return a type name for the parameter. \r
+ * A class name is fully qualified.\r
+ * The name may be a generic type parameter such as "T".\r
+ * An inner class name is concatinated by ".". \r
+ * For example, "A.B" is returned for a type "A<T>.B". \r
+ */\r
+ public String getParamType(int index) {\r
+ extractParametersIfNecessary();\r
+ if (index >= paramTypes.length) return null;\r
+ return paramTypes[index];\r
+ }\r
+ \r
+ /**\r
+ * @param index specifies the position of a parameter.\r
+ * @return a formal parameter name. \r
+ */\r
+ public String getParamName(int index) {\r
+ if (method.localVariables == null) return null;\r
+ if (index >= method.localVariables.size()) return null;\r
+ int paramIndex = getVariableTableIndexOfParamAt(index);\r
+ for (int i=0; i<method.localVariables.size(); ++i) {\r
+ LocalVariableNode var = (LocalVariableNode)method.localVariables.get(i);\r
+ if (var.index == paramIndex && var.start == method.instructions.getFirst()) {\r
+ return var.name;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ private void extractParametersIfNecessary() {\r
+ if (paramTypes != null) return;\r
+ \r
+ MethodSignatureReader reader = new MethodSignatureReader(method.desc);\r
+ int thisParam = isStatic() ? 0: 1; \r
+ \r
+ // read type names\r
+ this.returnType = reader.getReturnType();\r
+ this.paramCount = reader.getParamCount() + thisParam;\r
+ String[] params = new String[this.paramCount];\r
+ if (!isStatic()) {\r
+ if (getClassName() != null) {\r
+ params[0] = getClassName();\r
+ } else {\r
+ params[0] = "(Owner-Class)";\r
+ }\r
+ }\r
+ for (int i=0; i<reader.getParamCount(); ++i) {\r
+ params[i+thisParam] = reader.getParamType(i);\r
+ }\r
+ \r
+ // read generics flag\r
+ paramGeneric = new boolean[this.paramCount];\r
+ for (int i=0; i<reader.getParamCount(); ++i) {\r
+ paramGeneric[i+thisParam] = reader.isGenericType(i);\r
+ }\r
+ \r
+ // compute index for local variable table\r
+ this.paramIndex = new int[params.length];\r
+ int index = 0;\r
+ for (int i=0; i<params.length; ++i) {\r
+ paramIndex[i] = index;\r
+ if (params[i].equals("double") || params[i].equals("long")) {\r
+ // Double and Long are double word values.\r
+ index += 2;\r
+ } else {\r
+ index += 1;\r
+ }\r
+ }\r
+ \r
+ // finished\r
+ this.paramTypes = params;\r
+ }\r
+\r
+ /**\r
+ * @return a method node.\r
+ */\r
+ public MethodNode getMethodNode() {\r
+ return method;\r
+ }\r
+ \r
+ private void computeMinMaxLine() {\r
+ if (lines == null) {\r
+ TIntHashSet array = new TIntHashSet(method.instructions.size());\r
+ for (int i=0; i<method.instructions.size(); ++i) {\r
+ if (method.instructions.get(i).getType() == AbstractInsnNode.LINE) {\r
+ LineNumberNode node = (LineNumberNode)method.instructions.get(i);\r
+ array.add(node.line);\r
+ }\r
+ }\r
+ if (array.isEmpty()) {\r
+ lines = new int[0];\r
+ maxLine = 0;\r
+ minLine = 0;\r
+ } else {\r
+ lines = array.toArray();\r
+ Arrays.sort(lines);\r
+ maxLine = lines[lines.length - 1];\r
+ minLine = lines[0];\r
+ }\r
+ }\r
+ }\r
+ \r
+ \r
+ /**\r
+ * @return the maximum line including an instruction of the method.\r
+ * 0 indicates the method has no line number information.\r
+ */\r
+ public int getMaxLine() {\r
+ computeMinMaxLine();\r
+ return maxLine;\r
+ }\r
+ \r
+ /**\r
+ * @return the minimum line including an instruction of the method.\r
+ * 0 indicates the method has no line number information.\r
+ */\r
+ public int getMinLine() {\r
+ computeMinMaxLine();\r
+ return minLine;\r
+ }\r
+ \r
+ /**\r
+ * @return an array which is filled with the numbers from minimum line to maximum line.\r
+ */\r
+ public int[] getLineNumbers() {\r
+ computeMinMaxLine();\r
+ return lines;\r
+ }\r
+\r
+ /**\r
+ * @param instructionIndex\r
+ * @return the line number including a specified instruction. \r
+ */\r
+ public int getLine(int instructionIndex) {\r
+ for (int i=instructionIndex; i>=0; --i) {\r
+ if (method.instructions.get(i).getType() == AbstractInsnNode.LINE) {\r
+ return ((LineNumberNode)method.instructions.get(i)).line;\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+ \r
+ /**\r
+ * @param line\r
+ * @return an array of instruction index values that consist of a specified line.\r
+ */\r
+ public int[] getInstructions(int line) {\r
+ TIntArrayList lineInstructions = new TIntArrayList();\r
+ boolean inside = false;\r
+ for (int i=0; i<method.instructions.size(); ++i) {\r
+ if (method.instructions.get(i).getType() == AbstractInsnNode.LINE) {\r
+ inside = ((LineNumberNode)method.instructions.get(i)).line == line;\r
+ }\r
+ if (inside) lineInstructions.add(i);\r
+ }\r
+ return lineInstructions.toArray();\r
+ }\r
+\r
+ /**\r
+ * Returns a list of invocations in the method body.\r
+ * @return a list of <code>CallSite</code>.\r
+ */\r
+ public List<CallSite> getCallSites() {\r
+ List<CallSite> callsites = new ArrayList<CallSite>(method.instructions.size());\r
+ for (int i=0; i<method.instructions.size(); ++i) {\r
+ CallSite c = getCallSite(i);\r
+ if (c != null) callsites.add(c);\r
+ }\r
+ return callsites;\r
+ }\r
+ \r
+ /**\r
+ * Returns an invocation in a method call instruction. \r
+ * If the instruction is not a method call, this method returns null.\r
+ * @param instructionIndex\r
+ * @return a <code>CallSite</code> object for an instruction.\r
+ */\r
+ public CallSite getCallSite(final int instructionIndex) {\r
+ if (method.instructions.get(instructionIndex).getType() == AbstractInsnNode.METHOD_INSN) {\r
+ MethodInsnNode m = (MethodInsnNode)method.instructions.get(instructionIndex);\r
+ return new CallSite(this, instructionIndex, m.owner, m.name, m.desc, getInvokeType(m));\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ private CallSite.Kind getInvokeType(MethodInsnNode m) {\r
+ CallSite.Kind k = CallSite.Kind.VIRTUAL;\r
+ if (m.getOpcode() == Opcodes.INVOKESTATIC) k = CallSite.Kind.STATIC;\r
+ else if (m.getOpcode() == Opcodes.INVOKESPECIAL) k = CallSite.Kind.SPECIAL;\r
+ return k;\r
+ }\r
+ \r
+ /**\r
+ * Returns a list of field accesses in the method body.\r
+ * @return a list of <code>FieldAccess</code>.\r
+ */\r
+ public List<FieldAccess> getFieldAccesses() {\r
+ List<FieldAccess> fields = new ArrayList<FieldAccess>(32);\r
+ for (int i=0; i<method.instructions.size(); ++i) {\r
+ if (method.instructions.get(i).getType() == AbstractInsnNode.FIELD_INSN) {\r
+ FieldAccess fieldAccess = getFieldAccess(i);\r
+ if (fieldAccess != null) { \r
+ fields.add(fieldAccess);\r
+ }\r
+ }\r
+ }\r
+ return fields;\r
+ }\r
+ \r
+ /**\r
+ * Returns a field access in an instruction.\r
+ * If the instruction is not a field access, this method returns null.\r
+ * @param instructionIndex\r
+ * @return a <FieldAccess> object.\r
+ */\r
+ public FieldAccess getFieldAccess(final int instructionIndex) {\r
+ assert method.instructions.get(instructionIndex).getType() == AbstractInsnNode.FIELD_INSN;\r
+ \r
+ final FieldInsnNode f = (FieldInsnNode)method.instructions.get(instructionIndex);\r
+ switch (f.getOpcode()) {\r
+ case Opcodes.PUTFIELD:\r
+ return FieldAccess.createPutField(f.owner, f.name, f.desc, false);\r
+ case Opcodes.PUTSTATIC:\r
+ return FieldAccess.createPutField(f.owner, f.name, f.desc, true);\r
+ case Opcodes.GETFIELD:\r
+ return FieldAccess.createGetField(f.owner, f.name, f.desc, false);\r
+ case Opcodes.GETSTATIC:\r
+ return FieldAccess.createGetField(f.owner, f.name, f.desc, true);\r
+ default:\r
+ assert false: "Unknown Field Operation Found.";\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * @return an array of index values for return instructions.\r
+ */\r
+ public int[] getReturnInstructions() {\r
+ TIntSet returns = new TIntHashSet();\r
+ for (int i=0; i<method.instructions.size(); i++) {\r
+ AbstractInsnNode ain = method.instructions.get(i);\r
+ if (OpcodeString.isReturnOperation(ain)) {\r
+ returns.add(i);\r
+ }\r
+ }\r
+ return returns.toArray();\r
+ }\r
+\r
+ /**\r
+ * @return a <code>DataDependence</code> object.\r
+ * The object has information about data dependencies.\r
+ */\r
+ public DataDependence getDataDependence() {\r
+ computeFlow();\r
+ return dataDependence;\r
+ }\r
+ \r
+ /**\r
+ * @return a control dependence graph.\r
+ */\r
+ public DirectedGraph getControlDependence() {\r
+ return ControlDependence.getDependence(getInstructionCount(), getControlFlow());\r
+ }\r
+ \r
+ /**\r
+ * @return a control-flow graph.\r
+ */\r
+ public DirectedGraph getControlFlow() {\r
+ computeFlow();\r
+ return new DirectedGraph(getInstructionCount(), analyzer.getNormalControlFlow());\r
+ }\r
+ \r
+ /**\r
+ * @return a conservative control-flow graph.\r
+ * The graph assumes that every instruction in a try block may throw an exception.\r
+ */\r
+ public DirectedGraph getConservativeControlFlow() {\r
+ computeFlow();\r
+ return new DirectedGraph(getInstructionCount(), analyzer.getConservativeControlFlow());\r
+ }\r
+ \r
+ private void computeFlow() {\r
+ if (analyzer == null) {\r
+ ObjectIdMap<AbstractInsnNode> instructions = new ObjectIdMap<AbstractInsnNode>(method.instructions.size());\r
+ for (int i=0; i<method.instructions.size(); ++i) {\r
+ instructions.add(method.instructions.get(i));\r
+ }\r
+ instructions.freeze();\r
+ DataFlowInterpreter interpreter = new DataFlowInterpreter(instructions);\r
+ analyzer = new DataFlowAnalyzer(interpreter);\r
+ try {\r
+ analyzer.analyze(method.name, method);\r
+ dataDependence = new DataDependence(instructions, analyzer);\r
+ } catch (AnalyzerException e) {\r
+ System.err.println(e.getMessage());\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return a unique string that specifies the method declaration. \r
+ */\r
+ public String getMethodKey() {\r
+ return getClassName() + "#" + getMethodName() + "#" + getDescriptor();\r
+ }\r
+\r
+ /**\r
+ * @param instructionIndex\r
+ * @return a string representation of the specified instruction.\r
+ */\r
+ public String getInstructionString(final int instructionIndex) {\r
+ return OpcodeString.getInstructionString(method, instructionIndex);\r
+ }\r
+ \r
+ /**\r
+ * @return a shorter string representation of the method signature.\r
+ */\r
+ public String toString() {\r
+ return getMethodName() + getDescriptor();\r
+ }\r
+\r
+ /**\r
+ * @return a longer string representation of the method signature.\r
+ */\r
+ public String toLongString() {\r
+ StringBuilder name = new StringBuilder();\r
+ if (getClassName() != null) {\r
+ name.append(getClassName());\r
+ name.append(".");\r
+ }\r
+ name.append(getMethodName());\r
+ name.append("(");\r
+ for (int i=0; i<getParamCount(); ++i) {\r
+ if (i>0) name.append(", ");\r
+ name.append(getParamType(i));\r
+ String paramName = getParamName(i);\r
+ if (paramName != null) {\r
+ name.append(":");\r
+ name.append(paramName);\r
+ }\r
+ }\r
+ name.append(")");\r
+ name.append(": ");\r
+ name.append(getReturnType());\r
+ return name.toString();\r
+ }\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import soba.core.MethodInfo;\r
+\r
+/**\r
+ * This class has information about a call site in a method body.\r
+ */\r
+public class CallSite {\r
+\r
+ public enum Kind { STATIC, SPECIAL, VIRTUAL };\r
+ \r
+ private MethodInfo ownerMethod;\r
+ private int instructionIndex;\r
+ private String className;\r
+ private String methodName;\r
+ private String methodDesc;\r
+ private Kind invokeType;\r
+ \r
+ /**\r
+ * Creates a new <code>CallSite</code> instance.\r
+ * @param m specifies an owner method of a call site.\r
+ * @param instIndex specifies an instruction.\r
+ * @param className is a class name of a callee.\r
+ * @param methodName is a method name of a callee.\r
+ * @param methodDesc is a method descriptor of a callee.\r
+ * @param kind is an invocation kind.\r
+ */\r
+ public CallSite(MethodInfo m, int instIndex, String className, String methodName, String methodDesc, Kind kind) {\r
+ this.ownerMethod = m;\r
+ this.instructionIndex = instIndex;\r
+ this.className = className;\r
+ this.methodName = methodName;\r
+ this.methodDesc = methodDesc;\r
+ this.invokeType = kind;\r
+ }\r
+\r
+ /**\r
+ * @return a <code>MethodInfo</code> object which is owner of the call site.\r
+ */\r
+ public MethodInfo getOwnerMethod() {\r
+ return ownerMethod;\r
+ }\r
+ \r
+ /**\r
+ * @return an index value of the call site in the method body instructions.\r
+ */\r
+ public int getInstructionIndex() {\r
+ return instructionIndex;\r
+ }\r
+ \r
+ /**\r
+ * @return true if the method is NOT a virtual method call.\r
+ * In other words, the method to be invoked is declared as static\r
+ * or a certain implementation is specified by the invocation, \r
+ * e.g. "super.m()". \r
+ */\r
+ public boolean isStaticOrSpecial() {\r
+ return invokeType != Kind.VIRTUAL;\r
+ }\r
+ \r
+ /**\r
+ * @return true if the invocation calls a static method.\r
+ */\r
+ public boolean isStaticMethod() {\r
+ return invokeType == Kind.STATIC;\r
+ }\r
+ \r
+ /**\r
+ * @return the class name of the callee.\r
+ */\r
+ public String getClassName() {\r
+ return className;\r
+ }\r
+ \r
+ /**\r
+ * @return the method name of the callee.\r
+ */\r
+ public String getMethodName() {\r
+ return methodName;\r
+ }\r
+ \r
+ /**\r
+ * @return the method descriptor of the callee.\r
+ */\r
+ public String getDescriptor() {\r
+ return methodDesc;\r
+ }\r
+ \r
+ @Override\r
+ public String toString() {\r
+ StringBuilder sb = new StringBuilder();\r
+ sb.append(className);\r
+ sb.append(".");\r
+ sb.append(methodName);\r
+ sb.append(methodDesc);\r
+ sb.append(" called by ");\r
+ sb.append(ownerMethod.toLongString());\r
+ return sb.toString();\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import java.util.Arrays;\r
+\r
+import soba.util.IntPairList;\r
+import soba.util.IntPairProc;\r
+import soba.util.IntPairSet;\r
+import soba.util.graph.DepthFirstSearch;\r
+import soba.util.graph.DirectedGraph;\r
+import soba.util.graph.DominanceTree;\r
+import soba.util.graph.IDepthFirstVisitor;\r
+import soba.util.graph.IDirectedGraph;\r
+import soba.util.graph.SingleRootDirectedGraph;\r
+\r
+public class ControlDependence {\r
+\r
+ \r
+ /**\r
+ * @param instructionCount is the number of instructions.\r
+ * @param controlFlowGraph is a directed graph representing control-flow among instructions.\r
+ * @return a directed graph which represents control dependencies.\r
+ * The graph should be a "regular" control-flow graph excluding exceptional control-flow paths.\r
+ * Note: This graph does not contain dependencies from the method entry.\r
+ */\r
+ public static DirectedGraph getDependence(final int instructionCount, final DirectedGraph controlFlowGraph) {\r
+ IDirectedGraph reverseControlFlow = controlFlowGraph.getReverseGraph();\r
+ SingleRootDirectedGraph rootGraph = new SingleRootDirectedGraph(reverseControlFlow);\r
+ DominanceTree tree = new DominanceTree(rootGraph);\r
+\r
+ final IntPairList controlDependenceCandidate = new IntPairList();\r
+ for (int i=0; i<instructionCount; ++i) {\r
+ if (controlFlowGraph.getEdges(i).length > 1) { // is branch\r
+ final int postDom = tree.getDominator(i); // post dominator\r
+ DepthFirstSearch.search(controlFlowGraph, i, new IDepthFirstVisitor() {\r
+ \r
+ private int start; \r
+ @Override\r
+ public void onStart(int startVertexId) {\r
+ this.start = startVertexId;\r
+ }\r
+\r
+ @Override\r
+ public boolean onVisit(int vertexId) {\r
+ if (start != vertexId && vertexId != postDom) {\r
+ controlDependenceCandidate.add(start, vertexId);\r
+ }\r
+ return vertexId != postDom;\r
+ }\r
+ @Override\r
+ public void onVisitAgain(int vertexId) {\r
+ }\r
+ \r
+ @Override\r
+ public void onLeave(int vertexId) {\r
+ }\r
+ \r
+ @Override\r
+ public void onFinished(boolean[] visited) {\r
+ }\r
+ });\r
+ }\r
+ }\r
+ \r
+ // Removing redundant edges: if A->B and B->C, then A->C is redundant. If A->B and B->A, both A->C and B->C are not redundant. \r
+ DirectedGraph candidate = new DirectedGraph(instructionCount, controlDependenceCandidate);\r
+ final IntPairSet redundantEdges = new IntPairSet();\r
+ for (int src=0; src<instructionCount; ++src) {\r
+ for (int v: candidate.getEdges(src)) {\r
+ if (controlFlowGraph.getEdges(v).length > 1) { // v is a branch vertex\r
+ int[] edges = candidate.getEdges(v);\r
+ if (Arrays.binarySearch(edges, src) < 0) { // if not src->v and v->src\r
+ for (int d: candidate.getEdges(v)) { \r
+ redundantEdges.add(src, d);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ final IntPairList controlDependence = new IntPairList();\r
+ controlDependenceCandidate.foreach(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ if (!redundantEdges.contains(elem1, elem2)) { \r
+ controlDependence.add(elem1, elem2);\r
+ }\r
+ return true;\r
+ }\r
+ });\r
+ return new DirectedGraph(instructionCount, controlDependence);\r
+\r
+ }\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.analysis.Frame;\r
+\r
+import soba.core.method.asm.DataFlowAnalyzer;\r
+import soba.core.method.asm.FastSourceInterpreter;\r
+import soba.core.method.asm.FastSourceValue;\r
+import soba.util.IntPairList;\r
+import soba.util.ObjectIdMap;\r
+import soba.util.graph.DirectedGraph;\r
+\r
+/**\r
+ * This class has data dependence information in a single method.\r
+ */\r
+public class DataDependence {\r
+\r
+ private ObjectIdMap<AbstractInsnNode> instructions;\r
+ private DataFlowAnalyzer analyzer;\r
+ private LocalVariables locals;\r
+ \r
+ private List<DataFlowEdge> dataFlowEdges;\r
+ private List<DataFlowEdge> dataFlowEdgesSourceOrder;\r
+ \r
+ /**\r
+ * Creates a new <code>DataDependence</code> instance.\r
+ * @param instructions are instructions in the method.\r
+ * @param analyzer\r
+ */\r
+ public DataDependence(ObjectIdMap<AbstractInsnNode> instructions, DataFlowAnalyzer analyzer) {\r
+ this.instructions = instructions;\r
+ this.analyzer = analyzer;\r
+ computeEdges();\r
+ }\r
+ \r
+ /**\r
+ * Returns a graph representing data-dependencies in a single method\r
+ * Note: This graph does not contain data dependence edges from formal parameters of the method.\r
+ */\r
+ public DirectedGraph getDependenceGraph() {\r
+ IntPairList edges = new IntPairList(dataFlowEdges.size());\r
+ for (DataFlowEdge e: dataFlowEdges) {\r
+ if (e.getSourceInstruction() != FastSourceInterpreter.METHOD_ENTRY) {\r
+ edges.add(e.getSourceInstruction(), e.getDestinationInstruction());\r
+ }\r
+ }\r
+ return new DirectedGraph(instructions.size(), edges);\r
+ }\r
+ \r
+ /**\r
+ * @return a list of data flow edges.\r
+ * The edges are sorted by their destination instructions.\r
+ */\r
+ public List<DataFlowEdge> getEdges() {\r
+ return dataFlowEdges;\r
+ }\r
+ \r
+ /**\r
+ * @return a list of data flow edges.\r
+ * The edges are sorted by their source instructions.\r
+ */\r
+ public List<DataFlowEdge> getEdgesInSourceOrder() {\r
+ return dataFlowEdgesSourceOrder;\r
+ }\r
+ \r
+ /**\r
+ * @return a <code>LocalVariables</code> object.\r
+ */\r
+ public LocalVariables getLocalVariables() {\r
+ if (locals == null) {\r
+ locals = new LocalVariables(this, analyzer.getAnalyzedMethod());\r
+ }\r
+ return locals;\r
+ }\r
+ \r
+ public String getVariableName(DataFlowEdge e) {\r
+ LocalVariables locals = getLocalVariables();\r
+ if (e.isLocal()) {\r
+ if (e.getSourceInstruction() != FastSourceInterpreter.METHOD_ENTRY) {\r
+ int sourceIndex = locals.findEntryForInstruction(e.getSourceInstruction());\r
+ String sourceName = locals.getVariableName(sourceIndex);\r
+ if (sourceName != null) {\r
+ return sourceName;\r
+ }\r
+ }\r
+ int destinationIndex = locals.findEntryForInstruction(e.getDestinationInstruction());\r
+ String destinationName = locals.getVariableName(destinationIndex);\r
+ if (destinationName == null) {\r
+ return e.getVariableIndex() + "_unavailable";\r
+ }\r
+ return destinationName;\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ public String getVariableDescriptor(DataFlowEdge e) {\r
+ LocalVariables locals = getLocalVariables();\r
+ if (e.isLocal()) {\r
+ if (e.getSourceInstruction() != FastSourceInterpreter.METHOD_ENTRY) {\r
+ int sourceIndex = locals.findEntryForInstruction(e.getSourceInstruction());\r
+ String sourceDescriptor = locals.getDescriptor(sourceIndex);\r
+ if (sourceDescriptor != null) {\r
+ return sourceDescriptor;\r
+ }\r
+ }\r
+ int destinationIndex = locals.findEntryForInstruction(e.getDestinationInstruction());\r
+ String destinationDescriptor = locals.getDescriptor(destinationIndex);\r
+ if (destinationDescriptor == null) {\r
+ return "null";\r
+ }\r
+ return destinationDescriptor;\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+// /**\r
+// * Return a local variable corresponding to a data-flow edge.\r
+// * @param e specifies a data-flow edge.\r
+// * The edge must be returned by the same DataFlowInfo object.\r
+// * @return a local variable information.\r
+// * If the edge is caused by an operand stack, null is returned.\r
+// */\r
+// public ILocalVariableInfo getLocalVariable(DataFlowEdge e) {\r
+// if (e.isLocal()) {\r
+// ILocalVariableInfo v1;\r
+// if (e.getSourceInstruction() == FastSourceInterpreter.METHOD_ENTRY) {\r
+// locals.getVariableName(e.getVariableIndex());\r
+// v1 = localVariables.getParam(e.getVariableIndex());\r
+// } else {\r
+// v1 = localVariables.getAccessedVariable(e.getSourceInstruction());\r
+// }\r
+// if (v1 != null && !v1.isAnonymous()) {\r
+// return v1;\r
+// }\r
+// ILocalVariableInfo v2 = localVariables.getAccessedVariable(e.getDestinationInstruction());\r
+// if (v2 != null && !v2.isAnonymous() || v1 == null) {\r
+// return v2;\r
+// }\r
+// // v1.isAnonymous AND (v2 == null OR v2.isAnonymous)\r
+// return v1;\r
+// } else {\r
+// return null;\r
+// }\r
+// }\r
+ \r
+ /**\r
+ * Returns a list of data-definition vertices for each operand used by the specified instruction.\r
+ * @param instructionIndex specifies an instruction using an operand stack.\r
+ * @return a two-dimensional array.\r
+ * array[operandIndex] indicates a list of instructions that defined the operand.\r
+ * The result is consistent with a return value of getEdges().\r
+ */\r
+ public int[][] getDataDefinition(int instructionIndex) {\r
+ if (useStack(instructionIndex)) {\r
+ int operands = analyzer.getOperandCount(instructionIndex);\r
+ int[][] operandDef = new int[operands][];\r
+ for (int i=0; i<operands; ++i) {\r
+ Frame<?> f = analyzer.getFrames()[instructionIndex];\r
+ FastSourceValue value = (FastSourceValue)f.getStack(f.getStackSize() - operands + i);\r
+ operandDef[i] = value.getInstructions();\r
+ }\r
+ return operandDef;\r
+ } else {\r
+ AbstractInsnNode to = instructions.getItem(instructionIndex);\r
+ if (referLocal(instructionIndex)) {\r
+ int localIndex = OpcodeString.getVarIndex(to);\r
+ Frame<?> f = analyzer.getFrames()[instructionIndex];\r
+ if (f != null) {\r
+ FastSourceValue value = (FastSourceValue)f.getLocal(localIndex);\r
+ int[][] localDef = new int[1][];\r
+ localDef[0] = value.getInstructions();\r
+ return localDef;\r
+ } else {\r
+ // To avoid a problem caused by certain methods including many JSRs\r
+ return new int[0][0];\r
+ }\r
+ }\r
+ }\r
+ return new int[0][];\r
+ }\r
+ \r
+ /**\r
+ * @param destinationInstruction is an instruction index value.\r
+ * @return a list of data flow edges which destination is specified.\r
+ */\r
+ public List<DataFlowEdge> getIncomingEdges(final int destinationInstruction) {\r
+ List<DataFlowEdge> edges = new ArrayList<>();\r
+ for (DataFlowEdge e: dataFlowEdges) {\r
+ if (e.getDestinationInstruction() == destinationInstruction) {\r
+ edges.add(e);\r
+ }\r
+ }\r
+ return edges;\r
+ }\r
+ \r
+ /**\r
+ * @param destinationInstruction is an instruction index value.\r
+ * @param operandIndex is an operand index value in the instruction.\r
+ * @return a data flow edge which destination and operandIndex is specified.\r
+ * (Assumed that this incoming edge is just only one)\r
+ */\r
+ public DataFlowEdge getIncomingEdge(final int destinationInstruction, final int operandIndex) {\r
+ for (final DataFlowEdge dfe : dataFlowEdges) {\r
+ if (dfe.getDestinationInstruction() == destinationInstruction && dfe.getDestinationOperandIndex() == operandIndex) {\r
+ return dfe;\r
+ }\r
+ }\r
+ throw new UnsupportedOperationException();\r
+ }\r
+ \r
+ /**\r
+ * @param destinationInstruction is an instruction index value.\r
+ * @param operandIndex is an operand index value in the instruction.\r
+ * @return a list of data flow edges which destination and operandIndex is specified.\r
+ */\r
+ public List<DataFlowEdge> getIncomingEdges(final int destinationInstruction, final int operandIndex) {\r
+ List<DataFlowEdge> edges = new ArrayList<>();\r
+ for (DataFlowEdge e: dataFlowEdges) {\r
+ if (e.getDestinationInstruction() == destinationInstruction &&\r
+ e.getDestinationOperandIndex() == operandIndex) {\r
+ edges.add(e);\r
+ }\r
+ }\r
+ return edges;\r
+ }\r
+ \r
+ private void computeEdges() {\r
+ List<DataFlowEdge> edges = new ArrayList<DataFlowEdge>();\r
+ \r
+ for (int instructionIndex=0; instructionIndex<instructions.size(); ++instructionIndex) {\r
+ Frame<?> f = analyzer.getFrames()[instructionIndex];\r
+ if (useStack(instructionIndex)) {\r
+ int operands = analyzer.getOperandCount(instructionIndex);\r
+ for (int opIndex=0; opIndex<operands; ++opIndex) {\r
+ int stackPos = f.getStackSize() - operands + opIndex;\r
+ FastSourceValue value = (FastSourceValue)f.getStack(stackPos);\r
+ for (int from: value.getInstructions()) {\r
+ edges.add(new DataFlowEdge(from, instructionIndex, opIndex, operands, stackPos, false));\r
+ }\r
+ }\r
+ } else if (referLocal(instructionIndex)) {\r
+ AbstractInsnNode to = instructions.getItem(instructionIndex);\r
+ int localIndex = OpcodeString.getVarIndex(to);\r
+ if (f != null) {\r
+ FastSourceValue value = (FastSourceValue)f.getLocal(localIndex);\r
+ for (int from: value.getInstructions()) {\r
+ edges.add(new DataFlowEdge(from, instructionIndex, 0, 1, localIndex, true));\r
+ }\r
+ } else {\r
+ // A frame object is missing for several instructions in certain methods including many JSRs.\r
+ // We skip the data-flow edges for the "unknown" sources.\r
+ // edges.add(new DataFlowEdge(65536, instructionIndex, 0, 1, localIndex, true));\r
+ }\r
+ }\r
+ }\r
+ List<DataFlowEdge> sourceOrder = new ArrayList<DataFlowEdge>(edges.size());\r
+ sourceOrder.addAll(edges);\r
+ Collections.sort(sourceOrder, new DataFlowEdge.SourceComparator());\r
+ dataFlowEdgesSourceOrder = sourceOrder;\r
+ dataFlowEdges = edges;\r
+ } \r
+\r
+ /**\r
+ * @return true if the specified instruction refers to operands on a stack.\r
+ */\r
+ public boolean useStack(int instructionIndex) {\r
+ return analyzer.getOperandCount(instructionIndex) > 0;\r
+ }\r
+\r
+ /**\r
+ * @return true if the specified instruction refers to a local variable.\r
+ */\r
+ private boolean referLocal(int instructionIndex) { \r
+ return OpcodeString.isLocalReferenceOperation(instructions.getItem(instructionIndex));\r
+ }\r
+ \r
+ /**\r
+ * @return an instruction object.\r
+ */\r
+ public AbstractInsnNode getInstruction(int index) {\r
+ return instructions.getItem(index);\r
+ }\r
+ \r
+ /**\r
+ * @param instructionIndex specifies an instruction.\r
+ * @return the number of operands used by the specified instruction.\r
+ * For an instruction that uses a local variable,\r
+ * 1 is returned.\r
+ */\r
+ public int getOperandCount(int instructionIndex) {\r
+ return analyzer.getOperandCount(instructionIndex);\r
+ }\r
+ \r
+ /**\r
+ * Returns a state of Frame at a specified instruction.\r
+ * Frame represents the current state of a operand stack and a local variable table.\r
+ * @param instructionIndex specifies an instruction.\r
+ * @return Frame object. The return value may be null if \r
+ * control-flow analysis somewhat failed. \r
+ * (It is rarely occurs for certain methods.)\r
+ */\r
+ public Frame<?> getFrame(int instructionIndex) {\r
+ return analyzer.getFrames()[instructionIndex];\r
+ }\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import java.util.Comparator;\r
+\r
+import soba.core.method.asm.FastSourceInterpreter;\r
+\r
+/**\r
+ * This class represents a data flow edge.\r
+ */\r
+public class DataFlowEdge {\r
+\r
+ /**\r
+ * A field specifies an instruction.\r
+ */\r
+ private int from;\r
+ private int to;\r
+ private int operandIndex;\r
+ private int operandCount;\r
+ private int variableIndex;\r
+ private boolean isLocal;\r
+ \r
+ /**\r
+ * Creates a new <code>DataFlowEdge</code> instance.\r
+ * @param from is index value of a source instruction.\r
+ * @param to is index value of a destination instruction.\r
+ * @param operandIndex is a operand index value in the instruction.\r
+ * @param operandCount is the number of a operand.\r
+ * @param variableIndex is a index value of a local variable table or operand stack.\r
+ * @param isLocal is true if the instructions access a local variable.\r
+ */\r
+ public DataFlowEdge(int from, int to, int operandIndex, int operandCount, int variableIndex, boolean isLocal) {\r
+ assert operandIndex < operandCount;\r
+ \r
+ this.from = from;\r
+ this.to = to;\r
+ this.operandIndex = operandIndex;\r
+ this.operandCount = operandCount;\r
+ this.variableIndex = variableIndex;\r
+ this.isLocal = isLocal;\r
+ }\r
+ \r
+ /**\r
+ * @return the index value of an instruction which produces data.\r
+ * This method returns -1 if the definition instruction cannot be resolved. \r
+ * For example, a catch clause starts with ASTORE instruction whose data source cannot be resolved. \r
+ * If isParameter() is true, it is a parameter from outside of the method.\r
+ */\r
+ public int getSourceInstruction() {\r
+ return from;\r
+ }\r
+ \r
+ /**\r
+ * @return the index value of an instruction which consumes data.\r
+ */\r
+ public int getDestinationInstruction() {\r
+ return to;\r
+ }\r
+ \r
+ /**\r
+ * @return the index value in the operand stack.\r
+ */\r
+ public int getDestinationOperandIndex() {\r
+ return operandIndex;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of the operand in the destination instruction.\r
+ */\r
+ public int getDestinationOperandCount() {\r
+ return operandCount;\r
+ }\r
+ \r
+ /**\r
+ * @return a variable index pointing to an entry in \r
+ * a local variable table or a operand stack.\r
+ */\r
+ public int getVariableIndex() {\r
+ return variableIndex;\r
+ }\r
+ \r
+ /**\r
+ * @return true if the source instruction is a formal parameter.\r
+ */\r
+ public boolean isParameter() {\r
+ return isLocal && (from == FastSourceInterpreter.METHOD_ENTRY);\r
+ }\r
+ \r
+ /**\r
+ * @return true if the edge represents a data-flow through a local variable.\r
+ * The method returns false for a data-flow edge for an operand stack.\r
+ */\r
+ public boolean isLocal() {\r
+ return isLocal;\r
+ }\r
+ \r
+ @Override\r
+ public String toString() {\r
+ StringBuilder builder = new StringBuilder(64);\r
+ if (isParameter()) {\r
+ builder.append("PARAM");\r
+ } else {\r
+ builder.append(from);\r
+ }\r
+ builder.append(" -> ");\r
+ builder.append(to);\r
+ if (operandCount > 1) {\r
+ builder.append(" [");\r
+ builder.append(operandIndex + 1);\r
+ builder.append("/");\r
+ builder.append(operandCount);\r
+ builder.append("]");\r
+ }\r
+ if (isLocal) {\r
+ builder.append(" (LOCAL:");\r
+ } else {\r
+ builder.append(" (STACK:");\r
+ }\r
+ builder.append(variableIndex);\r
+ builder.append(")");\r
+ return builder.toString();\r
+ }\r
+ \r
+ private static int compareVariable(DataFlowEdge o1, DataFlowEdge o2) {\r
+ if (o1.operandIndex == o2.operandIndex) {\r
+ if (o1.variableIndex == o2.variableIndex) {\r
+ if (o1.isLocal == o2.isLocal) {\r
+ return 0;\r
+ } else {\r
+ if (o1.isLocal) return 1;\r
+ else return -1;\r
+ }\r
+ } else {\r
+ return o1.variableIndex - o2.variableIndex;\r
+ }\r
+ } else {\r
+ return o1.operandIndex - o2.operandIndex;\r
+ }\r
+ }\r
+ \r
+ public static class SourceComparator implements Comparator<DataFlowEdge> {\r
+ \r
+ @Override\r
+ public int compare(DataFlowEdge o1, DataFlowEdge o2) {\r
+ if (o1.from == o2.from) {\r
+ if (o1.to == o2.to) {\r
+ return compareVariable(o1, o2);\r
+ } else {\r
+ return o1.to - o2.to;\r
+ }\r
+ } else {\r
+ return o1.from - o2.from;\r
+ }\r
+ }\r
+ }\r
+ \r
+ public static class DestinationComparator implements Comparator<DataFlowEdge> {\r
+ \r
+ @Override\r
+ public int compare(DataFlowEdge o1, DataFlowEdge o2) {\r
+ if (o1.to == o2.to) {\r
+ if (o1.from == o2.from) {\r
+ return compareVariable(o1, o2);\r
+ } else {\r
+ return o1.from - o2.from;\r
+ }\r
+ } else {\r
+ return o1.to - o2.to;\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+public class FieldAccess {\r
+\r
+ private String className;\r
+ private String fieldName;\r
+ private String fieldDesc;\r
+ private boolean isStatic;\r
+ private boolean isGet; // true==GET, false==PUT \r
+\r
+ private FieldAccess(String className, String fieldName, String fieldDesc, boolean isStatic, boolean isGet) {\r
+ this.className = className;\r
+ this.fieldName = fieldName;\r
+ this.fieldDesc = fieldDesc;\r
+ this.isStatic = isStatic;\r
+ this.isGet = isGet;\r
+ }\r
+ \r
+ /**\r
+ * Creates a new <code>FieldAccess</code> instance representing a load instruction of a field.\r
+ * @param className is a class name of the accessed field.\r
+ * @param fieldName is a field name.\r
+ * @param fieldDesc is a descriptor of the field.\r
+ * @param isStatic is true if it is a static field.\r
+ * @return a <code>FieldAccess</code> object.\r
+ */\r
+ public static FieldAccess createGetField(String className, String fieldName, String fieldDesc, boolean isStatic) {\r
+ return new FieldAccess(className, fieldName, fieldDesc, isStatic, true);\r
+ }\r
+\r
+ /**\r
+ * Creates a new <code>FieldAccess</code> instance representing a store instruction of a field.\r
+ * @param className is a class name of the accessed field.\r
+ * @param fieldName is a field name.\r
+ * @param fieldDesc is a descriptor of the field.\r
+ * @param isStatic is true if it is a static field.\r
+ * @return a <code>FieldAccess</code> object.\r
+ */\r
+ public static FieldAccess createPutField(String className, String fieldName, String fieldDesc, boolean isStatic) {\r
+ return new FieldAccess(className, fieldName, fieldDesc, isStatic, false);\r
+ }\r
+ \r
+ /**\r
+ * @return the class name of the field.\r
+ */\r
+ public String getClassName() {\r
+ return className;\r
+ }\r
+\r
+ /**\r
+ * @return the field name.\r
+ */\r
+ public String getFieldName() {\r
+ return fieldName;\r
+ }\r
+ \r
+ /**\r
+ * @return the descriptor of the field. \r
+ */\r
+ public String getDescriptor() {\r
+ return fieldDesc;\r
+ }\r
+ \r
+ /**\r
+ * @return true if the field is static.\r
+ */\r
+ public boolean isStatic() {\r
+ return isStatic;\r
+ }\r
+ \r
+ /**\r
+ * @return true if the instruction loads the field value.\r
+ */\r
+ public boolean isGet() {\r
+ return isGet;\r
+ }\r
+\r
+ /**\r
+ * @return true if the instruction stores the field value.\r
+ */\r
+ public boolean isPut() {\r
+ return !isGet;\r
+ }\r
+ \r
+ @Override\r
+ public String toString() {\r
+ StringBuilder b = new StringBuilder();\r
+ if (isGet) {\r
+ b.append("GET");\r
+ } else {\r
+ b.append("PUT");\r
+ }\r
+ if (isStatic) {\r
+ b.append("STATIC");\r
+ } else {\r
+ b.append("FIELD");\r
+ }\r
+ b.append(" ");\r
+ b.append(className);\r
+ b.append(".");\r
+ b.append(fieldName);\r
+ b.append(": ");\r
+ b.append(fieldDesc);\r
+ return b.toString();\r
+ }\r
+ \r
+ @Override\r
+ public boolean equals(Object obj) {\r
+ if (obj == null) { return false; }\r
+ \r
+ FieldAccess fa = (FieldAccess) obj;\r
+ return this.toString().equals(fa.toString());\r
+ }\r
+\r
+ @Override\r
+ public int hashCode() {\r
+ final int prime = 31;\r
+ int result = 1;\r
+ result = prime * result\r
+ + ((className == null) ? 0 : className.hashCode());\r
+ result = prime * result\r
+ + ((fieldDesc == null) ? 0 : fieldDesc.hashCode());\r
+ result = prime * result\r
+ + ((fieldName == null) ? 0 : fieldName.hashCode());\r
+ result = prime * result + (isGet ? 1231 : 1237);\r
+ result = prime * result + (isStatic ? 1231 : 1237);\r
+ return result;\r
+ }\r
+ \r
+\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+\r
+public interface ILocalVariableInfo {\r
+\r
+ public boolean isAnonymous();\r
+ public String getName();\r
+ public String getDescriptor();\r
+ public String getGenericsSignature();\r
+ \r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import gnu.trove.iterator.TIntIterator;\r
+import gnu.trove.set.hash.TIntHashSet;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.LocalVariableNode;\r
+import org.objectweb.asm.tree.MethodNode;\r
+import org.objectweb.asm.tree.VarInsnNode;\r
+\r
+import soba.core.signature.TypeResolver;\r
+import soba.util.IntPairProc;\r
+import soba.util.IntPairSet;\r
+\r
+\r
+/**\r
+ * An instance of LocalVariables maintains a list of \r
+ * local variable entries.\r
+ * Each entry corresponds to a set of def-use chains \r
+ * that share the same instructions.\r
+ * \r
+ * A variable may be represented by two or more entries\r
+ * if the variable is re-used for several "independent"\r
+ * def-use chains.\r
+ */\r
+public class LocalVariables {\r
+ \r
+ private ArrayList<Entry> entries;\r
+ private MethodNode m;\r
+\r
+ /**\r
+ * Creates a new <code>LocalVariables</code> instance.\r
+ * @param dataDependence is a <code>DataDependence</code> object.\r
+ * @param node\r
+ */\r
+ public LocalVariables(DataDependence dataDependence, MethodNode node) {\r
+ this.m = node;\r
+ \r
+ // Create entries based on data-flow edges.\r
+ // Each entry is a set of data-flow edges that have some common def/use instructions.\r
+ ArrayList<Entry> entries = new ArrayList<Entry>();\r
+ for (DataFlowEdge edge: dataDependence.getEdges()) {\r
+ if (edge.isLocal()) {\r
+ int[] index = findEntries(entries, edge);\r
+ if (index[0] != -1) {\r
+ Entry e1 = entries.get(index[0]);\r
+ if (index[1] == -1) {\r
+ e1.add(edge);\r
+ checkObjectFlag(e1, dataDependence, edge);\r
+ } else if (index[1] != index[0]) {\r
+ Entry e2 = entries.remove(index[1]);\r
+ e1.merge(e2);\r
+ } else { \r
+ // index[0] == index[1]; both instructions are already involved in a single entry. \r
+ checkObjectFlag(e1, dataDependence, edge);\r
+ }\r
+ } else {\r
+ // Both instructions are not included in an entry.\r
+ // Create a new entry for the edge.\r
+ Entry e = new Entry(edge); \r
+ entries.add(e);\r
+ checkObjectFlag(e, dataDependence, edge);\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ // Associate local variable nodes to entries.\r
+ List<?> variables = node.localVariables;\r
+ for (int i=0; i<variables.size(); ++i) {\r
+ LocalVariableNode var = (LocalVariableNode)variables.get(i);\r
+ for (Entry e: entries) { \r
+ if (e.isDataflowOf(var)) {\r
+ e.addLocalVariableNode(var);\r
+ }\r
+ }\r
+ }\r
+\r
+ this.entries = entries;\r
+ \r
+ // Add entries for STORE instructions without LOAD instructions.\r
+ for (int i=0; i<node.instructions.size(); ++i) {\r
+ AbstractInsnNode instruction = node.instructions.get(i);\r
+ if (OpcodeString.isStoreOperation(instruction) || OpcodeString.isLoadOperation(instruction)) {\r
+ int entry = findEntryForInstruction(i);\r
+ if (entry == -1) {\r
+ Entry e = new Entry(i, (VarInsnNode)instruction);\r
+ this.entries.add(e);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ private void checkObjectFlag(Entry e, DataDependence dataflow, DataFlowEdge edge) {\r
+ AbstractInsnNode varNode = dataflow.getInstruction(edge.getDestinationInstruction());\r
+ if (varNode.getOpcode() == Opcodes.ALOAD) {\r
+ e.setObjectTypeEntry(true);\r
+ }\r
+ if (edge.getSourceInstruction() >= 0) {\r
+ varNode = dataflow.getInstruction(edge.getSourceInstruction());\r
+ if (varNode.getOpcode() == Opcodes.ASTORE) {\r
+ e.setObjectTypeEntry(true);\r
+ } else if (edge.getDestinationOperandIndex() == 0 && \r
+ (varNode.getOpcode() == Opcodes.AALOAD ||\r
+ varNode.getOpcode() == Opcodes.AASTORE)) {\r
+ e.setArrayTypeEntry(true);\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return the number of the variable entries.\r
+ * It may be larger than the number of variables in the method. \r
+ */\r
+ public int getVariableEntryCount() {\r
+ return entries.size();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return a variable name.\r
+ */\r
+ public String getVariableName(int entryIndex) {\r
+ return entries.get(entryIndex).getVariableName();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return a descriptor of the specified variable.\r
+ */\r
+ public String getDescriptor(int entryIndex) {\r
+ return entries.get(entryIndex).getDesc();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return the type name. The value becomes null \r
+ * if the type name is not included in the analyzed classfile.\r
+ */\r
+ public String getVariableType(int entryIndex) {\r
+ return entries.get(entryIndex).getTypeName();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return the index value in the local variable table.\r
+ */\r
+ public int getVariableIndex(int entryIndex) {\r
+ return entries.get(entryIndex).getVariableIndex();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return true if the variable is an object type.\r
+ */\r
+ public boolean isObjectVariable(int entryIndex) {\r
+ return entries.get(entryIndex).isObjectType();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return true if the variable is an array.\r
+ */\r
+ public boolean isArrayVariable(int entryIndex) {\r
+ return entries.get(entryIndex).isArrayType();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a local variable.\r
+ * @return true if there is no instruction which uses the variable value.\r
+ */\r
+ public boolean hasNoDataDependence(int entryIndex) {\r
+ return entries.get(entryIndex).isAlone();\r
+ }\r
+ \r
+ /**\r
+ * @param entryIndex specifies a variable entry.\r
+ * @return true if the entry corresponds to a method parameter.\r
+ * Please note that this method returns false\r
+ * for a parameter whose value must be overwritten \r
+ * by another instruction.\r
+ */\r
+ public boolean isParameter(int entryIndex) {\r
+ return entries.get(entryIndex).isParameter();\r
+ }\r
+ \r
+ /**\r
+ * @param instructionIndex specifies an instruction.\r
+ * @return the index value in the variable entries.\r
+ * The variable is accessed by the specified instruction.\r
+ */\r
+ public int findEntryForInstruction(int instructionIndex) {\r
+ for (int i=0; i<entries.size(); ++i) {\r
+ Entry e = entries.get(i);\r
+ if (e.containsSource(instructionIndex) ||\r
+ e.containsDestination(instructionIndex)) {\r
+ return i;\r
+ }\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Returns a pair of indices that indicate a pair of Entries\r
+ * containing the specified values.\r
+ * The return_value[0] specifies a set containing value1,\r
+ * while the return_value[1] specifies a set containing value2.\r
+ * If no set contains value1, \r
+ * return_value is a pair (a set containing value2, -1).\r
+ * If no values are found in the sets, (-1, -1) is returned.\r
+ * @return \r
+ */\r
+ private static int[] findEntries(ArrayList<Entry> entries, DataFlowEdge edge) {\r
+ int[] ret = new int[] {-1, -1};\r
+ for (int i=0; i<entries.size(); ++i) {\r
+ Entry e = entries.get(i);\r
+ if (e.isConnected(edge)) {\r
+ if (ret[0] == -1) {\r
+ ret[0] = i;\r
+ } else if (ret[1] == -1) {\r
+ ret[1] = i;\r
+ return ret;\r
+ }\r
+ }\r
+ }\r
+ return ret;\r
+ }\r
+ \r
+ private class Entry {\r
+ \r
+ private TIntHashSet defs;\r
+ private TIntHashSet refs;\r
+ private IntPairSet refWithOperands;\r
+ private int variableIndex;\r
+ private boolean isObjectType;\r
+ private boolean isArrayType;\r
+ private String typeName;\r
+ private String typeNameWithGenerics;\r
+ private String variableName;\r
+ private String desc;\r
+ private ArrayList<LocalVariableNode> variables;\r
+ private boolean isParam;\r
+ private boolean isAlone; // true if there is a store instruction without a load instruction.\r
+ \r
+ private Entry(DataFlowEdge e) {\r
+ this.variableIndex = e.getVariableIndex();\r
+ this.variables = new ArrayList<LocalVariableNode>();\r
+ \r
+ defs = new TIntHashSet();\r
+ defs.add(e.getSourceInstruction());\r
+ refs = new TIntHashSet();\r
+ refs.add(e.getDestinationInstruction());\r
+ refWithOperands = new IntPairSet();\r
+ refWithOperands.add(e.getDestinationInstruction(), e.getDestinationOperandIndex());\r
+ isParam = e.isParameter();\r
+ isAlone = false;\r
+ // isObjectType and isArrayType are set by an external method.\r
+ }\r
+\r
+ private Entry(int instructionIndex, VarInsnNode var) {\r
+ assert OpcodeString.isStoreOperation(var) || OpcodeString.isAfterJSR(var) : "A STORE instruction may exist without LOAD instructions. But there are no LOAD instructions without STORE.";\r
+ this.variableIndex = var.var;\r
+ this.variables = new ArrayList<LocalVariableNode>(1);\r
+ defs = new TIntHashSet(2);\r
+ defs.add(instructionIndex);\r
+ refs = new TIntHashSet();\r
+ refWithOperands = new IntPairSet();\r
+ isParam = false;\r
+ isObjectType = var.getOpcode() == Opcodes.ASTORE;\r
+ isAlone = true;\r
+ }\r
+ \r
+\r
+ public boolean isParameter() {\r
+ return defs.contains(-1);\r
+ }\r
+ \r
+ public boolean isAlone() {\r
+ return isAlone;\r
+ }\r
+ \r
+ private void setObjectTypeEntry(boolean value) { \r
+ this.isObjectType = value;\r
+ }\r
+\r
+ private void setArrayTypeEntry(boolean value) { \r
+ this.isArrayType = value;\r
+ }\r
+\r
+ private void add(DataFlowEdge e) {\r
+ assert this.variableIndex == e.getVariableIndex();\r
+ defs.add(e.getSourceInstruction());\r
+ refs.add(e.getDestinationInstruction());\r
+ refWithOperands.add(e.getDestinationInstruction(), e.getDestinationOperandIndex());\r
+ isParam = isParam || e.isParameter();\r
+ }\r
+ \r
+ private void merge(Entry another) {\r
+ assert this.variableIndex == another.variableIndex; \r
+ \r
+ defs.addAll(another.defs.toArray());\r
+ another.refWithOperands.foreach(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int instructionIndex, int operand) {\r
+ refs.add(instructionIndex);\r
+ refWithOperands.add(instructionIndex, operand);\r
+ return true;\r
+ }\r
+ });\r
+ if (another.isObjectType) {\r
+ this.isObjectType = another.isObjectType;\r
+ }\r
+ }\r
+ \r
+ private boolean isDataflowOf(LocalVariableNode var) {\r
+ if (this.variableIndex == var.index) {\r
+ \r
+ for (TIntIterator it=defs.iterator(); it.hasNext(); ) {\r
+ int def = it.next();\r
+ if (0 <= def && def < m.instructions.size()) {\r
+ AbstractInsnNode defNode = m.instructions.get(def);\r
+ if (OpcodeString.isAccess(defNode, var)) {\r
+ return true;\r
+ }\r
+ } else {\r
+ if (var.start == m.instructions.getFirst()) {\r
+ return true;\r
+ }\r
+ }\r
+ }\r
+\r
+ for (TIntIterator it=refs.iterator(); it.hasNext(); ) {\r
+ int ref = it.next();\r
+ AbstractInsnNode refNode = m.instructions.get(ref);\r
+ if (OpcodeString.isAccess(refNode, var)) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ \r
+ } else {\r
+ return false;\r
+ }\r
+ \r
+ }\r
+ \r
+ private void addLocalVariableNode(LocalVariableNode node) {\r
+ variables.add(node);\r
+ if (variableName == null) {\r
+ variableName = node.name;\r
+ } else {\r
+ assert node.name == null || variableName.equals(node.name): "Combined " + variableName.toString() + " and " + node.name;\r
+ }\r
+ if (typeName == null) {\r
+ if (node.desc != null) {\r
+ desc = node.desc;\r
+ typeName = TypeResolver.getTypeName(node.desc);\r
+ }\r
+ } else {\r
+ assert node.desc == null || typeName.equals(TypeResolver.getTypeName(node.desc));\r
+ }\r
+ if (typeNameWithGenerics == null) {\r
+ if (node.signature != null) {\r
+ typeNameWithGenerics = TypeResolver.getTypeName(node.signature);\r
+ }\r
+ } else {\r
+ assert node.signature == null || typeNameWithGenerics.equals(TypeResolver.getTypeName(node.signature));\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @param edge\r
+ * @return edge is connected with some vertices in the entry.\r
+ */\r
+ private boolean isConnected(DataFlowEdge edge) {\r
+ return this.variableIndex == edge.getVariableIndex() &&\r
+ containsSource(edge.getSourceInstruction()) ||\r
+ containsDestinationOperand(edge.getDestinationInstruction(), edge.getDestinationOperandIndex());\r
+ }\r
+ \r
+ /**\r
+ * @param instruction\r
+ * @return true if the entry contains the specified \r
+ * source instruction.\r
+ */\r
+ private boolean containsSource(int instruction) {\r
+ return defs.contains(instruction);\r
+ }\r
+\r
+ /**\r
+ * @param instruction\r
+ * @return true if the entry contains the specified \r
+ * destination instruction.\r
+ */\r
+ private boolean containsDestination(int instruction) {\r
+ return refWithOperands.containsFirst(instruction);\r
+ }\r
+\r
+ /**\r
+ * @param instruction\r
+ * @return true if the entry contains the specified \r
+ * destination instruction.\r
+ */\r
+ private boolean containsDestinationOperand(int instruction, int operandIndex) {\r
+ return refWithOperands.contains(instruction, operandIndex);\r
+ }\r
+\r
+ private boolean isObjectType() {\r
+ return isObjectType;\r
+ }\r
+ \r
+ private boolean isArrayType() {\r
+ return isArrayType;\r
+ }\r
+ \r
+ private int getVariableIndex() {\r
+ return variableIndex;\r
+ }\r
+ \r
+ private String getTypeName() {\r
+ return typeName;\r
+ }\r
+ \r
+ private String getVariableName() {\r
+ return variableName;\r
+ }\r
+ \r
+ private String getDesc() {\r
+ return desc;\r
+ }\r
+\r
+ }\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.FieldInsnNode;\r
+import org.objectweb.asm.tree.FrameNode;\r
+import org.objectweb.asm.tree.IincInsnNode;\r
+import org.objectweb.asm.tree.JumpInsnNode;\r
+import org.objectweb.asm.tree.LabelNode;\r
+import org.objectweb.asm.tree.LdcInsnNode;\r
+import org.objectweb.asm.tree.LineNumberNode;\r
+import org.objectweb.asm.tree.LocalVariableNode;\r
+import org.objectweb.asm.tree.MethodInsnNode;\r
+import org.objectweb.asm.tree.MethodNode;\r
+import org.objectweb.asm.tree.VarInsnNode;\r
+\r
+import soba.core.signature.TypeResolver;\r
+\r
+\r
+\r
+public class OpcodeString {\r
+ \r
+ public static final String TYPE_PRIMITIVE = "P";\r
+ public static final String TYPE_REFERENCE = "R";\r
+ public static final String TYPE_UNKNOWN = "U";\r
+ \r
+\r
+ /**\r
+ * This method returns a local variable index accessed by \r
+ * the specified instruction node.\r
+ * \r
+ * The same index may be used for two or more local variables.\r
+ * To distinguish such variables, use findLocalVariable method. \r
+ */\r
+ public static int getVarIndex(AbstractInsnNode node) {\r
+ if (node.getType() == AbstractInsnNode.VAR_INSN) {\r
+ return ((VarInsnNode)node).var;\r
+ } else if (node.getType() == AbstractInsnNode.IINC_INSN) {\r
+ return ((IincInsnNode)node).var;\r
+ } else {\r
+ assert false: "getVarIndex is called for an instruction without variable informaiton.";\r
+ return -1;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * This method returns true if a specified instruction \r
+ * reads or writes a local variable represented by \r
+ * the specified local variable node. \r
+ * \r
+ * @param insn specifies an instruction.\r
+ * @param var specifies a local variable.\r
+ * @return true if the instruction accesses the variable.\r
+ * If var is null, this method always returns false.\r
+ */\r
+ public static boolean isAccess(AbstractInsnNode insn, LocalVariableNode var) {\r
+ if (insn == null || var == null) return false;\r
+ \r
+ int varIndex = getVarIndex(insn);\r
+ if (var.index == varIndex) {\r
+ // Check whether the variable's scope includes insn. \r
+ AbstractInsnNode scopeEndInstruction; \r
+ if (isStoreOperation(insn) || isIncrementOperation(insn)) {\r
+ if (var.start == insn.getNext()) return true;\r
+ else {\r
+ if (var.start != var.end &&\r
+ var.end.getPrevious() != null && \r
+ (isStoreOperation(var.end.getPrevious()) || \r
+ isIncrementOperation(var.end.getPrevious()))) {\r
+ scopeEndInstruction = var.end.getPrevious(); \r
+ } else {\r
+ scopeEndInstruction = var.end;\r
+ }\r
+ }\r
+ } else {\r
+ assert isLoadOperation(insn) || isRET(insn);\r
+ scopeEndInstruction = var.end;\r
+ }\r
+ boolean inScope = false;\r
+ AbstractInsnNode pos = var.start;\r
+ while ((pos != null)&&(pos != scopeEndInstruction)) {\r
+ if (pos == insn) {\r
+ inScope = true;\r
+ break;\r
+ }\r
+ pos = pos.getNext();\r
+ }\r
+ return inScope;\r
+ \r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * This method returns a local variable node accessed by \r
+ * the specified VarInsnNode (if exists). \r
+ * \r
+ * This method is necessary because two or more variables may \r
+ * have the same varIndex.\r
+ * It should be noted that a variable "x" becomes in-scope only AFTER\r
+ * its variable declaration such as "C x = v;".\r
+ * The STORE instruction accessing "x" is out of scope of the variable.\r
+ */\r
+ public static LocalVariableNode findLocalVariable(MethodNode method, AbstractInsnNode insn) {\r
+ if (method.localVariables == null) return null;\r
+\r
+ List<?> variables = method.localVariables;\r
+ for (int i=0; i<variables.size(); ++i) {\r
+ LocalVariableNode var = (LocalVariableNode)variables.get(i);\r
+ if (isAccess(insn, var)) {\r
+ return var;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ \r
+ /**\r
+ * Return true if a node is an instruction to load a return address\r
+ * that is stored into an anonymous local variable.\r
+ * @param node\r
+ * @return\r
+ */\r
+ public static boolean isAfterJSR(AbstractInsnNode node) {\r
+ if (node.getOpcode() != Opcodes.ALOAD) return false;\r
+ \r
+ AbstractInsnNode n = node.getPrevious();\r
+ if (n.getType() == AbstractInsnNode.LABEL) {\r
+ n = n.getPrevious();\r
+ }\r
+ return n != null && n.getOpcode() == Opcodes.JSR;\r
+ }\r
+ \r
+ public static boolean isDefUseOperation(AbstractInsnNode node) {\r
+ // Note: VarInsn and IincInsn except for "RET" instruction.\r
+ return isStoreOperation(node) || isLoadOperation(node) || isIncrementOperation(node);\r
+ }\r
+ \r
+ public static boolean isIncrementOperation(AbstractInsnNode node) {\r
+ return node.getOpcode() == Opcodes.IINC;\r
+ }\r
+ \r
+ public static boolean isPrimitiveOperation(AbstractInsnNode node) {\r
+ int opcode = node.getOpcode();\r
+ return (opcode == Opcodes.ISTORE ||\r
+ opcode == Opcodes.FSTORE ||\r
+ opcode == Opcodes.LSTORE ||\r
+ opcode == Opcodes.DSTORE ||\r
+ opcode == Opcodes.ILOAD ||\r
+ opcode == Opcodes.FLOAD ||\r
+ opcode == Opcodes.LLOAD ||\r
+ opcode == Opcodes.DLOAD ||\r
+ opcode == Opcodes.IINC);\r
+ }\r
+ \r
+ public static boolean isStoreOperation(AbstractInsnNode node) {\r
+ int opcode = node.getOpcode();\r
+ return (opcode == Opcodes.ISTORE ||\r
+ opcode == Opcodes.ASTORE ||\r
+ opcode == Opcodes.FSTORE ||\r
+ opcode == Opcodes.LSTORE ||\r
+ opcode == Opcodes.DSTORE);\r
+ }\r
+\r
+ public static boolean isRET(AbstractInsnNode node) {\r
+ return node.getOpcode() == Opcodes.RET;\r
+ }\r
+ \r
+ public static boolean isLocalReferenceOperation(AbstractInsnNode node) {\r
+ return isLoadOperation(node) || node.getOpcode() == Opcodes.IINC;\r
+ }\r
+ \r
+ public static boolean isLoadOperation(AbstractInsnNode node) {\r
+ int opcode = node.getOpcode();\r
+ return (opcode == Opcodes.ILOAD ||\r
+ opcode == Opcodes.ALOAD ||\r
+ opcode == Opcodes.FLOAD ||\r
+ opcode == Opcodes.LLOAD ||\r
+ opcode == Opcodes.DLOAD);\r
+ }\r
+ \r
+ public static boolean isReturnOperation(AbstractInsnNode node) {\r
+ int opcode = node.getOpcode();\r
+ return (opcode == Opcodes.IRETURN ||\r
+ opcode == Opcodes.LRETURN ||\r
+ opcode == Opcodes.FRETURN ||\r
+ opcode == Opcodes.DRETURN ||\r
+ opcode == Opcodes.ARETURN ||\r
+ opcode == Opcodes.RETURN);\r
+ }\r
+ \r
+ public static boolean isConstantOperation(int opcode) {\r
+ switch (opcode) { \r
+ case Opcodes.ACONST_NULL:\r
+ case Opcodes.BIPUSH:\r
+ case Opcodes.DCONST_0:\r
+ case Opcodes.DCONST_1:\r
+ case Opcodes.FCONST_0:\r
+ case Opcodes.FCONST_1:\r
+ case Opcodes.FCONST_2:\r
+ case Opcodes.ICONST_0:\r
+ case Opcodes.ICONST_1:\r
+ case Opcodes.ICONST_2:\r
+ case Opcodes.ICONST_3:\r
+ case Opcodes.ICONST_4:\r
+ case Opcodes.ICONST_5:\r
+ case Opcodes.ICONST_M1:\r
+ case Opcodes.LCONST_0:\r
+ case Opcodes.LCONST_1:\r
+ case Opcodes.LDC:\r
+ case Opcodes.SIPUSH:\r
+ return true;\r
+ \r
+ default:\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ public static String getVariableString(LocalVariableNode node) { \r
+ return node.name + ": " + TypeResolver.getTypeName(node.desc);\r
+ }\r
+ \r
+\r
+ /**\r
+ * @return a string representation for a label node.\r
+ * @see getLabelString(MethodNode, int)\r
+ */\r
+ public static String getLabelString(MethodNode method, LabelNode label) {\r
+ return getLabelString(method, method.instructions.indexOf(label));\r
+ }\r
+\r
+ /**\r
+ * @return a string representation for a label located in a position specified by index.\r
+ * While label.toString() returns a different string for each execution,\r
+ * this method returns the same string if a label is located in the same position in a method.\r
+ */\r
+ public static String getLabelString(MethodNode method, int index) {\r
+ assert method.instructions.get(index).getType() == AbstractInsnNode.LABEL;\r
+ String right = "00000" + Integer.toString(index);\r
+ return "L" + right.substring(right.length()-5);\r
+ }\r
+ \r
+ /**\r
+ * @param method specifies a method containing an instruction.\r
+ * @param index specifies the position of an instruction in the list of instructions.\r
+ * @return a string representation of an instruction.\r
+ */\r
+ public static String getInstructionString(MethodNode method, int index) {\r
+ if (index == -1) return "ARG";\r
+ \r
+ AbstractInsnNode node = method.instructions.get(index);\r
+ int opcode = node.getOpcode();\r
+ String op = Integer.toString(index) + ": " + OpcodeString.getString(opcode);\r
+\r
+ switch (node.getType()) {\r
+ case AbstractInsnNode.VAR_INSN:\r
+ case AbstractInsnNode.IINC_INSN:\r
+ \r
+ LocalVariableNode n = OpcodeString.findLocalVariable(method, node);\r
+ if (n != null) {\r
+ return op + " " + Integer.toString(n.index) + " (" + n.name + ")";\r
+ } else {\r
+ int varIndex = OpcodeString.getVarIndex(node);\r
+ return op + " " + Integer.toString(varIndex);\r
+ }\r
+ \r
+ case AbstractInsnNode.FIELD_INSN:\r
+ FieldInsnNode fieldNode = (FieldInsnNode)node;\r
+ return op + " " + fieldNode.owner + "#" + fieldNode.name + ": " + TypeResolver.getTypeName(fieldNode.desc);\r
+ \r
+ case AbstractInsnNode.METHOD_INSN:\r
+ MethodInsnNode methodInsnNode = (MethodInsnNode)node;\r
+ return op + " " + methodInsnNode.owner + "#" + methodInsnNode.name + methodInsnNode.desc;\r
+ \r
+ case AbstractInsnNode.LINE:\r
+ LineNumberNode lineNode = (LineNumberNode)node;\r
+ return Integer.toString(index) + ": " + "(line=" + lineNode.line + ")";\r
+ \r
+ case AbstractInsnNode.LABEL:\r
+ return Integer.toString(index) + ": " + "(" + getLabelString(method, index) + ")";\r
+ \r
+ case AbstractInsnNode.JUMP_INSN:\r
+ JumpInsnNode jumpNode = (JumpInsnNode)node;\r
+ return op + " " + getLabelString(method, jumpNode.label);\r
+ \r
+ case AbstractInsnNode.FRAME:\r
+ FrameNode frameNode = (FrameNode)node;\r
+ return Integer.toString(index) + ": FRAME-OP(" + frameNode.type + ")";\r
+ \r
+ case AbstractInsnNode.LDC_INSN:\r
+ LdcInsnNode ldc = (LdcInsnNode)node;\r
+ return op + " " + ldc.cst.toString();\r
+ \r
+ default: \r
+ return op; \r
+ }\r
+ }\r
+ \r
+ private static String getString(int opcode) {\r
+ if (0 <= opcode && opcode < opcodeNames.length) {\r
+ return opcodeNames[opcode];\r
+ } else {\r
+ return Integer.toString(opcode);\r
+ }\r
+ }\r
+ \r
+ private static String[] opcodeNames = new String[] { \r
+ "NOP", "ACONST_NULL", "ICONST_M1", "ICONST_0", \r
+ "ICONST_1", "ICONST_2", "ICONST_3", "ICONST_4", \r
+ "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0",\r
+ "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1",\r
+ "BIPUSH", "SIPUSH", "LDC", "LDC_W", \r
+ "LDC2_W", "ILOAD", "LLOAD", "FLOAD", \r
+ "DLOAD", "ALOAD", "ILOAD_0", "ILOAD_1",\r
+ "ILOAD_2", "ILOAD_3", "LLOAD_0", "LLOAD_1",\r
+ "LLOAD_2", "LLOAD_3", "FLOAD_0", "FLOAD_1",\r
+ "FLOAD_2", "FLOAD_3", "DLOAD_0", "DLOAD_1",\r
+ "DLOAD_2", "DLOAD_3", "ALOAD_0", "ALOAD_1",\r
+ "ALOAD_2", "ALOAD_3", "IALOAD", "LALOAD",\r
+ "FALOAD", "DALOAD", "AALOAD", "BALOAD",\r
+ "CALOAD", "SALOAD", "ISTORE", "LSTORE",\r
+ "FSTORE", "DSTORE", "ASTORE", "ISTORE_0",\r
+ "ISTORE_1", "ISTORE_2", "ISTORE_3", "LSTORE_0",\r
+ "LSTORE_1", "LSTORE_2", "LSTORE_3", "FSTORE_0",\r
+ "FSTORE_1", "FSTORE_2", "FSTORE_3", "DSTORE_0",\r
+ "DSTORE_1", "DSTORE_2", "DSTORE_3", "ASTORE_0",\r
+ "ASTORE_1", "ASTORE_2", "ASTORE_3", "IASTORE",\r
+ "LASTORE", "FASTORE", "DASTORE", "AASTORE",\r
+ "BASTORE", "CASTORE", "SASTORE", "POP",\r
+ "POP2", "DUP", "DUP_X1", "DUP_X2",\r
+ "DUP2", "DUP2_X1", "DUP_X2", "SWAP",\r
+ "IADD", "LADD", "FADD", "DADD",\r
+ "ISUB", "LSUB", "FSUB", "DSUB",\r
+ "IMUL", "LMUL", "FMUL", "DMUL",\r
+ "IDIV", "LDIV", "FDIV", "DDIV",\r
+ "IREM", "LREM", "FREM", "DREM",\r
+ "INEG", "LNEG", "FNEG", "DNEG",\r
+ "ISHL", "LSHL", "ISHR", "LSHR",\r
+ "IUSHR","LUSHR", "IAND", "LAND",\r
+ "IOR", "LOR", "IXOR", "LXOR",\r
+ "IINC", "I2L", "I2F", "I2D",\r
+ "L2I", "L2F", "L2D", "F2I",\r
+ "F2L", "F2D", "D2I", "D2L",\r
+ "D2F", "I2B", "I2C", "I2S",\r
+ "LCMP", "FCMPL", "FCMPG", "DCMPL",\r
+ "DCMPG", "IFEQ", "IFNE", "IFLT",\r
+ "IFGE", "IFGT", "IFLE", "IF_ICMPEQ",\r
+ "IF_ICMPNE", "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT",\r
+ "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE", "GOTO",\r
+ "JSR", "RET", "TABLESWITCH", "LOOKUPSWITCH", \r
+ "IRETURN", "LRETURN", "FRETURN", "DRETURN",\r
+ "ARETURN", "RETURN", "GETSTATIC", "PUTSTATIC",\r
+ "GETFIELD", "PUTFIELD", "INVOKEVIRTUAL", "INVOKESPECIAL",\r
+ "INVOKESTATIC", "INVOKEINTERFACE", "INVOKEDYNAMIC", "NEW", \r
+ "NEWARRAY", "ANEWARRAY", "ARRAYLENGTH", "ATHROW",\r
+ "CHECKCAST", "INSTANCEOF", "MONITORENTER", "MONITOREXIT", \r
+ "WIDE", "MULTIANEWARRAY", "IFNULL", "IFNONNULL", \r
+ "GOTO_W", "JSR_W" \r
+ };\r
+\r
+ static {\r
+ assert opcodeNames.length == 202;\r
+ }\r
+}\r
--- /dev/null
+package soba.core.method.asm;\r
+\r
+import org.objectweb.asm.tree.MethodNode;\r
+import org.objectweb.asm.tree.analysis.Analyzer;\r
+import org.objectweb.asm.tree.analysis.AnalyzerException;\r
+import org.objectweb.asm.tree.analysis.Frame;\r
+import org.objectweb.asm.tree.analysis.Value;\r
+\r
+import soba.util.IntPairList;\r
+import soba.util.IntPairSet;\r
+import soba.util.IntPairUtil;\r
+\r
+public class DataFlowAnalyzer extends Analyzer<Value> {\r
+\r
+ private MethodNode method;\r
+ private IntPairSet controlFlow = new IntPairSet();\r
+ private IntPairSet exceptionalFlow = new IntPairSet();\r
+ private DataFlowInterpreter interpreter;\r
+\r
+ public DataFlowAnalyzer(DataFlowInterpreter interpreter) {\r
+ super(interpreter);\r
+ this.interpreter = interpreter;\r
+ }\r
+ \r
+ @Override\r
+ public Frame<Value>[] analyze(String owner, MethodNode m) throws AnalyzerException {\r
+ this.method = m;\r
+ return super.analyze(owner, m);\r
+ }\r
+ \r
+ @Override\r
+ protected void newControlFlowEdge(int insn, int successor) {\r
+ controlFlow.add(insn, successor);\r
+ super.newControlFlowEdge(insn, successor);\r
+ }\r
+ \r
+ @Override\r
+ protected boolean newControlFlowExceptionEdge(int insn, int successor) {\r
+ exceptionalFlow.add(insn, successor);\r
+ return super.newControlFlowExceptionEdge(insn, successor);\r
+ }\r
+ \r
+ public MethodNode getAnalyzedMethod() {\r
+ return method;\r
+ }\r
+ \r
+ public IntPairList getNormalControlFlow() {\r
+ return IntPairUtil.createList(controlFlow);\r
+ }\r
+ \r
+ public IntPairList getConservativeControlFlow() {\r
+ return IntPairUtil.createList(controlFlow, exceptionalFlow);\r
+ }\r
+ \r
+ public int getOperandCount(int instructionIndex) {\r
+ return interpreter.getOperandCount(instructionIndex);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core.method.asm;\r
+\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Type;\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.analysis.Value;\r
+\r
+import soba.util.ObjectIdMap;\r
+\r
+public class DataFlowInterpreter extends FastSourceInterpreter {\r
+ \r
+ private ObjectIdMap<AbstractInsnNode> instructions;\r
+ private int[] operands;\r
+ \r
+ public DataFlowInterpreter(ObjectIdMap<AbstractInsnNode> instructions) {\r
+ super(instructions);\r
+ this.instructions = instructions;\r
+ this.operands = new int[instructions.size()];\r
+ }\r
+ \r
+ public int getInstructionCount() {\r
+ return instructions.size();\r
+ }\r
+ \r
+ public int getOperandCount(int opIndex) { \r
+ return operands[opIndex];\r
+ }\r
+\r
+ @Override\r
+ public Value unaryOperation(AbstractInsnNode insn, Value value) {\r
+ if (insn.getOpcode() == IINC) {\r
+ // IINC does not use Operand Stack.\r
+ operands[ instructions.getId(insn) ] = 0;\r
+ return super.unaryOperation(insn, value);\r
+ } else {\r
+ operands[ instructions.getId(insn) ] = 1;\r
+ return super.unaryOperation(insn, value);\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void returnOperation(AbstractInsnNode insn, Value value,\r
+ Value expected) {\r
+ operands[ instructions.getId(insn) ] = 1;\r
+ super.returnOperation(insn, value, expected);\r
+ }\r
+ \r
+ @Override\r
+ public Value ternaryOperation(AbstractInsnNode insn, Value value1,\r
+ Value value2, Value value3) {\r
+ operands[ instructions.getId(insn) ] = 3;\r
+ return super.ternaryOperation(insn, value1, value2, value3);\r
+ }\r
+ \r
+ @Override\r
+ public Value binaryOperation(AbstractInsnNode insn, Value value1,\r
+ Value value2) {\r
+ operands[ instructions.getId(insn) ] = 2;\r
+ return super.binaryOperation(insn, value1, value2);\r
+ }\r
+ \r
+ @Override\r
+ public Value copyOperation(AbstractInsnNode insn, Value value) {\r
+ int operandCount = 0;\r
+ switch (insn.getOpcode()) {\r
+ case ILOAD:\r
+ case LLOAD:\r
+ case FLOAD:\r
+ case DLOAD:\r
+ case ALOAD:\r
+ operandCount = 0;\r
+ break;\r
+ case ISTORE:\r
+ case LSTORE:\r
+ case FSTORE:\r
+ case DSTORE:\r
+ case ASTORE:\r
+ operandCount = 1;\r
+ break;\r
+\r
+ case DUP:\r
+ case DUP_X1:\r
+ case DUP_X2:\r
+ case DUP2:\r
+ case DUP2_X1:\r
+ case DUP2_X2:\r
+ case SWAP:\r
+ // DUP and SWAP do not modify the value.\r
+ // Propagate the defined value.\r
+ return value;\r
+ \r
+ }\r
+ operands[ instructions.getId(insn) ] = operandCount;\r
+ return super.copyOperation(insn, value);\r
+ }\r
+ \r
+ @Override\r
+ public Value newValue(Type type) {\r
+ return super.newValue(type);\r
+ }\r
+ \r
+ @Override\r
+ public Value newOperation(AbstractInsnNode insn) {\r
+ // no arguments\r
+ return super.newOperation(insn);\r
+ }\r
+ \r
+ @Override \r
+ public Value naryOperation(AbstractInsnNode insn, List<? extends Value> values) {\r
+ operands[ instructions.getId(insn) ] = values.size();\r
+ return super.naryOperation(insn, values);\r
+ }\r
+ \r
+}\r
--- /dev/null
+/**\r
+ * Note: this class is implemented based on SourceInterpreter involved in ASM.\r
+ * \r
+ * This class is different from SourceInterpreter:\r
+ * SourceInterpreter uses an empty set to represent a data-flow from a method parameter.\r
+ * Therefore, the class fails to merge a data-flow path from a method parameter \r
+ * and aother path that overwrites the value.\r
+ * An example code is soba.testdata.DefUseTestData.overwriteParam().\r
+ */\r
+/***\r
+ * ASM: a very small and fast Java bytecode manipulation framework\r
+ * Copyright (c) 2000-2007 INRIA, France Telecom\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ * 3. Neither the name of the copyright holders nor the names of its\r
+ * contributors may be used to endorse or promote products derived from\r
+ * this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\r
+ * THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+package soba.core.method.asm;\r
+\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.Type;\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.FieldInsnNode;\r
+import org.objectweb.asm.tree.InvokeDynamicInsnNode;\r
+import org.objectweb.asm.tree.LdcInsnNode;\r
+import org.objectweb.asm.tree.MethodInsnNode;\r
+import org.objectweb.asm.tree.analysis.Interpreter;\r
+import org.objectweb.asm.tree.analysis.Value;\r
+\r
+import soba.util.ObjectIdMap;\r
+\r
+public class FastSourceInterpreter extends Interpreter<Value> implements Opcodes {\r
+ \r
+ public static final int METHOD_ENTRY = -1;\r
+ private ObjectIdMap<AbstractInsnNode> instructions;\r
+ \r
+ public FastSourceInterpreter(ObjectIdMap<AbstractInsnNode> instructions) {\r
+ super(ASM5);\r
+ this.instructions = instructions;\r
+ }\r
+\r
+ /**\r
+ * This implementation is different from SourceInterpreter.\r
+ * We distinguish a method parameter with an "un-initialized" \r
+ * entry for double-word data.\r
+ */\r
+ public Value newValue(final Type type) {\r
+ if (type == null) {\r
+ // an anonymous value that fills the second entry for double-word data.\r
+ return new FastSourceValue(1);\r
+ } else if (type == Type.VOID_TYPE) {\r
+ return null;\r
+ } else {\r
+ return new FastSourceValue(type.getSize(), METHOD_ENTRY);\r
+ }\r
+ }\r
+\r
+ public Value newOperation(final AbstractInsnNode insn) {\r
+ int size;\r
+ switch (insn.getOpcode()) {\r
+ case LCONST_0:\r
+ case LCONST_1:\r
+ case DCONST_0:\r
+ case DCONST_1:\r
+ size = 2;\r
+ break;\r
+ case LDC:\r
+ Object cst = ((LdcInsnNode) insn).cst;\r
+ size = cst instanceof Long || cst instanceof Double ? 2 : 1;\r
+ break;\r
+ case GETSTATIC:\r
+ size = Type.getType(((FieldInsnNode) insn).desc).getSize();\r
+ break;\r
+ default:\r
+ size = 1;\r
+ }\r
+ return new FastSourceValue(size, instructions.getId(insn));\r
+ }\r
+\r
+ public Value copyOperation(final AbstractInsnNode insn, final Value value) {\r
+ return new FastSourceValue(value.getSize(), instructions.getId(insn));\r
+ }\r
+\r
+ public Value unaryOperation(final AbstractInsnNode insn, final Value value)\r
+ {\r
+ int size;\r
+ switch (insn.getOpcode()) {\r
+ case LNEG:\r
+ case DNEG:\r
+ case I2L:\r
+ case I2D:\r
+ case L2D:\r
+ case F2L:\r
+ case F2D:\r
+ case D2L:\r
+ size = 2;\r
+ break;\r
+ case GETFIELD:\r
+ size = Type.getType(((FieldInsnNode) insn).desc).getSize();\r
+ break;\r
+ default:\r
+ size = 1;\r
+ }\r
+ return new FastSourceValue(size, instructions.getId(insn));\r
+ }\r
+\r
+ public Value binaryOperation(\r
+ final AbstractInsnNode insn,\r
+ final Value value1,\r
+ final Value value2)\r
+ {\r
+ int size;\r
+ switch (insn.getOpcode()) {\r
+ case LALOAD:\r
+ case DALOAD:\r
+ case LADD:\r
+ case DADD:\r
+ case LSUB:\r
+ case DSUB:\r
+ case LMUL:\r
+ case DMUL:\r
+ case LDIV:\r
+ case DDIV:\r
+ case LREM:\r
+ case DREM:\r
+ case LSHL:\r
+ case LSHR:\r
+ case LUSHR:\r
+ case LAND:\r
+ case LOR:\r
+ case LXOR:\r
+ size = 2;\r
+ break;\r
+ default:\r
+ size = 1;\r
+ }\r
+ return new FastSourceValue(size, instructions.getId(insn));\r
+ }\r
+\r
+ public Value ternaryOperation(\r
+ final AbstractInsnNode insn,\r
+ final Value value1,\r
+ final Value value2,\r
+ final Value value3)\r
+ {\r
+ return new FastSourceValue(1, instructions.getId(insn));\r
+ }\r
+\r
+ public Value naryOperation(final AbstractInsnNode insn, final List<? extends Value> values) {\r
+ int size;\r
+ if (insn.getOpcode() == MULTIANEWARRAY) {\r
+ size = 1;\r
+ } else if (insn.getOpcode() == INVOKEDYNAMIC) {\r
+ size = Type.getReturnType(((InvokeDynamicInsnNode) insn).desc).getSize();\r
+ } else {\r
+ size = Type.getReturnType(((MethodInsnNode) insn).desc).getSize();\r
+ }\r
+ return new FastSourceValue(size, instructions.getId(insn));\r
+ }\r
+\r
+ public void returnOperation(\r
+ final AbstractInsnNode insn,\r
+ final Value value,\r
+ final Value expected)\r
+ {\r
+ }\r
+\r
+ /**\r
+ * Implementation Note: This method must return v \r
+ * if v contains all elements in w.\r
+ */\r
+ public Value merge(final Value v, final Value w) {\r
+ if (v == w) return v;\r
+ FastSourceValue dv = (FastSourceValue)v;\r
+ FastSourceValue dw = (FastSourceValue)w;\r
+\r
+ if (dv.getSize() == dw.getSize() && dv.containsAll(dw)) {\r
+ return v;\r
+ } else {\r
+ return new FastSourceValue(dv, dw);\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * This class is implemented based on SourceValue involved in ASM.\r
+ */\r
+/***\r
+ * ASM: a very small and fast Java bytecode manipulation framework\r
+ * Copyright (c) 2000-2007 INRIA, France Telecom\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ * 3. Neither the name of the copyright holders nor the names of its\r
+ * contributors may be used to endorse or promote products derived from\r
+ * this software without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\r
+ * THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+package soba.core.method.asm;\r
+\r
+\r
+\r
+import java.util.Arrays;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+\r
+import org.objectweb.asm.tree.analysis.Value;\r
+\r
+public class FastSourceValue implements Value {\r
+\r
+ /**\r
+ * The number of words indicating the value.\r
+ */\r
+ private final int size;\r
+\r
+ /**\r
+ * Index of instructions that define the value.\r
+ */\r
+ private int[] instructions;\r
+ \r
+ private static int[] EMPTY_ARRAY = new int[0];\r
+\r
+ public FastSourceValue(int size) {\r
+ this.size = size;\r
+ this.instructions = EMPTY_ARRAY;\r
+ }\r
+\r
+ public FastSourceValue(int size, int instructionIndex) {\r
+ this.size = size;\r
+ this.instructions = new int[] { instructionIndex };\r
+ }\r
+\r
+ public FastSourceValue(int size, int[] instructionIndices) {\r
+ this.size = size;\r
+ this.instructions = instructionIndices;\r
+ }\r
+ \r
+ public int getSize() {\r
+ return size;\r
+ }\r
+ \r
+ public int[] getInstructions() {\r
+ return instructions;\r
+ }\r
+ \r
+ public FastSourceValue(FastSourceValue base1, FastSourceValue base2) {\r
+ this.size = Math.min(base1.size, base2.size);\r
+ TIntArrayList list = new TIntArrayList(base1.instructions.length + base2.instructions.length);\r
+ int index1 = 0;\r
+ int index2 = 0;\r
+ while (index1 < base1.instructions.length && index2 < base2.instructions.length) {\r
+ int v1 = base1.instructions[index1];\r
+ int v2 = base2.instructions[index2];\r
+ if (v1 == v2) {\r
+ // Add only one element (behaves as "Set")\r
+ list.add(v1);\r
+ index1++;\r
+ index2++;\r
+ } else if (v1 < v2) {\r
+ list.add(v1);\r
+ index1++;\r
+ } else { //v1 > v2\r
+ list.add(v2);\r
+ index2++;\r
+ }\r
+ }\r
+ while (index1 < base1.instructions.length) {\r
+ list.add(base1.instructions[index1]);\r
+ index1++;\r
+ }\r
+ while (index2 < base2.instructions.length) {\r
+ list.add(base2.instructions[index2]);\r
+ index2++;\r
+ }\r
+ this.instructions = list.toArray();\r
+ }\r
+ \r
+ public boolean containsAll(FastSourceValue another) {\r
+ int thisIndex = 0;\r
+ int anotherIndex = 0;\r
+ while (anotherIndex < another.instructions.length) {\r
+ if (thisIndex < this.instructions.length) {\r
+ int thisValue = instructions[thisIndex];\r
+ int anotherValue = another.instructions[anotherIndex];\r
+ if (thisValue > anotherValue) {\r
+ return false;\r
+ } else if (thisValue == anotherValue) {\r
+ thisIndex++;\r
+ anotherIndex++;\r
+ } else { // thisValue < anotherValue\r
+ thisIndex++;\r
+ }\r
+ \r
+ } else {\r
+ // End of this.instructions, but not the end of another.instruction\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Two FastSourceValues are the same if the value \r
+ * is defined by the same instructions.\r
+ */\r
+ public boolean equals(Object another) {\r
+ if (another instanceof FastSourceValue) {\r
+ FastSourceValue v = (FastSourceValue)another;\r
+ return size == v.size && Arrays.equals(instructions, v.instructions);\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ public int hashCode() {\r
+ return Arrays.hashCode(instructions);\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.signature;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.signature.SignatureReader;\r
+import org.objectweb.asm.signature.SignatureVisitor;\r
+\r
+\r
+/**\r
+ * This class translates a method signature to \r
+ * a list of parameters, a return type, and a list of exception types.\r
+ * TODO The current implementation ignores type arguments for generic class.\r
+ */\r
+public class MethodSignatureReader {\r
+\r
+ private int paramCount;\r
+ private TypeVisitor returnTypeVisitor;\r
+ private List<TypeVisitor> exceptionTypeVisitors;\r
+ private List<TypeVisitor> paramTypeVisitors;\r
+\r
+ /**\r
+ * Creates a new <code>MethodSignatureReader</code> instance.\r
+ * @param signature\r
+ */\r
+ public MethodSignatureReader(String signature) {\r
+ exceptionTypeVisitors = new ArrayList<TypeVisitor>();\r
+ paramTypeVisitors = new ArrayList<TypeVisitor>();\r
+ paramCount = 0;\r
+\r
+ SignatureReader sigReader = new SignatureReader(signature);\r
+ sigReader.accept(new SignatureVisitor(Opcodes.ASM5) {\r
+ \r
+ /* \r
+ * SignatureVisitor for a method signature receives a following method call sequence.\r
+ * ( visitFormalTypeParameter visitClassBound? visitInterfaceBound* )* \r
+ * ( visitParameterType* \r
+ * visitReturnType \r
+ * visitExceptionType* )\r
+ */\r
+\r
+ @Override\r
+ public void visitFormalTypeParameter(String name) {\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitParameterType() {\r
+ paramCount++;\r
+ TypeVisitor param = new TypeVisitor();\r
+ paramTypeVisitors.add(param);\r
+ return param;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitReturnType() {\r
+ returnTypeVisitor = new TypeVisitor();\r
+ return returnTypeVisitor;\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitExceptionType() {\r
+ TypeVisitor exceptionTypeVisitor = new TypeVisitor();\r
+ exceptionTypeVisitors.add(exceptionTypeVisitor);\r
+ return exceptionTypeVisitor;\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public void visitTypeVariable(String name) {\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitTypeArgument(char wildcard) {\r
+ return this;\r
+ }\r
+ \r
+ @Override\r
+ public void visitTypeArgument() {\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitSuperclass() {\r
+ return this;\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitInterfaceBound() {\r
+ return this;\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitInterface() {\r
+ return this;\r
+ }\r
+ \r
+ @Override\r
+ public void visitInnerClassType(String name) {\r
+ }\r
+\r
+ @Override\r
+ public void visitEnd() {\r
+ }\r
+ \r
+ @Override\r
+ public void visitClassType(String name) {\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitClassBound() {\r
+ return this;\r
+ }\r
+ \r
+ @Override\r
+ public void visitBaseType(char descriptor) {\r
+ }\r
+ \r
+ @Override\r
+ public SignatureVisitor visitArrayType() {\r
+ return null;\r
+ }\r
+ });\r
+ assert paramTypeVisitors.size() == paramCount: \r
+ "Failed to read a method signature: paramTypes=" + Integer.toString(paramTypeVisitors.size()) + ", paramCount=" + Integer.toString(paramCount);\r
+ }\r
+ \r
+ public int getParamCount() {\r
+ return paramCount;\r
+ }\r
+ \r
+ public String getParamType(int paramIndex) {\r
+ return paramTypeVisitors.get(paramIndex).getTypeName();\r
+ }\r
+\r
+ public boolean isGenericType(int paramIndex) {\r
+ return paramTypeVisitors.get(paramIndex).isGenericType();\r
+ }\r
+ \r
+ public String getReturnType() {\r
+ return returnTypeVisitor.getTypeName();\r
+ }\r
+\r
+ public boolean isGenericReturnType() {\r
+ return returnTypeVisitor.isGenericType();\r
+ }\r
+ \r
+ public int getExceptionCount() {\r
+ return exceptionTypeVisitors.size();\r
+ }\r
+ \r
+ public String getExceptionType(int exceptionIndex) { \r
+ return exceptionTypeVisitors.get(exceptionIndex).getTypeName();\r
+ }\r
+\r
+ public boolean isGenericExceptionType(int exceptionIndex) { \r
+ return exceptionTypeVisitors.get(exceptionIndex).isGenericType();\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.signature;\r
+\r
+public class TypeConstants {\r
+\r
+ public static final String BOOLEAN = "boolean";\r
+ public static final String BYTE = "byte";\r
+ public static final String CHAR = "char";\r
+ public static final String SHORT = "short";\r
+ public static final String INT = "int";\r
+ public static final String LONG = "long";\r
+ public static final String FLOAT = "float";\r
+ public static final String DOUBLE = "double";\r
+ public static final String VOID = "void";\r
+ public static final String JAVA_STRING = "java/lang/String";\r
+ public static final String UNKNOWN_TYPE = "UNKNOWN-TYPE";\r
+ \r
+ public static final String[] PRIMITIVE_TYPES = {\r
+ BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE \r
+ };\r
+\r
+ /**\r
+ * @param name specifies a type name.\r
+ * @return true if a given name is a primitive types\r
+ * (excluding "void").\r
+ */\r
+ public static boolean isPrimitiveTypeName(String name) { \r
+ for (int i=0; i<PRIMITIVE_TYPES.length; ++i) {\r
+ if (PRIMITIVE_TYPES[i].equals(name)) return true;\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ /**\r
+ * return name == primitive || name == void\r
+ * NOTE: name is normal type name. (NOT bytecode descriptor, such as I or Z)\r
+ * @param name\r
+ * @return\r
+ */\r
+ public static boolean isPrimitiveOrVoid(String name) {\r
+ return isPrimitiveTypeName(name) || VOID.equals(name);\r
+ }\r
+ \r
+ /**\r
+ * return name == void\r
+ */\r
+ public static boolean isVoid(String name) {\r
+ return VOID.equals(name);\r
+ }\r
+ \r
+ /**\r
+ * @param name specifies a type.\r
+ * @return the number of words to store the type.\r
+ * 2 is returned for double and long. \r
+ */\r
+ public static int getWordCount(String name) {\r
+ if (DOUBLE.equals(name) || LONG.equals(name)) {\r
+ return 2;\r
+ } else {\r
+ return 1;\r
+ }\r
+ }\r
+ \r
+ public static boolean isJavaString(String name) {\r
+ return name != null && name.equals(JAVA_STRING);\r
+ }\r
+}\r
--- /dev/null
+package soba.core.signature;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import org.objectweb.asm.signature.SignatureReader;\r
+\r
+\r
+/**\r
+ * A utility class to resolve a type name using TypeVisitor.\r
+ */\r
+public class TypeResolver {\r
+\r
+ private static Map<String, String> types = null;\r
+ \r
+ /**\r
+ * Returns a readable text for a specified type descriptor.\r
+ * If failed to parse, return argument's typeDesc.\r
+ * @param typeDescriptor is a type descriptor of a single type.\r
+ * @return a type name corresponding to a specified descriptor.\r
+ */\r
+ public static String getTypeName(String typeDesc) {\r
+ if (typeDesc == null) { \r
+ return null;\r
+ }\r
+ if (types == null) {\r
+ types = new HashMap<>(4096);\r
+ }\r
+ if (types.containsKey(typeDesc)) {\r
+ return types.get(typeDesc);\r
+ } else {\r
+ SignatureReader sig = new SignatureReader(typeDesc);\r
+ TypeVisitor reader = new TypeVisitor();\r
+ try {\r
+ sig.acceptType(reader);\r
+ types.put(typeDesc, reader.getTypeName());\r
+ return reader.getTypeName();\r
+ } catch(Exception e) {\r
+ types.put(typeDesc, typeDesc);\r
+ return typeDesc;\r
+ }\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.signature;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.signature.SignatureVisitor;\r
+\r
+/**\r
+ * TypeVisitor processes a type signature representing a single type.\r
+ */\r
+public class TypeVisitor extends SignatureVisitor {\r
+ \r
+ // TypeVisitor receives the following method calls:\r
+ // ( visitBaseType | \r
+ // visitTypeVariable | \r
+ // visitArrayType | \r
+ // ( visitClassType visitTypeArgument* ( visitInnerClassType visitTypeArgument* )* visitEnd )\r
+ // )\r
+ \r
+ private int arrayDimension = 0;\r
+\r
+ private String typename = null;\r
+ private boolean generictype = false;\r
+ private List<TypeVisitor> typeArguments = null;\r
+ private List<String> innerTypeNames = null;\r
+ private List<List<TypeVisitor>> innerTypeArguments = null;\r
+ private boolean parsingInner = false;\r
+ \r
+ public TypeVisitor() {\r
+ super(Opcodes.ASM5);\r
+ }\r
+ \r
+ /**\r
+ * @return a type name.\r
+ * A return value may include generic types\r
+ * such as "java/util/List<java/lang/String>" and "java/util/List<T>".\r
+ */\r
+ public String getTypeName() {\r
+ StringBuilder buf = new StringBuilder();\r
+ buf.append(typename);\r
+ \r
+ if (typeArguments != null) {\r
+ buf.append("<");\r
+ for (int i=0; i<typeArguments.size(); ++i) {\r
+ if (i>0) buf.append(",");\r
+ buf.append(typeArguments.get(i).getTypeName());\r
+ }\r
+ buf.append(">");\r
+ }\r
+ if (innerTypeNames != null) {\r
+ for (int i=0; i<innerTypeNames.size(); ++i) {\r
+ buf.append(".");\r
+ buf.append(innerTypeNames.get(i));\r
+ \r
+ List<TypeVisitor> arguments = innerTypeArguments.get(i);\r
+ if (arguments.size() > 0) {\r
+ buf.append("<");\r
+ for (int innerArg=0; innerArg<arguments.size(); ++innerArg) {\r
+ if (innerArg>0) buf.append(",");\r
+ buf.append(arguments.get(innerArg).getTypeName());\r
+ }\r
+ buf.append(">");\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ for (int i=0; i<arrayDimension; ++i) {\r
+ buf.append("[]");\r
+ }\r
+ return buf.toString(); \r
+ }\r
+ \r
+ public boolean isGenericType() {\r
+ return generictype;\r
+ }\r
+ \r
+\r
+ @Override\r
+ public void visitBaseType(char descriptor) {\r
+ switch (descriptor) {\r
+ case 'Z':\r
+ typename = TypeConstants.BOOLEAN;\r
+ break;\r
+ case 'B':\r
+ typename = TypeConstants.BYTE;\r
+ break;\r
+ case 'C': \r
+ typename = TypeConstants.CHAR;\r
+ break;\r
+ case 'S':\r
+ typename = TypeConstants.SHORT;\r
+ break;\r
+ case 'I': \r
+ typename = TypeConstants.INT;\r
+ break;\r
+ case 'J': \r
+ typename = TypeConstants.LONG;\r
+ break;\r
+ case 'F':\r
+ typename = TypeConstants.FLOAT;\r
+ break;\r
+ case 'D':\r
+ typename = TypeConstants.DOUBLE;\r
+ break;\r
+ case 'V':\r
+ typename = TypeConstants.VOID;\r
+ break;\r
+ default:\r
+ typename = Character.toString(descriptor);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void visitTypeVariable(String name) {\r
+ typename = name;\r
+ generictype = true;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitArrayType() {\r
+ arrayDimension++;\r
+ return this; // reuse the object to parse the base type.\r
+ }\r
+\r
+ @Override\r
+ public void visitClassType(String name) {\r
+ typename = name;\r
+ }\r
+\r
+ private TypeVisitor createTypeArgumentVisitor() {\r
+ TypeVisitor argument = new TypeVisitor();\r
+ if (parsingInner) {\r
+ int size = innerTypeArguments.size();\r
+ innerTypeArguments.get(size-1).add(argument);\r
+ } else {\r
+ if (typeArguments == null) {\r
+ typeArguments = new ArrayList<TypeVisitor>();\r
+ }\r
+ typeArguments.add(argument);\r
+ }\r
+ return argument;\r
+ }\r
+ \r
+ @Override\r
+ public void visitTypeArgument() {\r
+ // Add a visitor and directly store data to the visitor.\r
+ TypeVisitor argument = createTypeArgumentVisitor();\r
+ argument.typename = "?";\r
+ argument.generictype = true;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitTypeArgument(char wildcard) {\r
+ TypeVisitor argument = createTypeArgumentVisitor();\r
+ return argument;\r
+ }\r
+\r
+ // Inner class type is separated from class type \r
+ // in order to process a nested generic class such as "C<T1>.INNER<T2>".\r
+ @Override\r
+ public void visitInnerClassType(String name) {\r
+ parsingInner = true;\r
+ if (innerTypeNames == null) {\r
+ innerTypeNames = new ArrayList<String>();\r
+ innerTypeArguments = new ArrayList<List<TypeVisitor>>();\r
+ }\r
+ innerTypeNames.add(name);\r
+ innerTypeArguments.add(new ArrayList<TypeVisitor>());\r
+ }\r
+\r
+ @Override\r
+ public void visitEnd() {\r
+ }\r
+\r
+ \r
+\r
+ // Following methods are not used.\r
+ \r
+ @Override\r
+ public SignatureVisitor visitInterface() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitInterfaceBound() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitClassBound() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitExceptionType() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public void visitFormalTypeParameter(String name) {\r
+ assert false: "This method never used for parsing a type.";\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitParameterType() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitReturnType() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public SignatureVisitor visitSuperclass() {\r
+ assert false: "This method never used for parsing a type.";\r
+ return null;\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import soba.core.ClassHierarchy;\r
+import soba.core.IDynamicBindingResolver;\r
+import soba.core.JavaProgram;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.CallSite;\r
+\r
+/**\r
+ * This class wraps VTA resolver and CHA resolver. \r
+ * While VTAResolver resolves only INVOKEVIRTUAL and INVOKEINTERFACE, \r
+ * this wrapper object can easily resolve all invocations using both algorithms.\r
+ */\r
+public abstract class CallResolver implements IDynamicBindingResolver {\r
+\r
+ public static CallResolver getCHA(JavaProgram program) {\r
+ return new CHA(program.getClassHierarchy());\r
+ }\r
+\r
+ public static CallResolver getCHA(ClassHierarchy hierarchy) {\r
+ return new CHA(hierarchy);\r
+ }\r
+ \r
+ public static CallResolver getVTA(JavaProgram program, IAnalysisTarget selector) {\r
+ VTAResolver vta = new VTAResolver(program, selector);\r
+ return new VTA(program.getClassHierarchy(), vta);\r
+ }\r
+ \r
+ public static CallResolver getVTA(JavaProgram program) {\r
+ VTAResolver vta = new VTAResolver(program);\r
+ return new VTA(program.getClassHierarchy(), vta);\r
+ }\r
+ \r
+ /**\r
+ * @param c specifies a call site. This instance must be extracted from MethodBody object.\r
+ * @return\r
+ */\r
+ public abstract MethodInfo[] resolveCall(CallSite cs);\r
+ \r
+ private static class CHA extends CallResolver {\r
+ \r
+ private ClassHierarchy ch;\r
+ \r
+ public CHA(ClassHierarchy ch) {\r
+ this.ch = ch;\r
+ }\r
+ \r
+ public MethodInfo[] resolveCall(CallSite c) {\r
+ return ch.resolveCall(c);\r
+ }\r
+ }\r
+ \r
+ private static class VTA extends CallResolver {\r
+ \r
+ private ClassHierarchy ch;\r
+ private VTAResolver vta;\r
+ \r
+ public VTA(ClassHierarchy ch, VTAResolver vta) {\r
+ this.ch = ch;\r
+ this.vta = vta;\r
+ }\r
+ \r
+ public MethodInfo[] resolveCall(CallSite c) {\r
+ if (c.isStaticOrSpecial()) {\r
+ return ch.resolveCall(c);\r
+ } else {\r
+ return vta.resolveCall(c);\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * \r
+ */\r
+package soba.core.vta;\r
+\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+\r
+import java.util.ArrayList;\r
+\r
+import soba.core.method.CallSite;\r
+import soba.core.signature.MethodSignatureReader;\r
+import soba.core.signature.TypeConstants;\r
+\r
+/**\r
+ * An instance of MethodParameters represents \r
+ * a set of formal parameters of a method \r
+ * or a set of actual parameters of a method call.\r
+ */\r
+class CallSiteVertices {\r
+ \r
+ /**\r
+ * The number of formal parameters of the method.\r
+ * This includes "this", excludes a return value.\r
+ */\r
+ private int paramCount;\r
+ \r
+ /**\r
+ * paramIndex[vertexIndex] specifies the position \r
+ * of a method argument corresponding to the vertex.\r
+ * If paramIndex[vertexIndex] == paramCount, \r
+ * the vertex represents a return value. \r
+ */\r
+ private int[] paramIndex;\r
+\r
+\r
+ /**\r
+ * vertexIDs[vertexIndex] indicates a vertex ID.\r
+ */\r
+ private int[] vertexIDs;\r
+ \r
+ /**\r
+ * vertexTypes[vertexIndex] indicates a type name\r
+ * corresponding to the vertex.\r
+ */\r
+ private String[] vertexTypes;\r
+ \r
+ /**\r
+ * Additional parameter for for actual parameters.\r
+ */\r
+ private CallSite callsite;\r
+ \r
+ public CallSiteVertices(CallSite c, int startID) {\r
+ this.callsite = c;\r
+ MethodSignatureReader sig = new MethodSignatureReader(c.getDescriptor());\r
+ if (!c.isStaticMethod()) {\r
+ paramCount = sig.getParamCount() + 1;\r
+ } else {\r
+ paramCount = sig.getParamCount();\r
+ }\r
+\r
+ TIntArrayList params = new TIntArrayList(paramCount);\r
+ ArrayList<String> types = new ArrayList<String>(paramCount);\r
+ int thisCount = 0;\r
+ if (!c.isStaticMethod()) {\r
+ thisCount = 1;\r
+ params.add(0);\r
+ types.add(c.getClassName());\r
+ }\r
+ \r
+ for (int i=0; i<sig.getParamCount(); ++i) {\r
+ String t = sig.getParamType(i);\r
+ if (!TypeConstants.isPrimitiveOrVoid(t)) {\r
+ params.add(i+thisCount);\r
+ types.add(t+thisCount);\r
+ }\r
+ }\r
+ if (!TypeConstants.isPrimitiveOrVoid(sig.getReturnType())) {\r
+ params.add(paramCount);\r
+ types.add(sig.getReturnType());\r
+ }\r
+ paramIndex = params.toArray();\r
+ vertexTypes = types.toArray(new String[0]);\r
+ vertexIDs = new int[paramIndex.length];\r
+ for (int i=0; i<paramIndex.length; ++i) {\r
+ vertexIDs[i] = startID + i;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return a call site information.\r
+ * This method returns non-null for objects \r
+ * representing actual-parameters.\r
+ */\r
+ public CallSite getCallSite() {\r
+ return callsite;\r
+ }\r
+ \r
+ public boolean isObjectParam(int param) {\r
+ for (int i=0; i<paramIndex.length; ++i) {\r
+ if (paramIndex[i] == param) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ /**\r
+ * @param param specifies the position of a method parameter\r
+ * in the parameter list.\r
+ * @return\r
+ */\r
+ public int getParamVertexId(int param) {\r
+ for (int i=0; i<paramIndex.length; ++i) {\r
+ if (paramIndex[i] == param) {\r
+ return vertexIDs[i];\r
+ }\r
+ }\r
+ return VTAResolver.VERTEX_ERROR;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of input parameters.\r
+ */\r
+ public int getParamCount() {\r
+ return paramCount;\r
+ }\r
+ \r
+ public boolean hasReturnValue() {\r
+ if (paramIndex.length > 0) { \r
+ return paramIndex[paramIndex.length-1] == paramCount;\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ public int getReturnValueVertex() {\r
+ if (hasReturnValue()) {\r
+ return vertexIDs[paramIndex.length-1];\r
+ } else {\r
+ return VTAResolver.VERTEX_ERROR;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return the number of vertices \r
+ */\r
+ public int getVertexCount() {\r
+ return paramIndex.length;\r
+ }\r
+ \r
+ /**\r
+ * @param vertexIndex specifies a vertex: 0 .. getVertexCount()-1. \r
+ * @return vertex ID.\r
+ */\r
+ public int getVertex(int vertexIndex) {\r
+ return vertexIDs[vertexIndex];\r
+ }\r
+ \r
+ public String getTypeName(int vertexIndex) {\r
+ return vertexTypes[vertexIndex];\r
+ }\r
+ \r
+ public String getReturnValueTypeName() {\r
+ if (hasReturnValue()) {\r
+ return vertexTypes[paramIndex.length-1];\r
+ } else {\r
+ assert false;\r
+ return null;\r
+ }\r
+ }\r
+ \r
+}
\ No newline at end of file
--- /dev/null
+package soba.core.vta;\r
+\r
+import soba.core.FieldInfo;\r
+import soba.core.MethodInfo;\r
+\r
+public interface IAnalysisTarget {\r
+\r
+ public boolean isTargetMethod(MethodInfo m);\r
+ public boolean isTargetField(FieldInfo f);\r
+ public boolean assumeExternalCallers(MethodInfo m);\r
+\r
+ public boolean isExcludedType(String className);\r
+ \r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+public interface ITopologicalVisitor {\r
+ \r
+ /**\r
+ * @param vertexId identifies a visited vertex.\r
+ * @return true if you want to continue visiting \r
+ * process beyond the vertex.\r
+ */\r
+ public boolean onVisit(int vertexId);\r
+\r
+ public void onFinished();\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+\r
+import java.util.ArrayList;\r
+\r
+import soba.core.MethodInfo;\r
+import soba.core.method.LocalVariables;\r
+import soba.core.signature.TypeConstants;\r
+\r
+public class MethodVertices {\r
+ \r
+ \r
+ private LocalVariables locals;\r
+ private int[] variableIndex;\r
+\r
+ private int[] vertexIDs;\r
+ private int returnVertexID;\r
+\r
+ private int[] paramVertexIDs;\r
+ private int vertexCount = 0;\r
+ \r
+ private ArrayList<String> typenames;\r
+\r
+ public MethodVertices(MethodInfo m, LocalVariables table, int startID) {\r
+ this.locals = table;\r
+ this.variableIndex = new int[m.getParamCount()];\r
+ this.typenames = new ArrayList<String>();\r
+ \r
+ paramVertexIDs = new int[m.getParamCount()];\r
+ int vID = startID;\r
+ int var = 0;\r
+ for (int i=0; i<m.getParamCount(); ++i) {\r
+ String type = m.getParamType(i);\r
+ variableIndex[i] = var;\r
+ var += TypeConstants.getWordCount(type);\r
+ if (!TypeConstants.isPrimitiveOrVoid(type)) {\r
+ paramVertexIDs[i] = vID;\r
+ vID++;\r
+ typenames.add(type);\r
+ }\r
+ }\r
+ \r
+ TIntArrayList vertices = new TIntArrayList();\r
+ for (int i=0; i<locals.getVariableEntryCount(); ++i) {\r
+ if (locals.isObjectVariable(i)) {\r
+ if (locals.isParameter(i)) {\r
+ int paramIndex = getParamIndex(locals.getVariableIndex(i));\r
+ vertices.add(paramVertexIDs[paramIndex]);\r
+ } else {\r
+ vertices.add(vID);\r
+ vID++;\r
+ String t = locals.getVariableType(i);\r
+ if (t == null) {\r
+ if (locals.isArrayVariable(i)) {\r
+ t = TypeSet.DEFAULT_UNKNOWN_ARRAYTYPE;\r
+ } else {\r
+ t = TypeSet.DEFAULT_UNKNOWN_TYPE;\r
+ }\r
+ }\r
+ typenames.add(t);\r
+ }\r
+ } else {\r
+ vertices.add(VTAResolver.VERTEX_ERROR);\r
+ }\r
+ }\r
+ vertexIDs = vertices.toArray();\r
+ \r
+ if (!TypeConstants.isPrimitiveOrVoid(m.getReturnType())) {\r
+ returnVertexID = vID;\r
+ vID++;\r
+ typenames.add(m.getReturnType());\r
+ }\r
+ vertexCount = vID - startID;\r
+ }\r
+ \r
+ public int getLocalVertex(int instruction) {\r
+ int entryIndex = locals.findEntryForInstruction(instruction);\r
+ if (entryIndex != -1) {\r
+ return vertexIDs[entryIndex];\r
+ } else {\r
+ // Some local variables are assigned but never used.\r
+ return VTAResolver.VERTEX_ERROR;\r
+ }\r
+ }\r
+ \r
+ public int getReturnVertex() {\r
+ return returnVertexID;\r
+ }\r
+ \r
+ public int getFormalVertex(int paramIndex) {\r
+ return paramVertexIDs[paramIndex];\r
+ }\r
+ \r
+ public boolean hasFormalVertex(int paramIndex) {\r
+ return paramVertexIDs[paramIndex] != VTAResolver.VERTEX_ERROR;\r
+ }\r
+ \r
+ private int getParamIndex(int varIndex) {\r
+ for (int i=0; i<variableIndex.length; ++i) {\r
+ if (variableIndex[i] == varIndex) {\r
+ return i;\r
+ }\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ public int getVertexCount() {\r
+ return vertexCount;\r
+ }\r
+ \r
+ public String getTypeName(int vertexIndex) { \r
+ return typenames.get(vertexIndex);\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+\r
+import java.util.ArrayList;\r
+\r
+\r
+\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.InsnList;\r
+import org.objectweb.asm.tree.MultiANewArrayInsnNode;\r
+import org.objectweb.asm.tree.TypeInsnNode;\r
+\r
+import soba.core.signature.TypeResolver;\r
+\r
+public class NewVertices {\r
+\r
+ private int[] instructionIndices;\r
+ private int[] vertexIDs;\r
+ private String[] types;\r
+\r
+ \r
+ public NewVertices(InsnList instructions, int startID) {\r
+ TIntArrayList indices = new TIntArrayList(); \r
+ TIntArrayList vIDs = new TIntArrayList(); \r
+ ArrayList<String> typeNames = new ArrayList<String>();\r
+ int vID = startID;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ AbstractInsnNode instruction = instructions.get(i);\r
+ if (instruction.getOpcode() == Opcodes.NEW) {\r
+ indices.add(i);\r
+ vIDs.add(vID);\r
+ vID++;\r
+ typeNames.add(((TypeInsnNode)instruction).desc);\r
+ } else if (instruction.getOpcode() == Opcodes.ANEWARRAY) {\r
+ indices.add(i);\r
+ vIDs.add(vID);\r
+ vID++;\r
+ String desc = ((TypeInsnNode)instruction).desc;\r
+ if (desc.startsWith("[")) {\r
+ typeNames.add(TypeResolver.getTypeName(desc));\r
+ } else {\r
+ typeNames.add(desc + "[]");\r
+ }\r
+ } else if (instruction.getOpcode() == Opcodes.MULTIANEWARRAY) {\r
+ indices.add(i);\r
+ vIDs.add(vID);\r
+ vID++;\r
+ typeNames.add(TypeResolver.getTypeName(((MultiANewArrayInsnNode)instruction).desc));\r
+ }\r
+ // Implementation Note: NEWARRAY is not included because the instruction generates an array of primitive values. \r
+ }\r
+ instructionIndices = indices.toArray();\r
+ vertexIDs = vIDs.toArray();\r
+ types = new String[typeNames.size()]; \r
+ for (int i=0; i<types.length; ++i) {\r
+ types[i] = typeNames.get(i);\r
+ }\r
+ }\r
+ \r
+ public int getNewInstructionVertex(int instructionIndex) {\r
+ for (int i=0; i<instructionIndices.length; ++i) {\r
+ if (instructionIndices[i] == instructionIndex) {\r
+ return vertexIDs[i];\r
+ }\r
+ }\r
+ return VTAResolver.VERTEX_ERROR;\r
+ }\r
+ \r
+ public int getVertex(int vertexIndex) {\r
+ return vertexIDs[vertexIndex];\r
+ }\r
+ \r
+ public String getTypeName(int vertexIndex) {\r
+ return types[vertexIndex];\r
+ }\r
+ \r
+ public int getVertexCount() {\r
+ return vertexIDs.length;\r
+ }\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import soba.util.graph.DirectedAcyclicGraph;\r
+\r
+public class TopologicalOrderSearch {\r
+\r
+ /**\r
+ * Visit vertices in their topological order.\r
+ * @param graph\r
+ * @param visit\r
+ */\r
+ public static void searchFromRoot(final DirectedAcyclicGraph graph, ITopologicalVisitor visit) {\r
+ // To find root vertices, count the number of incoming edges for each vertex.\r
+ int[] incoming = new int[graph.getVertexCount()];\r
+ for (int i=0; i<graph.getVertexCount(); ++i) {\r
+ for (int to: graph.getEdges(i)) {\r
+ incoming[to] += 1;\r
+ }\r
+ }\r
+ \r
+ // Push root vertices into the queue.\r
+ int[] queue = new int[graph.getVertexCount()];\r
+ int queueEndIndex = 0;\r
+ for (int i=0; i<graph.getVertexCount(); ++i) {\r
+ if (incoming[i] == 0 && graph.isRepresentativeNode(i)) {\r
+ queue[queueEndIndex] = i;\r
+ queueEndIndex++;\r
+ }\r
+ }\r
+ \r
+ // Main Loop\r
+ int queueIndex = 0;\r
+ while (queueIndex < queueEndIndex) {\r
+ int v = queue[queueIndex];\r
+ queueIndex++;\r
+ boolean continueVisit = visit.onVisit(v);\r
+ if (continueVisit) {\r
+ for (int to: graph.getEdges(v)) {\r
+ incoming[to] -= 1;\r
+ if (incoming[to] == 0) {\r
+ queue[queueEndIndex] = to;\r
+ queueEndIndex++;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ visit.onFinished();\r
+ }\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import java.util.List;\r
+\r
+public class TypeSet {\r
+ \r
+ public static final String DEFAULT_UNKNOWN_TYPE = "java/lang/Object";\r
+ public static final String DEFAULT_UNKNOWN_ARRAYTYPE = DEFAULT_UNKNOWN_TYPE + "[]";\r
+\r
+ private TypeSetManager manager;\r
+ private int typesId;\r
+ private int approximatedTypesId;\r
+ \r
+ /**\r
+ * Creates a new <code>TypeSet</code> instance without type information.\r
+ * @param manager\r
+ */\r
+ public TypeSet(TypeSetManager manager) {\r
+ this.manager = manager;\r
+ this.typesId = manager.getEmptyId();\r
+ this.approximatedTypesId = manager.getEmptyId();\r
+ }\r
+ \r
+ /**\r
+ * Creates a new <code>TypeSet</code> with a single type. \r
+ * @param manager\r
+ * @param typename\r
+ */\r
+ public TypeSet(TypeSetManager manager, String typename) { \r
+ this.manager = manager;\r
+ assert typename != null;\r
+ this.typesId = manager.getId(typename);\r
+ this.approximatedTypesId = manager.getEmptyId();\r
+ }\r
+\r
+ /**\r
+ * Creates a new <code>TypeSet</code> with multiple types.\r
+ * @param manager\r
+ * @param parents\r
+ */\r
+ public TypeSet(TypeSetManager manager, List<TypeSet> parents) {\r
+ this.manager = manager;\r
+ if (parents.size() == 0) {\r
+ this.typesId = manager.getEmptyId();\r
+ this.approximatedTypesId = manager.getEmptyId();\r
+ } else if (parents.size() == 1) {\r
+ this.typesId = parents.get(0).typesId;\r
+ this.approximatedTypesId = parents.get(0).approximatedTypesId;\r
+ } else {\r
+ int mergeTypesId = parents.get(0).typesId;\r
+ int mergeAppoximatedTypesId = parents.get(0).approximatedTypesId;\r
+ for (int i=1; i<parents.size(); ++i) {\r
+ mergeTypesId = manager.merge(mergeTypesId, parents.get(i).typesId);\r
+ mergeAppoximatedTypesId = manager.merge(mergeAppoximatedTypesId, parents.get(i).approximatedTypesId);\r
+ }\r
+ this.typesId = mergeTypesId;\r
+ this.approximatedTypesId = mergeAppoximatedTypesId;\r
+ }\r
+ }\r
+ \r
+ public TypeSet addType(String additionalType) {\r
+ TypeSet copy = new TypeSet(manager);\r
+ copy.typesId = manager.merge(this.typesId, manager.getId(additionalType));\r
+ copy.approximatedTypesId = this.approximatedTypesId;\r
+ return copy;\r
+ }\r
+\r
+ public static TypeSet createApproximation(TypeSetManager manager, String typename) {\r
+ TypeSet t = new TypeSet(manager);\r
+ t.approximatedTypesId = manager.getId(typename); \r
+ return t;\r
+ }\r
+\r
+ public TypeSet addApproximatedType(String additionalType) {\r
+ TypeSet copy = new TypeSet(manager);\r
+ copy.typesId = this.typesId;\r
+ copy.approximatedTypesId = manager.merge(this.approximatedTypesId, manager.getId(additionalType));\r
+ return copy;\r
+ }\r
+ \r
+\r
+ public boolean contains(String t) {\r
+ if (t == null) return false;\r
+ String[] types = manager.getStrings(this.typesId);\r
+ for (int i=0; i<types.length; ++i) {\r
+ if (t.equals(types[i])) return true;\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ public int getTypeCount() { \r
+ return manager.getStrings(typesId).length;\r
+ }\r
+ \r
+ public String getType(int index) {\r
+ return manager.getStrings(typesId)[index];\r
+ }\r
+ \r
+ public int getApproximatedTypeCount() {\r
+ return manager.getStrings(approximatedTypesId).length;\r
+ }\r
+ \r
+ public String getApproximatedType(int index) {\r
+ return manager.getStrings(approximatedTypesId)[index];\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+\r
+import gnu.trove.map.hash.TIntIntHashMap;\r
+import gnu.trove.map.hash.TIntObjectHashMap;\r
+\r
+import java.util.Arrays;\r
+import java.util.HashSet;\r
+\r
+import soba.util.ObjectIdMap;\r
+\r
+public class TypeSetManager {\r
+\r
+ private ObjectIdMap<String> cache;\r
+ private TIntObjectHashMap<String[]> strings;\r
+ private static final String[] EMPTY = new String[0];\r
+ private static final String SEPARATOR = "|";\r
+ private static final String SEPARATOR_REGEX = "\\|";\r
+ \r
+ private TIntObjectHashMap<TIntIntHashMap> mergeMap;\r
+ \r
+ public TypeSetManager() {\r
+ cache = new ObjectIdMap<String>();\r
+ strings = new TIntObjectHashMap<String[]>();\r
+ cache.add("");\r
+ \r
+ mergeMap = new TIntObjectHashMap<TIntIntHashMap>();\r
+ }\r
+ \r
+ public int getEmptyId() {\r
+ return cache.getId("");\r
+ }\r
+ \r
+ public int getId(String singleString) {\r
+ return cache.getId(singleString);\r
+ }\r
+ \r
+ public int getId(String[] strings) {\r
+ return cache.getId(toSingleString(strings));\r
+ }\r
+ \r
+ public String[] getStrings(int id) {\r
+ if (strings.containsKey(id)) {\r
+ return strings.get(id);\r
+ } else {\r
+ String s = cache.getItem(id);\r
+ if (s.length() == 0) {\r
+ return EMPTY;\r
+ } else {\r
+ String[] array = s.split(SEPARATOR_REGEX);\r
+ strings.put(id, array);\r
+ return array;\r
+ }\r
+ }\r
+ }\r
+ \r
+ public int merge(int id1, int id2) {\r
+ if (id1 > id2) {\r
+ int swap = id1;\r
+ id1 = id2;\r
+ id2 = swap;\r
+ }\r
+ TIntIntHashMap map = mergeMap.get(id1);\r
+ if (map != null) {\r
+ if (map.containsKey(id2)) {\r
+ return map.get(id2);\r
+ }\r
+ }\r
+ \r
+ HashSet<String> union = new HashSet<String>();\r
+ for (String s: getStrings(id1)) {\r
+ union.add(s);\r
+ }\r
+ for (String s: getStrings(id2)) {\r
+ union.add(s);\r
+ }\r
+ String[] unionStrings = union.toArray(EMPTY);\r
+ Arrays.sort(unionStrings);\r
+ int result = getId(unionStrings);\r
+ if (map == null) {\r
+ map = new TIntIntHashMap();\r
+ mergeMap.put(id1, map);\r
+ }\r
+ map.put(id2, result);\r
+ return result;\r
+ }\r
+ \r
+ private String toSingleString(String[] types) {\r
+ StringBuilder b = new StringBuilder();\r
+ for (int i=0; i<types.length; ++i) {\r
+ if (i > 0) b.append(SEPARATOR);\r
+ b.append(types[i]);\r
+ }\r
+ return b.toString();\r
+ }\r
+\r
+ public int size() {\r
+ int total = 0;\r
+ for (int i=0; i<cache.size(); ++i) {\r
+ total += cache.getItem(i).length();\r
+ }\r
+ return total;\r
+ }\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+import gnu.trove.map.hash.TIntObjectHashMap;\r
+import gnu.trove.procedure.TIntObjectProcedure;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.objectweb.asm.tree.AbstractInsnNode;\r
+import org.objectweb.asm.tree.FieldInsnNode;\r
+import org.objectweb.asm.tree.InsnList;\r
+import org.objectweb.asm.tree.MethodNode;\r
+import org.objectweb.asm.tree.TryCatchBlockNode;\r
+import org.objectweb.asm.Opcodes;\r
+\r
+import soba.core.ClassHierarchy;\r
+import soba.core.ClassInfo;\r
+import soba.core.FieldInfo;\r
+import soba.core.IDynamicBindingResolver;\r
+import soba.core.JavaProgram;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.CallSite;\r
+import soba.core.method.DataDependence;\r
+import soba.core.signature.TypeConstants;\r
+import soba.core.signature.TypeResolver;\r
+import soba.util.IntPairList;\r
+import soba.util.graph.DirectedAcyclicGraph;\r
+import soba.util.graph.DirectedGraph;\r
+\r
+public class VTAResolver implements IDynamicBindingResolver {\r
+ \r
+ public static int VERTEX_ERROR = 0;\r
+ private static final String ARRAY_SUFFIX = "[]";\r
+\r
+ private Map<FieldInfo, FieldVertex> fieldVertex;\r
+ private Map<MethodInfo, CallSiteVertices[]> callsiteMap; // methodInfo * instructionIndex -> callsite\r
+ private Map<MethodInfo, NewVertices> newVerticesMap;\r
+ private Map<MethodInfo, MethodVertices> localVerticesMap;\r
+ private TIntObjectHashMap<String> catchVariableVertices;\r
+ \r
+ private ArrayList<String> declaredTypeNames; // vertex ID -> type name (constraint) of the vertex.\r
+ \r
+ private ClassHierarchy hierarchy;\r
+ private IAnalysisTarget target;\r
+ \r
+ private IntPairList edges;\r
+ \r
+ private TypeSet[] reachingTypes;\r
+\r
+ private TypeSetManager typeSetManager;\r
+ \r
+ /**\r
+ * Creates a new <code>VTAResolver</code> instance.\r
+ * All methods and fields in the program are analyzed.\r
+ * @param program\r
+ */\r
+ public VTAResolver(final JavaProgram program) {\r
+ this(program, new IAnalysisTarget() {\r
+ @Override\r
+ public boolean isTargetMethod(MethodInfo m) {\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public boolean isTargetField(FieldInfo f) {\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public boolean isExcludedType(String className) {\r
+ return false;\r
+ }\r
+ \r
+ @Override\r
+ public boolean assumeExternalCallers(MethodInfo m) {\r
+ return false;\r
+ }\r
+ });\r
+ }\r
+ \r
+ /**\r
+ * Creates a new <code>VTAResolver</code> instance.\r
+ * @param program\r
+ * @param selector specifies the analysis target in the program.\r
+ */\r
+ public VTAResolver(final JavaProgram program, final IAnalysisTarget selector) {\r
+ target = selector;\r
+ edges = new IntPairList(65536);\r
+ hierarchy = program.getClassHierarchy();\r
+ \r
+ callsiteMap = new HashMap<MethodInfo, CallSiteVertices[]>();\r
+ newVerticesMap = new HashMap<MethodInfo, NewVertices>();\r
+ fieldVertex = new HashMap<FieldInfo, FieldVertex>();\r
+ localVerticesMap = new HashMap<MethodInfo, MethodVertices>();\r
+ catchVariableVertices = new TIntObjectHashMap<String>();\r
+ declaredTypeNames = new ArrayList<String>(65536);\r
+ List<CallSiteVertices> callsitesWithoutCallees = new ArrayList<CallSiteVertices>(); \r
+ \r
+ // Create vertices for inter-procedural connection\r
+ fieldVertex = new HashMap<FieldInfo, FieldVertex>();\r
+ int vID = VERTEX_ERROR+1;\r
+ declaredTypeNames.add(TypeSet.DEFAULT_UNKNOWN_TYPE);\r
+ for (ClassInfo c: program.getClasses()) {\r
+ for (int mIndex = 0; mIndex < c.getMethodCount(); mIndex++) {\r
+ MethodInfo m = c.getMethod(mIndex);\r
+ if (m.hasMethodBody() && (selector == null || selector.isTargetMethod(m))) {\r
+ // Create vertices for local variables (including formal parameters)\r
+// MethodBody body = m.getMethodBody();\r
+ DataDependence dataflow = m.getDataDependence();\r
+ MethodVertices localVertices = new MethodVertices(m, dataflow.getLocalVariables(), vID);\r
+ this.localVerticesMap.put(m, localVertices);\r
+ vID += localVertices.getVertexCount();\r
+ for (int i=0; i<localVertices.getVertexCount(); ++i) {\r
+ declaredTypeNames.add(localVertices.getTypeName(i));\r
+ }\r
+ }\r
+ }\r
+ for (int fIndex = 0; fIndex < c.getFieldCount(); fIndex++) {\r
+ FieldInfo f = c.getField(fIndex);\r
+ if (!TypeConstants.isPrimitiveTypeName(f.getFieldTypeName())) {\r
+ FieldVertex fv = new FieldVertex(f, vID);\r
+ fieldVertex.put(f, fv);\r
+ vID++;\r
+ declaredTypeNames.add(fv.getTypeName());\r
+ }\r
+ }\r
+ }\r
+ \r
+ // Build a type propagation graph\r
+ for (ClassInfo c: program.getClasses()) {\r
+ for (int mIndex = 0; mIndex < c.getMethodCount(); mIndex++) {\r
+ MethodInfo m = c.getMethod(mIndex);\r
+ if (m.hasMethodBody() && (selector == null || selector.isTargetMethod(m))) {\r
+// MethodBody body = m.getMethodBody();\r
+ DataDependence dataflow = m.getDataDependence();\r
+ \r
+ MethodNode mnode = m.getMethodNode();\r
+\r
+ // Create vertices for "new" instructions \r
+ NewVertices newVertices = new NewVertices(mnode.instructions, vID);\r
+ vID += newVertices.getVertexCount();\r
+ this.newVerticesMap.put(m, newVertices);\r
+ for (int i=0; i<newVertices.getVertexCount(); ++i) {\r
+ declaredTypeNames.add(newVertices.getTypeName(i));\r
+ assert newVertices.getTypeName(i) != null;\r
+ }\r
+\r
+ // Create vertices for method invocations.\r
+ // Connect inter-procedural edges.\r
+ CallSiteVertices[] callsites = new CallSiteVertices[m.getInstructionCount()];\r
+ callsiteMap.put(m, callsites);\r
+\r
+ for (CallSite callsite: m.getCallSites()) {\r
+ MethodInfo[] methods = hierarchy.resolveCall(callsite);\r
+ CallSiteVertices actuals = new CallSiteVertices(callsite, vID);\r
+ callsites[callsite.getInstructionIndex()] = actuals;\r
+ vID += actuals.getVertexCount();\r
+ for (int i=0; i<actuals.getVertexCount(); ++i) {\r
+ declaredTypeNames.add(actuals.getTypeName(i));\r
+ }\r
+\r
+ boolean methodNotIncluded = false;\r
+ if (methods.length > 0) {\r
+ for (MethodInfo called: methods) {\r
+ \r
+ MethodVertices formals = localVerticesMap.get(called);\r
+ if (formals != null) {\r
+ for (int i=0; i<actuals.getParamCount(); ++i) {\r
+ if (actuals.isObjectParam(i)) {\r
+ assert actuals.getParamVertexId(i) != VERTEX_ERROR;\r
+ assert formals.getFormalVertex(i) != VERTEX_ERROR;\r
+ \r
+ addEdge(actuals.getParamVertexId(i), formals.getFormalVertex(i));\r
+ }\r
+ }\r
+ if (actuals.hasReturnValue()) {\r
+ assert formals.getReturnVertex() != VERTEX_ERROR;\r
+ assert actuals.getReturnValueVertex() != VERTEX_ERROR;\r
+ addEdge(formals.getReturnVertex(), actuals.getReturnValueVertex());\r
+ }\r
+ } else {\r
+ // The method may be out of target.\r
+ methodNotIncluded = true;\r
+ }\r
+ }\r
+ }\r
+ if ((methodNotIncluded || methods.length == 0) && actuals.hasReturnValue()) callsitesWithoutCallees.add(actuals);\r
+\r
+ }\r
+\r
+ \r
+ // Process instructions in a method\r
+ for (int i=0; i<m.getInstructionCount(); ++i) {\r
+ AbstractInsnNode instruction = mnode.instructions.get(i);\r
+ analyzeInstruction(i, instruction, m, dataflow);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // Construct a graph object\r
+ DirectedGraph graph = new DirectedGraph(vID, edges);\r
+ DirectedAcyclicGraph typePropagationDAG = new DirectedAcyclicGraph(graph);\r
+\r
+ this.typeSetManager = new TypeSetManager();\r
+ assignTypes(typePropagationDAG, callsitesWithoutCallees, selector);\r
+ propagateTypes(typePropagationDAG);\r
+ }\r
+ \r
+ \r
+ /**\r
+ * This method resolves dynamic binding for INVOKEVIRTUAL/INVOKEINTERFACE calls.\r
+ * It should be noted that this binding does not correctly return binding information\r
+ * for INVOKESPECIAL and INVOKESTATIC.\r
+ * @param cs specifies a method call.\r
+ * @return an array of <code>IMethodInfo</code> objects that are invoked by \r
+ * the specified invocation instruction.\r
+ * The array is a subset of CHA result (returned by <code>ClassHierarchy</code>).\r
+ * The array excludes unreachable types according to VTA analysis\r
+ * and a type filter represented by IAnalysisTarget.isExcludedType().\r
+ */\r
+ @Override\r
+ public MethodInfo[] resolveCall(CallSite cs) {\r
+ int instruction = cs.getInstructionIndex();\r
+ CallSiteVertices[] callsites = callsiteMap.get(cs.getOwnerMethod());\r
+ if (callsites != null) {\r
+ CallSiteVertices params = callsites[instruction];\r
+ if (params != null) {\r
+ HashSet<MethodInfo> called = new HashSet<>();\r
+ int v = params.getParamVertexId(0);\r
+ TypeSet types = reachingTypes[v];\r
+ String methodName = params.getCallSite().getMethodName();\r
+ String methodDesc = params.getCallSite().getDescriptor();\r
+ if (types != null) {\r
+ ArrayList<String> declaredType = new ArrayList<String>();\r
+ declaredType.add(declaredTypeNames.get(v));\r
+ Collection<String> declaredSubtypes = hierarchy.getAllSubtypes(declaredType);\r
+ \r
+ for (int i=0; i<types.getTypeCount(); ++i) {\r
+ String className = types.getType(i);\r
+ if (declaredSubtypes.contains(className)) {\r
+ MethodInfo m = hierarchy.resolveSpecialCall(className, methodName, methodDesc);\r
+ if (m != null && !target.isExcludedType(m.getClassName())) {\r
+ called.add(m);\r
+ }\r
+ }\r
+ }\r
+ \r
+ ArrayList<String> approxTypes = new ArrayList<String>();\r
+ for (int i=0; i<types.getApproximatedTypeCount(); ++i) {\r
+ approxTypes.add(types.getApproximatedType(i));\r
+ }\r
+ Collection<String> subtypes = hierarchy.getAllSubtypes(approxTypes);\r
+ for (String className: subtypes) {\r
+ if (declaredSubtypes.contains(className)) {\r
+ MethodInfo m = hierarchy.resolveSpecialCall(className, methodName, methodDesc);\r
+ if (m != null && !target.isExcludedType(m.getClassName())) {\r
+ called.add(m);\r
+ }\r
+ }\r
+ }\r
+ MethodInfo[] methods = called.toArray(new MethodInfo[0]);\r
+ Arrays.sort(methods, new Comparator<MethodInfo>() {\r
+ @Override\r
+ public int compare(MethodInfo o1, MethodInfo o2) {\r
+ int idx = o1.getClassName().compareTo(o2.getClassName());\r
+ if (idx != 0) return idx;\r
+ \r
+ idx = o1.getMethodName().compareTo(o2.getMethodName());\r
+ if (idx != 0) return idx;\r
+\r
+ idx = o1.getDescriptor().compareTo(o2.getDescriptor());\r
+ if (idx != 0) return idx;\r
+ \r
+ return o1.hashCode() - o2.hashCode();\r
+ }\r
+ });\r
+ return methods;\r
+ } else {\r
+ // types == null if the invocation is not processed -- this condition is never satisfied.\r
+ return new MethodInfo[0];\r
+ }\r
+ } else {\r
+ // params == null if the specified instruction is not an invocation.\r
+ return new MethodInfo[0];\r
+ }\r
+ } else {\r
+ // callsites == null if the caller is not included in the analysis.\r
+ return new MethodInfo[0];\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Assign types for each vertex.\r
+ * @param typePropagationDAG\r
+ */\r
+ private void assignTypes(final DirectedAcyclicGraph typePropagationDAG, List<CallSiteVertices> callsitesWithoutCallees, IAnalysisTarget selector) {\r
+ reachingTypes = new TypeSet[typePropagationDAG.getVertexCount()];\r
+ for (NewVertices vertices: newVerticesMap.values()) {\r
+ for (int i=0; i<vertices.getVertexCount(); ++i) {\r
+ String typeName = extractBaseType(vertices.getTypeName(i));\r
+ int v = vertices.getVertex(i);\r
+ assignSpecificType(typePropagationDAG, v, typeName);\r
+ }\r
+ }\r
+ for (CallSiteVertices vertices: callsitesWithoutCallees) {\r
+ assert vertices.hasReturnValue();\r
+ int v = vertices.getReturnValueVertex();\r
+ assignApproximatedType(typePropagationDAG, v, extractBaseType(vertices.getReturnValueTypeName()));\r
+ }\r
+ // Assign approximated types for parameters from outside\r
+ for (MethodInfo m: localVerticesMap.keySet()) {\r
+ if (selector != null && selector.assumeExternalCallers(m)) {\r
+ MethodVertices methodVertices = localVerticesMap.get(m); \r
+ for (int i=0; i<m.getParamCount(); ++i) {\r
+ if (methodVertices.hasFormalVertex(i)) {\r
+ int v = methodVertices.getFormalVertex(i);\r
+ assignApproximatedType(typePropagationDAG, v, extractBaseType(m.getParamType(i)));\r
+ }\r
+ }\r
+ }\r
+ }\r
+ // Assign approximated types for exception types in catch blocks\r
+ catchVariableVertices.forEachEntry(new TIntObjectProcedure<String>() {\r
+ @Override\r
+ public boolean execute(int v, String typeName) {\r
+ assignApproximatedType(typePropagationDAG, v, typeName);\r
+ return true;\r
+ }\r
+ });\r
+ // Assign approximated types to fields which are not included in analysis target.\r
+ if (selector != null) {\r
+ for (FieldVertex fv: fieldVertex.values()) {\r
+ int vertexId = fv.getId();\r
+ FieldInfo f = fv.getFieldInfo();\r
+ if (!selector.isTargetField(f)) {\r
+ assignApproximatedType(typePropagationDAG, vertexId, extractBaseType(fv.getTypeName()));\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Assign a specific type to a vertex.\r
+ * @param typePropagationDAG specifies a DAG.\r
+ * @param v specifies a vertex.\r
+ * @param typeName specifies a type name.\r
+ */\r
+ private void assignSpecificType(DirectedAcyclicGraph typePropagationDAG, int v, String typeName) {\r
+ v = typePropagationDAG.getRepresentativeNode(v);\r
+ if (reachingTypes[v] == null) {\r
+ reachingTypes[v] = new TypeSet(typeSetManager, typeName);\r
+ } else {\r
+ reachingTypes[v] = reachingTypes[v].addType(typeName);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Assign an approximated type to a vertex.\r
+ * @param typePropagationDAG specifies a DAG.\r
+ * @param v specifies a vertex.\r
+ * @param typeName specifies a type name.\r
+ */\r
+ private void assignApproximatedType(DirectedAcyclicGraph typePropagationDAG, int v, String typeName) {\r
+ v = typePropagationDAG.getRepresentativeNode(v);\r
+ if (reachingTypes[v] == null) {\r
+ reachingTypes[v] = TypeSet.createApproximation(typeSetManager, typeName);\r
+ } else {\r
+ reachingTypes[v] = reachingTypes[v].addApproximatedType(typeName);\r
+ }\r
+ }\r
+ \r
+\r
+ \r
+ private void propagateTypes(final DirectedAcyclicGraph typePropagationDAG) {\r
+ final DirectedAcyclicGraph reverse = typePropagationDAG.getReverseGraph(); \r
+\r
+ TopologicalOrderSearch.searchFromRoot(typePropagationDAG, new ITopologicalVisitor() {\r
+ \r
+ @Override\r
+ public boolean onVisit(int vertexId) {\r
+ // Don't propagate types through ERROR vertex.\r
+ if (vertexId == VERTEX_ERROR) {\r
+ reachingTypes[vertexId] = new TypeSet(typeSetManager);\r
+ return true;\r
+ }\r
+ \r
+\r
+ int[] incoming = reverse.getEdges(vertexId);\r
+ if (incoming.length == 1) {\r
+ if (reachingTypes[vertexId] == null) {\r
+ reachingTypes[vertexId] = reachingTypes[incoming[0]];\r
+ } else {\r
+ ArrayList<TypeSet> types = new ArrayList<TypeSet>();\r
+ types.add(reachingTypes[vertexId]);\r
+ types.add(reachingTypes[incoming[0]]);\r
+ reachingTypes[vertexId] = new TypeSet(typeSetManager, types);\r
+ }\r
+ } else if (incoming.length > 1) {\r
+ // Merge reaching types.\r
+ ArrayList<TypeSet> types = new ArrayList<TypeSet>();\r
+ if (reachingTypes[vertexId] != null) {\r
+ types.add(reachingTypes[vertexId]);\r
+ }\r
+ for (int i=0; i<incoming.length; ++i) {\r
+ assert reachingTypes[incoming[i]] != null;\r
+ types.add(reachingTypes[incoming[i]]);\r
+ }\r
+ reachingTypes[vertexId] = new TypeSet(typeSetManager, types);\r
+ } else {\r
+ assert incoming.length == 0: "Unreachable vertices";\r
+ // Assign an empty set for unreachable vertices.\r
+ if (reachingTypes[vertexId] == null) {\r
+ reachingTypes[vertexId] = new TypeSet(typeSetManager);\r
+ }\r
+ }\r
+ \r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public void onFinished() {\r
+ // Vertices in the same SCC share the same TypeSet.\r
+ for (int i=0; i<reachingTypes.length; ++i) {\r
+ int v = typePropagationDAG.getRepresentativeNode(i);\r
+ if (v != i) {\r
+ reachingTypes[i] = reachingTypes[v];\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+ \r
+ private void analyzeInstruction(int index, AbstractInsnNode instruction, MethodInfo m, DataDependence dataflow) {\r
+ \r
+ switch (instruction.getOpcode()) { \r
+ case Opcodes.ARETURN:\r
+ {\r
+ int targetVertexId = localVerticesMap.get(m).getReturnVertex();\r
+ int[][] operandSources = dataflow.getDataDefinition(index);\r
+ assert operandSources.length == 1: "ARETURN takes a single parameter.";\r
+ int[] sources = operandSources[0];\r
+ for (int sourceInstructionIndex: sources) {\r
+ for (int sourceId: getSourceVertices(sourceInstructionIndex, m, dataflow)) {\r
+ addEdge(sourceId, targetVertexId);\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ case Opcodes.PUTFIELD:\r
+ case Opcodes.PUTSTATIC:\r
+ {\r
+ FieldVertex v = getFieldVertexId((FieldInsnNode)instruction);\r
+ if (v == null) return;\r
+ int targetVertexId = v.getId();\r
+ int[][] operandSources = dataflow.getDataDefinition(index);\r
+ int[] sources;\r
+ if (instruction.getOpcode() == Opcodes.PUTFIELD) {\r
+ assert operandSources.length == 2: "PUTFIELD takes two parameters (object and value)";\r
+ sources = operandSources[1];\r
+ } else {\r
+ assert operandSources.length == 1: "PUTFIELD takes a parameter (value)";\r
+ sources = operandSources[0];\r
+ }\r
+ for (int sourceInstructionIndex: sources) {\r
+ for (int sourceId: getSourceVertices(sourceInstructionIndex, m, dataflow)) {\r
+ addEdge(sourceId, targetVertexId);\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ case Opcodes.ASTORE:\r
+ {\r
+ int[][] operandSources = dataflow.getDataDefinition(index);\r
+ int[] sources = operandSources[0]; \r
+ assert operandSources.length == 1: "ASTORE takes a single parameter (value)";\r
+ if (sources.length == 1 && sources[0] == -1) {\r
+ // ASTORE at the beginning of CATCH block has a direct data flow (without ALOAD instruction).\r
+ List<?> blocks = m.getMethodNode().tryCatchBlocks;\r
+ for (int i=0; i<blocks.size(); ++i) {\r
+ TryCatchBlockNode node = (TryCatchBlockNode)blocks.get(i);\r
+ AbstractInsnNode handler = node.handler;\r
+ while (handler != null) {\r
+ if (handler == instruction) {\r
+ // instruction is to store an exception object to a local variable.\r
+ int vertexId = getLocalVariableVertex(m, index);\r
+ String type = node.type;\r
+ if (type == null) type = TypeSet.DEFAULT_UNKNOWN_TYPE;\r
+ catchVariableVertices.put(vertexId, type);\r
+ return;\r
+ } else {\r
+ if (handler.getType() == AbstractInsnNode.LABEL || \r
+ handler.getType() == AbstractInsnNode.FRAME ||\r
+ handler.getType() == AbstractInsnNode.LINE) {\r
+ handler = handler.getNext();\r
+ } else {\r
+ break; // different try-catch or finally block\r
+ }\r
+ }\r
+ }\r
+ }\r
+ assert false: "A separated ASTORE outside of try-catch blocks.";\r
+ } else {\r
+ int targetVertexId = getLocalVariableVertex(m, index);\r
+ if (targetVertexId != VERTEX_ERROR) { \r
+ for (int sourceInstructionIndex: sources) {\r
+ for (int sourceId: getSourceVertices(sourceInstructionIndex, m, dataflow)) {\r
+ addEdge(sourceId, targetVertexId);\r
+ }\r
+ }\r
+ } else {\r
+ assert false: "ASTORE must have its corresponding ALOAD.";\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ case Opcodes.INVOKEVIRTUAL:\r
+ case Opcodes.INVOKESPECIAL:\r
+ case Opcodes.INVOKESTATIC:\r
+ case Opcodes.INVOKEDYNAMIC:\r
+ case Opcodes.INVOKEINTERFACE:\r
+ {\r
+ // Create edges for invocations.\r
+ // This process is separated from creation of vertices since\r
+ // vertices of return values must be generated.\r
+ CallSiteVertices actuals = callsiteMap.get(m)[index];\r
+ if (actuals != null) {\r
+ int[][] operandSources = dataflow.getDataDefinition(index);\r
+ assert (operandSources.length == actuals.getParamCount()): "The number of operands must be the same as the number of actual vertices.";\r
+ // In general, the number of operands is the same as actual parameters.\r
+ for (int i=0; i<actuals.getParamCount(); ++i) {\r
+ if (actuals.isObjectParam(i)) {\r
+ int actualId = actuals.getParamVertexId(i);\r
+ for (int sourceInstruction: operandSources[i]) {\r
+ for (int sourceVertexId: getSourceVertices(sourceInstruction, m, dataflow)) {\r
+ addEdge(sourceVertexId, actualId);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ case Opcodes.AASTORE:\r
+ {\r
+ int[][] operandSources = dataflow.getDataDefinition(index);\r
+ int[] arraySources = operandSources[0];\r
+ int[] valueSources = operandSources[2];\r
+ assert operandSources.length == 3: "AASTORE takes three parameters: object, index and value.";\r
+ for (int valueSourceInstructionIndex: valueSources) {\r
+ for (int sourceId: getSourceVertices(valueSourceInstructionIndex, m, dataflow)) {\r
+ for (int arraySourceInstructionIndex: arraySources) {\r
+ for (int arrayId: getSourceVertices(arraySourceInstructionIndex, m, dataflow)) {\r
+ addEdge(sourceId, arrayId);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ \r
+ private void addEdge(int sourceVertex, int destinationVertex) {\r
+ edges.add(sourceVertex, destinationVertex);\r
+ \r
+ // If at least one of the vertices is an array type,\r
+ // or both of the vertices are "java.lang.Object", \r
+ // then connect a back edge to represent an alias.\r
+ String sourceType = declaredTypeNames.get(sourceVertex);\r
+ String destinationType = declaredTypeNames.get(destinationVertex);\r
+ if ((sourceType.endsWith(ARRAY_SUFFIX) || destinationType.endsWith(ARRAY_SUFFIX)) || \r
+ (sourceType.equals("java/lang/Object") && destinationType.equals("java/lang/Object"))) {\r
+ edges.add(destinationVertex, sourceVertex);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Return a vertex ID representing a local variable accessed by a specified instruction.\r
+ * @param m\r
+ * @param instructionIndex\r
+ * @return\r
+ */\r
+ private int getLocalVariableVertex(MethodInfo m, int instructionIndex) {\r
+ MethodVertices l = localVerticesMap.get(m);\r
+ if (l != null) {\r
+ return l.getLocalVertex(instructionIndex);\r
+ } else {\r
+ assert false: "MethodVertices is not registered.";\r
+ return VERTEX_ERROR;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @param instructionIndex specifies an instruction \r
+ * that generates a value. \r
+ * @return vertex IDs that may correspond to a value. \r
+ */\r
+ private int[] getSourceVertices(int instructionIndex, MethodInfo m, DataDependence dataflow) {\r
+ assert instructionIndex >= 0: "Instruction must be >=0 : " + Integer.toString(instructionIndex);\r
+ \r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ AbstractInsnNode node = instructions.get(instructionIndex);\r
+ switch (node.getOpcode()) {\r
+ case Opcodes.CHECKCAST:\r
+ {\r
+ int[][] operands = dataflow.getDataDefinition(instructionIndex);\r
+ assert operands.length == 1: "CHECKCAST takes a single parameter.";\r
+ TIntArrayList defs = new TIntArrayList();\r
+ for (int sourceInstruction: operands[0]) {\r
+ defs.add(getSourceVertices(sourceInstruction, m, dataflow));\r
+ }\r
+ return defs.toArray();\r
+ }\r
+ case Opcodes.INVOKEINTERFACE:\r
+ case Opcodes.INVOKESPECIAL:\r
+ case Opcodes.INVOKESTATIC:\r
+ case Opcodes.INVOKEVIRTUAL:\r
+ case Opcodes.INVOKEDYNAMIC:\r
+ CallSiteVertices[] sites = callsiteMap.get(m);\r
+ if (sites[instructionIndex] != null) {\r
+ return new int[] { sites[instructionIndex].getReturnValueVertex() };\r
+ } else {\r
+ return new int[] { VERTEX_ERROR };\r
+ }\r
+\r
+ case Opcodes.GETFIELD:\r
+ case Opcodes.GETSTATIC:\r
+ FieldInsnNode f = (FieldInsnNode)node;\r
+ FieldVertex v = getFieldVertexId(f);\r
+ if (v != null) {\r
+ return new int[] { v.getId() };\r
+ } else {\r
+ return new int[0];\r
+ }\r
+\r
+ case Opcodes.AALOAD:\r
+ {\r
+ int[][] operands = dataflow.getDataDefinition(instructionIndex);\r
+ assert operands.length == 2: "AALOAD takes two parameters.";\r
+ TIntArrayList defs = new TIntArrayList();\r
+ for (int sourceInstruction: operands[0]) { // arrayRef\r
+ defs.add(getSourceVertices(sourceInstruction, m, dataflow));\r
+ }\r
+ return defs.toArray();\r
+ }\r
+ \r
+ case Opcodes.ALOAD:\r
+ return new int[] { getLocalVariableVertex(m, instructionIndex) };\r
+ \r
+ case Opcodes.MULTIANEWARRAY:\r
+ case Opcodes.ANEWARRAY: \r
+ case Opcodes.NEW:\r
+ return new int[] { newVerticesMap.get(m).getNewInstructionVertex(instructionIndex) };\r
+ \r
+ default:\r
+ return new int[] { VERTEX_ERROR };\r
+ }\r
+ }\r
+ \r
+ private FieldVertex getFieldVertexId(FieldInsnNode node) {\r
+ String className = node.owner;\r
+ String fieldName = node.name;\r
+ String desc = node.desc;\r
+ String owner;\r
+ if (node.getOpcode() == Opcodes.PUTSTATIC || \r
+ node.getOpcode() == Opcodes.GETSTATIC) {\r
+ owner = hierarchy.resolveStaticFieldOwner(className, fieldName, desc);\r
+ } else {\r
+ assert node.getOpcode() == Opcodes.PUTFIELD || \r
+ node.getOpcode() == Opcodes.GETFIELD;\r
+ owner = hierarchy.resolveInstanceFieldOwner(className, fieldName, desc);\r
+ }\r
+ ClassInfo c = hierarchy.getClassInfo(owner);\r
+ if (c != null) {\r
+ FieldInfo f = c.findField(fieldName, desc);\r
+ if (f != null) {\r
+ FieldVertex fv = fieldVertex.get(f);\r
+ if (fv != null) {\r
+ return fv;\r
+ } else {\r
+ assert TypeConstants.isPrimitiveTypeName(f.getFieldTypeName());\r
+ return null;\r
+ }\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * Return a set of types that may be invoked by a method call.\r
+ * @param m specifies a caller method.\r
+ * @param instruction specifies an INVOKE instruction in the caller method.\r
+ * @return a TypeSet including types that may be assigned to a receiver object.\r
+ */\r
+ public TypeSet getReceiverTypeAtCallsite(MethodInfo m, int instruction) {\r
+ CallSiteVertices[] callsites = callsiteMap.get(m);\r
+ if (callsites != null) {\r
+ CallSiteVertices params = callsites[instruction];\r
+ if (params != null) {\r
+ if (!params.getCallSite().isStaticMethod()) {\r
+ int v = params.getParamVertexId(0);\r
+ return reachingTypes[v];\r
+ } else {\r
+ return null;\r
+ }\r
+ } else {\r
+ // params == null if no method implementations are included in the analysis.\r
+ return null;\r
+ }\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ public TypeSet getMethodParamType(MethodInfo m, int paramIndex) {\r
+ MethodVertices vertices = localVerticesMap.get(m);\r
+ if (vertices.hasFormalVertex(paramIndex)) {\r
+ return reachingTypes[vertices.getFormalVertex(paramIndex)];\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ private static class FieldVertex {\r
+ private int vertexID;\r
+ private FieldInfo fieldInfo;\r
+ public FieldVertex(FieldInfo f, int vID) {\r
+ this.vertexID = vID;\r
+ this.fieldInfo= f;\r
+ }\r
+ \r
+ public int getId() {\r
+ return vertexID;\r
+ }\r
+ \r
+ public String getTypeName() {\r
+ return TypeResolver.getTypeName(fieldInfo.getDescriptor());\r
+ }\r
+ \r
+ public FieldInfo getFieldInfo() {\r
+ return fieldInfo;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @param typename represents a type. \r
+ * The value may be an array type.\r
+ * @return If typename is a regular type, \r
+ * the typename is returned.\r
+ * If typename is an array type, \r
+ * the return value is a base type of the array. \r
+ */\r
+ private String extractBaseType(String typename) {\r
+ assert typename != null;\r
+ while (typename.endsWith("[]")) {\r
+ typename = typename.substring(0, typename.length() - 2);\r
+ }\r
+ assert typename != null;\r
+ return typename;\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import java.util.Arrays;\r
+\r
+/**\r
+ * This class represents a list of integer pairs.\r
+ */\r
+public class IntPairList {\r
+\r
+ private int count;\r
+ private long[] values;\r
+ private boolean frozen;\r
+\r
+ /**\r
+ * Creates a new <code>IntPairList</code> instance with the default size.\r
+ */\r
+ public IntPairList() {\r
+ this(1024);\r
+ }\r
+\r
+ /**\r
+ * @param capacity specifies the list size.\r
+ * Creates a new <code>IntPairList</code> instance with the specified size.\r
+ */\r
+ public IntPairList(int capacity) {\r
+ values = new long[capacity]; \r
+ count = 0;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of elements.\r
+ */\r
+ public int size() {\r
+ return count; \r
+ }\r
+ \r
+ private long compose(int elem1, int elem2) {\r
+ return (((long)elem1) << 32) | elem2;\r
+ }\r
+\r
+ /**\r
+ * @param index specifies a pair.\r
+ * @return a first value of the specified pair.\r
+ */\r
+ public int getFirstValue(int index) {\r
+ if (index < 0 || index >= count) {\r
+ throw new ArrayIndexOutOfBoundsException(index);\r
+ }\r
+ return (int)(values[index] >> 32);\r
+ }\r
+\r
+ /**\r
+ * @param index specifies a pair.\r
+ * @return a second value of the specified pair.\r
+ */\r
+ public int getSecondValue(int index) {\r
+ if (index < 0 || index >= count) {\r
+ throw new ArrayIndexOutOfBoundsException(index);\r
+ }\r
+ return (int)(values[index] & 0xFFFFFFFF);\r
+ }\r
+\r
+ /**\r
+ * Adds a pair of integers.\r
+ * @param elem1 specifies a first value.\r
+ * @param elem2 specifies a second value.\r
+ */\r
+ public void add(int elem1, int elem2) {\r
+ if (!frozen) {\r
+ if (values.length == count) growUp();\r
+ values[count] = compose(elem1, elem2);\r
+ count++;\r
+ } else {\r
+ throw new FrozenListException();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Adds all the pairs of another list.\r
+ * @param another is a <code>IntPairList</code> object.\r
+ */\r
+ public void addAll(IntPairList another) {\r
+ if (!frozen) {\r
+ int anotherSize = another.size();\r
+ for (int i=0; i<anotherSize; ++i) {\r
+ if (values.length == count) growUp();\r
+ values[count] = another.values[i];\r
+ count++;\r
+ }\r
+ } else {\r
+ throw new FrozenListException();\r
+ }\r
+ }\r
+\r
+ private void growUp() {\r
+ long[] newValues = new long[values.length * 2];\r
+ for (int i=0; i<count; ++i) {\r
+ newValues[i] = values[i];\r
+ }\r
+ values = newValues;\r
+ }\r
+ \r
+ /**\r
+ * Sets a first value of a pair at the specified position.\r
+ * @param index specifies a position in the list.\r
+ * @param first is a stored value.\r
+ */\r
+ public void setFirstValue(int index, int first) {\r
+ if (!frozen) {\r
+ if ((index < 0) || (count <= index)) {\r
+ throw new ArrayIndexOutOfBoundsException();\r
+ } else {\r
+ int second = getSecondValue(index);\r
+ values[index] = compose(first, second);\r
+ }\r
+ } else {\r
+ throw new FrozenListException();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Sets a second value of a pair at the specified position.\r
+ * @param index specifies a position in the list.\r
+ * @param second is a stored value.\r
+ */\r
+ public void setSecondValue(int index, int second) {\r
+ if (!frozen) {\r
+ if ((index < 0) || (count <= index)) {\r
+ throw new ArrayIndexOutOfBoundsException();\r
+ } else {\r
+ int first = getFirstValue(index);\r
+ values[index] = compose(first, second);\r
+ }\r
+ } else {\r
+ throw new FrozenListException();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Sorts a list by the order of the composed values.\r
+ */\r
+ public void sort() {\r
+ Arrays.sort(values, 0, count);\r
+ }\r
+ \r
+ /**\r
+ * Executes a procedure for each element.\r
+ * @param proc\r
+ */\r
+ public void foreach(IntPairProc proc) {\r
+ boolean cont = true;\r
+ for (int i=0; cont && (i<count); ++i) {\r
+ cont = proc.execute(getFirstValue(i), getSecondValue(i));\r
+ }\r
+ }\r
+ \r
+ \r
+ /**\r
+ * This method freezes the list and releases \r
+ * unnecessary memory buffer for additional elements.\r
+ * If you add a new pair of integers, \r
+ * this list throws an exception.\r
+ */\r
+ public void freeze() {\r
+ frozen = true;\r
+ }\r
+ \r
+ public static class FrozenListException extends RuntimeException {\r
+ private static final long serialVersionUID = 1519361503126979153L;\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+/**\r
+ * A callback interface for a pair of integers.\r
+ * @author ishio\r
+ *\r
+ */\r
+public interface IntPairProc {\r
+\r
+ /**\r
+ * @return true if you want to continue this loop.\r
+ */\r
+ public boolean execute(int elem1, int elem2);\r
+\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import gnu.trove.map.hash.TIntObjectHashMap;\r
+import gnu.trove.procedure.TIntObjectProcedure;\r
+import gnu.trove.procedure.TIntProcedure;\r
+import gnu.trove.set.hash.TIntHashSet;\r
+\r
+/**\r
+ * This class represents a set of integer pairs.\r
+ */\r
+public class IntPairSet {\r
+\r
+ private TIntObjectHashMap<TIntHashSet> intPairs;\r
+ private int pairCount;\r
+ \r
+ /**\r
+ * Creates a new <code>IntPairSet</code> instance.\r
+ */\r
+ public IntPairSet() {\r
+ intPairs = new TIntObjectHashMap<TIntHashSet>();\r
+ pairCount = 0;\r
+ }\r
+ \r
+ /**\r
+ * Adds a pair of integers.\r
+ * If the same pair of integers has been already stored,\r
+ * this method does not change the state.\r
+ * @param elem1 specifies a first value.\r
+ * @param elem2 specifies a second value.\r
+ */\r
+ public void add(int elem1, int elem2) {\r
+ TIntHashSet secondValues = intPairs.get(elem1);\r
+ if (secondValues == null) {\r
+ secondValues = new TIntHashSet();\r
+ intPairs.put(elem1, secondValues);\r
+ }\r
+ boolean added = secondValues.add(elem2);\r
+ if (added) {\r
+ ++pairCount;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return true if the set contains a pair (elem1, elem2).\r
+ */\r
+ public boolean contains(int elem1, int elem2) {\r
+ TIntHashSet secondValues = intPairs.get(elem1);\r
+ if (secondValues != null) {\r
+ return secondValues.contains(elem2);\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return true if the set contains a pair (elem1, _).\r
+ */\r
+ public boolean containsFirst(int elem1) {\r
+ TIntHashSet secondValues = intPairs.get(elem1);\r
+ if (secondValues != null) {\r
+ return !secondValues.isEmpty();\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return the number of pairs in the set.\r
+ */\r
+ public int size() {\r
+ return pairCount;\r
+ }\r
+ \r
+ /**\r
+ * Executes a procedure for each element.\r
+ * @param proc\r
+ */\r
+ public void foreach(final IntPairProc proc) { \r
+ intPairs.forEachEntry(new IntPairIterator(proc));\r
+ }\r
+ \r
+ private class IntPairIterator implements TIntObjectProcedure<TIntHashSet> {\r
+ \r
+ private IntPairProc proc;\r
+ private boolean continueFlag;\r
+ \r
+ public IntPairIterator(IntPairProc proc) {\r
+ this.proc = proc;\r
+ this.continueFlag = true;\r
+ }\r
+ \r
+ @Override\r
+ public boolean execute(final int firstValue, TIntHashSet secondValues) {\r
+ secondValues.forEach(new TIntProcedure() {\r
+ \r
+ @Override\r
+ public boolean execute(int secondValue) {\r
+ continueFlag = proc.execute(firstValue, secondValue);\r
+ return continueFlag;\r
+ }\r
+ }); \r
+ return continueFlag;\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+/**\r
+ * A utility for collections of integer pairs.\r
+ */\r
+public class IntPairUtil {\r
+ \r
+ /**\r
+ * Creates an IntPairList from an IntPairSet.\r
+ * The resultant list is not sorted;\r
+ * the order of elements depends on the set's implementation.\r
+ */\r
+ public static IntPairList createList(IntPairSet set) {\r
+ final IntPairList list = new IntPairList(set.size());\r
+ set.foreach(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ list.add(elem1, elem2);\r
+ return true;\r
+ }\r
+ });\r
+ return list;\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Creates an IntPairList that includes two IntPairList sets.\r
+ */\r
+ public static IntPairList createList(final IntPairSet set1, final IntPairSet set2) {\r
+ final IntPairList list = new IntPairList(set1.size() + set2.size());\r
+ set1.foreach(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ list.add(elem1, elem2);\r
+ return true;\r
+ }\r
+ });\r
+ set2.foreach(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ if (!set1.contains(elem1, elem2)) {\r
+ list.add(elem1, elem2);\r
+ }\r
+ return true;\r
+ }\r
+ });\r
+ return list;\r
+ \r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+/**\r
+ * <code>IntSetStack</code> is a stack in which int values can be stored.\r
+ * The stack cannot contain two instances of the same value;\r
+ * for example, "aIntSetStack.push(0); aIntSetStack.push(0);" \r
+ * causes a DuplicatedValueException.\r
+ * Instead of the set constraint, this stack \r
+ * provides O(1) implementation of contains(int).\r
+ */\r
+public class IntSetStack extends IntStack {\r
+\r
+ private int max;\r
+ private static final int DIV = 31;\r
+ private int[] elements;\r
+ private boolean ignoreDuplicatedElement;\r
+ \r
+ private static int elementIndex(int value) {\r
+ return value/DIV;\r
+ }\r
+\r
+ private static int bitMask(int value) {\r
+ int b = value%DIV;\r
+ return 1 << b;\r
+ }\r
+\r
+ /**\r
+ * Creates a new <code>IntSetStack</code> instance.\r
+ * @param maxValue is a maximum size of the stack.\r
+ */\r
+ public IntSetStack(int maxValue) {\r
+ super();\r
+ max = maxValue;\r
+ elements = new int[elementIndex(maxValue)+1];\r
+ }\r
+ \r
+ /**\r
+ * Creates a new <code>IntSetStack</code> instance with a specified size.\r
+ * @param maxValue is a maximum size of the stack.\r
+ * @param capacity is a size of the stack. \r
+ */\r
+ public IntSetStack(int maxValue, int capacity) {\r
+ super(capacity);\r
+ elements = new int[elementIndex(maxValue)+1];\r
+ }\r
+ \r
+ public void setIgnoreDuplicatedElement(boolean ignore) {\r
+ ignoreDuplicatedElement = ignore;\r
+ }\r
+ \r
+ /**\r
+ * O(1) implementation \r
+ */\r
+ @Override\r
+ public boolean contains(int value) {\r
+ int bit = elements[ elementIndex(value) ];\r
+ return ((bit & bitMask(value)) != 0);\r
+ }\r
+ \r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public void push(int value) {\r
+ if (max < value) throw new InvalidElementException(max, value);\r
+ int index = elementIndex(value);\r
+ int bit = elements[ index ];\r
+ int bitMask = bitMask(value);\r
+ if ((bit & bitMask) == 0) {\r
+ super.push(value);\r
+ elements[ index ] |= bitMask;\r
+ } else {\r
+ if (!ignoreDuplicatedElement) {\r
+ throw new DuplicatedElementException();\r
+ }\r
+ }\r
+ assert (elements[index] & bitMask) != 0;\r
+ }\r
+ \r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public int pop() {\r
+ int value = super.pop();\r
+ int index = elementIndex(value);\r
+ int bitMask = bitMask(value);\r
+ elements[index] ^= bitMask;\r
+ assert (elements[index] & bitMask) == 0;\r
+ return value;\r
+ }\r
+\r
+ public class InvalidElementException extends RuntimeException {\r
+\r
+ private static final long serialVersionUID = -8544940855615952576L;\r
+\r
+ private int max;\r
+ private int value;\r
+ \r
+ public InvalidElementException(int max, int value) {\r
+ this.max = max;\r
+ this.value = value;\r
+ }\r
+ \r
+ @Override\r
+ public String getMessage() {\r
+ return "The given value cannot be stored into the stack. MAX=" + \r
+ Integer.toString(max) + ", VALUE=" + Integer.toString(value);\r
+ }\r
+ \r
+ public int getValue() {\r
+ return value;\r
+ }\r
+ \r
+ public int getMax() {\r
+ return max;\r
+ }\r
+ }\r
+\r
+ public class DuplicatedElementException extends RuntimeException {\r
+\r
+ private static final long serialVersionUID = -3896583429225358307L;\r
+\r
+ public DuplicatedElementException() {\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import java.util.EmptyStackException;\r
+\r
+/**\r
+ * This class represents a integer stack.\r
+ */\r
+public class IntStack {\r
+\r
+ private int count = 0;\r
+ private int[] values;\r
+\r
+ /**\r
+ * Creates a new <code>IntStack</code> with the default size.\r
+ */\r
+ public IntStack() {\r
+ this(1024);\r
+ }\r
+ \r
+ /**\r
+ * Creates a new <code>IntStack</code> with a specified size.\r
+ * @param capacity is a size of the stack.\r
+ */\r
+ public IntStack(int capacity) {\r
+ count = 0;\r
+ values = new int[capacity];\r
+ }\r
+ \r
+ /**\r
+ * @return true if the stack is empty.\r
+ */\r
+ public boolean isEmpty() {\r
+ return count == 0;\r
+ }\r
+ \r
+ /**\r
+ * Pushes a value to the top of the stack.\r
+ * @param value\r
+ */\r
+ public void push(int value) {\r
+ if (count >= values.length) {\r
+ growUp();\r
+ }\r
+ values[count] = value;\r
+ count++;\r
+ }\r
+ \r
+ /**\r
+ * Gets a value from the top of the stack.\r
+ * The value is deleted from the stack.\r
+ * @return a top value of the stack.\r
+ */\r
+ public int pop() {\r
+ if (count == 0) {\r
+ throw new EmptyStackException();\r
+ }\r
+ count--;\r
+ return values[count];\r
+ }\r
+ \r
+ /**\r
+ * Gets a top value of the stack.\r
+ * The value is not deleted from the stack.\r
+ * @return a top value of the stack.\r
+ */\r
+ public int peek() {\r
+ if (count == 0) {\r
+ throw new EmptyStackException();\r
+ }\r
+ return values[count-1];\r
+ }\r
+ \r
+ /**\r
+ * @param value\r
+ * @return true if the value is contained in the stack.\r
+ */\r
+ public boolean contains(int value) {\r
+ for (int i=0; i<count; ++i) {\r
+ if (values[i] == value) return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private void growUp() {\r
+ int[] newValues = new int[values.length * 2];\r
+ for (int i=0; i<count; ++i) {\r
+ newValues[i] = values[i];\r
+ }\r
+ values = newValues;\r
+ }\r
+ \r
+ /**\r
+ * @return the number of elements in the stack.\r
+ */\r
+ public int size() {\r
+ return count;\r
+ }\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+import java.util.ArrayList;\r
+\r
+public class ObjectIdMap<T> {\r
+ \r
+ private TObjectIntHashMap<T> map;\r
+ private ArrayList<T> idToObject;\r
+ private boolean frozen;\r
+ \r
+ /**\r
+ * Creates a new <code>ObjectIdMap</code> instance with the default size.\r
+ */\r
+ public ObjectIdMap() {\r
+ this(1024 * 1024);\r
+ }\r
+\r
+ /**\r
+ * Creates a new <code>ObjectIdMap</code> instance with a specified size.\r
+ * @param capacity\r
+ */\r
+ public ObjectIdMap(int capacity) {\r
+ map = new TObjectIntHashMap<T>(capacity * 2);\r
+ idToObject = new ArrayList<T>(capacity);\r
+ frozen = false;\r
+ }\r
+ \r
+ /**\r
+ * Disable assigning new IDs.\r
+ * For frozen maps, getId method with a new object \r
+ * returns -1 (or throws AssertionError).\r
+ */\r
+ public void freeze() {\r
+ frozen = true;\r
+ }\r
+ \r
+ /**\r
+ * Adds a new object.\r
+ * @param s\r
+ */\r
+ public void add(T s) {\r
+ getId(s);\r
+ }\r
+ \r
+ /**\r
+ * @param item specifies an object.\r
+ * @return the ID integer corresponding to the object.\r
+ * If a new object is given, this method returns a new id.\r
+ */\r
+ public int getId(T item) {\r
+ if (map.containsKey(item)) {\r
+ return map.get(item);\r
+ } else {\r
+ if (frozen) {\r
+ // A new object is added to the frozen id map.\r
+ throw new FrozenMapException();\r
+ } else {\r
+ int newId = idToObject.size();\r
+ map.put(item, newId);\r
+ idToObject.add(item);\r
+ return newId;\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @param id\r
+ * @return an object.\r
+ */\r
+ public T getItem(int id) {\r
+ if ((id < 0) || (id >= idToObject.size())) return null;\r
+ else return idToObject.get(id);\r
+ }\r
+ \r
+ /**\r
+ * @return the number of elements.\r
+ */\r
+ public int size() {\r
+ return idToObject.size();\r
+ }\r
+ \r
+ public static class FrozenMapException extends RuntimeException {\r
+ \r
+ private static final long serialVersionUID = -1682458461699095201L;\r
+\r
+ public FrozenMapException() {\r
+ super();\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+/**\r
+ * <code>Timer</code> records time consumed by this program.\r
+ */\r
+public class Timer {\r
+\r
+ private long startTimestamp;\r
+ private long timestamp;\r
+\r
+ /**\r
+ * Starts a timer.\r
+ */\r
+ public Timer() {\r
+ timestamp = System.currentTimeMillis();\r
+ startTimestamp = timestamp;\r
+ }\r
+ \r
+ /**\r
+ * @return consumed milliseconds since a previous checkpoint.\r
+ */\r
+ public long checkpoint() {\r
+ long oldTimestamp = timestamp;\r
+ timestamp = System.currentTimeMillis();\r
+ return timestamp - oldTimestamp;\r
+ }\r
+ \r
+ /**\r
+ * @return consumed milliseconds since the timer is created.\r
+ */\r
+ public long getTotaltime() {\r
+ return System.currentTimeMillis() - startTimestamp;\r
+ }\r
+}\r
--- /dev/null
+package soba.util.debug;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.LocalVariableNode;\r
+import org.objectweb.asm.tree.MethodNode;\r
+import org.objectweb.asm.tree.TryCatchBlockNode;\r
+import org.objectweb.asm.tree.analysis.Frame;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.DataDependence;\r
+import soba.core.method.DataFlowEdge;\r
+import soba.core.method.OpcodeString;\r
+import soba.core.method.asm.FastSourceValue;\r
+import soba.util.IntPairProc;\r
+import soba.util.Timer;\r
+import soba.util.files.Directory;\r
+import soba.util.files.IClassList;\r
+import soba.util.files.SingleFile;\r
+import soba.util.files.ZipFile;\r
+import soba.util.graph.IDirectedGraph;\r
+\r
+public class DumpClass {\r
+\r
+ static Timer timer;\r
+ static int classCount = 0;\r
+ static int methodCount = 0;\r
+ static int failedMethodCount = 0;\r
+ static long instructionCount = 0;\r
+ static long dataflowEdgeCount = 0;\r
+ static boolean enableOutput = true;\r
+ static boolean dumpStackframe = false;\r
+ static boolean dumpParamName = false;\r
+ static boolean dumpTryBlock = false;\r
+ \r
+ /**\r
+ * @param args\r
+ */\r
+ public static void main(String[] args) {\r
+ timer = new Timer();\r
+ List<IClassList> files = new ArrayList<IClassList>();\r
+ \r
+ if (args.length == 0) {\r
+ Directory bin = new Directory(new File("bin"));\r
+ Directory lib = new Directory(new File("lib"));\r
+ files.add(bin);\r
+ files.add(lib);\r
+ } else {\r
+ for (String arg: args) {\r
+ if (arg.equals("--disable-output")) {\r
+ enableOutput = false;\r
+ } else if (arg.equals("--output-frame")) {\r
+ dumpStackframe = true;\r
+ } else if (arg.equals("--output-param")) {\r
+ dumpParamName = true;\r
+ } else if (arg.equals("--output-try")) {\r
+ dumpTryBlock = true;\r
+ } else {\r
+ File f = new File(arg);\r
+ if (f.isDirectory()) {\r
+ Directory dir = new Directory(f);\r
+ dir.enableRecursiveZipSearch();\r
+ files.add(dir);\r
+ } else if (ZipFile.isZipFile(f)) {\r
+ ZipFile zip = new ZipFile(f);\r
+ zip.enableRecursiveSearch();\r
+ files.add(zip);\r
+ } else {\r
+ SingleFile file = new SingleFile(f);\r
+ files.add(file);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ JavaProgram program = new JavaProgram((IClassList[]) files.toArray(new IClassList[files.size()]));\r
+ program.getClasses().forEach(c -> processClass(c));\r
+ \r
+ System.err.println("FINISHED: " + timer.getTotaltime() + " ms");\r
+ System.err.println("#Classes: " + classCount);\r
+ System.err.println("#Methods: " + methodCount);\r
+ System.err.println("#Instructions: " + instructionCount);\r
+ System.err.println("#Edges: " + dataflowEdgeCount);\r
+ System.err.println("#Failed: " + failedMethodCount);\r
+ }\r
+ \r
+ private static void processClass(ClassInfo c) {\r
+ classCount++;\r
+ for (MethodInfo m: c.getMethods()) {\r
+ if (enableOutput) {\r
+ System.out.println(constructMethodNameString(c, m.getMethodNode()));\r
+ }\r
+ \r
+ MethodInfo methodInfo = m;\r
+ if (!methodInfo.hasMethodBody()) continue;\r
+\r
+ DataDependence info = methodInfo.getDataDependence();\r
+ \r
+ if (info != null) {\r
+ methodCount++;\r
+ instructionCount += methodInfo.getInstructionCount();\r
+ \r
+ if (dumpParamName) {\r
+ for (int i=0; i<methodInfo.getParamCount(); ++i) {\r
+ System.out.print(" Param ");\r
+ System.out.print(i + 1);\r
+ System.out.print(" ");\r
+ System.out.print(methodInfo.getParamType(i));\r
+ System.out.print(" ");\r
+ System.out.println(methodInfo.getParamName(i));\r
+ }\r
+ }\r
+\r
+ \r
+ for (int i=0; i<methodInfo.getInstructionCount(); ++i) {\r
+ if (enableOutput) {\r
+ System.out.println(" " + OpcodeString.getInstructionString(m.getMethodNode(), i)); \r
+ }\r
+ if (dumpStackframe) dumpStackframe(info, i);\r
+ }\r
+ \r
+ if (dumpTryBlock) {\r
+ if (m.getMethodNode().tryCatchBlocks != null) {\r
+ System.out.println(" Try-catch Table:");\r
+ for (TryCatchBlockNode node: m.getMethodNode().tryCatchBlocks) {\r
+ System.out.println(" start=" + OpcodeString.getLabelString(m.getMethodNode(), node.start) + ", end=" + OpcodeString.getLabelString(m.getMethodNode(), node.end) + ", handler=" + OpcodeString.getLabelString(m.getMethodNode(), node.handler) + " (" + node.type + ")");\r
+ }\r
+ System.out.println(" Try-catch Table End");\r
+ }\r
+ }\r
+ \r
+ List<DataFlowEdge> edges = info.getEdges();\r
+ if (enableOutput) {\r
+ for (DataFlowEdge e: edges) {\r
+ System.out.print(" ");\r
+ System.out.print(e.toString());\r
+ System.out.print(" ");\r
+// ILocalVariableInfo v = info.getLocalVariable(e);\r
+// if (v != null) {\r
+// System.out.print(v.getName() + ": " + v.getDescriptor());\r
+// }\r
+ String variableName = info.getVariableName(e);\r
+ if (variableName != null) {\r
+ System.out.print(variableName + ": " + info.getVariableDescriptor(e));\r
+ }\r
+ System.out.println();\r
+ }\r
+ }\r
+ dataflowEdgeCount += edges.size();\r
+ \r
+ IDirectedGraph cflow = methodInfo.getControlFlow();\r
+ if (enableOutput) {\r
+ cflow.forEachEdge(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ System.out.println(" [CFLOW] " + elem1 + " -> " + elem2);\r
+ return true;\r
+ }\r
+ });\r
+ }\r
+ \r
+ IDirectedGraph cdepends = methodInfo.getControlDependence();\r
+ if (enableOutput) {\r
+ cdepends.forEachEdge(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ System.out.println(" [Control] " + elem1 + " -> " + elem2);\r
+ return true;\r
+ }\r
+ });\r
+ }\r
+ \r
+ } else {\r
+ failedMethodCount++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ private static void dumpStackframe(DataDependence info, int i) {\r
+ Frame<?> f = info.getFrame(i);\r
+ if (f == null) {\r
+ if (enableOutput) {\r
+ System.out.print(" ");\r
+ System.out.print(i);\r
+ System.out.println(" [STACK] null");\r
+ }\r
+ } else {\r
+ int size = f.getStackSize();\r
+ for (int s=0; s<size; ++s) {\r
+ FastSourceValue source = (FastSourceValue)f.getStack(s);\r
+ for (int pos: source.getInstructions()) {\r
+ if (enableOutput) {\r
+ System.out.print(" ");\r
+ System.out.print(i);\r
+ System.out.print(" [STACK] <");\r
+ System.out.print(s);\r
+ System.out.print("/");\r
+ System.out.print(size);\r
+ System.out.print("> ");\r
+ System.out.print(pos);\r
+ System.out.print(": ");\r
+ if (pos >= 0) {\r
+ System.out.println(info.getInstruction(pos).toString());\r
+ } else {\r
+ System.out.println("method param");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ int locals = f.getLocals();\r
+ for (int l=0; l<locals; ++l) {\r
+ FastSourceValue source = (FastSourceValue)f.getLocal(l);\r
+ for (int pos: source.getInstructions()) {\r
+ if (enableOutput) {\r
+ System.out.print(" ");\r
+ System.out.print(i);\r
+ System.out.print(" [LOCAL] <");\r
+ System.out.print(l);\r
+ System.out.print("/");\r
+ System.out.print(locals);\r
+ System.out.print("> ");\r
+ System.out.print(pos);\r
+ System.out.print(": ");\r
+ if (pos >= 0) {\r
+ System.out.println(info.getInstruction(pos).toString());\r
+ } else {\r
+ System.out.println("method param");\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ int consumeStack = info.getOperandCount(i);\r
+ int maxStack = f.getStackSize();\r
+ for (int s=0; s<consumeStack; ++s) {\r
+ FastSourceValue source = (FastSourceValue)f.getStack(maxStack-1-s);\r
+ for (int pos: source.getInstructions()) {\r
+ if (enableOutput) {\r
+ System.out.print(" ");\r
+ System.out.print(i);\r
+ System.out.print(" [OPERAND");\r
+ System.out.print(s);\r
+ System.out.print("] ");\r
+ System.out.print(pos);\r
+ System.out.print(": ");\r
+ if (pos >= 0) {\r
+ System.out.println(info.getInstruction(pos).toString());\r
+ } else {\r
+ System.out.println("method param");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ int[][] def = info.getDataDefinition(i);\r
+ if (enableOutput) {\r
+ for (int operandIndex=0; operandIndex<def.length; ++operandIndex) {\r
+ System.out.print(" Operand " + Integer.toString(operandIndex) + " Data Dependence: ");\r
+ if (def[operandIndex] != null) {\r
+ if (def[operandIndex].length == 0) {\r
+ System.out.print("EMPTY");\r
+ } else {\r
+ for (int fromIndex=0; fromIndex<def[operandIndex].length; ++fromIndex) {\r
+ if (fromIndex>0) System.out.print(", ");\r
+ System.out.print(def[operandIndex][fromIndex]);\r
+ }\r
+ }\r
+ } else {\r
+ System.out.print("null");\r
+ }\r
+ System.out.println();\r
+ }\r
+ }\r
+ } \r
+ \r
+ private static String constructMethodNameString(ClassInfo c, MethodNode m) {\r
+ StringBuilder buf = new StringBuilder();\r
+ buf.append(c.getClassName());\r
+ buf.append("#");\r
+ buf.append(m.name);\r
+ buf.append("#");\r
+ buf.append(m.desc);\r
+ buf.append("#");\r
+ buf.append(m.signature);\r
+ if ((m.access & Opcodes.ACC_SYNTHETIC) != 0) {\r
+ buf.append(" [Synthetic]");\r
+ }\r
+ \r
+ return buf.toString();\r
+ }\r
+ \r
+ @SuppressWarnings({ "unused" } ) \r
+ private static void dumpVariables(MethodNode m) {\r
+ List<LocalVariableNode> variables = m.localVariables;\r
+ if (variables != null) {\r
+ System.out.println("---VARIABLES BEGIN---");\r
+ for (LocalVariableNode v: variables) {\r
+ System.out.println(" " + v.name + ": " + v.desc + " ");\r
+ }\r
+ System.out.println("---VARIABLES END---");\r
+ }\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import java.io.File;\r
+import java.io.FileFilter;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import soba.core.ClassInfo;\r
+\r
+/**\r
+ * The factory creates a list of JAR files in the system class paths.\r
+ */\r
+public class ClasspathUtil {\r
+ \r
+ public static IClassList[] getClassList(String[] files) {\r
+ return getClassList(Arrays.asList(files), null);\r
+ }\r
+\r
+ public static IClassList[] getClassList(String[] files, String label) {\r
+ return getClassList(Arrays.asList(files), label);\r
+ }\r
+\r
+ public static IClassList[] getClassList(List<String> files) { \r
+ return getClassList(files, null);\r
+ }\r
+\r
+ public static IClassList[] getClassList(String[] appFiles, String[] libFiles) {\r
+ IClassList[] apps = getClassList(appFiles);\r
+ IClassList[] libs = getClassList(libFiles, ClassInfo.LIBRARY_LABEL);\r
+ return merge(apps, libs);\r
+ }\r
+ \r
+ public static IClassList[] getClassList(List<String> files, String label) { \r
+ List<IClassList> result = new ArrayList<IClassList>();\r
+ for (String filepath: files) {\r
+ File f = new File(filepath);\r
+ if (f.isDirectory()) {\r
+ Directory dir = new Directory(f);\r
+ dir.enableRecursiveZipSearch();\r
+ dir.setLabel(label);\r
+ result.add(dir);\r
+ } else if (ZipFile.isZipFile(f)) {\r
+ ZipFile zip = new ZipFile(f);\r
+ zip.enableRecursiveSearch();\r
+ zip.setLabel(label);\r
+ result.add(zip);\r
+ } else if (ZipFile.isClassFile(f)) {\r
+ SingleFile file = new SingleFile(f);\r
+ file.setLabel(label);\r
+ result.add(file); \r
+ }\r
+ }\r
+ return result.toArray(new IClassList[0]);\r
+ }\r
+\r
+ public static List<String> enumerateSystemClasspath() {\r
+ final String PATH_SPLIT_REGEX = "\\s*" + File.pathSeparatorChar + "\\s*";\r
+ List<String> classpath = new ArrayList<String>(1024);\r
+ \r
+ String classPath = System.getProperty("java.class.path");\r
+ for (String path: classPath.split(PATH_SPLIT_REGEX)) {\r
+ classpath.add(new File(path).getAbsolutePath());\r
+ }\r
+ \r
+ String bootClassPath = System.getProperty("sun.boot.class.path");\r
+ for (String path: bootClassPath.split(PATH_SPLIT_REGEX)) {\r
+ classpath.add(new File(path).getAbsolutePath());\r
+ }\r
+ \r
+ String extDirs = System.getProperty("java.ext.dirs");\r
+ for (String extDirPath: extDirs.split(PATH_SPLIT_REGEX)) {\r
+ File extDir = new File(extDirPath);\r
+ if (extDir.isDirectory() && extDir.canRead()) {\r
+ File[] extFiles = extDir.listFiles(new FileFilter() {\r
+ @Override\r
+ public boolean accept(File pathname) {\r
+ String lowerFilename = pathname.getAbsolutePath();\r
+ return lowerFilename.endsWith(".jar") || lowerFilename.endsWith(".zip");\r
+ }\r
+ });\r
+ for (File extFile: extFiles) {\r
+ classpath.add(extFile.getAbsolutePath());\r
+ }\r
+ }\r
+ }\r
+ return classpath;\r
+ }\r
+\r
+ public static IClassList[] merge(IClassList[] list1, IClassList[] list2) {\r
+ IClassList[] result = Arrays.copyOf(list1, list1.length + list2.length);\r
+ System.arraycopy(list2, 0, result, list1.length, list2.length);\r
+ return result;\r
+ }\r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import java.io.File;\r
+import java.io.FileFilter;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Stack;\r
+\r
+\r
+public class Directory implements IClassList {\r
+\r
+ private File dir;\r
+ private String label;\r
+ private boolean searchZip = false;\r
+ private boolean searchZipRecursive = false;\r
+ private boolean autoOpen = true;\r
+ \r
+ public Directory(File dir) {\r
+ assert dir.isDirectory(): dir.getAbsolutePath() + " is not a directory.";\r
+ this.dir = dir;\r
+ }\r
+ \r
+ public void setLabel(String l) {\r
+ this.label = l;\r
+ }\r
+ \r
+ @Override\r
+ public String getLabel() {\r
+ return label;\r
+ }\r
+ \r
+ /**\r
+ * Enumerate a list of directories involved in the specified top directory.\r
+ * @param topDir specifies a directory including sub-directories to be enumerated.\r
+ * @param depth specifies the maximum number of recursive search.\r
+ * "0" returns the top directory itself. "1" returns a list of directories in the top directory.\r
+ * @return\r
+ */\r
+ public static Directory[] listSubdirectories(File topDir, int depth) {\r
+ ArrayList<File> dirs = new ArrayList<File>();\r
+ dirs.add(topDir);\r
+ \r
+ for (int i=0; i<depth; ++i) {\r
+ ArrayList<File> subdirs = new ArrayList<File>();\r
+ for (File d: dirs) {\r
+ for (File f: d.listFiles()) {\r
+ if (f.isDirectory() && \r
+ !f.getName().equals(".") &&\r
+ !f.getName().equals("..")) subdirs.add(f);\r
+ }\r
+ }\r
+ dirs = subdirs;\r
+ }\r
+ \r
+ Directory[] result = new Directory[dirs.size()];\r
+ for (int i=0; i<dirs.size(); ++i) {\r
+ result[i] = new Directory(dirs.get(i));\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ /**\r
+ * @return a directory information corresponding to the object.\r
+ */\r
+ public File getDirectory() {\r
+ return dir;\r
+ }\r
+ \r
+ public void enableZipSearch() {\r
+ this.searchZip = true;\r
+ }\r
+ \r
+ public void enableRecursiveZipSearch() {\r
+ this.searchZip = true;\r
+ this.searchZipRecursive = true;\r
+ }\r
+ \r
+ public void disableAutoOpen() {\r
+ this.autoOpen = false;\r
+ }\r
+ \r
+ @Override\r
+ public void process(IClassListCallback c) {\r
+ FileFilterCallback filter = new FileFilterCallback(c); \r
+ Stack<File> worklist = new Stack<File>();\r
+ worklist.push(dir);\r
+ \r
+ while (!worklist.empty()) {\r
+ File f = worklist.pop();\r
+ if (f.isDirectory()) { \r
+ if (f.exists() && f.canRead()) {\r
+ File[] contents = f.listFiles(filter);\r
+ if (contents != null) {\r
+ for (File content: contents) {\r
+ if (!dir.equals(content)) { \r
+ worklist.add(content);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ } else if (f.isFile() && c.isTarget(f.getAbsolutePath()) && f.canRead()) {\r
+ try {\r
+ if (autoOpen) {\r
+ FileInputStream binaryStream = new FileInputStream(f);\r
+ c.process(f.getCanonicalPath(), binaryStream);\r
+ binaryStream.close();\r
+ } else {\r
+ c.process(f.getCanonicalPath(), null);\r
+ }\r
+ } catch (IOException e) {\r
+ boolean stop = c.reportError(f.getAbsolutePath(), e);\r
+ if (stop) break;\r
+ }\r
+ } else if (ZipFile.isZipFile(f) && f.canRead()) {\r
+ ZipFile zip = new ZipFile(f);\r
+ if (searchZipRecursive) zip.enableRecursiveSearch();\r
+ zip.process(c);\r
+ }\r
+ }\r
+ }\r
+ \r
+ private class FileFilterCallback implements FileFilter { \r
+ \r
+ private IClassListCallback callback;\r
+ public FileFilterCallback(IClassListCallback c) {\r
+ this.callback = c;\r
+ }\r
+ @Override\r
+ public boolean accept(File f) {\r
+ return f.isDirectory() ||\r
+ (searchZip && ZipFile.isZipFile(f)) || \r
+ (f.isFile() && callback.isTarget(f.getAbsolutePath()));\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+public interface IClassList {\r
+\r
+ /**\r
+ * Start a file enumeration process.\r
+ * @param c receives call back method calls from the enumerator.\r
+ */\r
+ public void process(IClassListCallback c);\r
+ \r
+ \r
+ public String getLabel();\r
+ \r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+\r
+public interface IClassListCallback {\r
+\r
+ \r
+ /**\r
+ * @param name is a string representing the data. \r
+ * @return The implementation returns true if the implementation \r
+ * want to process the data.\r
+ * The implementation may return false to skip the data. \r
+ */\r
+ public boolean isTarget(String name);\r
+ \r
+ public void process(String name, InputStream stream) throws IOException;\r
+ \r
+ /**\r
+ * @param name represents the data.\r
+ * @param e is an exception occurred during the process.\r
+ * @return The implementation returns true if the implementation\r
+ * want to stop the process as soon as possible.\r
+ */\r
+ public boolean reportError(String name, Exception e);\r
+ \r
+\r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+\r
+public class SingleFile implements IClassList {\r
+ \r
+ private File file;\r
+ private String label;\r
+\r
+ public SingleFile(File f) {\r
+ assert f.isFile(): f.getAbsolutePath() + " is not a file.";\r
+ this.file = f;\r
+ }\r
+\r
+ public void setLabel(String l) {\r
+ this.label = l;\r
+ }\r
+ \r
+ @Override\r
+ public String getLabel() {\r
+ return label;\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public void process(IClassListCallback c) {\r
+ String filename = file.getAbsolutePath();\r
+ if (c.isTarget(filename)) {\r
+ try {\r
+ FileInputStream binaryStream = new FileInputStream(file);\r
+ c.process(filename, binaryStream);\r
+ binaryStream.close();\r
+ } catch (IOException e) {\r
+ c.reportError(filename, e);\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import java.io.File;\r
+import java.io.FileFilter;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.zip.ZipEntry;\r
+import java.util.zip.ZipInputStream;\r
+\r
+/**\r
+ * A class to select files to be processed.\r
+ *\r
+ */\r
+public class ZipFile implements IClassList {\r
+\r
+ private static final String[] zipExt = new String[] { ".jar", ".zip", ".war" };\r
+\r
+ public static boolean isZipFile(String filename) { \r
+ String lowerFileName = filename.toLowerCase();\r
+ for (String ext: zipExt) {\r
+ if (lowerFileName.endsWith(ext)) return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ public static boolean isZipFile(File f) {\r
+ return f.isFile() && isZipFile(f.getAbsolutePath());\r
+ }\r
+\r
+\r
+ public static boolean isClassFile(String filename) {\r
+ String lowerFileName = filename.toLowerCase(); \r
+ return lowerFileName.endsWith(".class");\r
+ }\r
+\r
+ public static boolean isClassFile(File f) {\r
+ return f.isFile() && isClassFile(f.getAbsolutePath());\r
+ }\r
+\r
+ public static class ClassFileFilter implements FileFilter {\r
+\r
+ @Override\r
+ public boolean accept(File f) {\r
+ return f.isDirectory() || isClassFile(f) || isZipFile(f);\r
+ }\r
+ }\r
+ \r
+ private File zip;\r
+ private String label;\r
+ private boolean searchRecursive; \r
+ \r
+ public ZipFile(File zipFile) {\r
+ assert isZipFile(zipFile);\r
+ this.zip = zipFile;\r
+ }\r
+ \r
+ public void setLabel(String l) {\r
+ this.label = l;\r
+ }\r
+ \r
+ @Override\r
+ public String getLabel() {\r
+ return label;\r
+ }\r
+ \r
+ public void enableRecursiveSearch() {\r
+ searchRecursive = true;\r
+ }\r
+\r
+ @Override\r
+ public void process(IClassListCallback c) {\r
+ try {\r
+ processZip(new FileInputStream(zip), zip.getAbsolutePath(), c, true);\r
+ } catch (IOException e) {\r
+ c.reportError(zip.getAbsolutePath(), e);\r
+ }\r
+ }\r
+\r
+ private void processZip(InputStream stream, String zipFilename, IClassListCallback c, boolean closeStream) {\r
+ ZipInputStream zip = new ZipInputStream(stream);\r
+ String lastEntry = zipFilename;\r
+ try {\r
+ ZipEntry entry = zip.getNextEntry();\r
+ while (entry != null) {\r
+ lastEntry = zipFilename + "/" + entry.getName();\r
+ if (c.isTarget(entry.getName())) {\r
+ c.process(lastEntry, zip);\r
+ } else if (searchRecursive && ZipFile.isZipFile(entry.getName())) {\r
+ processZip(zip, lastEntry, c, false);\r
+ }\r
+ zip.closeEntry();\r
+ entry = zip.getNextEntry();\r
+ }\r
+ if (closeStream) {\r
+ zip.close();\r
+ }\r
+ } catch (IOException e) {\r
+ c.reportError(lastEntry, e);\r
+ } catch (RuntimeException e) {\r
+ c.reportError(lastEntry, e);\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import java.util.Stack;\r
+\r
+/**\r
+ * An implementation of Depth-First Search \r
+ * for IDirectedGraph object.\r
+ */\r
+public class DepthFirstSearch {\r
+\r
+ /**\r
+ * Executes DFS from the specified vertex.\r
+ * @param startVertexId\r
+ * @param visit will receive a call back from this method.\r
+ */\r
+ public static void search(final IDirectedGraph graph, int startVertexId, IDepthFirstVisitor visit) {\r
+ Stack<DfsProgress> stack = new Stack<DfsProgress>();\r
+ boolean[] visited = new boolean[graph.getVertexCount()];\r
+ visit.onStart(startVertexId);\r
+\r
+ DfsProgress progress = new DfsProgress(graph, startVertexId);\r
+ stack.push(progress);\r
+ \r
+ while (!stack.isEmpty()) {\r
+ // The top of the stack represents the current location and state.\r
+ DfsProgress node = stack.peek();\r
+ \r
+ boolean isFirstVisit = !visited[node.getVertex()];\r
+ visited[node.getVertex()] = true;\r
+ \r
+ if (isFirstVisit) { \r
+ boolean continueVisit = visit.onVisit(node.getVertex());\r
+ if (!continueVisit) {\r
+ stack.pop(); // go back to the previous vertex\r
+ visit.onLeave(node.getVertex());\r
+ continue; \r
+ }\r
+ }\r
+ \r
+ // Find a next node to visit\r
+ int nextNode = -1;\r
+ while (node.hasNext()) {\r
+ int nextNodeCandidate = node.next();\r
+ if (!visited[nextNodeCandidate]) {\r
+ nextNode = nextNodeCandidate;\r
+ break;\r
+ } else {\r
+ visit.onVisitAgain(nextNodeCandidate);\r
+ }\r
+ } \r
+ if (nextNode != -1) { \r
+ // If found, set the next visit \r
+ stack.push(new DfsProgress(graph, nextNode));\r
+ } else {\r
+ // If not found, go back to the previous node from the current node. \r
+ stack.pop();\r
+ visit.onLeave(node.getVertex());\r
+ }\r
+ }\r
+ \r
+ visit.onFinished(visited);\r
+ }\r
+ \r
+ /**\r
+ * This object manages visited edges from a vertex. \r
+ */\r
+ private static class DfsProgress {\r
+\r
+ private IDirectedGraph graph;\r
+ private int vertex;\r
+ private int edgeIndex;\r
+ \r
+ public DfsProgress(IDirectedGraph graph, int vertexId) {\r
+ this.graph = graph;\r
+ this.vertex = vertexId;\r
+ this.edgeIndex = 0;\r
+ }\r
+ public int getVertex() {\r
+ return vertex;\r
+ }\r
+ public boolean hasNext() {\r
+ int[] edgeList = graph.getEdges(vertex);\r
+ return (edgeList != null) && (edgeIndex < edgeList.length);\r
+ }\r
+ public int next() {\r
+ if (graph.getEdges(vertex) != null) {\r
+ int next = graph.getEdges(vertex)[edgeIndex];\r
+ edgeIndex++;\r
+ return next;\r
+ } else {\r
+ return -1;\r
+ }\r
+ }\r
+ \r
+ }\r
+ \r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import java.util.Arrays;\r
+\r
+import soba.util.IntPairProc;\r
+import soba.util.IntPairSet;\r
+import soba.util.IntPairUtil;\r
+import soba.util.IntSetStack;\r
+import soba.util.IntStack;\r
+\r
+/**\r
+ * This class represents a directed acyclic graph.\r
+ */\r
+public class DirectedAcyclicGraph implements IDirectedGraph {\r
+ \r
+ private IDirectedGraph base;\r
+ private int[] sccIds;\r
+ private DirectedGraph dag;\r
+\r
+ /**\r
+ * Creates a new <code>DirectedAcyclicGraph</code> instance from a specified base graph.\r
+ * If the base graph has strongly connected components, \r
+ * they are removed by Tarjan's algorithm.\r
+ * @param base is a directed graph.\r
+ */\r
+ public DirectedAcyclicGraph(IDirectedGraph base) {\r
+ this.base = base;\r
+ this.sccIds = new int[base.getVertexCount()];\r
+ removeStronglyConnectedComponents();\r
+ }\r
+ \r
+ /**\r
+ * A copy constructor for getReverseGraph()\r
+ */\r
+ private DirectedAcyclicGraph(DirectedAcyclicGraph g) {\r
+ this.base = g.base;\r
+ this.sccIds = g.sccIds;\r
+ this.dag = g.dag;\r
+ }\r
+ \r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public int[] getEdges(int memberId) {\r
+ return dag.getEdges(memberId);\r
+ }\r
+ \r
+ /**\r
+ * @return the number of vertices.\r
+ * The value is the same as the base graph.\r
+ */\r
+ @Override\r
+ public int getVertexCount() {\r
+ return dag.getVertexCount();\r
+ }\r
+ \r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public void forEachEdge(IntPairProc proc) {\r
+ dag.forEachEdge(proc);\r
+ }\r
+ \r
+ /**\r
+ * @param vertexId specifies a vertex.\r
+ * @return true if the ID specifies a vertex \r
+ * included in the DAG.\r
+ * If the vertex is a part of strongly connected components\r
+ * and excluded from the DAG, the method returns false. \r
+ */\r
+ public boolean isRepresentativeNode(int vertexId) {\r
+ return sccIds[vertexId] == vertexId;\r
+ }\r
+ \r
+ /**\r
+ * @param vertexId specifies a vertex.\r
+ * @return a vertex ID which is a representative node \r
+ * of the strongly connected components including the specified vertexId.\r
+ */\r
+ public int getRepresentativeNode(int vertexId) {\r
+ return sccIds[vertexId];\r
+ }\r
+ \r
+ /**\r
+ * @return a new graph with reversed edges.\r
+ * An edge from vertex A to vertex B in the original graph is \r
+ * translated into an edge from B to A in the new graph.\r
+ */\r
+ public DirectedAcyclicGraph getReverseGraph() {\r
+ DirectedAcyclicGraph reverse = new DirectedAcyclicGraph(this);\r
+ reverse.dag = this.dag.getReverseGraph();\r
+ return reverse;\r
+ }\r
+ \r
+ @Override\r
+ public boolean equals(Object obj) {\r
+ if (obj instanceof DirectedAcyclicGraph) {\r
+ DirectedAcyclicGraph another = (DirectedAcyclicGraph)obj;\r
+ if (this.getVertexCount() == another.getVertexCount()) {\r
+ for (int i=0; i<getVertexCount(); ++i) {\r
+ if (this.sccIds[i] != another.sccIds[i]) return false;\r
+ if (isRepresentativeNode(i)) {\r
+ int[] to1 = this.getEdges(i);\r
+ int[] to2 = another.getEdges(i);\r
+ if (to1.length != to2.length) return false;\r
+ for (int j=0; j<to1.length; ++j) {\r
+ if (to1[j] != to2[j]) return false;\r
+ }\r
+ }\r
+ }\r
+ \r
+ return true;\r
+ } else {\r
+ return false;\r
+ }\r
+ \r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ \r
+ private TarjanData data;\r
+ /**\r
+ * Apply Tarjan's algorithm to detect SCCs.\r
+ */\r
+ private void removeStronglyConnectedComponents() { \r
+ data = new TarjanData();\r
+ for (int i=0; i<base.getVertexCount(); ++i) {\r
+ if (data.visitIndex[i] == -1) {\r
+ tarjanDFS(i);\r
+ }\r
+ }\r
+ \r
+ final IntPairSet dagEdges = new IntPairSet();\r
+ base.forEachEdge(new IntPairProc() {\r
+ \r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ int from = sccIds[elem1];\r
+ int to = sccIds[elem2];\r
+ if (from != to) dagEdges.add(from, to);\r
+ return true;\r
+ }\r
+ });\r
+ dag = new DirectedGraph(base.getVertexCount(), IntPairUtil.createList(dagEdges));\r
+ data = null;\r
+ }\r
+ \r
+ private void tarjanDFS(int vId) {\r
+ data.visitIndex[vId] = data.currentIndex;\r
+ data.lowlink[vId] = data.currentIndex;\r
+ data.currentIndex++;\r
+ data.stack.push(vId);\r
+ //System.out.println("currentIndex = " + data.currentIndex + ", stack = " + data.stack.size());\r
+ \r
+ // For each edge from vId\r
+ for (int to: base.getEdges(vId)) {\r
+ // If the next vertex is not visited yet, visit it.\r
+ if (data.visitIndex[to] == -1) {\r
+ tarjanDFS(to);\r
+ data.lowlink[vId] = Math.min(data.lowlink[vId], data.lowlink[to]); \r
+ } else if (data.stack.contains(to)) {\r
+ data.lowlink[vId] = Math.min(data.lowlink[vId], data.visitIndex[to]); \r
+ }\r
+ }\r
+\r
+ // If the minimum ID of reachable vertices is equals to the vertex ID,\r
+ // then the vertex is a "root" of SCC.\r
+ // (If vID is not a "root" and included in a SCC, \r
+ // the vID is kept on the stack -- the root of SCC will pop the vID.)\r
+ if (data.lowlink[vId] == data.visitIndex[vId]) {\r
+ int pop;\r
+ do {\r
+ pop = data.stack.pop();\r
+ sccIds[pop] = vId;\r
+ } while (pop != vId); // until pop == vId\r
+ }\r
+ }\r
+ \r
+ private class TarjanData {\r
+ public int currentIndex;\r
+ public int[] visitIndex; // ID for each vertex\r
+ public int[] lowlink; // the minimum ID of vertices reachable from a vertex\r
+ public IntStack stack;\r
+ \r
+ public TarjanData() {\r
+ currentIndex = 0;\r
+ visitIndex = new int[base.getVertexCount()];\r
+ lowlink = new int[base.getVertexCount()];\r
+ stack = new IntSetStack(base.getVertexCount());\r
+ Arrays.fill(visitIndex, -1);\r
+ }\r
+ \r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import java.util.Arrays;\r
+\r
+import soba.util.IntPairList;\r
+import soba.util.IntPairProc;\r
+\r
+import gnu.trove.set.hash.TIntHashSet;\r
+\r
+/**\r
+ * An instance of this class is a directed graph.\r
+ * Each vertex is represented as an integer\r
+ * between 0 and vertexCount-1. \r
+ */\r
+public class DirectedGraph implements IDirectedGraph {\r
+\r
+ private int vertexCount;\r
+ private int edgeCount;\r
+ private IntPairList edges;\r
+ private int[][] forward;\r
+ \r
+ private static final int[] EMPTY_ARRAY = new int[0];\r
+ \r
+ /**\r
+ * Creates a new <code>DirectedGraph</code> instance.\r
+ * Duplicated edges are ignored.\r
+ * @param vertexCount is the number of vertices.\r
+ * @param edges will be modified by DirectedGraph.\r
+ */\r
+ public DirectedGraph(int vertexCount, IntPairList edges) {\r
+ this.vertexCount = vertexCount;\r
+ this.edges = edges;\r
+ this.edgeCount = edges.size();\r
+ this.edges.sort();\r
+ this.forward = constructEdgeArray(edges);\r
+ }\r
+ \r
+ /**\r
+ * @param edges are pairs of vertex IDs. \r
+ * @return array representing edges.\r
+ * Duplicated edges are excluded in the resultant array.\r
+ */\r
+ private int[][] constructEdgeArray(IntPairList edges) {\r
+ // forwardTemp[V] means a list of edges from vertex V.\r
+ TIntHashSet[] forwardTemp = new TIntHashSet[vertexCount];\r
+ for (int i=0; i<edges.size(); ++i) {\r
+ int from = edges.getFirstValue(i);\r
+ int to = edges.getSecondValue(i);\r
+\r
+ if (forwardTemp[from] == null) {\r
+ forwardTemp[from] = new TIntHashSet(2);\r
+ }\r
+ forwardTemp[from].add(to);\r
+ }\r
+ // Translate a set object to an array.\r
+ int[][] forward = new int[vertexCount][];\r
+ for (int i=0; i<vertexCount; ++i) {\r
+ if (forwardTemp[i] != null) {\r
+ int[] array = forwardTemp[i].toArray();\r
+ Arrays.sort(array);\r
+ forward[i] = array;\r
+ } else {\r
+ forward[i] = EMPTY_ARRAY;\r
+ }\r
+ }\r
+ return forward;\r
+ }\r
+\r
+ /**\r
+ * @return the number of edges.\r
+ */\r
+ public int getEdgeCount() {\r
+ return edgeCount;\r
+ }\r
+\r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public int getVertexCount() {\r
+ return vertexCount;\r
+ }\r
+ \r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public void forEachEdge(IntPairProc proc) {\r
+ edges.foreach(proc);\r
+ }\r
+ \r
+ /** {@inheritDoc} */\r
+ @Override\r
+ public int[] getEdges(int memberId) {\r
+ return this.forward[memberId];\r
+ }\r
+\r
+ /**\r
+ * @return a new graph with reversed edges.\r
+ * An edge from vertex A to vertex B in the original graph is \r
+ * translated into an edge from B to A in the new graph.\r
+ */\r
+ public DirectedGraph getReverseGraph() {\r
+ return GraphUtil.getReverseGraph(this);\r
+ }\r
+ \r
+ /**\r
+ * @return a new graph whose vertices are connected by undirected edges.\r
+ * In other words, a directed edge from vertex A to vertex B is \r
+ * translated into two edges A to B and B to A. \r
+ */\r
+ public DirectedGraph getUndirectedGraph() {\r
+ return GraphUtil.getUndirectedGraph(this);\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+/**\r
+ * This class implements an algorithm described in \r
+ * Keith D. Cooper, Timothy J. Harvey and Ken Kennedy: A Simple, Fast Dominance Algorithm.\r
+ */\r
+public class DominanceTree {\r
+\r
+ private SingleRootDirectedGraph base;\r
+ private IDirectedGraph reverse;\r
+ private int[] reversePostOrder;\r
+ private int[] immediateDominator;\r
+\r
+ /**\r
+ * Creates a new <code>DominanceTree</code> instance from a <code>SingleRootDirectedGraph</code> object.\r
+ * @param graph is a <code>SingleRootDirectedGraph</code> object.\r
+ */\r
+ public DominanceTree(SingleRootDirectedGraph graph) {\r
+ this.base = graph;\r
+ this.reverse = graph.getReverseGraph();\r
+ this.reversePostOrder = new int[base.getVertexCount()];\r
+ this.immediateDominator = new int[base.getVertexCount()];\r
+ computeSpanningTree();\r
+ computeDominators();\r
+ }\r
+ \r
+ /**\r
+ * @param vertexID specifies a vertex.\r
+ * @return true if the specified vertex is root of the tree.\r
+ */\r
+ public boolean isRoot(int vertexID) {\r
+ return vertexID == base.getRootId();\r
+ }\r
+ \r
+ /**\r
+ * @param vertexID specifies a vertex.\r
+ * @return the dominator vertex ID of the specified vertex.\r
+ */\r
+ public int getDominator(int vertexID) {\r
+ return immediateDominator[vertexID];\r
+ }\r
+\r
+ /**\r
+ * Visit nodes using depth-first search.\r
+ * Reverse-post-order traversal.\r
+ */\r
+ private void computeSpanningTree() {\r
+ \r
+ DepthFirstSearch.search(base, base.getRootId(), new IDepthFirstVisitor() {\r
+ \r
+ private int reversePostOrderIndex = base.getVertexCount() - 1;\r
+\r
+ @Override\r
+ public void onStart(int startVertexId) {\r
+ }\r
+ \r
+ @Override\r
+ public boolean onVisit(int vertexId) {\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public void onLeave(int vertexId) {\r
+ reversePostOrder[vertexId] = reversePostOrderIndex;\r
+ reversePostOrderIndex--;\r
+ }\r
+ \r
+ @Override\r
+ public void onFinished(boolean[] visited) {\r
+ }\r
+ \r
+ @Override\r
+ public void onVisitAgain(int vertexId) {\r
+ }\r
+ });\r
+ }\r
+ \r
+ private void computeDominators() {\r
+ \r
+ // sort vertices according to the descending order of reverse post order (a regular post order)\r
+ int[] sortedVertices = new int[base.getVertexCount()];\r
+ for (int i=0; i<base.getVertexCount(); ++i) {\r
+ sortedVertices[reversePostOrder[i]] = i;\r
+ }\r
+ \r
+ // initialize idom\r
+ for (int from=0; from<base.getVertexCount(); ++from) {\r
+ for (int to: base.getEdges(from)) {\r
+ if (reversePostOrder[from] < reversePostOrder[to]) immediateDominator[to] = from;\r
+ }\r
+ }\r
+ \r
+ // iteratively compute dominators\r
+ boolean changed = true;\r
+ while (changed) {\r
+ changed = false;\r
+ for (int v: sortedVertices) {\r
+ // dom(v) == Nearest Common Ancestor of v's predecessors\r
+ int[] pred = reverse.getEdges(v);\r
+ for (int idx=0; idx<pred.length; ++idx) {\r
+ int idom = immediateDominator[v];\r
+ int nca = nearestCommonAncestor(idom, pred[idx]);\r
+ if (idom != nca) {\r
+ changed = true;\r
+ immediateDominator[v] = nca;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Returns the nearest common ancestor of two nodes.\r
+ */\r
+ public final int nearestCommonAncestor(int v1, int v2) {\r
+ while (v1 != v2) {\r
+ int i1 = reversePostOrder[v1];\r
+ int i2 = reversePostOrder[v2];\r
+ if (i1 > i2) { \r
+ v1 = immediateDominator[v1];\r
+ } else {\r
+ assert (i1 < i2): "v1!=v2 implies i1!=i2";\r
+ v2 = immediateDominator[v2];\r
+ }\r
+ }\r
+ return v1;\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import soba.util.IntPairList;\r
+import soba.util.IntPairProc;\r
+\r
+public class GraphUtil {\r
+\r
+ /**\r
+ * @return a new graph with reversed edges.\r
+ * An edge from vertex A to vertex B in the original graph is \r
+ * translated into an edge from B to A in the new graph.\r
+ */\r
+ public static DirectedGraph getReverseGraph(IDirectedGraph g) {\r
+ final IntPairList reverseEdges = new IntPairList();\r
+ g.forEachEdge(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ reverseEdges.add(elem2, elem1);\r
+ return true;\r
+ }\r
+ });\r
+ return new DirectedGraph(g.getVertexCount(), reverseEdges);\r
+ }\r
+ \r
+ /**\r
+ * @return a new graph whose vertices are connected by undirected edges.\r
+ * In other words, a directed edge from vertex A to vertex B is \r
+ * translated into two edges A to B and B to A. \r
+ */\r
+ public static DirectedGraph getUndirectedGraph(IDirectedGraph g) {\r
+ final IntPairList undirectedEdges = new IntPairList();\r
+ g.forEachEdge(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ undirectedEdges.add(elem1, elem2);\r
+ undirectedEdges.add(elem2, elem1);\r
+ return true;\r
+ }\r
+ });\r
+ return new DirectedGraph(g.getVertexCount(), undirectedEdges);\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+public interface IDepthFirstVisitor {\r
+\r
+ /**\r
+ * This method is called in order to notify the beginning of a visiting process.\r
+ * @param startVertexId\r
+ */\r
+ public void onStart(int startVertexId);\r
+ \r
+ /**\r
+ * @param vertexId identifies a visited vertex.\r
+ * @return true if you want to continue visiting process beyond the vertex.\r
+ */\r
+ public boolean onVisit(int vertexId);\r
+ \r
+ /**\r
+ * This method is called when a vertex is visited again.\r
+ * You may use this method to detect two or more paths for the vertex.\r
+ * @param vertexId\r
+ */\r
+ public void onVisitAgain(int vertexId);\r
+ \r
+ /**\r
+ * @param vertexId\r
+ */\r
+ public void onLeave(int vertexId);\r
+ \r
+ /**\r
+ * This method is called when the visit process is finished.\r
+ * @param visited\r
+ */\r
+ public void onFinished(boolean[] visited);\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import soba.util.IntPairProc;\r
+\r
+public interface IDirectedGraph {\r
+\r
+ /**\r
+ * @return the number of vertices.\r
+ */\r
+ public int getVertexCount();\r
+ \r
+ /**\r
+ * @param memberId specifies a vertex.\r
+ * @return an array of vertex IDs connected from the specified vertex.\r
+ */\r
+ public int[] getEdges(int memberId);\r
+ \r
+ /**\r
+ * Executes a procedure for each edge.\r
+ */\r
+ public void forEachEdge(IntPairProc proc);\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import soba.util.IntPairList;\r
+import soba.util.IntPairProc;\r
+\r
+/**\r
+ * A directed graph that has a single root node.\r
+ */\r
+public class SingleRootDirectedGraph implements IDirectedGraph {\r
+\r
+ private IDirectedGraph base;\r
+ private int[] edgesFromRoot;\r
+ \r
+ /**\r
+ * Creates a new <code>SingleRootDirectedGraph</code> instance from a base graph.\r
+ * The instance is a directed graph with a single root.\r
+ * The root is connected to vertices which have no incoming edges in the original graph.\r
+ * @param base is a directed graph.\r
+ */\r
+ public SingleRootDirectedGraph(IDirectedGraph base) {\r
+ this.base = base;\r
+ \r
+ // To find vertices without incoming edges, first cycles must be removed from the graph.\r
+ DirectedAcyclicGraph dag = new DirectedAcyclicGraph(base);\r
+ final boolean[] hasIncomingEdge = new boolean[base.getVertexCount()];\r
+ dag.forEachEdge(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int from, int to) {\r
+ hasIncomingEdge[to] = true;\r
+ return true;\r
+ }\r
+ });\r
+ \r
+ // Find vertices which have incoming edges\r
+ int count = 0;\r
+ for (int i=0; i<hasIncomingEdge.length; ++i) {\r
+ if (!hasIncomingEdge[i] && dag.isRepresentativeNode(i)) count++;\r
+ }\r
+ \r
+ // Connect the root vertex to the vertices that have no incoming edges\r
+ edgesFromRoot = new int[count];\r
+ int edgeIndex = 0;\r
+ for (int i=0; i<base.getVertexCount(); ++i) {\r
+ if (!hasIncomingEdge[i] && dag.isRepresentativeNode(i)) {\r
+ edgesFromRoot[edgeIndex] = i;\r
+ edgeIndex++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return the root vertex ID.\r
+ */\r
+ public int getRootId() {\r
+ return base.getVertexCount();\r
+ }\r
+ \r
+ @Override\r
+ public int getVertexCount() {\r
+ return base.getVertexCount() + 1;\r
+ }\r
+ \r
+ @Override\r
+ public int[] getEdges(int memberId) {\r
+ if (memberId < base.getVertexCount()) {\r
+ return base.getEdges(memberId);\r
+ } else {\r
+ return edgesFromRoot;\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void forEachEdge(IntPairProc proc) {\r
+ // base vertices\r
+ for (int from=0; from<base.getVertexCount(); ++from) {\r
+ for (int to: base.getEdges(from)) {\r
+ if (!proc.execute(from, to)) return;\r
+ }\r
+ }\r
+ // the root vertex\r
+ for (int to: edgesFromRoot) {\r
+ if (!proc.execute(getRootId(), to)) return;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @return a new graph with reversed edges.\r
+ * An edge from vertex A to vertex B in the original graph is \r
+ * translated into an edge from B to A in the new graph.\r
+ */\r
+ public DirectedGraph getReverseGraph() {\r
+ final IntPairList reverseEdges = new IntPairList();\r
+ base.forEachEdge(new IntPairProc() {\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ reverseEdges.add(elem2, elem1);\r
+ return true;\r
+ }\r
+ });\r
+ for (int id: getEdges(getRootId())) {\r
+ reverseEdges.add(id, getRootId());\r
+ }\r
+ return new DirectedGraph(getVertexCount(), reverseEdges);\r
+ }\r
+\r
+ \r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+import java.util.stream.Collectors;\r
+\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+import soba.core.ClassHierarchy;\r
+import soba.core.ClassInfo;\r
+import soba.core.ClassHierarchy.FrozenHierarchyException;\r
+import soba.core.method.FieldAccess;\r
+\r
+public class ClassHierarchyTest implements ExampleProgram {\r
+\r
+ private ClassHierarchy ch;\r
+\r
+ // Class hierarchy\r
+ //\r
+ // C\r
+ // -> D\r
+ // -> H\r
+ // -> F\r
+ // -> G\r
+ // E\r
+ \r
+ private static ClassInfo c;\r
+ private static ClassInfo d;\r
+ private static ClassInfo e;\r
+ private static ClassInfo f;\r
+ private static ClassInfo g;\r
+ private static ClassInfo h;\r
+ private static ClassInfo i;\r
+ private static ClassInfo j;\r
+ private static ClassInfo k;\r
+ \r
+ @BeforeClass\r
+ public static void setUpClassInfo() throws IOException {\r
+ c = new ClassInfo("C.class", new FileInputStream("bin/" + CLASS_C + ".class"));\r
+ d = new ClassInfo("D.class", new FileInputStream("bin/" + CLASS_D + ".class"));\r
+ e = new ClassInfo("E.class", new FileInputStream("bin/" + CLASS_E + ".class"));\r
+ f = new ClassInfo("F.class", new FileInputStream("bin/" + CLASS_F + ".class"));\r
+ g = new ClassInfo("G.class", new FileInputStream("bin/" + CLASS_G + ".class"));\r
+ h = new ClassInfo("H.class", new FileInputStream("bin/" + CLASS_H + ".class"));\r
+ i = new ClassInfo("I.class", new FileInputStream("bin/" + CLASS_I + ".class"));\r
+ j = new ClassInfo("I.class", new FileInputStream("bin/" + CLASS_J + ".class"));\r
+ k = new ClassInfo("I.class", new FileInputStream("bin/" + CLASS_K + ".class"));\r
+ }\r
+ \r
+ @Before\r
+ public void createHierarchy() {\r
+ ch = new ClassHierarchy();\r
+ ch.registerClass(c);\r
+ ch.registerClass(d);\r
+ ch.registerClass(e);\r
+ ch.registerClass(f);\r
+ ch.registerClass(g);\r
+ ch.registerClass(h);\r
+ ch.registerClass(i);\r
+ ch.registerClass(j);\r
+ ch.registerClass(k);\r
+ }\r
+ \r
+ @Test\r
+ public void testClasses() {\r
+ assertThat(ch.getClassCount(), is(9));\r
+ assertThat(ch.getClasses(), containsInAnyOrder(CLASS_C, CLASS_D, CLASS_E, CLASS_F, CLASS_G,\r
+ CLASS_H, CLASS_I, CLASS_J, CLASS_K));\r
+ assertThat(ch.getRequestedClasses(), is(empty()));\r
+ }\r
+ \r
+ @Test\r
+ public void testGetClassInfo() {\r
+ assertThat(ch.getClassInfo(CLASS_C), is(c));\r
+ assertThat(ch.getClassInfo(CLASS_D), is(d));\r
+ assertThat(ch.getClassInfo(CLASS_E), is(e));\r
+ assertThat(ch.getClassInfo("pkg/Unknown"), is(nullValue()));\r
+ }\r
+ \r
+ @Test\r
+ public void testGetSuperClass() {\r
+ assertThat(ch.getSuperClass(CLASS_D), is(CLASS_C));\r
+ assertThat(ch.getSuperClass(CLASS_C), is("java/lang/Object"));\r
+ assertThat(ch.getSuperClass(CLASS_E), is("java/lang/Object"));\r
+ assertThat(ch.getSuperClass("pkg/Unknown"), is(nullValue()));\r
+ }\r
+ \r
+ @Test\r
+ public void testIsSamePackage() {\r
+ assertThat(ch.isSamePackage(CLASS_C, CLASS_D), is(true));\r
+ assertThat(ch.isSamePackage(CLASS_C, CLASS_F), is(false));\r
+ assertThat(ch.isSamePackage(CLASS_C, "pkg/Unknown"), is(false));\r
+ assertThat(ch.isSamePackage("pkg/Unknown", CLASS_D), is(false));\r
+ }\r
+ \r
+ @Test\r
+ public void testGetSubtypes() {\r
+ Collection<String> subtypesOfC = ch.getSubtypes(CLASS_C);\r
+ assertThat(subtypesOfC, containsInAnyOrder(CLASS_D, CLASS_F, CLASS_G));\r
+ Collection<String> subtypesOfE = ch.getSubtypes(CLASS_E);\r
+ assertThat(subtypesOfE, is(empty()));\r
+ Collection<String> subtypesOfI = ch.getSubtypes(CLASS_I);\r
+ assertThat(subtypesOfI, containsInAnyOrder(CLASS_D, CLASS_K));\r
+ }\r
+ \r
+ @Test\r
+ public void testGetAllSubtypes() {\r
+ Set<String> typeNames = new HashSet<>();\r
+ typeNames.add(CLASS_C);\r
+ Collection<String> allSubtypesOfC = ch.getAllSubtypes(typeNames);\r
+ assertThat(allSubtypesOfC, containsInAnyOrder(CLASS_C, CLASS_D, CLASS_H, CLASS_F, CLASS_G));\r
+\r
+ typeNames.add(CLASS_I);\r
+ Collection<String> allSubtypesOfCandI = ch.getAllSubtypes(typeNames);\r
+ assertThat(allSubtypesOfCandI, containsInAnyOrder(CLASS_C, CLASS_D, CLASS_H, CLASS_F,\r
+ CLASS_G, CLASS_I, CLASS_K));\r
+ }\r
+ \r
+ @Test\r
+ public void testListAllSuperTypes() {\r
+ Collection<String> supertypesOfH = ch.listAllSuperTypes(CLASS_H);\r
+ assertThat(supertypesOfH, containsInAnyOrder(CLASS_C, CLASS_D, CLASS_K, CLASS_I, "java/lang/Object"));\r
+\r
+ Collection<String> supertypesOfI = ch.listAllSuperTypes(CLASS_I);\r
+ assertThat(supertypesOfI, containsInAnyOrder("java/lang/Object"));\r
+ }\r
+ \r
+ @Test\r
+ public void testGetSuperInterfaces() {\r
+ Collection<String> superInterfaceOfH = ch.getSuperInterfaces(CLASS_D);\r
+ assertThat(superInterfaceOfH, containsInAnyOrder(CLASS_I, CLASS_K));\r
+ Collection<String> superInterfaceOfC = ch.getSuperInterfaces(CLASS_C);\r
+ assertThat(superInterfaceOfC, is(empty()));\r
+ }\r
+ \r
+ @Test\r
+ public void testResolveCall01() {\r
+ MethodInfo[] methodMain = ch.resolveCall(CLASS_E, "main", "([Ljava/lang/String;)V", false);\r
+ checkClasses(methodMain, CLASS_E);\r
+ }\r
+\r
+ @Test\r
+ public void testResolveCall02() {\r
+ // C.n() has 4 implementation: C, D, G, H. F.n() is different.\r
+ MethodInfo[] methodsN = ch.resolveCall(CLASS_C, "n", "()V", true);\r
+ checkClasses(methodsN, CLASS_C, CLASS_D, CLASS_G, CLASS_H);\r
+ \r
+ MethodInfo[] methodsNf = ch.resolveCall(CLASS_F, "n", "()V", true);\r
+ checkClasses(methodsNf, CLASS_F);\r
+ }\r
+ \r
+ @Test\r
+ public void testResolveCall03() {\r
+ // C.m() and D.m() are defined. H.m() is not implemented. \r
+ MethodInfo[] methodsM = ch.resolveCall(CLASS_D, "m", "()V", true);\r
+ checkClasses(methodsM, CLASS_D);\r
+\r
+ MethodInfo[] methodsM2 = ch.resolveCall(CLASS_H, "m", "()V", true);\r
+ checkClasses(methodsM2, CLASS_D);\r
+\r
+ MethodInfo[] methodsM3 = ch.resolveCall(CLASS_C, "m", "()V", true);\r
+ checkClasses(methodsM3, CLASS_C, CLASS_D);\r
+ }\r
+ \r
+ @Test\r
+ public void testResolveCall04() {\r
+ // p() is defined by C and H, not by D.\r
+ MethodInfo[] methodsPc = ch.resolveCall(CLASS_C, "p", "(I)V", true);\r
+ checkClasses(methodsPc, CLASS_C, CLASS_H);\r
+ MethodInfo[] methodsPd = ch.resolveCall(CLASS_C, "p", "(I)V", true);\r
+ checkClasses(methodsPd, CLASS_C, CLASS_H);\r
+ MethodInfo[] methodsPh = ch.resolveCall(CLASS_H, "p", "(I)V", true);\r
+ checkClasses(methodsPh, CLASS_H);\r
+ }\r
+ \r
+ @Test\r
+ public void testResolveCall05() {\r
+ // q() is defined by C and H but it is private.\r
+ MethodInfo[] methodsQc = ch.resolveCall(CLASS_C, "q", "(D)V", true);\r
+ checkClasses(methodsQc, CLASS_C);\r
+ MethodInfo[] methodsQh = ch.resolveCall(CLASS_H, "q", "(D)V", true);\r
+ checkClasses(methodsQh, CLASS_H);\r
+ }\r
+ \r
+ @Test\r
+ public void testResolveCall06() {\r
+ MethodInfo[] methodsMi = ch.resolveCall(CLASS_I, "m", "()V", true);\r
+ checkClasses(methodsMi, CLASS_D);\r
+ }\r
+ \r
+ @Test\r
+ public void testFields() {\r
+ assertThat(ch.resolveField(FieldAccess.createGetField(CLASS_D, "x", "I", false)).getClassName(), is(CLASS_C));\r
+ assertThat(ch.resolveField(FieldAccess.createGetField(CLASS_C, "x", "I", false)).getClassName(), is(CLASS_C));\r
+ assertThat(ch.resolveField(FieldAccess.createGetField(CLASS_H, "x", "I", false)).getClassName(), is(CLASS_H));\r
+ assertThat(ch.resolveField(FieldAccess.createGetField(CLASS_I, "x", "I", true)).getClassName(), is(CLASS_I));\r
+ assertThat(ch.resolveField(FieldAccess.createGetField(CLASS_J, "x", "I", true)).getClassName(), is(CLASS_J));\r
+ assertThat(ch.resolveField(FieldAccess.createGetField(CLASS_K, "x", "I", true)).getClassName(), is(CLASS_I));\r
+ }\r
+ \r
+ private void checkClasses(MethodInfo[] resolved, String... classNames) {\r
+ assertThat(Arrays.stream(resolved).map(m -> m.getClassName()).collect(Collectors.toList()), containsInAnyOrder(classNames));\r
+ }\r
+ \r
+ @Test\r
+ public void testFreeze() {\r
+ assertThat(ch.isFrozen(), is(false));\r
+ ch.freeze();\r
+ assertThat(ch.isFrozen(), is(true));\r
+ try {\r
+ ch.registerClass(f);\r
+ fail();\r
+ } catch (FrozenHierarchyException e) {\r
+ }\r
+ try {\r
+ ch.registerInterfaces(CLASS_F, new ArrayList<String>());\r
+ fail();\r
+ } catch (FrozenHierarchyException e) {\r
+ }\r
+ try {\r
+ ch.registerSuperClass(CLASS_C, "java/lang/Object");\r
+ fail();\r
+ } catch (FrozenHierarchyException e) {\r
+ }\r
+ try {\r
+ ch.registerSubtype("pkg/NewChild", CLASS_C);\r
+ fail();\r
+ } catch (FrozenHierarchyException e) {\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+\r
+import org.junit.Test;\r
+\r
+import soba.core.ClassInfo;\r
+\r
+public class ClassInfoTest implements ExampleProgram {\r
+\r
+ @Test\r
+ public void testClassInfo01() throws Exception {\r
+ String fileName = "bin/" + CLASS_D + ".class";\r
+ ClassInfo c = new ClassInfo(fileName, new FileInputStream(fileName));\r
+ \r
+ assertThat(c.getPackageName(), is("soba/testdata/inheritance1"));\r
+ assertThat(c.getClassName(), is(CLASS_D));\r
+ assertThat(c.getSuperClass(), is(CLASS_C));\r
+ assertThat(c.getInterfaces(), containsInAnyOrder("soba/testdata/inheritance1/I", \r
+ "soba/testdata/inheritance1/K"));\r
+ assertThat(c.getHash(), is(notNullValue()));\r
+ assertThat(c.getClassDirPath(), is("soba" + File.separator + "testdata" + File.separator + "inheritance1"));\r
+ assertThat(c.getClassFileName(), is(fileName));\r
+ assertThat(c.getSourceFileName(), is("soba" + File.separator + "testdata" + File.separator + "inheritance1" + File.separator + "D.java"));\r
+ assertThat(c.getLabel(), is(nullValue()));\r
+ assertThat(c.isLibrary(), is(false));\r
+ \r
+ assertThat(c.getMethodCount(), is(11));\r
+ assertThat(c.getMethods(), hasSize(11));\r
+ assertThat(c.getMethod(0), is(notNullValue()));\r
+ assertThat(c.findMethod("m", "()V"), is(notNullValue()));\r
+ assertThat(c.findMethod("n", "()V"), is(notNullValue()));\r
+ assertThat(c.findMethod("x", "(I)V"), is(notNullValue()));\r
+ assertThat(c.findMethod("example", "(IJDLjava/lang/String;)I"), is(notNullValue()));\r
+ assertThat(c.findMethod("toString", "()Ljava/lang/String;"), is(notNullValue()));\r
+ assertThat(c.findMethod("notExist", "()V"), is(nullValue()));\r
+ \r
+ assertThat(c.getFieldCount(), is(0));\r
+ assertThat(c.getFields(), is(empty()));\r
+ }\r
+ \r
+ @Test\r
+ public void testClassInfo02() throws Exception {\r
+ String fileName = "bin/" + CLASS_H + ".class";\r
+ ClassInfo c = new ClassInfo(fileName, new FileInputStream(fileName), "label");\r
+ \r
+ assertThat(c.getPackageName(), is("soba/testdata/inheritance2"));\r
+ assertThat(c.getClassName(), is(CLASS_H));\r
+ assertThat(c.getSuperClass(), is(CLASS_D));\r
+ assertThat(c.getInterfaces(), is(empty()));\r
+ assertThat(c.getHash(), is(notNullValue()));\r
+ assertThat(c.getClassDirPath(), is("soba" + File.separator + "testdata" + File.separator + "inheritance2"));\r
+ assertThat(c.getClassFileName(), is(fileName));\r
+ assertThat(c.getSourceFileName(), is("soba" + File.separator + "testdata" + File.separator + "inheritance2" + File.separator + "H.java"));\r
+ assertThat(c.getLabel(), is("label"));\r
+ assertThat(c.isLibrary(), is(false));\r
+ \r
+ assertThat(c.getMethodCount(), is(4));\r
+ assertThat(c.getMethods(), hasSize(4));\r
+ assertThat(c.getMethod(0), is(notNullValue()));\r
+ assertThat(c.findMethod("n", "()V"), is(notNullValue()));\r
+ assertThat(c.findMethod("p", "(I)V"), is(notNullValue()));\r
+ assertThat(c.findMethod("q", "(D)V"), is(notNullValue()));\r
+ assertThat(c.findMethod("<init>", "()V"), is(notNullValue()));\r
+ \r
+ assertThat(c.getFieldCount(), is(1));\r
+ assertThat(c.getFields(), hasSize(1));\r
+ assertThat(c.getField(0), is(notNullValue()));\r
+ assertThat(c.findField("x", "I"), is(notNullValue()));\r
+ }\r
+ \r
+ @Test\r
+ public void testLibrary01() throws IOException {\r
+ String fileName = "bin/" + CLASS_H + ".class";\r
+ ClassInfo c = ClassInfo.createLibraryClass(fileName, new FileInputStream(fileName));\r
+ assertThat(c.isLibrary(), is(true));\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+public interface ExampleProgram {\r
+\r
+ public static final String CLASS_C = "soba/testdata/inheritance1/C";\r
+ public static final String CLASS_D = "soba/testdata/inheritance1/D";\r
+ public static final String CLASS_E = "soba/testdata/inheritance2/E";\r
+ public static final String CLASS_F = "soba/testdata/inheritance2/F";\r
+ public static final String CLASS_G = "soba/testdata/inheritance1/G";\r
+ public static final String CLASS_H = "soba/testdata/inheritance2/H";\r
+ public static final String CLASS_I = "soba/testdata/inheritance1/I";\r
+ public static final String CLASS_J = "soba/testdata/inheritance1/J";\r
+ public static final String CLASS_K = "soba/testdata/inheritance1/K";\r
+\r
+ \r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.After;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.FieldInfo;\r
+import soba.core.JavaProgram;\r
+\r
+public class FieldInfoTest {\r
+\r
+ private static FieldInfo fi;\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() throws Exception {\r
+ JavaProgram program = JavaProgramTest.readExampleProgram();\r
+ ClassInfo c = program.getClassInfo(ExampleProgram.CLASS_C);\r
+ fi = c.getField(0);\r
+ }\r
+\r
+ @AfterClass\r
+ public static void tearDownAfterClass() throws Exception {\r
+ }\r
+\r
+ @Before\r
+ public void setUp() throws Exception {\r
+ }\r
+\r
+ @After\r
+ public void tearDown() throws Exception {\r
+ }\r
+\r
+ @Test\r
+ public void testGetPackageName() {\r
+ assertThat(fi.getPackageName(), is("soba/testdata/inheritance1"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetClassName() {\r
+ assertThat(fi.getClassName(), is("soba/testdata/inheritance1/C"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetFieldName() {\r
+ assertThat(fi.getFieldName(), is("x"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetDescriptor() {\r
+ assertThat(fi.getDescriptor(), is("I"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetFieldTypeName() {\r
+ assertThat(fi.getFieldTypeName(), is("int"));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import java.io.File;\r
+\r
+import soba.core.JavaProgram;\r
+import soba.util.files.Directory;\r
+import soba.util.files.IClassList;\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class JavaProgramTest implements ExampleProgram {\r
+\r
+ private static JavaProgram program;\r
+ \r
+ public static JavaProgram readExampleProgram() {\r
+ Directory dir = new Directory(new File("bin/soba/testdata/"));\r
+ JavaProgram program = new JavaProgram(new IClassList[] {dir});\r
+ return program;\r
+ }\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() {\r
+ program = readExampleProgram();\r
+ }\r
+\r
+ @Test\r
+ public void testJavaProgram01() {\r
+ assertThat(program.getClasses(), is(notNullValue()));\r
+ assertThat(program.getClasses(), hasSize(22));\r
+\r
+ assertThat(program.getFiltered(), is(empty()));\r
+ assertThat(program.getDuplicated(), is(empty()));\r
+ assertThat(program.getErrors(), is(empty()));\r
+\r
+ assertThat(program.getClassHierarchy(), is(notNullValue()));\r
+\r
+ assertThat(program.getClassInfo(CLASS_C), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_D), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_E), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_F), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_G), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_H), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_I), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_J), is(notNullValue()));\r
+ assertThat(program.getClassInfo(CLASS_K), is(notNullValue()));\r
+ assertThat(program.getClassInfo("NotExistClass"), is(nullValue()));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.MethodInfo;\r
+import soba.util.UtilForAssertThat;\r
+import soba.util.graph.DirectedGraph;\r
+\r
+public class MethodInfoTest implements ExampleProgram {\r
+\r
+ private static JavaProgram program; \r
+ \r
+ @BeforeClass\r
+ public static void readExampleProgram() {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ }\r
+ \r
+ @Test\r
+ public void testMethodInfo01() {\r
+ ClassInfo c = program.getClassInfo(CLASS_D);\r
+ MethodInfo m = c.findMethod("example", "(IJDLjava/lang/String;)I");\r
+ \r
+ assertThat(m.getPackageName(), is("soba/testdata/inheritance1"));\r
+ assertThat(m.getClassName(), is(CLASS_D));\r
+ assertThat(m.getMethodName(), is("example"));\r
+ assertThat(m.getDescriptor(), is("(IJDLjava/lang/String;)I"));\r
+ assertThat(m.getGenericsSignature(), is(nullValue()));\r
+ assertThat(m.hasMethodBody(), is(true));\r
+ assertThat(m.isLibrary(), is(false));\r
+ assertThat(m.isPrivate(), is(false));\r
+ assertThat(m.isPublic(), is(true));\r
+ assertThat(m.isProtected(), is(false));\r
+ assertThat(m.isStatic(), is(false));\r
+ assertThat(m.isSynthetic(), is(false));\r
+ assertThat(m.isOverridable(), is(true));\r
+ assertThat(m.isPackagePrivate(), is(false));\r
+ assertThat(m.getInstructionCount(), is(5));\r
+ assertThat(m.getParamCount(), is(5));\r
+ assertThat(m.getReturnType(), is("int"));\r
+ assertThat(m.getReceiverObjectParamIndex(), is(0));\r
+ \r
+ assertThat(m.getParamName(0), is("this"));\r
+ assertThat(m.getParamName(1), is("i"));\r
+ assertThat(m.getParamName(2), is("l"));\r
+ assertThat(m.getParamName(3), is("d"));\r
+ assertThat(m.getParamName(4), is("s"));\r
+ assertThat(m.getParamName(5), is(nullValue()));\r
+ assertThat(m.getParamType(0), is(CLASS_D));\r
+ assertThat(m.getParamType(1), is("int"));\r
+ assertThat(m.getParamType(2), is("long"));\r
+ assertThat(m.getParamType(3), is("double"));\r
+ assertThat(m.getParamType(4), is("java/lang/String"));\r
+ assertThat(m.getVariableTableIndexOfParamAt(0), is(0));\r
+ assertThat(m.getVariableTableIndexOfParamAt(1), is(1));\r
+ assertThat(m.getVariableTableIndexOfParamAt(2), is(2));\r
+ assertThat(m.getVariableTableIndexOfParamAt(3), is(4));\r
+ assertThat(m.getVariableTableIndexOfParamAt(4), is(6));\r
+ assertThat(m.isParameterOrderingNumber(0), is(true));\r
+ assertThat(m.getParameterOrderingNumber(0), is(0));\r
+\r
+ assertThat(m.getMaxLine(), is(49));\r
+ assertThat(m.getMinLine(), is(49));\r
+ assertThat(UtilForAssertThat.asIntegerArray(m.getLineNumbers()), is(arrayContaining(49)));\r
+ assertThat(m.getLine(2), is(49));\r
+ assertThat(UtilForAssertThat.asIntegerArray(m.getInstructions(49)), is(arrayContainingInAnyOrder(1, 2, 3, 4)));\r
+ \r
+ assertThat(m.getCallSites(), is(empty()));\r
+ assertThat(m.getCallSite(2), is(nullValue()));\r
+ \r
+ assertThat(m.getFieldAccesses(), is(empty()));\r
+ \r
+ assertThat(UtilForAssertThat.asIntegerArray(m.getReturnInstructions()), is(arrayContainingInAnyOrder(3)));\r
+ \r
+ assertThat(m.getDataDependence(), is(notNullValue()));\r
+ assertThat(m.getControlDependence(), is(notNullValue()));\r
+ assertThat(m.getConservativeControlFlow(), is(notNullValue()));\r
+ DirectedGraph cfg = m.getControlFlow();\r
+ assertThat(cfg.getVertexCount(), is(5));\r
+ assertThat(cfg.getEdgeCount(), is(3));\r
+ Integer[] edges0 = UtilForAssertThat.asIntegerArray(cfg.getEdges(0));\r
+ Integer[] edges1 = UtilForAssertThat.asIntegerArray(cfg.getEdges(1));\r
+ Integer[] edges2 = UtilForAssertThat.asIntegerArray(cfg.getEdges(2));\r
+ Integer[] edges3 = UtilForAssertThat.asIntegerArray(cfg.getEdges(3));\r
+ Integer[] edges4 = UtilForAssertThat.asIntegerArray(cfg.getEdges(4));\r
+ assertThat(edges0, is(arrayContainingInAnyOrder(1)));\r
+ assertThat(edges1, is(arrayContainingInAnyOrder(2)));\r
+ assertThat(edges2, is(arrayContainingInAnyOrder(3)));\r
+ assertThat(edges3, is(emptyArray()));\r
+ assertThat(edges4, is(emptyArray()));\r
+ \r
+ assertThat(m.getMethodKey(), is(CLASS_D + "#example#(IJDLjava/lang/String;)I"));\r
+ assertThat(m.toLongString(), is(CLASS_D + ".example(" + CLASS_D + ":this, int:i, long:l, double:d, java/lang/String:s): int"));\r
+ assertThat(m.getInstructionString(0), is("0: (L00000)"));\r
+ assertThat(m.getInstructionString(1), is("1: (line=49)"));\r
+ assertThat(m.getInstructionString(2), is("2: ILOAD 1 (i)"));\r
+ assertThat(m.getInstructionString(3), is("3: IRETURN"));\r
+ assertThat(m.getInstructionString(4), is("4: (L00004)"));\r
+ }\r
+ \r
+ @Test\r
+ public void testMethodInfo02() {\r
+ ClassInfo c = program.getClassInfo(CLASS_C);\r
+ MethodInfo m = c.findMethod("main", "([Ljava/lang/String;)V");\r
+ \r
+ assertThat(m.getPackageName(), is("soba/testdata/inheritance1"));\r
+ assertThat(m.getClassName(), is(CLASS_C));\r
+ assertThat(m.getMethodName(), is("main"));\r
+ assertThat(m.getDescriptor(), is("([Ljava/lang/String;)V"));\r
+ assertThat(m.getGenericsSignature(), is(nullValue()));\r
+ assertThat(m.hasMethodBody(), is(true));\r
+ assertThat(m.isLibrary(), is(false));\r
+ assertThat(m.isPrivate(), is(false));\r
+ assertThat(m.isPublic(), is(true));\r
+ assertThat(m.isProtected(), is(false));\r
+ assertThat(m.isStatic(), is(true));\r
+ assertThat(m.isSynthetic(), is(false));\r
+ assertThat(m.isOverridable(), is(false));\r
+ assertThat(m.isPackagePrivate(), is(false));\r
+ assertThat(m.getInstructionCount(), is(27));\r
+ assertThat(m.getParamCount(), is(1));\r
+ assertThat(m.getReturnType(), is("void"));\r
+ \r
+ assertThat(m.getParamName(0), is("args"));\r
+ assertThat(m.getParamType(0), is("java/lang/String[]"));\r
+ assertThat(m.getVariableTableIndexOfParamAt(0), is(0));\r
+ assertThat(m.isParameterOrderingNumber(0), is(true));\r
+ assertThat(m.getParameterOrderingNumber(0), is(0));\r
+\r
+ assertThat(m.getMaxLine(), is(15));\r
+ assertThat(m.getMinLine(), is(10));\r
+ assertThat(UtilForAssertThat.asIntegerArray(m.getLineNumbers()), is(arrayContaining(10, 11, 12, 13, 14, 15)));\r
+ assertThat(m.getLine(9), is(11));\r
+ assertThat(UtilForAssertThat.asIntegerArray(m.getInstructions(11)), is(arrayContainingInAnyOrder(6, 7, 8, 9, 10, 11)));\r
+ \r
+ assertThat(m.getCallSites(), hasSize(4));\r
+ assertThat(m.getCallSite(9), is(notNullValue()));\r
+ \r
+ assertThat(m.getFieldAccesses(), hasSize(1));\r
+ \r
+ assertThat(UtilForAssertThat.asIntegerArray(m.getReturnInstructions()), is(arrayContainingInAnyOrder(25)));\r
+ \r
+ assertThat(m.getDataDependence(), is(notNullValue()));\r
+ assertThat(m.getControlDependence(), is(notNullValue()));\r
+ assertThat(m.getConservativeControlFlow(), is(notNullValue()));\r
+ assertThat(m.getControlFlow(), is(notNullValue()));\r
+ \r
+ assertThat(m.getMethodKey(), is(CLASS_C + "#main#([Ljava/lang/String;)V"));\r
+ assertThat(m.toLongString(), is(CLASS_C + ".main(java/lang/String[]:args): void"));\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import org.junit.Test;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+import soba.core.method.ControlDependence;\r
+import soba.util.IntPairList;\r
+import soba.util.UtilForAssertThat;\r
+import soba.util.graph.DirectedGraph;\r
+\r
+public class ControlDependenceTest {\r
+\r
+ public static DirectedGraph buildControlFlowGraph() { \r
+ IntPairList edges = new IntPairList();\r
+ edges.add(0, 1); // cycle 0->1->2\r
+ edges.add(1, 2);\r
+ edges.add(2, 0);\r
+ edges.add(1, 3);\r
+ \r
+ edges.add(3, 4);\r
+ edges.add(3, 5);\r
+ \r
+ edges.add(5, 6); // another cycle: 5->6->8->5\r
+ edges.add(6, 8);\r
+ edges.add(8, 5);\r
+\r
+ edges.add(6, 7); \r
+ edges.add(7, 9);\r
+ edges.add(7, 10);\r
+ edges.add(9, 11);\r
+ edges.add(10, 11);\r
+ edges.add(11, 12); // 13 is not connected to any other vertices\r
+ \r
+ return new DirectedGraph(14, edges);\r
+ }\r
+\r
+\r
+ @Test\r
+ public void testControlDependence() {\r
+ DirectedGraph g = buildControlFlowGraph();\r
+ DirectedGraph cd = ControlDependence.getDependence(14, g);\r
+ \r
+ // Not a branch vertex\r
+ Integer[] edgesFrom0 = UtilForAssertThat.asIntegerArray(cd.getEdges(0));\r
+ Integer[] edgesFrom2 = UtilForAssertThat.asIntegerArray(cd.getEdges(2));\r
+ Integer[] edgesFrom4 = UtilForAssertThat.asIntegerArray(cd.getEdges(4));\r
+ Integer[] edgesFrom5 = UtilForAssertThat.asIntegerArray(cd.getEdges(5));\r
+ Integer[] edgesFrom8 = UtilForAssertThat.asIntegerArray(cd.getEdges(8));\r
+ Integer[] edgesFrom9 = UtilForAssertThat.asIntegerArray(cd.getEdges(9));\r
+ Integer[] edgesFrom10 = UtilForAssertThat.asIntegerArray(cd.getEdges(10));\r
+ Integer[] edgesFrom11 = UtilForAssertThat.asIntegerArray(cd.getEdges(11));\r
+ Integer[] edgesFrom12 = UtilForAssertThat.asIntegerArray(cd.getEdges(12));\r
+ Integer[] edgesFrom13 = UtilForAssertThat.asIntegerArray(cd.getEdges(13));\r
+ assertThat(edgesFrom0, is(emptyArray()));\r
+ assertThat(edgesFrom2, is(emptyArray()));\r
+ assertThat(edgesFrom4, is(emptyArray()));\r
+ assertThat(edgesFrom5, is(emptyArray()));\r
+ assertThat(edgesFrom8, is(emptyArray()));\r
+ assertThat(edgesFrom9, is(emptyArray()));\r
+ assertThat(edgesFrom10, is(emptyArray()));\r
+ assertThat(edgesFrom11, is(emptyArray()));\r
+ assertThat(edgesFrom12, is(emptyArray()));\r
+ assertThat(edgesFrom13, is(emptyArray()));\r
+ \r
+ // Conditional branches\r
+ Integer[] edgesFrom1 = UtilForAssertThat.asIntegerArray(cd.getEdges(1));\r
+ Integer[] edgesFrom3 = UtilForAssertThat.asIntegerArray(cd.getEdges(3));\r
+ Integer[] edgesFrom6 = UtilForAssertThat.asIntegerArray(cd.getEdges(6));\r
+ Integer[] edgesFrom7 = UtilForAssertThat.asIntegerArray(cd.getEdges(7));\r
+ assertThat(edgesFrom1, is(arrayContainingInAnyOrder(0, 2)));\r
+ assertThat(edgesFrom3, is(arrayContainingInAnyOrder(4, 6, 7, 11, 12)));\r
+ assertThat(edgesFrom6, is(arrayContainingInAnyOrder(5, 8)));\r
+ assertThat(edgesFrom7, is(arrayContainingInAnyOrder(9, 10)));\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import java.util.List;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+import soba.core.JavaProgram;\r
+import soba.core.JavaProgramTest;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.DataFlowEdge;\r
+import soba.util.UtilForAssertThat;\r
+import soba.util.graph.DirectedGraph;\r
+\r
+public class DataDependenceTest {\r
+\r
+ private static JavaProgram program;\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ }\r
+ \r
+ @Test\r
+ public void testDataDependence01() {\r
+ MethodInfo m = program.getClassInfo("soba/testdata/DefUseTestData").findMethod("overwriteParam", "(II)V");\r
+ DataDependence dd = m.getDataDependence();\r
+ assertThat(dd, is(notNullValue()));\r
+\r
+ List<DataFlowEdge> edges = dd.getEdges();\r
+ assertThat(edges, hasSize(7));\r
+ assertThat(containsEdge(edges, -1, 2), is(true));\r
+ assertThat(containsEdge(edges, 2, 3), is(true));\r
+ assertThat(containsEdge(edges, 4, 5), is(true));\r
+ assertThat(containsEdge(edges, -1, 10), is(true));\r
+ assertThat(containsEdge(edges, 5, 10), is(true));\r
+ assertThat(containsEdge(edges, 9, 11), is(true));\r
+ assertThat(containsEdge(edges, 10, 11), is(true));\r
+ \r
+ List<DataFlowEdge> edgesSourceOrder = dd.getEdgesInSourceOrder();\r
+ assertThat(edgesSourceOrder, hasSize(7));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, -1, 2, 0), is(true));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, -1, 10, 1), is(true));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, 2, 3, 2), is(true));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, 4, 5, 3), is(true));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, 5, 10, 4), is(true));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, 9, 11, 5), is(true));\r
+ assertThat(containsEdgeAt(edgesSourceOrder, 10, 11, 6), is(true));\r
+ \r
+ DirectedGraph dependence = dd.getDependenceGraph();\r
+ assertThat(dependence.getVertexCount(), is(m.getInstructionCount()));\r
+ assertThat(dependence.getEdgeCount(), is(5));\r
+ Integer[] edgesFrom0 = UtilForAssertThat.asIntegerArray(dependence.getEdges(0));\r
+ Integer[] edgesFrom1 = UtilForAssertThat.asIntegerArray(dependence.getEdges(1));\r
+ Integer[] edgesFrom2 = UtilForAssertThat.asIntegerArray(dependence.getEdges(2));\r
+ Integer[] edgesFrom3 = UtilForAssertThat.asIntegerArray(dependence.getEdges(3));\r
+ Integer[] edgesFrom4 = UtilForAssertThat.asIntegerArray(dependence.getEdges(4));\r
+ Integer[] edgesFrom5 = UtilForAssertThat.asIntegerArray(dependence.getEdges(5));\r
+ Integer[] edgesFrom6 = UtilForAssertThat.asIntegerArray(dependence.getEdges(6));\r
+ Integer[] edgesFrom7 = UtilForAssertThat.asIntegerArray(dependence.getEdges(7));\r
+ Integer[] edgesFrom8 = UtilForAssertThat.asIntegerArray(dependence.getEdges(8));\r
+ Integer[] edgesFrom9 = UtilForAssertThat.asIntegerArray(dependence.getEdges(9));\r
+ Integer[] edgesFrom10 = UtilForAssertThat.asIntegerArray(dependence.getEdges(10));\r
+ Integer[] edgesFrom11 = UtilForAssertThat.asIntegerArray(dependence.getEdges(11));\r
+ Integer[] edgesFrom12 = UtilForAssertThat.asIntegerArray(dependence.getEdges(12));\r
+ Integer[] edgesFrom13 = UtilForAssertThat.asIntegerArray(dependence.getEdges(13));\r
+ Integer[] edgesFrom14 = UtilForAssertThat.asIntegerArray(dependence.getEdges(14));\r
+ Integer[] edgesFrom15 = UtilForAssertThat.asIntegerArray(dependence.getEdges(15));\r
+ assertThat(edgesFrom2, is(arrayContainingInAnyOrder(3)));\r
+ assertThat(edgesFrom4, is(arrayContainingInAnyOrder(5)));\r
+ assertThat(edgesFrom5, is(arrayContainingInAnyOrder(10)));\r
+ assertThat(edgesFrom9, is(arrayContainingInAnyOrder(11)));\r
+ assertThat(edgesFrom10, is(arrayContainingInAnyOrder(11)));\r
+ assertThat(edgesFrom0, is(emptyArray()));\r
+ assertThat(edgesFrom1, is(emptyArray()));\r
+ assertThat(edgesFrom3, is(emptyArray()));\r
+ assertThat(edgesFrom6, is(emptyArray()));\r
+ assertThat(edgesFrom7, is(emptyArray()));\r
+ assertThat(edgesFrom8, is(emptyArray()));\r
+ assertThat(edgesFrom11, is(emptyArray()));\r
+ assertThat(edgesFrom12, is(emptyArray()));\r
+ assertThat(edgesFrom13, is(emptyArray()));\r
+ assertThat(edgesFrom14, is(emptyArray()));\r
+ assertThat(edgesFrom15, is(emptyArray()));\r
+\r
+ List<DataFlowEdge> incomingEdges2 = dd.getIncomingEdges(2);\r
+ assertThat(incomingEdges2, hasSize(1));\r
+ assertThat(containsEdge(incomingEdges2, -1, 2), is(true));\r
+ assertThat(dd.getVariableName(incomingEdges2.get(0)), is("x"));\r
+ assertThat(dd.getVariableDescriptor(incomingEdges2.get(0)), is("I"));\r
+ List<DataFlowEdge> incomingEdges11 = dd.getIncomingEdges(11);\r
+ assertThat(incomingEdges11, hasSize(2));\r
+ assertThat(containsEdge(incomingEdges11, 9, 11), is(true));\r
+ assertThat(containsEdge(incomingEdges11, 10, 11), is(true));\r
+ \r
+ DataFlowEdge incomingEdge11At0 = dd.getIncomingEdge(11, 0);\r
+ assertThat(incomingEdge11At0.getSourceInstruction(), is(9));\r
+ DataFlowEdge incommingEdge11At1 = dd.getIncomingEdge(11, 1);\r
+ assertThat(incommingEdge11At1.getSourceInstruction(), is(10));\r
+ \r
+ List<DataFlowEdge> incomingEdges11At0 = dd.getIncomingEdges(11, 0);\r
+ assertThat(incomingEdges11At0, hasSize(1));\r
+ assertThat(containsEdge(incomingEdges11At0, 9, 11), is(true));\r
+ List<DataFlowEdge> incomingEdges11At1 = dd.getIncomingEdges(11, 1);\r
+ assertThat(incomingEdges11At1, hasSize(1));\r
+ assertThat(containsEdge(incomingEdges11At1, 10, 11), is(true));\r
+ \r
+ int[][] definition10 = dd.getDataDefinition(10);\r
+ assertThat(definition10.length, is(1));\r
+ Integer[] local = UtilForAssertThat.asIntegerArray(definition10[0]);\r
+ assertThat(local, is(arrayContainingInAnyOrder(-1, 5)));\r
+ int[][] definition11 = dd.getDataDefinition(11);\r
+ assertThat(definition11.length, is(2));\r
+ Integer[] operand0 = UtilForAssertThat.asIntegerArray(definition11[0]);\r
+ Integer[] operand1 = UtilForAssertThat.asIntegerArray(definition11[1]);\r
+ assertThat(operand0, is(arrayContainingInAnyOrder(9)));\r
+ assertThat(operand1, is(arrayContainingInAnyOrder(10)));\r
+ \r
+ assertThat(dd.getOperandCount(2), is(0));\r
+ assertThat(dd.getOperandCount(3), is(1));\r
+ assertThat(dd.getOperandCount(11), is(2));\r
+ }\r
+ \r
+ @Test\r
+ public void testDataDependence02() {\r
+ MethodInfo m = program.getClassInfo("soba/testdata/DefUseTestData").findMethod("localDataDependence", "()V");\r
+ DataDependence dd = m.getDataDependence();\r
+ assertThat(dd, is(notNullValue()));\r
+\r
+ List<DataFlowEdge> edges = dd.getEdges();\r
+ assertThat(edges, hasSize(17));\r
+ assertThat(containsEdge(edges, 2, 3), is(true));\r
+ assertThat(containsEdge(edges, 3, 6), is(true));\r
+ assertThat(containsEdge(edges, 6, 7), is(true));\r
+ assertThat(containsEdge(edges, 10, 11), is(true));\r
+ assertThat(containsEdge(edges, 18, 19), is(true));\r
+ assertThat(containsEdge(edges, 19, 23), is(true));\r
+ assertThat(containsEdge(edges, 22, 24), is(true));\r
+ assertThat(containsEdge(edges, 23, 24), is(true));\r
+ assertThat(containsEdge(edges, 27, 28), is(true));\r
+ assertThat(containsEdge(edges, 11, 33), is(true));\r
+ assertThat(containsEdge(edges, 28, 33), is(true));\r
+ assertThat(containsEdge(edges, 32, 34), is(true));\r
+ assertThat(containsEdge(edges, 11, 38), is(true));\r
+ assertThat(containsEdge(edges, 28, 38), is(true));\r
+ assertThat(containsEdge(edges, 37, 39), is(true));\r
+ assertThat(containsEdge(edges, 38, 39), is(true));\r
+ }\r
+ \r
+ @Test\r
+ public void testDataDependence03() {\r
+ MethodInfo m = program.getClassInfo("soba/testdata/DefUseTestData").findMethod("tryFinallyDependence", "()I");\r
+ DataDependence dd = m.getDataDependence();\r
+ assertThat(dd, is(notNullValue()));\r
+ }\r
+ \r
+ private boolean containsEdge(List<DataFlowEdge> edges, int from, int to) {\r
+ return edges.stream().anyMatch(\r
+ e -> e.getSourceInstruction() == from && \r
+ e.getDestinationInstruction() == to);\r
+ }\r
+ \r
+ private boolean containsEdgeAt(List<DataFlowEdge> edges, int from, int to, int pos) {\r
+ return edges.size() > pos && \r
+ edges.get(pos).getSourceInstruction() == from &&\r
+ edges.get(pos).getDestinationInstruction() == to;\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.method;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.After;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.InsnList;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.JavaProgramTest;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.LocalVariables;\r
+\r
+public class LocalVariablesTest {\r
+\r
+ private static JavaProgram program;\r
+ private static ClassInfo c;\r
+ private static LocalVariables variables;\r
+ private static InsnList instructions;\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() throws Exception {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ c = program.getClassInfo("soba/testdata/DefUseTestData");\r
+ MethodInfo m = c.findMethod("localDataDependence", "()V");\r
+ instructions = m.getMethodNode().instructions;\r
+ variables = new LocalVariables(m.getDataDependence(), m.getMethodNode());\r
+ }\r
+\r
+ @AfterClass\r
+ public static void tearDownAfterClass() throws Exception {\r
+ }\r
+\r
+ @Before\r
+ public void setUp() throws Exception {\r
+ }\r
+\r
+ @After\r
+ public void tearDown() throws Exception {\r
+ }\r
+\r
+ @Test\r
+ public void testGetVariableEntryCount() {\r
+ assertThat(variables.getVariableEntryCount(), is(3));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVariableName() {\r
+ assertThat(variables.getVariableName(0), is("b"));\r
+ assertThat(variables.getVariableName(1), is("x"));\r
+ assertThat(variables.getVariableName(2), is("x"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVariableType() {\r
+ assertThat(variables.getVariableType(0), is("boolean"));\r
+ assertThat(variables.getVariableType(1), is("int"));\r
+ assertThat(variables.getVariableType(2), is("int"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVariableIndex() {\r
+ assertThat(variables.getVariableIndex(0), is(2)); // variable b\r
+ assertThat(variables.getVariableIndex(1), is(1)); // variable x\r
+ assertThat(variables.getVariableIndex(2), is(1)); // variable x\r
+ }\r
+\r
+ @Test\r
+ public void testIsObjectVariable() {\r
+ assertThat(variables.isObjectVariable(0), is(false));\r
+ assertThat(variables.isObjectVariable(1), is(false));\r
+ assertThat(variables.isObjectVariable(2), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testIsArrayVariable() {\r
+ assertThat(variables.isArrayVariable(0), is(false));\r
+ assertThat(variables.isArrayVariable(1), is(false));\r
+ assertThat(variables.isArrayVariable(2), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testHasNoDataDependence() {\r
+ assertThat(variables.hasNoDataDependence(0), is(false));\r
+ assertThat(variables.hasNoDataDependence(1), is(false));\r
+ assertThat(variables.hasNoDataDependence(2), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testIsParameter() {\r
+ assertThat(variables.isParameter(0), is(false));\r
+ assertThat(variables.isParameter(1), is(false));\r
+ assertThat(variables.isParameter(2), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testFindEntryForInstruction() {\r
+ int storeCount = 0;\r
+ int loadCount = 0;\r
+ for (int i = 0; i < instructions.size(); i++) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.ISTORE) {\r
+ if (storeCount == 0) {\r
+ assertThat(variables.findEntryForInstruction(i), is(0));\r
+ } else if (storeCount == 1 || storeCount == 3) {\r
+ assertThat(variables.findEntryForInstruction(i), is(2));\r
+ } else {\r
+ assertThat(variables.findEntryForInstruction(i), is(1));\r
+ }\r
+ storeCount++;\r
+ } else if (instructions.get(i).getOpcode() == Opcodes.ILOAD) {\r
+ if (loadCount == 0) {\r
+ assertThat(variables.findEntryForInstruction(i), is(0));\r
+ } else if (loadCount == 1) {\r
+ assertThat(variables.findEntryForInstruction(i), is(1));\r
+ } else {\r
+ assertThat(variables.findEntryForInstruction(i), is(2));\r
+ }\r
+ loadCount++;\r
+ } else {\r
+ assertThat(variables.findEntryForInstruction(i), is(-1));\r
+ }\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.method.asm;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+//import org.junit.Assert;\r
+import org.junit.Test;\r
+\r
+import soba.core.method.asm.FastSourceValue;\r
+import soba.util.UtilForAssertThat;\r
+\r
+public class FastSourceValueTest {\r
+\r
+ @Test\r
+ public void testConstructorWithoutInstruction() {\r
+ FastSourceValue value1 = new FastSourceValue(1);\r
+ assertThat(value1.getSize(), is(1));\r
+ assertThat(value1.getInstructions().length, is(0));\r
+\r
+ FastSourceValue value2 = new FastSourceValue(2);\r
+ assertThat(value2.getSize(), is(2));\r
+ assertThat(value2.getInstructions().length, is(0));\r
+ }\r
+ \r
+ @Test\r
+ public void testConstructorWithSingleInstruction() {\r
+ FastSourceValue value1 = new FastSourceValue(1, 2);\r
+ assertThat(value1.getSize(), is(1));\r
+ assertThat(value1.getInstructions().length, is(1));\r
+ assertThat(value1.getInstructions()[0], is(2));\r
+ }\r
+ \r
+ @Test\r
+ public void testConstructorWithArray() {\r
+ FastSourceValue value1 = new FastSourceValue(2, new int[] { 1, 4, 9 });\r
+ assertThat(value1.getSize(), is(2));\r
+ assertThat(value1.getInstructions().length, is(3));\r
+ assertThat(value1.getInstructions()[0], is(1));\r
+ assertThat(value1.getInstructions()[1], is(4));\r
+ assertThat(value1.getInstructions()[2], is(9));\r
+ }\r
+ \r
+ @Test\r
+ public void testMerge() {\r
+ FastSourceValue value123 = new FastSourceValue(1, new int[] {1, 2, 3});\r
+ FastSourceValue value456 = new FastSourceValue(1, new int[] {4, 5, 6});\r
+ FastSourceValue value135 = new FastSourceValue(1, new int[] {1, 3, 5});\r
+ FastSourceValue value246 = new FastSourceValue(1, new int[] {2, 4, 6});\r
+ FastSourceValue valueNULL = new FastSourceValue(1, new int[0]);\r
+ FastSourceValue value123Size2 = new FastSourceValue(2, new int[] {1, 2, 3});\r
+ \r
+ FastSourceValue value123456concat = new FastSourceValue(value123, value456);\r
+ Integer[] instructions123456 = UtilForAssertThat.asIntegerArray(value123456concat.getInstructions());\r
+ assertThat(instructions123456, is(arrayContainingInAnyOrder(1, 2, 3, 4, 5, 6)));\r
+ \r
+ FastSourceValue value123456anotherConcat = new FastSourceValue(value456, value123);\r
+ Integer[] instructions123456another = UtilForAssertThat.asIntegerArray(value123456anotherConcat.getInstructions());\r
+ assertThat(instructions123456another, is(arrayContainingInAnyOrder(1, 2, 3, 4, 5, 6)));\r
+\r
+ FastSourceValue value123456mix = new FastSourceValue(value135, value246);\r
+ Integer[] instructions123456mix = UtilForAssertThat.asIntegerArray(value123456mix.getInstructions());\r
+ assertThat(instructions123456mix, is(arrayContainingInAnyOrder(1, 2, 3, 4, 5, 6)));\r
+\r
+ FastSourceValue value123456anotherMix = new FastSourceValue(value246, value135);\r
+ Integer[] instructions123456anotherMix = UtilForAssertThat.asIntegerArray(value123456anotherMix.getInstructions());\r
+ assertThat(instructions123456anotherMix, is(arrayContainingInAnyOrder(1, 2, 3, 4, 5, 6)));\r
+\r
+ FastSourceValue value123unchanged = new FastSourceValue(value123, valueNULL);\r
+ Integer[] instructions123 = UtilForAssertThat.asIntegerArray(value123unchanged.getInstructions());\r
+ assertThat(instructions123, is(arrayContainingInAnyOrder(1, 2, 3)));\r
+\r
+ FastSourceValue value246unchanged = new FastSourceValue(valueNULL, value246);\r
+ Integer[] instructions246 = UtilForAssertThat.asIntegerArray(value246unchanged.getInstructions());\r
+ assertThat(instructions246, is(arrayContainingInAnyOrder(2, 4, 6)));\r
+ \r
+ FastSourceValue valueConcatNull = new FastSourceValue(valueNULL, valueNULL);\r
+ Integer[] instructionsNull = UtilForAssertThat.asIntegerArray(valueConcatNull.getInstructions());\r
+ assertThat(instructionsNull, is(emptyArray()));\r
+ \r
+ FastSourceValue value1235 = new FastSourceValue(value123, value135);\r
+ Integer[] instructions1235 = UtilForAssertThat.asIntegerArray(value1235.getInstructions());\r
+ assertThat(instructions1235, is(arrayContainingInAnyOrder(1, 2, 3, 5)));\r
+\r
+ FastSourceValue value1235another = new FastSourceValue(value135, value123);\r
+ Integer[] instructions1235another = UtilForAssertThat.asIntegerArray(value1235another.getInstructions());\r
+ assertThat(instructions1235another, is(arrayContainingInAnyOrder(1, 2, 3 ,5)));\r
+\r
+ FastSourceValue value2456 = new FastSourceValue(value246, value456);\r
+ Integer[] instructions2456 = UtilForAssertThat.asIntegerArray(value2456.getInstructions());\r
+ assertThat(instructions2456, is(arrayContainingInAnyOrder(2, 4, 5, 6)));\r
+\r
+ FastSourceValue value2456another = new FastSourceValue(value456, value246);\r
+ Integer[] instructions2456another = UtilForAssertThat.asIntegerArray(value2456another.getInstructions());\r
+ assertThat(instructions2456another, is(arrayContainingInAnyOrder(2, 4, 5, 6)));\r
+ \r
+ FastSourceValue value123differentSize = new FastSourceValue(value123, value123Size2);\r
+ Integer[] instructions123different = UtilForAssertThat.asIntegerArray(value123differentSize.getInstructions());\r
+ assertThat(instructions123different, is(arrayContainingInAnyOrder(1, 2, 3)));\r
+ assertThat(value123differentSize.getSize(), is(1));\r
+\r
+ FastSourceValue value123differentSizeAnother = new FastSourceValue(value123Size2, value123);\r
+ Integer[] instructions123differentAnother = UtilForAssertThat.asIntegerArray(value123differentSizeAnother.getInstructions());\r
+ assertThat(instructions123differentAnother, is(arrayContainingInAnyOrder(1, 2, 3)));\r
+ assertThat(value123differentSizeAnother.getSize(), is(1));\r
+ }\r
+\r
+ @Test\r
+ public void testContainsAll() {\r
+ FastSourceValue value123 = new FastSourceValue(1, new int[] {1, 2, 3});\r
+ FastSourceValue value246 = new FastSourceValue(1, new int[] {2, 4, 6});\r
+ FastSourceValue valueNULL = new FastSourceValue(1, new int[0]);\r
+ FastSourceValue value0 = new FastSourceValue(1, 0);\r
+ FastSourceValue value1 = new FastSourceValue(1, 1);\r
+ FastSourceValue value2 = new FastSourceValue(1, 2);\r
+ FastSourceValue value3 = new FastSourceValue(1, 3);\r
+ FastSourceValue value4 = new FastSourceValue(1, 4);\r
+ FastSourceValue value12 = new FastSourceValue(1, new int[] {1, 2});\r
+ FastSourceValue value23 = new FastSourceValue(1, new int[] {2, 3});\r
+ FastSourceValue value45 = new FastSourceValue(1, new int[] {4, 5});\r
+ FastSourceValue value46 = new FastSourceValue(1, new int[] {4, 6});\r
+ FastSourceValue value1234 = new FastSourceValue(1, new int[] {1, 2, 3, 4});\r
+ FastSourceValue value24 = new FastSourceValue(1, new int[] {2, 4});\r
+ \r
+ assertThat(value123.containsAll(valueNULL), is(true));\r
+ assertThat(value123.containsAll(value123), is(true));\r
+ assertThat(value123.containsAll(value1), is(true));\r
+ assertThat(value123.containsAll(value2), is(true));\r
+ assertThat(value123.containsAll(value3), is(true));\r
+ assertThat(value123.containsAll(value12), is(true));\r
+ assertThat(value123.containsAll(value23), is(true));\r
+ assertThat(value123.containsAll(value0), is(false));\r
+ assertThat(value123.containsAll(value4), is(false));\r
+ assertThat(value123.containsAll(value246), is(false));\r
+ assertThat(value123.containsAll(value45), is(false));\r
+ assertThat(value123.containsAll(value46), is(false));\r
+ assertThat(value123.containsAll(value1234), is(false));\r
+ assertThat(value123.containsAll(value24), is(false));\r
+ \r
+ assertThat(valueNULL.containsAll(valueNULL), is(true));\r
+ assertThat(valueNULL.containsAll(value1), is(false));\r
+ assertThat(valueNULL.containsAll(value123), is(false));\r
+ \r
+ assertThat(value246.containsAll(value46), is(true));\r
+ assertThat(value246.containsAll(value4), is(true));\r
+ assertThat(value246.containsAll(value24), is(true));\r
+ assertThat(value246.containsAll(value123), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testEquals() {\r
+ int[] v123 = new int[] {1, 2, 3};\r
+ FastSourceValue value123 = new FastSourceValue(1, v123);\r
+ FastSourceValue value123another = new FastSourceValue(1, v123);\r
+ FastSourceValue value123differentSize = new FastSourceValue(2, v123);\r
+ FastSourceValue value123differentArray = new FastSourceValue(1, new int[] {1, 2, 3});\r
+ FastSourceValue value123differentSizeAndArray = new FastSourceValue(2, new int[] {1, 2, 3});\r
+ \r
+ assertThat(value123.equals(value123another), is(true));\r
+ assertThat(value123.equals(value123differentSize), is(false));\r
+ assertThat(value123.equals(value123differentArray), is(true));\r
+ assertThat(value123.equals(value123differentSizeAndArray), is(false));\r
+ }\r
+}\r
--- /dev/null
+package soba.core.signature;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Test;\r
+\r
+public class TypeResolverTest {\r
+\r
+ @Test\r
+ public void testGetTypeName() {\r
+ assertThat(TypeResolver.getTypeName("Z"), is("boolean"));\r
+ assertThat(TypeResolver.getTypeName("B"), is("byte"));\r
+ assertThat(TypeResolver.getTypeName("C"), is("char"));\r
+ assertThat(TypeResolver.getTypeName("S"), is("short"));\r
+ assertThat(TypeResolver.getTypeName("I"), is("int"));\r
+ assertThat(TypeResolver.getTypeName("J"), is("long"));\r
+ assertThat(TypeResolver.getTypeName("F"), is("float"));\r
+ assertThat(TypeResolver.getTypeName("D"), is("double"));\r
+ assertThat(TypeResolver.getTypeName("V"), is("void"));\r
+ assertThat(TypeResolver.getTypeName("Ljava/lang/String;"), is("java/lang/String"));\r
+ assertThat(TypeResolver.getTypeName("[I"), is("int[]"));\r
+ assertThat(TypeResolver.getTypeName("[[[I"), is("int[][][]"));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.JavaProgramTest;\r
+import soba.core.MethodInfo;\r
+import soba.core.method.CallSite;\r
+import soba.core.vta.CallSiteVertices;\r
+\r
+public class CallSiteVerticesTest {\r
+\r
+ private static JavaProgram program;\r
+ private static MethodInfo m;\r
+ private static CallSiteVertices v;\r
+ private static final int startID = 1;\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() throws Exception {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ ClassInfo c = program.getClassInfo("soba/testdata/ObjectTransferCode");\r
+ m = c.findMethod("newObject", "(I)[[I");\r
+ CallSite callSite = null;\r
+ for (int i = 0; i < m.getInstructionCount(); i++) {\r
+ CallSite cs = m.getCallSite(i);\r
+ if (cs != null && cs.getMethodName().equals("m2")) {\r
+ callSite = cs;\r
+ break;\r
+ }\r
+ }\r
+ v = new CallSiteVertices(callSite, startID);\r
+ }\r
+\r
+// @Test\r
+// public void testGetCallSite() {\r
+// fail("Not yet implemented");\r
+// }\r
+\r
+ @Test\r
+ public void testIsObjectParam() {\r
+ assertThat(v.isObjectParam(0), is(true));\r
+ assertThat(v.isObjectParam(1), is(true));\r
+ }\r
+\r
+ @Test\r
+ public void testGetParamVertexId() {\r
+ assertThat(v.getParamVertexId(0), is(startID));\r
+ assertThat(v.getParamVertexId(1), is(startID + 1));\r
+ }\r
+\r
+ @Test\r
+ public void testGetParamCount() {\r
+ assertThat(v.getParamCount(), is(2));\r
+ }\r
+\r
+ @Test\r
+ public void testHasReturnValue() {\r
+ assertThat(v.hasReturnValue(), is(true));\r
+ }\r
+\r
+ @Test\r
+ public void testGetReturnValueVertex() {\r
+ assertThat(v.getReturnValueVertex(), is(startID + 2));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVertexCount() {\r
+ assertThat(v.getVertexCount(), is(3));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVertex() {\r
+ assertThat(v.getVertex(0), is(startID));\r
+ assertThat(v.getVertex(1), is(startID + 1));\r
+ assertThat(v.getVertex(2), is(startID + 2));\r
+ }\r
+\r
+ @Test\r
+ public void testGetTypeName() {\r
+ assertThat(v.getTypeName(0), is("soba/testdata/ObjectTransferCode"));\r
+ assertThat(v.getTypeName(1), is("soba/testdata/ObjectTransferCode1"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetReturnValueTypeName() {\r
+ assertThat(v.getReturnValueTypeName(), is("soba/testdata/ObjectTransferCode"));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.InsnList;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.JavaProgramTest;\r
+import soba.core.MethodInfo;\r
+import soba.core.vta.MethodVertices;\r
+import soba.core.vta.VTAResolver;\r
+\r
+public class MethodVerticesTest {\r
+\r
+ private static JavaProgram program;\r
+ private static MethodInfo m;\r
+ private static MethodVertices v;\r
+ private static final int startID = 1;\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() throws Exception {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ ClassInfo c = program.getClassInfo("soba/testdata/ObjectTransferCode");\r
+ m = c.findMethod("newObject", "(I)[[I");\r
+ v = new MethodVertices(m, m.getDataDependence().getLocalVariables(), startID);\r
+ }\r
+\r
+ @Test\r
+ public void testGetLocalVertex() {\r
+ int storeCount = 0;\r
+ int loadCount = 0;\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ for (int i = 0; i < instructions.size(); i++) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.ASTORE) {\r
+ if (storeCount <= 2) {\r
+ assertThat(v.getLocalVertex(i), is(startID + storeCount + 1));\r
+ } else if (storeCount == 3) {\r
+ assertThat(v.getLocalVertex(i), is(startID + 6));\r
+ } else {\r
+ assertThat(v.getLocalVertex(i), is(startID + storeCount));\r
+ }\r
+ storeCount++;\r
+ } else if (instructions.get(i).getOpcode() == Opcodes.ALOAD) {\r
+ if (loadCount == 0) {\r
+ assertThat(v.getLocalVertex(i), is(startID + 1));\r
+ } else if (loadCount == 1 || loadCount == 3) {\r
+ assertThat(v.getLocalVertex(i), is(startID));\r
+ } else if (loadCount == 2) {\r
+ assertThat(v.getLocalVertex(i), is(startID + 2));\r
+ } else {\r
+ assertThat(v.getLocalVertex(i), is(startID + loadCount - 1));\r
+ }\r
+ loadCount++;\r
+ } else {\r
+ assertThat(v.getLocalVertex(i), is(VTAResolver.VERTEX_ERROR));\r
+ }\r
+ }\r
+ }\r
+\r
+ @Test\r
+ public void testGetReturnVertex() {\r
+ assertThat(v.getReturnVertex(), is(startID + 7));\r
+ }\r
+\r
+ @Test\r
+ public void testGetFormalVertex() {\r
+ assertThat(v.getFormalVertex(0), is(startID));\r
+ }\r
+\r
+ @Test\r
+ public void testHasFormalVertex() {\r
+ assertThat(v.hasFormalVertex(0), is(true));\r
+ assertThat(v.hasFormalVertex(1), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVertexCount() {\r
+ assertThat(v.getVertexCount(), is(8));\r
+ }\r
+\r
+ @Test\r
+ public void testGetTypeName() {\r
+ assertThat(v.getTypeName(0), is("soba/testdata/ObjectTransferCode"));\r
+ assertThat(v.getTypeName(1), is("soba/testdata/ObjectTransferCode"));\r
+ assertThat(v.getTypeName(2), is("soba/testdata/ObjectTransferCode"));\r
+ assertThat(v.getTypeName(3), is("soba/testdata/ObjectTransferCode"));\r
+ assertThat(v.getTypeName(4), is("soba/testdata/ObjectTransferCode[]"));\r
+ assertThat(v.getTypeName(5), is("int[][]"));\r
+ assertThat(v.getTypeName(6), is("java/lang/Object"));\r
+ assertThat(v.getTypeName(7), is("int[][]"));\r
+}\r
+\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.InsnList;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.JavaProgramTest;\r
+import soba.core.MethodInfo;\r
+import soba.core.vta.NewVertices;\r
+import soba.core.vta.VTAResolver;\r
+\r
+public class NewVerticesTest {\r
+\r
+ private static JavaProgram program;\r
+ private static MethodInfo m;\r
+ private static NewVertices v;\r
+ private static final int startID = 1;\r
+ \r
+ @BeforeClass\r
+ public static void setUpBeforeClass() throws Exception {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ ClassInfo c = program.getClassInfo("soba/testdata/ObjectTransferCode");\r
+ m = c.findMethod("newObject", "(I)[[I");\r
+ v = new NewVertices(m.getMethodNode().instructions, startID);\r
+ }\r
+\r
+ @Test\r
+ public void testGetNewInstructionVertex() {\r
+ int count = startID;\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ for (int i = 0; i < instructions.size(); i++) {\r
+ switch (instructions.get(i).getOpcode()) {\r
+ case Opcodes.NEW:\r
+ case Opcodes.ANEWARRAY:\r
+ case Opcodes.MULTIANEWARRAY:\r
+ assertThat(v.getNewInstructionVertex(i), is(count));\r
+ count++;\r
+ break;\r
+ default:\r
+ assertThat(v.getNewInstructionVertex(i), is(VTAResolver.VERTEX_ERROR));\r
+ }\r
+ }\r
+ }\r
+\r
+ @Test\r
+ public void testGetVertex() {\r
+ assertThat(v.getVertex(0), is(startID));\r
+ assertThat(v.getVertex(1), is(startID + 1));\r
+ assertThat(v.getVertex(2), is(startID + 2));\r
+ }\r
+\r
+ @Test\r
+ public void testGetTypeName() {\r
+ assertThat(v.getTypeName(0), is("soba/testdata/ObjectTransferCode"));\r
+ assertThat(v.getTypeName(1), is("soba/testdata/ObjectTransferCode[]"));\r
+ assertThat(v.getTypeName(2), is("int[][]"));\r
+ }\r
+\r
+ @Test\r
+ public void testGetVertexCount() {\r
+ assertThat(v.getVertexCount(), is(3));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.core.vta;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import java.util.Arrays;\r
+import java.util.stream.Collectors;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.objectweb.asm.Opcodes;\r
+import org.objectweb.asm.tree.InsnList;\r
+import org.objectweb.asm.tree.MethodInsnNode;\r
+\r
+import soba.core.ClassInfo;\r
+import soba.core.ExampleProgram;\r
+import soba.core.FieldInfo;\r
+import soba.core.JavaProgram;\r
+import soba.core.JavaProgramTest;\r
+import soba.core.MethodInfo;\r
+import soba.core.vta.IAnalysisTarget;\r
+import soba.core.vta.TypeSet;\r
+import soba.core.vta.VTAResolver;\r
+\r
+public class VTAResolverTest implements ExampleProgram {\r
+\r
+ private static JavaProgram program;\r
+ private static VTAResolver resolver;\r
+ \r
+ @BeforeClass\r
+ public static void setupResolver() {\r
+ program = JavaProgramTest.readExampleProgram();\r
+ resolver = new VTAResolver(program, new IAnalysisTarget(){\r
+ @Override\r
+ public boolean assumeExternalCallers(MethodInfo m) {\r
+ return false;\r
+ }\r
+ @Override\r
+ public boolean isExcludedType(String className) {\r
+ return false;\r
+ }\r
+ @Override\r
+ public boolean isTargetMethod(MethodInfo m) {\r
+ return m.getClassName().startsWith("soba/testdata");\r
+ }\r
+ @Override\r
+ public boolean isTargetField(FieldInfo f) {\r
+ return true;\r
+ }\r
+ });\r
+ }\r
+ \r
+ private void checkClasses(MethodInfo[] resolved, String... classNames) {\r
+ assertThat(Arrays.stream(resolved).map(m -> m.getClassName()).collect(Collectors.toList()), containsInAnyOrder(classNames));\r
+ }\r
+ \r
+ @Test\r
+ public void testResolveCall01() {\r
+ ClassInfo c = program.getClassInfo(CLASS_E);\r
+ MethodInfo m = c.findMethod("testDynamicBinding1", "()V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int counter = 0;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ if (counter == 0 || counter == 1) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_D, CLASS_G);\r
+ }\r
+ counter++;\r
+ }\r
+ }\r
+ assertThat(counter, is(2));\r
+ \r
+ }\r
+\r
+ @Test\r
+ public void testResolveCall02() {\r
+ ClassInfo c = program.getClassInfo("soba/testdata/inheritance2/E");\r
+ MethodInfo m = c.findMethod("testDynamicBinding2", "()V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int counter = 0;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ if (counter == 0) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_D);\r
+ } else if (counter == 1) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_G);\r
+ }\r
+ counter++;\r
+ }\r
+ }\r
+ assertThat(counter, is(2));\r
+ \r
+ }\r
+\r
+ /**\r
+ * Inherited but not overridden methods\r
+ */\r
+ @Test\r
+ public void testResolvedCall03() {\r
+ ClassInfo c = program.getClassInfo(CLASS_E);\r
+ MethodInfo m = c.findMethod("testDynamicBinding3", "()V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int counter = 0;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ if (counter == 0) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_C);\r
+ } else if (counter == 1) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_C);\r
+ }\r
+ counter++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Declared but not implemented method\r
+ */\r
+ @Test\r
+ public void testResolvedCall04() {\r
+ ClassInfo c = program.getClassInfo(CLASS_E);\r
+ MethodInfo m = c.findMethod("testDynamicBinding6", "(Lsoba/testdata/inheritance2/L;)V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int counter = 0;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ if (counter == 0) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ assertThat(methods, is(emptyArray()));\r
+ } else if (counter == 1) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_C, CLASS_D, CLASS_G);\r
+ }\r
+ counter++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Resolves library method calls.\r
+ */\r
+ @Test\r
+ public void testResolveCall05() {\r
+ ClassInfo c = program.getClassInfo(CLASS_E);\r
+ MethodInfo m = c.findMethod("testDynamicBinding7", "(Z)V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ for (int i = 0; i < instructions.size(); i++) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, "java/util/ArrayList", "java/util/LinkedList");\r
+ }\r
+ }\r
+ }\r
+ \r
+ private void checkLoopBinding(MethodInfo m) {\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ MethodInsnNode call = (MethodInsnNode)instructions.get(i);\r
+ if (call.name.equals("x")) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ checkClasses(methods, CLASS_D, CLASS_G);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ private void checkParamBinding(MethodInfo m) {\r
+ TypeSet typeset = resolver.getMethodParamType(m, 1);\r
+ assertThat(typeset.getTypeCount(), is(2));\r
+ }\r
+ \r
+ @Test\r
+ public void testLoopBinding() {\r
+ ClassInfo c = program.getClassInfo(CLASS_E);\r
+ checkLoopBinding(c.findMethod("testDynamicBinding4", "(Lsoba/testdata/inheritance1/C;)V"));\r
+ checkLoopBinding(c.findMethod("testDynamicBinding5", "(Lsoba/testdata/inheritance1/C;)V"));\r
+ }\r
+ \r
+ @Test\r
+ public void testMethodParamType() {\r
+ ClassInfo c = program.getClassInfo(CLASS_E);\r
+ checkParamBinding(c.findMethod("testDynamicBinding4", "(Lsoba/testdata/inheritance1/C;)V"));\r
+ checkParamBinding(c.findMethod("testDynamicBinding5", "(Lsoba/testdata/inheritance1/C;)V"));\r
+ }\r
+\r
+ @Test\r
+ public void testgetReceiverTypeAtCallsite() {\r
+ ClassInfo c = program.getClassInfo(CLASS_C);\r
+ MethodInfo m = c.findMethod("main", "([Ljava/lang/String;)V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int count = 0;\r
+ for (int i = 0; i < instructions.size(); i++) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ if (count == 0) {\r
+ TypeSet typeSet = resolver.getReceiverTypeAtCallsite(m, i);\r
+ assertThat(typeSet.getTypeCount(), is(0));\r
+ } else {\r
+ TypeSet typeSet = resolver.getReceiverTypeAtCallsite(m, i);\r
+ assertThat(typeSet.getTypeCount(), is(1));\r
+ }\r
+ count++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Test\r
+ public void testReflection() {\r
+ ClassInfo c = program.getClassInfo("soba/testdata/ReflectionCode");\r
+ MethodInfo m = c.findMethod("newInstanceUser", "()V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int counter = 0;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ MethodInsnNode call = (MethodInsnNode)instructions.get(i);\r
+ if (call.name.equals("toString")) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ counter++;\r
+ if (counter == 1) {\r
+ checkClasses(methods, CLASS_C, CLASS_D);\r
+ } else {\r
+ checkClasses(methods, CLASS_D);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ assertThat(counter, is(2));\r
+ }\r
+ \r
+ @Test\r
+ public void testReflection2() {\r
+ ClassInfo c = program.getClassInfo("soba/testdata/ReflectionCode");\r
+ MethodInfo m = c.findMethod("newInstanceUser2", "()V");\r
+ InsnList instructions = m.getMethodNode().instructions;\r
+ int counter = 0;\r
+ for (int i=0; i<instructions.size(); ++i) {\r
+ if (instructions.get(i).getOpcode() == Opcodes.INVOKEVIRTUAL) {\r
+ MethodInsnNode call = (MethodInsnNode)instructions.get(i);\r
+ if (call.name.equals("toString")) {\r
+ MethodInfo[] methods = resolver.resolveCall(m.getCallSite(i));\r
+ assertThat(methods.length, is(greaterThan(1))); // at least C and D implements toString.\r
+ counter++;\r
+ }\r
+ }\r
+ }\r
+ assertThat(counter, is(1));\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata;\r
+\r
+public class ControlDependenceCode {\r
+\r
+ /**\r
+ * This code fragment causes a loop of control dependency. \r
+ */\r
+ public static void main(String[] args) {\r
+ int x = 0;\r
+ for (String s : args) { // controls "if" statements in the loop.\r
+ if (s == null) {\r
+ continue;\r
+ }\r
+ if (s.length() == 1) { // Because this goes to the exit of this method, this statement controls the enclosing "for" statement. \r
+ test();\r
+ return;\r
+ } else if (s.length() == 2) {\r
+ x = 2;\r
+ continue;\r
+ } else if (s.length() == 3) {\r
+ x = 3;\r
+ }\r
+ }\r
+ use(x);\r
+ }\r
+ \r
+ private static void use(int x) {\r
+ \r
+ }\r
+ \r
+ private static void test() {\r
+ \r
+ }\r
+}\r
--- /dev/null
+package soba.testdata;\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+\r
+/**\r
+ * This class itself has no functionality. \r
+ * The code is used only for testing.\r
+ * @author ishio\r
+ */\r
+public class DefUseTestData {\r
+\r
+ \r
+ public void overwriteParam(int x, int y) {\r
+ if (x == 0) y = 1;\r
+ System.out.println(y);\r
+ }\r
+ \r
+ public void localDataDependence() {\r
+ int x;\r
+ boolean b = true;\r
+ if (b) {\r
+ x = 1;\r
+ } else {\r
+ x = 2;\r
+ System.err.println(x); // x = 2\r
+ x = 3;\r
+ }\r
+ System.err.println(x); // x = 1 or 3\r
+ System.err.println(x); // x = 1 or 3\r
+ }\r
+\r
+ /**\r
+ * The method body has no meaning; this code is to use a finally block.\r
+ * @return\r
+ */\r
+ public int tryFinallyDependence() {\r
+ int x = 0;\r
+ File f = new File("test");\r
+ FileInputStream stream = null;\r
+ try {\r
+ stream = new FileInputStream(f);\r
+ x = stream.read();\r
+ if (x == 0) {\r
+ return x;\r
+ } else {\r
+ return x+1;\r
+ }\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ } finally {\r
+ try {\r
+ if (stream != null) stream.close();\r
+ } catch (IOException e) {\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+ \r
+ public int forStatement() {\r
+ int x = 0;\r
+ for (int i=0; i<100; i++) {\r
+ x += i;\r
+ System.out.println(i);\r
+ if (i / 80 == 1) return x;\r
+ System.out.println(x);\r
+ }\r
+ return x;\r
+ }\r
+\r
+ /**\r
+ * The same implementation as forStatement.\r
+ */\r
+ public int whileStatement() {\r
+ int x = 0;\r
+ int i = 0;\r
+ while (i<100) {\r
+ x += i;\r
+ System.out.println(i);\r
+ if (i / 80 == 1) return x;\r
+ System.out.println(x);\r
+ i++;\r
+ }\r
+ return x;\r
+ }\r
+ \r
+ public void withInnerClass() {\r
+ \r
+ final int k = 100;\r
+ \r
+ final Inner1 inner1 = new Inner1() {\r
+ \r
+ private int x = k;\r
+ private int y = 100;\r
+ \r
+ public void printInner() {\r
+ System.out.println(x);\r
+ System.out.println(y);\r
+ }\r
+ \r
+ @Override\r
+ public void print() {\r
+ printInner();\r
+ }\r
+ };\r
+ \r
+ Inner2 inner2 = new Inner2(inner1) {\r
+\r
+ private int x = k;\r
+ private Inner3 i = new Inner3(new Inner1());\r
+\r
+ public void print() {\r
+ System.out.println(x);\r
+ System.out.println(i);\r
+ }\r
+ };\r
+ inner2.print();\r
+ \r
+ }\r
+ \r
+ class Inner1 {\r
+ \r
+ public Inner1() {\r
+ }\r
+ \r
+ public void print() {\r
+ }\r
+ }\r
+ \r
+ class Inner2 {\r
+ private Inner1 arg;\r
+ public Inner2(Inner1 arg) {\r
+ this.arg = arg;\r
+ }\r
+ public void print() {\r
+ arg.print();\r
+ }\r
+\r
+ class Inner3 {\r
+ public Inner3(Inner1 arg) {\r
+ \r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata;\r
+\r
+public class ObjectTransferCode {\r
+\r
+ public int[][] newObject(int x) {\r
+ ObjectTransferCode obj1 = new ObjectTransferCode();\r
+ ObjectTransferCode obj2, obj3;\r
+ obj2 = obj1;\r
+ obj3 = null;\r
+ m1(obj2);\r
+ obj1 = m2(obj3);\r
+ ObjectTransferCode[] array = new ObjectTransferCode[1];\r
+ int[][] multiArray = new int[1][1];\r
+ System.out.println(array.length);\r
+// System.out.println(multiArray.length);\r
+ return multiArray;\r
+ }\r
+ \r
+ public void m1(ObjectTransferCode obj) {\r
+ \r
+ }\r
+ \r
+ public ObjectTransferCode m2(ObjectTransferCode obj) {\r
+ return obj;\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata;\r
+\r
+\r
+import soba.testdata.inheritance1.D;\r
+\r
+public class ReflectionCode {\r
+\r
+ \r
+ public void newInstanceUser() {\r
+ Class<?> c = D.class;\r
+ try {\r
+ Object o = c.newInstance();\r
+ System.out.print(o.toString());\r
+ \r
+ D obj = (D)o;\r
+ System.out.print(obj.toString());\r
+ } catch (IllegalAccessException e) {\r
+ } catch (InstantiationException e) {\r
+ }\r
+ }\r
+\r
+ public void newInstanceUser2() {\r
+ Class<?> c = D.class;\r
+ try {\r
+ Object o = c.newInstance();\r
+ System.out.print(o.toString());\r
+ } catch (IllegalAccessException e) {\r
+ } catch (InstantiationException e) {\r
+ }\r
+ }\r
+ \r
+\r
+}\r
--- /dev/null
+package soba.testdata;\r
+\r
+public class StatementUnitsData {\r
+\r
+ public void f1(boolean b) {\r
+ int i = b ? 1: 0;\r
+ System.out.println(i);\r
+ }\r
+ \r
+ public void f2(boolean b) {\r
+ int i = b ? 1: 0;\r
+ System.out.println(i == 0 ? true : false);\r
+ }\r
+\r
+ public void f3(boolean b1, boolean b2) {\r
+ boolean i = b1 && b2 ? b2 : (b1 || b2);\r
+ System.out.println(i);\r
+ }\r
+\r
+ public void if1(boolean b1, boolean b2) {\r
+ if (b1 && b2) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+\r
+ public void if1nest(boolean b1, boolean b2) {\r
+ if (b1) {\r
+ if (b2) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void if2(boolean b1, boolean b2) {\r
+ if (b1 || b2) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+\r
+ public void if3(boolean b1, boolean b2) {\r
+ if ((b1 && !b2) || (!b1 && b2)) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+\r
+ public void if4(boolean b1, boolean b2) {\r
+ if ((b1 || !b2) && (!b1 || b2)) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * This method has exactly the same bytecode as if4. (in Eclipse 3.8.0)\r
+ */\r
+ public void if4nest(boolean b1, boolean b2) {\r
+ if (b1 || !b2) { if (!b1 || b2) {\r
+ System.out.println(b1);\r
+ } }\r
+ }\r
+\r
+ public void g1(boolean b1, boolean b2) {\r
+ if ((b1 || b2) ? b1 : b2) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+\r
+ public void g1nest(boolean b1, boolean b2) {\r
+ if ((b1 || b2)) {\r
+ if (b1) {\r
+ System.out.println(b1);\r
+ }\r
+ } else {\r
+ if (b2) {\r
+ System.out.println(b1);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void g2(boolean b1, boolean b2, boolean b3) {\r
+ boolean b = !(b1 && b2) || b3;\r
+ System.out.println(b);\r
+ }\r
+\r
+ public void h1(boolean b1, boolean b2, boolean b3) {\r
+ boolean b;\r
+ if (b1) {\r
+ b = b2;\r
+ } else {\r
+ b = b3;\r
+ }\r
+ System.out.println(b);\r
+ }\r
+\r
+ public void h2(boolean b1, boolean b2, boolean b3, boolean b4) {\r
+ boolean b;\r
+ if (b1 && b2) {\r
+ b = b3;\r
+ } else {\r
+ b = b4;\r
+ }\r
+ System.out.println(b);\r
+ }\r
+\r
+ public void h3(int x, int y, int z, boolean b) {\r
+ if ((x = (b ? y : z)) == 0 ) {\r
+ System.out.println(b);\r
+ }\r
+ }\r
+\r
+ public void i1(Object obj, boolean x, boolean y) {\r
+ boolean b = (x && y) ? (obj == null) : false;\r
+ System.out.println(b);\r
+ }\r
+\r
+ public void i2(Object obj) {\r
+ boolean b = (obj == null);\r
+ System.out.println(b);\r
+ }\r
+\r
+ public interface I {\r
+ public void method(int x, int y);\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata.inheritance1;\r
+\r
+import soba.testdata.inheritance2.F;\r
+\r
+public class C {\r
+\r
+ protected int x;\r
+ \r
+ public static void main(String[] args) {\r
+ System.err.println(J.x);\r
+ F f = new F();\r
+ C c = f;\r
+ c.o();\r
+ f.o();\r
+ }\r
+ \r
+ public C(int x) {\r
+ System.err.println("C.<init>");\r
+ q(0.0);\r
+ }\r
+\r
+ protected void m() {\r
+ System.err.println("C.m");\r
+ }\r
+ \r
+ void n() {\r
+ System.err.println("C.n()");\r
+ }\r
+ \r
+ void o() {\r
+ System.err.println("C.o()");\r
+ }\r
+ \r
+ protected void p(int x) {\r
+ System.out.println("C.p(int)");\r
+ }\r
+ \r
+ private void q(double d) {\r
+ System.out.println("C.q(double)");\r
+ }\r
+ \r
+ public static void novariables() {\r
+ \r
+ }\r
+ \r
+ public void x(int t) {\r
+ System.out.println("C.x(int)");\r
+ }\r
+ \r
+ public void y(int t) {\r
+ System.out.println("C.y(int)");\r
+ }\r
+ \r
+ @Override\r
+ public String toString() {\r
+ return super.toString();\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata.inheritance1;\r
+\r
+import soba.testdata.inheritance2.F;\r
+import soba.testdata.inheritance2.H;\r
+\r
+public class D extends C implements I, K {\r
+\r
+ \r
+ public D() {\r
+ this(K.x);\r
+ System.err.println("D.<init>");\r
+ }\r
+ \r
+ public D(int x) {\r
+ super(x);\r
+ System.err.println("D.<init>(int)");\r
+ }\r
+ \r
+ public void m() {\r
+ super.m();\r
+ System.err.println("D.m");\r
+ }\r
+ \r
+ public void testPackagePrivate() {\r
+ C c = new C(0);\r
+ c.n();\r
+ }\r
+\r
+ public void testPackagePrivate2() {\r
+ C c = new F();\r
+ c.n();\r
+ }\r
+\r
+ public void testPackagePrivate3() {\r
+ C c = new G();\r
+ c.n();\r
+ }\r
+\r
+ public void testPackagePrivate4() {\r
+ C c = new H();\r
+ c.n();\r
+ }\r
+\r
+ public void n() {\r
+ System.err.println("D.n()");\r
+ }\r
+ \r
+ public int example(int i, long l, double d, String s) {\r
+ return i;\r
+ }\r
+ \r
+ public void x(int t) {\r
+ System.out.println("D.x(int)");\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return super.toString();\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata.inheritance1;\r
+\r
+public class G extends C {\r
+\r
+ public G() {\r
+ super(0);\r
+ }\r
+ \r
+ @Override\r
+ void n() {\r
+ super.n();\r
+ System.err.println("G.n()");\r
+ }\r
+ \r
+ public final void finalMethod() {\r
+ \r
+ }\r
+ \r
+ protected volatile int volatileField = 0;\r
+ public transient int transientField = 0;\r
+ private final int finalField = 0;\r
+ \r
+ \r
+ public class InternalG {\r
+ \r
+ public InternalG() {\r
+ super();\r
+ System.out.println();\r
+ }\r
+ \r
+ public void m() {\r
+ finalMethod();\r
+ n();\r
+ System.out.println(finalField);\r
+ }\r
+ \r
+ }\r
+ \r
+ public void x(int t) {\r
+ System.out.println("G.x(int)");\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.testdata.inheritance1;\r
+\r
+public interface I {\r
+\r
+ public static final int x = 2;\r
+ public void m();\r
+}\r
--- /dev/null
+package soba.testdata.inheritance1;\r
+\r
+public interface J {\r
+\r
+ public static final int x = 1;\r
+}\r
--- /dev/null
+package soba.testdata.inheritance1;\r
+\r
+public interface K extends I {\r
+\r
+}\r
--- /dev/null
+package soba.testdata.inheritance2;\r
+\r
+import java.util.ArrayList;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+\r
+import soba.testdata.inheritance1.C;\r
+import soba.testdata.inheritance1.D;\r
+import soba.testdata.inheritance1.G;\r
+\r
+public class E {\r
+\r
+ public static void main(String[] args) {\r
+ new E().exec();\r
+ }\r
+ \r
+ public E() {\r
+ D d = new D(0);\r
+ d.m();\r
+ }\r
+ \r
+ public void testPackagePrivate() {\r
+ F f = new F();\r
+ f.n();\r
+ }\r
+\r
+\r
+ public void exec() {\r
+ System.err.println("Hello, World!");\r
+ D d = new D();\r
+ d.testPackagePrivate();\r
+ d.testPackagePrivate2();\r
+ d.testPackagePrivate3();\r
+ d.testPackagePrivate4();\r
+ }\r
+ \r
+ private int flag;\r
+ \r
+ /*\r
+ * Test case:\r
+ * def1 -> use1, use2\r
+ * def2 -> use2\r
+ * VTA judges both use1 and use2 may call D.x(int) and G.x(int)\r
+ */\r
+ public void testDynamicBinding1() {\r
+ C c = new D(0); // def1\r
+ c.x(0); // use1\r
+ if (flag != 0) { \r
+ c = new G(); // def2\r
+ }\r
+ c.x(1); // use2\r
+ }\r
+ \r
+ /*\r
+ * Test case:\r
+ * def1 -> use1\r
+ * def2 -> use2\r
+ * Our VTA implementation judges use1 call D.x(int), use2 calls G.x(int)\r
+ * This is because our VTA uses a flow-sensitive data-flow analysis \r
+ * to distinguish local variable entries.\r
+ */\r
+ public void testDynamicBinding2() {\r
+ C c = new D(0); // def1 \r
+ c.x(0); // use1\r
+ c = new G(); // def2\r
+ c.x(1); // use2\r
+ }\r
+ \r
+ public void testDynamicBinding3() {\r
+ D d1 = new D(0); // def1 \r
+ d1.y(1); // use1 -- this invokes C.y() because D does not override C.y().\r
+ C d2 = new D(0); // def2 \r
+ d2.y(1); // use2\r
+ }\r
+\r
+ public void testDynamicBinding4(C c) {\r
+ if (c == null) {\r
+ c = new D(0);\r
+ }\r
+ testDynamicBinding5(c);\r
+ c.x(0);\r
+ }\r
+\r
+ public void testDynamicBinding5(C c) {\r
+ if (c == null) {\r
+ c = new G();\r
+ }\r
+ testDynamicBinding4(c);\r
+ c.x(1);\r
+ }\r
+ \r
+ public void testDynamicBinding6(L l) {\r
+ l.getC().x(0); // There are no implementation of getC()\r
+ }\r
+\r
+ public void testDynamicBinding7(boolean b) {\r
+ List<String> list;\r
+ if (b) {\r
+ list = new ArrayList<>();\r
+ } else {\r
+ list = new LinkedList<>();\r
+ }\r
+ int size =list.size();\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata.inheritance2;\r
+\r
+import soba.testdata.inheritance1.C;\r
+\r
+public class F extends C {\r
+\r
+ public F() {\r
+ super(0);\r
+ }\r
+ \r
+ /**\r
+ * This method declaration does not override C.n()\r
+ * because C.n() is package-private.\r
+ */\r
+ void n() {\r
+ System.err.println("F.n()");\r
+ }\r
+ \r
+ public void o() {\r
+ System.err.println("F.o()");\r
+ }\r
+ \r
+ public void k() {\r
+ String[][][] array = new String[10][20][30];\r
+ System.err.println(array.toString());\r
+ String[] another = new String[10];\r
+ System.err.println(another.toString());\r
+ }\r
+}\r
--- /dev/null
+package soba.testdata.inheritance2;\r
+\r
+import soba.testdata.inheritance1.D;\r
+\r
+public class H extends D {\r
+\r
+ protected int x;\r
+ \r
+ public H() {\r
+ p(1);\r
+ q(0.1);\r
+ }\r
+ \r
+ @Override\r
+ public void n() {\r
+ System.err.println("H.n()");\r
+ }\r
+ \r
+ protected void p(int x) {\r
+ System.out.println("C.p(int)");\r
+ }\r
+ \r
+ private void q(double d) {\r
+ System.out.println("C.q(double)");\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.testdata.inheritance2;\r
+\r
+import soba.testdata.inheritance1.C;\r
+\r
+public abstract class L {\r
+\r
+ public abstract C getC();\r
+ \r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Test;\r
+\r
+\r
+public class IntPairListTest {\r
+\r
+ @Test\r
+ public void testAdd() throws Exception {\r
+ IntPairList list = new IntPairList(2);\r
+ list.add(1, 2);\r
+ list.add(3, 4);\r
+ list.add(5, 6);\r
+ \r
+ assertThat(list.getFirstValue(0), is(1));\r
+ assertThat(list.getSecondValue(0), is(2));\r
+ assertThat(list.getFirstValue(1), is(3));\r
+ assertThat(list.getSecondValue(1), is(4));\r
+ assertThat(list.getFirstValue(2), is(5));\r
+ assertThat(list.getSecondValue(2), is(6));\r
+ assertThat(list.size(), is(3));\r
+ }\r
+\r
+ @Test\r
+ public void testSortByFirstValues() throws Exception {\r
+ IntPairList list = new IntPairList();\r
+ list.add(1, 1); // 1\r
+ list.add(2, 0); // 2\r
+ list.add(3, 0); // 3\r
+ list.add(4, 4); // 4\r
+ list.add(5, 5); // 5\r
+ list.add(6, 3); // 6\r
+ list.add(7, 2); // 7\r
+ list.add(8, 4); // 8\r
+ list.add(9, 1); // 9\r
+ list.add(0, 5); // 0\r
+ \r
+ list.sort();\r
+ \r
+ assertThat(list.getFirstValue(2), is(2));\r
+ assertThat(list.getSecondValue(2), is(0));\r
+ assertThat(list.getFirstValue(4), is(4));\r
+ assertThat(list.getSecondValue(4), is(4));\r
+ assertThat(list.getFirstValue(9), is(9));\r
+ assertThat(list.getSecondValue(9), is(1));\r
+ }\r
+\r
+\r
+ @Test\r
+ public void testSetValues() throws Exception {\r
+ IntPairList list = new IntPairList(2);\r
+ list.add(1, 2);\r
+ list.add(3, 4);\r
+ list.add(5, 6);\r
+ \r
+ list.setFirstValue(1, 10);\r
+ assertThat(list.getFirstValue(0), is(1));\r
+ assertThat(list.getFirstValue(1), is(10));\r
+ assertThat(list.getFirstValue(2), is(5));\r
+ assertThat(list.getSecondValue(0), is(2));\r
+ assertThat(list.getSecondValue(1), is(4));\r
+ assertThat(list.getSecondValue(2), is(6));\r
+ \r
+ list.setSecondValue(2, 27);\r
+ assertThat(list.getFirstValue(0), is(1));\r
+ assertThat(list.getFirstValue(1), is(10));\r
+ assertThat(list.getFirstValue(2), is(5));\r
+ assertThat(list.getSecondValue(0), is(2));\r
+ assertThat(list.getSecondValue(1), is(4));\r
+ assertThat(list.getSecondValue(2), is(27));\r
+ }\r
+ \r
+ @Test\r
+ public void testAddAll() { \r
+ IntPairList list = new IntPairList(2);\r
+ list.add(1, 2);\r
+ list.add(3, 4);\r
+ IntPairList another = new IntPairList(2);\r
+ another.add(5, 6);\r
+ another.add(7, 8);\r
+ \r
+ list.addAll(another);\r
+ assertThat(list.size(), is(4));\r
+ assertThat(list.getFirstValue(0), is(1));\r
+ assertThat(list.getSecondValue(0), is(2));\r
+ assertThat(list.getFirstValue(1), is(3));\r
+ assertThat(list.getSecondValue(1), is(4));\r
+ assertThat(list.getFirstValue(2), is(5));\r
+ assertThat(list.getSecondValue(2), is(6));\r
+ assertThat(list.getFirstValue(3), is(7));\r
+ assertThat(list.getSecondValue(3), is(8));\r
+ assertThat(another.size(), is(2));\r
+\r
+ another.addAll(another);\r
+ assertThat(another.size(), is(4));\r
+ assertThat(another.getFirstValue(0), is(5));\r
+ assertThat(another.getSecondValue(0), is(6));\r
+ assertThat(another.getFirstValue(1), is(7));\r
+ assertThat(another.getSecondValue(1), is(8));\r
+ assertThat(another.getFirstValue(2), is(5));\r
+ assertThat(another.getSecondValue(2), is(6));\r
+ assertThat(another.getFirstValue(3), is(7));\r
+ assertThat(another.getSecondValue(3), is(8));\r
+ }\r
+ \r
+ @Test\r
+ public void testFreeze() {\r
+ IntPairList list = new IntPairList(2);\r
+ list.add(1, 2);\r
+ list.freeze();\r
+ try {\r
+ list.add(3, 4);\r
+ fail();\r
+ } catch (IntPairList.FrozenListException e) {\r
+ }\r
+ try {\r
+ list.setFirstValue(0, 1);\r
+ fail();\r
+ } catch (IntPairList.FrozenListException e) {\r
+ }\r
+ try {\r
+ list.addAll(list);\r
+ fail();\r
+ } catch (IntPairList.FrozenListException e) {\r
+ }\r
+ }\r
+ \r
+ @Test\r
+ public void testArrayIndexOutOfBounds() {\r
+ IntPairList list = new IntPairList(2);\r
+ list.add(1, 2);\r
+ try {\r
+ list.getFirstValue(-1);\r
+ fail();\r
+ } catch (ArrayIndexOutOfBoundsException e) {\r
+ }\r
+ try {\r
+ list.getFirstValue(1);\r
+ fail();\r
+ } catch (ArrayIndexOutOfBoundsException e) {\r
+ }\r
+ try {\r
+ list.getSecondValue(-1);\r
+ fail();\r
+ } catch (ArrayIndexOutOfBoundsException e) {\r
+ }\r
+ try {\r
+ list.getSecondValue(1);\r
+ fail();\r
+ } catch (ArrayIndexOutOfBoundsException e) {\r
+ }\r
+ }\r
+ \r
+ @Test\r
+ public void testForEach() {\r
+ IntPairList list = new IntPairList(2);\r
+ list.add(1, 2);\r
+ list.add(3, 4);\r
+ list.add(5, 6);\r
+ list.foreach(new IntPairProc() {\r
+ int times = 0;\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ if (times == 0) {\r
+ assertThat(elem1, is(1));\r
+ assertThat(elem2, is(2));\r
+ } else if (times == 1) {\r
+ assertThat(elem1, is(3));\r
+ assertThat(elem2, is(4));\r
+ } else {\r
+ fail();\r
+ }\r
+ times++;\r
+ return times == 1;\r
+ }\r
+ });\r
+ }\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Test;\r
+\r
+public class IntPairSetTest {\r
+\r
+ @Test\r
+ public void testIntPairSet() {\r
+ IntPairSet set = new IntPairSet();\r
+ assertThat(set.size(), is(0));\r
+ set.add(0, 1);\r
+ assertThat(set.size(), is(1));\r
+ set.add(3, 2);\r
+ assertThat(set.size(), is(2));\r
+ assertThat(set.contains(0, 1), is(true));\r
+ assertThat(set.contains(3, 2), is(true));\r
+ assertThat(set.contains(0, 2), is(false));\r
+ set.add(1, 2);\r
+ assertThat(set.size(), is(3));\r
+ set.add(0, 1);\r
+ assertThat(set.size(), is(3));\r
+ assertThat(set.contains(0, 1), is(true));\r
+ assertThat(set.contains(1, 2), is(true));\r
+ assertThat(set.contains(3, 2), is(true));\r
+ }\r
+\r
+ private boolean visited01 = false;\r
+ private boolean visited12 = false;\r
+ private boolean visited14 = false;\r
+ private boolean visited32 = false;\r
+ private boolean visited41 = false;\r
+ \r
+ @Test\r
+ public void testForEach() {\r
+ IntPairSet set = new IntPairSet();\r
+ set.add(4, 1);\r
+ set.add(3, 2);\r
+ set.add(1, 4);\r
+ set.add(1, 2);\r
+ set.add(0, 1);\r
+ set.foreach(new IntPairProc() {\r
+ \r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ switch (elem1) {\r
+ case 0: \r
+ assertThat(elem2, is(1));\r
+ assertThat(visited01, is(false));\r
+ visited01 = true;\r
+ break;\r
+ case 1:\r
+ if (elem2 == 2) {\r
+ assertThat(visited12, is(false));\r
+ visited12 = true;\r
+ } else if (elem2 == 4) {\r
+ assertThat(visited14, is(false));\r
+ visited14 = true;\r
+ } else {\r
+ fail();\r
+ }\r
+ break;\r
+ case 3:\r
+ assertThat(elem2, is(2));\r
+ assertThat(visited32, is(false));\r
+ visited32 = true;\r
+ break;\r
+ case 4:\r
+ assertThat(elem2, is(1));\r
+ assertThat(visited41, is(false));\r
+ visited41 = true;\r
+ break;\r
+ default:\r
+ fail();\r
+ }\r
+ return true;\r
+ }\r
+ });\r
+ assertThat(visited01, is(true));\r
+ assertThat(visited12, is(true));\r
+ assertThat(visited14, is(true));\r
+ assertThat(visited32, is(true));\r
+ assertThat(visited41, is(true));\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Test;\r
+\r
+public class IntPairUtilTest {\r
+\r
+ @Test\r
+ public void testCreateList() {\r
+ IntPairSet set = new IntPairSet();\r
+ set.add(4, 1);\r
+ set.add(3, 2);\r
+ set.add(1, 4);\r
+ set.add(1, 2);\r
+ set.add(4, 1);\r
+ set.add(0, 1);\r
+ set.add(2, 9);\r
+ set.add(3, 2);\r
+ IntPairList list = IntPairUtil.createList(set);\r
+ list.sort();\r
+ assertThat(list.size(), is(6));\r
+ assertThat(list.getFirstValue(0), is(0));\r
+ assertThat(list.getSecondValue(0), is(1));\r
+ assertThat(list.getFirstValue(1), is(1));\r
+ assertThat(list.getSecondValue(1), is(2));\r
+ assertThat(list.getFirstValue(2), is(1));\r
+ assertThat(list.getSecondValue(2), is(4));\r
+ assertThat(list.getFirstValue(3), is(2));\r
+ assertThat(list.getSecondValue(3), is(9));\r
+ assertThat(list.getFirstValue(4), is(3));\r
+ assertThat(list.getSecondValue(4), is(2));\r
+ assertThat(list.getFirstValue(5), is(4));\r
+ assertThat(list.getSecondValue(5), is(1));\r
+ }\r
+\r
+\r
+ @Test\r
+ public void testCreateList2() {\r
+ IntPairSet set = new IntPairSet();\r
+ set.add(4, 1);\r
+ set.add(3, 2);\r
+ set.add(1, 4);\r
+ set.add(1, 2);\r
+ set.add(4, 1);\r
+ set.add(0, 1);\r
+ set.add(2, 9);\r
+ set.add(3, 2);\r
+ IntPairSet set2 = new IntPairSet();\r
+ set2.add(1, 9);\r
+ set2.add(4, 1);\r
+ \r
+ IntPairList list = IntPairUtil.createList(set, set2);\r
+ list.sort();\r
+ assertThat(list.size(), is(7));\r
+ assertThat(list.getFirstValue(0), is(0));\r
+ assertThat(list.getSecondValue(0), is(1));\r
+ assertThat(list.getFirstValue(1), is(1));\r
+ assertThat(list.getSecondValue(1), is(2));\r
+ assertThat(list.getFirstValue(2), is(1));\r
+ assertThat(list.getSecondValue(2), is(4));\r
+ assertThat(list.getFirstValue(3), is(1));\r
+ assertThat(list.getSecondValue(3), is(9));\r
+ assertThat(list.getFirstValue(4), is(2));\r
+ assertThat(list.getSecondValue(4), is(9));\r
+ assertThat(list.getFirstValue(5), is(3));\r
+ assertThat(list.getSecondValue(5), is(2));\r
+ assertThat(list.getFirstValue(6), is(4));\r
+ assertThat(list.getSecondValue(6), is(1));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import java.util.EmptyStackException;\r
+\r
+import org.junit.Assert;\r
+import org.junit.Test;\r
+\r
+import soba.util.IntSetStack.DuplicatedElementException;\r
+import soba.util.IntSetStack.InvalidElementException;\r
+\r
+\r
+public class IntSetStackTest {\r
+\r
+ @Test\r
+ public void testStack() throws Exception {\r
+ IntSetStack stack = new IntSetStack(3);\r
+ Assert.assertTrue(stack.isEmpty());\r
+ Assert.assertFalse(stack.contains(0));\r
+ Assert.assertFalse(stack.contains(1));\r
+ Assert.assertFalse(stack.contains(2));\r
+ \r
+ stack.push(0);\r
+ Assert.assertEquals(0, stack.peek());\r
+ Assert.assertFalse(stack.isEmpty());\r
+ Assert.assertTrue(stack.contains(0));\r
+ Assert.assertFalse(stack.contains(1));\r
+ Assert.assertFalse(stack.contains(2));\r
+ \r
+ stack.push(1);\r
+ Assert.assertEquals(1, stack.peek());\r
+ Assert.assertFalse(stack.isEmpty());\r
+ Assert.assertTrue(stack.contains(0));\r
+ Assert.assertTrue(stack.contains(1));\r
+ Assert.assertFalse(stack.contains(2));\r
+ \r
+ stack.push(2);\r
+ Assert.assertEquals(2, stack.peek());\r
+ Assert.assertFalse(stack.isEmpty());\r
+ Assert.assertTrue(stack.contains(0));\r
+ Assert.assertTrue(stack.contains(1));\r
+ Assert.assertTrue(stack.contains(2));\r
+ \r
+ Assert.assertEquals(2, stack.pop());\r
+ Assert.assertEquals(1, stack.peek());\r
+ Assert.assertTrue(stack.contains(0));\r
+ Assert.assertTrue(stack.contains(1));\r
+ Assert.assertFalse(stack.contains(2));\r
+\r
+ Assert.assertEquals(1, stack.pop());\r
+ Assert.assertEquals(0, stack.pop());\r
+ Assert.assertTrue(stack.isEmpty());\r
+\r
+ Assert.assertFalse(stack.contains(0));\r
+ Assert.assertFalse(stack.contains(1));\r
+ Assert.assertFalse(stack.contains(2));\r
+\r
+ try {\r
+ stack.pop();\r
+ Assert.fail();\r
+ } catch (EmptyStackException e) {\r
+ }\r
+ \r
+ try {\r
+ stack.peek();\r
+ Assert.fail();\r
+ } catch (EmptyStackException e) {\r
+ }\r
+ }\r
+ \r
+ @Test\r
+ public void testStackContains() {\r
+ IntSetStack stack = new IntSetStack(128);\r
+ try {\r
+ stack.push(64);\r
+ stack.push(96);\r
+ stack.push(127);\r
+ stack.push(128);\r
+ stack.push(129);\r
+ Assert.fail();\r
+ } catch (InvalidElementException e) {\r
+ Assert.assertEquals(129, e.getValue());\r
+ }\r
+ \r
+ Assert.assertTrue(stack.contains(64));\r
+ Assert.assertTrue(stack.contains(96));\r
+ Assert.assertTrue(stack.contains(127));\r
+ Assert.assertTrue(stack.contains(128));\r
+ Assert.assertFalse(stack.contains(63));\r
+ Assert.assertFalse(stack.contains(65));\r
+ Assert.assertFalse(stack.contains(95));\r
+ Assert.assertFalse(stack.contains(97));\r
+ Assert.assertFalse(stack.contains(126));\r
+ \r
+ try {\r
+ stack.push(64);\r
+ Assert.fail();\r
+ } catch (DuplicatedElementException e) {\r
+ }\r
+ }\r
+ \r
+ @Test\r
+ public void testStackIgnoreDuplicatedElement() {\r
+ IntSetStack stack = new IntSetStack(8);\r
+ stack.setIgnoreDuplicatedElement(true);\r
+ stack.push(1);\r
+ stack.push(2);\r
+ stack.push(3);\r
+ stack.push(4);\r
+ stack.push(2);\r
+ stack.push(4);\r
+ stack.push(1);\r
+ stack.push(3);\r
+ stack.push(0);\r
+ Assert.assertEquals(0, stack.pop());\r
+ Assert.assertEquals(4, stack.pop());\r
+ Assert.assertEquals(3, stack.pop());\r
+ Assert.assertEquals(2, stack.pop());\r
+ Assert.assertEquals(1, stack.pop());\r
+ Assert.assertTrue(stack.isEmpty());\r
+ stack.push(1);\r
+ stack.push(2);\r
+ stack.push(3);\r
+ stack.push(4);\r
+ Assert.assertEquals(4, stack.pop());\r
+ Assert.assertEquals(3, stack.pop());\r
+ Assert.assertEquals(2, stack.pop());\r
+ Assert.assertEquals(1, stack.pop());\r
+ Assert.assertTrue(stack.isEmpty());\r
+ }\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+import java.util.EmptyStackException;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Test;\r
+\r
+\r
+public class IntStackTest {\r
+\r
+ @Test\r
+ public void testStack() throws Exception {\r
+ IntStack stack = new IntStack(3);\r
+ assertThat(stack.isEmpty(), is(true));\r
+ stack.push(0);\r
+ assertThat(stack.peek(), is(0));\r
+ assertThat(stack.isEmpty(), is(false));\r
+ stack.push(1);\r
+ assertThat(stack.peek(), is(1));\r
+ assertThat(stack.isEmpty(), is(false));\r
+ stack.push(2);\r
+ assertThat(stack.peek(), is(2));\r
+ assertThat(stack.isEmpty(), is(false));\r
+ \r
+ assertThat(stack.pop(), is(2));\r
+ assertThat(stack.peek(), is(1));\r
+ assertThat(stack.pop(), is(1));\r
+ assertThat(stack.pop(), is(0));\r
+ assertThat(stack.isEmpty(), is(true));\r
+ try {\r
+ stack.pop();\r
+ fail();\r
+ } catch (EmptyStackException e) {\r
+ }\r
+ try {\r
+ stack.peek();\r
+ fail();\r
+ } catch (EmptyStackException e) {\r
+ }\r
+ \r
+ stack.push(0);\r
+ stack.push(1);\r
+ stack.push(2);\r
+ stack.push(3);\r
+ stack.push(4);\r
+ stack.push(5);\r
+ stack.push(6);\r
+ assertThat(stack.pop(), is(6));\r
+ assertThat(stack.contains(3), is(true));\r
+ assertThat(stack.pop(), is(5));\r
+ assertThat(stack.pop(), is(4));\r
+ assertThat(stack.contains(3), is(true));\r
+ assertThat(stack.pop(), is(3));\r
+ assertThat(stack.contains(3), is(false));\r
+ assertThat(stack.pop(), is(2));\r
+ assertThat(stack.pop(), is(1));\r
+ assertThat(stack.isEmpty(), is(false));\r
+ assertThat(stack.pop(), is(0));\r
+ assertThat(stack.isEmpty(), is(true));\r
+ }\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Test;\r
+\r
+public class ObjectIdMapTest {\r
+\r
+ @Test\r
+ public void testObjectIdMap() {\r
+ ObjectIdMap<String> idMap = new ObjectIdMap<String>();\r
+ int idABC = idMap.getId("abc");\r
+ int idAB = idMap.getId("ab");\r
+ int idABC2 = idMap.getId("abc");\r
+ int idD = idMap.getId("d");\r
+ assertThat(idMap.size(), is(3));\r
+ assertThat(idABC, is(0));\r
+ assertThat(idABC2, is(0));\r
+ assertThat(idAB, is(1));\r
+ assertThat(idD, is(2));\r
+ assertThat(idMap.getItem(idABC), is("abc"));\r
+ assertThat(idMap.getItem(idAB), is("ab"));\r
+ assertThat(idMap.getItem(idD), is("d"));\r
+ assertThat(idMap.getItem(-1), is(nullValue()));\r
+ assertThat(idMap.getItem(4), is(nullValue()));\r
+ }\r
+ \r
+ @Test\r
+ public void testAdd() { \r
+ ObjectIdMap<String> idMap = new ObjectIdMap<String>();\r
+ idMap.add("abc");\r
+ idMap.add("ab");\r
+ idMap.add("ab");\r
+ idMap.add("abc");\r
+ assertThat(idMap.size(), is(2));\r
+ assertThat(idMap.getItem(0), is("abc"));\r
+ assertThat(idMap.getItem(1), is("ab"));\r
+ }\r
+\r
+ @Test\r
+ public void testFreeze() { \r
+ ObjectIdMap<String> idMap = new ObjectIdMap<String>();\r
+ idMap.add("abc");\r
+ idMap.add("ab");\r
+ idMap.freeze();\r
+ try {\r
+ idMap.add("abc"); // ignored already registerd item\r
+ idMap.add("xyz"); // throws an exception\r
+ fail();\r
+ } catch (ObjectIdMap.FrozenMapException e) {\r
+ }\r
+ assertThat(idMap.getId("ab"), is(1));\r
+ assertThat(idMap.getItem(0), is("abc"));\r
+ assertThat(idMap.getItem(3), is(nullValue()));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util;\r
+\r
+public class UtilForAssertThat { \r
+\r
+ public static Integer[] asIntegerArray(int[] array) {\r
+ Integer[] result = new Integer[array.length];\r
+ for (int i = 0; i < result.length; i++) {\r
+ result[i] = array[i];\r
+ }\r
+ return result;\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.junit.Test;\r
+\r
+import soba.core.ClassInfo;\r
+\r
+public class ClasspathUtilTest {\r
+\r
+ @Test\r
+ public void testEnumerateSystemClasspath() {\r
+ List<String> systemList = ClasspathUtil.enumerateSystemClasspath();\r
+ assertThat(systemList, is(notNullValue()));\r
+ }\r
+ \r
+ @Test\r
+ public void testMerge() { \r
+ IClassList[] e1 = ClasspathUtil.getClassList(new String[] { "." });\r
+ IClassList[] e2 = ClasspathUtil.getClassList(new String[] { "..", "." });\r
+ IClassList[] e3 = ClasspathUtil.merge(e1, e2);\r
+ assertThat(e1[0] == e3[0], is(true));\r
+ assertThat(e2[0] == e3[1], is(true));\r
+ assertThat(e2[1] == e3[2], is(true));\r
+ }\r
+ \r
+ @Test\r
+ public void testGetClassList() {\r
+ String[] fileArray = new String[]{"bin/soba/testdata"};\r
+ IClassList[] results1 = ClasspathUtil.getClassList(fileArray);\r
+ assertThat(results1, is(arrayWithSize(1)));\r
+ IClassList[] results2 = ClasspathUtil.getClassList(fileArray, "");\r
+ assertThat(results2, is(arrayWithSize(1)));\r
+ \r
+ List<String> fileList = new ArrayList<>();\r
+ fileList.add("bin/soba/testdata");\r
+ IClassList[] results3 = ClasspathUtil.getClassList(fileList);\r
+ assertThat(results3, is(arrayWithSize(1)));\r
+ IClassList[] results4 = ClasspathUtil.getClassList(fileList, null);\r
+ assertThat(results4, is(arrayWithSize(1)));\r
+ \r
+ String[] zipFile = new String[]{"lib/asm-debug-all-5.0.3.jar"};\r
+ IClassList[] results5 = ClasspathUtil.getClassList(zipFile);\r
+ assertThat(results5, is(arrayWithSize(1)));\r
+ \r
+ String[] classFile = new String[]{"bin/soba/testdata/DefUseTestData.class"};\r
+ IClassList[] results6 = ClasspathUtil.getClassList(classFile);\r
+ assertThat(results6, is(arrayWithSize(1)));\r
+ \r
+ String[] appFiles = new String[]{"bin/soba/testdata/DefUseTestData.class"};\r
+ String[] libFiles = new String[]{"lib/asm-debug-all-5.0.3.jar"};\r
+ IClassList[] results7 = ClasspathUtil.getClassList(appFiles, libFiles);\r
+ assertThat(results7, is(arrayWithSize(2)));\r
+ assertThat(results7[1].getLabel(), is(ClassInfo.LIBRARY_LABEL));\r
+ }\r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+import static org.junit.Assume.*;\r
+\r
+import org.junit.Test;\r
+import java.io.File;\r
+\r
+public class DirectoryTest {\r
+ \r
+ @Test\r
+ public void testListSubdirectories() {\r
+ File f = new File("src");\r
+ assumeThat(f.isDirectory(), is(true));\r
+ \r
+ Directory[] dir = Directory.listSubdirectories(f, 0);\r
+ assertThat(dir.length, is(1));\r
+ assertThat(dir[0].getDirectory(), is(f));\r
+\r
+ File soba = new File(f, "soba");\r
+ Directory[] subdirs = Directory.listSubdirectories(f, 1);\r
+ assertThat(dir.length, is(1));\r
+ assertThat(subdirs[0].getDirectory(), is(soba));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.files;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+import static org.junit.Assume.*;\r
+\r
+import java.io.File;\r
+\r
+import org.junit.Test;\r
+\r
+public class ZipFileTest {\r
+\r
+ @Test\r
+ public void testIsZipFile() {\r
+ File jarFile = new File("lib/asm-debug-all-5.0.3.jar");\r
+ assumeThat(jarFile.exists(), is(true));\r
+ assertThat(ZipFile.isZipFile(jarFile), is(true));\r
+ \r
+ File sourceFile = new File("tests/soba/util/files/ZipFileTest.java");\r
+ assumeThat(sourceFile.exists(), is(true));\r
+ assertThat(ZipFile.isZipFile(sourceFile), is(false));\r
+ }\r
+\r
+ @Test\r
+ public void testIsClassFile() {\r
+ File classFile = new File("bin/soba/util/files/ZipFileTest.class");\r
+ assumeThat(classFile.exists(), is(true));\r
+ assertThat(ZipFile.isClassFile(classFile), is(true));\r
+ \r
+ File sourceFile = new File("tests/soba/util/files/ZipFileTest.java");\r
+ assumeThat(sourceFile.exists(), is(true));\r
+ assertThat(ZipFile.isClassFile(sourceFile), is(false));\r
+ }\r
+\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+public class DepthFirstSearchTest {\r
+\r
+ private DirectedGraph graph;\r
+ \r
+ /**\r
+ * Check the sequence of visited vertices.\r
+ */\r
+ private class VisitList implements IDepthFirstVisitor {\r
+\r
+ int[] visitList;\r
+ int[] leaveList;\r
+ boolean[] expectedVisited;\r
+ int visitIndex = 0;\r
+ int leaveIndex = 0;\r
+ \r
+ public VisitList(int[] visitList, int[] leaveList) {\r
+ this.visitList = visitList;\r
+ this.leaveList = leaveList;\r
+ this.expectedVisited = new boolean[graph.getVertexCount()];\r
+ for (int v: visitList) {\r
+ expectedVisited[v] = true;\r
+ }\r
+ for (int v: leaveList) {\r
+// Assert.assertTrue(expectedVisited[v]);\r
+ assertThat(expectedVisited[v], is(true));\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void onStart(int startVertexId) {\r
+ }\r
+ \r
+ @Override\r
+ public boolean onVisit(int vertexId) {\r
+ assertThat(vertexId, is(visitList[visitIndex]));\r
+ visitIndex++;\r
+ return continueVisit(vertexId);\r
+ }\r
+ \r
+ protected boolean continueVisit(int vertexId) {\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public void onLeave(int vertexId) {\r
+ assertThat(vertexId, is(leaveList[leaveIndex]));\r
+ leaveIndex++;\r
+ }\r
+ @Override\r
+ public void onFinished(boolean[] visited) {\r
+ for (int i=0; i<visited.length; ++i) {\r
+ assertThat(visited[i], is(expectedVisited[i]));\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void onVisitAgain(int vertexId) {\r
+ // The vertices must be visited\r
+ boolean visited = false;\r
+ for (int i=0; i<visitIndex; ++i) {\r
+ if (visitList[i] == vertexId) {\r
+ visited = true;\r
+ break;\r
+ }\r
+ }\r
+ assertThat(visited, is(true));\r
+ }\r
+ \r
+ }\r
+ \r
+ \r
+ /**\r
+ digraph {\r
+ 0 -> 1; 1 -> 2; 2 -> 0; 1 -> 3; 3 -> 4; 3 -> 5;\r
+ 5 -> 6; 6 -> 7; 6 -> 8; 7 -> 8; 8 -> 5; 7 -> 9;\r
+ 7 -> 10; 9 -> 11; 10 -> 11; 11 -> 12; 13;\r
+ }\r
+ */\r
+ @Before\r
+ public void buildGraph() throws Exception {\r
+ graph = GraphTestBase.buildGraph();\r
+ }\r
+ \r
+ @Test\r
+ public void testDFS() throws Exception {\r
+ DepthFirstSearch.search(graph, 7, new VisitList(new int[]{7, 8, 5, 6, 9, 11, 12, 10}, new int[]{6, 5, 8, 12, 11, 9, 10, 7}));\r
+ DepthFirstSearch.search(graph, 7, new VisitList(new int[] {7}, new int[] {7}) {\r
+ protected boolean continueVisit(int vertexId) {\r
+ return false;\r
+ };\r
+ });\r
+ DepthFirstSearch.search(graph, 0, new VisitList(new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, new int[] {2, 4, 8, 9, 10, 7, 6, 5, 3, 1, 0}) {\r
+ protected boolean continueVisit(int vertexId) {\r
+ return vertexId <= 7;\r
+ };\r
+ });\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+import soba.util.IntPairProc;\r
+import soba.util.UtilForAssertThat;\r
+\r
+public class DirectedAcyclicGraphTest {\r
+\r
+ DirectedGraph graph;\r
+ DirectedAcyclicGraph dag;\r
+ \r
+ @Before\r
+ public void buildGraph() throws Exception {\r
+ graph = GraphTestBase.buildGraph();\r
+ dag = new DirectedAcyclicGraph(graph);\r
+ }\r
+ \r
+ @Test\r
+ public void testAcyclicGraphNodes() {\r
+ assertThat(dag.getVertexCount(), is(graph.getVertexCount()));\r
+ assertThat(dag.getVertexCount(), is(graph.getVertexCount()));\r
+ assertThat(dag.isRepresentativeNode(0), is(true));\r
+ assertThat(dag.isRepresentativeNode(1), is(false));\r
+ assertThat(dag.isRepresentativeNode(2), is(false));\r
+ assertThat(dag.isRepresentativeNode(3), is(true));\r
+ assertThat(dag.isRepresentativeNode(4), is(true));\r
+ assertThat(dag.isRepresentativeNode(5), is(true));\r
+ assertThat(dag.isRepresentativeNode(6), is(false));\r
+ assertThat(dag.isRepresentativeNode(7), is(false));\r
+ assertThat(dag.isRepresentativeNode(8), is(false));\r
+ assertThat(dag.isRepresentativeNode(9), is(true));\r
+ assertThat(dag.isRepresentativeNode(10), is(true));\r
+ assertThat(dag.isRepresentativeNode(11), is(true));\r
+ assertThat(dag.isRepresentativeNode(12), is(true));\r
+ assertThat(dag.isRepresentativeNode(13), is(true));\r
+ }\r
+ \r
+ @Test\r
+ public void testAcyclicGraphEdges() {\r
+ dag.forEachEdge(new IntPairProc() {\r
+\r
+ int index = 0;\r
+ int[][] expected = new int[][] {\r
+ {0, 3}, {3, 4}, {3, 5}, {5, 9}, {5, 10},\r
+ {9, 11}, {10, 11}, {11, 12} };\r
+\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ assertThat(elem1, is(expected[index][0]));\r
+ assertThat(elem2, is(expected[index][1]));\r
+ index++;\r
+ return true;\r
+ }\r
+ });\r
+ \r
+ assertThat(UtilForAssertThat.asIntegerArray(dag.getEdges(13)), is(emptyArray()));\r
+ assertThat(UtilForAssertThat.asIntegerArray(dag.getEdges(3)), is(arrayContainingInAnyOrder(4, 5)));\r
+ }\r
+\r
+ \r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+import gnu.trove.list.array.TIntArrayList;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+import soba.util.UtilForAssertThat;\r
+\r
+public class DirectedGraphTest {\r
+ \r
+ private static DirectedGraph graph;\r
+ \r
+ @BeforeClass\r
+ public static void buildGraph() {\r
+ graph = GraphTestBase.buildGraph();\r
+ }\r
+ \r
+ \r
+ @Test\r
+ public void testDepthFirstSearch() throws Exception {\r
+ final TIntArrayList visited = new TIntArrayList();\r
+ DepthFirstSearch.search(graph, 0, new IDepthFirstVisitor() {\r
+ @Override\r
+ public void onStart(int startVertexId) {\r
+ }\r
+ @Override\r
+ public boolean onVisit(int vertexId) {\r
+ visited.add(vertexId);\r
+ return true;\r
+ }\r
+ @Override\r
+ public void onLeave(int vertexId) {\r
+ }\r
+ @Override\r
+ public void onFinished(boolean[] visited) {\r
+ assertThat(visited.length, is(14));\r
+ for (int i=0; i<13; ++i) {\r
+ assertThat(visited[i], is(true));\r
+ }\r
+ assertThat(visited[13], is(false));\r
+ }\r
+ @Override\r
+ public void onVisitAgain(int vertexId) {\r
+ assertThat(visited.contains(vertexId), is(true));\r
+ }\r
+ });\r
+\r
+ assertThat(visited.size(), is(13));\r
+ } \r
+ \r
+ @Test\r
+ public void testEdgeCount() {\r
+ assertThat(graph.getEdgeCount(), is(16));\r
+ }\r
+ \r
+ @Test\r
+ public void testReverseGraph() {\r
+ DirectedGraph r = graph.getReverseGraph();\r
+ assertThat(r.getVertexCount(), is(14));\r
+ assertThat(r.getEdgeCount(), is(16));\r
+ \r
+ // check all edges\r
+ Integer[] edgesFrom0 = UtilForAssertThat.asIntegerArray(r.getEdges(0));\r
+ assertThat(edgesFrom0, is(arrayContainingInAnyOrder(2)));\r
+\r
+ Integer[] edgesFrom1 = UtilForAssertThat.asIntegerArray(r.getEdges(1));\r
+ assertThat(edgesFrom1, is(arrayContainingInAnyOrder(0)));\r
+\r
+ Integer[] edgesFrom2 = UtilForAssertThat.asIntegerArray(r.getEdges(2));\r
+ assertThat(edgesFrom2, is(arrayContainingInAnyOrder(1)));\r
+\r
+ Integer[] edgesFrom3 = UtilForAssertThat.asIntegerArray(r.getEdges(3));\r
+ assertThat(edgesFrom3, is(arrayContainingInAnyOrder(1)));\r
+\r
+ Integer[] edgesFrom4 = UtilForAssertThat.asIntegerArray(r.getEdges(4));\r
+ assertThat(edgesFrom4, is(arrayContainingInAnyOrder(3)));\r
+ \r
+ Integer[] edgesFrom5 = UtilForAssertThat.asIntegerArray(r.getEdges(5));\r
+ assertThat(edgesFrom5, is(arrayContainingInAnyOrder(3, 8)));\r
+ \r
+ Integer[] edgesFrom6 = UtilForAssertThat.asIntegerArray(r.getEdges(6));\r
+ assertThat(edgesFrom6, is(arrayContainingInAnyOrder(5)));\r
+\r
+ Integer[] edgesFrom7 = UtilForAssertThat.asIntegerArray(r.getEdges(7));\r
+ assertThat(edgesFrom7, is(arrayContainingInAnyOrder(6)));\r
+ \r
+ Integer[] edgesFrom8 = UtilForAssertThat.asIntegerArray(r.getEdges(8));\r
+ assertThat(edgesFrom8, is(arrayContainingInAnyOrder(6, 7)));\r
+\r
+ Integer[] edgesFrom9 = UtilForAssertThat.asIntegerArray(r.getEdges(9));\r
+ assertThat(edgesFrom9, is(arrayContainingInAnyOrder(7)));\r
+ \r
+ Integer[] edgesFrom10 = UtilForAssertThat.asIntegerArray(r.getEdges(10));\r
+ assertThat(edgesFrom10, is(arrayContainingInAnyOrder(7)));\r
+\r
+ Integer[] edgesFrom11 = UtilForAssertThat.asIntegerArray(r.getEdges(11));\r
+ assertThat(edgesFrom11, is(arrayContainingInAnyOrder(9, 10)));\r
+\r
+ Integer[] edgesFrom12 = UtilForAssertThat.asIntegerArray(r.getEdges(12));\r
+ assertThat(edgesFrom12, is(arrayContainingInAnyOrder(11)));\r
+\r
+ Integer[] edgesFrom13 = UtilForAssertThat.asIntegerArray(r.getEdges(13));\r
+ assertThat(edgesFrom13, is(emptyArray()));\r
+ }\r
+ \r
+ @Test\r
+ public void testUndirectedGraph() {\r
+ DirectedGraph g = graph.getUndirectedGraph();\r
+ Integer[] edgesFrom1 = UtilForAssertThat.asIntegerArray(g.getEdges(1));\r
+ assertThat(edgesFrom1, is(arrayContainingInAnyOrder(0, 2, 3)));\r
+ \r
+ Integer[] edgesFrom10 = UtilForAssertThat.asIntegerArray(g.getEdges(10));\r
+ assertThat(edgesFrom10, is(arrayContainingInAnyOrder(7, 11)));\r
+ \r
+ Integer[] edgesFrom12 = UtilForAssertThat.asIntegerArray(g.getEdges(12));\r
+ assertThat(edgesFrom12, is(arrayContainingInAnyOrder(11)));\r
+ \r
+ Integer[] edgesFrom13 = UtilForAssertThat.asIntegerArray(g.getEdges(13));\r
+ assertThat(edgesFrom13, is(emptyArray()));\r
+ \r
+ assertThat(g.getEdgeCount(), is(32));\r
+ }\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+\r
+public class DominanceTreeTest {\r
+\r
+ private static DirectedGraph graph;\r
+ \r
+ @BeforeClass\r
+ public static void buildGraph() throws Exception {\r
+ graph = GraphTestBase.buildGraph();\r
+ }\r
+\r
+ @Test\r
+ public void testDominanceTree() {\r
+ SingleRootDirectedGraph g = new SingleRootDirectedGraph(graph);\r
+ DominanceTree tree = new DominanceTree(g);\r
+ \r
+ assertThat(tree.isRoot(g.getRootId()), is(true));\r
+ assertThat(tree.isRoot(0), is(false));\r
+ assertThat(tree.isRoot(13), is(false));\r
+ assertThat(tree.getDominator(0), is(14));\r
+ assertThat(tree.getDominator(1), is(0));\r
+ assertThat(tree.getDominator(2), is(1));\r
+ assertThat(tree.getDominator(3), is(1));\r
+ assertThat(tree.getDominator(4), is(3));\r
+ assertThat(tree.getDominator(5), is(3));\r
+ assertThat(tree.getDominator(6), is(5));\r
+ assertThat(tree.getDominator(7), is(6));\r
+ assertThat(tree.getDominator(8), is(6));\r
+ assertThat(tree.getDominator(9), is(7));\r
+ assertThat(tree.getDominator(10), is(7));\r
+ assertThat(tree.getDominator(11), is(7));\r
+ assertThat(tree.getDominator(12), is(11));\r
+ assertThat(tree.getDominator(13), is(14));\r
+ \r
+ assertThat(tree.nearestCommonAncestor(1, 13), is(14));\r
+ assertThat(tree.nearestCommonAncestor(5, 12), is(5));\r
+ assertThat(tree.nearestCommonAncestor(4, 12), is(3));\r
+ assertThat(tree.nearestCommonAncestor(12, 4), is(3));\r
+ assertThat(tree.nearestCommonAncestor(8, 6), is(6));\r
+ }\r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+\r
+import soba.util.IntPairList;\r
+\r
+public class GraphTestBase {\r
+\r
+ public static DirectedGraph buildGraph() { \r
+ IntPairList edges = new IntPairList();\r
+ edges.add(0, 1); // cycle 0->1->2\r
+ edges.add(1, 2);\r
+ edges.add(2, 0);\r
+ edges.add(1, 3);\r
+ \r
+ edges.add(3, 4);\r
+ edges.add(3, 5);\r
+ \r
+ edges.add(5, 6); // two cycles: 5->6->8->5\r
+ edges.add(6, 7); // and 5->6->7->8->5\r
+ edges.add(6, 8);\r
+ edges.add(7, 8);\r
+ edges.add(8, 5);\r
+\r
+ edges.add(7, 9);\r
+ edges.add(7, 10);\r
+ edges.add(9, 11);\r
+ edges.add(10, 11);\r
+ edges.add(11, 12); // 13 is not connected to any other vertices\r
+ \r
+ return new DirectedGraph(14, edges);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package soba.util.graph;\r
+\r
+import static org.junit.Assert.*;\r
+import static org.hamcrest.Matchers.*;\r
+\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+import soba.util.IntPairProc;\r
+\r
+\r
+public class SingleRootDirectedGraphTest {\r
+\r
+\r
+ private DirectedGraph graph;\r
+ \r
+ @Before\r
+ public void buildGraph() throws Exception {\r
+ graph = GraphTestBase.buildGraph();\r
+ }\r
+\r
+ @Test\r
+ public void testSingleRootDirectedGraph() throws Exception {\r
+ SingleRootDirectedGraph base = new SingleRootDirectedGraph(graph);\r
+ assertThat(base.getVertexCount(), is(15));\r
+ assertThat(base.getRootId(), is(14));\r
+ assertThat(base.getEdges(14).length, is(2));\r
+ assertThat(base.getEdges(14)[0], is(0));\r
+ assertThat(base.getEdges(14)[1], is(13));\r
+ }\r
+ \r
+ @Test\r
+ public void testEdges() throws Exception {\r
+ final SingleRootDirectedGraph base = new SingleRootDirectedGraph(graph);\r
+ base.forEachEdge(new IntPairProc() {\r
+ \r
+ int index = 0;\r
+ int[][] expected = new int[][] {\r
+ {0, 1}, {1, 2}, {1, 3}, {2, 0},\r
+ {3, 4}, {3, 5}, {5, 6}, {6, 7},\r
+ {6, 8}, {7, 8}, {7, 9}, {7, 10},\r
+ {8, 5}, {9, 11}, {10, 11}, {11, 12},\r
+ {14, 0}, {14, 13}\r
+ };\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ assertThat(elem1, is(expected[index][0]));\r
+ assertThat(elem2, is(expected[index][1]));\r
+ index++;\r
+ return true;\r
+ }\r
+ });\r
+ base.forEachEdge(new IntPairProc() {\r
+ private boolean first = true;\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ if (first) {\r
+ first = false;\r
+ return false;\r
+ } else {\r
+ fail();\r
+ return false;\r
+ }\r
+ }\r
+ });\r
+ base.forEachEdge(new IntPairProc() {\r
+ private boolean firstFromRoot = true;\r
+ @Override\r
+ public boolean execute(int elem1, int elem2) {\r
+ if (base.getRootId() == elem1) {\r
+ if (firstFromRoot) {\r
+ firstFromRoot = false;\r
+ return false;\r
+ } else {\r
+ fail();\r
+ return false;\r
+ }\r
+ } else {\r
+ return true;\r
+ }\r
+ }\r
+ });\r
+ }\r
+}\r