├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── graft │ │ ├── Banner.java │ │ ├── Config.java │ │ ├── Const.java │ │ ├── Graft.java │ │ ├── GraftException.java │ │ ├── GraftRuntimeException.java │ │ ├── Options.java │ │ ├── analysis │ │ ├── AliasAnalysis.java │ │ ├── GraftAnalysis.java │ │ └── TaintAnalysis.java │ │ ├── cpg │ │ ├── AstBuilder.java │ │ ├── CfgBuilder.java │ │ ├── CpgBuilder.java │ │ ├── CpgUtil.java │ │ ├── Interproc.java │ │ ├── PdgBuilder.java │ │ ├── structure │ │ │ └── CodePropertyGraph.java │ │ └── visitors │ │ │ ├── ConstantVisitor.java │ │ │ ├── ExprVisitor.java │ │ │ ├── RefVisitor.java │ │ │ ├── StmtVisitor.java │ │ │ └── TypeVisitor.java │ │ ├── db │ │ ├── GraphUtil.java │ │ ├── Neo4jUtil.java │ │ └── TinkerGraphUtil.java │ │ ├── traversal │ │ ├── CpgTraversalDsl.java │ │ └── CpgTraversalSourceDsl.java │ │ └── utils │ │ ├── DisplayUtil.java │ │ ├── DotUtil.java │ │ ├── FileUtil.java │ │ ├── LogUtil.java │ │ └── SootUtil.java └── resources │ ├── default.properties │ └── logback.xml └── test ├── java └── graft │ ├── TestGraft.java │ └── traversal │ ├── TestCpgTraversalDsl.java │ └── TestCpgTraversalSourceDsl.java └── resources └── simple.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Java stuff 2 | *.class 3 | *.jar 4 | *.zip 5 | *.tar.gz 6 | 7 | # Gradle stuff 8 | .gradle 9 | build/ 10 | out/ 11 | !gradle-wrapper.jar 12 | 13 | # IDE stuff 14 | *.iml 15 | .idea/ 16 | .vscode/ 17 | .settings/ 18 | .project 19 | .classpath 20 | 21 | # generated code 22 | src/main/generated 23 | bin/ 24 | 25 | # Graft folder 26 | .graft/ 27 | 28 | # other stuff 29 | etc/ 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graft 2 | 3 | > Keirsgieter, W., & Visser, W. (2020). Graft: Static Analysis of Java Bytecode with Graph Databases. Conference of the South African Institute of Computer Scientists and Information Technologists 2020. 4 | 5 | Graft is a static analysis tool for Java programs, based on the theory of *[code property graphs](https://www.sec.cs.tu-bs.de/pubs/2014-ieeesp.pdf)* (CPGs). 6 | 7 | The CPG is generated from Java bytecode and stored in a local graph database. 8 | The graph can be traversed to find various common security vulnerabilities, including taint-related vulnerabilities. 9 | 10 | A command line shell is also provided that allows the user to perform ad-hoc traversals on the CPG interactively. 11 | 12 | You are welcome to use this project for research purposes, but please remember to cite us! 13 | ``` 14 | @article{Keirsgieter2020GraftSA, 15 | title={Graft: Static Analysis of Java Bytecode with Graph Databases}, 16 | author={Wim Keirsgieter and Willem Visser}, 17 | journal={Conference of the South African Institute of Computer Scientists and Information Technologists 2020}, 18 | year={2020} 19 | } 20 | ``` 21 | 22 | ## Building and running Graft 23 | 24 | Graft is a Gradle project, and can be built by simply running `gradle build` (or `./gradlew build`). 25 | 26 | During the build, two executable scripts are generated: `graft` and `graft-shell`. All references to `graft` in the Usage section refer to the `graft` executable. 27 | `graft-shell` opens a Groovy shell on the Graft classpath (see `graft-shell` section in Usage). 28 | 29 | ## Usage 30 | 31 | Graft needs to be initialized within the project by running `graft init` before the CPG can be built and analysed. 32 | 33 | ### `graft init` 34 | 35 | This command initializes the Graft project by creating a `.graft` folder in the project's root directory. 36 | This folder contains a properties file with the project's configuration, and a `db` folder that stores the graph database. 37 | 38 | The user provides a name for the project, the project's target directory (where the Java classes are located) and classpath, and selects the graph database implementation. 39 | 40 | After initialization, the database contains only a single root node - the CPG can be built with `graft build`. 41 | 42 | ### `graft build` 43 | 44 | This command performs the initial construction of the CPG. 45 | If the database already contains a CPG, the user has the option of overwriting it. 46 | 47 | Construction of the CPG takes some time, and should be run only once after running `graft init` for the first time. 48 | If the program has changed and the CPG needs to be updated, the user should run `graft update` instead. 49 | 50 | ### `graft status` 51 | 52 | Print the status of the CPG - number of nodes and edges, as well as classes changed since the last update. 53 | 54 | ### `graft update` 55 | 56 | This command checks for changes in the target program that are not yet reflected in the CPG by comparing hash values of the class files. 57 | If some classes have been changed, they will be updated in the CPG without affecting the rest of the graph. 58 | 59 | This procedure is much faster than reconstructing the entire graph each time, and should probably be run after each incremental change in the program. 60 | 61 | ### `graft run ` 62 | 63 | This command can be used to run a predefined analysis on the graph. 64 | Two such analyses (`TaintAnalysis` and `AliasAnalysis`) are built in and can be run with `graft run taint` and `graft run alias` respectively. 65 | 66 | 67 | ### `graft dot ` 68 | 69 | This command prints the CPG out to the given file in dot format for visualisation. 70 | Not recommended for anything other than trivially small programs (this command is more useful for debugging really). 71 | 72 | ### `graft dump ` 73 | 74 | This command dumps the CPG to the given file for portability. Again not recommended for larger graphs. 75 | 76 | ### `graft-shell` 77 | 78 | This command opens the Graft shell. From here, the user can interactively inspect the CPG and run traversals on it. 79 | 80 | Example work flow: 81 | 82 | ```java 83 | import graft.cpg.structure.CodePropertyGraph 84 | import static graft.traversal.__.* 85 | import static graft.Const.* 86 | 87 | cpg = CodePropertyGraph.fromFile('') 88 | 89 | // get a list of all method entry nodes in the CPG 90 | entries = cpg.traversal().entries().toList() 91 | 92 | // get a list of all assignments to the variable 'x' 93 | assigns = cpg.traversal().getAssignStmts().where(getTgt().has(NAME, 'x')).toList() 94 | 95 | // dump the current CPG to a file 96 | cpg.dump('') 97 | 98 | // write the current CPG to a dotfile 99 | cpg.toDot('') 100 | ``` 101 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'za.ac.sun.cs' 6 | version '1.0' 7 | 8 | sourceCompatibility = 1.8 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | 16 | // annotation processors 17 | annotationProcessor 'org.apache.tinkerpop:gremlin-core:3.4.2' 18 | annotationProcessor 'org.neo4j:neo4j-tinkerpop-api-impl:0.7-3.2.3' 19 | testAnnotationProcessor 'org.apache.tinkerpop:gremlin-core:3.4.2' 20 | testAnnotationProcessor 'org.neo4j:neo4j-tinkerpop-api-impl:0.7-3.2.3' 21 | 22 | // Soot 23 | implementation 'ca.mcgill.sable:soot:3.3.0' 24 | 25 | // graph db drivers 26 | implementation 'org.apache.tinkerpop:gremlin-core:3.4.2' 27 | implementation 'org.apache.tinkerpop:tinkergraph-gremlin:3.4.2' 28 | implementation 'org.apache.tinkerpop:neo4j-gremlin:3.4.2' 29 | // implementation 'io.shiftleft:tinkergraph-gremlin:3.3.4.18' 30 | implementation 'org.neo4j:neo4j-tinkerpop-api-impl:0.7-3.2.3' 31 | 32 | // logging 33 | implementation 'org.slf4j:slf4j-api:1.6.6' 34 | implementation 'ch.qos.logback:logback-classic:1.0.13' 35 | 36 | // configuration 37 | implementation 'org.apache.commons:commons-configuration2:2.4' 38 | implementation 'commons-beanutils:commons-beanutils:1.9.3' 39 | 40 | // command line parsing 41 | implementation 'commons-cli:commons-cli:1.4' 42 | 43 | implementation 'org.codehaus.groovy:groovy-all:2.5.8' 44 | 45 | testImplementation 'junit:junit:4.12' 46 | } 47 | 48 | configurations.all { 49 | exclude module: 'slf4j-nop' 50 | } 51 | 52 | clean { 53 | delete += 'bin' 54 | delete += 'sootOutput' 55 | } 56 | 57 | build { 58 | 59 | doLast { 60 | // add script generation to build task 61 | // from https://stackoverflow.com/a/20106665 62 | mkdir 'bin' 63 | File shellScript = file('bin/graft-shell') 64 | shellScript.withPrintWriter { 65 | it.println '#!/bin/sh' 66 | it.println "groovysh -cp ${getRuntimeClasspath()}" 67 | } 68 | File startScript = file('bin/graft') 69 | startScript.withPrintWriter { 70 | it.println '#!/bin/sh' 71 | it.println "java -ea -cp ${getRuntimeClasspath()} graft.Graft \"\$@\"" 72 | } 73 | ant.chmod(file: shellScript.absolutePath, perm: 'u+x') 74 | ant.chmod(file: startScript.absolutePath, perm: 'u+x') 75 | } 76 | 77 | } 78 | 79 | String getRuntimeClasspath() { 80 | sourceSets.main.runtimeClasspath.collect { it.absolutePath }.join(':') 81 | } 82 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimkeir/graft/f0f8c669c62856b3d02c6f8bb7b605b86185937f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 08 16:00:05 SAST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'graft' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/graft/Banner.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | /** 4 | * Basic banner for terminal display. 5 | * 6 | * @author Wim Keirsgieter 7 | */ 8 | public class Banner { 9 | 10 | private static int DEFAULT_WIDTH = 100; 11 | 12 | private StringBuilder sb; 13 | private int width; 14 | 15 | // ******************************************************************************************** 16 | // constructors 17 | // ******************************************************************************************** 18 | 19 | public Banner() { 20 | this(DEFAULT_WIDTH); 21 | } 22 | 23 | public Banner(int width) { 24 | this(width, ""); 25 | } 26 | 27 | public Banner(String heading) { 28 | this(DEFAULT_WIDTH, heading); 29 | } 30 | 31 | public Banner(int width, String heading) { 32 | this.width = width; 33 | sb = new StringBuilder(); 34 | 35 | border(); 36 | println(); 37 | 38 | int lenHeading = heading.length(); 39 | if (lenHeading > 0) { 40 | println(heading); 41 | println(); 42 | } 43 | } 44 | 45 | // ******************************************************************************************** 46 | // instance methods 47 | // ******************************************************************************************** 48 | 49 | /** 50 | * Print an empty line to the banner. 51 | */ 52 | public void println() { 53 | sb.append(String.format("| %1$-" + (width - 4) + "s |\n", " ")); 54 | } 55 | 56 | /** 57 | * Print the given string to the banner. 58 | * 59 | * @param s the string to print 60 | */ 61 | public void println(String s) { 62 | sb.append(String.format("| %1$-" + (width - 4) + "s |\n", s)); 63 | } 64 | 65 | /** 66 | * Display the banner to stdout. 67 | */ 68 | public void display() { 69 | println(); 70 | border(); 71 | System.out.println(sb.toString()); 72 | } 73 | 74 | private void border() { 75 | for (int i = 0; i < width; i++) { 76 | sb.append('='); 77 | } 78 | sb.append('\n'); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/graft/Config.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | import java.io.*; 4 | import java.net.URISyntaxException; 5 | import java.net.URL; 6 | import java.nio.file.Path; 7 | import java.util.Iterator; 8 | 9 | import org.apache.commons.configuration.Configuration; 10 | import org.apache.commons.configuration.ConfigurationException; 11 | import org.apache.commons.configuration.PropertiesConfiguration; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import static graft.Const.*; 17 | 18 | /** 19 | * A wrapper around the Apache Commons Configuration, with helpful static methods. 20 | * 21 | * @author Wim Keirsgieter 22 | */ 23 | public class Config { 24 | 25 | private static Logger log = LoggerFactory.getLogger(Config.class); 26 | 27 | private Configuration configuration; 28 | 29 | // ******************************************************************************************** 30 | // private constructors 31 | // ******************************************************************************************** 32 | 33 | private Config(Configuration configuration) { 34 | this.configuration = configuration; 35 | } 36 | 37 | // ******************************************************************************************** 38 | // instance methods 39 | // ******************************************************************************************** 40 | 41 | /** 42 | * Create a copy of the Config object. 43 | * 44 | * @return a copy of the Config object 45 | */ 46 | public Config copy() { 47 | Configuration copy = new PropertiesConfiguration(); 48 | Iterator keys = configuration.getKeys(); 49 | while (keys.hasNext()) { 50 | String key = keys.next(); 51 | copy.addProperty(key, configuration.getProperty(key)); 52 | } 53 | return new Config(copy); 54 | } 55 | 56 | /** 57 | * Combine this Config with another Config object, overwriting any duplicate fields with values 58 | * from the other Config. 59 | * 60 | * @param other the Config object to combine with this one 61 | */ 62 | public void combine(Config other) { 63 | Iterator keys = other.keys(); 64 | while (keys.hasNext()) { 65 | String key = keys.next(); 66 | configuration.setProperty(key, other.getProperty(key)); 67 | } 68 | } 69 | 70 | /** 71 | * Get an Iterator of the keys in this Config object. 72 | * 73 | * @return an Iterator of property keys 74 | */ 75 | public Iterator keys() { 76 | return configuration.getKeys(); 77 | } 78 | 79 | /** 80 | * Check whether this Config contains the given key. 81 | * 82 | * @param key the key to check for membership 83 | * @return true if the Config contains a value for the key, else false 84 | */ 85 | public boolean containsKey(String key) { 86 | return configuration.containsKey(key); 87 | } 88 | 89 | // property access methods 90 | 91 | /** 92 | * Returns the given property if it exists. 93 | * 94 | * @param key the property key 95 | * @return the property value 96 | */ 97 | public Object getProperty(String key) { 98 | checkContains(key); 99 | return configuration.getProperty(key); 100 | } 101 | 102 | /** 103 | * Set the given property value. 104 | * 105 | * @param key the property key 106 | * @param value the property value 107 | */ 108 | public void setProperty(String key, Object value) { 109 | configuration.setProperty(key, value); 110 | } 111 | 112 | /** 113 | * Get the given string property if it exists. 114 | * 115 | * @param key the property key 116 | * @return the property value 117 | */ 118 | public String getString(String key) { 119 | checkContains(key); 120 | return configuration.getString(key); 121 | } 122 | 123 | /** 124 | * Debug the properties of the Config object. 125 | */ 126 | public void debug() { 127 | if (log.isDebugEnabled()) { 128 | log.debug("Running with options:"); 129 | Iterator keys = keys(); 130 | while (keys.hasNext()) { 131 | String key = keys.next(); 132 | log.debug(" - {}: {}", key, getProperty(key)); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Write the Config object to a properties file. 139 | * 140 | * @param path the path to the file 141 | */ 142 | public void toFile(Path path) { 143 | try { 144 | toFile(path.toFile()); 145 | } catch (IOException e) { 146 | throw new GraftRuntimeException("Cannot write config to file '" + path.toFile().getName() + "'", e); 147 | } 148 | } 149 | 150 | private void toFile(File file) throws IOException { 151 | if (file.exists()) { 152 | log.warn("File '{}' already exists, overwriting", file.getName()); 153 | } 154 | 155 | FileWriter out = new FileWriter(file); 156 | out.write(PROPERTIES_HEADER); 157 | 158 | Iterator keys = keys(); 159 | while (keys.hasNext()) { 160 | String key = keys.next(); 161 | out.write(key + " = " + getProperty(key) + "\n"); 162 | } 163 | 164 | out.close(); 165 | } 166 | 167 | private void checkContains(String key) { 168 | if (!containsKey(key)) { 169 | throw new GraftRuntimeException("No value set for key '" + key + "'"); 170 | } 171 | } 172 | 173 | // ******************************************************************************************** 174 | // public static methods 175 | // ******************************************************************************************** 176 | 177 | /** 178 | * Get the default configuration. 179 | * 180 | * @return the default configuration 181 | */ 182 | static Config getDefault() { 183 | URL url = ClassLoader.getSystemClassLoader().getResource(DEFAULT_CONFIG_RESOURCE); 184 | assert url != null; 185 | try { 186 | File file = new File(url.toURI()); 187 | return fromFile(file); 188 | } catch (URISyntaxException| GraftRuntimeException e) { 189 | throw new GraftRuntimeException("Could not load default config file"); 190 | } 191 | } 192 | 193 | /** 194 | * Load a configuration from a properties file. 195 | * 196 | * @param file the properties file 197 | * @return the configuration 198 | */ 199 | static Config fromFile(File file) { 200 | try { 201 | Configuration configuration = new PropertiesConfiguration(file); 202 | return new Config(configuration); 203 | } catch (ConfigurationException e) { 204 | throw new GraftRuntimeException("Cannot read config file '" + file.getName() + "'"); 205 | } 206 | } 207 | 208 | /** 209 | * Load a configuration from a properties file. 210 | * 211 | * @param filename the properties file 212 | * @return the configuration 213 | */ 214 | static Config fromFile(String filename) { 215 | File file = new File(filename); 216 | if (!file.exists()) { 217 | throw new GraftRuntimeException("No such config file '" + filename + "'"); 218 | } 219 | if (!file.isFile()) { 220 | throw new GraftRuntimeException("Cannot open config file '" + filename + "'"); 221 | } 222 | return fromFile(file); 223 | } 224 | 225 | /** 226 | * Load a configuration from a properties file with defaults. 227 | * 228 | * @param filename the properties file 229 | * @return the configuration 230 | */ 231 | static Config fromFileWithDefaults(String filename) { 232 | Config def = getDefault(); 233 | Config conf = fromFile(filename); 234 | def.combine(conf); 235 | return def; 236 | } 237 | 238 | // ******************************************************************************************** 239 | // overridden Object methods 240 | // ******************************************************************************************** 241 | 242 | @Override 243 | public String toString() { 244 | StringBuilder sb = new StringBuilder(); 245 | sb.append("Config:\n"); 246 | 247 | Iterator keys = keys(); 248 | while (keys.hasNext()) { 249 | String key = keys.next(); 250 | sb.append(key).append(": ").append(getProperty(key)).append("\n"); 251 | } 252 | 253 | return sb.toString(); 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /src/main/java/graft/Const.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | /** 4 | * Various constants used throughout the program. 5 | * 6 | * @author Wim Keirsgieter 7 | */ 8 | public class Const { 9 | 10 | public static final String PROPERTIES_HEADER = "# Auto-generated properties file\n# Do not edit this file\n\n"; 11 | 12 | public static final String DEFAULT_CONFIG_RESOURCE = "default.properties"; 13 | 14 | // ******************************************************************************************** 15 | // general constants 16 | // ******************************************************************************************** 17 | 18 | /** 19 | * The maximum size of a call string context. 20 | */ 21 | public static final int K_LIMIT = 100; 22 | 23 | public static final String CPG_ROOT = "cpg-root"; 24 | 25 | public static final String GRAFT_DIR_NAME = ".graft"; 26 | public static final String DB_FOLDER_NAME = "db"; 27 | public static final String DB_FILE_NAME = "cpg"; 28 | public static final String PROPERTIES_FILE_NAME = "graft.properties"; 29 | 30 | // CLI commands 31 | public static final String CMD_INIT = "init"; 32 | public static final String CMD_BUILD = "build"; 33 | public static final String CMD_UPDATE = "update"; 34 | public static final String CMD_RUN = "run"; 35 | public static final String CMD_SHELL = "shell"; 36 | public static final String CMD_DOT = "dot"; 37 | public static final String CMD_DUMP = "dump"; 38 | public static final String CMD_STATUS = "status"; 39 | 40 | // analyses 41 | public static final String ALIAS_ANALYSIS = "alias"; 42 | public static final String TAINT_ANALYSIS = "taint"; 43 | 44 | // project metadata 45 | public static final String PROJECT_NAME = "project-name"; 46 | public static final String TARGET_DIR = "target-dir"; 47 | public static final String CLASSPATH = "classpath"; 48 | 49 | // regular expressions 50 | public static final String CLASS_FILE_REGEX = "[a-zA-Z_$]+[a-zA-Z_1-9$]*.class"; 51 | public static final String PROJECT_NAME_REGEX = "[a-zA-Z0-9_-]+"; 52 | 53 | // defaults 54 | public static final String DEFAULT_DB_DIRECTORY = ".db"; 55 | 56 | // option keys 57 | public static final String OPT_GENERAL_LOG_LEVEL = "general.log-level"; 58 | public static final String OPT_DB_DIRECTORY = "db.directory"; 59 | public static final String OPT_DB_FILE = "db.file"; 60 | public static final String OPT_DB_FILE_FORMAT = "db.file-format"; 61 | public static final String OPT_DB_IMPLEMENTATION = "db.implementation"; 62 | public static final String OPT_PROJECT_NAME = "project-name"; 63 | public static final String OPT_CLASSPATH = "soot.options.classpath"; 64 | public static final String OPT_TAINT_SOURCE = "taint.source"; 65 | public static final String OPT_TAINT_SINK = "taint.sink"; 66 | public static final String OPT_TAINT_SANITIZER = "taint.sanitizer"; 67 | public static final String OPT_TARGET_DIR = "target-dir"; 68 | 69 | // log levels 70 | public static final String TRACE = "trace"; 71 | public static final String DEBUG = "debug"; 72 | public static final String INFO = "info"; 73 | public static final String WARN = "warn"; 74 | public static final String ERROR = "error"; 75 | public static final String ALL = "all"; 76 | 77 | // invoke types 78 | public static final String DYNAMIC = "dynamic"; 79 | public static final String INTERFACE = "interface"; 80 | public static final String SPECIAL = "special"; 81 | public static final String STATIC = "static"; 82 | public static final String VIRTUAL = "virtual"; 83 | 84 | // miscellaneous 85 | public static final String NONE = ""; 86 | public static final String UNKNOWN = ""; 87 | public static final String TRUE = "true"; 88 | public static final String FALSE = "false"; 89 | 90 | // general property keys 91 | public static final String NODE_TYPE = "node-type"; 92 | public static final String EDGE_TYPE = "edge-type"; 93 | public static final String TEXT_LABEL = "text-label"; 94 | 95 | // graph implementations 96 | public static final String TINKERGRAPH = "tinkergraph"; 97 | public static final String NEO4J = "neo4j"; 98 | 99 | // ******************************************************************************************** 100 | // control flow graphs 101 | // ******************************************************************************************** 102 | 103 | public static final String CFG_NODE = "cfg-node"; 104 | public static final String CFG_EDGE = "cfg-edge"; 105 | 106 | // CFG node types 107 | public static final String ENTRY = "entry"; 108 | public static final String ASSIGN_STMT = "assign-stmt"; 109 | public static final String BREAKPOINT_STMT = "breakpoint-stmt"; 110 | public static final String CONDITIONAL_STMT = "conditional-stmt"; 111 | public static final String ENTER_MONITOR_STMT = "enter-monitor-stmt"; 112 | public static final String EXIT_MONITOR_STMT = "exit-monitor-stmt"; 113 | public static final String INVOKE_STMT = "invoke-stmt"; 114 | public static final String LOOKUP_SWITCH_STMT = "lookup-switch-stmt"; 115 | public static final String RETURN_STMT = "return-stmt"; 116 | public static final String TABLE_SWITCH_STMT = "table-switch-stmt"; 117 | public static final String THROW_STMT = "throw-stmt"; 118 | 119 | // CFG edge types 120 | public static final String EMPTY = "E"; 121 | public static final String CALL = "call"; 122 | public static final String RET = "ret"; 123 | 124 | // CFG node property keys 125 | public static final String METHOD_NAME = "method-name"; 126 | public static final String METHOD_SIG = "method-sig"; 127 | public static final String SRC_LINE_NO = "line-no"; 128 | 129 | // CFG edge property keys 130 | public static final String CONTEXT = "context"; 131 | public static final String INTERPROC = "interproc"; 132 | 133 | // CFG edge property values 134 | public static final String DEFAULT_TARGET = "default-target"; 135 | 136 | // ******************************************************************************************** 137 | // abstract syntax trees 138 | // ******************************************************************************************** 139 | 140 | public static final String AST_NODE = "ast-node"; 141 | public static final String AST_EDGE = "ast-edge"; 142 | 143 | // AST node types 144 | public static final String CAST_EXPR = "cast"; 145 | public static final String CLASS = "class"; 146 | public static final String CONSTANT = "literal"; 147 | public static final String LOCAL_VAR = "local-var"; 148 | public static final String BINARY_EXPR = "binary-expr"; 149 | public static final String INSTANCEOF_EXPR = "instanceof"; 150 | public static final String INVOKE_EXPR = "invoke-expr"; 151 | public static final String NEW_ARRAY_EXPR = "new-array-expr"; 152 | public static final String NEW_EXPR = "new-expr"; 153 | public static final String PACKAGE = "package"; 154 | public static final String UNARY_EXPR = "unary-expr"; 155 | 156 | // AST edge types 157 | public static final String ARG = "arg"; 158 | public static final String BASE = "base"; 159 | public static final String CONDITION = "condition"; 160 | public static final String CONSTRUCTOR = "constructor"; 161 | public static final String EXPR = "expr"; 162 | public static final String LEFT_OPERAND = "left-op"; 163 | public static final String METHOD = "method"; 164 | public static final String MONITOR = "monitor"; 165 | public static final String OPERAND = "operand"; 166 | public static final String RIGHT_OPERAND = "right-op"; 167 | public static final String RETURNS = "returns"; 168 | public static final String SIZE = "size"; 169 | public static final String STATEMENT = "statement"; 170 | public static final String SWITCH_KEY = "switch-key"; 171 | public static final String TARGET = "target"; 172 | public static final String THROWS = "throws"; 173 | public static final String VALUE = "value"; 174 | 175 | // AST node property keys 176 | public static final String BASE_TYPE = "base-type"; 177 | public static final String CAST_TYPE = "cast-type"; 178 | public static final String CHECK_TYPE = "check-type"; 179 | public static final String EXPR_TYPE = "expr-type"; 180 | public static final String FIELD_NAME = "field-name"; 181 | public static final String FIELD_SIG = "field-sig"; 182 | public static final String FIELD_REF_TYPE = "field-ref-type"; 183 | public static final String FULL_NAME = "full-name"; 184 | public static final String INVOKE_TYPE = "invoke-type"; 185 | public static final String JAVA_TYPE = "java-type"; 186 | public static final String NAME = "name"; 187 | public static final String NEW_EXPR_TYPE = "new-expr-type"; 188 | public static final String OPERATOR = "operator"; 189 | public static final String SHORT_NAME = "short-name"; 190 | public static final String FILE_PATH = "file-path"; 191 | public static final String FILE_NAME = "file-name"; 192 | public static final String FILE_HASH = "file-hash"; 193 | public static final String PACKAGE_NAME = "package-name"; 194 | public static final String REF_TYPE = "ref-type"; 195 | 196 | // AST edge property keys 197 | public static final String INDEX = "index"; 198 | public static final String DIM = "dim"; 199 | 200 | // primitive types and literals 201 | public static final String BOOLEAN = "boolean"; 202 | public static final String BYTE = "byte"; 203 | public static final String CHAR = "char"; 204 | public static final String DOUBLE = "double"; 205 | public static final String FLOAT = "float"; 206 | public static final String INT = "int"; 207 | public static final String LONG = "long"; 208 | public static final String NULL = "null"; 209 | public static final String STRING = "string"; 210 | public static final String SHORT = "short"; 211 | public static final String VOID = "void"; 212 | 213 | // reference types 214 | public static final String REF = "ref"; 215 | public static final String ARRAY_REF = "array-ref"; 216 | public static final String EXCEPTION_REF = "exception-ref"; 217 | public static final String FIELD_REF = "field-ref"; 218 | public static final String INSTANCE_FIELD_REF = "inst-field-ref"; 219 | public static final String PARAM_REF = "param-ref"; 220 | public static final String STATIC_FIELD_REF = "field-ref"; 221 | public static final String THIS_REF = "this-ref"; 222 | 223 | // binary operators 224 | public static final String AND = "and"; 225 | public static final String CMP = "cmp"; 226 | public static final String CMPG = "cmpg"; 227 | public static final String CMPL = "cmpl"; 228 | public static final String DIVIDE = "div"; 229 | public static final String EQUALS = "eq"; 230 | public static final String GREATER = "gt"; 231 | public static final String GREATER_EQUALS = "ge"; 232 | public static final String LEFT_SHIFT = "left-shift"; 233 | public static final String LESS = "lt"; 234 | public static final String LESS_EQUALS = "le"; 235 | public static final String MINUS = "minus"; 236 | public static final String MULTIPLY = "multiply"; 237 | public static final String NOT_EQUALS = "ne"; 238 | public static final String OR = "or"; 239 | public static final String PLUS = "plus"; 240 | public static final String REMAINDER = "rem"; 241 | public static final String SIGNED_RIGHT_SHIFT = "signed-right-shift"; 242 | public static final String UNSIGNED_RIGHT_SHIFT = "unsigned-right-shift"; 243 | public static final String XOR = "xor"; 244 | 245 | // unary operators 246 | public static final String LENGTH = "length"; 247 | public static final String NEGATION = "neg"; 248 | 249 | // ******************************************************************************************** 250 | // program dependence graph 251 | // ******************************************************************************************** 252 | 253 | public static final String PDG_EDGE = "pdg-edge"; 254 | 255 | // PDG edge types 256 | public static final String ARG_DEP = ""; 257 | public static final String DATA_DEP = "data-dep"; 258 | public static final String CONTROL_DEP = "control-dep"; 259 | public static final String RET_DEP = ""; 260 | 261 | // PDG edge property keys 262 | public static final String VAR_NAME = "var-name"; 263 | 264 | // ******************************************************************************************** 265 | // alias analysis 266 | // ******************************************************************************************** 267 | 268 | public static final String MAY_ALIAS = "may-alias"; 269 | 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/graft/Graft.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | import java.io.File; 4 | import java.io.InputStreamReader; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.List; 8 | import java.util.Scanner; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import graft.analysis.AliasAnalysis; 14 | import graft.analysis.GraftAnalysis; 15 | import graft.analysis.TaintAnalysis; 16 | 17 | import graft.cpg.CpgBuilder; 18 | import graft.cpg.structure.CodePropertyGraph; 19 | 20 | import graft.utils.LogUtil; 21 | import graft.utils.SootUtil; 22 | 23 | import static graft.Const.*; 24 | import static graft.db.GraphUtil.*; 25 | 26 | /** 27 | * Graft is a code property graph analysis tool for Java programs. 28 | * 29 | * See the README for more information. 30 | * 31 | * @author Wim Keirsgieter 32 | */ 33 | public class Graft { 34 | 35 | private static Logger log = LoggerFactory.getLogger(Graft.class); 36 | 37 | private static final Path WORKING_DIR = Paths.get("").toAbsolutePath(); 38 | private static final Path GRAFT_DIR = WORKING_DIR.resolve(GRAFT_DIR_NAME); 39 | private static final Path DB_FOLDER = GRAFT_DIR.resolve(DB_FOLDER_NAME); 40 | private static final Path PROPERTIES_FILE = GRAFT_DIR.resolve(PROPERTIES_FILE_NAME); 41 | 42 | /** 43 | * The project CPG instance. 44 | */ 45 | private static CodePropertyGraph cpg; 46 | 47 | /** 48 | * Get a reference to the project CPG. 49 | * 50 | * @return a reference to the project CPG. 51 | */ 52 | public static CodePropertyGraph cpg() { 53 | if (cpg == null) { 54 | throw new GraftRuntimeException("CPG not initialised"); 55 | } 56 | return cpg; 57 | } 58 | 59 | private static void userOpts() { 60 | Scanner in = new Scanner(new InputStreamReader(System.in)); 61 | 62 | System.out.print("1. Project name: "); 63 | String name = in.next(); 64 | while (!name.matches(PROJECT_NAME_REGEX)) { 65 | System.out.println("Invalid project name '" + name + "'"); 66 | System.out.print("1. Project name: "); 67 | name = in.next(); 68 | } 69 | 70 | System.out.print("2. Target directory: "); 71 | String targetDir = in.next(); 72 | 73 | System.out.print("3. Classpath: "); 74 | String classpath = in.next(); 75 | 76 | System.out.print("4. Database [tinkergraph, neo4j]: "); 77 | String impl = in.next(); 78 | while (!(impl.equals(TINKERGRAPH) || impl.equals(NEO4J))) { 79 | System.out.println("Invalid database implementation '" + impl + "'"); 80 | System.out.print("4. Database [tinkergraph, neo4j]: "); 81 | impl = in.next(); 82 | } 83 | 84 | Options.v().setProperty(OPT_PROJECT_NAME, name); 85 | Options.v().setProperty(OPT_TARGET_DIR, targetDir); 86 | Options.v().setProperty(OPT_CLASSPATH, classpath); 87 | Options.v().setProperty(OPT_DB_IMPLEMENTATION, impl); 88 | } 89 | 90 | private static void initNewDb() { 91 | checkOrExit(DB_FOLDER.toFile().mkdir(), "Could not create DB folder"); 92 | 93 | switch (Options.v().getString(OPT_DB_IMPLEMENTATION)) { 94 | case TINKERGRAPH: 95 | String dbFile = DB_FOLDER.resolve(dbFileName()).toString(); 96 | Options.v().setProperty(OPT_DB_FILE, dbFile); 97 | cpg = newTinkergraphCpg(); 98 | break; 99 | case NEO4J: 100 | String dbDir = DB_FOLDER.toString(); 101 | Options.v().setProperty(OPT_DB_DIRECTORY, dbDir); 102 | cpg = newNeo4jCpg(dbDir); 103 | break; 104 | default: 105 | log.error("Cannot initialise new database"); 106 | throw new GraftRuntimeException( 107 | "Unrecognised database implementation '" + 108 | Options.v().getString(OPT_DB_IMPLEMENTATION) + "'"); 109 | } 110 | } 111 | 112 | // ******************************************************************************************** 113 | // commands 114 | // ******************************************************************************************** 115 | 116 | /** 117 | * Initialise a new Graft project in the current working directory. 118 | * 119 | * The user is prompted for a project name, target directory, and project classpath. They are 120 | * also given an option to choose the database implementation to use (default Tinkergraph). 121 | * 122 | * A Graft folder is created in the current directory with contents: 123 | * - graft.properties (stores the project configuration - should not be edited) 124 | * - db/ (the CPG is persisted here) 125 | * 126 | * If using Neo4j, the db folder will contain the Neo4j database binaries. If using Tinkergraph, 127 | * the graph will be read from and written to a file stored in the db folder (the file format 128 | * is configurable, default JSON). 129 | * 130 | * This command will fail if there is already a Graft folder in the current directory. 131 | */ 132 | private static void init() { 133 | checkOrExit(!GRAFT_DIR.toFile().exists(), "Graft folder already exists"); 134 | 135 | // create Graft folder (exit if unsuccessful) 136 | checkOrExit(GRAFT_DIR.toFile().mkdir(), "Could not create Graft folder"); 137 | log.info("Created Graft folder in directory '{}'", WORKING_DIR); 138 | 139 | // load default configuration 140 | initOpts(); 141 | 142 | // prompt user for project options 143 | userOpts(); 144 | 145 | // set up new database 146 | initNewDb(); 147 | 148 | // write project configuration to properties file 149 | Options.v().debug(); 150 | Options.v().toFile(PROPERTIES_FILE); 151 | 152 | Banner banner = new Banner("New Graft project: " + Options.v().getString(OPT_PROJECT_NAME)); 153 | banner.println("Target directory: " + Options.v().getString(OPT_TARGET_DIR)); 154 | banner.println("Database: " + Options.v().getString(OPT_DB_IMPLEMENTATION)); 155 | banner.display(); 156 | 157 | shutdown(); 158 | } 159 | 160 | /** 161 | * Build the initial project CPG and persist it to the database. 162 | * 163 | * If the database already contains a CPG, it can be optionally overwritten. 164 | */ 165 | private static void build() { 166 | startup(); 167 | 168 | String targetDir = Options.v().getString(OPT_TARGET_DIR); 169 | checkOrExit(1, targetDir != null, "Target directory not set"); 170 | CpgBuilder cpgBuilder; 171 | 172 | if (cpg.traversal().V().count().next() > 1) { 173 | System.out.println("CPG already exists for project '" + Options.v().getString(OPT_PROJECT_NAME) + "'..."); 174 | System.out.println("Use 'graft update' instead"); 175 | shutdown(); 176 | } 177 | 178 | cpgBuilder = new CpgBuilder(); 179 | cpgBuilder.buildCpg(targetDir); 180 | 181 | shutdown(); 182 | } 183 | 184 | private static void update() { 185 | startup(); 186 | CpgBuilder cpgBuilder = new CpgBuilder(); 187 | cpgBuilder.amendCpg(); 188 | shutdown(); 189 | } 190 | 191 | private static void runAnalysis(String analysisClass) { 192 | startup(); 193 | 194 | GraftAnalysis analysis; 195 | switch (analysisClass) { 196 | case TAINT_ANALYSIS: 197 | analysis = new TaintAnalysis("etc/taint.groovy"); 198 | break; 199 | case ALIAS_ANALYSIS: 200 | analysis = new AliasAnalysis(); 201 | break; 202 | default: 203 | // TODO: try and load class dynamically 204 | throw new RuntimeException("Unrecognised analysis class"); 205 | } 206 | 207 | analysis.doAnalysis(); 208 | shutdown(); 209 | } 210 | 211 | private static void dot(String filename) { 212 | startup(); 213 | cpg().toDot(filename); 214 | shutdown(); 215 | } 216 | 217 | private static void dump(String filename) { 218 | startup(); 219 | cpg().dump(filename); 220 | shutdown(); 221 | } 222 | 223 | private static void status() { 224 | startup(); 225 | cpg().status().display(); 226 | shutdown(); 227 | } 228 | 229 | private static void startup() { 230 | checkOrExit(GRAFT_DIR.toFile().exists(), "Directory is not a Graft project"); 231 | checkOrExit(PROPERTIES_FILE.toFile().exists(), "No properties file in Graft directory"); 232 | initOpts(PROPERTIES_FILE.toString()); 233 | cpg = getCpg(); 234 | SootUtil.configureSoot(); 235 | 236 | if (log.isDebugEnabled()) { 237 | long V = cpg.traversal().V().count().next(); 238 | log.debug("Node count: {}", V); 239 | long E = cpg.traversal().E().count().next(); 240 | log.debug("Edge count: {}", E); 241 | } 242 | 243 | List amendedClasses = CpgBuilder.amendedClasses(); 244 | if (amendedClasses.size() != 0 && cpg.nrV() > 1) { 245 | log.info("{} classes changed since CPG construction", amendedClasses.size()); 246 | log.info("Run 'graft update' to update CPG"); 247 | } 248 | } 249 | 250 | private static void shutdown() { 251 | assert cpg != null; 252 | cpg.commit(); 253 | if (Options.v().getString(OPT_DB_IMPLEMENTATION).equals(TINKERGRAPH)) { 254 | String graphFile = Options.v().getString(OPT_DB_FILE); 255 | assert graphFile != null; 256 | cpg.dump(graphFile); 257 | } 258 | cpg.close(); 259 | System.exit(0); 260 | } 261 | 262 | private static void initOpts() { 263 | Config config = Config.getDefault(); 264 | Options.init(config); 265 | LogUtil.setLogLevel(Options.v().getString(OPT_GENERAL_LOG_LEVEL)); 266 | } 267 | 268 | private static void initOpts(String configFile) { 269 | Config config = Config.fromFileWithDefaults(configFile); 270 | Options.init(config); 271 | LogUtil.setLogLevel(Options.v().getString(OPT_GENERAL_LOG_LEVEL)); 272 | } 273 | 274 | private static void checkOrExit(boolean condition, String fmt, Object ... args) { 275 | checkOrExit(1, condition, fmt, args); 276 | } 277 | 278 | private static void checkOrExit(int code, boolean condition, String fmt, Object ... args) { 279 | if (!condition) { 280 | log.error(fmt, args); 281 | System.exit(code); 282 | } 283 | } 284 | 285 | private static String dbFileName() { 286 | String format = Options.v().getString(OPT_DB_FILE_FORMAT); 287 | switch (format) { 288 | case "graphson": 289 | case "json": 290 | return Options.v().getString(OPT_PROJECT_NAME) + ".json"; 291 | case "xml": 292 | case "graphml": 293 | return Options.v().getString(OPT_PROJECT_NAME) + ".xml"; 294 | case "kryo": 295 | return Options.v().getString(OPT_PROJECT_NAME) + ".kryo"; 296 | default: 297 | throw new GraftRuntimeException("Unrecognised DB file format '" + format); 298 | } 299 | } 300 | 301 | public static void main(String[] args) { 302 | checkOrExit(0, args.length >= 1, "Invalid command line arguments"); 303 | 304 | String cmd = args[0]; 305 | switch (cmd) { 306 | case CMD_INIT: 307 | init(); 308 | break; 309 | case CMD_BUILD: 310 | build(); 311 | break; 312 | case CMD_UPDATE: 313 | update(); 314 | break; 315 | case CMD_RUN: 316 | if (args.length < 2) { 317 | log.error("No analysis specified"); 318 | System.exit(0); 319 | } 320 | runAnalysis(args[1]); 321 | break; 322 | case CMD_DOT: 323 | checkOrExit(0, args.length == 2, "No dotfile provided"); 324 | dot(args[1]); 325 | break; 326 | case CMD_DUMP: 327 | checkOrExit(0, args.length == 2, "No dump file provided"); 328 | dump(args[1]); 329 | break; 330 | case CMD_STATUS: 331 | status(); 332 | break; 333 | default: 334 | log.error("Unrecognised command '{}'", cmd); 335 | System.exit(1); 336 | } 337 | } 338 | 339 | } 340 | -------------------------------------------------------------------------------- /src/main/java/graft/GraftException.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | public class GraftException extends Exception { 4 | 5 | private String message; 6 | private Throwable cause; 7 | 8 | public GraftException(String message) { 9 | this.message = message; 10 | this.cause = null; 11 | } 12 | 13 | public GraftException(String message, Throwable cause) { 14 | this.message = message; 15 | this.cause = cause; 16 | } 17 | 18 | public String getMessage() { 19 | return message; 20 | } 21 | 22 | public Throwable getCause() { 23 | return cause; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/graft/GraftRuntimeException.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | public class GraftRuntimeException extends RuntimeException { 4 | 5 | private String message; 6 | private Throwable cause; 7 | 8 | public GraftRuntimeException() { 9 | this(""); 10 | } 11 | 12 | public GraftRuntimeException(String message) { 13 | this.message = message; 14 | this.cause = null; 15 | } 16 | 17 | public GraftRuntimeException(String message, Throwable cause) { 18 | this.message = message; 19 | this.cause = cause; 20 | } 21 | 22 | public String getMessage() { 23 | return message; 24 | } 25 | 26 | public Throwable getCause() { 27 | return cause; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/graft/Options.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * Global options for Graft. 8 | * 9 | * @author Wim Keirsgieter 10 | */ 11 | public class Options { 12 | 13 | private static Logger log = LoggerFactory.getLogger(Options.class); 14 | 15 | /** 16 | * Singleton options instance. 17 | */ 18 | private static Config options; 19 | 20 | /** 21 | * Initialize options (at start of Graft run). 22 | * 23 | * @param config the Graft configuration file 24 | */ 25 | public static void init(Config config) { 26 | if (options != null) { 27 | throw new GraftRuntimeException("Options already initialized"); 28 | } 29 | options = config.copy(); 30 | if (log.isDebugEnabled()) { 31 | options.debug(); 32 | } 33 | } 34 | 35 | /** 36 | * Check if the global options have been initialized. 37 | * 38 | * @return true if the global options have been initialized, else false 39 | */ 40 | public static boolean isInit() { 41 | return options != null; 42 | } 43 | 44 | /** 45 | * Get the current global options instance. 46 | * 47 | * @return the current options instance 48 | */ 49 | public static Config v() { 50 | if (options == null) { 51 | throw new GraftRuntimeException("Options not initialized"); 52 | } 53 | return options; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/graft/analysis/AliasAnalysis.java: -------------------------------------------------------------------------------- 1 | package graft.analysis; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.*; 9 | 10 | import graft.Banner; 11 | import graft.Graft; 12 | import graft.traversal.CpgTraversal; 13 | 14 | import static graft.Const.*; 15 | import static graft.cpg.CpgUtil.*; 16 | import static graft.traversal.__.*; 17 | import static graft.utils.DisplayUtil.*; 18 | 19 | /** 20 | * Basic, intraprocedural may-alias analysis. 21 | */ 22 | public class AliasAnalysis implements GraftAnalysis { 23 | 24 | private static Logger log = LoggerFactory.getLogger(AliasAnalysis.class); 25 | 26 | private Banner banner; 27 | 28 | /** 29 | * Instantiate a new alias analysis. 30 | */ 31 | public AliasAnalysis() { 32 | banner = new Banner("Alias Analysis"); 33 | } 34 | 35 | @Override 36 | public void doAnalysis() { 37 | CpgTraversal entries = Graft.cpg().traversal().entries(); 38 | 39 | int nrMethods = 0; 40 | long start = System.currentTimeMillis(); 41 | while (entries.hasNext()) { 42 | Vertex entry = (Vertex) entries.next(); 43 | mayAlias(entry); 44 | nrMethods++; 45 | } 46 | long end = System.currentTimeMillis(); 47 | 48 | banner.println("Alias analysis successfully performed on " + nrMethods + " methods"); 49 | banner.println("Elapsed time: " + displayTime(end - start)); 50 | banner.display(); 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | private void mayAlias(Vertex entry) { 55 | String methodSig = entry.value(METHOD_SIG); 56 | log.debug("Running mayAlias on method '{}'", methodSig); 57 | Map> pts = new HashMap<>(); 58 | 59 | CpgTraversal refAssigns = Graft.cpg().traversal() 60 | .getRefAssignStmts() 61 | .where(astIn(STATEMENT).values(METHOD_SIG).is(methodSig)); 62 | log.debug("{} ref assigns", refAssigns.clone().count().next()); 63 | 64 | while (refAssigns.hasNext()) { 65 | Vertex refAssign = (Vertex) refAssigns.next(); 66 | String tgtName = Graft.cpg().traversal() 67 | .V(refAssign) 68 | .getTgt() 69 | .values(NAME).next().toString(); 70 | String valName = Graft.cpg().traversal() 71 | .V(refAssign) 72 | .getVal() 73 | .values(NAME).next().toString(); 74 | addToSet(pts, tgtName, valName); 75 | } 76 | log.debug("Points-to sets filled"); 77 | 78 | for (String key : pts.keySet()) { 79 | for (String val : pts.get(key)) { 80 | CpgTraversal keyLocals = astNodes(entry).locals(key); 81 | astNodes(entry).copy() 82 | .locals(val) 83 | .coalesce( 84 | inE(MAY_ALIAS).where(outV().is(keyLocals.copy())), 85 | addE(MAY_ALIAS) 86 | .from(keyLocals.copy()) 87 | .property(EDGE_TYPE, MAY_ALIAS) 88 | .property(TEXT_LABEL, MAY_ALIAS) 89 | ).iterate(); 90 | } 91 | } 92 | } 93 | 94 | private void addToSet(Map> map, String key, String val) { 95 | Set set = map.get(key); 96 | if (set == null) { 97 | set = new HashSet<>(); 98 | } 99 | set.add(val); 100 | map.put(key, set); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/graft/analysis/GraftAnalysis.java: -------------------------------------------------------------------------------- 1 | package graft.analysis; 2 | 3 | /** 4 | * Base interface for analysis runs. 5 | * 6 | * @author Wim Keirsgieter 7 | */ 8 | public interface GraftAnalysis { 9 | 10 | void doAnalysis(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/graft/analysis/TaintAnalysis.java: -------------------------------------------------------------------------------- 1 | package graft.analysis; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.*; 6 | 7 | import groovy.lang.Binding; 8 | import groovy.lang.GroovyShell; 9 | 10 | import org.apache.tinkerpop.gremlin.process.traversal.Path; 11 | import org.apache.tinkerpop.gremlin.structure.Edge; 12 | import org.apache.tinkerpop.gremlin.structure.Vertex; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import graft.Banner; 18 | import graft.Graft; 19 | import graft.traversal.CpgTraversal; 20 | 21 | import static graft.Const.*; 22 | import static graft.cpg.CpgUtil.*; 23 | import static graft.traversal.__.*; 24 | import static graft.utils.DisplayUtil.*; 25 | import static graft.utils.FileUtil.*; 26 | 27 | /** 28 | * This class performs a taint analysis on the CPG, using the given source, sink and sanitizer descriptions. 29 | * 30 | * @author Wim Keirsgieter 31 | */ 32 | public class TaintAnalysis implements GraftAnalysis { 33 | 34 | private static Logger log = LoggerFactory.getLogger(TaintAnalysis.class); 35 | 36 | private String descrFile; 37 | private int nrVulns; 38 | private Banner banner; 39 | private List vulnBanners; 40 | 41 | /** 42 | * Instantiate a new taint analysis. 43 | * 44 | * @param descrFile the path of the taint descriptions file. 45 | */ 46 | public TaintAnalysis(String descrFile) { 47 | this.descrFile = descrFile; 48 | banner = new Banner("Taint Analysis"); 49 | vulnBanners = new ArrayList<>(); 50 | } 51 | 52 | @Override 53 | public void doAnalysis() { 54 | log.info("Running taint analysis..."); 55 | long start = System.currentTimeMillis(); 56 | CpgTraversal source, sink, sanitizer; 57 | 58 | log.debug("Loading taint descriptions"); 59 | Binding binding = new Binding(); 60 | GroovyShell shell = new GroovyShell(binding); 61 | 62 | try { 63 | shell.evaluate(new File(descrFile)); 64 | source = (CpgTraversal) shell.getVariable("source"); 65 | sink = (CpgTraversal) shell.getVariable("sink"); 66 | sanitizer = (CpgTraversal) shell.getVariable("sanitizer"); 67 | } catch (IOException e) { 68 | log.error("Unable to read taint descriptions, failure"); 69 | if (log.isDebugEnabled()) { 70 | e.printStackTrace(); 71 | } 72 | return; 73 | } 74 | 75 | List dataFlows = Graft.cpg().traversal() 76 | .V().where(source) 77 | .repeat(timeLimit(50000).outE(PDG_EDGE).inV().simplePath()) 78 | .until(sink) 79 | .path() 80 | .toList(); 81 | int nrPotentials = dataFlows.size(); 82 | log.info("{} data flows between sources and sinks", nrPotentials); 83 | banner.println(nrPotentials + " potentially tainted data flow paths"); 84 | 85 | for (Path dataFlow : dataFlows) { 86 | if (!isSanitized(dataFlow, sanitizer)) { 87 | nrVulns++; 88 | reportTaintedPath(dataFlow); 89 | } 90 | } 91 | long end = System.currentTimeMillis(); 92 | 93 | banner.println(nrVulns + " taint vulnerabilities found"); 94 | banner.println(); 95 | 96 | log.info("Taint analysis completed in {}", displayTime(end - start)); 97 | banner.println("Elapsed time: " + displayTime(end - start)); 98 | banner.display(); 99 | 100 | for (Banner vulnBanner : vulnBanners) { 101 | vulnBanner.display(); 102 | } 103 | } 104 | 105 | private void reportTaintedPath(Path pdgPath) { 106 | Vertex src = pdgPath.get(0); 107 | Vertex sink = pdgPath.get(pdgPath.size() - 1); 108 | Banner vulnBanner = new Banner("TAINT VULNERABILITY"); 109 | 110 | String srcLoc = getClassName(getFileName(src)); 111 | vulnBanner.println("Source: " + src.value(TEXT_LABEL)); 112 | vulnBanner.println("(" + srcLoc + ", " + src.value(SRC_LINE_NO) + ")"); 113 | vulnBanner.println(); 114 | 115 | for (int i = 1; i < pdgPath.size() - 2; i += 2) { 116 | Edge varDep = pdgPath.get(i); 117 | Vertex propThrough = pdgPath.get(i + 1); 118 | String propLoc = getClassName(getFileName(propThrough)); 119 | vulnBanner.println("Tainted var '" + 120 | varDep.value(TEXT_LABEL) + 121 | "' redefined at: " + 122 | propThrough.value(TEXT_LABEL)); 123 | vulnBanner.println("(" + propLoc + ", " + propThrough.value(SRC_LINE_NO) + ")"); 124 | } 125 | vulnBanner.println(); 126 | 127 | String sinkLoc = getClassName(getFileName(sink)); 128 | vulnBanner.println("Sink: " + sink.value(TEXT_LABEL)); 129 | vulnBanner.println("(" + sinkLoc + ", " + sink.value(SRC_LINE_NO) + ")"); 130 | 131 | vulnBanners.add(vulnBanner); 132 | } 133 | 134 | private boolean isSanitized(Path pdgPath, CpgTraversal sanitizer) { 135 | log.debug("Checking path for sanitization: {}", pdgPath); 136 | 137 | for (int i = 0; i < pdgPath.size() - 2; i += 2) { 138 | Edge e = pdgPath.get(i + 1); 139 | 140 | String varName; 141 | try { 142 | varName = e.value(VAR_NAME); 143 | } catch (IllegalStateException exc) { 144 | varName = ""; 145 | } 146 | 147 | Vertex v = pdgPath.get(i); 148 | Vertex w = pdgPath.get(i + 2); 149 | List cfgPaths = Graft.cpg().traversal() 150 | .V(v) 151 | .repeat(timeLimit(10000).out(CFG_EDGE).simplePath()) 152 | .until(is(w)) 153 | .path().dedup().toList(); 154 | 155 | for (Path cfgPath : cfgPaths) { 156 | boolean cfgSanitized = false; 157 | for (int j = 0; j < cfgPath.size(); j++) { 158 | //System.out.println(debugVertex(cfgPath.get(j))); 159 | CpgTraversal sans = Graft.cpg().traversal() 160 | .V(((Vertex) cfgPath.get(j)).id()) 161 | .where(or( 162 | sanitizer.copy() 163 | //defines(varName) 164 | )); 165 | if ((long) sans.count().next() > 0) { 166 | cfgSanitized = true; 167 | } 168 | } 169 | if (!cfgSanitized) { 170 | return false; 171 | } 172 | } 173 | } 174 | return true; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/AstBuilder.java: -------------------------------------------------------------------------------- 1 | package graft.cpg; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import soot.Local; 9 | import soot.RefType; 10 | import soot.Value; 11 | import soot.jimple.Constant; 12 | import soot.jimple.Expr; 13 | import soot.jimple.Ref; 14 | 15 | import graft.Graft; 16 | import graft.cpg.visitors.ConstantVisitor; 17 | import graft.cpg.visitors.ExprVisitor; 18 | import graft.cpg.visitors.RefVisitor; 19 | 20 | import static graft.Const.*; 21 | import static graft.cpg.CpgUtil.*; 22 | 23 | /** 24 | * Generate AST nodes and subtrees. 25 | * 26 | * @author Wim Keirsgieter 27 | */ 28 | public class AstBuilder { 29 | 30 | private static Logger log = LoggerFactory.getLogger(AstBuilder.class); 31 | 32 | /** 33 | * Initialize a new AstBuilder instance (should be done for each method). 34 | */ 35 | public AstBuilder() { } 36 | 37 | /** 38 | * Generate an AST node (possibly a subtree) for the given Soot value. 39 | * 40 | * @param value the Soot value 41 | * @return the generated AST node 42 | */ 43 | public Vertex genValueNode(Value value) { 44 | if (value instanceof Local) { 45 | return genLocalNode((Local) value); 46 | } else if (value instanceof Constant) { 47 | return genConstantNode((Constant) value); 48 | } else if (value instanceof Expr) { 49 | return genExprNode((Expr) value); 50 | } else if (value instanceof Ref) { 51 | return genRefNode((Ref) value); 52 | } else { 53 | log.warn("Unhandled Value type '{}', no AST node generated", value.getClass().getSimpleName()); 54 | return null; 55 | } 56 | } 57 | 58 | // Generates an AST node for a local variable 59 | private Vertex genLocalNode(Local local) { 60 | boolean refType = local.getType() instanceof RefType; 61 | return (Vertex) Graft.cpg().traversal() 62 | .addLocalNode(getTypeString(local.getType()), local.toString(), local.getName()) 63 | .property(REF_TYPE, refType) 64 | .next(); 65 | } 66 | 67 | // Generates an AST node for a constant value using the ConstantVisitor 68 | private Vertex genConstantNode(Constant constant) { 69 | ConstantVisitor visitor = new ConstantVisitor(); 70 | constant.apply(visitor); 71 | return (Vertex) visitor.getResult(); 72 | } 73 | 74 | // Generates an AST node for an expression using the ExprVisitor 75 | private Vertex genExprNode(Expr expr) { 76 | ExprVisitor visitor = new ExprVisitor(this); 77 | expr.apply(visitor); 78 | return (Vertex) visitor.getResult(); 79 | } 80 | 81 | // Generates an AST node for a reference using the RefVisitor 82 | private Vertex genRefNode(Ref ref) { 83 | RefVisitor visitor = new RefVisitor(this); 84 | ref.apply(visitor); 85 | return (Vertex) visitor.getResult(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/CfgBuilder.java: -------------------------------------------------------------------------------- 1 | package graft.cpg; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.apache.tinkerpop.gremlin.structure.Vertex; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import soot.SootMethod; 13 | import soot.Unit; 14 | import soot.jimple.*; 15 | import soot.toolkits.graph.UnitGraph; 16 | 17 | import graft.Graft; 18 | import graft.cpg.visitors.StmtVisitor; 19 | 20 | import static graft.Const.*; 21 | import static graft.cpg.CpgUtil.*; 22 | import static graft.traversal.__.*; 23 | 24 | /** 25 | * Generate the control flow graph. 26 | * 27 | * @author Wim Keirsgieter 28 | */ 29 | public class CfgBuilder { 30 | 31 | private static Logger log = LoggerFactory.getLogger(CfgBuilder.class); 32 | 33 | private AstBuilder astBuilder; 34 | private Map generatedNodes; 35 | private UnitGraph unitGraph; 36 | private Vertex entryNode; 37 | 38 | public CfgBuilder(UnitGraph unitGraph, AstBuilder astBuilder) { 39 | this.unitGraph = unitGraph; 40 | this.astBuilder = astBuilder; 41 | this.generatedNodes = new HashMap<>(); 42 | } 43 | 44 | // ******************************************************************************************** 45 | // public methods 46 | // ******************************************************************************************** 47 | 48 | public Vertex buildCfg() { 49 | SootMethod method = unitGraph.getBody().getMethod(); 50 | log.debug("Building CFG for method '{}'", method.getName()); 51 | entryNode = (Vertex) Graft.cpg().traversal() 52 | .addEntryNode(method.getName(), method.getSignature(), getTypeString(method.getReturnType())) 53 | .next(); 54 | 55 | for (Unit head : unitGraph.getHeads()) { 56 | Vertex headVertex = genUnitNode(head); 57 | Graft.cpg().traversal() 58 | .addEmptyEdge() 59 | .from(entryNode).to(headVertex) 60 | .iterate(); 61 | } 62 | 63 | return entryNode; 64 | } 65 | 66 | public Map generatedNodes() { 67 | return generatedNodes; 68 | } 69 | 70 | // ******************************************************************************************** 71 | // private methods 72 | // ******************************************************************************************** 73 | 74 | // Generate a CFG node for the given unit, with its successors 75 | @SuppressWarnings("unchecked") 76 | private Vertex genUnitNode(Unit unit) { 77 | if (unit instanceof GotoStmt) { 78 | // collapse goto statements 79 | return genUnitNode(((GotoStmt) unit).getTarget()); 80 | } 81 | 82 | log.trace("Generating Unit '{}'", unit.toString()); 83 | 84 | Vertex unitVertex = generatedNodes.get(unit); 85 | if (unitVertex == null) { 86 | StmtVisitor visitor = new StmtVisitor(astBuilder); 87 | unit.apply(visitor); 88 | unitVertex = (Vertex) visitor.getResult(); 89 | 90 | // AST statement edge from entry node 91 | Graft.cpg().traversal() 92 | .addAstE(STATEMENT, STATEMENT) 93 | .from(entryNode).to(unitVertex) 94 | .iterate(); 95 | 96 | generatedNodes.put(unit, unitVertex); 97 | } else { 98 | return unitVertex; 99 | } 100 | 101 | // handle possible conditional edges 102 | if (unit instanceof IfStmt) { 103 | return genIfAndSuccs(unitVertex, (IfStmt) unit); 104 | } else if (unit instanceof LookupSwitchStmt) { 105 | return genLookupSwitchAndSuccs(unitVertex, (LookupSwitchStmt) unit); 106 | } else if (unit instanceof TableSwitchStmt) { 107 | return genTableSwitchAndSuccs(unitVertex, (TableSwitchStmt) unit); 108 | } 109 | 110 | List succs = unitGraph.getSuccsOf(unit); 111 | assert succs.size() <= 1; 112 | 113 | if (succs.size() == 1) { 114 | Vertex succNode = genUnitNode(succs.get(0)); 115 | Graft.cpg().traversal() 116 | .V(unitVertex).as("v") 117 | .V(succNode) 118 | .coalesce( 119 | inE(CFG_EDGE).where(outV().as("v")), 120 | addEmptyEdge().from("v") 121 | ).iterate(); 122 | } 123 | 124 | return unitVertex; 125 | } 126 | 127 | private Vertex genIfAndSuccs(Vertex ifNode, IfStmt ifStmt) { 128 | for (Unit succ : unitGraph.getSuccsOf(ifStmt)) { 129 | Vertex succNode = genUnitNode(succ); 130 | if (succ.equals(ifStmt.getTarget())) { 131 | Graft.cpg().traversal() 132 | .addCondEdge(TRUE) 133 | .from(ifNode).to(succNode) 134 | .iterate(); 135 | } else { 136 | Graft.cpg().traversal() 137 | .addCondEdge(FALSE) 138 | .from(ifNode).to(succNode) 139 | .iterate(); 140 | } 141 | } 142 | return ifNode; 143 | } 144 | 145 | private Vertex genLookupSwitchAndSuccs(Vertex switchNode, LookupSwitchStmt switchStmt) { 146 | for (int i = 0; i < switchStmt.getTargetCount(); i++) { 147 | Vertex targetNode = genUnitNode(switchStmt.getTarget(i)); 148 | Graft.cpg().traversal() 149 | .addCondEdge(switchStmt.getLookupValue(i) + "") 150 | .from(switchNode).to(targetNode) 151 | .iterate(); 152 | } 153 | genDefaultTarget(switchNode, switchStmt); 154 | return switchNode; 155 | } 156 | 157 | private Vertex genTableSwitchAndSuccs(Vertex switchNode, TableSwitchStmt switchStmt) { 158 | int i = 0; 159 | for (Unit target : switchStmt.getTargets()) { 160 | Vertex targetNode = genUnitNode(target); 161 | Graft.cpg().traversal() 162 | .addCondEdge((i++) + "") 163 | .from(switchNode).to(targetNode) 164 | .iterate(); 165 | } 166 | genDefaultTarget(switchNode, switchStmt); 167 | return switchNode; 168 | } 169 | 170 | private void genDefaultTarget(Vertex switchNode, SwitchStmt switchStmt) { 171 | if (switchStmt.getDefaultTarget() != null) { 172 | Vertex defaultNode = genUnitNode(switchStmt.getDefaultTarget()); 173 | Graft.cpg().traversal() 174 | .addCondEdge(DEFAULT_TARGET) 175 | .from(switchNode).to(defaultNode) 176 | .iterate(); 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/CpgBuilder.java: -------------------------------------------------------------------------------- 1 | package graft.cpg; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | 8 | import org.apache.tinkerpop.gremlin.structure.Vertex; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import soot.*; 14 | import soot.toolkits.graph.BriefUnitGraph; 15 | import soot.toolkits.graph.UnitGraph; 16 | 17 | import graft.traversal.CpgTraversalSource; 18 | import graft.utils.SootUtil; 19 | 20 | import graft.*; 21 | import graft.traversal.CpgTraversal; 22 | 23 | import static graft.Const.*; 24 | import static graft.traversal.__.*; 25 | import static graft.utils.DisplayUtil.*; 26 | import static graft.utils.FileUtil.*; 27 | 28 | /** 29 | * Handles the actual construction of the CPG. 30 | * 31 | * @author Wim Keirsgieter 32 | */ 33 | public class CpgBuilder { 34 | 35 | private Banner banner; 36 | 37 | private static Logger log = LoggerFactory.getLogger(CpgBuilder.class); 38 | 39 | public CpgBuilder() { 40 | banner = new Banner("CPG construction summary"); 41 | } 42 | 43 | /** 44 | * Build a CPG for the program in the given target directory. 45 | * 46 | * @param targetDir the target directory of the program 47 | */ 48 | public void buildCpg(String targetDir) { 49 | log.info("Building CPG"); 50 | log.info("Target directory: {})", targetDir); 51 | 52 | long start = System.currentTimeMillis(); 53 | try { 54 | 55 | // get class files in target director 56 | File target = getTarget(targetDir); 57 | List classFiles = getClassFiles(target); 58 | int nrClasses = classFiles.size(); 59 | if (nrClasses == 0) { 60 | banner.println("- No class files in target dir"); 61 | banner.display(); 62 | return; 63 | } 64 | log.info("{} class(es) to load", nrClasses); 65 | 66 | // get class names from class files 67 | List classNames = classFiles.stream() 68 | .map(file -> getClassName(target, file)) 69 | .collect(Collectors.toList()); 70 | 71 | // load classes into Scene 72 | log.info("Loading class(es)..."); 73 | SootUtil.loadClasses(classNames.toArray(new String[0])); 74 | long loaded = System.currentTimeMillis(); 75 | 76 | // build CPGs for all classes 77 | for (int i = 0; i < nrClasses; i++) { 78 | String className = classNames.get(i); 79 | File classFile = classFiles.get(i); 80 | 81 | // load class from Scene 82 | log.debug("Building CPG for class '{}'...", className); 83 | SootClass cls = Scene.v().loadClassAndSupport(className); 84 | 85 | // build class CPG 86 | Vertex classNode = buildCpg(cls, classFile); 87 | 88 | // class edge from root or package node to class node 89 | log.debug("Adding class edge"); 90 | String packageName = cls.getPackageName(); 91 | Vertex packageNode = packageNodes(packageName); 92 | Graft.cpg().traversal() 93 | .addAstE(CLASS, CLASS) 94 | .from(packageNode).to(classNode) 95 | .iterate(); 96 | } 97 | long built = System.currentTimeMillis(); 98 | 99 | // generate interproc edges 100 | log.info("Individual CPGs loaded, adding interprocedural edges..."); 101 | Interproc.genInterprocEdges(); 102 | long interproc = System.currentTimeMillis(); 103 | 104 | banner.println("CPG constructed successfully!"); 105 | banner.println(); 106 | 107 | // performance stats 108 | banner.println("Elapsed times:"); 109 | long timeToLoad = loaded - start; 110 | long timeToBuild = built - loaded; 111 | long timeToInter = interproc - built; 112 | banner.println("* " + nrClasses + " class(es) loaded in " + displayTime(timeToLoad)); 113 | banner.println("* Intraprocedural CPGs built in " + displayTime(timeToBuild)); 114 | banner.println("* Interprocedural edges generated in " + displayTime(timeToInter)); 115 | banner.println("* TOTAL ELAPSED TIME: " + displayTime(timeToLoad + timeToBuild + timeToInter)); 116 | banner.println(); 117 | 118 | // CPG stats 119 | banner.println("CPG info:"); 120 | long nrNodes = Graft.cpg().traversal().V().count().next(); 121 | long nrEdges = Graft.cpg().traversal().E().count().next(); 122 | long nrMethods = (long) Graft.cpg().traversal().entries().count().next(); 123 | banner.println("* " + nrMethods + " methods in " + nrClasses + " class(es)"); 124 | banner.println("* " + nrNodes + " nodes"); 125 | banner.println("* " + nrEdges + " edges"); 126 | 127 | } catch (GraftRuntimeException e) { 128 | banner.println(e.getClass().getName() + " during CPG construction"); 129 | banner.println(e.getMessage()); 130 | } 131 | 132 | banner.display(); 133 | } 134 | 135 | /** 136 | * Update the CPG given changes in the configured target directory. 137 | */ 138 | public void amendCpg() { 139 | String targetDirName = Options.v().getString(OPT_TARGET_DIR); 140 | File targetDir = new File(targetDirName); 141 | 142 | banner = new Banner(); 143 | banner.println("Amending CPG"); 144 | banner.println("Target dir: " + targetDir); 145 | 146 | List classFiles = getClassFiles(targetDir); 147 | if (classFiles.size() == 0) { 148 | banner.println("No class files in target dir"); 149 | banner.display(); 150 | return; 151 | } 152 | 153 | List amendedClasses = amendedClasses(); 154 | List classNames = new ArrayList<>(); 155 | banner.println("Files changed:"); 156 | for (File classFile : amendedClasses) { 157 | String className = getClassName(targetDir, classFile); 158 | banner.println("- " + classFile.getName() + " (" + className + ")"); 159 | classNames.add(className); 160 | } 161 | 162 | if (amendedClasses.size() == 0) { 163 | banner.println("No files changed - nothing to do"); 164 | banner.display(); 165 | return; 166 | } 167 | banner.println(amendedClasses.size() + " classes to amend"); 168 | 169 | long start = System.currentTimeMillis(); 170 | 171 | Vertex cpgRoot = Graft.cpg().traversal().V().hasLabel(CPG_ROOT).next(); 172 | SootUtil.loadClasses(classNames.toArray(new String[0])); 173 | long prevNodes = Graft.cpg().nrV(); 174 | long prevEdges = Graft.cpg().nrE(); 175 | 176 | for (int i = 0; i < amendedClasses.size(); i++) { 177 | log.debug("Amending CPG of class '{}'", classNames.get(i)); 178 | SootClass cls = Scene.v().loadClassAndSupport(classNames.get(i)); 179 | CpgBuilder.amendCpg(cpgRoot, cls, amendedClasses.get(i)); 180 | } 181 | 182 | Graft.cpg().commit(); 183 | long end = System.currentTimeMillis(); 184 | 185 | banner.println("CPG amended successfully in " + displayTime(end - start)); 186 | banner.println("Nodes: " + Graft.cpg().nrV() + " (prev " + prevNodes + ")"); 187 | banner.println("Edges: " + Graft.cpg().nrE() + " (prev " + prevEdges + ")"); 188 | banner.display(); 189 | } 190 | 191 | /** 192 | * Get a list of all classes changed since CPG construction. 193 | * 194 | * @return a list of changed classes 195 | */ 196 | public static List amendedClasses() { 197 | String targetDirName = Options.v().getString(OPT_TARGET_DIR); 198 | File targetDir = new File(targetDirName); 199 | 200 | List classFiles = getClassFiles(targetDir); 201 | 202 | List amendedClasses = new ArrayList<>(); 203 | for (File classFile : classFiles) { 204 | String className = getClassName(targetDir, classFile); 205 | try { 206 | String hash = hashFile(classFile); 207 | if (!hash.equals(CpgUtil.getClassHash(className))) { 208 | amendedClasses.add(classFile); 209 | } 210 | } catch (GraftException e) { 211 | log.warn("Could not hash file '{}'", classFile.getName(), e); 212 | } 213 | } 214 | 215 | return amendedClasses; 216 | } 217 | 218 | private static Vertex buildCpg(SootClass cls, File classFile) { 219 | String fileHash = UNKNOWN; 220 | try { 221 | fileHash = hashFile(classFile); 222 | } catch (GraftException e) { 223 | log.warn("Could not hash file '{}'", classFile.getName(), e); 224 | } 225 | 226 | Vertex classNode = (Vertex) Graft.cpg().traversal() 227 | .addAstV(CLASS, cls.getShortName()) 228 | .property(SHORT_NAME, cls.getShortName()) 229 | .property(FULL_NAME, cls.getName()) 230 | .property(FILE_NAME, classFile.getName()) 231 | .property(FILE_PATH, classFile.getPath()) 232 | .property(FILE_HASH, fileHash) 233 | .next(); 234 | 235 | log.debug("{} methods in class", cls.getMethodCount()); 236 | for (SootMethod method : cls.getMethods()) { 237 | Body body; 238 | try { 239 | body = method.retrieveActiveBody(); 240 | } catch (RuntimeException e) { 241 | // no active bodies for interface methods, abstract methods etc. 242 | log.debug("No active body for method {}", method.getSignature()); 243 | continue; 244 | } 245 | if (body == null) continue; 246 | try { 247 | Vertex methodEntry = buildCpg(body); 248 | if (method.isConstructor()) { 249 | Graft.cpg().traversal() 250 | .addAstE(CONSTRUCTOR, CONSTRUCTOR) 251 | .from(classNode).to(methodEntry) 252 | .iterate(); 253 | } else { 254 | Graft.cpg().traversal() 255 | .addAstE(METHOD, METHOD) 256 | .from(classNode).to(methodEntry) 257 | .iterate(); 258 | } 259 | } catch (SootMethodRefImpl.ClassResolutionFailedException e) { 260 | log.debug("Class resolution failed for method {}", method.getSignature()); 261 | } 262 | } 263 | 264 | Graft.cpg().commit(); 265 | 266 | return classNode; 267 | } 268 | 269 | private static Vertex buildCpg(Body body) { 270 | UnitGraph unitGraph = new BriefUnitGraph(body); 271 | CfgBuilder cfgBuilder = new CfgBuilder(unitGraph, new AstBuilder()); 272 | Vertex methodEntry = cfgBuilder.buildCfg(); 273 | PdgBuilder.buildPdg(unitGraph, cfgBuilder.generatedNodes()); 274 | return methodEntry; 275 | } 276 | 277 | private static void amendCpg(Vertex cpgRoot, SootClass cls, File classFile) { 278 | CpgTraversalSource g = Graft.cpg().traversal(); 279 | g.V().hasLabel(AST_NODE) 280 | .has(NODE_TYPE, CLASS) 281 | .has(FULL_NAME, cls.getName()) 282 | .drop() 283 | .iterate(); 284 | 285 | for (SootMethod method : cls.getMethods()) { 286 | CpgUtil.dropMethod(method.getSignature()); 287 | } 288 | CpgUtil.dropClass(cls.getName()); 289 | 290 | Vertex classNode = buildCpg(cls, classFile); 291 | Graft.cpg().traversal() 292 | .addAstE(CLASS, CLASS) 293 | .from(cpgRoot).to(classNode) 294 | .iterate(); 295 | 296 | // entry nodes of methods in amended classes 297 | CpgTraversal entries = Graft.cpg().traversal().V() 298 | .and( 299 | V(classNode).astOut(METHOD).store("entries"), 300 | V(classNode).astOut(CONSTRUCTOR).store("entries") 301 | ).cap("entries").unfold().dedup(); 302 | while (entries.hasNext()) { 303 | Vertex entry = (Vertex) entries.next(); 304 | Interproc.genInterprocEdges(entry); 305 | } 306 | 307 | Graft.cpg().commit(); 308 | } 309 | 310 | private static File getTarget(String targetDir) { 311 | File target = new File(targetDir); 312 | 313 | if (!target.exists()) { 314 | throw new GraftRuntimeException("Target directory '" + targetDir + "' does not exist"); 315 | } 316 | if (!target.isDirectory()) { 317 | throw new GraftRuntimeException("Target directory '" + targetDir + "' is not a directory"); 318 | } 319 | 320 | return target; 321 | } 322 | 323 | @SuppressWarnings("unchecked") 324 | private static Vertex packageNodes(String packageName) { 325 | String[] packages = packageName.split("\\."); 326 | Vertex prev = Graft.cpg().root(); 327 | for (String pack : packages) { 328 | if (pack.length() == 0) continue; 329 | Vertex packNode = (Vertex) Graft.cpg().traversal().V() 330 | .coalesce( 331 | packageOf(pack), 332 | addPackage(pack) 333 | ).next(); 334 | Graft.cpg().traversal() 335 | .addAstE(PACKAGE, PACKAGE) 336 | .from(prev).to(packNode) 337 | .iterate(); 338 | prev = packNode; 339 | } 340 | return prev; 341 | } 342 | 343 | } 344 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/CpgUtil.java: -------------------------------------------------------------------------------- 1 | package graft.cpg; 2 | 3 | import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy; 4 | import org.apache.tinkerpop.gremlin.structure.Edge; 5 | import org.apache.tinkerpop.gremlin.structure.Element; 6 | import org.apache.tinkerpop.gremlin.structure.Vertex; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import soot.Type; 12 | 13 | import graft.Graft; 14 | import graft.cpg.visitors.TypeVisitor; 15 | import graft.traversal.CpgTraversal; 16 | 17 | import static graft.Const.*; 18 | import static graft.traversal.__.*; 19 | 20 | /** 21 | * Utility methods for CPG construction. 22 | * 23 | * @author Wim Keirsgieter 24 | */ 25 | public class CpgUtil { 26 | 27 | private static Logger log = LoggerFactory.getLogger(CpgUtil.class); 28 | 29 | /** 30 | * Given a vertex, returns a traversal over all nodes in the AST rooted at that vertex. 31 | * 32 | * @param root the vertex at which the AST is rooted 33 | * @return a traversal over all nodes in the AST subtree 34 | */ 35 | @SuppressWarnings("unchecked") 36 | public static CpgTraversal astNodes(Vertex root) { 37 | return Graft.cpg().traversal().V(root).union( 38 | __(), 39 | repeat(timeLimit(10000).astOut()).emit() 40 | ); 41 | } 42 | 43 | /** 44 | * Returns a SubgraphStrategy that will only traverse the statement nodes of a given method. 45 | * 46 | * @param methodSig the signature of the method 47 | * @return the SubgraphStrategy for traversals of the method's statement nodes 48 | */ 49 | public static SubgraphStrategy methodSubgraphStrategy(String methodSig) { 50 | return SubgraphStrategy.build().vertices(and( 51 | hasLabel(CFG_NODE), 52 | astIn(STATEMENT).has(METHOD_SIG, methodSig) 53 | )).create(); 54 | } 55 | 56 | /** 57 | * Given a vertex in the CPG, traverse up to its class node and return the source file. 58 | * 59 | * @param v the vertex 60 | * @return the source file containing the vertex 61 | */ 62 | public static String getFileName(Vertex v) { 63 | return Graft.cpg().traversal().V(v) 64 | .until(has(NODE_TYPE, CLASS)).repeat(in(AST_EDGE)) 65 | .values(FILE_PATH).next().toString(); 66 | } 67 | 68 | /** 69 | * Drop the given method from the CPG. 70 | * 71 | * @param methodSig the signature of the method to drop 72 | */ 73 | public static void dropMethod(String methodSig) { 74 | Graft.cpg().traversal() 75 | .entryOf(methodSig).store("d") 76 | .repeat(astOut().store("d")) 77 | .cap("d") 78 | .unfold().drop().iterate(); 79 | } 80 | 81 | /** 82 | * Drop the given class from the CPG 83 | * 84 | * @param fullName the full name of the class to drop 85 | */ 86 | public static void dropClass(String fullName) { 87 | Graft.cpg().traversal() 88 | .classOf(fullName).store("d") 89 | .repeat(astOut().store("d")) 90 | .cap("d") 91 | .unfold().drop().iterate(); 92 | } 93 | 94 | /** 95 | * Get the stored hash of the given class file. 96 | * 97 | * @param className the name of the class 98 | * @return the stored hash of the class, if found (else empty string) 99 | */ 100 | public static String getClassHash(String className) { 101 | CpgTraversal t = Graft.cpg().traversal().classOf(className).values(FILE_HASH); 102 | if (t.hasNext()) { 103 | return t.next().toString(); 104 | } else { 105 | log.debug("Cannot get file hash of class '{}': no results", className); 106 | return ""; 107 | } 108 | } 109 | 110 | /** 111 | * Get the string name of a Soot type using the type visitor. 112 | * 113 | * @param type the type to get the name of 114 | * @return the string name of the given type 115 | */ 116 | public static String getTypeString(Type type) { 117 | TypeVisitor visitor = new TypeVisitor(); 118 | type.apply(visitor); 119 | return visitor.getResult().toString(); 120 | } 121 | 122 | /** 123 | * Get a string representation of a vertex and its properties for debugging. 124 | * 125 | * @param vertex the vertex to get a string representation of 126 | * @return a string representation of the given vertex 127 | */ 128 | public static String debugVertex(Vertex vertex) { 129 | return debugElement(vertex, NODE_TYPE); 130 | } 131 | 132 | /** 133 | * Get a string representation of an edge and its properties for debugging. 134 | * 135 | * @param edge the edge to get a string representation of 136 | * @return a string representation of the given edge 137 | */ 138 | public static String debugEdge(Edge edge) { 139 | return debugElement(edge, EDGE_TYPE); 140 | } 141 | 142 | private static String debugElement(Element element, String typeKey) { 143 | StringBuilder sb = new StringBuilder(); 144 | sb.append("<").append(element.label()).append(" "); 145 | sb.append("(").append(element.value(typeKey).toString()).append(")"); 146 | for (String key : element.keys()) { 147 | sb.append(" "); 148 | sb.append(key).append("='").append(element.value(key).toString()).append("'"); 149 | } 150 | sb.append(">"); 151 | return sb.toString(); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/Interproc.java: -------------------------------------------------------------------------------- 1 | package graft.cpg; 2 | 3 | import java.util.NoSuchElementException; 4 | 5 | import org.apache.tinkerpop.gremlin.process.traversal.P; 6 | import org.apache.tinkerpop.gremlin.structure.Vertex; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import graft.Graft; 12 | import graft.traversal.CpgTraversal; 13 | 14 | import static graft.Const.*; 15 | import static graft.traversal.__.*; 16 | 17 | /** 18 | * Handles generations of interprocedural control and data flow edges. 19 | * 20 | * @author Wim Keirsgieter 21 | */ 22 | public class Interproc { 23 | 24 | private static Logger log = LoggerFactory.getLogger(Interproc.class); 25 | 26 | private static void callsTo(Vertex calleeEntry) { 27 | String calleeSig = calleeEntry.value(METHOD_SIG); 28 | log.debug("Generating interproc edges for calls to {}", calleeSig); 29 | 30 | // call sites 31 | CpgTraversal calls = Graft.cpg().traversal() 32 | .invokesOf(calleeSig) 33 | .repeat(astIn()).until(label().is(CFG_NODE)); 34 | long nrCalls = (long) calls.copy().count().next(); 35 | log.debug("{} calls", nrCalls); 36 | if (nrCalls == 0) { 37 | // method is never invoked 38 | return; 39 | } 40 | 41 | // method return sites 42 | CpgTraversal rets = Graft.cpg().traversal().returnsOf(calleeSig); 43 | long nrReturns = (long) rets.copy().count().next(); 44 | 45 | if (nrReturns == 0) { 46 | log.debug("No returns for method {}", calleeSig); 47 | //throw new GraftRuntimeException("No returns for method " + methodSig); 48 | } 49 | 50 | // the next node after the caller in the CFG 51 | CpgTraversal retSites = calls.copy().cfgOut(false); 52 | long nrRetSites = (long) retSites.copy().count().next(); 53 | 54 | callEdges(calleeEntry, calls.copy()); 55 | argDepEdges(calleeEntry, calls.copy()); 56 | retDepEdges(rets.copy(), calls.copy()); 57 | if (nrRetSites > 0 && nrReturns > 0) { 58 | returnEdges(rets.copy(), retSites.copy()); 59 | } 60 | } 61 | 62 | private static void handleCall(Vertex invokeExpr) { 63 | String calleeSig = invokeExpr.value(METHOD_SIG); 64 | 65 | // call site statement node 66 | Vertex callSite = Graft.cpg().traversal().V(invokeExpr) 67 | .repeat(astIn()) 68 | .until(label().is(CFG_NODE)) 69 | .next(); 70 | 71 | // ret site statement node (or none) 72 | Vertex retSite; 73 | try { 74 | retSite = Graft.cpg().traversal().V(callSite).cfgOut() 75 | .cfgOut() 76 | .next(); 77 | } catch (NoSuchElementException e) { 78 | retSite = null; 79 | } 80 | 81 | // callee entry node (return if DNE) 82 | Vertex calleeEntry; 83 | try { 84 | calleeEntry = (Vertex) Graft.cpg().traversal() 85 | .entryOf(calleeSig) 86 | .next(); 87 | } catch (NoSuchElementException e) { 88 | // no callee entry - probably a library method 89 | return; 90 | } 91 | 92 | log.debug("Handling call to {}", calleeSig); 93 | 94 | // call edge 95 | Graft.cpg().traversal() 96 | .genCallEdge() 97 | .from(callSite).to(calleeEntry) 98 | .iterate(); 99 | 100 | // ret edges 101 | if (retSite != null) { 102 | Graft.cpg().traversal() 103 | .returnsOf(calleeSig).as("rets") 104 | .genRetEdge() 105 | .from("rets").to(retSite) 106 | .iterate(); 107 | } 108 | 109 | // arg edges 110 | Graft.cpg().traversal() 111 | .paramsOf(calleeSig).as("params") 112 | .addArgDepE() 113 | .from(callSite).to("params") 114 | .iterate(); 115 | 116 | // ret value edges 117 | Graft.cpg().traversal() 118 | .returnsOf(calleeSig).where(astOut().count().is(P.gt(0))).as("rets") 119 | .addRetDepE() 120 | .from("rets").to(callSite) 121 | .iterate(); 122 | } 123 | 124 | @SuppressWarnings("unchecked") 125 | private static void callEdges(Vertex entry, CpgTraversal calls) { 126 | Graft.cpg().traversal() 127 | .genCallEdge() 128 | .from(calls).to(entry) 129 | .iterate(); 130 | } 131 | 132 | @SuppressWarnings("unchecked") 133 | private static void returnEdges(CpgTraversal returns, CpgTraversal retSites) { 134 | Graft.cpg().traversal() 135 | .genRetEdge() 136 | .from(returns).to(retSites) 137 | .iterate(); 138 | } 139 | 140 | private static void argDepEdges(Vertex entry, CpgTraversal calls) { 141 | Graft.cpg().traversal().V(entry) 142 | .astOut(STATEMENT).has(NODE_TYPE, ASSIGN_STMT) 143 | .where(getVal().and( 144 | values(NODE_TYPE).is(REF), 145 | values(REF_TYPE).is(PARAM_REF) 146 | )).as("params") 147 | .addArgDepE() 148 | .from(calls).to("params") 149 | .iterate(); 150 | } 151 | 152 | @SuppressWarnings("unchecked") 153 | private static void retDepEdges(CpgTraversal returns, CpgTraversal calls) { 154 | returns.where(astOut().count().is(P.gt(0))).as("rets") 155 | .addRetDepE() 156 | .from("rets").to(calls) 157 | .iterate(); 158 | } 159 | 160 | /** 161 | * Generate interprocedural call and return edges between call sites and method entries / returns. 162 | */ 163 | public static void genInterprocEdges() { 164 | log.info("Generating interprocedural edges..."); 165 | 166 | CpgTraversal entries = Graft.cpg().traversal().entries(); 167 | while (entries.hasNext()) { 168 | callsTo((Vertex) entries.next()); 169 | } 170 | 171 | } 172 | 173 | /** 174 | * Generate interprocedural call and return edges into and out of the given method. 175 | * 176 | * @param callerEntry the entry of the method to generate interproc edges for 177 | */ 178 | public static void genInterprocEdges(Vertex callerEntry) { 179 | String callerSig = callerEntry.value(METHOD_SIG); 180 | log.debug("Generating interprocedural edges for method {}...", callerSig); 181 | 182 | callsTo(callerEntry); 183 | 184 | CpgTraversal calls = Graft.cpg().traversal() 185 | .invokeExprs() 186 | .where(repeat(astIn()).until(values(NODE_TYPE).is(ENTRY)) 187 | .values(METHOD_SIG).is(callerSig) 188 | ); 189 | 190 | while (calls.hasNext()) { 191 | Vertex call = (Vertex) calls.next(); 192 | handleCall(call); 193 | } 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/PdgBuilder.java: -------------------------------------------------------------------------------- 1 | package graft.cpg; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | 6 | import org.apache.tinkerpop.gremlin.structure.Vertex; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import soot.Local; 12 | import soot.Unit; 13 | import soot.Value; 14 | import soot.ValueBox; 15 | import soot.toolkits.graph.UnitGraph; 16 | import soot.toolkits.scalar.LocalDefs; 17 | import soot.toolkits.scalar.SimpleLocalDefs; 18 | 19 | import graft.Graft; 20 | import graft.GraftRuntimeException; 21 | import graft.traversal.CpgTraversalSource; 22 | 23 | /** 24 | * Handles construction of intraprocedural PDG edges. 25 | * 26 | * @author Wim Keirsgieter 27 | */ 28 | public class PdgBuilder { 29 | 30 | private static Logger log = LoggerFactory.getLogger(PdgBuilder.class); 31 | 32 | /** 33 | * Generate a PDG for the given unit graph, using Soot's local defs analysis. 34 | * 35 | * @param unitGraph the unit graph to generate the PDG for 36 | * @param unitNodes a mapping of units to their vertices in the CPG 37 | */ 38 | public static void buildPdg(UnitGraph unitGraph, Map unitNodes) { 39 | log.debug("Building PDG for method '{}'", unitGraph.getBody().getMethod().getName()); 40 | CpgTraversalSource g = Graft.cpg().traversal(); 41 | 42 | LocalDefs localDefs = new SimpleLocalDefs(unitGraph); 43 | 44 | Iterator units = unitGraph.iterator(); 45 | while (units.hasNext()) { 46 | Unit unit = units.next(); 47 | if (!unitNodes.containsKey(unit) || unitNodes.get(unit) == null) { 48 | continue; 49 | } 50 | Vertex unitVertex = unitNodes.get(unit); 51 | for (ValueBox valueBox : unit.getUseBoxes()) { 52 | Value value = valueBox.getValue(); 53 | if (!(value instanceof Local)) { 54 | continue; 55 | } 56 | Local local = (Local) value; 57 | for (Unit defSite : localDefs.getDefsOfAt(local, unit)) { 58 | if (unitNodes.get(defSite) == null) { 59 | log.warn("No def site for local '{}' in method '{}'", 60 | local.getName(), 61 | unitGraph.getBody().getMethod().getSignature()); 62 | throw new GraftRuntimeException(); 63 | //continue; 64 | } 65 | Vertex defVertex = g.V(unitNodes.get(defSite)).next(); 66 | Graft.cpg().traversal() 67 | .addDataDepE(local.getName()) 68 | .from(defVertex).to(unitVertex) 69 | .iterate(); 70 | } 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/structure/CodePropertyGraph.java: -------------------------------------------------------------------------------- 1 | package graft.cpg.structure; 2 | 3 | import org.apache.commons.configuration.BaseConfiguration; 4 | import org.apache.commons.configuration.Configuration; 5 | 6 | import org.apache.tinkerpop.gremlin.structure.Graph; 7 | import org.apache.tinkerpop.gremlin.structure.Vertex; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import graft.Banner; 13 | import graft.GraftRuntimeException; 14 | import graft.Options; 15 | 16 | import graft.db.Neo4jUtil; 17 | import graft.db.TinkerGraphUtil; 18 | 19 | import graft.traversal.CpgTraversal; 20 | import graft.traversal.CpgTraversalSource; 21 | 22 | import graft.utils.DotUtil; 23 | 24 | import static graft.Const.*; 25 | 26 | /** 27 | * An implementation of the code property graph structure. 28 | * 29 | * @author Wim Keirsgieter 30 | */ 31 | public class CodePropertyGraph { 32 | 33 | private static Logger log = LoggerFactory.getLogger(CodePropertyGraph.class); 34 | 35 | private String name; 36 | private Graph g; 37 | 38 | // ******************************************************************************************** 39 | // constructors 40 | // ******************************************************************************************** 41 | 42 | /** 43 | * Instantiate a code property graph from the given Gremlin graph. 44 | * 45 | * @param g the graph to instantiate the CPG from 46 | */ 47 | private CodePropertyGraph(Graph g) { 48 | this.g = g; 49 | 50 | CpgTraversal root = traversal().cpgRoot(); 51 | if (!root.hasNext()) { 52 | throw new GraftRuntimeException("No CPG root in graph"); 53 | } 54 | 55 | name = ((Vertex) root.next()).value(PROJECT_NAME); 56 | 57 | if (Options.isInit()) { 58 | if (!Options.v().containsKey(OPT_PROJECT_NAME)) { 59 | log.warn("Project name not set in options"); 60 | } else { 61 | if (!name.equals(Options.v().getString(OPT_PROJECT_NAME))) { 62 | log.warn("CPG name '{}' differs from project name '{}' in options"); 63 | } 64 | } 65 | } 66 | } 67 | 68 | // ******************************************************************************************** 69 | // instance methods 70 | // ******************************************************************************************** 71 | 72 | public String name() { 73 | return name; 74 | } 75 | 76 | public Vertex root() { 77 | return (Vertex) traversal().cpgRoot().next(); 78 | } 79 | 80 | // ******************************************************************************************** 81 | // CPG traversals 82 | // ******************************************************************************************** 83 | 84 | public CpgTraversalSource traversal() { 85 | return g.traversal(CpgTraversalSource.class); 86 | } 87 | 88 | public CpgTraversal V() { 89 | return traversal().V(); 90 | } 91 | 92 | public CpgTraversal E() { 93 | return traversal().E(); 94 | } 95 | 96 | public long nrV() { 97 | return traversal().V().count().next(); 98 | } 99 | 100 | public long nrAstV() { 101 | return (long) traversal().astV().count().next(); 102 | } 103 | 104 | public long nrCfgV() { 105 | return (long) traversal().cfgV().count().next(); 106 | } 107 | 108 | public long nrE() { 109 | return (long) traversal().E().count().next(); 110 | } 111 | 112 | public long nrAstE() { 113 | return (long) traversal().astE().count().next(); 114 | } 115 | 116 | public long nrCfgE() { 117 | return (long) traversal().cfgE().count().next(); 118 | } 119 | 120 | public long nrPdgE() { 121 | return (long) traversal().pdgE().count().next(); 122 | } 123 | 124 | public long nrAliasE() { 125 | return (long) traversal().aliasE().count().next(); 126 | } 127 | 128 | // ******************************************************************************************** 129 | // visualization / serialization 130 | // ******************************************************************************************** 131 | 132 | /** 133 | * Write the CPG to a dot file. 134 | * 135 | * @param filename the name of the dot file to write the CPG to. 136 | */ 137 | public void toDot(String filename) { 138 | DotUtil.graphToDot(this, filename); 139 | } 140 | 141 | /** 142 | * Dump the CPG to a graph file. 143 | * 144 | * @param filename the name of the file to dump the CPG to. 145 | */ 146 | public void dump(String filename) { 147 | g.traversal().io(filename).write().iterate(); 148 | } 149 | 150 | /** 151 | * Generate a status banner with information about the CPG. 152 | * 153 | * @return a status banner for the CPG 154 | */ 155 | public Banner status() { 156 | Banner banner = new Banner("Code Property Graph"); 157 | banner.println("Project name: " + name); 158 | banner.println(); 159 | 160 | banner.println("NODES"); 161 | banner.println("-----"); 162 | banner.println(nrV() + " TOTAL"); 163 | banner.println("* " + nrAstV() + " AST nodes"); 164 | banner.println("* " + nrCfgV() + " CFG nodes"); 165 | banner.println(); 166 | 167 | banner.println("EDGES"); 168 | banner.println("-----"); 169 | banner.println(nrE() + " TOTAL"); 170 | banner.println("* " + nrAstE() + " AST edges"); 171 | banner.println("* " + nrCfgE() + " CFG edges"); 172 | banner.println("* " + nrPdgE() + " PDG edges"); 173 | banner.println("* " + nrAliasE() + " alias edges"); 174 | 175 | return banner; 176 | } 177 | 178 | // ******************************************************************************************** 179 | // database interaction 180 | // ******************************************************************************************** 181 | 182 | /** 183 | * Commit the current transaction if the underlying database supports it. 184 | */ 185 | public void commit() { 186 | if (g.features().graph().supportsTransactions()) { 187 | g.tx().commit(); 188 | } 189 | } 190 | 191 | /** 192 | * Attempt to close the database connection. 193 | */ 194 | public void close() { 195 | try { 196 | g.close(); 197 | } catch (Exception e) { 198 | throw new GraftRuntimeException("Unable to close CPG", e); 199 | } 200 | } 201 | 202 | // ******************************************************************************************** 203 | // static methods 204 | // ******************************************************************************************** 205 | 206 | /** 207 | * Load a Tinkergraph CPG from the given file. 208 | * 209 | * @param filename the file to load the CPG from 210 | * @return the CPG loaded from the file 211 | */ 212 | public static CodePropertyGraph fromFile(String filename) { 213 | Graph g = TinkerGraphUtil.fromFile(filename); 214 | return new CodePropertyGraph(g); 215 | } 216 | 217 | /** 218 | * Load a Neo4j CPG from the given directory. 219 | * 220 | * @param dirName the directory of the Neo4j database 221 | * @return the CPG loaded from the directory 222 | */ 223 | public static CodePropertyGraph fromDir(String dirName) { 224 | Configuration neo4jConfig = new BaseConfiguration(); 225 | neo4jConfig.setProperty("gremlin.neo4j.directory", dirName); 226 | neo4jConfig.setProperty("gremlin.neo4j.conf.dbms.auto_index.nodes.enabled", "true"); 227 | neo4jConfig.setProperty("gremlin.neo4j.conf.dbms.auto_index.relationships.enabled", "true"); 228 | Graph g = Neo4jUtil.fromConfig(neo4jConfig); 229 | return new CodePropertyGraph(g); 230 | } 231 | 232 | /** 233 | * Initialize a new empty CPG using the given graph database instance. 234 | * 235 | * @param g the graph database instance 236 | * @param name the project name 237 | * @param target the project target directory 238 | * @param classpath the project classpath 239 | * @return the newly initialized CPG 240 | */ 241 | public static CodePropertyGraph initCpg(Graph g, String name, String target, String classpath) { 242 | g.traversal(CpgTraversalSource.class).addCpgRoot(name, target, classpath).iterate(); 243 | if (g.features().graph().supportsTransactions()) { 244 | g.tx().commit(); 245 | } 246 | return new CodePropertyGraph(g); 247 | } 248 | 249 | /** 250 | * Initialize a new empty CPG using the given graph database instance, using the project name, 251 | * target directory and classpath set in the global options. 252 | * 253 | * @param g the graph database instance 254 | * @return the newly intialized CPG 255 | */ 256 | public static CodePropertyGraph initCpg(Graph g) { 257 | String name = Options.v().getString(OPT_PROJECT_NAME); 258 | String target = Options.v().getString(OPT_TARGET_DIR); 259 | String classpath = Options.v().getString(OPT_CLASSPATH); 260 | return initCpg(g, name, target, classpath); 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/visitors/ConstantVisitor.java: -------------------------------------------------------------------------------- 1 | package graft.cpg.visitors; 2 | 3 | import graft.Graft; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import soot.jimple.*; 9 | 10 | import graft.cpg.CpgUtil; 11 | 12 | import static graft.Const.*; 13 | 14 | /** 15 | * Visitor applied to constant values to create AST nodes for them. 16 | * 17 | * @author Wim Keirsgieter 18 | */ 19 | public class ConstantVisitor extends AbstractConstantSwitch { 20 | 21 | private static Logger log = LoggerFactory.getLogger(ConstantVisitor.class); 22 | 23 | @Override 24 | public void caseDoubleConstant(DoubleConstant constant) { 25 | caseConstant(DOUBLE, constant.value); 26 | } 27 | 28 | @Override 29 | public void caseFloatConstant(FloatConstant constant) { 30 | caseConstant(FLOAT, constant.value); 31 | } 32 | 33 | @Override 34 | public void caseIntConstant(IntConstant constant) { 35 | caseConstant(INT, constant.value); 36 | } 37 | 38 | @Override 39 | public void caseLongConstant(LongConstant constant) { 40 | caseConstant(LONG, constant.value); 41 | } 42 | 43 | @Override 44 | public void caseNullConstant(NullConstant constant) { 45 | caseConstant(NULL, NULL); 46 | } 47 | 48 | @Override 49 | public void caseStringConstant(StringConstant constant) { 50 | caseConstant(STRING, constant.value); 51 | } 52 | 53 | @Override 54 | public void caseClassConstant(ClassConstant constant) { 55 | caseConstant(CLASS, constant.getValue()); 56 | } 57 | 58 | @Override 59 | public void defaultCase(Object obj) { 60 | log.warn("Unrecognised Constant type '{}'", obj.getClass()); 61 | Constant constant = (Constant) obj; 62 | caseConstant(CpgUtil.getTypeString(constant.getType()), UNKNOWN); 63 | } 64 | 65 | // Generate an AST node for a constant value 66 | private void caseConstant(String type, Object value) { 67 | setResult(Graft.cpg().traversal() 68 | .addConstNode(type, value.toString(), value.toString()) 69 | .next() 70 | ); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/visitors/ExprVisitor.java: -------------------------------------------------------------------------------- 1 | package graft.cpg.visitors; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import soot.Value; 9 | import soot.jimple.*; 10 | 11 | import graft.Graft; 12 | import graft.cpg.AstBuilder; 13 | 14 | import static graft.Const.*; 15 | import static graft.cpg.CpgUtil.*; 16 | 17 | /** 18 | * Visitor applied to expressions to create AST nodes (or subtrees) for them. 19 | * 20 | * @author Wim Keirsgieter 21 | */ 22 | public class ExprVisitor extends AbstractExprSwitch { 23 | 24 | private static Logger log = LoggerFactory.getLogger(ExprVisitor.class); 25 | 26 | private AstBuilder astBuilder; 27 | 28 | public ExprVisitor(AstBuilder astBuilder) { 29 | this.astBuilder = astBuilder; 30 | } 31 | 32 | // ******************************************************************************************** 33 | // binary expressions 34 | // ******************************************************************************************** 35 | 36 | @Override 37 | public void caseAddExpr(AddExpr expr) { 38 | caseBinaryExpr(expr, PLUS); 39 | } 40 | 41 | @Override 42 | public void caseAndExpr(AndExpr expr) { 43 | caseBinaryExpr(expr, AND); 44 | } 45 | 46 | @Override 47 | public void caseCmpExpr(CmpExpr expr) { 48 | caseBinaryExpr(expr, CMP); 49 | } 50 | 51 | @Override 52 | public void caseCmpgExpr(CmpgExpr expr) { 53 | caseBinaryExpr(expr, CMPG); 54 | } 55 | 56 | @Override 57 | public void caseCmplExpr(CmplExpr expr) { 58 | caseBinaryExpr(expr, CMPL); 59 | } 60 | 61 | @Override 62 | public void caseDivExpr(DivExpr expr) { 63 | caseBinaryExpr(expr, DIVIDE); 64 | } 65 | 66 | @Override 67 | public void caseEqExpr(EqExpr expr) { 68 | caseBinaryExpr(expr, EQUALS); 69 | } 70 | 71 | @Override 72 | public void caseNeExpr(NeExpr expr) { 73 | caseBinaryExpr(expr, NOT_EQUALS); 74 | } 75 | 76 | @Override 77 | public void caseGeExpr(GeExpr expr) { 78 | caseBinaryExpr(expr, GREATER_EQUALS); 79 | } 80 | 81 | @Override 82 | public void caseGtExpr(GtExpr expr) { 83 | caseBinaryExpr(expr, GREATER); 84 | } 85 | 86 | @Override 87 | public void caseLeExpr(LeExpr expr) { 88 | caseBinaryExpr(expr, LESS_EQUALS); 89 | } 90 | 91 | @Override 92 | public void caseLtExpr(LtExpr expr) { 93 | caseBinaryExpr(expr, LESS); 94 | } 95 | 96 | @Override 97 | public void caseMulExpr(MulExpr expr) { 98 | caseBinaryExpr(expr, MULTIPLY); 99 | } 100 | 101 | @Override 102 | public void caseOrExpr(OrExpr expr) { 103 | caseBinaryExpr(expr, OR); 104 | } 105 | 106 | @Override 107 | public void caseRemExpr(RemExpr expr) { 108 | caseBinaryExpr(expr, REMAINDER); 109 | } 110 | 111 | @Override 112 | public void caseShlExpr(ShlExpr expr) { 113 | caseBinaryExpr(expr, LEFT_SHIFT); 114 | } 115 | 116 | @Override 117 | public void caseShrExpr(ShrExpr expr) { 118 | caseBinaryExpr(expr, SIGNED_RIGHT_SHIFT); 119 | } 120 | 121 | @Override 122 | public void caseUshrExpr(UshrExpr expr) { 123 | caseBinaryExpr(expr, UNSIGNED_RIGHT_SHIFT); 124 | } 125 | 126 | @Override 127 | public void caseSubExpr(SubExpr expr) { 128 | caseBinaryExpr(expr, MINUS); 129 | } 130 | 131 | @Override 132 | public void caseXorExpr(XorExpr expr) { 133 | caseBinaryExpr(expr, XOR); 134 | } 135 | 136 | // ******************************************************************************************** 137 | // unary expressions 138 | // ******************************************************************************************** 139 | 140 | @Override 141 | public void caseLengthExpr(LengthExpr expr) { 142 | caseUnaryExpr(expr, LENGTH); 143 | } 144 | 145 | @Override 146 | public void caseNegExpr(NegExpr expr) { 147 | caseUnaryExpr(expr, NEGATION); 148 | } 149 | 150 | // ******************************************************************************************** 151 | // invoke expressions 152 | // ******************************************************************************************** 153 | 154 | @Override 155 | public void caseInterfaceInvokeExpr(InterfaceInvokeExpr invokeExpr) { 156 | caseInvokeExpr(invokeExpr, INTERFACE, invokeExpr.getBase()); 157 | } 158 | 159 | @Override 160 | public void caseSpecialInvokeExpr(SpecialInvokeExpr invokeExpr) { 161 | caseInvokeExpr(invokeExpr, SPECIAL, invokeExpr.getBase()); 162 | } 163 | 164 | @Override 165 | public void caseStaticInvokeExpr(StaticInvokeExpr invokeExpr) { 166 | caseInvokeExpr(invokeExpr, STATIC, null); 167 | } 168 | 169 | @Override 170 | public void caseVirtualInvokeExpr(VirtualInvokeExpr invokeExpr) { 171 | caseInvokeExpr(invokeExpr, VIRTUAL, invokeExpr.getBase()); 172 | } 173 | 174 | @Override 175 | public void caseDynamicInvokeExpr(DynamicInvokeExpr invokeExpr) { 176 | caseInvokeExpr(invokeExpr, DYNAMIC, null); 177 | } 178 | 179 | // ******************************************************************************************** 180 | // other expressions 181 | // ******************************************************************************************** 182 | 183 | @Override 184 | public void caseNewExpr(NewExpr expr) { 185 | setResult(Graft.cpg().traversal() 186 | .addExprNode(NEW_EXPR, expr.toString(), getTypeString(expr.getType())) 187 | .property(NEW_EXPR_TYPE, NEW_EXPR) 188 | .next() 189 | ); 190 | } 191 | 192 | @Override 193 | public void caseNewArrayExpr(NewArrayExpr expr) { 194 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 195 | .addExprNode(NEW_EXPR, expr.toString(), getTypeString(expr.getType())) 196 | .property(NEW_EXPR_TYPE, NEW_ARRAY_EXPR) 197 | .property(BASE_TYPE, getTypeString(expr.getBaseType())) 198 | .next(); 199 | 200 | Graft.cpg().traversal() 201 | .addAstE(SIZE, SIZE) 202 | .from(exprNode) 203 | .to(astBuilder.genValueNode(expr.getSize())) 204 | .iterate(); 205 | 206 | setResult(exprNode); 207 | } 208 | 209 | @Override 210 | public void caseNewMultiArrayExpr(NewMultiArrayExpr expr) { 211 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 212 | .addExprNode(NEW_EXPR, expr.toString(), getTypeString(expr.getType())) 213 | .property(NEW_EXPR_TYPE, NEW_ARRAY_EXPR) 214 | .property(BASE_TYPE, getTypeString(expr.getBaseType())) 215 | .next(); 216 | 217 | int i = 0; 218 | for (Value size : expr.getSizes()) { 219 | Graft.cpg().traversal() 220 | .addAstE(SIZE, SIZE) 221 | .from(exprNode) 222 | .to(astBuilder.genValueNode(size)) 223 | .property(DIM, i++) 224 | .iterate(); 225 | } 226 | 227 | setResult(exprNode); 228 | } 229 | 230 | @Override 231 | public void caseInstanceOfExpr(InstanceOfExpr expr) { 232 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 233 | .addExprNode(INSTANCEOF_EXPR, expr.toString(), getTypeString(expr.getType())) 234 | .property(CHECK_TYPE, getTypeString(expr.getCheckType())) 235 | .next(); 236 | 237 | Graft.cpg().traversal() 238 | .addAstE(OPERAND, OPERAND) 239 | .from(exprNode) 240 | .to(astBuilder.genValueNode(expr.getOp())) 241 | .iterate(); 242 | 243 | setResult(exprNode); 244 | } 245 | 246 | @Override 247 | public void caseCastExpr(CastExpr expr) { 248 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 249 | .addExprNode(CAST_EXPR, expr.toString(), getTypeString(expr.getType())) 250 | .property(CAST_TYPE, getTypeString(expr.getCastType())) 251 | .next(); 252 | 253 | Graft.cpg().traversal() 254 | .addAstE(OPERAND, OPERAND) 255 | .from(exprNode) 256 | .to(astBuilder.genValueNode(expr.getOp())) 257 | .iterate(); 258 | 259 | setResult(exprNode); 260 | } 261 | 262 | // ******************************************************************************************** 263 | // default 264 | // ******************************************************************************************** 265 | 266 | @Override 267 | public void defaultCase(Object obj) { 268 | log.warn("Unrecognised Expr type '{}', no AST node generated", obj.getClass()); 269 | } 270 | 271 | // ******************************************************************************************** 272 | // private methods 273 | // ******************************************************************************************** 274 | 275 | // Generates an AST subtree for a binary expression and its operands 276 | private void caseBinaryExpr(BinopExpr expr, String operator) { 277 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 278 | .addExprNode(BINARY_EXPR, expr.toString(), getTypeString(expr.getType())) 279 | .property(OPERATOR, operator) 280 | .next(); 281 | 282 | Graft.cpg().traversal() 283 | .addAstE(LEFT_OPERAND, LEFT_OPERAND) 284 | .from(exprNode) 285 | .to(astBuilder.genValueNode(expr.getOp1())) 286 | .iterate(); 287 | 288 | Graft.cpg().traversal() 289 | .addAstE(RIGHT_OPERAND, RIGHT_OPERAND) 290 | .from(exprNode) 291 | .to(astBuilder.genValueNode(expr.getOp2())) 292 | .iterate(); 293 | 294 | setResult(exprNode); 295 | } 296 | 297 | // Generates an AST subtree for a unary expression and its operand 298 | private void caseUnaryExpr(UnopExpr expr, String operator) { 299 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 300 | .addExprNode(UNARY_EXPR, expr.toString(), getTypeString(expr.getType())) 301 | .property(OPERATOR, operator) 302 | .next(); 303 | 304 | Graft.cpg().traversal() 305 | .addAstE(LEFT_OPERAND, LEFT_OPERAND) 306 | .from(exprNode) 307 | .to(astBuilder.genValueNode(expr.getOp())) 308 | .iterate(); 309 | 310 | setResult(exprNode); 311 | } 312 | 313 | // Generates an AST subtree for an invoke expression and possibly its base and args 314 | private void caseInvokeExpr(InvokeExpr expr, String invokeType, Value base) { 315 | Vertex exprNode = (Vertex) Graft.cpg().traversal() 316 | .addExprNode(INVOKE_EXPR, expr.toString(), getTypeString(expr.getType())) 317 | .property(INVOKE_TYPE, invokeType) 318 | .property(METHOD_SIG, expr.getMethod().getSignature()) 319 | .next(); 320 | 321 | if (base != null) { 322 | Graft.cpg().traversal() 323 | .addAstE(BASE, BASE) 324 | .from(exprNode) 325 | .to(astBuilder.genValueNode(base)) 326 | .iterate(); 327 | } 328 | 329 | int i = 0; 330 | for (Value arg : expr.getArgs()) { 331 | Graft.cpg().traversal() 332 | .addAstE(ARG, ARG) 333 | .from(exprNode) 334 | .to(astBuilder.genValueNode(arg)) 335 | .property(INDEX, i++) 336 | .iterate(); 337 | } 338 | 339 | setResult(exprNode); 340 | } 341 | 342 | } 343 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/visitors/RefVisitor.java: -------------------------------------------------------------------------------- 1 | package graft.cpg.visitors; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import soot.SootField; 9 | import soot.Value; 10 | import soot.jimple.*; 11 | 12 | import graft.Graft; 13 | import graft.cpg.AstBuilder; 14 | 15 | import static graft.Const.*; 16 | import static graft.cpg.CpgUtil.*; 17 | 18 | /** 19 | * Visitor applied to references to create AST nodes for them. 20 | * 21 | * @author Wim Keirsgieter 22 | */ 23 | public class RefVisitor extends AbstractRefSwitch { 24 | 25 | private static Logger log = LoggerFactory.getLogger(RefVisitor.class); 26 | 27 | private Object result; 28 | private AstBuilder astBuilder; 29 | 30 | public RefVisitor(AstBuilder astBuilder) { 31 | this.astBuilder = astBuilder; 32 | } 33 | 34 | public Object getResult() { 35 | return result; 36 | } 37 | 38 | @Override 39 | public void caseArrayRef(ArrayRef ref) { 40 | 41 | Vertex refNode = (Vertex) Graft.cpg().traversal() 42 | .addRefNode(ARRAY_REF, ref.toString(), getTypeString(ref.getType())) 43 | .next(); 44 | 45 | Graft.cpg().traversal() 46 | .addAstE(BASE, BASE) 47 | .from(refNode) 48 | .to(astBuilder.genValueNode(ref.getBase())) 49 | .iterate(); 50 | 51 | if (ref.getIndex() != null) { 52 | Graft.cpg().traversal() 53 | .addAstE(INDEX, INDEX) 54 | .from(refNode) 55 | .to(astBuilder.genValueNode(ref.getIndex())) 56 | .iterate(); 57 | } 58 | 59 | setResult(refNode); 60 | } 61 | 62 | @Override 63 | public void caseCaughtExceptionRef(CaughtExceptionRef ref) { 64 | setResult(Graft.cpg().traversal() 65 | .addRefNode(EXCEPTION_REF, ref.toString(), getTypeString(ref.getType())) 66 | .next() 67 | ); 68 | } 69 | 70 | @Override 71 | public void caseInstanceFieldRef(InstanceFieldRef ref) { 72 | caseFieldRef(ref.getField(), ref.getBase()); 73 | } 74 | 75 | @Override 76 | public void caseParameterRef(ParameterRef ref) { 77 | setResult(Graft.cpg().traversal() 78 | .addRefNode(PARAM_REF, ref.toString(), getTypeString(ref.getType())) 79 | .property(INDEX, ref.getIndex()) 80 | .next() 81 | ); 82 | } 83 | 84 | @Override 85 | public void caseStaticFieldRef(StaticFieldRef ref) { 86 | caseFieldRef(ref.getField(), null); 87 | } 88 | 89 | @Override 90 | public void caseThisRef(ThisRef ref) { 91 | setResult(Graft.cpg().traversal() 92 | .addRefNode(THIS_REF, ref.toString(), getTypeString(ref.getType())) 93 | .next() 94 | ); 95 | } 96 | 97 | @Override 98 | public void defaultCase(Object obj) { 99 | log.warn("Unrecognised Ref type '{}', no AST node generated", obj.getClass()); 100 | } 101 | 102 | // Generates an AST node for a field reference (and the base object for instance field refs) 103 | private void caseFieldRef(SootField field, Value base) { 104 | String fieldType = field.isStatic() ? STATIC_FIELD_REF : INSTANCE_FIELD_REF; 105 | Vertex refNode = (Vertex) Graft.cpg().traversal() 106 | .addRefNode(FIELD_REF, field.toString(), getTypeString(field.getType())) 107 | .property(FIELD_REF_TYPE, fieldType) 108 | .property(FIELD_NAME, field.getName()) 109 | .property(FIELD_SIG, field.getSignature()) 110 | .next(); 111 | 112 | if (base != null) { 113 | Graft.cpg().traversal() 114 | .addAstE(BASE, BASE) 115 | .from(refNode) 116 | .to(astBuilder.genValueNode(base)) 117 | .iterate(); 118 | } 119 | 120 | setResult(refNode); 121 | } 122 | 123 | private void setResult(Object object) { 124 | result = object; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/visitors/StmtVisitor.java: -------------------------------------------------------------------------------- 1 | package graft.cpg.visitors; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Vertex; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import soot.Value; 9 | import soot.jimple.*; 10 | 11 | import graft.Graft; 12 | import graft.cpg.AstBuilder; 13 | import graft.utils.SootUtil; 14 | 15 | import static graft.Const.*; 16 | 17 | /** 18 | * Visitor applied to Jimple statements to create CFG nodes for them. 19 | * 20 | * @author Wim Keirsgieter 21 | */ 22 | public class StmtVisitor extends AbstractStmtSwitch { 23 | 24 | private static Logger log = LoggerFactory.getLogger(StmtVisitor.class); 25 | 26 | private AstBuilder astBuilder; 27 | 28 | public StmtVisitor(AstBuilder astBuilder) { 29 | this.astBuilder = astBuilder; 30 | } 31 | 32 | @Override 33 | public void caseAssignStmt(AssignStmt stmt) { 34 | log.trace("Visiting AssignStmt"); 35 | caseDefinitionStmt(stmt); 36 | } 37 | 38 | @Override 39 | public void caseBreakpointStmt(BreakpointStmt stmt) { 40 | log.trace("Visiting BreakpointStmt"); 41 | log.info("Encountered breakpoint instruction during CFG construction"); 42 | setResult(Graft.cpg().traversal() 43 | .addStmtNode(BREAKPOINT_STMT, stmt.toString(), SootUtil.getLineNr(stmt)) 44 | .next()); 45 | } 46 | 47 | @Override 48 | public void caseEnterMonitorStmt(EnterMonitorStmt stmt) { 49 | log.trace("Visiting EnterMonitorStmt"); 50 | caseStmtWithOp(stmt, ENTER_MONITOR_STMT, stmt.getOp(), MONITOR); 51 | } 52 | 53 | @Override 54 | public void caseExitMonitorStmt(ExitMonitorStmt stmt) { 55 | log.trace("Visiting ExitMonitorStmt"); 56 | caseStmtWithOp(stmt, EXIT_MONITOR_STMT, stmt.getOp(), MONITOR); 57 | } 58 | 59 | @Override 60 | public void caseGotoStmt(GotoStmt stmt) { 61 | log.trace("Visiting GotoStmt - ignoring"); 62 | } 63 | 64 | @Override 65 | public void caseIdentityStmt(IdentityStmt stmt) { 66 | log.trace("Visiting IdentityStmt"); 67 | caseDefinitionStmt(stmt); 68 | } 69 | 70 | @Override 71 | public void caseIfStmt(IfStmt stmt) { 72 | log.trace("Visiting IfStmt"); 73 | caseStmtWithOp(stmt, CONDITIONAL_STMT, stmt.getCondition(), CONDITION); 74 | } 75 | 76 | @Override 77 | public void caseInvokeStmt(InvokeStmt stmt) { 78 | log.trace("Visiting InvokeStmt"); 79 | caseStmtWithOp(stmt, INVOKE_STMT, stmt.getInvokeExpr(), INVOKE_EXPR); 80 | } 81 | 82 | @Override 83 | public void caseLookupSwitchStmt(LookupSwitchStmt stmt) { 84 | log.trace("Visiting LookupSwitchStmt"); 85 | caseStmtWithOp(stmt, LOOKUP_SWITCH_STMT, stmt.getKey(), SWITCH_KEY); 86 | } 87 | 88 | @Override 89 | public void caseNopStmt(NopStmt stmt) { 90 | log.trace("Visiting NopStmt - ignoring"); 91 | } 92 | 93 | @Override 94 | public void caseRetStmt(RetStmt stmt) { 95 | log.trace("Visiting RetStmt"); 96 | setResult(Graft.cpg().traversal() 97 | .addStmtNode(RETURN_STMT, stmt.toString(), SootUtil.getLineNr(stmt)) 98 | .next()); 99 | } 100 | 101 | @Override 102 | public void caseReturnStmt(ReturnStmt stmt) { 103 | log.trace("Visiting ReturnStmt"); 104 | caseStmtWithOp(stmt, RETURN_STMT, stmt.getOp(), RETURNS); 105 | } 106 | 107 | @Override 108 | public void caseReturnVoidStmt(ReturnVoidStmt stmt) { 109 | log.trace("Visiting ReturnVoidStmt"); 110 | setResult(Graft.cpg().traversal() 111 | .addStmtNode(RETURN_STMT, stmt.toString(), SootUtil.getLineNr(stmt)) 112 | .next()); 113 | } 114 | 115 | @Override 116 | public void caseTableSwitchStmt(TableSwitchStmt stmt) { 117 | log.trace("Visiting TableSwitchStmt"); 118 | caseStmtWithOp(stmt, TABLE_SWITCH_STMT, stmt.getKey(), SWITCH_KEY); 119 | } 120 | 121 | @Override 122 | public void caseThrowStmt(ThrowStmt stmt) { 123 | log.trace("Visiting ThrowStmt"); 124 | caseStmtWithOp(stmt, THROW_STMT, stmt.getOp(), THROWS); 125 | } 126 | 127 | @Override 128 | public void defaultCase(Object obj) { 129 | log.warn("Unrecognised Stmt class '{}'", obj.getClass()); 130 | throw new RuntimeException("Unrecognised statement class (see logs for details)"); 131 | } 132 | 133 | // Generate CFG node for definition statements, with AST nodes for the target and value 134 | private void caseDefinitionStmt(DefinitionStmt stmt) { 135 | Vertex stmtNode = (Vertex) Graft.cpg().traversal() 136 | .addStmtNode(ASSIGN_STMT, stmt.toString(), SootUtil.getLineNr(stmt)) 137 | .next(); 138 | 139 | Graft.cpg().traversal() 140 | .addAstE(TARGET, TARGET) 141 | .from(stmtNode) 142 | .to(astBuilder.genValueNode(stmt.getLeftOp())) 143 | .iterate(); 144 | Graft.cpg().traversal() 145 | .addAstE(VALUE, VALUE) 146 | .from(stmtNode) 147 | .to(astBuilder.genValueNode(stmt.getRightOp())) 148 | .iterate(); 149 | 150 | setResult(stmtNode); 151 | } 152 | 153 | // Generates a CFG node for a statement of the given type with an operand of the given type 154 | private void caseStmtWithOp(Stmt stmt, String stmtType, Value op, String opType) { 155 | Vertex stmtNode = (Vertex) Graft.cpg().traversal() 156 | .addStmtNode(stmtType, stmt.toString(), SootUtil.getLineNr(stmt)) 157 | .next(); 158 | 159 | Graft.cpg().traversal() 160 | .addAstE(opType, opType) 161 | .from(stmtNode) 162 | .to(astBuilder.genValueNode(op)) 163 | .iterate(); 164 | 165 | setResult(stmtNode); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/graft/cpg/visitors/TypeVisitor.java: -------------------------------------------------------------------------------- 1 | package graft.cpg.visitors; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import soot.*; 7 | 8 | import static graft.Const.*; 9 | 10 | /** 11 | * Visitor applied to Soot types to get their string representation. 12 | * 13 | * @author Wim Keirsgieter 14 | */ 15 | public class TypeVisitor extends TypeSwitch { 16 | 17 | private static Logger log = LoggerFactory.getLogger(TypeVisitor.class); 18 | 19 | @Override 20 | public void caseArrayType(ArrayType type) { 21 | TypeVisitor typeVisitor = new TypeVisitor(); 22 | type.baseType.apply(typeVisitor); 23 | StringBuilder sb = new StringBuilder(typeVisitor.getResult().toString()); 24 | for (int i = 0; i < type.numDimensions; i++) { 25 | sb.append("[]"); 26 | } 27 | setResult(sb.toString()); 28 | } 29 | 30 | @Override 31 | public void caseBooleanType(BooleanType type) { 32 | setResult(BOOLEAN); 33 | } 34 | 35 | @Override 36 | public void caseByteType(ByteType type) { 37 | setResult(BYTE); 38 | } 39 | 40 | @Override 41 | public void caseCharType(CharType type) { 42 | setResult(CHAR); 43 | } 44 | 45 | @Override 46 | public void caseDoubleType(DoubleType type) { 47 | setResult(DOUBLE); 48 | } 49 | 50 | @Override 51 | public void caseFloatType(FloatType type) { 52 | setResult(FLOAT); 53 | } 54 | 55 | @Override 56 | public void caseIntType(IntType type) { 57 | setResult(INT); 58 | } 59 | 60 | @Override 61 | public void caseLongType(LongType type) { 62 | setResult(LONG); 63 | } 64 | 65 | @Override 66 | public void caseNullType(NullType type) { 67 | setResult(NULL); 68 | } 69 | 70 | @Override 71 | public void caseRefType(RefType type) { 72 | setResult(type.getClassName()); 73 | } 74 | 75 | @Override 76 | public void caseVoidType(VoidType type) { 77 | setResult(VOID); 78 | } 79 | 80 | @Override 81 | public void caseShortType(ShortType type) { 82 | setResult(SHORT); 83 | } 84 | 85 | @Override 86 | public void defaultCase(Type type) { 87 | log.warn("Unrecognised Type class '{}'", type.toString()); 88 | setResult(type.toString()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/graft/db/GraphUtil.java: -------------------------------------------------------------------------------- 1 | package graft.db; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Graph; 4 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import graft.GraftRuntimeException; 10 | import graft.Options; 11 | import graft.cpg.structure.CodePropertyGraph; 12 | 13 | import static graft.Const.*; 14 | 15 | /** 16 | * Utility methods for initializing the CPG and interacting with the graph database. 17 | * 18 | * @author Wim Keirsgieter 19 | */ 20 | public class GraphUtil { 21 | 22 | private static Logger log = LoggerFactory.getLogger(GraphUtil.class); 23 | 24 | /** 25 | * Initialize the CPG. 26 | */ 27 | public static CodePropertyGraph getCpg() { 28 | log.debug("Initializing graph..."); 29 | switch (Options.v().getString(OPT_DB_IMPLEMENTATION)) { 30 | case TINKERGRAPH: 31 | return CodePropertyGraph.fromFile(Options.v().getString(OPT_DB_FILE)); 32 | case NEO4J: 33 | return CodePropertyGraph.fromDir(Options.v().getString(OPT_DB_DIRECTORY)); 34 | default: 35 | throw new GraftRuntimeException( 36 | "Unknown graph implementation '" + 37 | Options.v().getString(OPT_DB_IMPLEMENTATION) + "'"); 38 | } 39 | } 40 | 41 | /** 42 | * Initialize a new Tinkergraph CPG. 43 | * 44 | * @return a new Tinkergraph CPG. 45 | */ 46 | public static CodePropertyGraph newTinkergraphCpg() { 47 | Graph g = TinkerGraph.open(); 48 | return CodePropertyGraph.initCpg(g); 49 | } 50 | 51 | /** 52 | * Initialize a new Neo4j CPG. 53 | * 54 | * @param dir the Neo4j directory 55 | * @return a new Neo4j CPG 56 | */ 57 | public static CodePropertyGraph newNeo4jCpg(String dir) { 58 | Graph g = Neo4jUtil.fromDir(dir); 59 | return CodePropertyGraph.initCpg(g); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/graft/db/Neo4jUtil.java: -------------------------------------------------------------------------------- 1 | package graft.db; 2 | 3 | import org.apache.commons.configuration.BaseConfiguration; 4 | import org.apache.commons.configuration.Configuration; 5 | 6 | import org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraph; 7 | 8 | /** 9 | * Utility methods for interacting with Neo4j graph databases. 10 | * 11 | * @author Wim Keirsgieter 12 | */ 13 | public class Neo4jUtil { 14 | 15 | /** 16 | * Initialize a Neo4j graph instance in the given directory. 17 | * 18 | * @param dir the Neo4j directory 19 | * @return the Neo4j graph instance 20 | */ 21 | public static Neo4jGraph fromDir(String dir) { 22 | Configuration config = new BaseConfiguration(); 23 | config.setProperty("gremlin.neo4j.directory", dir); 24 | config.setProperty("gremlin.neo4j.conf.dbms.auto_index.nodes.enabled", "true"); 25 | config.setProperty("gremlin.neo4j.conf.dbms.auto_index.relationships.enabled", "true"); 26 | return fromConfig(config); 27 | } 28 | 29 | /** 30 | * Initialize a Neo4j graph instance from the given config. 31 | * 32 | * @param config the Neo4j config 33 | * @return the Neo4j graph instance 34 | */ 35 | public static Neo4jGraph fromConfig(Configuration config) { 36 | Neo4jGraph g = Neo4jGraph.open(config); 37 | return g; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/graft/db/TinkerGraphUtil.java: -------------------------------------------------------------------------------- 1 | package graft.db; 2 | 3 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; 4 | 5 | /** 6 | * Utility methods for interacting with Tinkergraph databases. 7 | * 8 | * @author Wim Keirsgieter 9 | */ 10 | public class TinkerGraphUtil { 11 | 12 | /** 13 | * Initialize a new Tinkergraph instance from the given graph file. 14 | * 15 | * @param filename the name of the graph file to load 16 | * @return the new Tinkergraph instance 17 | */ 18 | public static TinkerGraph fromFile(String filename) { 19 | TinkerGraph g = TinkerGraph.open(); 20 | g.traversal().io(filename).read().iterate(); 21 | return g; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/graft/traversal/CpgTraversalDsl.java: -------------------------------------------------------------------------------- 1 | package graft.traversal; 2 | 3 | import org.apache.tinkerpop.gremlin.process.traversal.Path; 4 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl; 5 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 6 | 7 | import org.apache.tinkerpop.gremlin.structure.Edge; 8 | import org.apache.tinkerpop.gremlin.structure.Vertex; 9 | 10 | import static graft.Const.*; 11 | 12 | /** 13 | * Custom traversals for the Graft CPG traversal DSL. 14 | * 15 | * @author Wim Keirsgieter 16 | */ 17 | @GremlinDsl(traversalSource = "graft.traversal.CpgTraversalSourceDsl") 18 | public interface CpgTraversalDsl extends GraphTraversal.Admin { 19 | 20 | // ******************************************************************************************** 21 | // general purpose traversals 22 | // ******************************************************************************************** 23 | 24 | /** 25 | * Returns true if the property specified by the given key has a value that matches the given regex. 26 | * 27 | * @param key the key of the property to check 28 | * @param regex the regex pattern to check the property value against 29 | * @return true if the property value sanitizes the regex, else false 30 | */ 31 | default GraphTraversal hasPattern(String key, String regex) { 32 | return filter(x -> { 33 | if (x.get() instanceof Vertex) { 34 | Vertex vertex = (Vertex) x.get(); 35 | return vertex.value(key).toString().matches(regex); 36 | } else if (x.get() instanceof Edge) { 37 | Edge edge = (Edge) x.get(); 38 | return edge.value(key).toString().matches(regex); 39 | } else { 40 | return false; 41 | } 42 | }); 43 | } 44 | 45 | default GraphTraversal copy() { 46 | return this.clone(); 47 | } 48 | 49 | default GraphTraversal matches(String regex) { 50 | return filter(t -> t.get().toString().matches(regex)); 51 | } 52 | 53 | default GraphTraversal defines(String varName) { 54 | return and( 55 | label().is(CFG_NODE), 56 | values(NODE_TYPE).is(ASSIGN_STMT), 57 | getTgt().and( 58 | values(NODE_TYPE).is(LOCAL_VAR), 59 | values(NAME).is(varName) 60 | )); 61 | } 62 | 63 | default GraphTraversal locals(String varName) { 64 | return (CpgTraversal) hasLabel(AST_NODE) 65 | .has(NODE_TYPE, LOCAL_VAR) 66 | .has(NAME, varName); 67 | } 68 | 69 | @SuppressWarnings("unchecked") 70 | default GraphTraversal entries() { 71 | return (CpgTraversal) V().hasLabel(CFG_NODE).has(NODE_TYPE, ENTRY); 72 | } 73 | 74 | default GraphTraversal entryOf(String methodSig) { 75 | return entries().has(METHOD_SIG, methodSig); 76 | } 77 | 78 | default GraphTraversal packageNodes() { 79 | return astV(PACKAGE); 80 | } 81 | 82 | default GraphTraversal packageOf(String packName) { 83 | return packageNodes().has(PACKAGE_NAME, packName); 84 | } 85 | 86 | default GraphTraversal assignStmts() { 87 | return (CpgTraversal) has(NODE_TYPE, ASSIGN_STMT); 88 | } 89 | 90 | @SuppressWarnings("unchecked") 91 | default GraphTraversal source() { 92 | CpgTraversal g = (CpgTraversal) this.clone(); 93 | String[] varName = new String[]{}; 94 | return g.hasLabel(AST_NODE) 95 | .has(NODE_TYPE, LOCAL_VAR) 96 | .sideEffect(t -> { 97 | Vertex v = (Vertex) t; 98 | varName[0] = v.value(NAME); 99 | }) 100 | .dataDepIn(varName[0]); 101 | } 102 | 103 | @SuppressWarnings("unchecked") 104 | default GraphTraversal controlFlowsTo(Vertex w) { 105 | return repeat((CpgTraversal) timeLimit(1000).outE(CFG_EDGE).has(INTERPROC, false).inV().simplePath()) 106 | .until(is(w)) 107 | .path(); 108 | } 109 | 110 | // default GraphTraversal stmt() { 111 | // return coalesce( 112 | // hasLabel(CFG_NODE), 113 | // repeat(((CpgTraversal) timeLimit(500)).astIn()).until(label().is(CFG_NODE)) 114 | // ); 115 | // } 116 | 117 | @SuppressWarnings("unchecked") 118 | default GraphTraversal cfgRoot() { 119 | return until(label().is(CFG_NODE)).repeat(((CpgTraversal) timeLimit(100)).astIn()); 120 | } 121 | 122 | // ******************************************************************************************** 123 | // V traversals 124 | // ******************************************************************************************** 125 | 126 | // Control flow graph 127 | 128 | default GraphTraversal cfgV() { 129 | return V().hasLabel(CFG_NODE); 130 | } 131 | 132 | default GraphTraversal cfgV(String nodeType) { 133 | return cfgV().has(NODE_TYPE, nodeType); 134 | } 135 | 136 | // Abstract syntax tree 137 | 138 | default GraphTraversal astV() { 139 | return V().hasLabel(AST_NODE); 140 | } 141 | 142 | default GraphTraversal astV(String nodeType) { 143 | return astV().has(NODE_TYPE, nodeType); 144 | } 145 | 146 | // ******************************************************************************************** 147 | // in traversals 148 | // ******************************************************************************************** 149 | 150 | // Control flow graph 151 | 152 | default GraphTraversal cfgIn() { 153 | return in(CFG_EDGE); 154 | } 155 | 156 | default GraphTraversal cfgIn(boolean interproc) { 157 | return inE(CFG_EDGE).has(INTERPROC, interproc).outV(); 158 | } 159 | 160 | default GraphTraversal cfgIn(String edgeType) { 161 | return inE(CFG_EDGE).has(EDGE_TYPE, edgeType).outV(); 162 | } 163 | 164 | default GraphTraversal cfgIn(boolean interproc, String edgeType) { 165 | return inE(CFG_EDGE) 166 | .has(INTERPROC, interproc) 167 | .has(EDGE_TYPE, edgeType) 168 | .outV(); 169 | } 170 | 171 | // Abstract syntax tree 172 | 173 | default GraphTraversal astIn() { 174 | return in(AST_EDGE); 175 | } 176 | 177 | default GraphTraversal astIn(String edgeType) { 178 | return inE(AST_EDGE).has(EDGE_TYPE, edgeType).outV(); 179 | } 180 | 181 | // Program dependence graph 182 | 183 | default GraphTraversal pdgIn() { 184 | return in(PDG_EDGE); 185 | } 186 | 187 | default GraphTraversal pdgIn(boolean interproc) { 188 | return inE(PDG_EDGE).has(INTERPROC, interproc).outV(); 189 | } 190 | 191 | default GraphTraversal pdgIn(String edgeType) { 192 | return inE(PDG_EDGE).has(EDGE_TYPE, edgeType).outV(); 193 | } 194 | 195 | default GraphTraversal pdgIn(boolean interproc, String edgeType) { 196 | return inE(PDG_EDGE) 197 | .has(INTERPROC, interproc) 198 | .has(EDGE_TYPE, edgeType) 199 | .outV(); 200 | } 201 | 202 | default GraphTraversal dataDepIn() { 203 | return inE(PDG_EDGE).has(EDGE_TYPE, DATA_DEP).outV(); 204 | } 205 | 206 | default GraphTraversal dataDepIn(String varName) { 207 | return inE(PDG_EDGE) 208 | .has(EDGE_TYPE, DATA_DEP) 209 | .has(VAR_NAME, varName) 210 | .outV(); 211 | } 212 | 213 | // ******************************************************************************************** 214 | // out traversals 215 | // ******************************************************************************************** 216 | 217 | // Control flow graph 218 | 219 | default GraphTraversal cfgOut() { 220 | return out(CFG_EDGE); 221 | } 222 | 223 | default GraphTraversal cfgOut(boolean interproc) { 224 | return outE(CFG_EDGE).has(INTERPROC, interproc).inV(); 225 | } 226 | 227 | default GraphTraversal cfgOut(String edgeType) { 228 | return outE(CFG_EDGE).has(EDGE_TYPE, edgeType).inV(); 229 | } 230 | 231 | default GraphTraversal cfgOut(boolean interproc, String edgeType) { 232 | return outE(CFG_EDGE) 233 | .has(INTERPROC, interproc) 234 | .has(EDGE_TYPE, edgeType) 235 | .outV(); 236 | } 237 | 238 | default GraphTraversal outEmpty() { 239 | return outE(CFG_EDGE).has(EDGE_TYPE, EMPTY).inV(); 240 | } 241 | 242 | // Abstract syntax tree 243 | 244 | default GraphTraversal astOut() { 245 | return out(AST_EDGE); 246 | } 247 | 248 | default GraphTraversal astOut(String edgeType) { 249 | return outE(AST_EDGE).has(EDGE_TYPE, edgeType).inV(); 250 | } 251 | 252 | // Program dependence graph 253 | 254 | default GraphTraversal pdgOut() { 255 | return out(PDG_EDGE); 256 | } 257 | 258 | default GraphTraversal pdgOut(boolean interproc) { 259 | return outE(PDG_EDGE).has(INTERPROC, interproc).inV(); 260 | } 261 | 262 | default GraphTraversal pdgOut(String edgeType) { 263 | return outE(PDG_EDGE).has(EDGE_TYPE, edgeType).inV(); 264 | } 265 | 266 | default GraphTraversal pdgOut(boolean interproc, String edgeType) { 267 | return outE(PDG_EDGE) 268 | .has(INTERPROC, interproc) 269 | .has(EDGE_TYPE, edgeType) 270 | .inV(); 271 | } 272 | 273 | default GraphTraversal outDataDep() { 274 | return outE(PDG_EDGE).has(EDGE_TYPE, DATA_DEP).inV(); 275 | } 276 | 277 | default GraphTraversal outDataDep(String varName) { 278 | return outE(PDG_EDGE).has(VAR_NAME, varName).inV(); 279 | } 280 | 281 | // ******************************************************************************************** 282 | // inE traversals 283 | // ******************************************************************************************** 284 | 285 | // Control flow graph 286 | 287 | default GraphTraversal cfgInE() { 288 | return inE(CFG_EDGE); 289 | } 290 | 291 | default GraphTraversal cfgInE(String edgeType) { 292 | return cfgInE().has(EDGE_TYPE, edgeType); 293 | } 294 | 295 | // Abstract syntax tree 296 | 297 | default GraphTraversal astInE() { 298 | return inE(AST_EDGE); 299 | } 300 | 301 | default GraphTraversal astInE(String edgeType) { 302 | return astInE().has(EDGE_TYPE, edgeType); 303 | } 304 | 305 | // Program dependence graph 306 | 307 | default GraphTraversal pdgInE() { 308 | return inE(PDG_EDGE); 309 | } 310 | 311 | default GraphTraversal pdgInE(String edgeType) { 312 | return pdgInE().has(EDGE_TYPE, edgeType); 313 | } 314 | 315 | // ******************************************************************************************** 316 | // outE traversals 317 | // ******************************************************************************************** 318 | 319 | // Control flow graph 320 | 321 | default GraphTraversal cfgOutE() { 322 | return outE(CFG_EDGE); 323 | } 324 | 325 | default GraphTraversal cfgOutE(String edgeType) { 326 | return cfgOutE().has(EDGE_TYPE, edgeType); 327 | } 328 | 329 | // Abstract syntax tree 330 | 331 | default GraphTraversal astOutE() { 332 | return outE(AST_EDGE); 333 | } 334 | 335 | default GraphTraversal astOutE(String edgeType) { 336 | return astOutE().has(EDGE_TYPE, edgeType); 337 | } 338 | 339 | // Program dependence graph 340 | 341 | default GraphTraversal pdgOutE() { 342 | return outE(PDG_EDGE); 343 | } 344 | 345 | default GraphTraversal pdgOutE(String edgeType) { 346 | return pdgOutE().has(EDGE_TYPE, edgeType); 347 | } 348 | 349 | // ******************************************************************************************** 350 | // addV traversals 351 | // ******************************************************************************************** 352 | 353 | default GraphTraversal addCpgRoot(String name, String target, String classpath) { 354 | return (GraphTraversal) addV(CPG_ROOT) 355 | .property(NODE_TYPE, CPG_ROOT) 356 | .property(PROJECT_NAME, name) 357 | .property(TARGET_DIR, target) 358 | .property(CLASSPATH, classpath) 359 | .property(TEXT_LABEL, name); 360 | } 361 | 362 | 363 | default GraphTraversal addPackage(String name) { 364 | return addAstV(PACKAGE, name).property(PACKAGE_NAME, name); 365 | } 366 | 367 | // Control flow graph 368 | 369 | default GraphTraversal addCfgV() { 370 | return (GraphTraversal) addV(CFG_NODE); 371 | } 372 | 373 | default GraphTraversal addCfgV(String nodeType, String textLabel) { 374 | return addCfgV() 375 | .property(NODE_TYPE, nodeType) 376 | .property(TEXT_LABEL, textLabel); 377 | } 378 | 379 | default GraphTraversal addStmtNode(String nodeType, String textLabel, int line) { 380 | return addCfgV(nodeType, textLabel) 381 | .property(SRC_LINE_NO, line); 382 | } 383 | 384 | default GraphTraversal addEntryNode(String name, String signature, String type) { 385 | return addCfgV(ENTRY, name) 386 | .property(METHOD_NAME, name) 387 | .property(METHOD_SIG, signature) 388 | .property(JAVA_TYPE, type); 389 | } 390 | 391 | // Abstract syntax tree 392 | 393 | default GraphTraversal addAstV() { 394 | return (GraphTraversal) addV(AST_NODE); 395 | } 396 | 397 | default GraphTraversal addAstV(String nodeType, String textLabel) { 398 | return addAstV() 399 | .property(NODE_TYPE, nodeType) 400 | .property(TEXT_LABEL, textLabel); 401 | } 402 | 403 | default GraphTraversal addExprNode(String exprType, String textLabel, String javaType) { 404 | return addAstV(EXPR, textLabel) 405 | .property(EXPR_TYPE, exprType) 406 | .property(JAVA_TYPE, javaType); 407 | } 408 | 409 | default GraphTraversal addConstNode(String javaType, String textLabel, String value) { 410 | return addAstV(CONSTANT, textLabel) 411 | .property(JAVA_TYPE, javaType) 412 | .property(VALUE, value); 413 | } 414 | 415 | default GraphTraversal addRefNode(String refType, String textLabel, String javaType) { 416 | return addAstV(REF, textLabel) 417 | .property(REF_TYPE, refType) 418 | .property(JAVA_TYPE, javaType); 419 | } 420 | 421 | default GraphTraversal addLocalNode(String javaType, String textLabel, String name) { 422 | return addAstV(LOCAL_VAR, textLabel) 423 | .property(JAVA_TYPE, javaType) 424 | .property(NAME, name); 425 | } 426 | 427 | // ******************************************************************************************** 428 | // addE traversals 429 | // ******************************************************************************************** 430 | 431 | // Control flow graph 432 | 433 | default GraphTraversal addCfgE() { 434 | return (GraphTraversal) addE(CFG_EDGE); 435 | } 436 | 437 | default GraphTraversal addCfgE(String edgeType, String textLabel, boolean interproc) { 438 | return addCfgE() 439 | .property(EDGE_TYPE, edgeType) 440 | .property(TEXT_LABEL, textLabel) 441 | .property(INTERPROC, interproc); 442 | } 443 | 444 | default GraphTraversal addEmptyEdge() { 445 | return addCfgE(EMPTY, EMPTY, false); 446 | } 447 | 448 | default GraphTraversal addCondEdge(String condition) { 449 | return addCfgE(CONDITION, condition, false) 450 | .property(CONDITION, condition); 451 | } 452 | 453 | default GraphTraversal genCallEdge() { 454 | return addCfgE(CALL, CALL, true); 455 | } 456 | 457 | default GraphTraversal genRetEdge() { 458 | return addCfgE(RET, RET, true); 459 | } 460 | 461 | // Abstract syntax tree 462 | 463 | default GraphTraversal addAstE() { 464 | return (GraphTraversal) addE(AST_EDGE); 465 | } 466 | 467 | default GraphTraversal addAstE(String edgeType, String textLabel) { 468 | return addAstE() 469 | .property(EDGE_TYPE, edgeType) 470 | .property(TEXT_LABEL, textLabel); 471 | } 472 | 473 | // Program dependence graph 474 | 475 | default GraphTraversal addPdgE() { 476 | return (GraphTraversal) addE(PDG_EDGE); 477 | } 478 | 479 | default GraphTraversal addPdgE(String edgeType, String textLabel) { 480 | return addPdgE() 481 | .property(EDGE_TYPE, edgeType) 482 | .property(TEXT_LABEL, textLabel); 483 | } 484 | 485 | default GraphTraversal addDataDepE(String var) { 486 | return addPdgE(DATA_DEP, var) 487 | .property(VAR_NAME, var); 488 | } 489 | 490 | default GraphTraversal addArgDepE() { 491 | return addPdgE(ARG_DEP, ARG_DEP); 492 | } 493 | 494 | default GraphTraversal addRetDepE() { 495 | return addPdgE(RET_DEP, RET_DEP); 496 | } 497 | 498 | // ******************************************************************************************** 499 | // statement AST traversals 500 | // ******************************************************************************************** 501 | 502 | /** 503 | * Get the value node of an assign statement. 504 | * 505 | * @return the value node of an assign stmt 506 | */ 507 | default GraphTraversal getVal() { 508 | return hasLabel(CFG_NODE).has(NODE_TYPE, ASSIGN_STMT) // ensure we're on an assign stmt 509 | .outE(AST_EDGE).has(EDGE_TYPE, VALUE) 510 | .inV(); 511 | } 512 | 513 | /** 514 | * Get the target node of an assign statement. 515 | * 516 | * @return the value node of an assign stmt 517 | */ 518 | default GraphTraversal getTgt() { 519 | return hasLabel(CFG_NODE).has(NODE_TYPE, ASSIGN_STMT) // ensure we're on an assign stmt 520 | .outE(AST_EDGE).has(EDGE_TYPE, TARGET) 521 | .inV(); 522 | } 523 | 524 | } -------------------------------------------------------------------------------- /src/main/java/graft/traversal/CpgTraversalSourceDsl.java: -------------------------------------------------------------------------------- 1 | package graft.traversal; 2 | 3 | import org.apache.tinkerpop.gremlin.process.traversal.Path; 4 | import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; 5 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 6 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 7 | import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; 8 | 9 | import org.apache.tinkerpop.gremlin.structure.Edge; 10 | import org.apache.tinkerpop.gremlin.structure.Graph; 11 | import org.apache.tinkerpop.gremlin.structure.Vertex; 12 | 13 | import static graft.Const.*; 14 | import static graft.traversal.__.*; 15 | 16 | /** 17 | * Custom traversal sources for the Graft CPG traversal DSL. 18 | * 19 | * @author Wim Keirsgieter 20 | */ 21 | public class CpgTraversalSourceDsl extends GraphTraversalSource { 22 | 23 | // ******************************************************************************************** 24 | // constructors 25 | // ******************************************************************************************** 26 | 27 | public CpgTraversalSourceDsl(Graph graph, TraversalStrategies traversalStrategies) { 28 | super(graph, traversalStrategies); 29 | } 30 | 31 | public CpgTraversalSourceDsl(Graph graph) { 32 | super(graph); 33 | } 34 | 35 | // ******************************************************************************************** 36 | // general purpose traversal sources 37 | // ******************************************************************************************** 38 | 39 | /** 40 | * Return the root of the CPG. 41 | * 42 | * @return the CPG root 43 | */ 44 | public CpgTraversal cpgRoot() { 45 | return getV() 46 | .hasLabel(CPG_ROOT) 47 | .has(NODE_TYPE, CPG_ROOT); 48 | } 49 | 50 | public CpgTraversal locals(String varName) { 51 | return getV() 52 | .hasLabel(AST_NODE) 53 | .has(NODE_TYPE, LOCAL_VAR) 54 | .has(NAME, varName); 55 | } 56 | 57 | public CpgTraversal arg(String sigPattern, int index) { 58 | return callsTo(sigPattern) 59 | .outE(AST_EDGE) 60 | .has(EDGE_TYPE, ARG) 61 | .has(INDEX, index) 62 | .inV(); 63 | } 64 | 65 | public CpgTraversal packageNodes() { 66 | return astV(PACKAGE); 67 | } 68 | 69 | public CpgTraversal packageOf(String packName) { 70 | return packageNodes().has(PACKAGE_NAME, packName); 71 | } 72 | 73 | public CpgTraversal classNodes() { 74 | return astV(CLASS); 75 | } 76 | 77 | public CpgTraversal classOf(String fullName) { 78 | return classNodes().has(FULL_NAME, fullName); 79 | } 80 | 81 | public CpgTraversal entries() { 82 | return cfgV(ENTRY); 83 | } 84 | 85 | public CpgTraversal entryOf(String methodSig) { 86 | return entries().has(METHOD_SIG, methodSig); 87 | } 88 | 89 | public CpgTraversal stmtsOf(String methodSig) { 90 | return entryOf(methodSig).astOut(STATEMENT); 91 | } 92 | 93 | public CpgTraversal returns() { 94 | return cfgV(RETURN_STMT); 95 | } 96 | 97 | public CpgTraversal returnsOf(String methodSig) { 98 | return entryOf(methodSig).astOut(STATEMENT).has(NODE_TYPE, RETURN_STMT); 99 | } 100 | 101 | @SuppressWarnings("unchecked") 102 | public CpgTraversal callsTo(String sigPattern) { 103 | return (CpgTraversal) invokeExprs().hasPattern(METHOD_SIG, sigPattern); 104 | } 105 | 106 | public CpgTraversal invokeExprs() { 107 | return astV(EXPR).has(EXPR_TYPE, INVOKE_EXPR); 108 | } 109 | 110 | public CpgTraversal invokesOf(String methodSig) { 111 | return invokeExprs().has(METHOD_SIG, methodSig); 112 | } 113 | 114 | public CpgTraversal paramsOf(String methodSig) { 115 | return entryOf(methodSig) 116 | .astOut(STATEMENT).has(NODE_TYPE, ASSIGN_STMT) 117 | .where(getVal().and( 118 | has(NODE_TYPE, REF), 119 | has(REF_TYPE, PARAM_REF) 120 | ) 121 | ); 122 | } 123 | 124 | // ******************************************************************************************** 125 | // V traversals 126 | // ******************************************************************************************** 127 | 128 | // Control flow graph 129 | 130 | public CpgTraversal cfgV() { 131 | return getV().hasLabel(CFG_NODE); 132 | } 133 | 134 | public CpgTraversal cfgV(String nodeType) { 135 | return cfgV().has(NODE_TYPE, nodeType); 136 | } 137 | 138 | // Abstract syntax tree 139 | 140 | public CpgTraversal astV() { 141 | return getV().hasLabel(AST_NODE); 142 | } 143 | 144 | public CpgTraversal astV(String nodeType) { 145 | return astV().has(NODE_TYPE, nodeType); 146 | } 147 | 148 | // ******************************************************************************************** 149 | // E traversals 150 | // ******************************************************************************************** 151 | 152 | // Control flow graph 153 | 154 | public CpgTraversal cfgE() { 155 | return getE().hasLabel(CFG_EDGE); 156 | } 157 | 158 | public CpgTraversal cfgE(boolean interproc) { 159 | return cfgE().has(INTERPROC, interproc); 160 | } 161 | 162 | public CpgTraversal cfgE(String edgeType) { 163 | return cfgE().has(EDGE_TYPE, edgeType); 164 | } 165 | 166 | public CpgTraversal cfgE(String edgeType, boolean interproc) { 167 | return cfgE(interproc).has(EDGE_TYPE, edgeType); 168 | } 169 | 170 | // Abstract syntax tree 171 | 172 | public CpgTraversal astE() { 173 | return getE().hasLabel(AST_EDGE); 174 | } 175 | 176 | public CpgTraversal astE(String edgeType) { 177 | return astE().has(EDGE_TYPE, edgeType); 178 | } 179 | 180 | // Program dependence graph 181 | 182 | public CpgTraversal pdgE() { 183 | return getE().hasLabel(PDG_EDGE); 184 | } 185 | 186 | public CpgTraversal pdgE(boolean interproc) { 187 | return pdgE().has(INTERPROC, interproc); 188 | } 189 | 190 | public CpgTraversal pdgE(String edgeType) { 191 | return pdgE().has(EDGE_TYPE, edgeType); 192 | } 193 | 194 | public CpgTraversal pdgE(String edgeType, boolean interproc) { 195 | return pdgE(interproc).has(EDGE_TYPE, edgeType); 196 | } 197 | 198 | // Aliasing 199 | 200 | public CpgTraversal aliasE() { 201 | return getE().hasLabel(MAY_ALIAS); 202 | } 203 | 204 | // ******************************************************************************************** 205 | // addV traversals 206 | // ******************************************************************************************** 207 | 208 | public CpgTraversal addCpgRoot(String name, String target, String classpath) { 209 | return (CpgTraversal) addV(CPG_ROOT) 210 | .property(NODE_TYPE, CPG_ROOT) 211 | .property(PROJECT_NAME, name) 212 | .property(TARGET_DIR, target) 213 | .property(CLASSPATH, classpath) 214 | .property(TEXT_LABEL, name); 215 | } 216 | 217 | public CpgTraversal addPackage(String name) { 218 | return addAstV(PACKAGE, name).property(PACKAGE_NAME, name); 219 | } 220 | 221 | // Control flow graph 222 | 223 | public CpgTraversal addCfgV() { 224 | return (CpgTraversal) addV(CFG_NODE); 225 | } 226 | 227 | public CpgTraversal addCfgV(String nodeType, String textLabel) { 228 | return addCfgV() 229 | .property(NODE_TYPE, nodeType) 230 | .property(TEXT_LABEL, textLabel); 231 | } 232 | 233 | public CpgTraversal addStmtNode(String nodeType, String textLabel, int line) { 234 | return addCfgV(nodeType, textLabel) 235 | .property(SRC_LINE_NO, line); 236 | } 237 | 238 | public CpgTraversal addEntryNode(String name, String signature, String type) { 239 | return addCfgV(ENTRY, name) 240 | .property(METHOD_NAME, name) 241 | .property(METHOD_SIG, signature) 242 | .property(JAVA_TYPE, type); 243 | } 244 | 245 | // Abstract syntax tree 246 | 247 | public CpgTraversal addAstV() { 248 | return (CpgTraversal) addV(AST_NODE); 249 | } 250 | 251 | public CpgTraversal addAstV(String nodeType, String textLabel) { 252 | return addAstV() 253 | .property(NODE_TYPE, nodeType) 254 | .property(TEXT_LABEL, textLabel); 255 | } 256 | 257 | public CpgTraversal addExprNode(String exprType, String textLabel, String javaType) { 258 | return addAstV(EXPR, textLabel) 259 | .property(EXPR_TYPE, exprType) 260 | .property(JAVA_TYPE, javaType); 261 | } 262 | 263 | public CpgTraversal addConstNode(String javaType, String textLabel, String value) { 264 | return addAstV(CONSTANT, textLabel) 265 | .property(JAVA_TYPE, javaType) 266 | .property(VALUE, value); 267 | } 268 | 269 | public CpgTraversal addRefNode(String refType, String textLabel, String javaType) { 270 | return addAstV(REF, textLabel) 271 | .property(REF_TYPE, refType) 272 | .property(JAVA_TYPE, javaType); 273 | } 274 | 275 | public CpgTraversal addLocalNode(String javaType, String textLabel, String name) { 276 | return addAstV(LOCAL_VAR, textLabel) 277 | .property(JAVA_TYPE, javaType) 278 | .property(NAME, name); 279 | } 280 | 281 | // ******************************************************************************************** 282 | // addE traversals 283 | // ******************************************************************************************** 284 | 285 | // Control flow graph 286 | 287 | public CpgTraversal addCfgE() { 288 | return (CpgTraversal) addE(CFG_EDGE); 289 | } 290 | 291 | public CpgTraversal addCfgE(String edgeType, String textLabel, boolean interproc) { 292 | return addCfgE() 293 | .property(EDGE_TYPE, edgeType) 294 | .property(TEXT_LABEL, textLabel) 295 | .property(INTERPROC, interproc); 296 | } 297 | 298 | public CpgTraversal addEmptyEdge() { 299 | return addCfgE(EMPTY, EMPTY, false); 300 | } 301 | 302 | public CpgTraversal addCondEdge(String condition) { 303 | return addCfgE(CONDITION, condition, false) 304 | .property(CONDITION, condition); 305 | } 306 | 307 | public CpgTraversal genCallEdge() { 308 | return addCfgE(CALL, CALL, true); 309 | } 310 | 311 | public CpgTraversal genRetEdge() { 312 | return addCfgE(RET, RET, true); 313 | } 314 | 315 | // Abstract syntax tree 316 | 317 | public CpgTraversal addAstE() { 318 | return (CpgTraversal) addE(AST_EDGE); 319 | } 320 | 321 | public CpgTraversal addAstE(String edgeType, String textLabel) { 322 | return addAstE() 323 | .property(EDGE_TYPE, edgeType) 324 | .property(TEXT_LABEL, textLabel); 325 | } 326 | 327 | // Program dependence graph 328 | 329 | public CpgTraversal addPdgE() { 330 | return (CpgTraversal) addE(PDG_EDGE); 331 | } 332 | 333 | public CpgTraversal addPdgE(String edgeType, String textLabel) { 334 | return addPdgE() 335 | .property(EDGE_TYPE, edgeType) 336 | .property(TEXT_LABEL, textLabel); 337 | } 338 | 339 | public CpgTraversal addDataDepE(String var) { 340 | return addPdgE(DATA_DEP, var) 341 | .property(VAR_NAME, var); 342 | } 343 | 344 | public CpgTraversal addArgDepE() { 345 | return addPdgE(ARG_DEP, ARG_DEP); 346 | } 347 | 348 | public CpgTraversal addRetDepE() { 349 | return addPdgE(RET_DEP, RET_DEP); 350 | } 351 | 352 | // ******************************************************************************************** 353 | // grab specific statement types 354 | // ******************************************************************************************** 355 | 356 | /** 357 | * Get all assign statements of the form x = new T(), where x is a local 358 | * variable. 359 | * 360 | * @return all assign statements of the form x = new T() 361 | */ 362 | public CpgTraversal getNewAssignStmts() { 363 | return getAssignStmts() 364 | .getVal().has(NODE_TYPE, NEW_EXPR) 365 | .in(AST_EDGE) 366 | .getTgt().has(NODE_TYPE, LOCAL_VAR) 367 | .in(AST_EDGE); 368 | } 369 | 370 | /** 371 | * Get all assign statements of the form x = y, where x and y are both 372 | * local variables with reference types. 373 | * 374 | * @return all assign statements of the form x = y, where x and y are ref types 375 | */ 376 | public CpgTraversal getRefAssignStmts() { 377 | return getAssignStmts() 378 | .where(and( 379 | getVal() 380 | .has(NODE_TYPE, LOCAL_VAR) 381 | .has(REF_TYPE, true), 382 | getTgt() 383 | .has(NODE_TYPE, LOCAL_VAR) 384 | .has(REF_TYPE, true) 385 | )); 386 | } 387 | 388 | /** 389 | * Get all assign statements of the form x.f = y, where x is a local variable 390 | * and y is a reference type. 391 | * 392 | * @param varName the name of the local variable 393 | * @return all store statements on attributes of that variable 394 | */ 395 | public CpgTraversal getStoreStmts(String varName) { 396 | return getAssignStmts() 397 | .getTgt().or( 398 | has(NODE_TYPE, INSTANCE_FIELD_REF), 399 | has(NODE_TYPE, STATIC_FIELD_REF) 400 | ).outE(AST_EDGE).has(EDGE_TYPE, BASE) 401 | .inV() 402 | .has(NAME, varName) 403 | .in(AST_EDGE).in(AST_EDGE); 404 | } 405 | 406 | /** 407 | * Get all assign statements of the form y = x.f, where x is a local variable 408 | * and y is a reference type. 409 | * 410 | * @param varName the name of the local variable 411 | * @return all load statements on attributes of that variable 412 | */ 413 | public CpgTraversal getLoadStmts(String varName) { 414 | return getAssignStmts() 415 | .getVal() 416 | .or(has(NODE_TYPE, STATIC_FIELD_REF, has(NODE_TYPE, INSTANCE_FIELD_REF))) 417 | .outE(AST_EDGE).has(EDGE_TYPE, BASE) 418 | .has(NODE_TYPE, LOCAL_VAR) 419 | .has(NAME, varName) 420 | .in(AST_EDGE).in(AST_EDGE) 421 | .getTgt() 422 | .has(NODE_TYPE, LOCAL_VAR); 423 | } 424 | 425 | /** 426 | * Get all references to fields of the given (reference type) variable, both 427 | * instance and static. 428 | * 429 | * @param varName the name of the variable 430 | * @return all field refs of the given variable 431 | */ 432 | public CpgTraversal getFieldRefs(String varName) { 433 | return getFieldRefs() 434 | .outE(AST_EDGE).has(EDGE_TYPE, BASE) 435 | .inV() 436 | .has(NODE_TYPE, LOCAL_VAR) 437 | .has(NAME, varName); 438 | } 439 | 440 | /** 441 | * Get all instance and static field references. 442 | * 443 | * @return all field refs in the graph 444 | */ 445 | public CpgTraversal getFieldRefs() { 446 | return astV() 447 | .or( 448 | has(NODE_TYPE, INSTANCE_FIELD_REF), 449 | has(NODE_TYPE, STATIC_FIELD_REF) 450 | ); 451 | } 452 | 453 | /** 454 | * Get all assign statements in the graph. 455 | * 456 | * @return all assign statements in the graph 457 | */ 458 | public CpgTraversal getAssignStmts() { 459 | return cfgV(ASSIGN_STMT); 460 | } 461 | 462 | // ******************************************************************************************** 463 | // paths between vertices 464 | // ******************************************************************************************** 465 | 466 | public CpgTraversal pathsBetween(Vertex v, Vertex w, String edgeLabel) { 467 | return (CpgTraversal) V(v) 468 | .repeat(timeLimit(1000).out(edgeLabel).simplePath()) 469 | .until(is(w)) 470 | .path().dedup(); 471 | } 472 | 473 | @SuppressWarnings("unchecked") 474 | public CpgTraversal dataFlowsBetween(CpgTraversal source, CpgTraversal sink) { 475 | return getV().where(source) 476 | .repeat(timeLimit(1000).outDataDep().simplePath()) 477 | .until(sink) 478 | .path(); 479 | } 480 | 481 | public CpgTraversal controlFlowsBetween(Vertex v, Vertex w) { 482 | return (CpgTraversal) V(v) 483 | .repeat(timeLimit(1000).outEmpty().simplePath()) 484 | .until(is(w)) 485 | .path(); 486 | } 487 | 488 | // ******************************************************************************************** 489 | // utility traversals 490 | // ******************************************************************************************** 491 | 492 | protected CpgTraversal getV() { 493 | CpgTraversalSource clone = (CpgTraversalSource) this.clone(); 494 | clone.getBytecode().addStep(GraphTraversal.Symbols.V); 495 | 496 | CpgTraversal traversal = new DefaultCpgTraversal<>(clone); 497 | traversal.asAdmin().addStep(new GraphStep<>(traversal.asAdmin(), Vertex.class, true)); 498 | 499 | return traversal; 500 | } 501 | 502 | protected CpgTraversal getE() { 503 | CpgTraversalSource clone = (CpgTraversalSource) this.clone(); 504 | clone.getBytecode().addStep(GraphTraversal.Symbols.E); 505 | 506 | CpgTraversal traversal = new DefaultCpgTraversal<>(clone); 507 | traversal.asAdmin().addStep(new GraphStep<>(traversal.asAdmin(), Edge.class, true)); 508 | 509 | return traversal; 510 | } 511 | 512 | } 513 | -------------------------------------------------------------------------------- /src/main/java/graft/utils/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package graft.utils; 2 | 3 | /** 4 | * Miscellaneous utility methods for display and output. 5 | * 6 | * @author Wim Keirsgieter 7 | */ 8 | public class DisplayUtil { 9 | 10 | /** 11 | * Get the given elapsed time in human-readable form. 12 | * 13 | * @param millis elapsed time in milliseconds 14 | * @return the elapsed time in human-readable form. 15 | */ 16 | public static String displayTime(long millis) { 17 | if (millis < 1000) { 18 | return displayMillis(millis); 19 | } else if (millis < 60000) { 20 | return displaySeconds(millis); 21 | } else { 22 | return displayMinutes(millis); 23 | } 24 | } 25 | 26 | private static String displayMillis(long millis) { 27 | return millis + "ms"; 28 | } 29 | 30 | private static String displaySeconds(long millis) { 31 | return (millis / 1000) + " seconds (" + millis + "ms)"; 32 | } 33 | 34 | private static String displayMinutes(long millis) { 35 | return (millis / 60000) + " minutes (" + millis + "ms)"; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/graft/utils/DotUtil.java: -------------------------------------------------------------------------------- 1 | package graft.utils; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Edge; 4 | import org.apache.tinkerpop.gremlin.structure.Vertex; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.File; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | 13 | import graft.cpg.structure.CodePropertyGraph; 14 | import graft.traversal.CpgTraversal; 15 | 16 | import static graft.Const.*; 17 | 18 | /** 19 | * Utilities for writing the CPG to a dot file. 20 | * 21 | * @author Wim Keirsgieter 22 | */ 23 | public class DotUtil { 24 | 25 | private static Logger log = LoggerFactory.getLogger(DotUtil.class); 26 | 27 | /** 28 | * Write the given CPG to a dot file. 29 | * 30 | * @param graph the CPG to write to the file 31 | * @param filename the name of the dot file 32 | */ 33 | public static void graphToDot(CodePropertyGraph graph, String filename) { 34 | try { 35 | FileWriter out = new FileWriter(new File(filename)); 36 | out.write("digraph " + graph.name() + "{\n"); 37 | CpgTraversal nodes = graph.traversal().V(); 38 | while (nodes.hasNext()) { 39 | vertexToDot((Vertex) nodes.next(), out); 40 | } 41 | CpgTraversal edges = graph.traversal().E(); 42 | while (edges.hasNext()) { 43 | edgeToDot((Edge) edges.next(), out); 44 | } 45 | out.write("}"); 46 | out.close(); 47 | } catch (IOException e) { 48 | log.error("Unable to write graph '{}' to dotfile '{}'", graph.name(), filename, e); 49 | } 50 | } 51 | 52 | private static void vertexToDot(Vertex v, FileWriter out) throws IOException { 53 | out.write(v.id() + " [style=filled, shape=box"); 54 | switch (v.label()) { 55 | case CFG_NODE: 56 | cfgNodeToDot(v, out); 57 | break; 58 | case CPG_ROOT: 59 | case AST_NODE: 60 | astNodeToDot(v, out); 61 | break; 62 | default: 63 | log.warn("There are nodes with unrecognized labels in the CPG: '{}'", v.label()); 64 | } 65 | } 66 | 67 | private static void edgeToDot(Edge e, FileWriter out) throws IOException { 68 | out.write(e.outVertex().id() + " -> " + e.inVertex().id()); 69 | out.write(" [style=bold"); 70 | switch (e.label()) { 71 | case CFG_EDGE: 72 | cfgEdgeToDot(e, out); 73 | break; 74 | case AST_EDGE: 75 | astEdgeToDot(e, out); 76 | break; 77 | case PDG_EDGE: 78 | pdgEdgeToDot(e, out); 79 | break; 80 | case MAY_ALIAS: 81 | aliasEdgeToDot(e, out); 82 | break; 83 | default: 84 | log.warn("There are edges with unrecognized labels in the CPG: '{}'", e.label()); 85 | } 86 | } 87 | 88 | private static void cfgNodeToDot(Vertex v, FileWriter out) throws IOException { 89 | out.write(", color=blue"); 90 | out.write(", label=\"" + v.value(TEXT_LABEL).toString().replace("\"", "'")); 91 | out.write("\"];\n"); 92 | } 93 | 94 | private static void astNodeToDot(Vertex v, FileWriter out) throws IOException { 95 | out.write(", color=green"); 96 | out.write(", label=\"" + v.value(TEXT_LABEL).toString().replace("\"", "'")); 97 | out.write("\"];\n"); 98 | } 99 | 100 | private static void cfgEdgeToDot(Edge e, FileWriter out) throws IOException { 101 | out.write(", color=blue"); 102 | out.write(", label=\"" + e.value(TEXT_LABEL).toString()); 103 | out.write("\"];\n"); 104 | } 105 | 106 | private static void astEdgeToDot(Edge e, FileWriter out) throws IOException { 107 | out.write(", color=green, label=\"" + e.value(TEXT_LABEL) + "\"];\n"); 108 | } 109 | 110 | private static void pdgEdgeToDot(Edge e, FileWriter out) throws IOException { 111 | out.write(", color=red, label=\"" + e.value(TEXT_LABEL) + "\"];\n"); 112 | } 113 | 114 | private static void aliasEdgeToDot(Edge e, FileWriter out) throws IOException { 115 | out.write(", color=orange, label=\"" + e.value(TEXT_LABEL) + "\"];\n"); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/graft/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package graft.utils; 2 | 3 | import java.io.*; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.util.ArrayList; 7 | import java.util.Base64; 8 | import java.util.List; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import graft.GraftException; 14 | 15 | import static graft.Const.*; 16 | 17 | /** 18 | * Miscellaneous utility methods for dealing with Java class files. 19 | * 20 | * @author Wim Keirsgieter 21 | */ 22 | public class FileUtil { 23 | 24 | private static final int BUFFER_SIZE = 8192; 25 | private static final String HASH_ALGORITHM = "SHA-256"; 26 | 27 | private static Logger log = LoggerFactory.getLogger(FileUtil.class); 28 | 29 | /** 30 | * Hash the contents of a file. 31 | * 32 | * @param file the file to hash 33 | * @return the hash of the file 34 | * @throws GraftException if the operation fails 35 | */ 36 | public static String hashFile(File file) throws GraftException { 37 | // see https://stackoverflow.com/a/32032908/6208351 38 | byte[] buffer = new byte[BUFFER_SIZE]; 39 | int count; 40 | try { 41 | MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); 42 | BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); 43 | while ((count = in.read(buffer)) > 0) { 44 | digest.update(buffer, 0, count); 45 | } 46 | in.close(); 47 | byte[] hash = digest.digest(); 48 | return Base64.getEncoder().encodeToString(hash); 49 | } catch (NoSuchAlgorithmException | IOException e) { 50 | throw new GraftException("Could not hash file '" + file.getName() + "'", e); 51 | } 52 | } 53 | 54 | /** 55 | * Get the full name of the Java class in a given class file, relative to the given package 56 | * root directory. 57 | * 58 | * @param rootDir the package root directory 59 | * @param classFile the class file 60 | * @return the full Java class name 61 | */ 62 | public static String getClassName(File rootDir, File classFile) { 63 | return rootDir.toURI() 64 | .relativize(classFile.toURI()) 65 | .getPath() 66 | .replace('/', '.') 67 | .replace(".class", ""); 68 | } 69 | 70 | /** 71 | * Get the full name of a Java class from its file path. 72 | * 73 | * @param path the file path of the class 74 | * @return the class name 75 | */ 76 | public static String getClassName(String path) { 77 | return path 78 | .replace('/', '.') 79 | .replace(".class", ""); 80 | } 81 | 82 | /** 83 | * Get a list of all Java class files in a given directory, including its subdirectories. 84 | * 85 | * @param targetDir the name of the target directory 86 | * @return a list of all class files in the directory 87 | */ 88 | public static List getClassFiles(File targetDir) { 89 | List classFiles = new ArrayList<>(); 90 | for (File file : targetDir.listFiles()) { 91 | if (file.isDirectory()) { 92 | classFiles.addAll(getClassFiles(file)); 93 | } 94 | if (file.getName().matches(CLASS_FILE_REGEX)) { 95 | classFiles.add(file); 96 | } 97 | } 98 | return classFiles; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/graft/utils/LogUtil.java: -------------------------------------------------------------------------------- 1 | package graft.utils; 2 | 3 | import ch.qos.logback.classic.Level; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import graft.GraftRuntimeException; 9 | 10 | import static graft.Const.*; 11 | 12 | /** 13 | * Miscellaneous utility functions for working with logging libraries. 14 | */ 15 | public class LogUtil { 16 | 17 | /** 18 | * Set the root logging level of the project. 19 | * 20 | * @param logLevel the log level to set 21 | */ 22 | public static void setLogLevel(String logLevel) { 23 | ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); 24 | 25 | switch(logLevel) { 26 | case NONE: 27 | root.detachAppender("stdout"); 28 | break; 29 | case TRACE: 30 | root.setLevel(Level.TRACE); 31 | break; 32 | case DEBUG: 33 | root.setLevel(Level.DEBUG); 34 | break; 35 | case INFO: 36 | root.setLevel(Level.INFO); 37 | break; 38 | case WARN: 39 | root.setLevel(Level.WARN); 40 | break; 41 | case ERROR: 42 | root.setLevel(Level.ERROR); 43 | break; 44 | case ALL: 45 | root.setLevel(Level.ALL); 46 | break; 47 | default: 48 | throw new GraftRuntimeException("Unrecognised log level '" + logLevel + "'"); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/graft/utils/SootUtil.java: -------------------------------------------------------------------------------- 1 | package graft.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import soot.PhaseOptions; 10 | import soot.Scene; 11 | import soot.jimple.Stmt; 12 | import soot.tagkit.*; 13 | 14 | import graft.GraftRuntimeException; 15 | import graft.Options; 16 | 17 | import static graft.Const.*; 18 | 19 | /** 20 | * Utility methods for dealing with Soot. 21 | * 22 | * @author Wim Keirsgieter 23 | */ 24 | public class SootUtil { 25 | 26 | private static Logger log = LoggerFactory.getLogger(SootUtil.class); 27 | private static boolean configured = false; 28 | 29 | /** 30 | * Configure Soot options for CPG transformation. 31 | */ 32 | public static void configureSoot() { 33 | if (configured) { 34 | throw new GraftRuntimeException("Soot has already been configured"); 35 | } 36 | configured = true; 37 | 38 | // set application mode 39 | soot.options.Options.v().set_app(true); 40 | 41 | // make sure classpath is configured correctly 42 | soot.options.Options.v().set_soot_classpath(Options.v().getString(OPT_CLASSPATH)); 43 | soot.options.Options.v().set_prepend_classpath(true); 44 | 45 | // keep debugging info 46 | soot.options.Options.v().set_keep_line_number(true); 47 | soot.options.Options.v().set_keep_offset(true); 48 | 49 | // ignore library code 50 | soot.options.Options.v().set_no_bodies_for_excluded(true); 51 | soot.options.Options.v().set_allow_phantom_refs(true); 52 | 53 | // exclude java.lang packages 54 | List excluded = new ArrayList<>(); 55 | excluded.add("java.lang"); 56 | soot.options.Options.v().set_exclude(excluded); 57 | 58 | // keep variable names 59 | PhaseOptions.v().setPhaseOption("jb", "use-original-names:true"); 60 | } 61 | 62 | /** 63 | * Given a list of class names, load them into the Scene. 64 | * 65 | * @param classNames a list of class names 66 | */ 67 | public static void loadClasses(String[] classNames) { 68 | if (!configured) { 69 | throw new GraftRuntimeException("Soot has not been configured"); 70 | } 71 | for (String className : classNames) { 72 | log.debug("Adding basic class '{}' to scene", className); 73 | Scene.v().addBasicClass(className); 74 | } 75 | log.debug("Loading classes..."); 76 | Scene.v().loadBasicClasses(); 77 | Scene.v().loadNecessaryClasses(); 78 | } 79 | 80 | /** 81 | * Get the line number of a Jimple statement if it is set. 82 | * 83 | * @param stmt the Jimple statement 84 | * @return the line number of the statement or -1 if not set 85 | */ 86 | public static int getLineNr(Stmt stmt) { 87 | if (stmt == null) { 88 | return -1; 89 | } 90 | if (stmt.getTag("SourceLnPosTag") != null) { 91 | return ((SourceLnPosTag) stmt.getTag("SourceLnPosTag")).startLn(); 92 | } else if (stmt.getTag("JimpleLineNumberTag") != null) { 93 | return ((JimpleLineNumberTag) stmt.getTag("JimpleLineNumberTag")).getLineNumber(); 94 | } else if (stmt.getTag("LineNumberTag") != null) { 95 | return ((LineNumberTag) stmt.getTag("LineNumberTag")).getLineNumber(); 96 | } else if (stmt.getTag("SourceLineNumberTag") != null) { 97 | return ((SourceLineNumberTag) stmt.getTag("SourceLineNumberTag")).getLineNumber(); 98 | } else { 99 | return -1; 100 | } 101 | } 102 | 103 | /** 104 | * Get the source file name of a Jimple statement if it is set. 105 | * 106 | * @param stmt the Jimple statement 107 | * @return the source file name of the statement or UNKNOWN if not set 108 | */ 109 | public static String getSourceFile(Stmt stmt) { 110 | if (stmt == null) { 111 | return UNKNOWN; 112 | } 113 | if (stmt.getTag("SourceFileTag") != null) { 114 | return ((SourceFileTag) stmt.getTag("SourceFileTag")).getSourceFile(); 115 | } else { 116 | return UNKNOWN; 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/resources/default.properties: -------------------------------------------------------------------------------- 1 | # TODO: document defaults here 2 | 3 | general.log-level = info 4 | db.implementation = tinkergraph 5 | db.file-format = json -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d{HH:mm:ss} %-5level [%logger{50}] => %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/java/graft/TestGraft.java: -------------------------------------------------------------------------------- 1 | package graft; 2 | 3 | public class TestGraft { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/graft/traversal/TestCpgTraversalDsl.java: -------------------------------------------------------------------------------- 1 | package graft.traversal; 2 | 3 | import graft.cpg.structure.CodePropertyGraph; 4 | import org.junit.BeforeClass; 5 | import org.junit.Test; 6 | 7 | public class TestCpgTraversalDsl { 8 | 9 | private static final String CPG_PATH = "src/test/resources/simple.json"; 10 | private static CodePropertyGraph cpg; 11 | 12 | @BeforeClass 13 | public static void setUpClass() { 14 | // cpg = CodePropertyGraph.fromFile(CPG_PATH); 15 | } 16 | 17 | @Test 18 | public void testMatches() { 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/graft/traversal/TestCpgTraversalSourceDsl.java: -------------------------------------------------------------------------------- 1 | package graft.traversal; 2 | 3 | public class TestCpgTraversalSourceDsl { 4 | 5 | } 6 | --------------------------------------------------------------------------------