From 6c679d1b3aea11d7b0df3f15b370755b34d4f062 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 9 Feb 2024 19:18:53 +0300 Subject: [PATCH 001/117] Taint analysis --- .gitignore | 4 + build.gradle.kts | 2 +- buildSrc/src/main/kotlin/Dependencies.kt | 40 +- buildSrc/src/main/kotlin/Tests.kt | 2 + jacodb-analysis/.gitignore | 3 + jacodb-analysis/README.md | 2 +- jacodb-analysis/build.gradle.kts | 3 + .../org/jacodb/analysis/AnalysisMain.kt | 11 +- .../org/jacodb/analysis/config/Condition.kt | 197 ++++ .../org/jacodb/analysis/config/Position.kt | 89 ++ .../org/jacodb/analysis/config/TaintAction.kt | 112 +++ .../analysis/engine/AbstractAnalyzer.kt | 8 +- .../jacodb/analysis/engine/AnalyzerFactory.kt | 44 +- .../engine/BaseIfdsUnitRunnerFactory.kt | 355 +++++-- .../engine/BidiIfdsUnitRunnerFactory.kt | 56 +- .../org/jacodb/analysis/engine/IfdsEdge.kt | 17 +- .../org/jacodb/analysis/engine/IfdsResult.kt | 47 +- .../jacodb/analysis/engine/IfdsUnitManager.kt | 16 +- .../analysis/engine/IfdsUnitRunnerFactory.kt | 21 +- .../org/jacodb/analysis/engine/IfdsVertex.kt | 7 +- .../analysis/engine/MainIfdsUnitManager.kt | 180 +++- .../jacodb/analysis/engine/SummaryStorage.kt | 65 +- .../jacodb/analysis/engine/UnitResolver.kt | 61 +- .../analysis/engine/VulnerabilityInstance.kt | 8 +- .../analysis/graph/ApplicationGraphFactory.kt | 20 +- ...ApplicationGraphs.kt => BackwardGraphs.kt} | 27 +- .../org/jacodb/analysis/graph/GraphExt.kt | 91 ++ .../analysis/graph/JcApplicationGraphImpl.kt | 39 +- .../org/jacodb/analysis/graph/JcNoopInst.kt | 33 + .../graph/SimplifiedJcApplicationGraph.kt | 77 +- .../org/jacodb/analysis/ifds2/Aggregate.kt | 128 +++ .../org/jacodb/analysis/ifds2/Analyzer.kt | 34 + .../kotlin/org/jacodb/analysis/ifds2/Edge.kt | 63 ++ .../jacodb/analysis/ifds2/FlowFunctions.kt | 110 ++ .../org/jacodb/analysis/ifds2/Manager.kt | 39 + .../org/jacodb/analysis/ifds2/Runner.kt | 292 ++++++ .../kotlin/org/jacodb/analysis/ifds2/Trace.kt | 83 ++ .../org/jacodb/analysis/ifds2/Vertex.kt | 42 + .../jacodb/analysis/ifds2/taint/BidiRunner.kt | 139 +++ .../analysis/ifds2/taint/TaintAnalyzers.kt | 182 ++++ .../analysis/ifds2/taint/TaintEvents.kt | 36 + .../jacodb/analysis/ifds2/taint/TaintFacts.kt | 55 + .../ifds2/taint/TaintFlowFunctions.kt | 756 ++++++++++++++ .../analysis/ifds2/taint/TaintManager.kt | 305 ++++++ .../analysis/ifds2/taint/TaintSummaries.kt | 42 + .../org/jacodb/analysis/ifds2/taint/entry.kt | 32 + .../jacodb/analysis/ifds2/taint/globals.kt | 22 + .../analysis/ifds2/taint/npe/NpeAnalyzers.kt | 172 ++++ .../ifds2/taint/npe/NpeFlowFunctions.kt | 943 ++++++++++++++++++ .../analysis/ifds2/taint/npe/NpeManager.kt | 284 ++++++ .../jacodb/analysis/ifds2/taint/npe/entry.kt | 33 + .../org/jacodb/analysis/ifds2/taint/types.kt | 25 + .../impl/custom/NullAssumptionAnalysis.kt | 5 +- .../analysis/library/UnitResolversLibrary.kt | 41 - .../AbstractTaintBackwardFunctions.kt | 49 +- .../AbstractTaintForwardFunctions.kt | 248 ++++- .../analysis/library/analyzers/NpeAnalyzer.kt | 105 +- .../library/analyzers/SqlInjectionAnalyzer.kt | 26 +- .../library/analyzers/TaintAnalyzer.kt | 268 ++++- .../analysis/library/analyzers/TaintNode.kt | 44 +- .../analyzers/UnusedVariableAnalyzer.kt | 20 +- .../jacodb/analysis/library/analyzers/Util.kt | 25 +- .../org/jacodb/analysis/paths/AccessPath.kt | 70 +- .../org/jacodb/analysis/paths/Accessors.kt | 33 +- .../kotlin/org/jacodb/analysis/paths/Util.kt | 106 +- .../org/jacodb/analysis/sarif/DataClasses.kt | 34 +- .../analysis/impl/JavaAnalysisApiTest.java | 10 +- .../jacodb/analysis/impl/AliasAnalysisTest.kt | 4 +- .../jacodb/analysis/impl/BaseAnalysisTest.kt | 82 +- .../org/jacodb/analysis/impl/BenchRunners.kt | 315 ++++++ .../org/jacodb/analysis/impl/Ifds2NpeTest.kt | 294 ++++++ .../org/jacodb/analysis/impl/Ifds2SqlTest.kt | 139 +++ .../analysis/impl/JodaDateTimeAnalysisTest.kt | 13 +- .../jacodb/analysis/impl/NpeAnalysisTest.kt | 14 +- .../analysis/impl/SqlInjectionAnalysisTest.kt | 30 +- .../analysis/impl/UnusedVariableTest.kt | 31 +- .../src/test/resources/additional.json | 523 ++++++++++ .../src/test/resources/config_small.json | 706 +++++++++++++ .../test/resources/simplelogger.properties | 35 + .../src/main/kotlin/org/jacodb/api/Classes.kt | 5 - .../jacodb/api/analysis/ApplicationGraph.kt | 2 +- .../jacodb/api/cfg/FullExprSetCollector.kt | 149 ++- .../org/jacodb/api/cfg/JcExprVisitor.kt | 136 +++ .../kotlin/org/jacodb/api/cfg/JcGraphs.kt | 7 +- .../main/kotlin/org/jacodb/api/cfg/JcInst.kt | 484 ++++----- .../org/jacodb/api/cfg/JcInstVisitor.kt | 214 +--- .../org/jacodb/api/cfg/JcRawExprVisitor.kt | 126 +++ .../kotlin/org/jacodb/api/cfg/JcRawInst.kt | 270 +++-- .../org/jacodb/api/cfg/JcRawInstVisitor.kt | 205 +--- .../kotlin/org/jacodb/api/ext/JcCommons.kt | 14 +- .../main/kotlin/org/jacodb/api/ext/JcTypes.kt | 42 +- .../org/jacodb/api/ext/cfg/JcInstructions.kt | 80 +- .../src/main/kotlin/org/jacodb/cli/main.kt | 13 +- .../impl/StringConcatSimplifierTransformer.kt | 41 +- .../kotlin/org/jacodb/impl/cfg/GraphExt.kt | 28 +- .../kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt | 23 +- .../org/jacodb/impl/cfg/JcInstListBuilder.kt | 178 +++- .../org/jacodb/impl/cfg/JcInstListImpl.kt | 36 +- .../org/jacodb/impl/cfg/MethodNodeBuilder.kt | 140 ++- .../org/jacodb/impl/cfg/TypedMethodRefImpl.kt | 119 ++- .../jacodb/impl/cfg/util/InstructionFilter.kt | 11 +- .../classpaths/MethodInstructionsFeature.kt | 2 +- .../test/java/org/jacodb/testing/JavaApi.java | 25 +- .../kotlin/org/jacodb/testing/cfg/IRTest.kt | 102 +- .../jacodb/testing/analysis/NpeExamples.java | 17 +- .../analysis/SqlInjectionExamples.java | 47 + .../testing/cfg/LocalResolverExample.java | 20 + .../kotlin/org/jacodb/testing/BaseTest.kt | 23 +- jacodb-taint-configuration/build.gradle.kts | 8 +- .../taint/configuration/ConfigurationTrie.kt | 4 +- .../jacodb/taint/configuration/Position.kt | 24 +- .../SerializedTaintConfigurationItem.kt | 63 +- .../jacodb/taint/configuration/TaintAction.kt | 53 +- .../taint/configuration/TaintCondition.kt | 130 ++- .../TaintConfigurationFeature.kt | 334 ++++--- .../configuration/TaintConfigurationItem.kt | 45 +- .../jacodb/taint/configuration/TaintMark.kt | 8 +- 117 files changed, 9943 insertions(+), 2009 deletions(-) create mode 100644 jacodb-analysis/.gitignore create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/{BackwardApplicationGraphs.kt => BackwardGraphs.kt} (80%) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcNoopInst.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/UnitResolversLibrary.kt create mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt create mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt create mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt create mode 100644 jacodb-analysis/src/test/resources/additional.json create mode 100644 jacodb-analysis/src/test/resources/config_small.json create mode 100644 jacodb-analysis/src/test/resources/simplelogger.properties create mode 100644 jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt create mode 100644 jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt create mode 100644 jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/SqlInjectionExamples.java create mode 100644 jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java diff --git a/.gitignore b/.gitignore index 5a0a47dab..fc4236f9b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ build/ idea-community *.db +*.sarif +*.html +*.jfr +*.csv diff --git a/build.gradle.kts b/build.gradle.kts index 1b1169c68..b0bcf4903 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -188,7 +188,7 @@ if (!repoUrl.isNullOrEmpty()) { register("jar") { from(components["java"]) artifact(tasks.named("sourcesJar")) - artifact(tasks.named("dokkaJavadocJar")) + // artifact(tasks.named("dokkaJavadocJar")) groupId = "org.jacodb" artifactId = project.name diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index ae8000639..bdd08d87a 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -2,14 +2,13 @@ import org.gradle.plugin.use.PluginDependenciesSpec - object Versions { const val asm = "9.5" const val dokka = "1.7.20" const val gradle_download = "5.3.0" const val gradle_versions = "0.47.0" - const val hikaricp = "5.0.1" const val guava = "31.1-jre" + const val hikaricp = "5.0.1" const val javax_activation = "1.1" const val javax_mail = "1.4.7" const val javax_servlet_api = "2.5" @@ -22,6 +21,7 @@ object Versions { const val junit = "5.9.2" const val kotlin = "1.7.21" const val kotlin_logging = "1.8.3" + const val kotlin_logging5 = "5.1.0" const val kotlinx_benchmark = "0.4.4" const val kotlinx_cli = "0.3.5" const val kotlinx_collections_immutable = "0.3.5" @@ -66,6 +66,13 @@ object Libs { version = Versions.kotlin_logging ) + // https://github.com/oshai/kotlin-logging + val kotlin_logging5 = dep( + group = "io.github.oshai", + name = "kotlin-logging", + version = Versions.kotlin_logging5 + ) + // https://github.com/qos-ch/slf4j val slf4j_simple = dep( group = "org.slf4j", @@ -100,7 +107,7 @@ object Libs { // https://github.com/Kotlin/kotlinx.collections.immutable val kotlinx_collections_immutable = dep( group = "org.jetbrains.kotlinx", - name = "kotlinx-collections-immutable-jvm", + name = "kotlinx-collections-immutable", version = Versions.kotlinx_collections_immutable ) @@ -134,6 +141,11 @@ object Libs { ) // https://github.com/Kotlin/kotlinx.serialization + val kotlinx_serialization_core = dep( + group = "org.jetbrains.kotlinx", + name = "kotlinx-serialization-core", + version = Versions.kotlinx_serialization + ) val kotlinx_serialization_json = dep( group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", @@ -278,46 +290,48 @@ object Libs { } object Plugins { - - abstract class ProjectPlugin(val version: String, val id: String) + abstract class Plugin( + val version: String, + val id: String, + ) // https://github.com/Kotlin/dokka - object Dokka: ProjectPlugin( + object Dokka : Plugin( version = Versions.dokka, id = "org.jetbrains.dokka" ) // https://github.com/michel-kraemer/gradle-download-task - object GradleDownload: ProjectPlugin( + object GradleDownload : Plugin( version = Versions.gradle_download, id = "de.undercouch.download" ) // https://github.com/ben-manes/gradle-versions-plugin - object GradleVersions: ProjectPlugin( + object GradleVersions : Plugin( version = Versions.gradle_versions, id = "com.github.ben-manes.versions" ) // https://github.com/Kotlin/kotlinx-benchmark - object KotlinxBenchmark : ProjectPlugin( + object KotlinxBenchmark : Plugin( version = Versions.kotlinx_benchmark, id = "org.jetbrains.kotlinx.benchmark" ) // https://github.com/CadixDev/licenser - object Licenser : ProjectPlugin( + object Licenser : Plugin( version = Versions.licenser, id = "org.cadixdev.licenser" ) // https://github.com/johnrengelman/shadow - object Shadow : ProjectPlugin( + object Shadow : Plugin( version = Versions.shadow, id = "com.github.johnrengelman.shadow" ) } -fun PluginDependenciesSpec.id(plugin: Plugins.ProjectPlugin) { +fun PluginDependenciesSpec.id(plugin: Plugins.Plugin) { id(plugin.id).version(plugin.version) -} \ No newline at end of file +} diff --git a/buildSrc/src/main/kotlin/Tests.kt b/buildSrc/src/main/kotlin/Tests.kt index 873c3a628..ae53310c0 100644 --- a/buildSrc/src/main/kotlin/Tests.kt +++ b/buildSrc/src/main/kotlin/Tests.kt @@ -1,5 +1,6 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.testing.Test +import org.gradle.api.tasks.testing.logging.TestExceptionFormat object Tests { val lifecycleTag = "lifecycle" @@ -9,6 +10,7 @@ object Tests { fun Test.setup(jacocoTestReport: TaskProvider<*>) { testLogging { events("passed", "skipped", "failed") + exceptionFormat = TestExceptionFormat.FULL } finalizedBy(jacocoTestReport) // report is always generated after tests run jvmArgs = listOf("-Xmx2g", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=heapdump.hprof") diff --git a/jacodb-analysis/.gitignore b/jacodb-analysis/.gitignore new file mode 100644 index 000000000..4c8b08b9b --- /dev/null +++ b/jacodb-analysis/.gitignore @@ -0,0 +1,3 @@ +webgoat/ +owasp/ +shopizer/ diff --git a/jacodb-analysis/README.md b/jacodb-analysis/README.md index cb6a4b854..e2ef4a788 100644 --- a/jacodb-analysis/README.md +++ b/jacodb-analysis/README.md @@ -1,4 +1,4 @@ -# Module `jacodb-analysis` +# The `jacodb-analysis` module The `jacodb-analysis` module allows launching application dataflow analyses. It contains an API to write custom analyses, and several ready-to-use analyses. diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index e4e2daf47..7978ac1dd 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -6,11 +6,14 @@ plugins { dependencies { api(project(":jacodb-core")) api(project(":jacodb-api")) + api(project(":jacodb-taint-configuration")) implementation(Libs.kotlin_logging) + implementation(Libs.kotlin_logging5) implementation(Libs.slf4j_simple) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.kotlinx_serialization_json) + implementation(Libs.jdot) testImplementation(testFixtures(project(":jacodb-core"))) testImplementation(project(":jacodb-api")) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt index 1016d5c4b..9b0faa62b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt @@ -15,10 +15,10 @@ */ @file:JvmName("AnalysisMain") + package org.jacodb.analysis import kotlinx.serialization.Serializable -import mu.KLogging import org.jacodb.analysis.engine.IfdsUnitRunnerFactory import org.jacodb.analysis.engine.MainIfdsUnitManager import org.jacodb.analysis.engine.SummaryStorage @@ -28,14 +28,11 @@ import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -internal val logger = object : KLogging() {}.logger - typealias AnalysesOptions = Map @Serializable data class AnalysisConfig(val analyses: Map) - /** * This is the entry point for every analysis. * Calling this function will find all vulnerabilities reachable from [methods]. @@ -65,10 +62,10 @@ data class AnalysisConfig(val analyses: Map) */ fun runAnalysis( graph: JcApplicationGraph, - unitResolver: UnitResolver<*>, + unitResolver: UnitResolver, ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, methods: List, - timeoutMillis: Long = Long.MAX_VALUE + timeoutMillis: Long = Long.MAX_VALUE, ): List { return MainIfdsUnitManager(graph, unitResolver, ifdsUnitRunnerFactory, methods, timeoutMillis).analyze() -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt new file mode 100644 index 000000000..df853c76f --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.config + +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toPath +import org.jacodb.api.cfg.JcBool +import org.jacodb.api.cfg.JcConstant +import org.jacodb.api.cfg.JcInt +import org.jacodb.api.cfg.JcStringConstant +import org.jacodb.api.cfg.JcValue +import org.jacodb.api.ext.isAssignable +import org.jacodb.taint.configuration.And +import org.jacodb.taint.configuration.AnnotationType +import org.jacodb.taint.configuration.Condition +import org.jacodb.taint.configuration.ConditionVisitor +import org.jacodb.taint.configuration.ConstantBooleanValue +import org.jacodb.taint.configuration.ConstantEq +import org.jacodb.taint.configuration.ConstantGt +import org.jacodb.taint.configuration.ConstantIntValue +import org.jacodb.taint.configuration.ConstantLt +import org.jacodb.taint.configuration.ConstantMatches +import org.jacodb.taint.configuration.ConstantStringValue +import org.jacodb.taint.configuration.ConstantTrue +import org.jacodb.taint.configuration.ContainsMark +import org.jacodb.taint.configuration.IsConstant +import org.jacodb.taint.configuration.IsType +import org.jacodb.taint.configuration.Not +import org.jacodb.taint.configuration.Or +import org.jacodb.taint.configuration.PositionResolver +import org.jacodb.taint.configuration.SourceFunctionMatches +import org.jacodb.taint.configuration.TypeMatches + +class BasicConditionEvaluator( + internal val positionResolver: PositionResolver, +) : ConditionVisitor { + + // Default condition handler: + override fun visit(condition: Condition): Boolean { + return false + } + + override fun visit(condition: And): Boolean { + return condition.args.all { it.accept(this) } + } + + override fun visit(condition: Or): Boolean { + return condition.args.any { it.accept(this) } + } + + override fun visit(condition: Not): Boolean { + return !condition.arg.accept(this) + } + + override fun visit(condition: ConstantTrue): Boolean { + return true + } + + override fun visit(condition: IsConstant): Boolean { + val value = positionResolver.resolve(condition.position) + return value is JcConstant + } + + override fun visit(condition: IsType): Boolean { + error("Unexpected condition: $condition") + } + + override fun visit(condition: AnnotationType): Boolean { + error("Unexpected condition: $condition") + } + + override fun visit(condition: ConstantEq): Boolean { + val value = positionResolver.resolve(condition.position) + return when (val constant = condition.value) { + is ConstantBooleanValue -> { + value is JcBool && value.value == constant.value + } + + is ConstantIntValue -> { + value is JcInt && value.value == constant.value + } + + is ConstantStringValue -> { + // TODO: if 'value' is not string, convert it to string and compare with 'constant.value' + value is JcStringConstant && value.value == constant.value + } + + else -> error("Unexpected constant: $constant") + } + } + + override fun visit(condition: ConstantLt): Boolean { + val value = positionResolver.resolve(condition.position) + return when (val constant = condition.value) { + is ConstantIntValue -> { + value is JcInt && value.value < constant.value + } + + else -> error("Unexpected constant: $constant") + } + } + + override fun visit(condition: ConstantGt): Boolean { + val value = positionResolver.resolve(condition.position) + return when (val constant = condition.value) { + is ConstantIntValue -> { + value is JcInt && value.value > constant.value + } + + else -> error("Unexpected constant: $constant") + } + } + + override fun visit(condition: ConstantMatches): Boolean { + val value = positionResolver.resolve(condition.position) + val re = condition.pattern.toRegex() + return re.matches(value.toString()) + } + + override fun visit(condition: SourceFunctionMatches): Boolean { + TODO("Not implemented yet") + } + + override fun visit(condition: ContainsMark): Boolean { + error("This visitor does not support condition $condition. Use FactAwareConditionEvaluator instead") + } + + override fun visit(condition: TypeMatches): Boolean { + val value = positionResolver.resolve(condition.position) + return value.type.isAssignable(condition.type) + } +} + +class FactAwareConditionEvaluator( + private val fact: Tainted, + private val basicConditionEvaluator: BasicConditionEvaluator, +) : ConditionVisitor { + + constructor( + fact: Tainted, + positionResolver: PositionResolver, + ) : this(fact, BasicConditionEvaluator(positionResolver)) + + override fun visit(condition: ContainsMark): Boolean { + if (fact.mark == condition.mark) { + val value = basicConditionEvaluator.positionResolver.resolve(condition.position) + val variable = value.toPath() + if (variable.startsWith(fact.variable)) { + return true + } + } + return false + } + + override fun visit(condition: And): Boolean { + return condition.args.all { it.accept(this) } + } + + override fun visit(condition: Or): Boolean { + return condition.args.any { it.accept(this) } + } + + override fun visit(condition: Not): Boolean { + return !condition.arg.accept(this) + } + + override fun visit(condition: ConstantTrue): Boolean { + return true + } + + override fun visit(condition: IsConstant): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: IsType): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: AnnotationType): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantEq): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantLt): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantGt): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantMatches): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: SourceFunctionMatches): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: TypeMatches): Boolean = basicConditionEvaluator.visit(condition) + + override fun visit(condition: Condition): Boolean = basicConditionEvaluator.visit(condition) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt new file mode 100644 index 000000000..3147fb146 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.config + +import org.jacodb.analysis.paths.AccessPath +import org.jacodb.analysis.paths.ElementAccessor +import org.jacodb.analysis.paths.toPathOrNull +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstanceCallExpr +import org.jacodb.api.cfg.JcValue +import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.taint.configuration.AnyArgument +import org.jacodb.taint.configuration.Argument +import org.jacodb.taint.configuration.Position +import org.jacodb.taint.configuration.PositionResolver +import org.jacodb.taint.configuration.Result +import org.jacodb.taint.configuration.ResultAnyElement +import org.jacodb.taint.configuration.This + +class CallPositionToAccessPathResolver( + private val callStatement: JcInst, +) : PositionResolver { + private val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + + override fun resolve(position: Position): AccessPath = when (position) { + AnyArgument -> error("Unexpected $position") + + is Argument -> callExpr.args[position.index].toPathOrNull() + ?: error("Cannot resolve $position for $callStatement") + + This -> (callExpr as? JcInstanceCallExpr)?.instance?.toPathOrNull() + ?: error("Cannot resolve $position for $callStatement") + + Result -> if (callStatement is JcAssignInst) { + callStatement.lhv.toPathOrNull() + } else { + callExpr.toPathOrNull() + } ?: error("Cannot resolve $position for $callStatement") + + ResultAnyElement -> { + val path = if (callStatement is JcAssignInst) { + callStatement.lhv.toPathOrNull() + } else { + callExpr.toPathOrNull() + } ?: error("Cannot resolve $position for $callStatement") + path / ElementAccessor(null) + } + } +} + +class CallPositionToJcValueResolver( + private val callStatement: JcInst, +) : PositionResolver { + private val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + + override fun resolve(position: Position): JcValue = when (position) { + AnyArgument -> error("Unexpected $position") + + is Argument -> callExpr.args[position.index] + + This -> (callExpr as? JcInstanceCallExpr)?.instance + ?: error("Cannot resolve $position for $callStatement") + + Result -> if (callStatement is JcAssignInst) { + callStatement.lhv + } else { + error("Cannot resolve $position for $callStatement") + } + + ResultAnyElement -> error("Cannot resolve $position for $callStatement") + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt new file mode 100644 index 000000000..e28a78d20 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.config + +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.paths.AccessPath +import org.jacodb.taint.configuration.Action +import org.jacodb.taint.configuration.AssignMark +import org.jacodb.taint.configuration.CopyAllMarks +import org.jacodb.taint.configuration.CopyMark +import org.jacodb.taint.configuration.PositionResolver +import org.jacodb.taint.configuration.RemoveAllMarks +import org.jacodb.taint.configuration.RemoveMark +import org.jacodb.taint.configuration.TaintActionVisitor + +class TaintActionEvaluator( + internal val positionResolver: PositionResolver, +) { + fun evaluate(action: CopyAllMarks, fact: Tainted): Collection { + val from = positionResolver.resolve(action.from) + if (from == fact.variable) { + val to = positionResolver.resolve(action.to) + return setOf(fact, fact.copy(variable = to)) + } + return setOf(fact) // TODO: empty of singleton? + // return emptySet() + } + + fun evaluate(action: CopyMark, fact: Tainted): Collection { + if (fact.mark == action.mark) { + val from = positionResolver.resolve(action.from) + if (from == fact.variable) { + val to = positionResolver.resolve(action.to) + return setOf(fact, fact.copy(variable = to)) + } + } + return setOf(fact) // TODO: empty or singleton? + // return emptySet() + } + + fun evaluate(action: AssignMark): Tainted { + val variable = positionResolver.resolve(action.position) + return Tainted(variable, action.mark) + } + + fun evaluate(action: RemoveAllMarks, fact: Tainted): Collection { + val variable = positionResolver.resolve(action.position) + if (variable == fact.variable) { + return emptySet() + } + return setOf(fact) + } + + fun evaluate(action: RemoveMark, fact: Tainted): Collection { + if (fact.mark == action.mark) { + val variable = positionResolver.resolve(action.position) + if (variable == fact.variable) { + return emptySet() + } + } + return setOf(fact) + } +} + +class FactAwareTaintActionEvaluator( + private val fact: Tainted, + private val evaluator: TaintActionEvaluator, +) : TaintActionVisitor> { + + constructor( + fact: Tainted, + positionResolver: PositionResolver, + ) : this(fact, TaintActionEvaluator(positionResolver)) + + override fun visit(action: CopyAllMarks): Collection { + return evaluator.evaluate(action, fact) + } + + override fun visit(action: CopyMark): Collection { + return evaluator.evaluate(action, fact) + } + + override fun visit(action: AssignMark): Collection { + return setOf(fact, evaluator.evaluate(action)) + } + + override fun visit(action: RemoveAllMarks): Collection { + return evaluator.evaluate(action, fact) + } + + override fun visit(action: RemoveMark): Collection { + return evaluator.evaluate(action, fact) + } + + override fun visit(action: Action): Collection { + error("$this cannot handle $action") + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt index bf5bbbc4c..b2ba04806 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt @@ -34,7 +34,9 @@ import java.util.concurrent.ConcurrentHashMap * Usually this should be set to true for forward analyzers (which are expected to tell anything they found), * but in backward analyzers this should be set to false */ -abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyzer { +abstract class AbstractAnalyzer( + protected val graph: JcApplicationGraph, +) : Analyzer { protected val verticesWithTraceGraphNeeded: MutableSet = ConcurrentHashMap.newKeySet() abstract val isMainAnalyzer: Boolean @@ -45,7 +47,7 @@ abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyze * Otherwise, returns empty list. */ override fun handleNewEdge(edge: IfdsEdge): List { - return if (isMainAnalyzer && edge.v.statement in graph.exitPoints(edge.method)) { + return if (isMainAnalyzer && edge.to.statement in graph.exitPoints(edge.method)) { listOf(NewSummaryFact(SummaryEdgeFact(edge))) } else { emptyList() @@ -77,4 +79,4 @@ abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyze NewSummaryFact(TraceGraphFact(it)) } } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt index 97faab9fe..5c719c4ab 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt @@ -36,7 +36,7 @@ interface DomainFact * A special [DomainFact] that always holds */ object ZEROFact : DomainFact { - override fun toString() = "[ZERO fact]" + override fun toString(): String = "[ZERO fact]" } /** @@ -49,25 +49,45 @@ interface FlowFunctionsSpace { * (these are needed to initiate worklist in ifds analysis) */ fun obtainPossibleStartFacts(startStatement: JcInst): Collection - fun obtainSequentFlowFunction(current: JcInst, next: JcInst): FlowFunctionInstance - fun obtainCallToStartFlowFunction(callStatement: JcInst, callee: JcMethod): FlowFunctionInstance - fun obtainCallToReturnFlowFunction(callStatement: JcInst, returnSite: JcInst): FlowFunctionInstance - fun obtainExitToReturnSiteFlowFunction(callStatement: JcInst, returnSite: JcInst, exitStatement: JcInst): FlowFunctionInstance + + fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ): FlowFunctionInstance + + fun obtainCallToStartFlowFunction( + callStatement: JcInst, + callee: JcMethod, + ): FlowFunctionInstance + + fun obtainCallToReturnFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + graph: JcApplicationGraph, + ): FlowFunctionInstance + + fun obtainExitToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + exitStatement: JcInst, + ): FlowFunctionInstance } /** - * [Analyzer] interface describes how facts are propagated and how [AnalysisDependentEvent]s are produced by these facts during - * the run of tabulation algorithm by [BaseIfdsUnitRunner]. + * [Analyzer] interface describes how facts are propagated and how [AnalysisDependentEvent]s are + * produced by these facts during the run of tabulation algorithm by [BaseIfdsUnitRunner]. * - * Note that methods and properties of this interface may be accessed concurrently from different threads, - * so the implementations should be thread-safe. + * Note that methods and properties of this interface may be accessed concurrently from different + * threads, so the implementations should be thread-safe. * - * @property flowFunctions a [FlowFunctionsSpace] instance that describes how facts are generated and propagated - * during run of tabulation algorithm. + * @property flowFunctions a [FlowFunctionsSpace] instance that describes how facts are generated + * and propagated during run of tabulation algorithm. */ interface Analyzer { val flowFunctions: FlowFunctionsSpace + fun isSkipped(method: JcMethod): Boolean = false + /** * This method is called by [BaseIfdsUnitRunner] each time a new path edge is found. * @@ -101,4 +121,4 @@ interface Analyzer { */ fun interface AnalyzerFactory { fun newAnalyzer(graph: JcApplicationGraph): Analyzer -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt index d4d0f5d51..cc273fd05 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt @@ -27,42 +27,45 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.ApplicationGraph import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcInst +import org.jacodb.api.ext.cfg.callExpr import java.util.concurrent.ConcurrentHashMap +private val logger = io.github.oshai.kotlinlogging.KotlinLogging.logger {} + /** * This is a basic [IfdsUnitRunnerFactory], which creates one [BaseIfdsUnitRunner] for each [newRunner] call. * * @property analyzerFactory used to build [Analyzer] instance, which then will be used by launched [BaseIfdsUnitRunner]. */ -class BaseIfdsUnitRunnerFactory(private val analyzerFactory: AnalyzerFactory) : IfdsUnitRunnerFactory { - override fun newRunner( +class BaseIfdsUnitRunnerFactory( + private val analyzerFactory: AnalyzerFactory, +) : IfdsUnitRunnerFactory { + override fun newRunner( graph: JcApplicationGraph, - manager: IfdsUnitManager, - unitResolver: UnitResolver, + manager: IfdsUnitManager, + unitResolver: UnitResolver, unit: UnitType, - startMethods: List - ): IfdsUnitRunner { + startMethods: List, + ): IfdsUnitRunner { val analyzer = analyzerFactory.newAnalyzer(graph) return BaseIfdsUnitRunner(graph, analyzer, manager, unitResolver, unit, startMethods) } } /** - * Encapsulates launch of tabulation algorithm, described in RHS95, for one unit + * Encapsulates a launch of tabulation algorithm, described in RHS'95, for one unit. */ -private class BaseIfdsUnitRunner( - private val graph: ApplicationGraph, +internal class BaseIfdsUnitRunner( + private val graph: JcApplicationGraph, private val analyzer: Analyzer, - private val manager: IfdsUnitManager, - private val unitResolver: UnitResolver, + private val manager: IfdsUnitManager, + private val unitResolver: UnitResolver, unit: UnitType, - private val startMethods: List -) : AbstractIfdsUnitRunner(unit) { + private val startMethods: List, +) : AbstractIfdsUnitRunner(unit) { - private val pathEdges: MutableSet = ConcurrentHashMap.newKeySet() + internal val pathEdges: MutableSet = ConcurrentHashMap.newKeySet() private val summaryEdges: MutableMap> = mutableMapOf() private val callSitesOf: MutableMap> = mutableMapOf() private val pathEdgesPreds: MutableMap> = ConcurrentHashMap() @@ -82,14 +85,21 @@ private class BaseIfdsUnitRunner( * @param edge the new path edge * @param pred the description of predecessor of the edge */ - private suspend fun propagate(edge: IfdsEdge, pred: PathEdgePredecessor): Boolean { + private suspend fun propagate( + edge: IfdsEdge, + pred: PathEdgePredecessor, + ): Boolean { require(unitResolver.resolve(edge.method) == unit) - pathEdgesPreds.computeIfAbsent(edge) { - ConcurrentHashMap.newKeySet() - }.add(pred) + pathEdgesPreds.computeIfAbsent(edge) { ConcurrentHashMap.newKeySet() }.add(pred) + + // Update edge's reason: + edge.reason = pred.predEdge if (pathEdges.add(edge)) { + if (edge.from.statement.toString() == "noop" && edge.to.domainFact != ZEROFact) { + logger.trace { "Propagating $edge in ${edge.method} via ${edge.reason}" } + } workList.send(edge) analyzer.handleNewEdge(edge).forEach { manager.handleEvent(it, this) @@ -103,116 +113,238 @@ private class BaseIfdsUnitRunner( get() = unitResolver.resolve(this) != unit /** - * Implementation of tabulation algorithm, based on RHS95. It slightly differs from the original in the following: + * Implementation of tabulation algorithm, based on RHS'95. + * + * It slightly differs from the original in the following: * * - We do not analyze the whole supergraph (represented by [graph]), but only the methods that belong to our [unit]; * - Path edges are added to [workList] not only by the main cycle, but they can also be obtained from [manager]; - * - By summary edge we understand the path edge from the start node of the method to its exit node; - * - The supergraph is explored dynamically, and we do not inverse flow functions when new summary edge is found, i.e. - * the extension from Chapter 4 of NLR10 is implemented. + * - By "summary edge" we understand the path edge from the start node of the method to its exit node. + * - The supergraph is explored dynamically, and we do not inverse flow functions when new summary edge is found, + * i.e. the extension from Chapter 4 of NLR'10 is implemented. */ private suspend fun runTabulationAlgorithm(): Unit = coroutineScope { while (isActive) { - val curEdge = workList.tryReceive().getOrNull() ?: run { + val currentEdge = workList.tryReceive().getOrNull() ?: run { manager.handleEvent(QueueEmptinessChanged(true), this@BaseIfdsUnitRunner) workList.receive().also { manager.handleEvent(QueueEmptinessChanged(false), this@BaseIfdsUnitRunner) } } - val (u, v) = curEdge - val (curVertex, curFact) = v + val (startVertex, currentVertex) = currentEdge + val (current, currentFact) = currentVertex - val callees = graph.callees(curVertex).toList() - val curVertexIsCall = callees.isNotEmpty() - val curVertexIsExit = curVertex in graph.exitPoints(graph.methodOf(curVertex)) + val currentCallees = graph.callees(current).toList() + // val currentIsCall = currentCallees.isNotEmpty() + val currentIsCall = current.callExpr != null + val currentIsExit = current in graph.exitPoints(graph.methodOf(current)) - if (curVertexIsCall) { - for (returnSite in graph.successors(curVertex)) { - // Propagating through call-to-return-site edges (in RHS95 it is done in lines 17-19) - for (fact in flowSpace.obtainCallToReturnFlowFunction(curVertex, returnSite).compute(curFact)) { - val newEdge = IfdsEdge(u, IfdsVertex(returnSite, fact)) - propagate(newEdge, PathEdgePredecessor(curEdge, PredecessorKind.Sequent)) + if (currentIsCall) { + // Propagating through the call-to-return-site edge (lines 17-19 in RHS'95). + // + // START main :: (s, d1) + // | + // | (path edge) + // | + // CALL p :: (n, d2) + // : + // : (call-to-return-site edge) + // : + // RETURN FROM p :: (ret(n), d3) + // + // New path edge: + // (s -> n) + (n -> ret(n)) ==> (s -> ret(n)) + // + // Below: + // startVertex == (s, d1) + // currentVertex = (current, currentFact) == (n, d2) + // returnSiteVertex = (returnSite, returnSiteFact) == (ret(n), d3) + // newEdge == ((s,d1) -> (ret(n), d3)) + // + for (returnSite in graph.successors(current)) { + val factsAtReturnSite = flowSpace + .obtainCallToReturnFlowFunction(current, returnSite, graph) + .compute(currentFact) + for (returnSiteFact in factsAtReturnSite) { + val returnSiteVertex = IfdsVertex(returnSite, returnSiteFact) + val newEdge = IfdsEdge(startVertex, returnSiteVertex) + val predecessor = PathEdgePredecessor(currentEdge, PredecessorKind.Sequent) + propagate(newEdge, predecessor) } + } - for (callee in callees) { - val factsAtStart = flowSpace.obtainCallToStartFlowFunction(curVertex, callee).compute(curFact) - for (sPoint in graph.entryPoint(callee)) { - for (sFact in factsAtStart) { - val sVertex = IfdsVertex(sPoint, sFact) + // Propagating through the call. + // + // START main :: (s, d1) + // | + // CALL p :: (n, d2) + // : \ + // : \ + // : START p :: (s_p, d3) + // : | + // : EXIT p :: (e_p, d4) + // : / + // : / + // RETURN FROM p :: (ret(n), d5) + // + // New path edge: + // (s -> n) + (n -> s_p) + (s_p ~> e_p) + (e_p -> ret(n)) ==> (s -> ret(n)) + // + // Below: + // startVertex == (s, d1) + // currentVertex = (current, currentFact) == (n, d2) + // calleeStartVertex = (calleeStart, calleeStartFact) == (s_p, d3) + // exitVertex = (exit, exitFact) == (e_p, d4) + // returnSiteVertex = (returnSite, returnSiteFact) == (ret(n), d5) + // newEdge == ((s, d1) -> (ret(n), d5)) + // + for (callee in currentCallees) { + val factsAtCalleeStart = flowSpace + .obtainCallToStartFlowFunction(current, callee) + .compute(currentFact) + for (calleeStartFact in factsAtCalleeStart) { + for (calleeStart in graph.entryPoints(callee)) { + val calleeStartVertex = IfdsVertex(calleeStart, calleeStartFact) - val handleExitVertex: suspend (IfdsVertex) -> Unit = { (eStatement, eFact) -> - val finalFacts = flowSpace - .obtainExitToReturnSiteFlowFunction(curVertex, returnSite, eStatement) - .compute(eFact) - for (finalFact in finalFacts) { - val summaryEdge = IfdsEdge(IfdsVertex(sPoint, sFact), IfdsVertex(eStatement, eFact)) - val newEdge = IfdsEdge(u, IfdsVertex(returnSite, finalFact)) - propagate(newEdge, PathEdgePredecessor(curEdge, PredecessorKind.ThroughSummary(summaryEdge))) + // Handle callee exit vertex: + val handleExitVertex: suspend (IfdsVertex) -> Unit = + { (exit, exitFact) -> + val exitVertex = IfdsVertex(exit, exitFact) + for (returnSite in graph.successors(current)) { + val finalFacts = flowSpace + .obtainExitToReturnSiteFlowFunction(current, returnSite, exit) + .compute(exitFact) + for (returnSiteFact in finalFacts) { + val returnSiteVertex = IfdsVertex(returnSite, returnSiteFact) + val newEdge = IfdsEdge(startVertex, returnSiteVertex) + val summaryEdge = IfdsEdge(calleeStartVertex, exitVertex) + val predecessor = PathEdgePredecessor( + currentEdge, + PredecessorKind.ThroughSummary(summaryEdge) + ) + propagate(newEdge, predecessor) + } } } - if (callee.isExtern) { - // Notify about cross-unit call - analyzer.handleNewCrossUnitCall(CrossUnitCallFact(v, sVertex)).forEach { - manager.handleEvent(it, this@BaseIfdsUnitRunner) + if (callee.isExtern) { + // Notify about the cross-unit call: + analyzer + .handleNewCrossUnitCall(CrossUnitCallFact(currentVertex, calleeStartVertex)) + .forEach { event -> + manager.handleEvent(event, this@BaseIfdsUnitRunner) } - // Waiting for exit vertices and handling them - val exitVertices = flow { - manager.handleEvent( - SubscriptionForSummaryEdges(callee, this@flow), - this@BaseIfdsUnitRunner - ) - } - exitVertices - .filter { it.u == sVertex } - .map { it.v } - .onEach(handleExitVertex) - .launchIn(this) - } else { - // Save info about call for summary-facts that will be found later - callSitesOf.getOrPut(sVertex) { mutableSetOf() }.add(curEdge) + // Wait (asynchronously, via Flow) for summary edges and handle them: + val summaries = flow { + val event = SubscriptionForSummaryEdges(callee, this@flow) + manager.handleEvent(event, this@BaseIfdsUnitRunner) + } + summaries + .filter { it.from == calleeStartVertex } + .map { it.to } + .onEach(handleExitVertex) + .launchIn(this) + } else { + // Save info about the call for summary-facts that will be found later: + callSitesOf.getOrPut(calleeStartVertex) { mutableSetOf() }.add(currentEdge) - // Initiating analysis for callee - val nextEdge = IfdsEdge(sVertex, sVertex) - propagate(nextEdge, PathEdgePredecessor(curEdge, PredecessorKind.CallToStart)) + // Initiate analysis for callee: + val newEdge = IfdsEdge(calleeStartVertex, calleeStartVertex) + val predecessor = PathEdgePredecessor(currentEdge, PredecessorKind.CallToStart) + propagate(newEdge, predecessor) - // Handling already-found summary edges - // .toList() is needed below to avoid ConcurrentModificationException - for (exitVertex in summaryEdges[sVertex].orEmpty().toList()) { - handleExitVertex(exitVertex) - } + // Handle already-found summary edges: + // Note: `.toList()` is needed below to avoid ConcurrentModificationException + val exits = summaryEdges[calleeStartVertex].orEmpty().toList() + for (vertex in exits) { + handleExitVertex(vertex) } } } } } } else { - if (curVertexIsExit) { - // Propagating through newly found summary edge, similar to lines 22-31 of RHS95 - // TODO: rewrite this in a more reactive way - for (callerEdge in callSitesOf[u].orEmpty()) { - val callerStatement = callerEdge.v.statement - for (returnSite in graph.successors(callerStatement)) { - for (returnSiteFact in flowSpace.obtainExitToReturnSiteFlowFunction(callerStatement, returnSite, curVertex).compute(curFact)) { + if (currentIsExit) { + // Propagating through the newly found summary edge (lines 22-31 of RHS'95). + // + // START outer :: (s_c, d3) + // | + // CALL p :: (c, d4) + // : \ + // : \ + // : START p :: (s_p, d1) + // : | + // : EXIT p :: (e_p, d2) + // : / + // : / + // RETURN FROM p :: (ret(c), d5) + // + // New path edge: + // (s -> e) + (c -> s_p) + (c -> ret(c)) + (s_c -> c) ==> (s_c -> ret(c)) + // + // Below: + // startVertex == (s_p, d1) + // currentVertex = (current, currentFact) == (e_p, d2) + // callerPathEdge == ((s_c, d3) -> (c, d4)) + // callerVertex = (caller, callerFact) == (c, d4) + // returnSiteVertex = (returnSite, returnSiteFact) == (ret(c), d5) + // newEdge == ((s_p, d3) -> (ret(n), d5)) + // + // TODO: rewrite this in a more reactive way (?) + for (callerPathEdge in callSitesOf[startVertex].orEmpty()) { + val caller = callerPathEdge.to.statement + for (returnSite in graph.successors(caller)) { + val factsAtReturnSite = flowSpace + .obtainExitToReturnSiteFlowFunction(caller, returnSite, current) + .compute(currentFact) + for (returnSiteFact in factsAtReturnSite) { val returnSiteVertex = IfdsVertex(returnSite, returnSiteFact) - val newEdge = IfdsEdge(callerEdge.u, returnSiteVertex) - propagate(newEdge, PathEdgePredecessor(callerEdge, PredecessorKind.ThroughSummary(curEdge))) + val newEdge = IfdsEdge(callerPathEdge.from, returnSiteVertex) + val predecessor = PathEdgePredecessor( + callerPathEdge, + PredecessorKind.ThroughSummary(currentEdge) + ) + propagate(newEdge, predecessor) } } } - summaryEdges.getOrPut(curEdge.u) { mutableSetOf() }.add(curEdge.v) + summaryEdges.getOrPut(startVertex) { mutableSetOf() }.add(currentVertex) } - // Simple propagation through intraprocedural edge, as in lines 34-36 of RHS95 + // Simple propagation through the intra-procedural edge (lines 34-36 of RHS'95). // Note that generally speaking, exit vertices may have successors (in case of exceptional flow, etc.), - // so this part should be done for exit vertices as well - for (nextInst in graph.successors(curVertex)) { - val nextFacts = flowSpace.obtainSequentFlowFunction(curVertex, nextInst).compute(curFact) - for (nextFact in nextFacts) { - val newEdge = IfdsEdge(u, IfdsVertex(nextInst, nextFact)) - propagate(newEdge, PathEdgePredecessor(curEdge, PredecessorKind.Sequent)) + // so this part should be done for exit vertices as well. + // + // START main :: (s, d1) + // | + // | (path edge) + // | + // INSTRUCTION :: (n, d2) + // | + // | (path edge) + // | + // INSTRUCTION :: (m, d3) + // + // New path edge: + // (s -> n) + (n -> m) ==> (s -> m) + // + // Below: + // startVertex == (s, d1) + // currentVertex == (current, currentFact) == (n, d2) + // nextVertex == (next, nextFact) == (m, d3) + // newEdge = (startVertex, nextVertex) == ((s,d1) -> (m, d3)) + // + for (next in graph.successors(current)) { + val factsAtNext = flowSpace + .obtainSequentFlowFunction(current, next) + .compute(currentFact) + for (nextFact in factsAtNext) { + val nextVertex = IfdsVertex(next, nextFact) + val newEdge = IfdsEdge(startVertex, nextVertex) + val predecessor = PathEdgePredecessor(currentEdge, PredecessorKind.Sequent) + propagate(newEdge, predecessor) } } } @@ -221,33 +353,41 @@ private class BaseIfdsUnitRunner( private val ifdsResult: IfdsResult by lazy { val allEdges = pathEdges.toList() - - val resultFacts = allEdges.groupBy({ it.v.statement }) { - it.v.domainFact - }.mapValues { (_, facts) -> facts.toSet() } - + val resultFacts = allEdges + .groupBy({ it.to.statement }, { it.to.domainFact }) + .mapValues { (_, facts) -> facts.toSet() } IfdsResult(allEdges, resultFacts, pathEdgesPreds) } /** - * Performs some initialization and runs tabulation algorithm, sending all relevant events to [manager]. + * Performs some initialization and runs the tabulation algorithm, sending all relevant events to the [manager]. */ override suspend fun run() = coroutineScope { try { - // Adding initial facts to workList + // Add initial facts to workList: for (method in startMethods) { require(unitResolver.resolve(method) == unit) - for (sPoint in graph.entryPoint(method)) { - for (sFact in flowSpace.obtainPossibleStartFacts(sPoint)) { - val vertex = IfdsVertex(sPoint, sFact) - val edge = IfdsEdge(vertex, vertex) - propagate(edge, PathEdgePredecessor(edge, PredecessorKind.NoPredecessor)) + for (startStatement in graph.entryPoints(method)) { + val startFacts = flowSpace.obtainPossibleStartFacts(startStatement) + for (startFact in startFacts) { + val vertex = IfdsVertex(startStatement, startFact) + val edge = IfdsEdge(vertex, vertex) // loop + val predecessor = PathEdgePredecessor(edge, PredecessorKind.NoPredecessor) + propagate(edge, predecessor) } } } + // Run the tabulation algorithm: runTabulationAlgorithm() } finally { + logger.info { "Finishing ${this@BaseIfdsUnitRunner} for $unit" } + logger.info { "Total ${pathEdges.size} path edges for $unit using $analyzer" } + // for ((i, edge) in pathEdges.sortedBy { it.toString() }.withIndex()) { + // logger.debug { " - [${i + 1}/${pathEdges.size}] $edge" } + // } + + // Post-process left-over events: withContext(NonCancellable) { analyzer.handleIfdsResult(ifdsResult).forEach { manager.handleEvent(it, this@BaseIfdsUnitRunner) @@ -257,6 +397,7 @@ private class BaseIfdsUnitRunner( } override suspend fun submitNewEdge(edge: IfdsEdge) { - propagate(edge, PathEdgePredecessor(edge, PredecessorKind.Unknown)) + val predecessor = PathEdgePredecessor(edge, PredecessorKind.Unknown) + propagate(edge, predecessor) } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt index d472941fd..d3ab439c1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt @@ -18,7 +18,7 @@ package org.jacodb.analysis.engine import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope -import org.jacodb.analysis.graph.reversed +import org.jacodb.analysis.graph.BackwardJcApplicationGraph import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph @@ -47,15 +47,24 @@ import org.jacodb.api.analysis.JcApplicationGraph class BidiIfdsUnitRunnerFactory( private val forwardRunnerFactory: IfdsUnitRunnerFactory, private val backwardRunnerFactory: IfdsUnitRunnerFactory, - private val isParallel: Boolean = true + private val isParallel: Boolean = true, ) : IfdsUnitRunnerFactory { - private inner class BidiIfdsUnitRunner( + + override fun newRunner( + graph: JcApplicationGraph, + manager: IfdsUnitManager, + unitResolver: UnitResolver, + unit: UnitType, + startMethods: List, + ): IfdsUnitRunner = BidiIfdsUnitRunner(graph, manager, unitResolver, unit, startMethods) + + internal inner class BidiIfdsUnitRunner( graph: JcApplicationGraph, - private val manager: IfdsUnitManager, - private val unitResolver: UnitResolver, + private val manager: IfdsUnitManager, + private val unitResolver: UnitResolver, unit: UnitType, startMethods: List, - ) : AbstractIfdsUnitRunner(unit) { + ) : AbstractIfdsUnitRunner(unit) { @Volatile private var forwardQueueIsEmpty: Boolean = false @@ -63,25 +72,29 @@ class BidiIfdsUnitRunnerFactory( @Volatile private var backwardQueueIsEmpty: Boolean = false - private val forwardManager: IfdsUnitManager = object : IfdsUnitManager by manager { - - override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { + private val forwardManager: IfdsUnitManager = object : IfdsUnitManager by manager { + override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { when (event) { is EdgeForOtherRunnerQuery -> { if (unitResolver.resolve(event.edge.method) == unit) { + // Submit new edge directly to the backward runner: backwardRunner.submitNewEdge(event.edge) } else { + // Submit new edge via the manager: manager.handleEvent(event, this@BidiIfdsUnitRunner) } } + is NewSummaryFact -> { manager.handleEvent(event, this@BidiIfdsUnitRunner) } + is QueueEmptinessChanged -> { forwardQueueIsEmpty = event.isEmpty val newEvent = QueueEmptinessChanged(backwardQueueIsEmpty && forwardQueueIsEmpty) manager.handleEvent(newEvent, this@BidiIfdsUnitRunner) } + is SubscriptionForSummaryEdges -> { manager.handleEvent(event, this@BidiIfdsUnitRunner) } @@ -89,24 +102,25 @@ class BidiIfdsUnitRunnerFactory( } } - private val backwardManager: IfdsUnitManager = object : IfdsUnitManager { - override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { + private val backwardManager: IfdsUnitManager = object : IfdsUnitManager { + override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { when (event) { is EdgeForOtherRunnerQuery -> { if (unitResolver.resolve(event.edge.method) == unit) { forwardRunner.submitNewEdge(event.edge) } } + is NewSummaryFact -> { manager.handleEvent(event, this@BidiIfdsUnitRunner) } + is QueueEmptinessChanged -> { backwardQueueIsEmpty = event.isEmpty if (!isParallel && event.isEmpty) { runner.job?.cancel() ?: error("Runner job is not instantiated") } - val newEvent = - QueueEmptinessChanged(backwardQueueIsEmpty && forwardQueueIsEmpty) + val newEvent = QueueEmptinessChanged(backwardQueueIsEmpty && forwardQueueIsEmpty) manager.handleEvent(newEvent, this@BidiIfdsUnitRunner) } @@ -115,10 +129,10 @@ class BidiIfdsUnitRunnerFactory( } } - private val backwardRunner: IfdsUnitRunner = backwardRunnerFactory - .newRunner(graph.reversed, backwardManager, unitResolver, unit, startMethods) + internal val backwardRunner: IfdsUnitRunner = backwardRunnerFactory + .newRunner(BackwardJcApplicationGraph(graph), backwardManager, unitResolver, unit, startMethods) - private val forwardRunner: IfdsUnitRunner = forwardRunnerFactory + internal val forwardRunner: IfdsUnitRunner = forwardRunnerFactory .newRunner(graph, forwardManager, unitResolver, unit, startMethods) override suspend fun submitNewEdge(edge: IfdsEdge) { @@ -142,12 +156,4 @@ class BidiIfdsUnitRunnerFactory( } } } - - override fun newRunner( - graph: JcApplicationGraph, - manager: IfdsUnitManager, - unitResolver: UnitResolver, - unit: UnitType, - startMethods: List - ): IfdsUnitRunner = BidiIfdsUnitRunner(graph, manager, unitResolver, unit, startMethods) -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt index 3e081a9b7..b7a26611b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt @@ -19,15 +19,20 @@ package org.jacodb.analysis.engine import org.jacodb.api.JcMethod /** - * Represents a directed (from [u] to [v]) edge between two ifds vertices + * Represents a directed (from [from] to [to]) edge between two ifds vertices */ -data class IfdsEdge(val u: IfdsVertex, val v: IfdsVertex) { +data class IfdsEdge( + val from: IfdsVertex, + val to: IfdsVertex, +) { init { - require(u.method == v.method) + require(from.method == to.method) } + var reason: IfdsEdge? = null + val method: JcMethod - get() = u.method + get() = from.method } sealed interface PredecessorKind { @@ -44,5 +49,5 @@ sealed interface PredecessorKind { */ data class PathEdgePredecessor( val predEdge: IfdsEdge, - val kind: PredecessorKind -) \ No newline at end of file + val kind: PredecessorKind, +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt index e03d9f22c..ce6c3761d 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt @@ -15,16 +15,19 @@ */ package org.jacodb.analysis.engine + import org.jacodb.api.cfg.JcInst /** * Aggregates all facts and edges found by tabulation algorithm */ class IfdsResult( - val pathEdges: List, + pathEdgeList: List, val resultFacts: Map>, - val pathEdgesPreds: Map> + val pathEdgesPreds: Map>, ) { + private val pathEdges = pathEdgeList.groupBy { it.to } + private inner class TraceGraphBuilder(private val sink: IfdsVertex) { private val sources: MutableSet = mutableSetOf() private val edges: MutableMap> = mutableMapOf() @@ -43,8 +46,8 @@ class IfdsResult( visited.add(e) - if (stopAtMethodStart && e.u == e.v) { - addEdge(e.u, lastVertex) + if (stopAtMethodStart && e.from == e.to) { + addEdge(e.from, lastVertex) return } @@ -59,45 +62,49 @@ class IfdsResult( when (pred.kind) { is PredecessorKind.CallToStart -> { if (!stopAtMethodStart) { - addEdge(pred.predEdge.v, lastVertex) - dfs(pred.predEdge, pred.predEdge.v, false) + addEdge(pred.predEdge.to, lastVertex) + dfs(pred.predEdge, pred.predEdge.to, false) } } + is PredecessorKind.Sequent -> { - if (pred.predEdge.v.domainFact == v.domainFact) { + if (pred.predEdge.to.domainFact == v.domainFact) { dfs(pred.predEdge, lastVertex, stopAtMethodStart) } else { - addEdge(pred.predEdge.v, lastVertex) - dfs(pred.predEdge, pred.predEdge.v, stopAtMethodStart) + addEdge(pred.predEdge.to, lastVertex) + dfs(pred.predEdge, pred.predEdge.to, stopAtMethodStart) } } + is PredecessorKind.ThroughSummary -> { val summaryEdge = pred.kind.summaryEdge - addEdge(summaryEdge.v, lastVertex) // Return to next vertex - addEdge(pred.predEdge.v, summaryEdge.u) // Call to start - dfs(summaryEdge, summaryEdge.v, true) // Expand summary edge - dfs(pred.predEdge, pred.predEdge.v, stopAtMethodStart) // Continue normal analysis + addEdge(summaryEdge.to, lastVertex) // Return to next vertex + addEdge(pred.predEdge.to, summaryEdge.from) // Call to start + dfs(summaryEdge, summaryEdge.to, true) // Expand summary edge + dfs(pred.predEdge, pred.predEdge.to, stopAtMethodStart) // Continue normal analysis } + is PredecessorKind.Unknown -> { - addEdge(pred.predEdge.v, lastVertex) - if (pred.predEdge.u != pred.predEdge.v) { + addEdge(pred.predEdge.to, lastVertex) + if (pred.predEdge.from != pred.predEdge.to) { // TODO: ideally, we should analyze the place from which the edge was given to ifds, // for now we just go to method start - dfs(IfdsEdge(pred.predEdge.u, pred.predEdge.u), pred.predEdge.v, stopAtMethodStart) + dfs(IfdsEdge(pred.predEdge.from, pred.predEdge.from), pred.predEdge.to, stopAtMethodStart) } } + is PredecessorKind.NoPredecessor -> { sources.add(v) - addEdge(pred.predEdge.v, lastVertex) + addEdge(pred.predEdge.to, lastVertex) } } } } fun build(): TraceGraph { - val initEdges = pathEdges.filter { it.v == sink } + val initEdges = pathEdges[sink].orEmpty() initEdges.forEach { - dfs(it, it.v, false) + dfs(it, it.to, false) } return TraceGraph(sink, sources, edges) } @@ -109,4 +116,4 @@ class IfdsResult( fun resolveTraceGraph(vertex: IfdsVertex): TraceGraph { return TraceGraphBuilder(vertex).build() } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt index fb09509de..aa9965752 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt @@ -21,17 +21,16 @@ import org.jacodb.api.JcMethod /** * Implementations of this interface manage one or more runners and should be responsible for: - * - communication between different runners, i.e. they - * should submit received [EdgeForOtherRunnerQuery] to proper runners via [IfdsUnitRunner.submitNewEdge] call + * - communication between different runners, i.e. they should submit received + * [EdgeForOtherRunnerQuery] to proper runners via [IfdsUnitRunner.submitNewEdge] call * - providing runners with summaries for other units * - saving the [NewSummaryFact]s produced by runners * - managing lifecycles of the launched runners */ -interface IfdsUnitManager { - suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) +interface IfdsUnitManager { + suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) } - // TODO: provide visitor for this interface sealed interface IfdsUnitRunnerEvent @@ -42,7 +41,10 @@ data class QueueEmptinessChanged(val isEmpty: Boolean) : IfdsUnitRunnerEvent * @property collector the [FlowCollector] to which queried summary edges should be sent to, * somewhat similar to a callback */ -data class SubscriptionForSummaryEdges(val method: JcMethod, val collector: FlowCollector) : IfdsUnitRunnerEvent +data class SubscriptionForSummaryEdges( + val method: JcMethod, + val collector: FlowCollector, +) : IfdsUnitRunnerEvent /** * A common interface for all events that are allowed to be produced by [Analyzer] @@ -51,4 +53,4 @@ data class SubscriptionForSummaryEdges(val method: JcMethod, val collector: Flow sealed interface AnalysisDependentEvent : IfdsUnitRunnerEvent data class NewSummaryFact(val fact: SummaryFact) : AnalysisDependentEvent -data class EdgeForOtherRunnerQuery(val edge: IfdsEdge) : AnalysisDependentEvent \ No newline at end of file +data class EdgeForOtherRunnerQuery(val edge: IfdsEdge) : AnalysisDependentEvent diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt index fb00efe7c..96546d52c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt @@ -35,7 +35,7 @@ import org.jacodb.api.analysis.JcApplicationGraph * It is not recommended to implement this interface directly, instead, * [AbstractIfdsUnitRunner] should be extended. */ -interface IfdsUnitRunner { +interface IfdsUnitRunner { val unit: UnitType val job: Job? @@ -53,15 +53,16 @@ interface IfdsUnitRunner { * Inheritors should only implement [submitNewEdge] and a suspendable [run] method. * The latter is the main method of runner, that should do all its work. */ -abstract class AbstractIfdsUnitRunner(final override val unit: UnitType) : IfdsUnitRunner { +abstract class AbstractIfdsUnitRunner( + final override val unit: UnitType, +) : IfdsUnitRunner { /** * The main method of the runner, which will be called by [launchIn] */ protected abstract suspend fun run() private var _job: Job? = null - - final override val job: Job? by ::_job + final override val job: Job? get() = _job final override fun launchIn(scope: CoroutineScope): Job = scope.launch(start = CoroutineStart.LAZY) { run() @@ -86,11 +87,11 @@ interface IfdsUnitRunnerFactory { * * @param unitResolver will be used to get units of methods observed during analysis. */ - fun newRunner( + fun newRunner( graph: JcApplicationGraph, - manager: IfdsUnitManager, - unitResolver: UnitResolver, + manager: IfdsUnitManager, + unitResolver: UnitResolver, unit: UnitType, - startMethods: List - ) : IfdsUnitRunner -} \ No newline at end of file + startMethods: List, + ): IfdsUnitRunner +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt index ba0f73c8e..9b69c5166 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt @@ -19,7 +19,10 @@ package org.jacodb.analysis.engine import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst -data class IfdsVertex(val statement: JcInst, val domainFact: DomainFact) { +data class IfdsVertex( + val statement: JcInst, + val domainFact: DomainFact, +) { val method: JcMethod get() = statement.location.method -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt index 42128e9e1..f7c232aad 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt @@ -16,9 +16,11 @@ package org.jacodb.analysis.engine +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.map @@ -27,24 +29,27 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import org.jacodb.analysis.logger +import org.jacodb.analysis.library.analyzers.NpeTaintNode import org.jacodb.analysis.runAnalysis import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph +import java.io.File import java.util.concurrent.ConcurrentHashMap +private val logger = KotlinLogging.logger {} + /** * This manager launches and manages [IfdsUnitRunner]s for all units, reachable from [startMethods]. * It also merges [TraceGraph]s from different units giving a complete [TraceGraph] for each vulnerability. * See [runAnalysis] for more info. */ -class MainIfdsUnitManager( +class MainIfdsUnitManager( private val graph: JcApplicationGraph, - private val unitResolver: UnitResolver, + private val unitResolver: UnitResolver, private val ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, private val startMethods: List, - private val timeoutMillis: Long -) : IfdsUnitManager { + private val timeoutMillis: Long, +) : IfdsUnitManager { private val foundMethods: MutableMap> = mutableMapOf() private val crossUnitCallers: MutableMap> = mutableMapOf() @@ -54,11 +59,14 @@ class MainIfdsUnitManager( private val crossUnitCallsStorage = SummaryStorageImpl() private val vulnerabilitiesStorage = SummaryStorageImpl() - private val aliveRunners: MutableMap> = ConcurrentHashMap() + private val aliveRunners: MutableMap = ConcurrentHashMap() private val queueEmptiness: MutableMap = mutableMapOf() private val dependencies: MutableMap> = mutableMapOf() private val dependenciesRev: MutableMap> = mutableMapOf() + // private val deleteJobs: MutableMap = ConcurrentHashMap() + // private val pathEdgesStorage: MutableMap> = ConcurrentHashMap() + private fun getAllCallees(method: JcMethod): Set { val result = mutableSetOf() for (inst in method.flowGraph().instructions) { @@ -71,13 +79,14 @@ class MainIfdsUnitManager( private fun addStart(method: JcMethod) { val unit = unitResolver.resolve(method) + // TODO: remove this unnecessary if-condition (superseded by '.add()' below): if (method in foundMethods[unit].orEmpty()) { return } foundMethods.getOrPut(unit) { mutableSetOf() }.add(method) - val dependencies = getAllCallees(method) - dependencies.forEach { addStart(it) } + // val dependencies = getAllCallees(method) + // dependencies.forEach { addStart(it) } } private val IfdsVertex.traceGraph: TraceGraph @@ -101,6 +110,13 @@ class MainIfdsUnitManager( val allUnits = foundMethods.keys.toList() logger.info { "Starting analysis. Number of found units: ${allUnits.size}" } + for ((i, entry) in foundMethods.entries.withIndex()) { + val (unit, methods) = entry + logger.info { "Unit [${i + 1}/${foundMethods.size}] :: $unit :: total ${methods.size} methods" } + for ((j, method) in methods.withIndex()) { + logger.info { "- Method [${j + 1}/${methods.size}] $method" } + } + } val progressLoggerJob = launch { while (isActive) { @@ -128,6 +144,11 @@ class MainIfdsUnitManager( foundMethods[unit]!!.toList() ) aliveRunners[unit] = runner + // pathEdgesStorage[unit] = when (runner) { + // is BaseIfdsUnitRunner -> runner.pathEdges + // is BidiIfdsUnitRunnerFactory.BidiIfdsUnitRunner -> (runner.forwardRunner as BaseIfdsUnitRunner).pathEdges + // else -> error("Bad runner: $runner") + // } runner.launchIn(this) } @@ -138,10 +159,52 @@ class MainIfdsUnitManager( logger.info { "All jobs completed, gathering results..." } - val foundVulnerabilities = foundMethods.values.flatten().flatMap { method -> + val foundVulnerabilities = vulnerabilitiesStorage.knownMethods.flatMap { method -> vulnerabilitiesStorage.getCurrentFacts(method) } + logger.debug { "Total found ${foundVulnerabilities.size} sinks" } + for (vulnerability in foundVulnerabilities) { + logger.debug { "$vulnerability in ${vulnerability.method}" } + } + logger.info { "Total sinks: ${foundVulnerabilities.size}" } + + if (logger.isDebugEnabled()) { + val statsFileName = "stats.csv" + logger.debug { "Writing stats in '$statsFileName'..." } + File(statsFileName).outputStream().bufferedWriter().use { writer -> + val sep = ";" + writer.write(listOf("classname", "cwe", "method", "sink", "fact").joinToString(sep) + "\n") + for (vulnerability in foundVulnerabilities) { + val m = vulnerability.method + if (vulnerability.rule != null) { + for (cwe in vulnerability.rule.cwe) { + writer.write( + listOf( + m.enclosingClass.simpleName, + cwe, + m.name, + vulnerability.sink.statement, + vulnerability.sink.domainFact + ).joinToString(sep) { "\"$it\"" } + "\n") + } + } else if (vulnerability.sink.domainFact is NpeTaintNode) { + val cwe = 476 + writer.write( + listOf( + m.enclosingClass.simpleName, + cwe, + m.name, + vulnerability.sink.statement, + vulnerability.sink.domainFact + ).joinToString(sep) { "\"$it\"" } + "\n") + } else { + logger.warn { "Bad vulnerability without rule: $vulnerability" } + } + } + } + } + foundMethods.values.flatten().forEach { method -> for (crossUnitCall in crossUnitCallsStorage.getCurrentFacts(method)) { val calledMethod = graph.methodOf(crossUnitCall.calleeVertex.statement) @@ -152,7 +215,7 @@ class MainIfdsUnitManager( logger.info { "Restoring traces..." } foundVulnerabilities - .map { VulnerabilityInstance(it.vulnerabilityDescription, extendTraceGraph(it.sink.traceGraph)) } + .map { VulnerabilityInstance(it, extendTraceGraph(it.sink.traceGraph)) } .filter { it.traceGraph.sources.any { source -> graph.methodOf(source.statement) in startMethods || source.domainFact == ZEROFact @@ -163,7 +226,7 @@ class MainIfdsUnitManager( private val TraceGraph.methods: List get() { return (edges.keys.map { graph.methodOf(it.statement) } + - listOf(graph.methodOf(sink.statement))).distinct() + listOf(graph.methodOf(sink.statement))).distinct() } /** @@ -198,70 +261,93 @@ class MainIfdsUnitManager( return result } - override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { + override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { when (event) { is EdgeForOtherRunnerQuery -> { - val otherRunner = aliveRunners[unitResolver.resolve(event.edge.method)] ?: return + check(event.edge.from == event.edge.to) { "Edge for other runner must be a loop-edge" } + val method = event.edge.method + val unit = unitResolver.resolve(method) + val otherRunner = aliveRunners[unit] ?: return if (otherRunner.job?.isActive == true) { otherRunner.submitNewEdge(event.edge) } } + is NewSummaryFact -> { when (val fact = event.fact) { - is CrossUnitCallFact -> crossUnitCallsStorage.send(fact) - is SummaryEdgeFact -> summaryEdgesStorage.send(fact) - is TraceGraphFact -> tracesStorage.send(fact) - is VulnerabilityLocation -> vulnerabilitiesStorage.send(fact) + is CrossUnitCallFact -> crossUnitCallsStorage.add(fact) + is SummaryEdgeFact -> summaryEdgesStorage.add(fact) + is TraceGraphFact -> tracesStorage.add(fact) + is VulnerabilityLocation -> vulnerabilitiesStorage.add(fact) + else -> error("Unexpected $fact") } } + is QueueEmptinessChanged -> { eventChannel.send(Pair(event, runner)) } + is SubscriptionForSummaryEdges -> { eventChannel.send(Pair(event, runner)) - summaryEdgesStorage.getFacts(event.method).map { - it.edge - }.collect(event.collector) + summaryEdgesStorage + .getFacts(event.method) + .map { it.edge } + .collect(event.collector) } } } // Used to linearize all events that change dependencies or queue emptiness of runners - private val eventChannel: Channel>> = + private val eventChannel: Channel> = Channel(capacity = Int.MAX_VALUE) - private suspend fun dispatchDependencies() = eventChannel.consumeEach { (event, runner) -> - when (event) { - is SubscriptionForSummaryEdges -> { - dependencies.getOrPut(runner.unit) { mutableSetOf() } - .add(unitResolver.resolve(event.method)) - dependenciesRev.getOrPut(unitResolver.resolve(event.method)) { mutableSetOf() } - .add(runner.unit) - } - is QueueEmptinessChanged -> { - if (runner.unit !in aliveRunners) { - return@consumeEach + // TODO: replace async dispatcher with a synchronous one + private suspend fun dispatchDependencies() = coroutineScope { + eventChannel.consumeEach { (event, runner) -> + when (event) { + is SubscriptionForSummaryEdges -> { + dependencies.getOrPut(runner.unit) { mutableSetOf() } + .add(unitResolver.resolve(event.method)) + dependenciesRev.getOrPut(unitResolver.resolve(event.method)) { mutableSetOf() } + .add(runner.unit) } - queueEmptiness[runner.unit] = event.isEmpty - if (event.isEmpty) { - val toDelete = mutableListOf(runner.unit) - while (toDelete.isNotEmpty()) { - val current = toDelete.removeLast() - if (current in aliveRunners && - dependencies[runner.unit].orEmpty().all { queueEmptiness[it] != false } - ) { - aliveRunners[current]!!.job?.cancel() ?: error("Runner's job is not instantiated") - aliveRunners.remove(current) - for (next in dependenciesRev[current].orEmpty()) { - if (queueEmptiness[next] == true) { - toDelete.add(next) + + is QueueEmptinessChanged -> { + if (runner.unit !in aliveRunners) { + return@consumeEach + } + queueEmptiness[runner.unit] = event.isEmpty + // deleteJobs[runner.unit]?.run { + // logger.debug { "Cancelling the stopping of the runner for ${runner.unit}" } + // cancel() + // } + if (event.isEmpty) { + // deleteJobs[runner.unit] = launch { + // logger.debug { "Going to stop the runner for ${runner.unit} in 5 seconds..." } + // delay(5.seconds) + logger.info { "Stopping the runner for ${runner.unit}..." } + val toDelete = mutableListOf(runner.unit) + while (toDelete.isNotEmpty()) { + val current = toDelete.removeLast() + if (current in aliveRunners && + dependencies[runner.unit].orEmpty().all { queueEmptiness[it] != false } + ) { + if (aliveRunners[current] == null) continue + aliveRunners[current]!!.job?.cancel() ?: error("Runner's job is not instantiated") + aliveRunners.remove(current) + for (next in dependenciesRev[current].orEmpty()) { + if (queueEmptiness[next] == true) { + toDelete.add(next) + } } } } + // } } } + + else -> error("Unexpected event for dependencies dispatcher") } - else -> error("Unexpected event for dependencies dispatcher") } } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt index 77b12ded4..d255013bb 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt @@ -21,53 +21,71 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import org.jacodb.analysis.sarif.VulnerabilityDescription import org.jacodb.api.JcMethod +import org.jacodb.taint.configuration.TaintMethodSink import java.util.concurrent.ConcurrentHashMap /** * A common interface for anything that should be remembered and used * after the analysis of some unit is completed. */ -sealed interface SummaryFact { +interface SummaryFact { val method: JcMethod } /** * [SummaryFact] that denotes a possible vulnerability at [sink] */ -data class VulnerabilityLocation(val vulnerabilityDescription: VulnerabilityDescription, val sink: IfdsVertex) : SummaryFact { - override val method: JcMethod = sink.method +data class VulnerabilityLocation( + val vulnerabilityDescription: VulnerabilityDescription, + val sink: IfdsVertex, + val edge: IfdsEdge? = null, + val rule: TaintMethodSink? = null, +) : SummaryFact { + override val method: JcMethod + get() = sink.method } /** * Denotes some start-to-end edge that should be saved for the method */ -data class SummaryEdgeFact(val edge: IfdsEdge) : SummaryFact { - override val method: JcMethod = edge.method +data class SummaryEdgeFact( + val edge: IfdsEdge, +) : SummaryFact { + override val method: JcMethod + get() = edge.method } /** * Saves info about cross-unit call. * This info could later be used to restore full [TraceGraph]s */ -data class CrossUnitCallFact(val callerVertex: IfdsVertex, val calleeVertex: IfdsVertex) : SummaryFact { - override val method: JcMethod = callerVertex.method +data class CrossUnitCallFact( + val callerVertex: IfdsVertex, + val calleeVertex: IfdsVertex, +) : SummaryFact { + override val method: JcMethod + get() = callerVertex.method } /** * Wraps a [TraceGraph] that should be saved for some sink */ -data class TraceGraphFact(val graph: TraceGraph) : SummaryFact { - override val method: JcMethod = graph.sink.method +data class TraceGraphFact( + val graph: TraceGraph, +) : SummaryFact { + override val method: JcMethod + get() = graph.sink.method } /** * Contains summaries for many methods and allows to update them and subscribe for them. */ -interface SummaryStorage { +interface SummaryStorage { + /** * Adds [fact] to summary of its method */ - fun send(fact: T) + fun add(fact: T) /** * @return a flow with all facts summarized for the given [method]. @@ -87,25 +105,24 @@ interface SummaryStorage { val knownMethods: List } -class SummaryStorageImpl : SummaryStorage { +class SummaryStorageImpl : SummaryStorage + where T : SummaryFact { + private val summaries: MutableMap> = ConcurrentHashMap() private val outFlows: MutableMap> = ConcurrentHashMap() - override fun send(fact: T) { - if (summaries.computeIfAbsent(fact.method) { ConcurrentHashMap.newKeySet() }.add(fact)) { - val outFlow = outFlows.computeIfAbsent(fact.method) { MutableSharedFlow(replay = Int.MAX_VALUE) } - require(outFlow.tryEmit(fact)) + override fun add(fact: T) { + val isNew = summaries.computeIfAbsent(fact.method) { ConcurrentHashMap.newKeySet() }.add(fact) + if (isNew) { + val flow = outFlows.computeIfAbsent(fact.method) { + MutableSharedFlow(replay = Int.MAX_VALUE) + } + check(flow.tryEmit(fact)) } } override fun getFacts(method: JcMethod): SharedFlow { - return outFlows.computeIfAbsent(method) { - MutableSharedFlow(replay = Int.MAX_VALUE).also { flow -> - summaries[method].orEmpty().forEach { fact -> - require(flow.tryEmit(fact)) - } - } - } + return outFlows[method] ?: MutableSharedFlow() } override fun getCurrentFacts(method: JcMethod): List { @@ -114,4 +131,4 @@ class SummaryStorageImpl : SummaryStorage { override val knownMethods: List get() = summaries.keys.toList() -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt index 8b882699d..290a80843 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt @@ -14,14 +14,26 @@ * limitations under the License. */ +@file:Suppress("PublicApiImplicitType") + package org.jacodb.analysis.engine -import org.jacodb.analysis.library.MethodUnitResolver -import org.jacodb.analysis.library.PackageUnitResolver -import org.jacodb.analysis.library.SingletonUnitResolver -import org.jacodb.analysis.library.getClassUnitResolver import org.jacodb.analysis.runAnalysis +import org.jacodb.api.JcClassOrInterface import org.jacodb.api.JcMethod +import org.jacodb.api.ext.packageName + +interface UnitType + +data class MethodUnit(val method: JcMethod) : UnitType + +data class ClassUnit(val clazz: JcClassOrInterface) : UnitType + +data class PackageUnit(val packageName: String) : UnitType + +object UnknownUnit : UnitType + +object SingletonUnit : UnitType /** * Sets a mapping from [JcMethod] to abstract domain [UnitType]. @@ -31,18 +43,39 @@ import org.jacodb.api.JcMethod * * To get more info about how it is used in analysis, see [runAnalysis]. */ -fun interface UnitResolver { +fun interface UnitResolver { + fun resolve(method: JcMethod): UnitType companion object { - fun getByName(name: String): UnitResolver<*> { - return when (name) { - "method" -> MethodUnitResolver - "class" -> getClassUnitResolver(false) - "package" -> PackageUnitResolver - "singleton" -> SingletonUnitResolver - else -> error("Unknown unit resolver $name") - } + fun getByName(name: String): UnitResolver = when (name) { + "method" -> MethodUnitResolver + "class" -> ClassUnitResolver(false) + "package" -> PackageUnitResolver + "singleton" -> SingletonUnitResolver + else -> error("Unknown unit resolver '$name'") } } -} \ No newline at end of file +} + +val MethodUnitResolver = UnitResolver { method -> + MethodUnit(method) +} + +@Suppress("FunctionName") +fun ClassUnitResolver(includeNested: Boolean) = UnitResolver { method -> + val clazz = if (includeNested) { + generateSequence(method.enclosingClass) { it.outerClass }.last() + } else { + method.enclosingClass + } + ClassUnit(clazz) +} + +val PackageUnitResolver = UnitResolver { method -> + PackageUnit(method.enclosingClass.packageName) +} + +val SingletonUnitResolver = UnitResolver { _ -> + SingletonUnit +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt index 35583b981..37fdafcfa 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt @@ -16,8 +16,6 @@ package org.jacodb.analysis.engine -import org.jacodb.analysis.sarif.VulnerabilityDescription - /** * Represents a vulnerability (issue) found by analysis * @@ -25,6 +23,6 @@ import org.jacodb.analysis.sarif.VulnerabilityDescription * @property traceGraph contains sink, sources and traces that lead to occurrence of vulnerability */ data class VulnerabilityInstance( - val vulnerabilityDescription: VulnerabilityDescription, - val traceGraph: TraceGraph -) \ No newline at end of file + val location: VulnerabilityLocation, + val traceGraph: TraceGraph, +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/ApplicationGraphFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/ApplicationGraphFactory.kt index af7ea04c2..e4dfdd9e7 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/ApplicationGraphFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/ApplicationGraphFactory.kt @@ -15,8 +15,10 @@ */ @file:JvmName("ApplicationGraphFactory") + package org.jacodb.analysis.graph +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.future.future import org.jacodb.api.JcClasspath @@ -36,18 +38,24 @@ suspend fun JcClasspath.newApplicationGraphForAnalysis(bannedPackagePrefixes: Li } } -fun JcClasspath.asyncNewApplicationGraphForAnalysis( - bannedPackagePrefixes: List? = null -): CompletableFuture { - return GlobalScope.future { +/** + * Async adapter for calling [newApplicationGraphForAnalysis] from Java. + * + * See also: [answer on StackOverflow](https://stackoverflow.com/a/52887677/3592218). + */ +@OptIn(DelicateCoroutinesApi::class) +fun JcClasspath.newApplicationGraphForAnalysisAsync( + bannedPackagePrefixes: List? = null, +): CompletableFuture = + GlobalScope.future { newApplicationGraphForAnalysis(bannedPackagePrefixes) } -} val defaultBannedPackagePrefixes: List = listOf( "kotlin.", "java.", "jdk.internal.", "sun.", + "com.sun.", "javax.", -) \ No newline at end of file +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/BackwardApplicationGraphs.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/BackwardGraphs.kt similarity index 80% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/BackwardApplicationGraphs.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/BackwardGraphs.kt index 6dfaf7168..4d728aed1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/BackwardApplicationGraphs.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/BackwardGraphs.kt @@ -15,6 +15,7 @@ */ @file:JvmName("BackwardApplicationGraphs") + package org.jacodb.analysis.graph import org.jacodb.api.JcClasspath @@ -24,19 +25,21 @@ import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst private class BackwardApplicationGraph( - val forward: ApplicationGraph + val forward: ApplicationGraph, ) : ApplicationGraph { - override fun predecessors(node: Statement) = forward.successors(node) + init { + require(forward !is BackwardApplicationGraph) + } + + override fun predecessors(node: Statement) = forward.successors(node) override fun successors(node: Statement) = forward.predecessors(node) override fun callees(node: Statement) = forward.callees(node) - override fun callers(method: Method) = forward.callers(method) - override fun entryPoint(method: Method) = forward.exitPoints(method) - - override fun exitPoints(method: Method) = forward.entryPoint(method) + override fun entryPoints(method: Method) = forward.exitPoints(method) + override fun exitPoints(method: Method) = forward.entryPoints(method) override fun methodOf(node: Statement) = forward.methodOf(node) } @@ -48,8 +51,14 @@ val ApplicationGraph.reversed BackwardApplicationGraph(this) } -private class BackwardJcApplicationGraph(val forward: JcApplicationGraph) : - JcApplicationGraph, ApplicationGraph by BackwardApplicationGraph(forward) { +internal class BackwardJcApplicationGraph(val forward: JcApplicationGraph) : + JcApplicationGraph, + ApplicationGraph by BackwardApplicationGraph(forward) { + + init { + require(forward !is BackwardJcApplicationGraph) + } + override val classpath: JcClasspath get() = forward.classpath } @@ -59,4 +68,4 @@ val JcApplicationGraph.reversed: JcApplicationGraph this.forward } else { BackwardJcApplicationGraph(this) - } \ No newline at end of file + } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt new file mode 100644 index 000000000..4536a0f07 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.graph + +import info.leadinglight.jdot.Edge +import info.leadinglight.jdot.Graph +import info.leadinglight.jdot.Node +import info.leadinglight.jdot.enums.Color +import info.leadinglight.jdot.enums.Shape +import info.leadinglight.jdot.impl.Util +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcInst +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +fun JcApplicationGraph.dfs( + node: JcInst, + method: JcMethod, + visited: MutableSet, +) { + if (visited.add(node)) { + for (next in successors(node)) { + if (next.location.method == method) { + dfs(next, method, visited) + } + } + } +} + +fun JcApplicationGraph.view(method: JcMethod, dotCmd: String, viewerCmd: String) { + Util.sh(arrayOf(viewerCmd, "file://${toFile(method, dotCmd)}")) +} + +fun JcApplicationGraph.toFile( + method: JcMethod, + dotCmd: String, + file: File? = null, +): Path { + Graph.setDefaultCmd(dotCmd) + + val graph = Graph("jcApplicationGraph") + graph.setBgColor(Color.X11.transparent) + graph.setFontSize(12.0) + graph.setFontName("monospace") + + val allInstructions: MutableSet = hashSetOf() + for (start in entryPoints(method)) { + dfs(start, method, allInstructions) + } + + val nodes = mutableMapOf() + for ((index, inst) in allInstructions.withIndex()) { + val node = Node("$index") + .setShape(Shape.box) + .setLabel(inst.toString().replace("\"", "\\\"")) + .setFontSize(12.0) + nodes[inst] = node + graph.addNode(node) + } + + for ((inst, node) in nodes) { + for (next in successors(inst)) { + if (next in nodes) { + val edge = Edge(node.name, nodes[next]!!.name) + graph.addEdge(edge) + } + } + } + + val outFile = graph.dot2file("svg") + val newFile = "${outFile.removeSuffix("out")}svg" + val resultingFile = file?.toPath() ?: File(newFile).toPath() + Files.move(File(outFile).toPath(), resultingFile) + return resultingFile +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcApplicationGraphImpl.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcApplicationGraphImpl.kt index a53a08454..2909cfd65 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcApplicationGraphImpl.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcApplicationGraphImpl.kt @@ -28,48 +28,45 @@ import org.jacodb.impl.features.SyncUsagesExtension */ open class JcApplicationGraphImpl( override val classpath: JcClasspath, - private val usages: SyncUsagesExtension + private val usages: SyncUsagesExtension, ) : JcApplicationGraph { - private val methods = mutableSetOf() - override fun predecessors(node: JcInst): Sequence { - return node.location.method.flowGraph().predecessors(node).asSequence() + - node.location.method.flowGraph().throwers(node).asSequence() + val graph = node.location.method.flowGraph() + val predecessors = graph.predecessors(node) + val throwers = graph.throwers(node) + return predecessors.asSequence() + throwers.asSequence() } override fun successors(node: JcInst): Sequence { - return node.location.method.flowGraph().successors(node).asSequence() + - node.location.method.flowGraph().catchers(node).asSequence() + val graph = node.location.method.flowGraph() + val successors = graph.successors(node) + val catchers = graph.catchers(node) + return successors.asSequence() + catchers.asSequence() } override fun callees(node: JcInst): Sequence { - return node.callExpr?.method?.method?.let { - methods.add(it) - sequenceOf(it) - } ?: emptySequence() + val callExpr = node.callExpr ?: return emptySequence() + return sequenceOf(callExpr.method.method) } override fun callers(method: JcMethod): Sequence { - methods.add(method) return usages.findUsages(method).flatMap { - it.flowGraph().instructions.filter { inst -> - inst.callExpr?.method?.method == method - }.asSequence() + it.flowGraph().instructions.asSequence().filter { inst -> + val callExpr = inst.callExpr ?: return@filter false + callExpr.method.method == method + } } } - - override fun entryPoint(method: JcMethod): Sequence { - methods.add(method) + override fun entryPoints(method: JcMethod): Sequence { return method.flowGraph().entries.asSequence() } override fun exitPoints(method: JcMethod): Sequence { - methods.add(method) return method.flowGraph().exits.asSequence() } override fun methodOf(node: JcInst): JcMethod { - return node.location.method.also { methods.add(it) } + return node.location.method } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcNoopInst.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcNoopInst.kt new file mode 100644 index 000000000..3d693800a --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/JcNoopInst.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.graph + +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstLocation +import org.jacodb.api.cfg.JcInstVisitor + +data class JcNoopInst(override val location: JcInstLocation) : JcInst { + override val operands: List + get() = emptyList() + + override fun accept(visitor: JcInstVisitor): T { + return visitor.visitExternalJcInst(this) + } + + override fun toString(): String = "noop" +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt index 1f3fcd708..7c622fbde 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt @@ -20,10 +20,7 @@ import kotlinx.coroutines.runBlocking import org.jacodb.api.JcClassType import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcExpr import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstLocation -import org.jacodb.api.cfg.JcInstVisitor import org.jacodb.api.cfg.JcVirtualCallExpr import org.jacodb.api.ext.cfg.callExpr import org.jacodb.api.ext.isSubClassOf @@ -33,14 +30,14 @@ import org.jacodb.impl.features.hierarchyExt /** * This is adopted specially for IFDS [JcApplicationGraph] that * 1. Ignores method calls matching [bannedPackagePrefixes] (i.e., treats them as simple instructions with no callees) - * 2. In [callers] returns only callsites that were visited before + * 2. In [callers] returns only call sites that were visited before * 3. Adds a special [JcNoopInst] instruction to the beginning of each method * (because backward analysis may want for method to start with neutral instruction) */ internal class SimplifiedJcApplicationGraph( - private val impl: JcApplicationGraphImpl, + private val graph: JcApplicationGraph, private val bannedPackagePrefixes: List, -) : JcApplicationGraph by impl { +) : JcApplicationGraph by graph { private val hierarchyExtension = runBlocking { classpath.hierarchyExt() } @@ -62,34 +59,42 @@ internal class SimplifiedJcApplicationGraph( // For backward analysis we may want for method to start with "neutral" operation => // we add noop to the beginning of every method private fun getStartInst(method: JcMethod): JcNoopInst { - val methodEntryLineNumber = method.flowGraph().entries.firstOrNull()?.lineNumber - return JcNoopInst(JcInstLocationImpl(method, -1, methodEntryLineNumber?.let { it - 1 } ?: -1)) + val lineNumber = method.flowGraph().entries.firstOrNull()?.lineNumber?.let { it - 1 } ?: -1 + return JcNoopInst(JcInstLocationImpl(method, -1, lineNumber)) } override fun predecessors(node: JcInst): Sequence { val method = methodOf(node) - return if (node == getStartInst(method)) { - emptySequence() - } else { - if (node in impl.entryPoint(method)) { + return when (node) { + getStartInst(method) -> { + emptySequence() + } + + in graph.entryPoints(method) -> { sequenceOf(getStartInst(method)) - } else { - impl.predecessors(node) + } + + else -> { + graph.predecessors(node) } } } override fun successors(node: JcInst): Sequence { val method = methodOf(node) - return if (node == getStartInst(method)) { - impl.entryPoint(method) - } else { - impl.successors(node) + return when (node) { + getStartInst(method) -> { + graph.entryPoints(method) + } + + else -> { + graph.successors(node) + } } } private fun calleesUnmarked(node: JcInst): Sequence { - val callees = impl.callees(node).filterNot { callee -> + val callees = graph.callees(node).filterNot { callee -> bannedPackagePrefixes.any { callee.enclosingClass.name.startsWith(it) } } @@ -101,8 +106,8 @@ internal class SimplifiedJcApplicationGraph( val allOverrides = getOverrides(callee) .filter { it.enclosingClass isSubClassOf instanceClass || - // TODO: use only down-most override here - instanceClass isSubClassOf it.enclosingClass + // TODO: use only down-most override here + instanceClass isSubClassOf it.enclosingClass } // TODO: maybe filter inaccessible methods here? @@ -112,8 +117,8 @@ internal class SimplifiedJcApplicationGraph( override fun callees(node: JcInst): Sequence { return calleesUnmarked(node).also { - it.forEach { - visitedCallers.getOrPut(it) { mutableSetOf() }.add(node) + it.forEach { method -> + visitedCallers.getOrPut(method) { mutableSetOf() }.add(node) } } } @@ -123,22 +128,20 @@ internal class SimplifiedJcApplicationGraph( * In IFDS we don't need all method callers, we need only method callers which we visited earlier. */ // TODO: Think if this optimization is really needed - override fun callers(method: JcMethod): Sequence = visitedCallers.getOrDefault(method, mutableSetOf()).asSequence() + override fun callers(method: JcMethod): Sequence = + visitedCallers[method].orEmpty().asSequence() - override fun entryPoint(method: JcMethod): Sequence = sequenceOf(getStartInst(method)) - - companion object { + override fun entryPoints(method: JcMethod): Sequence = try { + sequenceOf(getStartInst(method)) + } catch (e: Throwable) { + emptySequence() } -} - -data class JcNoopInst(override val location: JcInstLocation): JcInst { - override val operands: List - get() = emptyList() - - override fun accept(visitor: JcInstVisitor): T { - return visitor.visitExternalJcInst(this) + override fun exitPoints(method: JcMethod): Sequence = try { + graph.exitPoints(method) + } catch (e: Throwable) { + emptySequence() } - override fun toString(): String = "noop" -} \ No newline at end of file + companion object +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt new file mode 100644 index 000000000..fdd688e34 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +import org.jacodb.analysis.ifds2.taint.Zero + +/** + * Aggregates all facts and edges found by the tabulation algorithm. + */ +class Aggregate( + pathEdges: Collection>, + val facts: Map>, + val reasons: Map, Set>, +) { + private val pathEdgesBySink: Map, Collection>> = + pathEdges.groupByTo(HashMap()) { it.to } + + fun buildTraceGraph(sink: Vertex): TraceGraph { + val sources: MutableSet> = hashSetOf() + val edges: MutableMap, MutableSet>> = + hashMapOf() + val visited: MutableSet> = hashSetOf() + + fun addEdge( + from: Vertex, + to: Vertex, + ) { + if (from != to) { + edges.getOrPut(from) { hashSetOf() }.add(to) + } + } + + fun dfs( + edge: Edge, + lastVertex: Vertex, + stopAtMethodStart: Boolean, + ) { + if (!visited.add(edge)) { + return + } + + if (stopAtMethodStart && edge.from == edge.to) { + addEdge(edge.from, lastVertex) + return + } + + val vertex = edge.to + // FIXME: not all domains have "Zero" fact! + if (vertex.fact == Zero) { + addEdge(vertex, lastVertex) + sources.add(vertex) + return + } + + for (reason in reasons[edge].orEmpty()) { + when (reason) { + is Reason.Sequent<*> -> { + @Suppress("UNCHECKED_CAST") + reason as Reason.Sequent + val predEdge = reason.edge + if (predEdge.to.fact == vertex.fact) { + dfs(predEdge, lastVertex, stopAtMethodStart) + } else { + addEdge(predEdge.to, lastVertex) + dfs(predEdge, predEdge.to, stopAtMethodStart) + } + } + + is Reason.CallToStart<*> -> { + @Suppress("UNCHECKED_CAST") + reason as Reason.CallToStart + val predEdge = reason.edge + if (!stopAtMethodStart) { + addEdge(predEdge.to, lastVertex) + dfs(predEdge, predEdge.to, false) + } + } + + is Reason.ThroughSummary<*> -> { + @Suppress("UNCHECKED_CAST") + reason as Reason.ThroughSummary + val predEdge = reason.edge + val summaryEdge = reason.summaryEdge + addEdge(summaryEdge.to, lastVertex) // Return to next vertex + addEdge(predEdge.to, summaryEdge.from) // Call to start + dfs(summaryEdge, summaryEdge.to, true) // Expand summary edge + dfs(predEdge, predEdge.to, stopAtMethodStart) // Continue normal analysis + } + + is Reason.External -> { + // TODO: check + addEdge(edge.to, lastVertex) + if (edge.from != edge.to) { + // TODO: ideally, we should analyze the place from which the edge was given to ifds, + // for now we just go to method start + dfs(Edge(edge.from, edge.from), edge.to, stopAtMethodStart) + } + } + + is Reason.Initial -> { + // TODO: check + sources.add(vertex) + addEdge(edge.to, lastVertex) + } + } + } + } + + for (edge in pathEdgesBySink[sink].orEmpty()) { + dfs(edge, edge.to, false) + } + return TraceGraph(sink, sources, edges) + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt new file mode 100644 index 000000000..b10228505 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +import org.jacodb.api.JcMethod + +interface Analyzer { + val flowFunctions: FlowFunctions + + fun isSkipped(method: JcMethod): Boolean = false + + fun handleNewEdge( + edge: Edge, + ): List + + fun handleCrossUnitCall( + caller: Vertex, + callee: Vertex, + ): List +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt new file mode 100644 index 000000000..10de7dacb --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +import org.jacodb.analysis.engine.IfdsEdge +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.api.JcMethod + +data class Edge( + val from: Vertex, + val to: Vertex, +) { + init { + require(from.method == to.method) + } + + val method: JcMethod + get() = from.method + + companion object { + // constructor + operator fun invoke(edge: IfdsEdge): Edge { + return Edge(Vertex(edge.from), Vertex(edge.to)) + } + } +} + +fun Edge.toIfds(): IfdsEdge = IfdsEdge(from.toIfds(), to.toIfds()) + +sealed class Reason { + + object Initial : Reason() + + object External : Reason() + + data class Sequent( + val edge: Edge, + ) : Reason() + + data class CallToStart( + val edge: Edge, + ) : Reason() + + data class ThroughSummary( + val edge: Edge, + val summaryEdge: Edge, + ) : Reason() + +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt new file mode 100644 index 000000000..5b41a5a9d --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("LiftReturnOrAssignment") + +package org.jacodb.analysis.ifds2 + +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst + +fun interface FlowFunction { + fun compute(fact: Fact): Collection +} + +interface FlowFunctions { + + /** + * Method for obtaining initial domain facts at the method entrypoint. + * Commonly, it is only `listOf(Zero)`. + */ + fun obtainPossibleStartFacts(method: JcMethod): Collection + + /** + * Sequent flow function. + * + * ``` + * [ DO() ] :: current + * | + * | (sequent edge) + * | + * [ DO() ] + * ``` + */ + fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ): FlowFunction + + /** + * Call-to-return-site flow function. + * + * ``` + * [ CALL p ] :: callStatement + * : + * : (call-to-return-site edge) + * : + * [ RETURN FROM p ] :: returnSite + * ``` + */ + fun obtainCallToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + ): FlowFunction + + /** + * Call-to-start flow function. + * + * ``` + * [ CALL p ] :: callStatement + * : \ + * : \ (call-to-start edge) + * : \ + * : [ START p ] + * : | + * : [ EXIT p ] + * : / + * : / + * [ RETURN FROM p ] + * ``` + */ + fun obtainCallToStartFlowFunction( + callStatement: JcInst, + calleeStart: JcInst, + ): FlowFunction + + /** + * Exit-to-return-site flow function. + * + * ``` + * [ CALL p ] :: callStatement + * : \ + * : \ + * : [ START p ] + * : | + * : [ EXIT p ] :: exitStatement + * : / + * : / (exit-to-return-site edge) + * : / + * [ RETURN FROM p ] :: returnSite + * ``` + */ + fun obtainExitToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + exitStatement: JcInst, + ): FlowFunction +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt new file mode 100644 index 000000000..1acc43a4a --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +import kotlinx.coroutines.CoroutineScope +import org.jacodb.api.JcMethod + +interface Manager { + fun handleEvent(event: Event) + + suspend fun handleControlEvent(event: ControlEvent) + + fun subscribeOnSummaryEdges( + method: JcMethod, + scope: CoroutineScope, + handler: suspend (Edge) -> Unit, + ) +} + +sealed interface ControlEvent + +data class QueueEmptinessChanged( + val runner: IRunner<*>, + val isEmpty: Boolean, +) : ControlEvent diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt new file mode 100644 index 000000000..ada9f59b4 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -0,0 +1,292 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.getOrElse +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.isActive +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.analysis.engine.UnitType +import org.jacodb.analysis.ifds2.taint.BidiRunner +import org.jacodb.analysis.ifds2.taint.Zero +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.ext.cfg.callExpr +import java.util.concurrent.ConcurrentHashMap + +private val logger = KotlinLogging.logger {} + +typealias Method = JcMethod +typealias Statement = JcInst + +interface IRunner { + + val unit: UnitType + + suspend fun run(startMethods: List) + fun submitNewEdge(edge: Edge) +} + +@Suppress("RecursivePropertyAccessor") +val IRunner<*>.pathEdges: Set> + get() = when (this) { + is Runner<*, *> -> pathEdges + is BidiRunner -> forwardRunner.pathEdges + backwardRunner.pathEdges + else -> error("Cannot extract pathEdges for $this") + } + +// TODO: make all fields private again +class Runner( + internal val graph: JcApplicationGraph, + internal val analyzer: Analyzer, + internal val manager: Manager, + internal val unitResolver: UnitResolver, + override val unit: UnitType, +) : IRunner { + + internal val flowSpace: FlowFunctions = + analyzer.flowFunctions + internal val workList: Channel> = + Channel(Channel.UNLIMITED) + internal val pathEdges: MutableSet> = + ConcurrentHashMap.newKeySet() + internal val reasons: MutableMap, MutableSet> = + ConcurrentHashMap() + internal val summaryEdges: MutableMap, MutableSet>> = + hashMapOf() + internal val callerPathEdgeOf: MutableMap, MutableSet>> = + hashMapOf() + + private val Edge.reasons: List + get() = this@Runner.reasons[this]!!.toList() + + override suspend fun run(startMethods: List) { + for (method in startMethods) { + addStart(method) + } + + tabulationAlgorithm() + } + + // TODO: should 'addStart' be public? + // TODO: should 'addStart' replace 'submitNewEdge'? + // TODO: inline + private fun addStart(method: JcMethod) { + require(unitResolver.resolve(method) == unit) + val startFacts = flowSpace.obtainPossibleStartFacts(method) + for (startFact in startFacts) { + for (start in graph.entryPoints(method)) { + val vertex = Vertex(start, startFact) + val edge = Edge(vertex, vertex) // loop + val reason = Reason.Initial + propagate(edge, reason) + } + } + } + + override fun submitNewEdge(edge: Edge) { + // TODO: add default-argument 'reason = Reason.External' to 'submitNewEdge' + propagate(edge, Reason.External) + } + + private fun propagate( + edge: Edge, + reason: Reason, + ): Boolean { + require(unitResolver.resolve(edge.method) == unit) { + "Propagated edge must be in the same unit" + } + + reasons.getOrPut(edge) { mutableSetOf() }.add(reason) + + // Handle only NEW edges: + if (pathEdges.add(edge)) { + val doPrintOnlyForward = true + val doPrintZero = false + if (!doPrintOnlyForward || edge.from.statement.toString() == "noop") { + if (doPrintZero || edge.to.fact != Zero) { + logger.trace { "Propagating edge=$edge in method=${edge.method} via reason=${reason}" } + } + } + + // Send edge to analyzer/manager: + for (event in analyzer.handleNewEdge(edge)) { + manager.handleEvent(event) + } + + // Add edge to worklist: + // workList.send(edge) + workList.trySend(edge).getOrThrow() + + return true + } + + return false + } + + private suspend fun tabulationAlgorithm() = coroutineScope { + while (isActive) { + val edge = workList.tryReceive().getOrElse { + manager.handleControlEvent(QueueEmptinessChanged(this@Runner, true)) + val edge = workList.receive() + manager.handleControlEvent(QueueEmptinessChanged(this@Runner, false)) + edge + } + tabulationAlgorithmStep(edge, this@coroutineScope) + } + } + + private val Method.isExtern: Boolean + get() = unitResolver.resolve(this) != unit + + private fun tabulationAlgorithmStep( + currentEdge: Edge, + scope: CoroutineScope, + ) { + val (startVertex, currentVertex) = currentEdge + val (current, currentFact) = currentVertex + + val currentCallees = graph.callees(current).toList() + val currentIsCall = current.callExpr != null + val currentIsExit = current in graph.exitPoints(current.location.method) + + if (currentIsCall) { + // Propagate through the call-to-return-site edge: + for (returnSite in graph.successors(current)) { + val factsAtReturnSite = flowSpace + .obtainCallToReturnSiteFlowFunction(current, returnSite) + .compute(currentFact) + for (returnSiteFact in factsAtReturnSite) { + val returnSiteVertex = Vertex(returnSite, returnSiteFact) + val newEdge = Edge(startVertex, returnSiteVertex) + val reason = Reason.Sequent(currentEdge) + propagate(newEdge, reason) + } + } + + // Propagate through the call: + for (callee in currentCallees) { + // TODO: check whether we need to analyze the callee (or it was skipped due to MethodSource) + if (analyzer.isSkipped(callee)) { + logger.info { "Skipping method $callee" } + continue + } + + for (calleeStart in graph.entryPoints(callee)) { + val factsAtCalleeStart = flowSpace + .obtainCallToStartFlowFunction(current, calleeStart) + .compute(currentFact) + for (calleeStartFact in factsAtCalleeStart) { + val calleeStartVertex = Vertex(calleeStart, calleeStartFact) + + if (callee.isExtern) { + // Initialize analysis of callee: + for (event in analyzer.handleCrossUnitCall(currentVertex, calleeStartVertex)) { + manager.handleEvent(event) + } + + // Subscribe on summary edges: + manager.subscribeOnSummaryEdges(callee, scope) { summaryEdge -> + if (summaryEdge.from == calleeStartVertex) { + handleSummaryEdge(currentEdge, summaryEdge) + } else { + logger.debug { "Skipping unsuitable summary edge: $summaryEdge" } + } + } + } else { + // Save info about the call for summary edges that will be found later: + callerPathEdgeOf.getOrPut(calleeStartVertex) { hashSetOf() }.add(currentEdge) + + // Initialize analysis of callee: + run { + val newEdge = Edge(calleeStartVertex, calleeStartVertex) // loop + val reason = Reason.CallToStart(currentEdge) + propagate(newEdge, reason) + } + + // Handle already-found summary edges: + for (exitVertex in summaryEdges[calleeStartVertex].orEmpty()) { + val summaryEdge = Edge(calleeStartVertex, exitVertex) + handleSummaryEdge(currentEdge, summaryEdge) + } + } + } + } + } + } else { + if (currentIsExit) { + // Propagate through the summary edge: + for (callerPathEdge in callerPathEdgeOf[startVertex].orEmpty()) { + handleSummaryEdge(currentEdge = callerPathEdge, summaryEdge = currentEdge) + } + + // Add new summary edge: + summaryEdges.getOrPut(startVertex) { hashSetOf() }.add(currentVertex) + } + + // Simple (sequential) propagation to the next instruction: + for (next in graph.successors(current)) { + val factsAtNext = flowSpace + .obtainSequentFlowFunction(current, next) + .compute(currentFact) + for (nextFact in factsAtNext) { + val nextVertex = Vertex(next, nextFact) + val newEdge = Edge(startVertex, nextVertex) + val reason = Reason.Sequent(currentEdge) + propagate(newEdge, reason) + } + } + } + } + + private fun handleSummaryEdge( + currentEdge: Edge, + summaryEdge: Edge, + ) { + val (startVertex, currentVertex) = currentEdge + val caller = currentVertex.statement + for (returnSite in graph.successors(caller)) { + val (exit, exitFact) = summaryEdge.to + val finalFacts = flowSpace + .obtainExitToReturnSiteFlowFunction(caller, returnSite, exit) + .compute(exitFact) + for (returnSiteFact in finalFacts) { + val returnSiteVertex = Vertex(returnSite, returnSiteFact) + val newEdge = Edge(startVertex, returnSiteVertex) + val reason = Reason.ThroughSummary(currentEdge, summaryEdge) + propagate(newEdge, reason) + } + } + } + + private fun getFinalFacts(): Map> { + val resultFacts: MutableMap> = mutableMapOf() + for (edge in pathEdges) { + resultFacts.getOrPut(edge.to.statement) { mutableSetOf() }.add(edge.to.fact) + } + return resultFacts + } + + private fun getAggregate(): Aggregate { + val facts = getFinalFacts() + return Aggregate(pathEdges, facts, reasons) + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt new file mode 100644 index 000000000..b082c014b --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +data class TraceGraph( + val sink: Vertex, + val sources: Set>, + val edges: Map, Set>>, +) { + + /** + * Returns all traces from [sources] to [sink]. + */ + fun getAllTraces(): Sequence>> = sequence { + for (v in sources) { + yieldAll(getAllTraces(mutableListOf(v))) + } + } + + private fun getAllTraces( + trace: MutableList>, + ): Sequence>> = sequence { + val v = trace.last() + if (v == sink) { + yield(trace.toList()) // copy list + return@sequence + } + for (u in edges[v].orEmpty()) { + if (u !in trace) { + trace.add(u) + yieldAll(getAllTraces(trace)) + trace.removeLast() + } + } + } + + /** + * Merges two graphs. + * + * [sink] will be chosen from receiver, and edges from both graphs will be merged. + * Also, all edges from [upGraph]'s sink to [entryPoints] will be added + * (these are edges "connecting" [upGraph] with receiver). + * + * Informally, this method extends receiver's traces from one side using [upGraph]. + */ + fun mergeWithUpGraph( + upGraph: TraceGraph, + entryPoints: Set>, + ): TraceGraph { + val validEntryPoints = entryPoints.intersect(edges.keys) + if (validEntryPoints.isEmpty()) return this + + val newSources = sources + upGraph.sources + val newEdges = edges.toMutableMap() + for ((source, destinations) in upGraph.edges) { + newEdges[source] = newEdges.getOrDefault(source, emptySet()) + destinations + } + newEdges[upGraph.sink] = newEdges.getOrDefault(upGraph.sink, emptySet()) + validEntryPoints + return TraceGraph(sink, newSources, newEdges) + } + + companion object { + fun bySink( + sink: Vertex, + ): TraceGraph { + return TraceGraph(sink, setOf(sink), emptyMap()) + } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt new file mode 100644 index 000000000..78b58071a --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2 + +import org.jacodb.analysis.engine.IfdsVertex +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.analysis.ifds2.taint.toDomainFact +import org.jacodb.analysis.ifds2.taint.toFact +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst + +data class Vertex( + val statement: JcInst, + val fact: Fact, +) { + val method: JcMethod + get() = statement.location.method + + companion object { + // constructor + operator fun invoke(vertex: IfdsVertex): Vertex { + return Vertex(vertex.statement, vertex.domainFact.toFact()) + } + } +} + +fun Vertex.toIfds(): IfdsVertex = + IfdsVertex(statement, fact.toDomainFact()) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt new file mode 100644 index 000000000..5275f3f8b --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.analysis.engine.UnitType +import org.jacodb.analysis.ifds2.ControlEvent +import org.jacodb.analysis.ifds2.Edge +import org.jacodb.analysis.ifds2.IRunner +import org.jacodb.analysis.ifds2.Manager +import org.jacodb.analysis.ifds2.QueueEmptinessChanged +import org.jacodb.api.JcMethod + +class BidiRunner( + val manager: TaintManager, + val unitResolver: UnitResolver, + override val unit: UnitType, + newForwardRunner: (Manager) -> TaintRunner, + newBackwardRunner: (Manager) -> TaintRunner, +) : IRunner { + + @Volatile + private var forwardQueueIsEmpty: Boolean = false + + @Volatile + private var backwardQueueIsEmpty: Boolean = false + + private val forwardManager: Manager = + object : Manager { + override fun handleEvent(event: TaintEvent) { + when (event) { + is EdgeForOtherRunner -> { + if (unitResolver.resolve(event.edge.method) == unit) { + // Submit new edge directly to the backward runner: + backwardRunner.submitNewEdge(event.edge) + } else { + // Submit new edge via the manager: + manager.handleEvent(event) + } + } + + else -> manager.handleEvent(event) + } + } + + override suspend fun handleControlEvent(event: ControlEvent) { + when (event) { + is QueueEmptinessChanged -> { + forwardQueueIsEmpty = event.isEmpty + val newEvent = QueueEmptinessChanged(event.runner, forwardQueueIsEmpty && backwardQueueIsEmpty) + manager.handleControlEvent(newEvent) + } + } + } + + override fun subscribeOnSummaryEdges( + method: JcMethod, + scope: CoroutineScope, + handler: suspend (TaintEdge) -> Unit, + ) { + manager.subscribeOnSummaryEdges(method, scope, handler) + } + } + + private val backwardManager: Manager = + object : Manager { + override fun handleEvent(event: TaintEvent) { + when (event) { + is EdgeForOtherRunner -> { + check(unitResolver.resolve(event.edge.method) == unit) + // Submit new edge directly to the forward runner: + forwardRunner.submitNewEdge(event.edge) + } + + else -> manager.handleEvent(event) + } + } + + override suspend fun handleControlEvent(event: ControlEvent) { + when (event) { + is QueueEmptinessChanged -> { + backwardQueueIsEmpty = event.isEmpty + val newEvent = QueueEmptinessChanged(event.runner, forwardQueueIsEmpty && backwardQueueIsEmpty) + manager.handleControlEvent(newEvent) + } + } + } + + override fun subscribeOnSummaryEdges( + method: JcMethod, + scope: CoroutineScope, + handler: suspend (TaintEdge) -> Unit, + ) { + // TODO: ignore? + manager.subscribeOnSummaryEdges(method, scope, handler) + } + } + + val forwardRunner: TaintRunner = newForwardRunner(forwardManager) + val backwardRunner: TaintRunner = newBackwardRunner(backwardManager) + + init { + check(forwardRunner.unit == unit) + check(backwardRunner.unit == unit) + } + + override fun submitNewEdge(edge: Edge) { + forwardRunner.submitNewEdge(edge) + } + + override suspend fun run(startMethods: List) = coroutineScope { + val backwardRunnerJob = launch(start = CoroutineStart.LAZY) { backwardRunner.run(startMethods) } + val forwardRunnerJob = launch(start = CoroutineStart.LAZY) { forwardRunner.run(startMethods) } + + backwardRunnerJob.start() + forwardRunnerJob.start() + + backwardRunnerJob.join() + forwardRunnerJob.join() + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt new file mode 100644 index 000000000..310a753b8 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -0,0 +1,182 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.ifds2.Analyzer +import org.jacodb.analysis.ifds2.Edge +import org.jacodb.analysis.paths.toPath +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcIfInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.impl.cfg.util.loops +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.taint.configuration.TaintMethodSink + +private val logger = KotlinLogging.logger {} + +class TaintAnalyzer( + private val graph: JcApplicationGraph, +) : Analyzer { + + override val flowFunctions: ForwardTaintFlowFunctions by lazy { + ForwardTaintFlowFunctions(graph.classpath, graph) + } + + private val taintConfigurationFeature: TaintConfigurationFeature? + get() = flowFunctions.taintConfigurationFeature + + private fun isExitPoint(statement: JcInst): Boolean { + return statement in graph.exitPoints(statement.location.method) + } + + private fun isSink(statement: JcInst, fact: TaintFact): Boolean { + // TODO + return false + } + + private val loopsCache: MutableMap> = hashMapOf() + + override fun handleNewEdge( + edge: TaintEdge, + ): List = buildList { + if (isExitPoint(edge.to.statement)) { + add(NewSummaryEdge(edge)) + } + + var defaultBehavior = true + run { + val callExpr = edge.to.statement.callExpr ?: return@run + val callee = callExpr.method.method + + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $callee" } + feature.getConfigForMethod(callee) + } ?: return@run + + // TODO: not always we want to skip sinks on Zero facts. + // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. + if (edge.to.fact !is Tainted) { + return@run + } + + // Determine whether 'edge.to' is a sink via config: + val conditionEvaluator = FactAwareConditionEvaluator( + edge.to.fact, + CallPositionToJcValueResolver(edge.to.statement), + ) + var triggeredItem: TaintMethodSink? = null + for (item in config.filterIsInstance()) { + defaultBehavior = false + try { + if (item.condition.accept(conditionEvaluator)) { + triggeredItem = item + break + } + } catch (_: IllegalStateException) { + } + // FIXME: unconditionally let it be the sink. + // triggeredItem = item + // break + } + if (triggeredItem != null) { + // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } + val message = "SINK" // TODO + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) + // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + add(NewVulnerability(vulnerability)) + // verticesWithTraceGraphNeeded.add(edge.to) + } + } + + if (defaultBehavior) { + // Default ("config"-less) behavior: + if (isSink(edge.to.statement, edge.to.fact)) { + // logger.info { "Found sink at ${edge.to} in ${edge.method}" } + val message = "SINK" // TODO + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) + // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + add(NewVulnerability(vulnerability)) + // verticesWithTraceGraphNeeded.add(edge.to) + } + + if (Globals.TAINTED_LOOP_BOUND_SINK) { + val statement = edge.to.statement + val fact = edge.to.fact + if (statement is JcIfInst && fact is Tainted) { + val loopHeads = loopsCache.getOrPut(statement.location.method) { + statement.location.method.flowGraph().loops.map { it.head } + } + if (statement in loopHeads) { + for (s in statement.condition.operands) { + val p = s.toPath() + if (p == fact.variable) { + val message = "Tainted loop operand" + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) + add(NewVulnerability(vulnerability)) + } + } + } + } + } + } + } + + override fun handleCrossUnitCall( + caller: TaintVertex, + callee: TaintVertex, + ): List = buildList { + add(EdgeForOtherRunner(TaintEdge(callee, callee))) + } +} + +class BackwardTaintAnalyzer( + private val graph: JcApplicationGraph, +) : Analyzer { + + override val flowFunctions: BackwardTaintFlowFunctions by lazy { + BackwardTaintFlowFunctions(graph.classpath, graph) + } + + private fun isExitPoint(statement: JcInst): Boolean { + return statement in graph.exitPoints(statement.location.method) + } + + private fun isSink(statement: JcInst, fact: TaintFact): Boolean { + // TODO + return false + } + + override fun handleNewEdge( + edge: TaintEdge, + ): List = buildList { + if (isExitPoint(edge.to.statement)) { + add(EdgeForOtherRunner(Edge(edge.to, edge.to))) + } + } + + override fun handleCrossUnitCall( + caller: TaintVertex, + callee: TaintVertex, + ): List { + return emptyList() + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt new file mode 100644 index 000000000..c37dc7dad --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +sealed interface TaintEvent + +data class NewSummaryEdge( + val edge: TaintEdge, +) : TaintEvent + +data class NewVulnerability( + val vulnerability: Vulnerability, +) : TaintEvent + +data class EdgeForOtherRunner( + val edge: TaintEdge, +) : TaintEvent { + init { + // TODO: remove this check + check(edge.from == edge.to) { "Edge for another runner must be a loop" } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt new file mode 100644 index 000000000..f7bcafaf2 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +import org.jacodb.analysis.engine.DomainFact +import org.jacodb.analysis.engine.ZEROFact +import org.jacodb.analysis.library.analyzers.NpeTaintNode +import org.jacodb.analysis.library.analyzers.TaintAnalysisNode +import org.jacodb.analysis.library.analyzers.TaintNode +import org.jacodb.analysis.paths.AccessPath +import org.jacodb.taint.configuration.TaintMark + +sealed interface TaintFact + +object Zero : TaintFact { + override fun toString(): String = this.javaClass.simpleName +} + +data class Tainted( + val variable: AccessPath, + val mark: TaintMark, +) : TaintFact { + constructor(fact: TaintNode) : this(fact.variable, TaintMark(fact.nodeType)) +} + +fun DomainFact.toFact(): TaintFact = when (this) { + ZEROFact -> Zero + is TaintNode -> Tainted(this) + else -> error("Go away") // object : TaintFact {} +} + +fun TaintFact.toDomainFact(): DomainFact = when (this) { + Zero -> ZEROFact + + is Tainted -> { + when (mark.name) { + "NPE" -> NpeTaintNode(variable) + else -> TaintAnalysisNode(variable, nodeType = mark.name) + } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt new file mode 100644 index 000000000..d0b315b01 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -0,0 +1,756 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("LiftReturnOrAssignment") + +package org.jacodb.analysis.ifds2.taint + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jacodb.analysis.config.BasicConditionEvaluator +import org.jacodb.analysis.config.CallPositionToAccessPathResolver +import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.config.TaintActionEvaluator +import org.jacodb.analysis.ifds2.FlowFunction +import org.jacodb.analysis.ifds2.FlowFunctions +import org.jacodb.analysis.library.analyzers.getArgument +import org.jacodb.analysis.library.analyzers.getArgumentsOf +import org.jacodb.analysis.library.analyzers.thisInstance +import org.jacodb.analysis.paths.ElementAccessor +import org.jacodb.analysis.paths.minus +import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toPath +import org.jacodb.analysis.paths.toPathOrNull +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcDynamicCallExpr +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstanceCallExpr +import org.jacodb.api.cfg.JcReturnInst +import org.jacodb.api.cfg.JcThis +import org.jacodb.api.cfg.JcValue +import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.taint.configuration.AnyArgument +import org.jacodb.taint.configuration.Argument +import org.jacodb.taint.configuration.AssignMark +import org.jacodb.taint.configuration.CopyAllMarks +import org.jacodb.taint.configuration.CopyMark +import org.jacodb.taint.configuration.RemoveAllMarks +import org.jacodb.taint.configuration.RemoveMark +import org.jacodb.taint.configuration.Result +import org.jacodb.taint.configuration.ResultAnyElement +import org.jacodb.taint.configuration.TaintCleaner +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.taint.configuration.TaintEntryPointSource +import org.jacodb.taint.configuration.TaintMethodSource +import org.jacodb.taint.configuration.TaintPassThrough +import org.jacodb.taint.configuration.This + +private val logger = KotlinLogging.logger {} + +@Suppress("PublicApiImplicitType") +class ForwardTaintFlowFunctions( + private val cp: JcClasspath, + private val graph: JcApplicationGraph, +) : FlowFunctions { + + internal val taintConfigurationFeature: TaintConfigurationFeature? by lazy { + cp.features + ?.singleOrNull { it is TaintConfigurationFeature } + ?.let { it as TaintConfigurationFeature } + } + + override fun obtainPossibleStartFacts( + method: JcMethod, + ): Collection = buildSet { + // Zero (reachability) fact always present at entrypoint: + add(Zero) + + // Extract initial facts from the config: + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $method" } + feature.getConfigForMethod(method) + } + if (config != null) { + // Note: both condition and action evaluator require a custom position resolver. + val conditionEvaluator = BasicConditionEvaluator { position -> + when (position) { + This -> method.thisInstance + + is Argument -> run { + val p = method.parameters[position.index] + cp.getArgument(p) + } ?: error("Cannot resolve $position for $method") + + AnyArgument -> error("Unexpected $position") + Result -> error("Unexpected $position") + ResultAnyElement -> error("Unexpected $position") + } + } + val actionEvaluator = TaintActionEvaluator { position -> + when (position) { + This -> method.thisInstance.toPathOrNull() + ?: error("Cannot resolve $position for $method") + + is Argument -> run { + val p = method.parameters[position.index] + cp.getArgument(p)?.toPathOrNull() + } ?: error("Cannot resolve $position for $method") + + AnyArgument -> error("Unexpected $position") + Result -> error("Unexpected $position") + ResultAnyElement -> error("Unexpected $position") + } + } + + // Handle EntryPointSource config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + when (action) { + is AssignMark -> { + add(actionEvaluator.evaluate(action)) + } + + else -> error("$action is not supported for $item") + } + } + } + } + } + } + + private fun transmitTaintAssign( + fact: Tainted, + from: JcExpr, + to: JcValue, + ): Collection { + val toPath = to.toPath() + val fromPath = from.toPathOrNull() + + if (fromPath != null) { + // Adhoc taint array: + if (fromPath.accesses.isNotEmpty() + && fromPath.accesses.last() is ElementAccessor + && fromPath == (fact.variable / ElementAccessor) + ) { + val newTaint = fact.copy(variable = toPath) + return setOf(fact, newTaint) + } + + val tail = fact.variable - fromPath + if (tail != null) { + // Both 'from' and 'to' are tainted now: + val newPath = toPath / tail + val newTaint = fact.copy(variable = newPath) + return setOf(fact, newTaint) + } + } + + + if (fact.variable.startsWith(toPath)) { + // 'to' was (sub-)tainted, but it is now overridden by 'from': + return emptySet() + } else { + // Neither 'from' nor 'to' are tainted: + return setOf(fact) + } + } + + private fun transmitTaintNormal( + fact: Tainted, + inst: JcInst, + ): List { + // Pass-through: + return listOf(fact) + } + + override fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ) = FlowFunction { fact -> + if (fact is Zero) { + return@FlowFunction listOf(Zero) + } + check(fact is Tainted) + + if (current is JcAssignInst) { + transmitTaintAssign(fact, from = current.rhv, to = current.lhv) + } else { + transmitTaintNormal(fact, current) + } + } + + private fun transmitTaint( + fact: Tainted, + from: JcValue, + to: JcValue, + ): Collection = buildSet { + val fromPath = from.toPath() + val toPath = to.toPath() + + val tail = (fact.variable - fromPath) ?: return@buildSet + val newPath = toPath / tail + val newTaint = fact.copy(variable = newPath) + add(newTaint) + } + + private fun transmitTaintArgumentActualToFormal( + fact: Tainted, + from: JcValue, // actual + to: JcValue, // formal + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintArgumentFormalToActual( + fact: Tainted, + from: JcValue, // formal + to: JcValue, // actual + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintInstanceToThis( + fact: Tainted, + from: JcValue, // instance + to: JcThis, // this + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintThisToInstance( + fact: Tainted, + from: JcThis, // this + to: JcValue, // instance + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintReturn( + fact: Tainted, + from: JcValue, + to: JcValue, + ): Collection = transmitTaint(fact, from, to) + + override fun obtainCallToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, // FIXME: unused? + ) = FlowFunction { fact -> + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + val callee = callExpr.method.method + + // FIXME: handle taint pass-through on invokedynamic-based String concatenation: + if (fact is Tainted + && callExpr is JcDynamicCallExpr + && callee.enclosingClass.name == "java.lang.invoke.StringConcatFactory" + && callStatement is JcAssignInst + ) { + for (arg in callExpr.args) { + if (arg.toPath() == fact.variable) { + return@FlowFunction setOf( + fact, + fact.copy(variable = callStatement.lhv.toPath()) + ) + } + } + return@FlowFunction setOf(fact) + } + + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $callee" } + feature.getConfigForMethod(callee) + } + + // If 'fact' is ZeroFact, handle MethodSource. If there are no suitable MethodSource items, perform default. + // For other facts (Tainted only?), handle PassThrough/Cleaner items. + // TODO: what to do with "other facts" on CopyAllMarks/RemoveAllMarks? + + // TODO: the call-to-return flow function should also return (or somehow mark internally) + // whether we need to analyze the callee. For example, when we have MethodSource, + // PassThrough or Cleaner for a call statement, we do not need to analyze the callee at all. + // However, when we do not have such items in our config, we have to perform the whole analysis + // of the callee: calling call-to-start flow function, launching the analysis of the callee, + // awaiting for summary edges, and finally executing the exit-to-return flow function. + // In such case, the call-to-return flow function should return empty list of facts, + // since they are going to be "handled by the summary edge". + + if (fact == Zero) { + return@FlowFunction buildSet { + add(Zero) + + if (config != null) { + val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(callStatement)) + val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) + + // Handle MethodSource config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + when (action) { + is AssignMark -> { + add(actionEvaluator.evaluate(action)) + } + + else -> error("$action is not supported for $item") + } + } + } + } + } + } + } + check(fact is Tainted) + + // TODO: handle 'activation' (c.f. Boomerang) here + + // if (config == null) { + // return@FlowFunction emptyList() + // } + + if (config != null) { + val facts = mutableSetOf() + val conditionEvaluator = FactAwareConditionEvaluator(fact, CallPositionToJcValueResolver(callStatement)) + val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) + var defaultBehavior = true + + // Handle PassThrough config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + defaultBehavior = false + when (action) { + is CopyMark -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is CopyAllMarks -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is RemoveMark -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is RemoveAllMarks -> { + facts += actionEvaluator.evaluate(action, fact) + } + + else -> error("$action is not supported for $item") + } + } + } + } + + // Handle Cleaner config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + defaultBehavior = false + when (action) { + is RemoveMark -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is RemoveAllMarks -> { + facts += actionEvaluator.evaluate(action, fact) + } + + else -> error("$action is not supported for $item") + } + } + } + } + + if (!defaultBehavior) { + if (facts.size > 0) { + logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } + } + return@FlowFunction facts + } else { + // Fall back to the default behavior, as if there were no config at all. + } + } + + // FIXME: adhoc for constructors: + if (callee.isConstructor) { + return@FlowFunction listOf(fact) + } + + // TODO: CONSIDER REFACTORING THIS + // Default behavior for "analyzable" method calls is to remove ("temporarily") + // all the marks from the 'instance' and arguments, in order to allow them "pass through" + // the callee (when it is going to be analyzed), i.e. through "call-to-start" and + // "exit-to-return" flow functions. + // When we know that we are NOT going to analyze the callee, we do NOT need + // to remove any marks from 'instance' and arguments. + // Currently, "analyzability" of the callee depends on the fact that the callee + // is "accessible" through the JcApplicationGraph::callees(). + if (callee in graph.callees(callStatement)) { + + if (fact.variable.isStatic) { + return@FlowFunction emptyList() + } + + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + if (fact.variable.startsWith(actual.toPathOrNull())) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } + + if (callExpr is JcInstanceCallExpr) { + // Possibly tainted instance: + if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } + + } + + if (callStatement is JcAssignInst) { + // Possibly tainted lhv: + if (fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { + return@FlowFunction emptyList() // Overridden by rhv + } + } + + // The "most default" behaviour is encapsulated here: + transmitTaintNormal(fact, callStatement) + } + + override fun obtainCallToStartFlowFunction( + callStatement: JcInst, + calleeStart: JcInst, + ) = FlowFunction { fact -> + val callee = calleeStart.location.method + + if (fact == Zero) { + return@FlowFunction obtainPossibleStartFacts(callee) + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + + buildSet { + // Transmit facts on arguments (from 'actual' to 'formal'): + val actualParams = callExpr.args + val formalParams = cp.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) + } + + // Transmit facts on instance (from 'instance' to 'this'): + if (callExpr is JcInstanceCallExpr) { + addAll(transmitTaintInstanceToThis(fact, from = callExpr.instance, to = callee.thisInstance)) + } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + } + } + + override fun obtainExitToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, // unused + exitStatement: JcInst, + ) = FlowFunction { fact -> + if (fact == Zero) { + return@FlowFunction listOf(Zero) + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + val callee = exitStatement.location.method + + buildSet { + // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: + if (fact.variable.isOnHeap) { + val actualParams = callExpr.args + val formalParams = cp.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) + } + } + + // Transmit facts on instance (from 'this' to 'instance'): + if (callExpr is JcInstanceCallExpr) { + addAll(transmitTaintThisToInstance(fact, from = callee.thisInstance, to = callExpr.instance)) + } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + + // Transmit facts on return value (from 'returnValue' to 'lhv'): + if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + exitStatement.returnValue?.let { returnValue -> + addAll(transmitTaintReturn(fact, from = returnValue, to = callStatement.lhv)) + } + } + } + } +} + +@Suppress("PublicApiImplicitType") +class BackwardTaintFlowFunctions( + private val project: JcClasspath, + private val graph: JcApplicationGraph, +) : FlowFunctions { + + override fun obtainPossibleStartFacts( + method: JcMethod, + ): Collection { + return listOf(Zero) + } + + private fun transmitTaintBackwardAssign( + fact: Tainted, + from: JcValue, + to: JcExpr, + ): Collection { + val fromPath = from.toPath() + val toPath = to.toPathOrNull() + + if (toPath != null) { + // TODO: think about arrays here + // // Adhoc taint array: + // if (fromPath.accesses.isNotEmpty() + // && fromPath.accesses.last() is ElementAccessor + // && fromPath.copy(accesses = fromPath.accesses.dropLast(1)) == fact.variable + // ) { + // val newTaint = fact.copy(variable = toPath) + // return setOf(fact, newTaint) + // } + + val tail = fact.variable - fromPath + if (tail != null) { + // Both 'from' and 'to' are tainted now: + val newPath = toPath / tail + val newTaint = fact.copy(variable = newPath) + return setOf(fact, newTaint) + } + + if (fact.variable.startsWith(toPath)) { + // 'to' was (sub-)tainted, but it is now overridden by 'from': + return emptySet() + } + } + + // Pass-through: + return setOf(fact) + } + + private fun transmitTaintBackwardNormal( + fact: Tainted, + inst: JcInst, + ): List { + // Pass-through: + return listOf(fact) + } + + override fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ) = FlowFunction { fact -> + if (fact is Zero) { + return@FlowFunction listOf(Zero) + } + check(fact is Tainted) + + if (current is JcAssignInst) { + transmitTaintBackwardAssign(fact, from = current.lhv, to = current.rhv) + } else { + transmitTaintBackwardNormal(fact, current) + } + } + + private fun transmitTaint( + fact: Tainted, + from: JcValue, + to: JcValue, + ): Collection = buildSet { + val fromPath = from.toPath() + val toPath = to.toPath() + + val tail = (fact.variable - fromPath) ?: return@buildSet + val newPath = toPath / tail + val newTaint = fact.copy(variable = newPath) + add(newTaint) + } + + private fun transmitTaintArgumentActualToFormal( + fact: Tainted, + from: JcValue, // actual + to: JcValue, // formal + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintArgumentFormalToActual( + fact: Tainted, + from: JcValue, // formal + to: JcValue, // actual + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintInstanceToThis( + fact: Tainted, + from: JcValue, // instance + to: JcThis, // this + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintThisToInstance( + fact: Tainted, + from: JcThis, // this + to: JcValue, // instance + ): Collection = transmitTaint(fact, from, to) + + private fun transmitTaintReturn( + fact: Tainted, + from: JcValue, + to: JcValue, + ): Collection = transmitTaint(fact, from, to) + + override fun obtainCallToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, // FIXME: unused? + ) = FlowFunction { fact -> + // TODO: pass-through on invokedynamic-based String concatenation + + if (fact == Zero) { + return@FlowFunction listOf(Zero) + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + val callee = callExpr.method.method + + // // FIXME: adhoc for constructors: + // if (callee.isConstructor) { + // return@FlowFunction listOf(fact) + // } + + if (callee in graph.callees(callStatement)) { + + if (fact.variable.isStatic) { + return@FlowFunction emptyList() + } + + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + if (fact.variable.startsWith(actual.toPathOrNull())) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } + + if (callExpr is JcInstanceCallExpr) { + // Possibly tainted instance: + if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } + + } + + if (callStatement is JcAssignInst) { + // Possibly tainted rhv: + if (fact.variable.startsWith(callStatement.rhv.toPathOrNull())) { + return@FlowFunction emptyList() // Overridden by lhv + } + } + + // The "most default" behaviour is encapsulated here: + transmitTaintBackwardNormal(fact, callStatement) + } + + override fun obtainCallToStartFlowFunction( + callStatement: JcInst, + calleeStart: JcInst, + ) = FlowFunction { fact -> + val callee = calleeStart.location.method + + if (fact == Zero) { + return@FlowFunction obtainPossibleStartFacts(callee) + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + + buildSet { + // Transmit facts on arguments (from 'actual' to 'formal'): + val actualParams = callExpr.args + val formalParams = project.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) + } + + // Transmit facts on instance (from 'instance' to 'this'): + if (callExpr is JcInstanceCallExpr) { + addAll(transmitTaintInstanceToThis(fact, from = callExpr.instance, to = callee.thisInstance)) + } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + + // Transmit facts on return value (from 'returnValue' to 'lhv'): + if (calleeStart is JcReturnInst && callStatement is JcAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + calleeStart.returnValue?.let { returnValue -> + addAll(transmitTaintReturn(fact, from = callStatement.lhv, to = returnValue)) + } + } + } + } + + override fun obtainExitToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + exitStatement: JcInst, + ) = FlowFunction { fact -> + if (fact == Zero) { + return@FlowFunction listOf(Zero) + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + val callee = exitStatement.location.method + + buildSet { + // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: + // TODO: "if passed by-ref" part is not implemented here yet + val actualParams = callExpr.args + val formalParams = project.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) + } + + // Transmit facts on instance (from 'this' to 'instance'): + if (callExpr is JcInstanceCallExpr) { + addAll(transmitTaintThisToInstance(fact, from = callee.thisInstance, to = callExpr.instance)) + } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt new file mode 100644 index 000000000..9dd4be062 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -0,0 +1,305 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.yield +import org.jacodb.analysis.engine.SummaryStorageImpl +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.analysis.engine.UnitType +import org.jacodb.analysis.engine.UnknownUnit +import org.jacodb.analysis.graph.reversed +import org.jacodb.analysis.ifds2.ControlEvent +import org.jacodb.analysis.ifds2.Manager +import org.jacodb.analysis.ifds2.QueueEmptinessChanged +import org.jacodb.analysis.ifds2.Runner +import org.jacodb.analysis.ifds2.pathEdges +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.taint.configuration.TaintMark +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +private val logger = KotlinLogging.logger {} + +class TaintManager( + private val graph: JcApplicationGraph, + private val unitResolver: UnitResolver, +) : Manager { + + private val methodsForUnit: MutableMap> = hashMapOf() + private val runnerForUnit: MutableMap = hashMapOf() + private val queueIsEmpty: MutableMap = ConcurrentHashMap() + + private val summaryEdgesStorage = SummaryStorageImpl() + private val vulnerabilitiesStorage = SummaryStorageImpl() + + private val stopRendezvous = Channel(Channel.RENDEZVOUS) + + private fun newRunner( + unit: UnitType, + ): TaintRunner { + check(unit !in runnerForUnit) { "Runner for $unit already exists" } + + val runner = if (Globals.BIDI_RUNNER) { + BidiRunner( + manager = this@TaintManager, + unitResolver = unitResolver, + unit = unit, + { manager -> + val analyzer = TaintAnalyzer(graph) + Runner( + graph = graph, + analyzer = analyzer, + manager = manager, + unitResolver = unitResolver, + unit = unit + ) + }, + { manager -> + val analyzer = BackwardTaintAnalyzer(graph) + Runner( + graph = graph.reversed, + analyzer = analyzer, + manager = manager, + unitResolver = unitResolver, + unit = unit + ) + } + ) + } else { + val analyzer = TaintAnalyzer(graph) + Runner(graph, analyzer, this@TaintManager, unitResolver, unit) + } + + runnerForUnit[unit] = runner + return runner + } + + private fun addStart(method: JcMethod) { + logger.info { "Adding start method: $method" } + val unit = unitResolver.resolve(method) + if (unit == UnknownUnit) return + methodsForUnit.getOrPut(unit) { mutableSetOf() }.add(method) + // TODO: val isNew = (...).add(); if (isNew) { deps.forEach { addStart(it) } } + } + + @OptIn(ExperimentalTime::class) + fun analyze( + startMethods: List, + ): List = runBlocking(Dispatchers.Default) { + val timeStart = TimeSource.Monotonic.markNow() + + // Add start methods: + for (method in startMethods) { + addStart(method) + } + + // Determine all units: + val allUnits = methodsForUnit.keys.toList() + logger.info { + "Starting analysis of ${ + methodsForUnit.values.sumOf { it.size } + } methods in ${allUnits.size} units" + } + + // Spawn runner jobs: + val allJobs = allUnits.map { unit -> + // Create the runner: + val runner = newRunner(unit) + + // Start the runner: + launch(start = CoroutineStart.LAZY) { + val methods = methodsForUnit[unit]!!.toList() + runner.run(methods) + } + } + + // Spawn progress job: + val progress = launch(Dispatchers.IO) { + logger.info { "Progress job started" } + while (isActive) { + delay(1.seconds) + logger.info { + "Progress: total propagated ${ + runnerForUnit.values.sumOf { it.pathEdges.size } + } path edges" + } + } + logger.info { "Progress job finished" } + } + + // Spawn stopper job: + val stopper = launch(Dispatchers.IO) { + logger.info { "Stopper job started" } + stopRendezvous.receive() + // delay(100) + // @OptIn(ExperimentalCoroutinesApi::class) + // if (runnerForUnit.values.any { !it.workList.isEmpty }) { + // logger.warn { "NOT all runners have empty work list" } + // error("?") + // } + logger.info { "Stopping all runners..." } + allJobs.forEach { it.cancel() } + logger.info { "Stopper job finished" } + } + + // Start all runner jobs: + val timeStartJobs = TimeSource.Monotonic.markNow() + allJobs.forEach { it.start() } + + // Await all runners: + withTimeoutOrNull(3600.seconds) { + allJobs.joinAll() + } ?: run { + allJobs.forEach { it.cancel() } + allJobs.joinAll() + } + progress.cancelAndJoin() + stopper.cancelAndJoin() + logger.info { + "All ${allJobs.size} jobs completed in %.1f s".format( + timeStartJobs.elapsedNow().toDouble(DurationUnit.SECONDS) + ) + } + + // Extract found vulnerabilities (sinks): + val foundVulnerabilities = vulnerabilitiesStorage.knownMethods + .flatMap { method -> + vulnerabilitiesStorage.getCurrentFacts(method) + } + logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } + for (vulnerability in foundVulnerabilities) { + logger.debug { "$vulnerability in ${vulnerability.method}" } + } + logger.info { "Total sinks: ${foundVulnerabilities.size}" } + + logger.info { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } + + if (logger.isDebugEnabled()) { + val statsFileName = "stats.csv" + logger.debug { "Writing stats in '$statsFileName'..." } + File(statsFileName).outputStream().bufferedWriter().use { writer -> + val sep = ";" + writer.write(listOf("classname", "cwe", "method", "sink", "fact").joinToString(sep) + "\n") + for (vulnerability in foundVulnerabilities) { + val m = vulnerability.method + if (vulnerability.rule != null) { + for (cwe in vulnerability.rule.cwe) { + writer.write( + listOf( + m.enclosingClass.simpleName, + cwe, + m.name, + vulnerability.sink.statement, + vulnerability.sink.fact + ).joinToString(sep) { "\"$it\"" } + "\n") + } + } else if ( + vulnerability.sink.fact is Tainted + && vulnerability.sink.fact.mark == TaintMark.NULLNESS + ) { + val cwe = 476 + writer.write( + listOf( + m.enclosingClass.simpleName, + cwe, + m.name, + vulnerability.sink.statement, + vulnerability.sink.fact + ).joinToString(sep) { "\"$it\"" } + "\n") + } else { + logger.warn { "Bad vulnerability without rule: $vulnerability" } + } + } + } + } + + logger.info { + "Analysis done in %.1f s".format( + timeStart.elapsedNow().toDouble(DurationUnit.SECONDS) + ) + } + foundVulnerabilities + } + + override fun handleEvent(event: TaintEvent) { + when (event) { + is NewSummaryEdge -> { + summaryEdgesStorage.add(SummaryEdge(event.edge)) + } + + is NewVulnerability -> { + vulnerabilitiesStorage.add(event.vulnerability) + } + + is EdgeForOtherRunner -> { + val method = event.edge.method + val unit = unitResolver.resolve(method) + val otherRunner = runnerForUnit[unit] ?: run { + logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } + return + } + otherRunner.submitNewEdge(event.edge) + } + } + } + + override suspend fun handleControlEvent(event: ControlEvent) { + when (event) { + is QueueEmptinessChanged -> { + queueIsEmpty[event.runner.unit] = event.isEmpty + if (event.isEmpty) { + yield() + if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { + stopRendezvous.send(Unit) + } + } + } + } + } + + override fun subscribeOnSummaryEdges( + method: JcMethod, + scope: CoroutineScope, + handler: suspend (TaintEdge) -> Unit, + ) { + summaryEdgesStorage + .getFacts(method) + .map { it.edge } + .onEach(handler) + .launchIn(scope) + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt new file mode 100644 index 000000000..4985287f2 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +import org.jacodb.analysis.engine.SummaryFact +import org.jacodb.api.JcMethod +import org.jacodb.taint.configuration.TaintMethodSink + +/** + * Represents a path edge which starts in an entrypoint + * and ends in an exit-point of a method. + */ +data class SummaryEdge( + val edge: TaintEdge, +) : SummaryFact { + override val method: JcMethod + get() = edge.method +} + +data class Vulnerability( + val message: String, + val sink: TaintVertex, + val edge: TaintEdge? = null, + val rule: TaintMethodSink? = null, +) : SummaryFact { + override val method: JcMethod + get() = sink.method +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt new file mode 100644 index 000000000..bc86a8580 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("LiftReturnOrAssignment") + +package org.jacodb.analysis.ifds2.taint + +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph + +fun runTaintAnalysis( + graph: JcApplicationGraph, + unitResolver: UnitResolver, + startMethods: List, +): List { + val manager = TaintManager(graph, unitResolver) + return manager.analyze(startMethods) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt new file mode 100644 index 000000000..f8a974798 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +internal object Globals { + var BIDI_RUNNER = true + var TAINTED_LOOP_BOUND_SINK = false +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt new file mode 100644 index 000000000..ceb62686b --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint.npe + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.ifds2.Analyzer +import org.jacodb.analysis.ifds2.taint.EdgeForOtherRunner +import org.jacodb.analysis.ifds2.taint.NewSummaryEdge +import org.jacodb.analysis.ifds2.taint.NewVulnerability +import org.jacodb.analysis.ifds2.taint.TaintEdge +import org.jacodb.analysis.ifds2.taint.TaintEvent +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.analysis.ifds2.taint.TaintVertex +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.Vulnerability +import org.jacodb.analysis.paths.isDereferencedAt +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.taint.configuration.TaintMark +import org.jacodb.taint.configuration.TaintMethodSink + +private val logger = KotlinLogging.logger {} + +class NpeAnalyzer( + private val graph: JcApplicationGraph, +) : Analyzer { + + override val flowFunctions: ForwardNpeFlowFunctions by lazy { + ForwardNpeFlowFunctions(graph.classpath, graph) + } + + private val taintConfigurationFeature: TaintConfigurationFeature? + get() = flowFunctions.taintConfigurationFeature + + private fun isExitPoint(statement: JcInst): Boolean { + return statement in graph.exitPoints(statement.location.method) + } + + private fun isSink(statement: JcInst, fact: TaintFact): Boolean { + // TODO + return false + } + + override fun handleNewEdge( + edge: TaintEdge, + ): List = buildList { + if (isExitPoint(edge.to.statement)) { + add(NewSummaryEdge(edge)) + } + + if (edge.to.fact is Tainted && edge.to.fact.mark == TaintMark.NULLNESS) { + if (edge.to.fact.variable.isDereferencedAt(edge.to.statement)) { + val message = "NPE" // TODO + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) + logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + add(NewVulnerability(vulnerability)) + } + } + + var defaultBehavior = true + run { + val callExpr = edge.to.statement.callExpr ?: return@run + val callee = callExpr.method.method + + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $callee" } + feature.getConfigForMethod(callee) + } ?: return@run + + // TODO: not always we want to skip sinks on Zero facts. + // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. + if (edge.to.fact !is Tainted) { + return@run + } + + // Determine whether 'edge.to' is a sink via config: + val conditionEvaluator = FactAwareConditionEvaluator( + edge.to.fact, + CallPositionToJcValueResolver(edge.to.statement), + ) + var triggeredItem: TaintMethodSink? = null + for (item in config.filterIsInstance()) { + defaultBehavior = false + if (item.condition.accept(conditionEvaluator)) { + triggeredItem = item + break + } + // FIXME: unconditionally let it be the sink. + // triggeredItem = item + // break + } + if (triggeredItem != null) { + // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } + val message = "SINK" // TODO + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) + // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + add(NewVulnerability(vulnerability)) + // verticesWithTraceGraphNeeded.add(edge.to) + } + } + + if (defaultBehavior) { + // Default ("config"-less) behavior: + if (isSink(edge.to.statement, edge.to.fact)) { + // logger.info { "Found sink at ${edge.to} in ${edge.method}" } + val message = "SINK" // TODO + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) + // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + add(NewVulnerability(vulnerability)) + // verticesWithTraceGraphNeeded.add(edge.to) + } + } + } + + override fun handleCrossUnitCall( + caller: TaintVertex, + callee: TaintVertex, + ): List = buildList { + add(EdgeForOtherRunner(TaintEdge(callee, callee))) + } +} + +// class BackwardTaintAnalyzer( +// private val graph: JcApplicationGraph, +// ) : Analyzer { +// +// override val flowFunctions: BackwardNpeFlowFunctions by lazy { +// BackwardNpeFlowFunctions(graph.classpath, graph) +// } +// +// private fun isExitPoint(statement: JcInst): Boolean { +// return statement in graph.exitPoints(statement.location.method) +// } +// +// private fun isSink(statement: JcInst, fact: TaintFact): Boolean { +// // TODO +// return false +// } +// +// override fun handleNewEdge( +// edge: TaintEdge, +// ): List = buildList { +// if (isExitPoint(edge.to.statement)) { +// add(EdgeForOtherRunner(Edge(edge.to, edge.to))) +// } +// } +// +// override fun handleCrossUnitCall( +// caller: TaintVertex, +// callee: TaintVertex, +// ): List { +// return emptyList() +// } +// } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt new file mode 100644 index 000000000..f4f979376 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt @@ -0,0 +1,943 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("LiftReturnOrAssignment") + +package org.jacodb.analysis.ifds2.taint.npe + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jacodb.analysis.config.BasicConditionEvaluator +import org.jacodb.analysis.config.CallPositionToAccessPathResolver +import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.config.TaintActionEvaluator +import org.jacodb.analysis.ifds2.FlowFunction +import org.jacodb.analysis.ifds2.FlowFunctions +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.Zero +import org.jacodb.analysis.library.analyzers.getArgument +import org.jacodb.analysis.library.analyzers.getArgumentsOf +import org.jacodb.analysis.library.analyzers.thisInstance +import org.jacodb.analysis.paths.AccessPath +import org.jacodb.analysis.paths.ElementAccessor +import org.jacodb.analysis.paths.isDereferencedAt +import org.jacodb.analysis.paths.minus +import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toPath +import org.jacodb.analysis.paths.toPathOrNull +import org.jacodb.api.JcArrayType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcArgument +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcCallExpr +import org.jacodb.api.cfg.JcDynamicCallExpr +import org.jacodb.api.cfg.JcEqExpr +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcIfInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstanceCallExpr +import org.jacodb.api.cfg.JcNeqExpr +import org.jacodb.api.cfg.JcNewArrayExpr +import org.jacodb.api.cfg.JcNullConstant +import org.jacodb.api.cfg.JcReturnInst +import org.jacodb.api.cfg.JcThis +import org.jacodb.api.cfg.JcValue +import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.api.ext.findTypeOrNull +import org.jacodb.api.ext.isNullable +import org.jacodb.taint.configuration.AnyArgument +import org.jacodb.taint.configuration.Argument +import org.jacodb.taint.configuration.AssignMark +import org.jacodb.taint.configuration.CopyAllMarks +import org.jacodb.taint.configuration.CopyMark +import org.jacodb.taint.configuration.RemoveAllMarks +import org.jacodb.taint.configuration.RemoveMark +import org.jacodb.taint.configuration.Result +import org.jacodb.taint.configuration.ResultAnyElement +import org.jacodb.taint.configuration.TaintCleaner +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.taint.configuration.TaintEntryPointSource +import org.jacodb.taint.configuration.TaintMark +import org.jacodb.taint.configuration.TaintMethodSource +import org.jacodb.taint.configuration.TaintPassThrough +import org.jacodb.taint.configuration.This + +private val logger = KotlinLogging.logger {} + +@Suppress("PublicApiImplicitType") +class ForwardNpeFlowFunctions( + private val cp: JcClasspath, + private val graph: JcApplicationGraph, +) : FlowFunctions { + + internal val taintConfigurationFeature: TaintConfigurationFeature? by lazy { + cp.features + ?.singleOrNull { it is TaintConfigurationFeature } + ?.let { it as TaintConfigurationFeature } + } + + override fun obtainPossibleStartFacts( + method: JcMethod, + ): Collection = buildSet { + addAll(obtainPossibleStartFactsBasic(method)) + + // Possibly null arguments: + for (p in method.parameters.filter { it.isNullable != false }) { + val t = cp.findTypeOrNull(p.type)!! + val arg = JcArgument.of(p.index, p.name, t) + val path = AccessPath.from(arg) + add(Tainted(path, TaintMark.NULLNESS)) + } + } + + private fun obtainPossibleStartFactsBasic( + method: JcMethod, + ): Collection = buildSet { + // Zero (reachability) fact always present at entrypoint: + add(Zero) + + // Possibly null arguments: + // for (p in method.parameters.filter { it.isNullable != false }) { + // val t = cp.findTypeOrNull(p.type)!! + // val arg = JcArgument.of(p.index, p.name, t) + // val path = AccessPath.from(arg) + // add(Tainted(path, TaintMark.NULLNESS)) + // } + + // Extract initial facts from the config: + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $method" } + feature.getConfigForMethod(method) + } + if (config != null) { + // Note: both condition and action evaluator require a custom position resolver. + val conditionEvaluator = BasicConditionEvaluator { position -> + when (position) { + This -> method.thisInstance + + is Argument -> run { + val p = method.parameters[position.index] + cp.getArgument(p) + } ?: error("Cannot resolve $position for $method") + + AnyArgument -> error("Unexpected $position") + Result -> error("Unexpected $position") + ResultAnyElement -> error("Unexpected $position") + } + } + val actionEvaluator = TaintActionEvaluator { position -> + when (position) { + This -> method.thisInstance.toPathOrNull() + ?: error("Cannot resolve $position for $method") + + is Argument -> run { + val p = method.parameters[position.index] + cp.getArgument(p)?.toPathOrNull() + } ?: error("Cannot resolve $position for $method") + + AnyArgument -> error("Unexpected $position") + Result -> error("Unexpected $position") + ResultAnyElement -> error("Unexpected $position") + } + } + + // Handle EntryPointSource config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + when (action) { + is AssignMark -> { + add(actionEvaluator.evaluate(action)) + } + + else -> error("$action is not supported for $item") + } + } + } + } + } + } + + private fun transmitTaintAssign( + fact: Tainted, + from: JcExpr, + to: JcValue, + ): Collection { + val toPath = to.toPath() + val fromPath = from.toPathOrNull() + + if (fact.mark == TaintMark.NULLNESS) { + // if (from is JcNewExpr || + // from is JcNewArrayExpr || + // from is JcConstant || + // (from is JcCallExpr && from.method.method.isNullable != true) + // ) { + if (fact.variable.startsWith(toPath)) { + // NULLNESS is overridden: + return emptySet() + } + } + + if (fromPath != null) { + // Adhoc taint array: + if (fromPath.accesses.isNotEmpty() + && fromPath.accesses.last() is ElementAccessor + && fromPath == (fact.variable / ElementAccessor) + ) { + val newTaint = fact.copy(variable = toPath) + return setOf(fact, newTaint) + } + + val tail = fact.variable - fromPath + if (tail != null) { + // Both 'from' and 'to' are tainted now: + val newPath = toPath / tail + val newTaint = fact.copy(variable = newPath) + return setOf(fact, newTaint) + } + } + + return buildSet { + if (from is JcNullConstant) { + add(Tainted(toPath, TaintMark.NULLNESS)) + } + + if (fact.variable.startsWith(toPath)) { + // 'to' was (sub-)tainted, but it is now overridden by 'from': + return@buildSet + } else { + // Neither 'from' nor 'to' are tainted: + add(fact) + } + } + } + + private fun transmitTaintNormal( + fact: Tainted, + inst: JcInst, + ): List { + // Pass-through: + return listOf(fact) + } + + private fun generates(inst: JcInst): Collection = buildList { + if (inst is JcAssignInst) { + val toPath = inst.lhv.toPath() + val from = inst.rhv + if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { + add(Tainted(toPath, TaintMark.NULLNESS)) + } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { + val accessors = List((from.type as JcArrayType).dimensions) { ElementAccessor(null) } + val path = toPath / accessors + add(Tainted(path, TaintMark.NULLNESS)) + } + } + } + + private val JcIfInst.pathComparedWithNull: AccessPath? + get() { + val expr = condition + return if (expr.rhv is JcNullConstant) { + expr.lhv.toPathOrNull() + } else if (expr.lhv is JcNullConstant) { + expr.rhv.toPathOrNull() + } else { + null + } + } + + override fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ) = FlowFunction { fact -> + if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { + if (fact.variable.isDereferencedAt(current)) { + return@FlowFunction emptySet() + } + } + + if (current is JcIfInst) { + val nextIsTrueBranch = next.location.index == current.trueBranch.index + val pathComparedWithNull = current.pathComparedWithNull + if (fact == Zero) { + if (pathComparedWithNull != null) { + if ((current.condition is JcEqExpr && nextIsTrueBranch) || + (current.condition is JcNeqExpr && !nextIsTrueBranch) + ) { + // This is a hack: instructions like `return null` in branch of next will be considered only if + // the fact holds (otherwise we could not get there) + // Note the absence of 'Zero' here! + return@FlowFunction listOf(Tainted(pathComparedWithNull, TaintMark.NULLNESS)) + } + } + } else if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { + val expr = current.condition + if (pathComparedWithNull != fact.variable) { + return@FlowFunction listOf(fact) + } + if ((expr is JcEqExpr && nextIsTrueBranch) || (expr is JcNeqExpr && !nextIsTrueBranch)) { + // comparedPath is null in this branch + return@FlowFunction listOf(Zero) + } else { + return@FlowFunction emptyList() + } + } + } + + if (fact is Zero) { + return@FlowFunction listOf(Zero) + generates(current) + } + check(fact is Tainted) + + if (current is JcAssignInst) { + transmitTaintAssign(fact, from = current.rhv, to = current.lhv) + } else { + transmitTaintNormal(fact, current) + } + } + + private fun transmitTaint( + fact: Tainted, + at: JcInst, + from: JcValue, + to: JcValue, + ): Collection = buildSet { + if (fact.mark == TaintMark.NULLNESS) { + if (fact.variable.isDereferencedAt(at)) { + return@buildSet + } + } + + val fromPath = from.toPath() + val toPath = to.toPath() + + val tail = (fact.variable - fromPath) ?: return@buildSet + val newPath = toPath / tail + val newTaint = fact.copy(variable = newPath) + add(newTaint) + } + + private fun transmitTaintArgumentActualToFormal( + fact: Tainted, + at: JcInst, + from: JcValue, // actual + to: JcValue, // formal + ): Collection = transmitTaint(fact, at, from, to) + + private fun transmitTaintArgumentFormalToActual( + fact: Tainted, + at: JcInst, + from: JcValue, // formal + to: JcValue, // actual + ): Collection = transmitTaint(fact, at, from, to) + + private fun transmitTaintInstanceToThis( + fact: Tainted, + at: JcInst, + from: JcValue, // instance + to: JcThis, // this + ): Collection = transmitTaint(fact, at, from, to) + + private fun transmitTaintThisToInstance( + fact: Tainted, + at: JcInst, + from: JcThis, // this + to: JcValue, // instance + ): Collection = transmitTaint(fact, at, from, to) + + private fun transmitTaintReturn( + fact: Tainted, + at: JcInst, + from: JcValue, + to: JcValue, + ): Collection = transmitTaint(fact, at, from, to) + + override fun obtainCallToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, // FIXME: unused? + ) = FlowFunction { fact -> + if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { + if (fact.variable.isDereferencedAt(callStatement)) { + return@FlowFunction emptySet() + } + } + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + val callee = callExpr.method.method + + // FIXME: handle taint pass-through on invokedynamic-based String concatenation: + if (fact is Tainted + && callExpr is JcDynamicCallExpr + && callee.enclosingClass.name == "java.lang.invoke.StringConcatFactory" + && callStatement is JcAssignInst + ) { + for (arg in callExpr.args) { + if (arg.toPath() == fact.variable) { + return@FlowFunction setOf( + fact, + fact.copy(variable = callStatement.lhv.toPath()) + ) + } + } + return@FlowFunction setOf(fact) + } + + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $callee" } + feature.getConfigForMethod(callee) + } + + if (fact == Zero) { + return@FlowFunction buildSet { + add(Zero) + + if (callStatement is JcAssignInst) { + val toPath = callStatement.lhv.toPath() + val from = callStatement.rhv + if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { + add(Tainted(toPath, TaintMark.NULLNESS)) + } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { + val size = (from.type as JcArrayType).dimensions + val accessors = List(size) { ElementAccessor } + val path = toPath / accessors + add(Tainted(path, TaintMark.NULLNESS)) + } + } + + if (config != null) { + val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(callStatement)) + val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) + + // Handle MethodSource config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + when (action) { + is AssignMark -> { + add(actionEvaluator.evaluate(action)) + } + + else -> error("$action is not supported for $item") + } + } + } + } + } + } + } + check(fact is Tainted) + + // TODO: handle 'activation' (c.f. Boomerang) here + + // if (config == null) { + // return@FlowFunction emptyList() + // } + + if (config != null) { + // FIXME: adhoc + if (callee.enclosingClass.name == "java.lang.StringBuilder" && callee.name == "append") { + // Skip rules for StringBuilder::append in NPE analysis. + } else { + val facts = mutableSetOf() + val conditionEvaluator = FactAwareConditionEvaluator(fact, CallPositionToJcValueResolver(callStatement)) + val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) + var defaultBehavior = true + + // Handle PassThrough config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + defaultBehavior = false + for (action in item.actionsAfter) { + when (action) { + is CopyMark -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is CopyAllMarks -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is RemoveMark -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is RemoveAllMarks -> { + facts += actionEvaluator.evaluate(action, fact) + } + + else -> error("$action is not supported for $item") + } + } + } + } + + // Handle Cleaner config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + defaultBehavior = false + for (action in item.actionsAfter) { + when (action) { + is RemoveMark -> { + facts += actionEvaluator.evaluate(action, fact) + } + + is RemoveAllMarks -> { + facts += actionEvaluator.evaluate(action, fact) + } + + else -> error("$action is not supported for $item") + } + } + } + } + + if (!defaultBehavior) { + if (facts.size > 0) { + logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } + } + return@FlowFunction facts + } else { + // Fall back to the default behavior, as if there were no config at all. + } + } + } + + // FIXME: adhoc for constructors: + if (callee.isConstructor) { + return@FlowFunction listOf(fact) + } + + // TODO: CONSIDER REFACTORING THIS + // Default behavior for "analyzable" method calls is to remove ("temporarily") + // all the marks from the 'instance' and arguments, in order to allow them "pass through" + // the callee (when it is going to be analyzed), i.e. through "call-to-start" and + // "exit-to-return" flow functions. + // When we know that we are NOT going to analyze the callee, we do NOT need + // to remove any marks from 'instance' and arguments. + // Currently, "analyzability" of the callee depends on the fact that the callee + // is "accessible" through the JcApplicationGraph::callees(). + if (callee in graph.callees(callStatement)) { + + if (fact.variable.isStatic) { + return@FlowFunction emptyList() + } + + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + if (fact.variable.startsWith(actual.toPathOrNull())) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } + + if (callExpr is JcInstanceCallExpr) { + // Possibly tainted instance: + if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } + + } + + if (callStatement is JcAssignInst) { + // Possibly tainted lhv: + if (fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { + return@FlowFunction emptyList() // Overridden by rhv + } + } + + // The "most default" behaviour is encapsulated here: + transmitTaintNormal(fact, callStatement) + } + + override fun obtainCallToStartFlowFunction( + callStatement: JcInst, + calleeStart: JcInst, + ) = FlowFunction { fact -> + val callee = calleeStart.location.method + + if (fact == Zero) { + // return@FlowFunction obtainPossibleStartFacts(callee) + return@FlowFunction obtainPossibleStartFactsBasic(callee) + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + + buildSet { + // Transmit facts on arguments (from 'actual' to 'formal'): + val actualParams = callExpr.args + val formalParams = cp.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll( + transmitTaintArgumentActualToFormal( + fact = fact, + at = callStatement, + from = actual, + to = formal + ) + ) + } + + // Transmit facts on instance (from 'instance' to 'this'): + if (callExpr is JcInstanceCallExpr) { + addAll( + transmitTaintInstanceToThis( + fact, + at = callStatement, + from = callExpr.instance, + to = callee.thisInstance + ) + ) + } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + } + } + + override fun obtainExitToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, // unused + exitStatement: JcInst, + ) = FlowFunction { fact -> + // TODO: do we even need to return non-empty list for zero fact here? + if (fact == Zero) { + // return@FlowFunction listOf(Zero) + return@FlowFunction buildSet { + add(Zero) + if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + exitStatement.returnValue?.let { returnValue -> + if (returnValue is JcNullConstant) { + val toPath = callStatement.lhv.toPath() + add(Tainted(toPath, TaintMark.NULLNESS)) + } + } + } + } + } + check(fact is Tainted) + + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + val callee = exitStatement.location.method + + buildSet { + // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: + if (fact.variable.isOnHeap) { + val actualParams = callExpr.args + val formalParams = cp.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll( + transmitTaintArgumentFormalToActual( + fact = fact, + at = callStatement, + from = formal, + to = actual + ) + ) + } + } + + // Transmit facts on instance (from 'this' to 'instance'): + if (callExpr is JcInstanceCallExpr) { + addAll( + transmitTaintThisToInstance( + fact = fact, + at = callStatement, + from = callee.thisInstance, + to = callExpr.instance + ) + ) + } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + + // Transmit facts on return value (from 'returnValue' to 'lhv'): + if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + exitStatement.returnValue?.let { returnValue -> + addAll( + transmitTaintReturn( + fact = fact, + at = callStatement, + from = returnValue, + to = callStatement.lhv + ) + ) + } + } + } + } +} + +// @Suppress("PublicApiImplicitType") +// class BackwardNpeFlowFunctions( +// private val project: JcClasspath, +// private val graph: JcApplicationGraph, +// ) : FlowFunctions { +// +// override fun obtainPossibleStartFacts( +// method: JcMethod, +// ): Collection { +// return listOf(Zero) +// } +// +// private fun transmitTaintBackwardAssign( +// fact: Tainted, +// from: JcValue, +// to: JcExpr, +// ): Collection { +// val fromPath = from.toPath() +// val toPath = to.toPathOrNull() +// +// if (toPath != null) { +// // TODO: think about arrays here +// // // Adhoc taint array: +// // if (fromPath.accesses.isNotEmpty() +// // && fromPath.accesses.last() is ElementAccessor +// // && fromPath.copy(accesses = fromPath.accesses.dropLast(1)) == fact.variable +// // ) { +// // val newTaint = fact.copy(variable = toPath) +// // return setOf(fact, newTaint) +// // } +// +// val tail = fact.variable - fromPath +// if (tail != null) { +// // Both 'from' and 'to' are tainted now: +// val newPath = toPath / tail +// val newTaint = fact.copy(variable = newPath) +// return setOf(fact, newTaint) +// } +// +// if (fact.variable.startsWith(toPath)) { +// // 'to' was (sub-)tainted, but it is now overridden by 'from': +// return emptySet() +// } +// } +// +// // Pass-through: +// return setOf(fact) +// } +// +// private fun transmitTaintBackwardNormal( +// fact: Tainted, +// inst: JcInst, +// ): List { +// // Pass-through: +// return listOf(fact) +// } +// +// override fun obtainSequentFlowFunction( +// current: JcInst, +// next: JcInst, +// ) = FlowFunction { fact -> +// if (fact is Zero) { +// return@FlowFunction listOf(Zero) +// } +// check(fact is Tainted) +// +// if (current is JcAssignInst) { +// transmitTaintBackwardAssign(fact, from = current.lhv, to = current.rhv) +// } else { +// transmitTaintBackwardNormal(fact, current) +// } +// } +// +// private fun transmitTaint( +// fact: Tainted, +// from: JcValue, +// to: JcValue, +// ): Collection = buildSet { +// val fromPath = from.toPath() +// val toPath = to.toPath() +// +// val tail = (fact.variable - fromPath) ?: return@buildSet +// val newPath = toPath / tail +// val newTaint = fact.copy(variable = newPath) +// add(newTaint) +// } +// +// private fun transmitTaintArgumentActualToFormal( +// fact: Tainted, +// from: JcValue, // actual +// to: JcValue, // formal +// ): Collection = transmitTaint(fact, from, to) +// +// private fun transmitTaintArgumentFormalToActual( +// fact: Tainted, +// from: JcValue, // formal +// to: JcValue, // actual +// ): Collection = transmitTaint(fact, from, to) +// +// private fun transmitTaintInstanceToThis( +// fact: Tainted, +// from: JcValue, // instance +// to: JcThis, // this +// ): Collection = transmitTaint(fact, from, to) +// +// private fun transmitTaintThisToInstance( +// fact: Tainted, +// from: JcThis, // this +// to: JcValue, // instance +// ): Collection = transmitTaint(fact, from, to) +// +// private fun transmitTaintReturn( +// fact: Tainted, +// from: JcValue, +// to: JcValue, +// ): Collection = transmitTaint(fact, from, to) +// +// override fun obtainCallToReturnSiteFlowFunction( +// callStatement: JcInst, +// returnSite: JcInst, // FIXME: unused? +// ) = FlowFunction { fact -> +// // TODO: pass-through on invokedynamic-based String concatenation +// +// if (fact == Zero) { +// return@FlowFunction listOf(Zero) +// } +// check(fact is Tainted) +// +// val callExpr = callStatement.callExpr +// ?: error("Call statement should have non-null callExpr") +// val callee = callExpr.method.method +// +// // // FIXME: adhoc for constructors: +// // if (callee.isConstructor) { +// // return@FlowFunction listOf(fact) +// // } +// +// if (callee in graph.callees(callStatement)) { +// +// if (fact.variable.isStatic) { +// return@FlowFunction emptyList() +// } +// +// for (actual in callExpr.args) { +// // Possibly tainted actual parameter: +// if (fact.variable.startsWith(actual.toPathOrNull())) { +// return@FlowFunction emptyList() // Will be handled by summary edge +// } +// } +// +// if (callExpr is JcInstanceCallExpr) { +// // Possibly tainted instance: +// if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { +// return@FlowFunction emptyList() // Will be handled by summary edge +// } +// } +// +// } +// +// if (callStatement is JcAssignInst) { +// // Possibly tainted rhv: +// if (fact.variable.startsWith(callStatement.rhv.toPathOrNull())) { +// return@FlowFunction emptyList() // Overridden by lhv +// } +// } +// +// // The "most default" behaviour is encapsulated here: +// transmitTaintBackwardNormal(fact, callStatement) +// } +// +// override fun obtainCallToStartFlowFunction( +// callStatement: JcInst, +// calleeStart: JcInst, +// ) = FlowFunction { fact -> +// val callee = calleeStart.location.method +// +// if (fact == Zero) { +// return@FlowFunction obtainPossibleStartFacts(callee) +// } +// check(fact is Tainted) +// +// val callExpr = callStatement.callExpr +// ?: error("Call statement should have non-null callExpr") +// +// buildSet { +// // Transmit facts on arguments (from 'actual' to 'formal'): +// val actualParams = callExpr.args +// val formalParams = project.getArgumentsOf(callee) +// for ((formal, actual) in formalParams.zip(actualParams)) { +// addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) +// } +// +// // Transmit facts on instance (from 'instance' to 'this'): +// if (callExpr is JcInstanceCallExpr) { +// addAll(transmitTaintInstanceToThis(fact, from = callExpr.instance, to = callee.thisInstance)) +// } +// +// // Transmit facts on static values: +// if (fact.variable.isStatic) { +// add(fact) +// } +// +// // Transmit facts on return value (from 'returnValue' to 'lhv'): +// if (calleeStart is JcReturnInst && callStatement is JcAssignInst) { +// // Note: returnValue can be null here in some weird cases, e.g. in lambda. +// calleeStart.returnValue?.let { returnValue -> +// addAll(transmitTaintReturn(fact, from = callStatement.lhv, to = returnValue)) +// } +// } +// } +// } +// +// override fun obtainExitToReturnSiteFlowFunction( +// callStatement: JcInst, +// returnSite: JcInst, +// exitStatement: JcInst, +// ) = FlowFunction { fact -> +// if (fact == Zero) { +// return@FlowFunction listOf(Zero) +// } +// check(fact is Tainted) +// +// val callExpr = callStatement.callExpr +// ?: error("Call statement should have non-null callExpr") +// val callee = exitStatement.location.method +// +// buildSet { +// // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: +// // TODO: "if passed by-ref" part is not implemented here yet +// val actualParams = callExpr.args +// val formalParams = project.getArgumentsOf(callee) +// for ((formal, actual) in formalParams.zip(actualParams)) { +// addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) +// } +// +// // Transmit facts on instance (from 'this' to 'instance'): +// if (callExpr is JcInstanceCallExpr) { +// addAll(transmitTaintThisToInstance(fact, from = callee.thisInstance, to = callExpr.instance)) +// } +// +// // Transmit facts on static values: +// if (fact.variable.isStatic) { +// add(fact) +// } +// } +// } +// } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt new file mode 100644 index 000000000..beffe61d5 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -0,0 +1,284 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint.npe + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.yield +import org.jacodb.analysis.engine.SummaryStorageImpl +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.analysis.engine.UnitType +import org.jacodb.analysis.ifds2.ControlEvent +import org.jacodb.analysis.ifds2.Manager +import org.jacodb.analysis.ifds2.QueueEmptinessChanged +import org.jacodb.analysis.ifds2.Runner +import org.jacodb.analysis.ifds2.pathEdges +import org.jacodb.analysis.ifds2.taint.EdgeForOtherRunner +import org.jacodb.analysis.ifds2.taint.NewSummaryEdge +import org.jacodb.analysis.ifds2.taint.NewVulnerability +import org.jacodb.analysis.ifds2.taint.SummaryEdge +import org.jacodb.analysis.ifds2.taint.TaintEdge +import org.jacodb.analysis.ifds2.taint.TaintEvent +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.analysis.ifds2.taint.TaintRunner +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.Vulnerability +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.taint.configuration.TaintMark +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +private val logger = KotlinLogging.logger {} + +class NpeManager( + private val graph: JcApplicationGraph, + private val unitResolver: UnitResolver, +) : Manager { + + private val methodsForUnit: MutableMap> = hashMapOf() + private val runnerForUnit: MutableMap = hashMapOf() + private val queueIsEmpty: MutableMap = ConcurrentHashMap() + + private val summaryEdgesStorage = SummaryStorageImpl() + private val vulnerabilitiesStorage = SummaryStorageImpl() + + private val stopRendezvous = Channel(Channel.RENDEZVOUS) + + private fun newRunner( + unit: UnitType, + ): TaintRunner { + check(unit !in runnerForUnit) { "Runner for $unit already exists" } + + val analyzer = NpeAnalyzer(graph) + val runner = Runner(graph, analyzer, this@NpeManager, unitResolver, unit) + + runnerForUnit[unit] = runner + return runner + } + + private fun addStart(method: JcMethod) { + logger.info { "Adding start method: $method" } + val unit = unitResolver.resolve(method) + methodsForUnit.getOrPut(unit) { mutableSetOf() }.add(method) + // TODO: val isNew = (...).add(); if (isNew) { deps.forEach { addStart(it) } } + } + + @OptIn(ExperimentalTime::class) + fun analyze( + startMethods: List, + ): List = runBlocking(Dispatchers.Default) { + val timeStart = TimeSource.Monotonic.markNow() + + // Add start methods: + for (method in startMethods) { + addStart(method) + } + + // Determine all units: + val allUnits = methodsForUnit.keys.toList() + logger.info { + "Starting analysis of ${ + methodsForUnit.values.sumOf { it.size } + } methods in ${allUnits.size} units" + } + + // Spawn runner jobs: + val allJobs = allUnits.map { unit -> + // Create the runner: + val runner = newRunner(unit) + + // Start the runner: + launch(start = CoroutineStart.LAZY) { + val methods = methodsForUnit[unit]!!.toList() + runner.run(methods) + } + } + + // Spawn progress job: + val progress = launch(Dispatchers.IO) { + logger.info { "Progress job started" } + while (isActive) { + delay(1.seconds) + logger.info { + "Progress: total propagated ${ + runnerForUnit.values.sumOf { it.pathEdges.size } + } path edges" + } + } + logger.info { "Progress job finished" } + } + + // Spawn stopper job: + val stopper = launch(Dispatchers.IO) { + logger.info { "Stopper job started" } + stopRendezvous.receive() + // delay(100) + // @OptIn(ExperimentalCoroutinesApi::class) + // if (runnerForUnit.values.any { !it.workList.isEmpty }) { + // logger.warn { "NOT all runners have empty work list" } + // error("?") + // } + logger.info { "Stopping all runners..." } + allJobs.forEach { it.cancel() } + logger.info { "Stopper job finished" } + } + + // Start all runner jobs: + val timeStartJobs = TimeSource.Monotonic.markNow() + allJobs.forEach { it.start() } + + // Await all runners: + withTimeoutOrNull(3600.seconds) { + allJobs.joinAll() + } ?: run { + allJobs.forEach { it.cancel() } + allJobs.joinAll() + } + progress.cancelAndJoin() + stopper.cancelAndJoin() + logger.info { + "All ${allJobs.size} jobs completed in %.1f s".format( + timeStartJobs.elapsedNow().toDouble(DurationUnit.SECONDS) + ) + } + + // Extract found vulnerabilities (sinks): + val foundVulnerabilities = vulnerabilitiesStorage.knownMethods + .flatMap { method -> + vulnerabilitiesStorage.getCurrentFacts(method) + } + logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } + for (vulnerability in foundVulnerabilities) { + logger.debug { "$vulnerability in ${vulnerability.method}" } + } + logger.info { "Total sinks: ${foundVulnerabilities.size}" } + + logger.info { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } + + if (logger.isDebugEnabled()) { + val statsFileName = "stats.csv" + logger.debug { "Writing stats in '$statsFileName'..." } + File(statsFileName).outputStream().bufferedWriter().use { writer -> + val sep = ";" + writer.write(listOf("classname", "cwe", "method", "sink", "fact").joinToString(sep) + "\n") + for (vulnerability in foundVulnerabilities) { + val m = vulnerability.method + if (vulnerability.rule != null) { + for (cwe in vulnerability.rule.cwe) { + writer.write( + listOf( + m.enclosingClass.simpleName, + cwe, + m.name, + vulnerability.sink.statement, + vulnerability.sink.fact + ).joinToString(sep) { "\"$it\"" } + "\n") + } + } else if ( + vulnerability.sink.fact is Tainted + && vulnerability.sink.fact.mark == TaintMark.NULLNESS + ) { + val cwe = 476 + writer.write( + listOf( + m.enclosingClass.simpleName, + cwe, + m.name, + vulnerability.sink.statement, + vulnerability.sink.fact + ).joinToString(sep) { "\"$it\"" } + "\n") + } else { + logger.warn { "Bad vulnerability without rule: $vulnerability" } + } + } + } + } + + logger.info { + "Analysis done in %.1f s".format( + timeStart.elapsedNow().toDouble(DurationUnit.SECONDS) + ) + } + foundVulnerabilities + } + + override fun handleEvent(event: TaintEvent) { + when (event) { + is NewSummaryEdge -> { + summaryEdgesStorage.add(SummaryEdge(event.edge)) + } + + is NewVulnerability -> { + vulnerabilitiesStorage.add(event.vulnerability) + } + + is EdgeForOtherRunner -> { + val method = event.edge.method + val unit = unitResolver.resolve(method) + val otherRunner = runnerForUnit[unit] ?: run { + logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } + return + } + otherRunner.submitNewEdge(event.edge) + } + } + } + + override suspend fun handleControlEvent(event: ControlEvent) { + when (event) { + is QueueEmptinessChanged -> { + queueIsEmpty[event.runner.unit] = event.isEmpty + if (event.isEmpty) { + yield() + if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { + stopRendezvous.send(Unit) + } + } + } + } + } + + override fun subscribeOnSummaryEdges( + method: JcMethod, + scope: CoroutineScope, + handler: suspend (TaintEdge) -> Unit, + ) { + summaryEdgesStorage + .getFacts(method) + .map { it.edge } + .onEach(handler) + .launchIn(scope) + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt new file mode 100644 index 000000000..833fa4a12 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("LiftReturnOrAssignment") + +package org.jacodb.analysis.ifds2.taint.npe + +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.analysis.ifds2.taint.Vulnerability +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph + +fun runNpeAnalysis( + graph: JcApplicationGraph, + unitResolver: UnitResolver, + startMethods: List, +): List { + val manager = NpeManager(graph, unitResolver) + return manager.analyze(startMethods) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt new file mode 100644 index 000000000..e4eed9033 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.ifds2.taint + +import org.jacodb.analysis.ifds2.Edge +import org.jacodb.analysis.ifds2.IRunner +import org.jacodb.analysis.ifds2.Vertex + +typealias TaintVertex = Vertex +typealias TaintEdge = Edge +typealias TaintRunner = IRunner diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt index 62c0cffa1..ead16f5d8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt @@ -28,11 +28,10 @@ import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstanceCallExpr import org.jacodb.api.cfg.JcLocal import org.jacodb.api.cfg.JcValue -import org.jacodb.api.ext.cfg.arrayRef +import org.jacodb.api.ext.cfg.arrayAccess import org.jacodb.api.ext.cfg.callExpr import org.jacodb.api.ext.cfg.fieldRef - class NullAnalysisMap : HashMap { constructor() : super() @@ -70,7 +69,7 @@ open class NullAssumptionAnalysis(graph: JcGraph) : BackwardFlowAnalysis - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("UnitResolversLibrary") -package org.jacodb.analysis.library - -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.api.JcClassOrInterface -import org.jacodb.api.JcMethod -import org.jacodb.api.ext.packageName - -val MethodUnitResolver = UnitResolver { method -> method } -val PackageUnitResolver = UnitResolver { method -> method.enclosingClass.packageName } -val SingletonUnitResolver = UnitResolver { _ -> Unit } - -fun getClassUnitResolver(includeNested: Boolean): UnitResolver { - return ClassUnitResolver(includeNested) -} - -private class ClassUnitResolver(private val includeNested: Boolean): UnitResolver { - override fun resolve(method: JcMethod): JcClassOrInterface { - return if (includeNested) { - generateSequence(method.enclosingClass) { it.outerClass }.last() - } else { - method.enclosingClass - } - } -} \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt index 5b21a6794..75683f8d9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt @@ -33,6 +33,7 @@ import org.jacodb.api.cfg.JcReturnInst import org.jacodb.api.cfg.JcValue import org.jacodb.api.ext.cfg.callExpr +@Suppress("PublicApiImplicitType") abstract class AbstractTaintBackwardFunctions( protected val graph: JcApplicationGraph, protected val maxPathLength: Int, @@ -42,11 +43,24 @@ abstract class AbstractTaintBackwardFunctions( return listOf(ZEROFact) } - abstract fun transmitBackDataFlow(from: JcValue, to: JcExpr, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List - - abstract fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List - - override fun obtainSequentFlowFunction(current: JcInst, next: JcInst) = FlowFunctionInstance { fact -> + abstract fun transmitBackDataFlow( + from: JcValue, + to: JcExpr, + atInst: JcInst, + fact: DomainFact, + dropFact: Boolean, + ): List + + abstract fun transmitDataFlowAtNormalInst( + inst: JcInst, + nextInst: JcInst, + fact: DomainFact, + ): List + + override fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ) = FlowFunctionInstance { fact -> // fact.activation != current needed here to jump over assignment where the fact appeared if (current is JcAssignInst && (fact !is TaintNode || fact.activation != current)) { transmitBackDataFlow(current.lhv, current.rhv, current, fact, dropFact = false) @@ -57,7 +71,7 @@ abstract class AbstractTaintBackwardFunctions( override fun obtainCallToStartFlowFunction( callStatement: JcInst, - callee: JcMethod + callee: JcMethod, ): FlowFunctionInstance = FlowFunctionInstance { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") @@ -68,7 +82,7 @@ abstract class AbstractTaintBackwardFunctions( } if (callStatement is JcAssignInst) { - graph.entryPoint(callee).filterIsInstance().forEach { returnInst -> + graph.entryPoints(callee).filterIsInstance().forEach { returnInst -> returnInst.returnValue?.let { addAll(transmitBackDataFlow(callStatement.lhv, it, callStatement, fact, dropFact = true)) } @@ -80,7 +94,7 @@ abstract class AbstractTaintBackwardFunctions( addAll(transmitBackDataFlow(callExpr.instance, thisInstance, callStatement, fact, dropFact = true)) } - val formalParams = graph.classpath.getFormalParamsOf(callee) + val formalParams = graph.classpath.getArgumentsOf(callee) callExpr.args.zip(formalParams).forEach { (actual, formal) -> // FilterNot is needed for reasons described in comment for symmetric case in @@ -93,7 +107,8 @@ abstract class AbstractTaintBackwardFunctions( override fun obtainCallToReturnFlowFunction( callStatement: JcInst, - returnSite: JcInst + returnSite: JcInst, + graph: JcApplicationGraph, ): FlowFunctionInstance = FlowFunctionInstance { fact -> if (fact !is TaintNode) { return@FlowFunctionInstance if (fact == ZEROFact) { @@ -140,12 +155,12 @@ abstract class AbstractTaintBackwardFunctions( override fun obtainExitToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, - exitStatement: JcInst + exitStatement: JcInst, ): FlowFunctionInstance = FlowFunctionInstance { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") val actualParams = callExpr.args val callee = graph.methodOf(exitStatement) - val formalParams = graph.classpath.getFormalParamsOf(callee) + val formalParams = graph.classpath.getArgumentsOf(callee) buildList { formalParams.zip(actualParams).forEach { (formal, actual) -> @@ -153,7 +168,15 @@ abstract class AbstractTaintBackwardFunctions( } if (callExpr is JcInstanceCallExpr) { - addAll(transmitBackDataFlow(callee.thisInstance, callExpr.instance, exitStatement, fact, dropFact = true)) + addAll( + transmitBackDataFlow( + callee.thisInstance, + callExpr.instance, + exitStatement, + fact, + dropFact = true + ) + ) } if (fact is TaintNode && fact.variable.isStatic) { @@ -161,4 +184,4 @@ abstract class AbstractTaintBackwardFunctions( } } } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt index 3edc9efa6..6ceaafa73 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt @@ -14,45 +14,90 @@ * limitations under the License. */ +@file:Suppress("LiftReturnOrAssignment") + package org.jacodb.analysis.library.analyzers +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jacodb.analysis.config.BasicConditionEvaluator +import org.jacodb.analysis.config.CallPositionToAccessPathResolver +import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.config.TaintActionEvaluator import org.jacodb.analysis.engine.DomainFact import org.jacodb.analysis.engine.FlowFunctionInstance import org.jacodb.analysis.engine.FlowFunctionsSpace import org.jacodb.analysis.engine.ZEROFact +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.toDomainFact import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toPath import org.jacodb.analysis.paths.toPathOrNull import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcDynamicCallExpr import org.jacodb.api.cfg.JcExpr import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstanceCallExpr import org.jacodb.api.cfg.JcReturnInst import org.jacodb.api.cfg.JcValue import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.taint.configuration.AssignMark +import org.jacodb.taint.configuration.CopyAllMarks +import org.jacodb.taint.configuration.CopyMark +import org.jacodb.taint.configuration.RemoveAllMarks +import org.jacodb.taint.configuration.RemoveMark +import org.jacodb.taint.configuration.TaintCleaner +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.taint.configuration.TaintMethodSource +import org.jacodb.taint.configuration.TaintPassThrough + +private val logger = KotlinLogging.logger {} +@Suppress("PublicApiImplicitType") abstract class AbstractTaintForwardFunctions( - protected val cp: JcClasspath + protected val cp: JcClasspath, ) : FlowFunctionsSpace { - abstract fun transmitDataFlow(from: JcExpr, to: JcValue, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List + internal val taintConfigurationFeature: TaintConfigurationFeature? by lazy { + cp.features + ?.singleOrNull { it is TaintConfigurationFeature } + ?.let { it as TaintConfigurationFeature } + } + + protected abstract fun transmitDataFlow( + from: JcExpr, + to: JcValue, + atInst: JcInst, + fact: DomainFact, + dropFact: Boolean, + ): List - abstract fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List + protected abstract fun transmitDataFlowAtNormalInst( + inst: JcInst, + nextInst: JcInst, + fact: DomainFact, + ): List - override fun obtainSequentFlowFunction(current: JcInst, next: JcInst) = FlowFunctionInstance { fact -> + final override fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ) = FlowFunctionInstance { fact -> if (fact is TaintNode && fact.activation == current) { listOf(fact.activatedCopy) } else if (current is JcAssignInst) { - transmitDataFlow(current.rhv, current.lhv, current, fact, dropFact = false) + // Note: 'next' is ignored + transmitDataFlow(current.rhv, current.lhv, current, fact, false) } else { transmitDataFlowAtNormalInst(current, next, fact) } } - override fun obtainCallToStartFlowFunction( + final override fun obtainCallToStartFlowFunction( callStatement: JcInst, - callee: JcMethod + callee: JcMethod, ) = FlowFunctionInstance { fact -> if (fact is TaintNode && fact.activation == callStatement) { return@FlowFunctionInstance emptyList() @@ -60,8 +105,10 @@ abstract class AbstractTaintForwardFunctions( val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") val actualParams = callExpr.args - val formalParams = cp.getFormalParamsOf(callee) - buildList { + val formalParams = cp.getArgumentsOf(callee) + buildSet { + // TODO: when dropFact=true, consider removing (note: once!) the fact afterwards + formalParams.zip(actualParams).forEach { (formal, actual) -> addAll(transmitDataFlow(actual, formal, callStatement, fact, dropFact = true)) } @@ -70,21 +117,71 @@ abstract class AbstractTaintForwardFunctions( addAll(transmitDataFlow(callExpr.instance, callee.thisInstance, callStatement, fact, dropFact = true)) } - if (fact == ZEROFact || (fact is TaintNode && fact.variable.isStatic)) { + if (fact is TaintNode && fact.variable.isStatic) { add(fact) } + + if (fact == ZEROFact) { + addAll(obtainPossibleStartFacts(callStatement)) + } } } - override fun obtainCallToReturnFlowFunction( + final override fun obtainCallToReturnFlowFunction( callStatement: JcInst, - returnSite: JcInst + returnSite: JcInst, + graph: JcApplicationGraph, ) = FlowFunctionInstance { fact -> - if (fact == ZEROFact) { + val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") + val callee = callExpr.method.method + + // FIXME: adhoc for constructors: + if (callee.isConstructor) { return@FlowFunctionInstance listOf(fact) } - if (fact !is TaintNode || fact.variable.isStatic) { + // FIXME: handle taint pass-through on invokedynamic-based String concatenation: + if (fact is TaintNode && callExpr is JcDynamicCallExpr && callee.enclosingClass.name == "java.lang.invoke.StringConcatFactory" && callStatement is JcAssignInst) { + for (arg in callExpr.args) { + if (arg.toPath() == fact.variable) { + return@FlowFunctionInstance setOf( + fact, + Tainted(fact).copy(variable = callStatement.lhv.toPath()).toDomainFact() + ) + } + } + return@FlowFunctionInstance setOf(fact) + } + + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $callee" } + feature.getConfigForMethod(callee) + } + + if (fact == ZEROFact) { + val facts = mutableSetOf() + if (config != null) { + val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(callStatement)) + val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + when (action) { + is AssignMark -> { + facts += actionEvaluator.evaluate(action) + } + + else -> error("$action is not supported for $item") + } + } + } + } + } + logger.debug { "call-to-return-site flow function for callee=$callee, fact=$fact returns ${facts.size} facts: $facts" } + return@FlowFunctionInstance facts.map { it.toDomainFact() } + ZEROFact + } + + if (fact !is TaintNode) { return@FlowFunctionInstance emptyList() } @@ -92,32 +189,115 @@ abstract class AbstractTaintForwardFunctions( return@FlowFunctionInstance listOf(fact.activatedCopy) } - val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - val actualParams = callExpr.args + if (config != null) { + val conditionEvaluator = FactAwareConditionEvaluator( + Tainted(fact), + CallPositionToJcValueResolver(callStatement) + ) + val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) + val facts = mutableSetOf() + var defaultBehavior = true + + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + defaultBehavior = false + for (action in item.actionsAfter) { + when (action) { + is CopyMark -> { + facts += actionEvaluator.evaluate(action, Tainted(fact)) + } + + is CopyAllMarks -> { + facts += actionEvaluator.evaluate(action, Tainted(fact)) + } - actualParams.mapNotNull { it.toPathOrNull() }.forEach { - if (fact.variable.startsWith(it)) { - return@FlowFunctionInstance emptyList() // Will be handled by summary edge + is RemoveMark -> { + facts += actionEvaluator.evaluate(action, Tainted(fact)) + } + + is RemoveAllMarks -> { + facts += actionEvaluator.evaluate(action, Tainted(fact)) + } + + else -> error("$action is not supported for $item") + } + } + } + } + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + defaultBehavior = false + for (action in item.actionsAfter) { + when (action) { + is RemoveMark -> { + facts += actionEvaluator.evaluate(action, Tainted(fact)) + } + + is RemoveAllMarks -> { + facts += actionEvaluator.evaluate(action, Tainted(fact)) + } + + else -> error("$action is not supported for $item") + } + } + } + } + + if (!defaultBehavior) { + if (facts.size > 0) { + logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } + } + return@FlowFunctionInstance facts.map { it.toDomainFact() } + } else { + // Fall back to the default behavior, as if there were no config at all. } } - if (callExpr is JcInstanceCallExpr) { - if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { - return@FlowFunctionInstance emptyList() // Will be handled by summary edge + // Default behavior for "analyzable" method calls is to remove ("temporarily") + // all the marks from the 'instance' and arguments, in order to allow them "pass through" + // the callee (when it is going to be analyzed), i.e. through "call-to-start" and + // "exit-to-return" flow functions. + // When we know that we are NOT going to analyze the callee, we do NOT need + // to remove any marks from 'instance' and arguments. + // Currently, "analyzability" of the callee depends on the fact that the callee + // is "accessible" through the JcApplicationGraph::callees(). + if (callee in graph.callees(callStatement)) { + + if (fact.variable.isStatic) { + return@FlowFunctionInstance emptyList() + } + + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + if (fact.variable.startsWith(actual.toPathOrNull())) { + return@FlowFunctionInstance emptyList() // Will be handled by summary edge + } + } + + if (callExpr is JcInstanceCallExpr) { + // Possibly tainted instance: + if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { + return@FlowFunctionInstance emptyList() // Will be handled by summary edge + } } + } - if (callStatement is JcAssignInst && fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { - return@FlowFunctionInstance emptyList() + if (callStatement is JcAssignInst) { + // Possibly tainted lhv: + if (fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { + return@FlowFunctionInstance emptyList() // Overridden by rhv + } } + // TODO: do we even need to call this here??? transmitDataFlowAtNormalInst(callStatement, returnSite, fact) } - override fun obtainExitToReturnSiteFlowFunction( + final override fun obtainExitToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, - exitStatement: JcInst + exitStatement: JcInst, ): FlowFunctionInstance = FlowFunctionInstance { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") val actualParams = callExpr.args @@ -128,11 +308,11 @@ abstract class AbstractTaintForwardFunctions( } else { fact } - val formalParams = cp.getFormalParamsOf(callee) + val formalParams = cp.getArgumentsOf(callee) buildList { if (fact is TaintNode && fact.variable.isOnHeap) { - // If there is some method A.f(formal: T) that is called like A.f(actual) then + // If there is some method A::f(formal: T) that is called like a.f(actual) then // 1. For all g^k, k >= 1, we should propagate back from formal.g^k to actual.g^k (as they are on heap) // 2. We shouldn't propagate from formal to actual (as formal is local) // Second case is why we need check for isOnHeap @@ -143,7 +323,15 @@ abstract class AbstractTaintForwardFunctions( } if (callExpr is JcInstanceCallExpr) { - addAll(transmitDataFlow(callee.thisInstance, callExpr.instance, exitStatement, updatedFact, dropFact = true)) + addAll( + transmitDataFlow( + callee.thisInstance, + callExpr.instance, + exitStatement, + updatedFact, + dropFact = true + ) + ) } if (callStatement is JcAssignInst && exitStatement is JcReturnInst) { @@ -157,4 +345,4 @@ abstract class AbstractTaintForwardFunctions( } } } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt index b8af34e37..a38c23ef9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt @@ -62,7 +62,10 @@ fun NpeAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { graph -> NpeAnalyzer(graph, maxPathLength) } -class NpeAnalyzer(graph: JcApplicationGraph, maxPathLength: Int) : AbstractAnalyzer(graph) { +class NpeAnalyzer( + graph: JcApplicationGraph, + maxPathLength: Int, +) : AbstractAnalyzer(graph) { override val flowFunctions: FlowFunctionsSpace = NpeForwardFunctions(graph.classpath, maxPathLength) override val isMainAnalyzer: Boolean @@ -73,13 +76,13 @@ class NpeAnalyzer(graph: JcApplicationGraph, maxPathLength: Int) : AbstractAnaly } override fun handleNewEdge(edge: IfdsEdge): List = buildList { - val (inst, fact0) = edge.v + val (inst, fact0) = edge.to if (fact0 is NpeTaintNode && fact0.activation == null && fact0.variable.isDereferencedAt(inst)) { val message = "Dereference of possibly-null ${fact0.variable}" val desc = VulnerabilityDescription(SarifMessage(message), ruleId) - add(NewSummaryFact((VulnerabilityLocation(desc, edge.v)))) - verticesWithTraceGraphNeeded.add(edge.v) + add(NewSummaryFact((VulnerabilityLocation(desc, edge.to, edge)))) + verticesWithTraceGraphNeeded.add(edge.to) } addAll(super.handleNewEdge(edge)) @@ -93,7 +96,7 @@ class NpeAnalyzer(graph: JcApplicationGraph, maxPathLength: Int) : AbstractAnaly private class NpeForwardFunctions( cp: JcClasspath, - private val maxPathLength: Int + private val maxPathLength: Int, ) : AbstractTaintForwardFunctions(cp) { private val JcIfInst.pathComparedWithNull: AccessPath? @@ -108,7 +111,13 @@ private class NpeForwardFunctions( } } - override fun transmitDataFlow(from: JcExpr, to: JcValue, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List { + override fun transmitDataFlow( + from: JcExpr, + to: JcValue, + atInst: JcInst, + fact: DomainFact, + dropFact: Boolean, + ): List { val default = if (dropFact && fact != ZEROFact) emptyList() else listOf(fact) val toPath = to.toPathOrNull()?.limit(maxPathLength) ?: return default @@ -116,8 +125,11 @@ private class NpeForwardFunctions( return if (from is JcNullConstant || (from is JcCallExpr && from.method.method.treatAsNullable)) { listOf(ZEROFact, NpeTaintNode(toPath)) // taint is generated here } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { - val arrayElemPath = AccessPath.fromOther(toPath, List((from.type as JcArrayType).dimensions) { ElementAccessor }) - listOf(ZEROFact, NpeTaintNode(arrayElemPath.limit(maxPathLength))) + val accessors = List((from.type as JcArrayType).dimensions) { + ElementAccessor(null) + } + val path = (toPath / accessors).limit(maxPathLength) + listOf(ZEROFact, NpeTaintNode(path)) } else { listOf(ZEROFact) } @@ -132,7 +144,12 @@ private class NpeForwardFunctions( return emptyList() } - if (from is JcNewExpr || from is JcNewArrayExpr || from is JcConstant || (from is JcCallExpr && !from.method.method.treatAsNullable)) { + if ( + from is JcNewExpr || + from is JcNewArrayExpr || + from is JcConstant || + (from is JcCallExpr && !from.method.method.treatAsNullable) + ) { return if (factPath.startsWith(toPath)) { emptyList() // new kills the fact here } else { @@ -145,7 +162,11 @@ private class NpeForwardFunctions( return normalFactFlow(fact, fromPath, toPath, dropFact, maxPathLength) } - override fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List { + override fun transmitDataFlowAtNormalInst( + inst: JcInst, + nextInst: JcInst, + fact: DomainFact, + ): List { val factPath = when (fact) { is NpeTaintNode -> fact.variable ZEROFact -> null @@ -192,9 +213,8 @@ private class NpeForwardFunctions( } } - override fun obtainPossibleStartFacts(startStatement: JcInst): Collection { - val result = mutableListOf(ZEROFact) -// return result + override fun obtainPossibleStartFacts(startStatement: JcInst): List = buildList { + add(ZEROFact) val method = startStatement.location.method @@ -202,29 +222,21 @@ private class NpeForwardFunctions( // an increase of false positives and significant performance drop // Possibly null arguments - result += method.flowGraph().locals + this += method.flowGraph().locals .filterIsInstance() .filter { method.parameters[it.index].isNullable != false } - .map { NpeTaintNode(AccessPath.fromLocal(it)) } + .map { NpeTaintNode(AccessPath.from(it)) } // Possibly null statics // TODO: handle statics in a more general manner - result += method.enclosingClass.fields + this += method.enclosingClass.fields .filter { it.isNullable != false && it.isStatic } .map { NpeTaintNode(AccessPath.fromStaticField(it)) } - val thisInstance = method.thisInstance - // Possibly null public non-final fields - result += method.enclosingClass.fields + this += method.enclosingClass.fields .filter { it.isNullable != false && !it.isStatic && it.isPublic && !it.isFinal } - .map { - NpeTaintNode( - AccessPath.fromOther(AccessPath.fromLocal(thisInstance), listOf(FieldAccessor(it))) - ) - } - - return result + .map { NpeTaintNode(AccessPath.from(method.thisInstance) / FieldAccessor(it)) } } } @@ -232,22 +244,35 @@ fun NpePrecalcBackwardAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { gr NpePrecalcBackwardAnalyzer(graph, maxPathLength) } -private class NpePrecalcBackwardAnalyzer(val graph: JcApplicationGraph, maxPathLength: Int) : AbstractAnalyzer(graph) { +private class NpePrecalcBackwardAnalyzer( + graph: JcApplicationGraph, + maxPathLength: Int, +) : AbstractAnalyzer(graph) { + override val flowFunctions: FlowFunctionsSpace = NpePrecalcBackwardFunctions(graph, maxPathLength) override val isMainAnalyzer: Boolean get() = false override fun handleNewEdge(edge: IfdsEdge): List = buildList { - if (edge.v.statement in graph.exitPoints(edge.method)) { - add(EdgeForOtherRunnerQuery((IfdsEdge(edge.v, edge.v)))) + if (edge.to.statement in graph.exitPoints(edge.method)) { + add(EdgeForOtherRunnerQuery((IfdsEdge(edge.to, edge.to)))) } } } -class NpePrecalcBackwardFunctions(graph: JcApplicationGraph, maxPathLength: Int) - : AbstractTaintBackwardFunctions(graph, maxPathLength) { - override fun transmitBackDataFlow(from: JcValue, to: JcExpr, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List { +class NpePrecalcBackwardFunctions( + graph: JcApplicationGraph, + maxPathLength: Int, +) : AbstractTaintBackwardFunctions(graph, maxPathLength) { + + override fun transmitBackDataFlow( + from: JcValue, + to: JcExpr, + atInst: JcInst, + fact: DomainFact, + dropFact: Boolean, + ): List { val thisInstance = atInst.location.method.thisInstance.toPath() if (fact == ZEROFact) { val derefs = atInst.values @@ -262,22 +287,28 @@ class NpePrecalcBackwardFunctions(graph: JcApplicationGraph, maxPathLength: Int) return emptyList() } - val factPath = (fact as? TaintNode)?.variable + val factPath = fact.variable val default = if (dropFact) emptyList() else listOf(fact) val toPath = to.toPathOrNull() ?: return default val fromPath = from.toPathOrNull() ?: return default val diff = factPath.minus(fromPath) if (diff != null) { - return listOf(fact.moveToOtherPath(AccessPath.fromOther(toPath, diff).limit(maxPathLength))).filterNot { + val newPath = (toPath / diff).limit(maxPathLength) + return listOf(fact.moveToOtherPath(newPath)).filterNot { it.variable == thisInstance } } return default } - override fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List = - listOf(fact) + override fun transmitDataFlowAtNormalInst( + inst: JcInst, + nextInst: JcInst, + fact: DomainFact, + ): List { + return listOf(fact) + } override fun obtainPossibleStartFacts(startStatement: JcInst): List { val values = startStatement.values @@ -299,4 +330,4 @@ private val JcMethod.treatAsNullable: Boolean private val knownNullableMethods = listOf( "java.lang.System.getProperty", "java.util.Properties.getProperty" -) \ No newline at end of file +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt index 0b779cb67..9b999427d 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt @@ -24,7 +24,7 @@ import org.jacodb.api.analysis.JcApplicationGraph class SqlInjectionAnalyzer( graph: JcApplicationGraph, - maxPathLength: Int + maxPathLength: Int, ) : TaintAnalyzer(graph, maxPathLength) { override val generates = isSourceMethodToGenerates(sqlSourceMatchers.asMethodMatchers) override val sanitizes = isSanitizeMethodToSanitizes(sqlSanitizeMatchers.asMethodMatchers) @@ -42,7 +42,7 @@ class SqlInjectionAnalyzer( class SqlInjectionBackwardAnalyzer( graph: JcApplicationGraph, - maxPathLength: Int + maxPathLength: Int, ) : TaintBackwardAnalyzer(graph, maxPathLength) { override val generates = isSourceMethodToGenerates(sqlSourceMatchers.asMethodMatchers) override val sinks = isSinkMethodToSinks(sqlSinkMatchers.asMethodMatchers) @@ -56,18 +56,18 @@ fun SqlInjectionBackwardAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { SqlInjectionBackwardAnalyzer(graph, maxPathLength) } -private val sqlSourceMatchers = listOf( - "java\\.io.+", - "java\\.lang\\.System\\#getenv", - "java\\.sql\\.ResultSet#get.+" +private val sqlSourceMatchers: List = listOf( + "java\\.io.+", // TODO + // "java\\.lang\\.System\\#getenv", // in config + // "java\\.sql\\.ResultSet#get.+" // in config ) -private val sqlSanitizeMatchers = listOf( - "java\\.sql\\.Statement#set.*", - "java\\.sql\\.PreparedStatement#set.*" +private val sqlSanitizeMatchers: List = listOf( + // "java\\.sql\\.Statement#set.*", // Remove + // "java\\.sql\\.PreparedStatement#set.*" // TODO ) -private val sqlSinkMatchers = listOf( - "java\\.sql\\.Statement#execute.*", - "java\\.sql\\.PreparedStatement#execute.*", -) \ No newline at end of file +private val sqlSinkMatchers: List = listOf( + // "java\\.sql\\.Statement#execute.*", // in config + // "java\\.sql\\.PreparedStatement#execute.*", // in config +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt index 3ed7b27b3..fcf338811 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt @@ -16,6 +16,11 @@ package org.jacodb.analysis.library.analyzers +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jacodb.analysis.config.BasicConditionEvaluator +import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.config.TaintActionEvaluator import org.jacodb.analysis.engine.AbstractAnalyzer import org.jacodb.analysis.engine.AnalysisDependentEvent import org.jacodb.analysis.engine.DomainFact @@ -26,7 +31,8 @@ import org.jacodb.analysis.engine.IfdsVertex import org.jacodb.analysis.engine.NewSummaryFact import org.jacodb.analysis.engine.VulnerabilityLocation import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.paths.AccessPath +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.toDomainFact import org.jacodb.analysis.paths.minus import org.jacodb.analysis.paths.startsWith import org.jacodb.analysis.paths.toPath @@ -44,12 +50,24 @@ import org.jacodb.api.cfg.JcValue import org.jacodb.api.cfg.locals import org.jacodb.api.cfg.values import org.jacodb.api.ext.cfg.callExpr +import org.jacodb.api.ext.findTypeOrNull +import org.jacodb.taint.configuration.AnyArgument +import org.jacodb.taint.configuration.Argument +import org.jacodb.taint.configuration.AssignMark +import org.jacodb.taint.configuration.Result +import org.jacodb.taint.configuration.ResultAnyElement +import org.jacodb.taint.configuration.TaintEntryPointSource +import org.jacodb.taint.configuration.TaintMethodSink +import org.jacodb.taint.configuration.This + +private val logger = KotlinLogging.logger {} fun isSourceMethodToGenerates(isSourceMethod: (JcMethod) -> Boolean): (JcInst) -> List { return generates@{ inst: JcInst -> - val callExpr = inst.callExpr?.takeIf { isSourceMethod(it.method.method) } ?: return@generates emptyList() + val callExpr = inst.callExpr?.takeIf { isSourceMethod(it.method.method) } + ?: return@generates emptyList() if (inst is JcAssignInst && isSourceMethod(callExpr.method.method)) { - listOf(TaintAnalysisNode(inst.lhv.toPath())) + listOf(TaintAnalysisNode(inst.lhv.toPath(), nodeType = "TAINT")) } else { emptyList() } @@ -58,15 +76,16 @@ fun isSourceMethodToGenerates(isSourceMethod: (JcMethod) -> Boolean): (JcInst) - fun isSinkMethodToSinks(isSinkMethod: (JcMethod) -> Boolean): (JcInst) -> List { return sinks@{ inst: JcInst -> - val callExpr = inst.callExpr?.takeIf { isSinkMethod(it.method.method) } ?: return@sinks emptyList() + val callExpr = inst.callExpr?.takeIf { isSinkMethod(it.method.method) } + ?: return@sinks emptyList() callExpr.values .mapNotNull { it.toPathOrNull() } - .map { TaintAnalysisNode(it) } + .map { TaintAnalysisNode(it, nodeType = "TAINT") } } } fun isSanitizeMethodToSanitizes(isSanitizeMethod: (JcMethod) -> Boolean): (JcExpr, TaintNode) -> Boolean { - return { expr: JcExpr, fact: TaintNode -> + return sanitizes@{ expr: JcExpr, fact: TaintNode -> if (expr !is JcCallExpr) { false } else { @@ -88,7 +107,7 @@ internal val List.asMethodMatchers: (JcMethod) -> Boolean abstract class TaintAnalyzer( graph: JcApplicationGraph, - maxPathLength: Int + maxPathLength: Int, ) : AbstractAnalyzer(graph) { abstract val generates: (JcInst) -> List abstract val sanitizes: (JcExpr, TaintNode) -> Boolean @@ -101,20 +120,92 @@ abstract class TaintAnalyzer( override val isMainAnalyzer: Boolean get() = true + // private val skipped: MutableMap = mutableMapOf() + // override fun isSkipped(method: JcMethod): Boolean { + // return skipped.getOrPut(method) { + // // TODO: read the config and assign True if there is a MethodSource item, and False otherwise. + // // Note: the computed value is cached. + // + // fun magic(): T = TODO() + // val current: JcInst = magic() + // val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(current)) + // + // // FIXME: we need the call itself in order to evaluate the condition + // for (item in config.items) { + // if (item is TaintMethodSource) { + // item.condition.accept(conditionEvaluator) + // } + // } + // + // TODO() + // } + // } + protected abstract fun generateDescriptionForSink(sink: IfdsVertex): VulnerabilityDescription override fun handleNewEdge(edge: IfdsEdge): List = buildList { - if (edge.v.domainFact in sinks(edge.v.statement)) { - val desc = generateDescriptionForSink(edge.v) - add(NewSummaryFact(VulnerabilityLocation(desc, edge.v))) - verticesWithTraceGraphNeeded.add(edge.v) + val configOk = run { + val callExpr = edge.to.statement.callExpr ?: return@run false + val callee = callExpr.method.method + + val config = (flowFunctions as TaintForwardFunctions) + .taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $callee" } + feature.getConfigForMethod(callee) + } ?: return@run false + + // TODO: not always we want to skip sinks on ZeroFacts. Some rules might have ConstantTrue or just true (when evaluated with ZeroFact) condition. + if (edge.to.domainFact !is TaintNode) { + return@run false + } + + // Determine whether 'edge.to' is a sink via config: + val conditionEvaluator = FactAwareConditionEvaluator( + Tainted(edge.to.domainFact), + CallPositionToJcValueResolver(edge.to.statement), + ) + var isSink = false + var triggeredItem: TaintMethodSink? = null + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + isSink = true + triggeredItem = item + break + } + // FIXME: unconditionally let it be the sink. + // isSink = true + // triggeredItem = item + // break + } + if (isSink) { + val desc = generateDescriptionForSink(edge.to) + val vulnerability = VulnerabilityLocation(desc, edge.to, edge, rule = triggeredItem) + logger.info { "Found sink: $vulnerability in ${vulnerability.method}" } + add(NewSummaryFact(vulnerability)) + verticesWithTraceGraphNeeded.add(edge.to) + } + true + } + + if (!configOk) { + // "config"-less behavior: + if (edge.to.domainFact in sinks(edge.to.statement)) { + val desc = generateDescriptionForSink(edge.to) + val vulnerability = VulnerabilityLocation(desc, edge.to, edge) + logger.info { "Found sink: $vulnerability in ${vulnerability.method}" } + add(NewSummaryFact(vulnerability)) + verticesWithTraceGraphNeeded.add(edge.to) + } } + + // "Default" behavior: + addAll(super.handleNewEdge(edge)) } } abstract class TaintBackwardAnalyzer( - val graph: JcApplicationGraph, - maxPathLength: Int + graph: JcApplicationGraph, + maxPathLength: Int, ) : AbstractAnalyzer(graph) { abstract val generates: (JcInst) -> List abstract val sinks: (JcInst) -> List @@ -127,8 +218,8 @@ abstract class TaintBackwardAnalyzer( } override fun handleNewEdge(edge: IfdsEdge): List = buildList { - if (edge.v.statement in graph.exitPoints(edge.method)) { - add(EdgeForOtherRunnerQuery(IfdsEdge(edge.v, edge.v))) + if (edge.to.statement in graph.exitPoints(edge.method)) { + add(EdgeForOtherRunnerQuery(IfdsEdge(edge.to, edge.to))) } } } @@ -137,9 +228,16 @@ private class TaintForwardFunctions( graph: JcApplicationGraph, private val maxPathLength: Int, private val generates: (JcInst) -> List, - private val sanitizes: (JcExpr, TaintNode) -> Boolean + private val sanitizes: (JcExpr, TaintNode) -> Boolean, ) : AbstractTaintForwardFunctions(graph.classpath) { - override fun transmitDataFlow(from: JcExpr, to: JcValue, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List { + + override fun transmitDataFlow( + from: JcExpr, + to: JcValue, + atInst: JcInst, + fact: DomainFact, + dropFact: Boolean, + ): List { if (fact == ZEROFact) { return listOf(ZEROFact) + generates(atInst) } @@ -148,7 +246,10 @@ private class TaintForwardFunctions( return emptyList() } - val default = if (dropFact || (sanitizes(from, fact) && fact.variable == (from as? JcInstanceCallExpr)?.instance?.toPath())) { + val default: List = if ( + dropFact || + (sanitizes(from, fact) && fact.variable == (from as? JcInstanceCallExpr)?.instance?.toPath()) + ) { emptyList() } else { listOf(fact) @@ -168,7 +269,12 @@ private class TaintForwardFunctions( } } - if (from.values.any { it.toPathOrNull().startsWith(fact.variable) || fact.variable.startsWith(it.toPathOrNull()) }) { + if ( + from.values.any { + it.toPathOrNull().startsWith(fact.variable) || + fact.variable.startsWith(it.toPathOrNull()) + } + ) { val instanceOrNull = (from as? JcInstanceCallExpr)?.instance if (instanceOrNull != null && !sanitizes(from, fact)) { val instancePath = instanceOrNull.toPathOrNull() @@ -183,50 +289,123 @@ private class TaintForwardFunctions( return default } - override fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List { + override fun transmitDataFlowAtNormalInst( + inst: JcInst, + nextInst: JcInst, // unused + fact: DomainFact, + ): List { + // Generate new facts: if (fact == ZEROFact) { - return generates(inst) + listOf(ZEROFact) + return listOf(ZEROFact) + generates(inst) } if (fact !is TaintNode) { return emptyList() } + // Pass-through: val callExpr = inst.callExpr ?: return listOf(fact) - val instance = (callExpr as? JcInstanceCallExpr)?.instance ?: return listOf(fact) - val factIsPassed = callExpr.values.any { - it.toPathOrNull().startsWith(fact.variable) || fact.variable.startsWith(it.toPathOrNull()) + if (callExpr !is JcInstanceCallExpr) { + return listOf(fact) } + val instance = callExpr.instance + // Sanitize: if (instance.toPath() == fact.variable && sanitizes(callExpr, fact)) { return emptyList() } - return if (factIsPassed && !sanitizes(callExpr, fact)) { - listOf(fact) + fact.moveToOtherPath(instance.toPath()) - } else { - listOf(fact) - } + // TODO: do no do this: + // val factIsPassed = callExpr.values.any { + // it.toPathOrNull().startsWith(fact.variable) || fact.variable.startsWith(it.toPathOrNull()) + // } + // return if (factIsPassed && !sanitizes(callExpr, fact)) { + // // Pass-through, but also (?) taint the 'instance' + // listOf(fact) + fact.moveToOtherPath(instance.toPath()) + // } else { + // // Pass-through + // listOf(fact) + // } + + // Pass-through + return listOf(fact) } - override fun obtainPossibleStartFacts(startStatement: JcInst): Collection { - val method = startStatement.location.method + override fun obtainPossibleStartFacts(startStatement: JcInst): List = buildList { + add(ZEROFact) - // Possibly null arguments - return listOf(ZEROFact) + method.flowGraph().locals - .filterIsInstance() - .map { TaintAnalysisNode(AccessPath.fromLocal(it)) } + val method = startStatement.location.method + val config = taintConfigurationFeature?.let { feature -> + logger.trace { "Extracting config for $method" } + feature.getConfigForMethod(method) + } + if (config != null) { + val conditionEvaluator = BasicConditionEvaluator { position -> + when (position) { + This -> method.thisInstance + + is Argument -> method.flowGraph().locals + .filterIsInstance() + .singleOrNull { it.index == position.index } + ?: error("Cannot resolve $position for $method") + + AnyArgument -> error("Unexpected $position") + Result -> error("Unexpected $position") + ResultAnyElement -> error("Unexpected $position") + } + } + val actionEvaluator = TaintActionEvaluator { position -> + when (position) { + This -> method.thisInstance.toPathOrNull() + ?: error("Cannot resolve $position for $method") + + is Argument -> { + val p = method.parameters[position.index] + val t = cp.findTypeOrNull(p.type) + if (t != null) { + JcArgument.of(p.index, p.name, t).toPathOrNull() + } else { + null + } + ?: error("Cannot resolve $position for $method") + } + + AnyArgument -> error("Unexpected $position") + Result -> error("Unexpected $position") + ResultAnyElement -> error("Unexpected $position") + } + } + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + when (action) { + is AssignMark -> { + add(actionEvaluator.evaluate(action).toDomainFact()) + } + + else -> error("$action is not supported for $item") + } + } + } + } + } } } - private class TaintBackwardFunctions( graph: JcApplicationGraph, val generates: (JcInst) -> List, val sinks: (JcInst) -> List, maxPathLength: Int, ) : AbstractTaintBackwardFunctions(graph, maxPathLength) { - override fun transmitBackDataFlow(from: JcValue, to: JcExpr, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List { + + override fun transmitBackDataFlow( + from: JcValue, + to: JcExpr, + atInst: JcInst, + fact: DomainFact, + dropFact: Boolean, + ): List { if (fact == ZEROFact) { return listOf(ZEROFact) + sinks(atInst) } @@ -243,15 +422,20 @@ private class TaintBackwardFunctions( if (toPath != null) { val diff = factPath.minus(fromPath) if (diff != null) { - return listOf(fact.moveToOtherPath(AccessPath.fromOther(toPath, diff).limit(maxPathLength))) + val newPath = (toPath / diff).limit(maxPathLength) + return listOf(fact.moveToOtherPath(newPath)) } } else if (factPath.startsWith(fromPath) || (to is JcInstanceCallExpr && factPath.startsWith(to.instance.toPath()))) { - return to.values.mapNotNull { it.toPathOrNull() }.map { TaintAnalysisNode(it) } + return to.values.mapNotNull { it.toPathOrNull() }.map { TaintAnalysisNode(it, nodeType = "TAINT") } } return default } - override fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List { + override fun transmitDataFlowAtNormalInst( + inst: JcInst, + nextInst: JcInst, + fact: DomainFact, + ): List { if (fact == ZEROFact) { return listOf(fact) + sinks(inst) } @@ -261,9 +445,9 @@ private class TaintBackwardFunctions( val callExpr = inst.callExpr as? JcInstanceCallExpr ?: return listOf(fact) if (fact.variable.startsWith(callExpr.instance.toPath())) { - return inst.values.mapNotNull { it.toPathOrNull() }.map { TaintAnalysisNode(it) } + return inst.values.mapNotNull { it.toPathOrNull() }.map { TaintAnalysisNode(it, nodeType = "TAINT") } } return listOf(fact) } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt index fcb815c6d..ad8bbaaa5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt @@ -17,6 +17,7 @@ package org.jacodb.analysis.library.analyzers import org.jacodb.analysis.engine.DomainFact +import org.jacodb.analysis.ifds2.taint.Tainted import org.jacodb.analysis.paths.AccessPath import org.jacodb.api.cfg.JcInst @@ -27,8 +28,11 @@ import org.jacodb.api.cfg.JcInst * @property activation is the activation point, as described in ARF14. Null value means that activation point was * passed (so, for analyses that do not use backward runner to taint aliases, [activation] will always be null). */ -abstract class TaintNode(val variable: AccessPath, val activation: JcInst? = null): DomainFact { - protected abstract val nodeType: String +abstract class TaintNode( + val variable: AccessPath, + val activation: JcInst? = null, +) : DomainFact { + internal abstract val nodeType: String abstract fun updateActivation(newActivation: JcInst?): TaintNode @@ -38,7 +42,11 @@ abstract class TaintNode(val variable: AccessPath, val activation: JcInst? = nul get() = updateActivation(null) override fun toString(): String { - return "[$nodeType]: $variable, activation point=$activation" + return if (activation != null) { + "[$nodeType]: $variable, activation=$activation" + } else { + "[$nodeType]: $variable" + } } override fun equals(other: Any?): Boolean { @@ -60,7 +68,10 @@ abstract class TaintNode(val variable: AccessPath, val activation: JcInst? = nul } } -class NpeTaintNode(variable: AccessPath, activation: JcInst? = null): TaintNode(variable, activation) { +class NpeTaintNode( + variable: AccessPath, + activation: JcInst? = null, +) : TaintNode(variable, activation) { override val nodeType: String get() = "NPE" @@ -68,22 +79,29 @@ class NpeTaintNode(variable: AccessPath, activation: JcInst? = null): TaintNode( return NpeTaintNode(variable, newActivation) } - override fun moveToOtherPath(newPath: AccessPath): TaintNode { + override fun moveToOtherPath(newPath: AccessPath): NpeTaintNode { return NpeTaintNode(newPath, activation) } } -data class UnusedVariableNode(val variable: AccessPath, val initStatement: JcInst): DomainFact +data class UnusedVariableNode( + val variable: AccessPath, + val initStatement: JcInst, +) : DomainFact -class TaintAnalysisNode(variable: AccessPath, activation: JcInst? = null): TaintNode(variable, activation) { - override val nodeType: String - get() = "Taint analysis" +class TaintAnalysisNode( + variable: AccessPath, + activation: JcInst? = null, + override val nodeType: String, // = "Taint analysis" +) : TaintNode(variable, activation) { + + constructor(fact: Tainted) : this(fact.variable, nodeType = fact.mark.name) override fun updateActivation(newActivation: JcInst?): TaintAnalysisNode { - return TaintAnalysisNode(variable, newActivation) + return TaintAnalysisNode(variable, newActivation, nodeType) } - override fun moveToOtherPath(newPath: AccessPath): TaintNode { - return TaintAnalysisNode(newPath, activation) + override fun moveToOtherPath(newPath: AccessPath): TaintAnalysisNode { + return TaintAnalysisNode(newPath, activation, nodeType) } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt index 2b7086c9a..beae4a2ec 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt @@ -48,8 +48,7 @@ import org.jacodb.api.cfg.JcTerminatingInst import org.jacodb.api.cfg.values import org.jacodb.api.ext.cfg.callExpr - -class UnusedVariableAnalyzer(val graph: JcApplicationGraph) : AbstractAnalyzer(graph) { +class UnusedVariableAnalyzer(graph: JcApplicationGraph) : AbstractAnalyzer(graph) { override val flowFunctions: FlowFunctionsSpace = UnusedVariableForwardFunctions(graph.classpath) override val isMainAnalyzer: Boolean @@ -78,7 +77,7 @@ class UnusedVariableAnalyzer(val graph: JcApplicationGraph) : AbstractAnalyzer(g return isUsedAt(callExpr) } if (inst is JcAssignInst) { - if (inst.lhv is JcArrayAccess && isUsedAt((inst.lhv as JcArrayAccess))) { + if (inst.lhv is JcArrayAccess && isUsedAt(inst.lhv as JcArrayAccess)) { return true } return isUsedAt(inst.rhv) && (inst.lhv !is JcLocal || inst.rhv !is JcLocal) @@ -119,7 +118,7 @@ val UnusedVariableAnalyzerFactory = AnalyzerFactory { graph -> } private class UnusedVariableForwardFunctions( - val classpath: JcClasspath + val classpath: JcClasspath, ) : FlowFunctionsSpace { override fun obtainPossibleStartFacts(startStatement: JcInst): Collection { @@ -153,21 +152,22 @@ private class UnusedVariableForwardFunctions( } if (fromPath == fact.variable) { - return@FlowFunctionInstance default.plus(UnusedVariableNode(toPath, fact.initStatement)) + return@FlowFunctionInstance default + UnusedVariableNode(toPath, fact.initStatement) } + default } override fun obtainCallToStartFlowFunction(callStatement: JcInst, callee: JcMethod) = FlowFunctionInstance { fact -> val callExpr = callStatement.callExpr ?: error("Call expr is expected to be not-null") - val formalParams = classpath.getFormalParamsOf(callee) + val formalParams = classpath.getArgumentsOf(callee) if (fact == ZEROFact) { // We don't show unused parameters for virtual calls if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { return@FlowFunctionInstance listOf(ZEROFact) } - return@FlowFunctionInstance formalParams.map { UnusedVariableNode(it.toPath(), callStatement) }.plus(ZEROFact) + return@FlowFunctionInstance formalParams.map { UnusedVariableNode(it.toPath(), callStatement) } + ZEROFact } if (fact !is UnusedVariableNode) { @@ -177,13 +177,13 @@ private class UnusedVariableForwardFunctions( emptyList() } - override fun obtainCallToReturnFlowFunction(callStatement: JcInst, returnSite: JcInst) = + override fun obtainCallToReturnFlowFunction(callStatement: JcInst, returnSite: JcInst, graph: JcApplicationGraph) = obtainSequentFlowFunction(callStatement, returnSite) override fun obtainExitToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, - exitStatement: JcInst + exitStatement: JcInst, ) = FlowFunctionInstance { fact -> if (fact == ZEROFact) { listOf(ZEROFact) @@ -191,4 +191,4 @@ private class UnusedVariableForwardFunctions( emptyList() } } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt index ce5b20210..82cdd575b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt @@ -21,6 +21,7 @@ import org.jacodb.analysis.paths.minus import org.jacodb.analysis.paths.startsWith import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod +import org.jacodb.api.JcParameter import org.jacodb.api.cfg.JcArgument import org.jacodb.api.cfg.JcThis import org.jacodb.api.ext.toType @@ -28,13 +29,22 @@ import org.jacodb.api.ext.toType val JcMethod.thisInstance: JcThis get() = JcThis(enclosingClass.toType()) -fun JcClasspath.getFormalParamsOf(method: JcMethod): List { - return method.parameters.map { - JcArgument.of(it.index, it.name, findTypeOrNull(it.type.typeName)!!) - } +fun JcClasspath.getArgument(param: JcParameter): JcArgument? { + val t = findTypeOrNull(param.type.typeName) ?: return null + return JcArgument.of(param.index, param.name, t) +} + +fun JcClasspath.getArgumentsOf(method: JcMethod): List { + return method.parameters.map { getArgument(it)!! } } -fun normalFactFlow(fact: TaintNode, fromPath: AccessPath, toPath: AccessPath, dropFact: Boolean, maxPathLength: Int): List { +fun normalFactFlow( + fact: TaintNode, + fromPath: AccessPath, + toPath: AccessPath, + dropFact: Boolean, + maxPathLength: Int, +): List { val factPath = fact.variable val default = if (dropFact) emptyList() else listOf(fact) @@ -42,8 +52,9 @@ fun normalFactFlow(fact: TaintNode, fromPath: AccessPath, toPath: AccessPath, dr // #AnalysisTest.`dereferencing copy of value saved before null assignment produce no npe` val diff = factPath.minus(fromPath) if (diff != null && (fact.activation == null || fromPath != factPath)) { + val newPath = (toPath / diff).limit(maxPathLength) return default - .plus(fact.moveToOtherPath(AccessPath.fromOther(toPath, diff).limit(maxPathLength))) + .plus(fact.moveToOtherPath(newPath)) .distinct() } @@ -52,4 +63,4 @@ fun normalFactFlow(fact: TaintNode, fromPath: AccessPath, toPath: AccessPath, dr } return default -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt index 4ba003ea8..e8351f90c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt @@ -17,47 +17,67 @@ package org.jacodb.analysis.paths import org.jacodb.api.JcField -import org.jacodb.api.cfg.JcLocal +import org.jacodb.api.JcTypedField +import org.jacodb.api.cfg.JcSimpleValue /** * This class is used to represent an access path that is needed for problems * where dataflow facts could be correlated with variables/values (such as NPE, uninitialized variable, etc.) */ -data class AccessPath private constructor(val value: JcLocal?, val accesses: List) { - companion object { +data class AccessPath private constructor( + val value: JcSimpleValue?, // null for static field + val accesses: List, +) { + init { + if (value == null) { + require(accesses.isNotEmpty()) + val a = accesses[0] + require(a is FieldAccessor) + require(a.field.isStatic) + } + } - fun fromLocal(value: JcLocal) = AccessPath(value, listOf()) + val isOnHeap: Boolean + get() = accesses.isNotEmpty() - fun fromStaticField(field: JcField): AccessPath { - if (!field.isStatic) { - throw IllegalArgumentException("Expected static field") - } + val isStatic: Boolean + get() = value == null - return AccessPath(null, listOf(FieldAccessor(field))) - } + fun limit(n: Int): AccessPath = AccessPath(value, accesses.take(n)) - fun fromOther(other: AccessPath, accesses: List): AccessPath { - if (accesses.any { it is FieldAccessor && it.field.isStatic }) { - throw IllegalArgumentException("Unexpected static field") + operator fun div(accesses: List): AccessPath { + for (accessor in accesses) { + if (accessor is FieldAccessor && accessor.field.isStatic) { + throw IllegalArgumentException("Unexpected static field: ${accessor.field}") } + } + + return AccessPath(value, this.accesses + accesses) + } - return AccessPath(other.value, other.accesses.plus(accesses)) + operator fun div(accessor: Accessor): AccessPath { + if (accessor is FieldAccessor && accessor.field.isStatic) { + throw IllegalArgumentException("Unexpected static field: ${accessor.field}") } + + return AccessPath(value, this.accesses + accessor) } - fun limit(n: Int) = AccessPath(value, accesses.take(n)) + override fun toString(): String { + return value.toString() + accesses.joinToString("") { it.toSuffix() } + } - val isOnHeap: Boolean - get() = accesses.isNotEmpty() + companion object { + fun from(value: JcSimpleValue): AccessPath = AccessPath(value, emptyList()) - val isStatic: Boolean - get() = value == null + fun fromStaticField(field: JcField): AccessPath { + require(field.isStatic) { "Expected static field" } + return AccessPath(null, listOf(FieldAccessor(field))) + } - override fun toString(): String { - var str = value.toString() - for (accessor in accesses) { - str += ".$accessor" + fun fromStaticField(field: JcTypedField): AccessPath { + require(field.isStatic) { "Expected static field" } + return AccessPath(null, listOf(FieldAccessor(field.field))) } - return str } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt index 10956d45d..4b6f5b366 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt @@ -17,17 +17,44 @@ package org.jacodb.analysis.paths import org.jacodb.api.JcField +import org.jacodb.api.cfg.JcValue -sealed interface Accessor +sealed interface Accessor { + fun toSuffix(): String +} + +data class FieldAccessor( + val field: JcField, +) : Accessor { + override fun toSuffix(): String { + return ".${field.name}" + } -data class FieldAccessor(val field: JcField) : Accessor { override fun toString(): String { return field.name } } +// data class ElementAccessor( +// val index: JcValue?, // null if "any" +// ) : Accessor { +// override fun toSuffix(): String { +// return if (index == null) "[*]" else "[$index]" +// } +// +// override fun toString(): String { +// return "[$index]" +// } +// } + object ElementAccessor : Accessor { + override fun toSuffix(): String { + return "[*]" + } + override fun toString(): String { return "*" } -} \ No newline at end of file +} + +fun ElementAccessor(index: JcValue?) = ElementAccessor diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt index 139d23bb5..f0bf44e7f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt @@ -23,50 +23,68 @@ import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstanceCallExpr import org.jacodb.api.cfg.JcLengthExpr -import org.jacodb.api.cfg.JcLocal +import org.jacodb.api.cfg.JcSimpleValue import org.jacodb.api.cfg.JcValue import org.jacodb.api.cfg.values +/** + * Converts `JcExpr` (in particular, `JcValue`) to `AccessPath`. + * - For `JcSimpleValue`, this method simply wraps the value. + * - For `JcArrayAccess` and `JcFieldRef`, this method "reverses" it and recursively constructs a list of accessors (`ElementAccessor` for array access, `FieldAccessor` for field access). + * - Returns `null` when the conversion to `AccessPath` is not possible. + * + * Example: + * `x.f[0].y` is `AccessPath(value = x, accesses = [Field(f), Element(0), Field(y)])` + */ internal fun JcExpr.toPathOrNull(): AccessPath? { - if (this is JcCastExpr) { - return operand.toPathOrNull() - } - if (this is JcLocal) { - return AccessPath.fromLocal(this) - } + return when (this) { + is JcSimpleValue -> { + AccessPath.from(this) + } - if (this is JcArrayAccess) { - return array.toPathOrNull()?.let { - AccessPath.fromOther(it, listOf(ElementAccessor)) + is JcCastExpr -> { + operand.toPathOrNull() + } + + is JcArrayAccess -> { + array.toPathOrNull()?.let { + it / listOf(ElementAccessor(index)) + } } - } - if (this is JcFieldRef) { - val instance = instance // enables smart cast + is JcFieldRef -> { + val instance = instance // enables smart cast - return if (instance == null) { - AccessPath.fromStaticField(field.field) - } else { - instance.toPathOrNull()?.let { - AccessPath.fromOther(it, listOf(FieldAccessor(field.field))) + if (instance == null) { + AccessPath.fromStaticField(field.field) + } else { + instance.toPathOrNull()?.let { it / FieldAccessor(field.field) } } } + + else -> null } - return null } internal fun JcValue.toPath(): AccessPath { return toPathOrNull() ?: error("Unable to build access path for value $this") } -internal fun AccessPath?.minus(other: AccessPath): List? { +// this = value.x.y[0] +// this = (value, accesses = listOf(Field, Field, Element)) +// +// other = value.x +// other = (value, accesses = listOf(Field)) +// +// (this - other) = listOf(Field, Element) = ".y[0]" +internal operator fun AccessPath?.minus(other: AccessPath): List? { if (this == null) { return null } if (value != other.value) { return null } - if (accesses.take(other.accesses.size) != other.accesses) { + if (this.accesses.take(other.accesses.size) != other.accesses) { return null } @@ -77,8 +95,14 @@ internal fun AccessPath?.startsWith(other: AccessPath?): Boolean { if (this == null || other == null) { return false } - - return minus(other) != null + if (this.value != other.value) { + return false + } + // Unnecessary check: + // if (this.accesses.size < other.accesses.size) { + // return false + // } + return this.accesses.take(other.accesses.size) == other.accesses } fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { @@ -86,15 +110,15 @@ fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { return false } - (expr as? JcInstanceCallExpr)?.let { - val instancePath = it.instance.toPathOrNull() + if (expr is JcInstanceCallExpr) { + val instancePath = expr.instance.toPathOrNull() if (instancePath.startsWith(this)) { return true } } - (expr as? JcLengthExpr)?.let { - val arrayPath = it.array.toPathOrNull() + if (expr is JcLengthExpr) { + val arrayPath = expr.array.toPathOrNull() if (arrayPath.startsWith(this)) { return true } @@ -103,7 +127,7 @@ fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { return expr.values .mapNotNull { it.toPathOrNull() } .any { - it.minus(this)?.isNotEmpty() == true + (it - this)?.isNotEmpty() == true } } @@ -113,4 +137,28 @@ fun AccessPath?.isDereferencedAt(inst: JcInst): Boolean { } return inst.operands.any { isDereferencedAt(it) } -} \ No newline at end of file +} + +internal fun JcValue.isTaintedWith(fact: JcValue): Boolean { + if (this == fact) { + return true + } + + return when (this) { + is JcFieldRef -> { + if (this.instance != null) { + this.instance!!.isTaintedWith(fact) + } else { + // static field + // TODO: ? + false + } + } + + is JcArrayAccess -> { + this.array.isTaintedWith(fact) + } + + else -> false + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt index 9a6e43201..035118275 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt @@ -46,7 +46,10 @@ data class SarifPhysicalLocation(val artifactLocation: SarifArtifactLocation, va data class SarifLogicalLocation(val fullyQualifiedName: String) @Serializable -data class SarifLocation(val physicalLocation: SarifPhysicalLocation, val logicalLocations: List) { +data class SarifLocation( + val physicalLocation: SarifPhysicalLocation, + val logicalLocations: List, +) { companion object { private val JcMethod.fullyQualifiedName: String get() = "${enclosingClass.name}#${name}" @@ -98,13 +101,13 @@ data class SarifResult( val message: SarifMessage, val level: SarifSeverityLevel, val locations: List, - val codeFlows: List + val codeFlows: List, ) { companion object { fun fromVulnerabilityInstance(instance: VulnerabilityInstance, maxPathsCount: Int): SarifResult = SarifResult( - instance.vulnerabilityDescription.ruleId, - instance.vulnerabilityDescription.message, - instance.vulnerabilityDescription.level, + instance.location.vulnerabilityDescription.ruleId, + instance.location.vulnerabilityDescription.message, + instance.location.vulnerabilityDescription.level, listOf(SarifLocation.fromInst(instance.traceGraph.sink.statement)), instance.traceGraph.getAllTraces().take(maxPathsCount).map { SarifCodeFlow.fromTrace(it) }.toList() ) @@ -140,7 +143,7 @@ data class SarifReport( @SerialName("\$schema") val schema: String, - val runs: List + val runs: List, ) { fun encodeToStream(stream: OutputStream) { json.encodeToStream(this, stream) @@ -161,7 +164,7 @@ data class SarifReport( fun fromVulnerabilities( vulnerabilities: List, - pathsCount: Int = defaultPathsCount + pathsCount: Int = defaultPathsCount, ): SarifReport = SarifReport( version = defaultVersion, schema = defaultSchema, @@ -177,13 +180,20 @@ data class SarifReport( @Serializable enum class SarifSeverityLevel { - @SerialName("error") ERROR, - @SerialName("warning") WARNING, - @SerialName("note") NOTE + @SerialName("error") + ERROR, + + @SerialName("warning") + WARNING, + + @SerialName("note") + NOTE } data class VulnerabilityDescription( val message: SarifMessage, val ruleId: String, - val level: SarifSeverityLevel = SarifSeverityLevel.WARNING -) \ No newline at end of file + val level: SarifSeverityLevel = SarifSeverityLevel.WARNING, + // TODO: don't miss the triggered rule! + // TODO: add val rule: Rule, +) diff --git a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java index 5f5199df2..6e9c59625 100644 --- a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java +++ b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java @@ -19,9 +19,9 @@ import org.jacodb.analysis.AnalysisMain; import org.jacodb.analysis.engine.IfdsUnitRunnerFactory; import org.jacodb.analysis.engine.UnitResolver; +import org.jacodb.analysis.engine.UnitResolverKt; import org.jacodb.analysis.graph.ApplicationGraphFactory; import org.jacodb.analysis.library.RunnersLibrary; -import org.jacodb.analysis.library.UnitResolversLibrary; import org.jacodb.api.JcClassOrInterface; import org.jacodb.api.JcClasspath; import org.jacodb.api.JcDatabase; @@ -56,9 +56,9 @@ public void testJavaAnalysisApi() throws ExecutionException, InterruptedExceptio List methodsToAnalyze = analyzedClass.getDeclaredMethods(); JcApplicationGraph applicationGraph = ApplicationGraphFactory - .asyncNewApplicationGraphForAnalysis(classpath, null) + .newApplicationGraphForAnalysisAsync(classpath, null) .get(); - UnitResolver resolver = UnitResolversLibrary.getMethodUnitResolver(); + UnitResolver resolver = UnitResolverKt.getMethodUnitResolver(); IfdsUnitRunnerFactory runner = RunnersLibrary.getUnusedVariableRunnerFactory(); AnalysisMain.runAnalysis( @@ -76,8 +76,8 @@ public void testCustomBannedPackagesApi() throws ExecutionException, Interrupted bannedPackages.add("my.package.that.wont.be.analyzed"); JcApplicationGraph customGraph = ApplicationGraphFactory - .asyncNewApplicationGraphForAnalysis(classpath, bannedPackages) + .newApplicationGraphForAnalysisAsync(classpath, bannedPackages) .get(); Assertions.assertNotNull(customGraph); } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt index f090469c2..e4ff210fc 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt @@ -17,9 +17,9 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.MethodUnitResolver import org.jacodb.analysis.graph.defaultBannedPackagePrefixes import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.MethodUnitResolver import org.jacodb.analysis.library.analyzers.TaintAnalysisNode import org.jacodb.analysis.library.analyzers.TaintNode import org.jacodb.analysis.library.newAliasRunnerFactory @@ -130,7 +130,7 @@ class AliasAnalysisTest : BaseTest() { inst.callExpr?.method?.name == "taint" && inst.callExpr?.method?.method?.enclosingClass?.simpleName == "Benchmark" ) { - listOf(TaintAnalysisNode(inst.lhv.toPath())) + listOf(TaintAnalysisNode(inst.lhv.toPath(), nodeType = "TAINT")) } else { emptyList() } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index 3e4800614..db29cf497 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -16,15 +16,17 @@ package org.jacodb.analysis.impl +import io.github.oshai.kotlinlogging.KotlinLogging import juliet.support.AbstractTestCase import kotlinx.coroutines.runBlocking import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods import org.jacodb.impl.features.classpaths.UnknownClasses import org.jacodb.impl.features.hierarchyExt +import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB import org.jacodb.testing.allClasspath @@ -34,25 +36,32 @@ import org.junit.jupiter.params.provider.Arguments import java.util.stream.Stream import kotlin.streams.asStream +private val logger = KotlinLogging.logger {} + abstract class BaseAnalysisTest : BaseTest() { companion object : WithGlobalDB(UnknownClasses) { - @JvmStatic - fun provideClassesForJuliet(cweNum: Int, cweSpecificBans: List = emptyList()): Stream = runBlocking { + fun getJulietClasses( + cweNum: Int, + cweSpecificBans: List = emptyList(), + ): Sequence = runBlocking { val cp = db.classpath(allClasspath) val hierarchyExt = cp.hierarchyExt() val baseClass = cp.findClass() - val classes = hierarchyExt.findSubClasses(baseClass, false) - classes.toArguments("CWE${cweNum}_", cweSpecificBans) + hierarchyExt.findSubClasses(baseClass, false) + .map { it.name } + .filter { it.contains("CWE${cweNum}_") } + .filterNot { className -> (commonJulietBans + cweSpecificBans).any { className.contains(it) } } + .sorted() } - private fun Sequence.toArguments(cwe: String, cweSpecificBans: List): Stream = this - .map { it.name } - .filter { it.contains(cwe) } - .filterNot { className -> (commonJulietBans + cweSpecificBans).any { className.contains(it) } } -// .filter { it.contains("_68") } - .sorted() - .map { Arguments.of(it) } - .asStream() + @JvmStatic + fun provideClassesForJuliet( + cweNum: Int, + cweSpecificBans: List = emptyList(), + ): Stream = + getJulietClasses(cweNum, cweSpecificBans) + .map { Arguments.of(it) } + .asStream() private val commonJulietBans = listOf( // TODO: containers not supported @@ -75,6 +84,19 @@ abstract class BaseAnalysisTest : BaseTest() { ) } + override val cp: JcClasspath = runBlocking { + val configPath = "config_small.json" + val defaultConfigResource = this.javaClass.getResourceAsStream("/$configPath") + if (defaultConfigResource != null) { + logger.info { "Loading '$configPath'..." } + val configJson = defaultConfigResource.bufferedReader().readText() + val configurationFeature = TaintConfigurationFeature.fromJson(configJson) + db.classpath(allClasspath, listOf(configurationFeature) + BaseAnalysisTest.classpathFeatures) + } else { + super.cp + } + } + protected abstract fun launchAnalysis(methods: List): List protected inline fun testOneAnalysisOnOneMethod( @@ -88,29 +110,41 @@ abstract class BaseAnalysisTest : BaseTest() { // TODO: think about better assertions here assertEquals(expectedLocations.size, sinks.size) expectedLocations.forEach { expected -> - assertTrue(sinks.any { it.contains(expected) }) { - "$expected unmatched in:\n${sinks.joinToString("\n")}" + val sinksRepresentations = sinks.map { it.traceGraph.sink.toString() } + assertTrue(sinksRepresentations.any { it.contains(expected) }) { + "$expected unmatched in:\n${sinksRepresentations.joinToString("\n")}" } } } protected fun testSingleJulietClass(vulnerabilityType: String, className: String) { + println(className) + val clazz = cp.findClass(className) - val goodMethod = clazz.methods.single { it.name == "good" } val badMethod = clazz.methods.single { it.name == "bad" } + val goodMethod = clazz.methods.single { it.name == "good" } - val goodIssues = findSinks(goodMethod, vulnerabilityType) val badIssues = findSinks(badMethod, vulnerabilityType) + logger.info { "badIssues: ${badIssues.size} total" } + for (issue in badIssues) { + logger.debug { " - $issue" } + } + assertTrue(badIssues.isNotEmpty()) { "Must find some sinks in 'bad' for $className" } - assertTrue(goodIssues.isEmpty()) - assertTrue(badIssues.isNotEmpty()) + val goodIssues = findSinks(goodMethod, vulnerabilityType) + logger.info { "goodIssues: ${goodIssues.size} total" } + for (issue in goodIssues) { + logger.debug { " - $issue" } + } + assertTrue(goodIssues.isEmpty()) { "Must NOT find any sinks in 'good' for $className" } } - protected fun findSinks(method: JcMethod, vulnerabilityType: String): Set { - val sinks = launchAnalysis(listOf(method)) - .filter { it.vulnerabilityDescription.ruleId == vulnerabilityType } - .map { it.traceGraph.sink.toString() } + protected fun findSinks(method: JcMethod, vulnerabilityType: String): Set { + val vulnerabilities = launchAnalysis(listOf(method)) + val sinks = vulnerabilities + .filter { it.location.vulnerabilityDescription.ruleId == vulnerabilityType } + // .map { it.traceGraph.sink.toString() } return sinks.toSet() } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt new file mode 100644 index 000000000..88d339f48 --- /dev/null +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt @@ -0,0 +1,315 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalTime::class, ExperimentalTime::class) + +package org.jacodb.analysis.impl + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.PackageUnitResolver +import org.jacodb.analysis.engine.SingletonUnit +import org.jacodb.analysis.engine.UnitResolver +import org.jacodb.analysis.engine.UnknownUnit +import org.jacodb.analysis.graph.newApplicationGraphForAnalysis +import org.jacodb.analysis.ifds2.taint.runTaintAnalysis +import org.jacodb.api.JcByteCodeLocation +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcDatabase +import org.jacodb.api.JcMethod +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.packageName +import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages +import org.jacodb.impl.features.classpaths.JcUnknownClass +import org.jacodb.impl.features.classpaths.UnknownClasses +import org.jacodb.impl.jacodb +import org.jacodb.taint.configuration.TaintConfigurationFeature +import java.io.File +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.Path +import kotlin.io.path.PathWalkOption +import kotlin.io.path.div +import kotlin.io.path.extension +import kotlin.io.path.walk +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource.Monotonic.markNow + +private val logger = KotlinLogging.logger {} + +object WebGoatBenchRunner { + private fun loadWebGoatBench(): BenchCp { + val webGoatDir = Path(object {}.javaClass.getResource("/webgoat")!!.path) + return loadWebAppBenchCp( + classes = webGoatDir / "classes", + dependencies = webGoatDir / "deps", + unitResolver = PackageUnitResolver + ).apply { + entrypointFilter = { method -> + if (method.enclosingClass.packageName.startsWith("org.owasp.webgoat.lessons")) { + true + } else { + false + } + } + } + } + + @JvmStatic + fun main(args: Array) { + val timeStart = markNow() + val bench = loadWebGoatBench() + bench.use { it.analyze() } + logger.info { "All done in %.1fs".format(timeStart.elapsedNow().toDouble(DurationUnit.SECONDS)) } + } +} + +object OwaspBenchRunner { + private fun loadOwaspJavaBench(): BenchCp { + val owaspJavaPath = Path(object {}.javaClass.getResource("/owasp")!!.path) + val runAll = true // make it 'false' to test on specific method(s) only + + return loadWebAppBenchCp( + classes = owaspJavaPath / "classes", + dependencies = owaspJavaPath / "deps", + // unitResolver = PackageUnitResolver + // unitResolver = SingletonUnitResolver + unitResolver = { method -> + if (method.enclosingClass.packageName.startsWith("org.owasp.benchmark.")) { + // ClassUnit(generateSequence(method.enclosingClass) { it.outerClass }.last()) + SingletonUnit + } else { + UnknownUnit + } + + // if (method.enclosingClass.packageName in bannedPackages) { + // UnknownUnit + // } else { + // SingletonUnit + // } + } + ).apply { + entrypointFilter = { method -> + if (method.enclosingClass.packageName.startsWith("org.owasp.benchmark.testcode")) { + if (runAll) { + // All methods: + true + } else { + // Specific method: + val specificMethod = "BenchmarkTest00008" + // val specificMethod = "BenchmarkTest00018" + // val specificMethod = "BenchmarkTest00024" + // val specificMethod = "BenchmarkTest00025" + // val specificMethod = "BenchmarkTest00026" + // val specificMethod = "BenchmarkTest00027" + // val specificMethod = "BenchmarkTest00032" + // val specificMethod = "BenchmarkTest00033" + // val specificMethod = "BenchmarkTest00034" + // val specificMethod = "BenchmarkTest00037" + // val specificMethod = "BenchmarkTest00043" + // val specificMethod = "BenchmarkTest00105" + if (method.enclosingClass.simpleName == specificMethod) { + true + } else { + false + } + + // // Methods with specific annotation: + // // println("Annotations of $method: ${method.enclosingClass.annotations.map{it.name}}") + // method.enclosingClass.annotations.any { annotation -> + // if (annotation.name == "javax.servlet.annotation.WebServlet") { + // // println("$method has annotation ${annotation.name} with values ${annotation.values}") + // (annotation.values["value"]!! as List)[0].startsWith("/sqli-") + // } else { + // false + // } + // } + } + } else { + false + } + } + } + } + + @JvmStatic + fun main(args: Array) { + val timeStart = markNow() + val bench = loadOwaspJavaBench() + bench.use { it.analyze() } + logger.info { "All done in %.1fs".format(timeStart.elapsedNow().toDouble(DurationUnit.SECONDS)) } + } +} + +object ShopizerBenchRunner { + private fun loadShopizerBench(): BenchCp { + val shopizerPath = Path(object {}.javaClass.getResource("/shopizer")!!.path) + return loadWebAppBenchCp( + classes = shopizerPath / "classes", + dependencies = shopizerPath / "deps", + unitResolver = PackageUnitResolver + ).apply { + entrypointFilter = { true } + } + } + + @JvmStatic + fun main(args: Array) { + val timeStart = markNow() + val bench = loadShopizerBench() + bench.use { it.analyze() } + logger.info { "All done in %.1fs".format(timeStart.elapsedNow().toDouble(DurationUnit.SECONDS)) } + } +} + +private class BenchCp( + val cp: JcClasspath, + val db: JcDatabase, + val benchLocations: List, + val unitResolver: UnitResolver, + var reportFileName: String? = null, + var entrypointFilter: (JcMethod) -> Boolean = { true }, +) : AutoCloseable { + override fun close() { + cp.close() + db.close() + } +} + +private fun loadBenchCp( + classes: List, + dependencies: List, + unitResolver: UnitResolver, +): BenchCp = runBlocking { + val cpFiles = classes + dependencies + + val db = jacodb { + useProcessJavaRuntime() + installFeatures(InMemoryHierarchy, Usages) + loadByteCode(cpFiles) + persistent("/tmp/index.db") + } + db.awaitBackgroundJobs() + + // val taintConfigFileName = "defaultTaintConfig.json" + val taintConfigFileName = "config_big.json" + val defaultConfigResource = this.javaClass.getResourceAsStream("/$taintConfigFileName")!! + var configJson = defaultConfigResource.bufferedReader().readText() + val additionalConfigResource = this.javaClass.getResourceAsStream("/additional.json") + if (additionalConfigResource != null) { + val additionalConfigJson = additionalConfigResource.bufferedReader().readText() + val configJsonLines = configJson.lines().toMutableList() + if (configJsonLines.last().isEmpty()) { + configJsonLines.removeLast() + } + check(configJsonLines.last() == "]") + val additionalConfigJsonLines = additionalConfigJson.lines().toMutableList() + if (additionalConfigJsonLines.last().isEmpty()) { + additionalConfigJsonLines.removeLast() + } + check(additionalConfigJsonLines.first() == "[") + check(additionalConfigJsonLines.last() == "]") + if (additionalConfigJsonLines.size > 2) { + configJsonLines.removeLast() + configJsonLines[configJsonLines.size - 1] = configJsonLines[configJsonLines.size - 1] + "," + for (line in additionalConfigJsonLines.subList(1, additionalConfigJsonLines.size - 1)) { + configJsonLines.add(line) + } + configJsonLines.add("]") + } + configJson = configJsonLines.joinToString("\n") + } + val configurationFeature = TaintConfigurationFeature.fromJson(configJson) + val features = listOf(configurationFeature, UnknownClasses) + val cp = db.classpath(cpFiles, features) + val locations = cp.locations.filter { it.jarOrFolder in classes } + + BenchCp(cp, db, locations, unitResolver) +} + +@OptIn(ExperimentalPathApi::class) +private fun loadWebAppBenchCp( + classes: Path, + dependencies: Path, + unitResolver: UnitResolver, +): BenchCp = + loadBenchCp( + classes = listOf(classes.toFile()), + dependencies = dependencies + .walk(PathWalkOption.INCLUDE_DIRECTORIES) + .filter { it.extension == "jar" } + .map { it.toFile() } + .toList(), + unitResolver = unitResolver + ) + +private fun BenchCp.analyze() { + val useSpecificClass = false + val startMethods = if (useSpecificClass) { + val className = "org.owasp.benchmark.testcode.BenchmarkTest00043" + logger.info { "Analyzing '$className'" } + val clazz = cp.findClass(className) + clazz.publicAndProtectedMethods().toList() + } else { + logger.info { "Filtering start methods..." } + cp.publicClasses(benchLocations) + .flatMap { it.publicAndProtectedMethods() } + .filter(entrypointFilter) + .toList() + } + + logger.info { "Start the analysis with ${startMethods.size} start methods:" } + for (method in startMethods) { + logger.info { method } + } + analyzeTaint(cp, unitResolver, startMethods, reportFileName) + logger.info { "Done the analysis with ${startMethods.size} start methods" } +} + +private fun analyzeTaint( + cp: JcClasspath, + unitResolver: UnitResolver, + startMethods: List, + reportFileName: String? = null, +) { + val graph = runBlocking { + cp.newApplicationGraphForAnalysis() + } + runTaintAnalysis(graph, unitResolver, startMethods) + // val vulnerabilities = runAnalysis(graph, unitResolver, newSqlInjectionRunnerFactory(), startMethods) + // val report = SarifReport.fromVulnerabilities(vulnerabilities) + // File(reportFileName ?: "report.sarif").outputStream().use { fileOutputStream -> + // report.encodeToStream(fileOutputStream) + // } +} + +private fun JcClasspath.publicClasses(locations: List): Sequence = + locations + .asSequence() + .flatMap { it.classNames ?: emptySet() } + .mapNotNull { findClassOrNull(it) } + .filterNot { it is JcUnknownClass } + .filterNot { it.isAbstract || it.isInterface || it.isAnonymous } + +private fun JcClassOrInterface.publicAndProtectedMethods(): Sequence = + declaredMethods.asSequence() + .filter { it.instList.size > 0 } + // TODO: some sinks are NOT found when filtering out constructors here + // .filter { !it.isConstructor } + .filter { it.isPublic || it.isProtected } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt new file mode 100644 index 000000000..dcfaa6baf --- /dev/null +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt @@ -0,0 +1,294 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.impl + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.SingletonUnitResolver +import org.jacodb.analysis.graph.JcApplicationGraphImpl +import org.jacodb.analysis.graph.newApplicationGraphForAnalysis +import org.jacodb.analysis.ifds2.taint.Vulnerability +import org.jacodb.analysis.ifds2.taint.npe.runNpeAnalysis +import org.jacodb.analysis.impl.BaseAnalysisTest.Companion.provideClassesForJuliet +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.ext.constructors +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.methods +import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages +import org.jacodb.impl.features.usagesExt +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.testing.BaseTest +import org.jacodb.testing.WithDB +import org.jacodb.testing.allClasspath +import org.jacodb.testing.analysis.NpeExamples +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.* +import java.util.stream.Stream + +private val logger = KotlinLogging.logger {} + +class Ifds2NpeTest : BaseTest() { + companion object : WithDB(Usages, InMemoryHierarchy) { + @JvmStatic + fun provideClassesForJuliet476(): Stream = + provideClassesForJuliet(476, listOf("null_check_after_deref")) + + @JvmStatic + fun provideClassesForJuliet690(): Stream = + provideClassesForJuliet(690) + } + + override val cp: JcClasspath = runBlocking { + val defaultConfigResource = this.javaClass.getResourceAsStream("/config_small.json") + if (defaultConfigResource != null) { + val configJson = defaultConfigResource.bufferedReader().readText() + val configurationFeature = TaintConfigurationFeature.fromJson(configJson) + db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) + } else { + super.cp + } + } + + @Test + fun `fields resolving should work through interfaces`() = runBlocking { + val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) + val callers = graph.callers(cp.findClass().constructors[2]) + println(callers.toList().size) + } + + @Test + fun `analyze simple NPE`() { + testOneMethod("npeOnLength", listOf("%3 = %0.length()")) + } + + @Test + fun `analyze no NPE`() { + testOneMethod("noNPE", emptyList()) + } + + @Test + fun `analyze NPE after fun with two exits`() { + testOneMethod( + "npeAfterTwoExits", + listOf("%4 = %0.length()", "%5 = %1.length()") + ) + } + + @Test + fun `no NPE after checked access`() { + testOneMethod("checkedAccess", emptyList()) + } + + @Disabled("Aliasing") + @Test + fun `no NPE after checked access with field`() { + testOneMethod("checkedAccessWithField", emptyList()) + } + + @Test + fun `consecutive NPEs handled properly`() { + testOneMethod( + "consecutiveNPEs", + listOf("%2 = arg$0.length()", "%4 = arg$0.length()") + ) + } + + @Test + fun `npe on virtual call when possible`() { + testOneMethod( + "possibleNPEOnVirtualCall", + listOf("%0 = arg$0.length()") + ) + } + + @Test + fun `no npe on virtual call when impossible`() { + testOneMethod( + "noNPEOnVirtualCall", + emptyList() + ) + } + + @Test + fun `basic test for NPE on fields`() { + testOneMethod("simpleNPEOnField", listOf("len2 = second.length()")) + } + + @Disabled("Flowdroid architecture not supported for async ifds yet") + @Test + fun `simple points-to analysis`() { + testOneMethod("simplePoints2", listOf("%5 = %4.length()")) + } + + @Disabled("Flowdroid architecture not supported for async ifds yet") + @Test + fun `complex aliasing`() { + testOneMethod("complexAliasing", listOf("%6 = %5.length()")) + } + + @Disabled("Flowdroid architecture not supported for async ifds yet") + @Test + fun `context injection in points-to`() { + testOneMethod( + "contextInjection", + listOf("%6 = %5.length()", "%3 = %2.length()") + ) + } + + @Disabled("Flowdroid architecture not supported for async ifds yet") + @Test + fun `activation points maintain flow sensitivity`() { + testOneMethod("flowSensitive", listOf("%8 = %7.length()")) + } + + @Test + fun `overridden null assignment in callee don't affect next caller's instructions`() { + testOneMethod("overriddenNullInCallee", emptyList()) + } + + @Test + fun `recursive classes handled correctly`() { + testOneMethod( + "recursiveClass", + listOf("%10 = %9.toString()", "%15 = %14.toString()") + ) + } + + @Test + fun `NPE on uninitialized array element dereferencing`() { + testOneMethod("simpleArrayNPE", listOf("%5 = %4.length()")) + } + + @Test + fun `no NPE on array element dereferencing after initialization`() { + testOneMethod("noNPEAfterArrayInit", emptyList()) + } + + @Disabled("Flowdroid architecture not supported for async ifds yet") + @Test + fun `array aliasing`() { + testOneMethod("arrayAliasing", listOf("%5 = %4.length()")) + } + + @Disabled("Flowdroid architecture not supported for async ifds yet") + @Test + fun `mixed array and class aliasing`() { + testOneMethod("mixedArrayClassAliasing", listOf("%13 = %12.length()")) + } + + @Test + fun `dereferencing field of null object`() { + testOneMethod("npeOnFieldDeref", listOf("%1 = %0.field")) + } + + @Test + fun `dereferencing copy of value saved before null assignment produce no npe`() { + testOneMethod("copyBeforeNullAssignment", emptyList()) + } + + @Test + fun `assigning null to copy doesn't affect original value`() { + testOneMethod("nullAssignmentToCopy", emptyList()) + } + + @ParameterizedTest + @MethodSource("provideClassesForJuliet476") + fun `test on Juliet's CWE 476`(className: String) { + testSingleJulietClass(className) + } + + @ParameterizedTest + @MethodSource("provideClassesForJuliet690") + fun `test on Juliet's CWE 690`(className: String) { + testSingleJulietClass(className) + } + + @Test + fun `test on specific Juliet's testcase`() { + // val className = "juliet.testcases.CWE476_NULL_Pointer_Dereference.CWE476_NULL_Pointer_Dereference__Integer_01" + // val className = "juliet.testcases.CWE690_NULL_Deref_From_Return.CWE690_NULL_Deref_From_Return__Class_StringBuilder_01" + val className = + "juliet.testcases.CWE690_NULL_Deref_From_Return.CWE690_NULL_Deref_From_Return__Properties_getProperty_equals_01" + + testSingleJulietClass(className) + } + + @Test + fun `analyse something`() { + val testingMethod = cp.findClass().declaredMethods.single { it.name == "id" } + val results = testingMethod.flowGraph() + print(results) + } + + private fun testSingleJulietClass(className: String) { + println(className) + + val clazz = cp.findClass(className) + val badMethod = clazz.methods.single { it.name == "bad" } + val goodMethod = clazz.methods.single { it.name == "good" } + + logger.info { "Searching for sinks in BAD method: $badMethod" } + val badIssues = findSinks(badMethod) + logger.info { "Total ${badIssues.size} issues in BAD method" } + for (issue in badIssues) { + logger.debug { " - $issue" } + } + Assertions.assertTrue(badIssues.isNotEmpty()) + + logger.info { "Searching for sinks in GOOD method: $goodMethod" } + val goodIssues = findSinks(goodMethod) + logger.info { "Total ${goodIssues.size} issues in GOOD method" } + for (issue in goodIssues) { + logger.debug { " - $issue" } + } + Assertions.assertTrue(goodIssues.isEmpty()) + } + + private inline fun testOneMethod( + methodName: String, + expectedLocations: Collection, + ) { + val method = cp.findClass().declaredMethods.single { it.name == methodName } + val sinks = findSinks(method) + + // TODO: think about better assertions here + Assertions.assertEquals(expectedLocations.size, sinks.size) + // expectedLocations.forEach { expected -> + // Assertions.assertTrue(sinks.map { it.traceGraph.sink.toString() }.any { it.contains(expected) }) + // } + } + + private fun findSinks(method: JcMethod): List { + val vulnerabilities = launchAnalysis(listOf(method)) + return vulnerabilities + } + + private fun launchAnalysis(methods: List): List { + val graph = runBlocking { + cp.newApplicationGraphForAnalysis() + } + val unitResolver = SingletonUnitResolver + return runNpeAnalysis(graph, unitResolver, methods) + } +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt new file mode 100644 index 000000000..08285a29c --- /dev/null +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.impl + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.SingletonUnitResolver +import org.jacodb.analysis.graph.newApplicationGraphForAnalysis +import org.jacodb.analysis.ifds2.taint.Vulnerability +import org.jacodb.analysis.ifds2.taint.runTaintAnalysis +import org.jacodb.analysis.impl.BaseAnalysisTest.Companion.provideClassesForJuliet +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.methods +import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.testing.BaseTest +import org.jacodb.testing.WithDB +import org.jacodb.testing.allClasspath +import org.jacodb.testing.analysis.SqlInjectionExamples +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +private val logger = KotlinLogging.logger {} + +class Ifds2SqlTest : BaseTest() { + companion object : WithDB(Usages, InMemoryHierarchy) { + @JvmStatic + fun provideClassesForJuliet89(): Stream = provideClassesForJuliet(89, specificBansCwe89) + + private val specificBansCwe89: List = listOf( + // Not working yet (#156) + "s03", "s04" + ) + } + + override val cp: JcClasspath = runBlocking { + val taintConfigFileName = "config_small.json" + val defaultConfigResource = this.javaClass.getResourceAsStream("/$taintConfigFileName") + if (defaultConfigResource != null) { + val configJson = defaultConfigResource.bufferedReader().readText() + val configurationFeature = TaintConfigurationFeature.fromJson(configJson) + db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) + } else { + super.cp + } + } + + @Test + fun `simple SQL injection`() { + val methodName = "bad" + val method = cp.findClass().declaredMethods.single { it.name == methodName } + val sinks = findSinks(method) + assertTrue(sinks.isNotEmpty()) + } + + @ParameterizedTest + @MethodSource("provideClassesForJuliet89") + fun `test on Juliet's CWE 89`(className: String) { + testSingleJulietClass(className) + } + + @Test + fun `test on Juliet's CWE 89 Environment executeBatch 01`() { + val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_01" + testSingleJulietClass(className) + } + + @Test + fun `test on Juliet's CWE 89 database prepareStatement 01`() { + val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__database_prepareStatement_01" + testSingleJulietClass(className) + } + + @Test + fun `test on specific Juliet instance`() { + // val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_45" + val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__connect_tcp_execute_01" + + testSingleJulietClass(className) + } + + private fun testSingleJulietClass(className: String) { + println(className) + + val clazz = cp.findClass(className) + val badMethod = clazz.methods.single { it.name == "bad" } + val goodMethod = clazz.methods.single { it.name == "good" } + + logger.info { "Searching for sinks in BAD method: $badMethod" } + val badIssues = findSinks(badMethod) + logger.info { "Total ${badIssues.size} issues in BAD method" } + for (issue in badIssues) { + logger.debug { " - $issue" } + } + assertTrue(badIssues.isNotEmpty()) { "Must find some sinks in 'bad' for $className" } + + logger.info { "Searching for sinks in GOOD method: $goodMethod" } + val goodIssues = findSinks(goodMethod) + logger.info { "Total ${goodIssues.size} issues in GOOD method" } + for (issue in goodIssues) { + logger.debug { " - $issue" } + } + assertTrue(goodIssues.isEmpty()) { "Must NOT find any sinks in 'good' for $className" } + } + + private fun findSinks(method: JcMethod): List { + val vulnerabilities = launchAnalysis(listOf(method)) + return vulnerabilities + } + + private fun launchAnalysis(methods: List): List { + val graph = runBlocking { + cp.newApplicationGraphForAnalysis() + } + val unitResolver = SingletonUnitResolver + return runTaintAnalysis(graph, unitResolver, methods) + } +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt index 803ed6003..a26031de5 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt @@ -17,12 +17,12 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.ClassUnitResolver import org.jacodb.analysis.engine.IfdsUnitRunnerFactory +import org.jacodb.analysis.engine.MethodUnitResolver import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.MethodUnitResolver import org.jacodb.analysis.library.UnusedVariableRunnerFactory -import org.jacodb.analysis.library.getClassUnitResolver import org.jacodb.analysis.library.newNpeRunnerFactory import org.jacodb.analysis.runAnalysis import org.jacodb.analysis.sarif.SarifReport @@ -35,7 +35,10 @@ import org.junit.jupiter.api.Test class JodaDateTimeAnalysisTest : BaseTest() { companion object : WithGlobalDB() - private fun testOne(unitResolver: UnitResolver, ifdsUnitRunnerFactory: IfdsUnitRunnerFactory) { + private fun testOne( + unitResolver: UnitResolver, + ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, + ) { val clazz = cp.findClass() val result = runAnalysis(graph, unitResolver, ifdsUnitRunnerFactory, clazz.declaredMethods, 60000L) @@ -46,7 +49,7 @@ class JodaDateTimeAnalysisTest : BaseTest() { @Test fun `test Unused variable analysis`() { - testOne(getClassUnitResolver(false), UnusedVariableRunnerFactory) + testOne(ClassUnitResolver(false), UnusedVariableRunnerFactory) } @Test @@ -57,4 +60,4 @@ class JodaDateTimeAnalysisTest : BaseTest() { private val graph = runBlocking { cp.newApplicationGraphForAnalysis() } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt index 01fec83a2..9d6d70f76 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt @@ -17,10 +17,10 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.engine.VulnerabilityInstance import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.SingletonUnitResolver import org.jacodb.analysis.library.analyzers.NpeAnalyzer import org.jacodb.analysis.library.newNpeRunnerFactory import org.jacodb.analysis.runAnalysis @@ -29,7 +29,6 @@ import org.jacodb.api.ext.constructors import org.jacodb.api.ext.findClass import org.jacodb.impl.features.usagesExt import org.jacodb.testing.analysis.NpeExamples -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -38,6 +37,8 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.* import java.util.stream.Stream +// Note: some tests might fail because of config rules on StringBuilder.append (CopyAllMarks actions). +@Disabled("Broken due to config") class NpeAnalysisTest : BaseAnalysisTest() { companion object { @JvmStatic @@ -199,6 +200,13 @@ class NpeAnalysisTest : BaseAnalysisTest() { testJuliet(className) } + @Test + fun `test on specific Juliet's CWE 476`() { + val className = "juliet.testcases.CWE476_NULL_Pointer_Dereference.CWE476_NULL_Pointer_Dereference__Integer_01" + + testSingleJulietClass(vulnerabilityType, className) + } + @Test fun `analyse something`() { val testingMethod = cp.findClass().declaredMethods.single { it.name == "id" } @@ -217,4 +225,4 @@ class NpeAnalysisTest : BaseAnalysisTest() { } return runAnalysis(graph, SingletonUnitResolver, newNpeRunnerFactory(), methods) } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt index da9697fbc..af39bee67 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt @@ -17,16 +17,20 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.engine.VulnerabilityInstance import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.SingletonUnitResolver import org.jacodb.analysis.library.analyzers.SqlInjectionAnalyzer import org.jacodb.analysis.library.newSqlInjectionRunnerFactory import org.jacodb.analysis.runAnalysis import org.jacodb.api.JcMethod +import org.jacodb.api.ext.findClass import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.Usages import org.jacodb.testing.WithDB +import org.jacodb.testing.analysis.SqlInjectionExamples +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -35,12 +39,21 @@ import java.util.stream.Stream class SqlInjectionAnalysisTest : BaseAnalysisTest() { companion object : WithDB(Usages, InMemoryHierarchy) { @JvmStatic - fun provideClassesForJuliet89(): Stream = provideClassesForJuliet(89, listOf( + fun provideClassesForJuliet89(): Stream = provideClassesForJuliet(89, specificBansCwe89) + + private val vulnerabilityType = SqlInjectionAnalyzer.vulnerabilityDescription.ruleId + private val specificBansCwe89: List = listOf( // Not working yet (#156) "s03", "s04" - )) + ) + } - private val vulnerabilityType = SqlInjectionAnalyzer.vulnerabilityDescription.ruleId + @Test + fun `simple SQL injection`() { + val methodName = "bad" + val method = cp.findClass().declaredMethods.single { it.name == methodName } + val sinks = findSinks(method, vulnerabilityType) + assertTrue(sinks.isNotEmpty()) } @ParameterizedTest @@ -49,10 +62,17 @@ class SqlInjectionAnalysisTest : BaseAnalysisTest() { testSingleJulietClass(vulnerabilityType, className) } + @Test + fun `test on specific Juliet's CWE 89`() { + val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_01" + + testSingleJulietClass(vulnerabilityType, className) + } + override fun launchAnalysis(methods: List): List { val graph = runBlocking { cp.newApplicationGraphForAnalysis() } return runAnalysis(graph, SingletonUnitResolver, newSqlInjectionRunnerFactory(), methods) } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt index b8884a88b..665d4ef2d 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt @@ -17,9 +17,9 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.engine.VulnerabilityInstance import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.SingletonUnitResolver import org.jacodb.analysis.library.UnusedVariableRunnerFactory import org.jacodb.analysis.library.analyzers.UnusedVariableAnalyzer import org.jacodb.analysis.runAnalysis @@ -27,29 +27,32 @@ import org.jacodb.api.JcMethod import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.Usages import org.jacodb.testing.WithDB -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import java.util.* import java.util.stream.Stream class UnusedVariableTest : BaseAnalysisTest() { companion object : WithDB(Usages, InMemoryHierarchy) { @JvmStatic - fun provideClassesForJuliet563(): Stream = provideClassesForJuliet(563, listOf( - // Unused variables are already optimized out by cfg - "unused_uninit_variable_", "unused_init_variable_int", "unused_init_variable_long", "unused_init_variable_String_", + fun provideClassesForJuliet563(): Stream = provideClassesForJuliet( + 563, listOf( + // Unused variables are already optimized out by cfg + "unused_uninit_variable_", + "unused_init_variable_int", + "unused_init_variable_long", + "unused_init_variable_String_", - // Unused variable is generated by cfg (!!) - "unused_value_StringBuilder_17", + // Unused variable is generated by cfg (!!) + "unused_value_StringBuilder_17", - // Expected answers are strange, seems to be problem in tests - "_12", + // Expected answers are strange, seems to be problem in tests + "_12", - // The variable isn't expected to be detected as unused actually - "_81" - )) + // The variable isn't expected to be detected as unused actually + "_81" + ) + ) private const val vulnerabilityType = UnusedVariableAnalyzer.ruleId } @@ -66,4 +69,4 @@ class UnusedVariableTest : BaseAnalysisTest() { } return runAnalysis(graph, SingletonUnitResolver, UnusedVariableRunnerFactory, methods) } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/test/resources/additional.json b/jacodb-analysis/src/test/resources/additional.json new file mode 100644 index 000000000..c0fdda453 --- /dev/null +++ b/jacodb-analysis/src/test/resources/additional.json @@ -0,0 +1,523 @@ +[ + { + "_": "EntryPointSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameMatches", + "pattern": ".*" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": ".*" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": false, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "AnnotationType", + "position": { + "_": "Argument", + "number": 0 + }, + "type": { + "_": "ClassMatcher", + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "org.springframework.web.bind.annotation" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "RequestParam" + } + } + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Argument", + "number": 0 + }, + "mark": { + "name": "XSS" + } + } + ] + }, + { + "_": "EntryPointSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameMatches", + "pattern": ".*" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": ".*" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": false, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "AnnotationType", + "position": { + "_": "Argument", + "number": 1 + }, + "type": { + "_": "ClassMatcher", + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "org.springframework.web.bind.annotation" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "RequestParam" + } + } + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Argument", + "number": 1 + }, + "mark": { + "name": "XSS" + } + } + ] + }, + { + "_": "EntryPointSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameMatches", + "pattern": ".*" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": ".*" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": false, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "AnnotationType", + "position": { + "_": "Argument", + "number": 2 + }, + "type": { + "_": "ClassMatcher", + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "org.springframework.web.bind.annotation" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "RequestParam" + } + } + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Argument", + "number": 2 + }, + "mark": { + "name": "XSS" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.net" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "URLDecoder" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "decode" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Connection" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "prepareCall|prepareStatement" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + } + ] + }, + { + "_": "MethodSink", + "ruleNote": "SQL Injection", + "cwe": [ + 89 + ], + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": "PreparedStatement" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "execute|executeQuery|executeUpdate|executeLargeUpdate" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "Or", + "args": [ + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "NETWORK" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "ARGS" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "LDAP" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "FORM" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "WEBSERVICE" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "PROPERTY" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "ENVIRONMENT" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "ICC" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "STREAM" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "FILE_SYSTEM" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "JSON" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "STDIN" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "DATABASE" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "CHANNEL" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "WEB" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "CONSOLE" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "XML" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "XSS" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "GUI_FORM" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "NAMING" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "REGISTRY" + } + } + ] + } + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.util" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Map" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "get" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "This" + }, + "to": { + "_": "Result" + } + } + ] + } +] diff --git a/jacodb-analysis/src/test/resources/config_small.json b/jacodb-analysis/src/test/resources/config_small.json new file mode 100644 index 000000000..b9d648e9d --- /dev/null +++ b/jacodb-analysis/src/test/resources/config_small.json @@ -0,0 +1,706 @@ +[ + { + "_": "MethodSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.lang" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "System" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "getenv" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Result" + }, + "mark": { + "name": "ENVIRONMENT" + } + } + ] + }, + { + "_": "MethodSink", + "ruleNote": "SQL Injection", + "cwe": [ + 89 + ], + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": "Statement" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "execute|executeQuery|executeUpdate|executeLargeUpdate" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "Or", + "args": [ + { + "_": "ContainsMark", + "position": { + "_": "Argument", + "number": 0 + }, + "mark": { + "name": "UNTRUSTED" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "Argument", + "number": 0 + }, + "mark": { + "name": "INJECTION" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "Argument", + "number": 0 + }, + "mark": { + "name": "ENVIRONMENT" + } + } + ] + } + }, + { + "_": "MethodSink", + "ruleNote": "SQL Injection", + "cwe": [ + 89 + ], + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": "Statement" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "executeBatch|executeLargeBatch" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "Or", + "args": [ + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "UNTRUSTED" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "INJECTION" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "ENVIRONMENT" + } + } + ] + } + }, + { + "_": "MethodSink", + "ruleNote": "SQL Injection", + "cwe": [ + 89 + ], + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": "PreparedStatement" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "execute|executeQuery|executeUpdate|executeLargeUpdate" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "Or", + "args": [ + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "UNTRUSTED" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "INJECTION" + } + }, + { + "_": "ContainsMark", + "position": { + "_": "This" + }, + "mark": { + "name": "ENVIRONMENT" + } + } + ] + } + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.lang" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "String" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "split" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "This" + }, + "to": { + "_": "ResultAnyElement" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.lang" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "String" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "concat" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + }, + { + "_": "CopyAllMarks", + "from": { + "_": "This" + }, + "to": { + "_": "Result" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.lang" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "StringBuilder" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "append" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "This" + } + }, + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + }, + + { + "_": "CopyAllMarks", + "from": { + "_": "This" + }, + "to": { + "_": "Result" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.lang" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "StringBuilder" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "toString" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "This" + }, + "to": { + "_": "Result" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Statement" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "addBatch" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "This" + } + } + ] + }, + { + "_": "MethodSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "ResultSet" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "get.*" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Result" + }, + "mark": { + "name": "UNTRUSTED" + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.sql" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Connection" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "prepareStatement" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + } + ] + }, + { + "_": "MethodSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.io" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "BufferedReader" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "readLine" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Result" + }, + "mark": { + "name": "UNTRUSTED" + } + } + ] + }, + { + "_": "MethodSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.util" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Properties" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "getProperty" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Result" + }, + "mark": { + "name": "NULLNESS" + } + } + ] + }, + { + "_": "MethodSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.lang" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "System" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "getProperty" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Result" + }, + "mark": { + "name": "NULLNESS" + } + } + ] + } +] diff --git a/jacodb-analysis/src/test/resources/simplelogger.properties b/jacodb-analysis/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..7cfd85f92 --- /dev/null +++ b/jacodb-analysis/src/test/resources/simplelogger.properties @@ -0,0 +1,35 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=info + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +org.slf4j.simpleLogger.log.org.jacodb.analysis.engine.BaseIfdsUnitRunnerFactory=debug +org.slf4j.simpleLogger.log.org.jacodb.analysis.ifds2=debug + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss + +# Set to true if you want to output the current thread name. +# Defaults to true. +org.slf4j.simpleLogger.showThreadName=false + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt index 70118efd4..c37196f94 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt @@ -68,19 +68,15 @@ interface JcClassOrInterface : JcAnnotatedSymbol, JcAccessible { get() { return access and Opcodes.ACC_INTERFACE != 0 } - - } interface JcAnnotation : JcSymbol { - val visible: Boolean val jcClass: JcClassOrInterface? val values: Map fun matches(className: String): Boolean - } interface JcMethod : JcSymbol, JcAnnotatedSymbol, JcAccessible { @@ -135,7 +131,6 @@ interface JcMethod : JcSymbol, JcAnnotatedSymbol, JcAccessible { } interface JcField : JcAnnotatedSymbol, JcAccessible { - val enclosingClass: JcClassOrInterface val type: TypeName val signature: String? diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/analysis/ApplicationGraph.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/analysis/ApplicationGraph.kt index 000da830a..0b413e5fe 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/analysis/ApplicationGraph.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/analysis/ApplicationGraph.kt @@ -26,7 +26,7 @@ interface ApplicationGraph { fun callees(node: Statement): Sequence fun callers(method: Method): Sequence - fun entryPoint(method: Method): Sequence + fun entryPoints(method: Method): Sequence fun exitPoints(method: Method): Sequence fun methodOf(node: Statement): Method diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt index 82d46085b..514b8a2f2 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt @@ -16,15 +16,16 @@ package org.jacodb.api.cfg -abstract class AbstractFullRawExprSetCollector : JcRawExprVisitor, DefaultJcRawInstVisitor { - - override val defaultInstHandler: (JcRawInst) -> Unit - get() = { - it.operands.forEach { - ifMatches(it) - it.accept(this) - } +abstract class AbstractFullRawExprSetCollector : + JcRawExprVisitor, + JcRawInstVisitor.Default { + + override fun defaultVisitJcRawInst(inst: JcRawInst) { + inst.operands.forEach { + ifMatches(it) + it.accept(this) } + } private fun visitBinaryExpr(expr: JcRawBinaryExpr) { ifMatches(expr) @@ -131,15 +132,16 @@ abstract class AbstractFullRawExprSetCollector : JcRawExprVisitor, Default abstract fun ifMatches(expr: JcRawExpr) } -abstract class AbstractFullExprSetCollector : JcExprVisitor, DefaultJcInstVisitor { +abstract class AbstractFullExprSetCollector : + JcExprVisitor.Default, + JcInstVisitor.Default { - override val defaultInstHandler: (JcInst) -> Any - get() = { - it.operands.forEach { - ifMatches(it) - it.accept(this) - } + override fun defaultVisitJcInst(inst: JcInst) { + inst.operands.forEach { + ifMatches(it) + it.accept(this) } + } private fun visitBinaryExpr(expr: JcBinaryExpr) { ifMatches(expr) @@ -155,115 +157,104 @@ abstract class AbstractFullExprSetCollector : JcExprVisitor, DefaultJcInstV expr.args.forEach { it.accept(this) } } - override fun visitJcAddExpr(expr: JcAddExpr): Any = visitBinaryExpr(expr) - override fun visitJcAndExpr(expr: JcAndExpr): Any = visitBinaryExpr(expr) - override fun visitJcCmpExpr(expr: JcCmpExpr): Any = visitBinaryExpr(expr) - override fun visitJcCmpgExpr(expr: JcCmpgExpr): Any = visitBinaryExpr(expr) - override fun visitJcCmplExpr(expr: JcCmplExpr): Any = visitBinaryExpr(expr) - override fun visitJcDivExpr(expr: JcDivExpr): Any = visitBinaryExpr(expr) - override fun visitJcMulExpr(expr: JcMulExpr): Any = visitBinaryExpr(expr) - override fun visitJcEqExpr(expr: JcEqExpr): Any = visitBinaryExpr(expr) - override fun visitJcNeqExpr(expr: JcNeqExpr): Any = visitBinaryExpr(expr) - override fun visitJcGeExpr(expr: JcGeExpr): Any = visitBinaryExpr(expr) - override fun visitJcGtExpr(expr: JcGtExpr): Any = visitBinaryExpr(expr) - override fun visitJcLeExpr(expr: JcLeExpr): Any = visitBinaryExpr(expr) - override fun visitJcLtExpr(expr: JcLtExpr): Any = visitBinaryExpr(expr) - override fun visitJcOrExpr(expr: JcOrExpr): Any = visitBinaryExpr(expr) - override fun visitJcRemExpr(expr: JcRemExpr): Any = visitBinaryExpr(expr) - override fun visitJcShlExpr(expr: JcShlExpr): Any = visitBinaryExpr(expr) - override fun visitJcShrExpr(expr: JcShrExpr): Any = visitBinaryExpr(expr) - override fun visitJcSubExpr(expr: JcSubExpr): Any = visitBinaryExpr(expr) - override fun visitJcUshrExpr(expr: JcUshrExpr): Any = visitBinaryExpr(expr) - override fun visitJcXorExpr(expr: JcXorExpr): Any = visitBinaryExpr(expr) - - override fun visitJcLambdaExpr(expr: JcLambdaExpr): Any { + override fun visitJcAddExpr(expr: JcAddExpr) = visitBinaryExpr(expr) + override fun visitJcAndExpr(expr: JcAndExpr) = visitBinaryExpr(expr) + override fun visitJcCmpExpr(expr: JcCmpExpr) = visitBinaryExpr(expr) + override fun visitJcCmpgExpr(expr: JcCmpgExpr) = visitBinaryExpr(expr) + override fun visitJcCmplExpr(expr: JcCmplExpr) = visitBinaryExpr(expr) + override fun visitJcDivExpr(expr: JcDivExpr) = visitBinaryExpr(expr) + override fun visitJcMulExpr(expr: JcMulExpr) = visitBinaryExpr(expr) + override fun visitJcEqExpr(expr: JcEqExpr) = visitBinaryExpr(expr) + override fun visitJcNeqExpr(expr: JcNeqExpr) = visitBinaryExpr(expr) + override fun visitJcGeExpr(expr: JcGeExpr) = visitBinaryExpr(expr) + override fun visitJcGtExpr(expr: JcGtExpr) = visitBinaryExpr(expr) + override fun visitJcLeExpr(expr: JcLeExpr) = visitBinaryExpr(expr) + override fun visitJcLtExpr(expr: JcLtExpr) = visitBinaryExpr(expr) + override fun visitJcOrExpr(expr: JcOrExpr) = visitBinaryExpr(expr) + override fun visitJcRemExpr(expr: JcRemExpr) = visitBinaryExpr(expr) + override fun visitJcShlExpr(expr: JcShlExpr) = visitBinaryExpr(expr) + override fun visitJcShrExpr(expr: JcShrExpr) = visitBinaryExpr(expr) + override fun visitJcSubExpr(expr: JcSubExpr) = visitBinaryExpr(expr) + override fun visitJcUshrExpr(expr: JcUshrExpr) = visitBinaryExpr(expr) + override fun visitJcXorExpr(expr: JcXorExpr) = visitBinaryExpr(expr) + + override fun visitJcLambdaExpr(expr: JcLambdaExpr) { ifMatches(expr) expr.args.forEach { it.accept(this) } - return Unit } - override fun visitJcLengthExpr(expr: JcLengthExpr): Any { + override fun visitJcLengthExpr(expr: JcLengthExpr) { ifMatches(expr) expr.array.accept(this) - return Unit } - override fun visitJcNegExpr(expr: JcNegExpr): Any { + override fun visitJcNegExpr(expr: JcNegExpr) { ifMatches(expr) expr.operand.accept(this) - return Unit } - override fun visitJcCastExpr(expr: JcCastExpr): Any { + override fun visitJcCastExpr(expr: JcCastExpr) { ifMatches(expr) expr.operand.accept(this) - return Unit } - override fun visitJcNewExpr(expr: JcNewExpr): Any { + override fun visitJcNewExpr(expr: JcNewExpr) { ifMatches(expr) - return Unit } - override fun visitJcNewArrayExpr(expr: JcNewArrayExpr): Any { + override fun visitJcNewArrayExpr(expr: JcNewArrayExpr) { ifMatches(expr) expr.dimensions.forEach { it.accept(this) } - return Unit } - override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): Any { + override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr) { ifMatches(expr) expr.operand.accept(this) - return Unit } - override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): Any { + override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr) { ifMatches(expr) expr.args.forEach { it.accept(this) } - return Unit } - override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): Any = visitCallExpr(expr) - override fun visitJcStaticCallExpr(expr: JcStaticCallExpr): Any = visitCallExpr(expr) - override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): Any = visitCallExpr(expr) - override fun visitJcThis(value: JcThis): Any = ifMatches(value) - override fun visitJcArgument(value: JcArgument): Any = ifMatches(value) - override fun visitJcLocalVar(value: JcLocalVar): Any = ifMatches(value) + override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr) = visitCallExpr(expr) + override fun visitJcStaticCallExpr(expr: JcStaticCallExpr) = visitCallExpr(expr) + override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr) = visitCallExpr(expr) + override fun visitJcThis(value: JcThis) = ifMatches(value) + override fun visitJcArgument(value: JcArgument) = ifMatches(value) + override fun visitJcLocalVar(value: JcLocalVar) = ifMatches(value) - override fun visitJcFieldRef(value: JcFieldRef): Any { + override fun visitJcFieldRef(value: JcFieldRef) { ifMatches(value) value.instance?.accept(this) - return Unit } - override fun visitJcArrayAccess(value: JcArrayAccess): Any { + override fun visitJcArrayAccess(value: JcArrayAccess) { ifMatches(value) value.array.accept(this) value.index.accept(this) - return Unit } - override fun visitJcPhiExpr(expr: JcPhiExpr): Any { + override fun visitJcPhiExpr(expr: JcPhiExpr) { ifMatches(expr) expr.args.forEach { it.accept(this) } expr.values.forEach { it.accept(this) } - return Unit } - override fun visitExternalJcExpr(expr: JcExpr): Any = ifMatches(expr) - override fun visitJcBool(value: JcBool): Any = ifMatches(value) - override fun visitJcByte(value: JcByte): Any = ifMatches(value) - override fun visitJcChar(value: JcChar): Any = ifMatches(value) - override fun visitJcShort(value: JcShort): Any = ifMatches(value) - override fun visitJcInt(value: JcInt): Any = ifMatches(value) - override fun visitJcLong(value: JcLong): Any = ifMatches(value) - override fun visitJcFloat(value: JcFloat): Any = ifMatches(value) - override fun visitJcDouble(value: JcDouble): Any = ifMatches(value) - override fun visitJcNullConstant(value: JcNullConstant): Any = ifMatches(value) - override fun visitJcStringConstant(value: JcStringConstant): Any = ifMatches(value) - override fun visitJcClassConstant(value: JcClassConstant): Any = ifMatches(value) - override fun visitJcMethodConstant(value: JcMethodConstant): Any = ifMatches(value) - override fun visitJcMethodType(value: JcMethodType): Any = ifMatches(value) + override fun defaultVisitJcExpr(expr: JcExpr) = ifMatches(expr) + override fun visitJcBool(value: JcBool) = ifMatches(value) + override fun visitJcByte(value: JcByte) = ifMatches(value) + override fun visitJcChar(value: JcChar) = ifMatches(value) + override fun visitJcShort(value: JcShort) = ifMatches(value) + override fun visitJcInt(value: JcInt) = ifMatches(value) + override fun visitJcLong(value: JcLong) = ifMatches(value) + override fun visitJcFloat(value: JcFloat) = ifMatches(value) + override fun visitJcDouble(value: JcDouble) = ifMatches(value) + override fun visitJcNullConstant(value: JcNullConstant) = ifMatches(value) + override fun visitJcStringConstant(value: JcStringConstant) = ifMatches(value) + override fun visitJcClassConstant(value: JcClassConstant) = ifMatches(value) + override fun visitJcMethodConstant(value: JcMethodConstant) = ifMatches(value) + override fun visitJcMethodType(value: JcMethodType) = ifMatches(value) abstract fun ifMatches(expr: JcExpr) -} \ No newline at end of file +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt new file mode 100644 index 000000000..ade7cd284 --- /dev/null +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.api.cfg + +interface JcExprVisitor { + fun visitExternalJcExpr(expr: JcExpr): T + + fun visitJcAddExpr(expr: JcAddExpr): T + fun visitJcAndExpr(expr: JcAndExpr): T + fun visitJcCmpExpr(expr: JcCmpExpr): T + fun visitJcCmpgExpr(expr: JcCmpgExpr): T + fun visitJcCmplExpr(expr: JcCmplExpr): T + fun visitJcDivExpr(expr: JcDivExpr): T + fun visitJcMulExpr(expr: JcMulExpr): T + fun visitJcEqExpr(expr: JcEqExpr): T + fun visitJcNeqExpr(expr: JcNeqExpr): T + fun visitJcGeExpr(expr: JcGeExpr): T + fun visitJcGtExpr(expr: JcGtExpr): T + fun visitJcLeExpr(expr: JcLeExpr): T + fun visitJcLtExpr(expr: JcLtExpr): T + fun visitJcOrExpr(expr: JcOrExpr): T + fun visitJcRemExpr(expr: JcRemExpr): T + fun visitJcShlExpr(expr: JcShlExpr): T + fun visitJcShrExpr(expr: JcShrExpr): T + fun visitJcSubExpr(expr: JcSubExpr): T + fun visitJcUshrExpr(expr: JcUshrExpr): T + fun visitJcXorExpr(expr: JcXorExpr): T + fun visitJcLengthExpr(expr: JcLengthExpr): T + fun visitJcNegExpr(expr: JcNegExpr): T + fun visitJcCastExpr(expr: JcCastExpr): T + fun visitJcNewExpr(expr: JcNewExpr): T + fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T + fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T + fun visitJcPhiExpr(expr: JcPhiExpr): T + fun visitJcLambdaExpr(expr: JcLambdaExpr): T + fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T + fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T + fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T + fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T + + fun visitExternalJcValue(value: JcValue): T + + fun visitJcThis(value: JcThis): T + fun visitJcArgument(value: JcArgument): T + fun visitJcLocalVar(value: JcLocalVar): T + fun visitJcFieldRef(value: JcFieldRef): T + fun visitJcArrayAccess(value: JcArrayAccess): T + fun visitJcBool(value: JcBool): T + fun visitJcByte(value: JcByte): T + fun visitJcChar(value: JcChar): T + fun visitJcShort(value: JcShort): T + fun visitJcInt(value: JcInt): T + fun visitJcLong(value: JcLong): T + fun visitJcFloat(value: JcFloat): T + fun visitJcDouble(value: JcDouble): T + fun visitJcNullConstant(value: JcNullConstant): T + fun visitJcStringConstant(value: JcStringConstant): T + fun visitJcClassConstant(value: JcClassConstant): T + fun visitJcMethodConstant(value: JcMethodConstant): T + fun visitJcMethodType(value: JcMethodType): T + + interface Default : JcExprVisitor { + fun defaultVisitJcExpr(expr: JcExpr): T + fun defaultVisitJcValue(value: JcValue): T = defaultVisitJcExpr(value) + + override fun visitExternalJcExpr(expr: JcExpr): T = defaultVisitJcExpr(expr) + + override fun visitJcAddExpr(expr: JcAddExpr): T = defaultVisitJcExpr(expr) + override fun visitJcAndExpr(expr: JcAndExpr): T = defaultVisitJcExpr(expr) + override fun visitJcCmpExpr(expr: JcCmpExpr): T = defaultVisitJcExpr(expr) + override fun visitJcCmpgExpr(expr: JcCmpgExpr): T = defaultVisitJcExpr(expr) + override fun visitJcCmplExpr(expr: JcCmplExpr): T = defaultVisitJcExpr(expr) + override fun visitJcDivExpr(expr: JcDivExpr): T = defaultVisitJcExpr(expr) + override fun visitJcMulExpr(expr: JcMulExpr): T = defaultVisitJcExpr(expr) + override fun visitJcEqExpr(expr: JcEqExpr): T = defaultVisitJcExpr(expr) + override fun visitJcNeqExpr(expr: JcNeqExpr): T = defaultVisitJcExpr(expr) + override fun visitJcGeExpr(expr: JcGeExpr): T = defaultVisitJcExpr(expr) + override fun visitJcGtExpr(expr: JcGtExpr): T = defaultVisitJcExpr(expr) + override fun visitJcLeExpr(expr: JcLeExpr): T = defaultVisitJcExpr(expr) + override fun visitJcLtExpr(expr: JcLtExpr): T = defaultVisitJcExpr(expr) + override fun visitJcOrExpr(expr: JcOrExpr): T = defaultVisitJcExpr(expr) + override fun visitJcRemExpr(expr: JcRemExpr): T = defaultVisitJcExpr(expr) + override fun visitJcShlExpr(expr: JcShlExpr): T = defaultVisitJcExpr(expr) + override fun visitJcShrExpr(expr: JcShrExpr): T = defaultVisitJcExpr(expr) + override fun visitJcSubExpr(expr: JcSubExpr): T = defaultVisitJcExpr(expr) + override fun visitJcUshrExpr(expr: JcUshrExpr): T = defaultVisitJcExpr(expr) + override fun visitJcXorExpr(expr: JcXorExpr): T = defaultVisitJcExpr(expr) + override fun visitJcLengthExpr(expr: JcLengthExpr): T = defaultVisitJcExpr(expr) + override fun visitJcNegExpr(expr: JcNegExpr): T = defaultVisitJcExpr(expr) + override fun visitJcCastExpr(expr: JcCastExpr): T = defaultVisitJcExpr(expr) + override fun visitJcNewExpr(expr: JcNewExpr): T = defaultVisitJcExpr(expr) + override fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T = defaultVisitJcExpr(expr) + override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T = defaultVisitJcExpr(expr) + override fun visitJcPhiExpr(expr: JcPhiExpr): T = defaultVisitJcExpr(expr) + override fun visitJcLambdaExpr(expr: JcLambdaExpr): T = defaultVisitJcExpr(expr) + override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T = defaultVisitJcExpr(expr) + override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T = defaultVisitJcExpr(expr) + override fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T = defaultVisitJcExpr(expr) + override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T = defaultVisitJcExpr(expr) + + override fun visitExternalJcValue(value: JcValue): T = defaultVisitJcValue(value) + + override fun visitJcThis(value: JcThis): T = defaultVisitJcValue(value) + override fun visitJcArgument(value: JcArgument): T = defaultVisitJcValue(value) + override fun visitJcLocalVar(value: JcLocalVar): T = defaultVisitJcValue(value) + override fun visitJcFieldRef(value: JcFieldRef): T = defaultVisitJcValue(value) + override fun visitJcArrayAccess(value: JcArrayAccess): T = defaultVisitJcValue(value) + override fun visitJcBool(value: JcBool): T = defaultVisitJcValue(value) + override fun visitJcByte(value: JcByte): T = defaultVisitJcValue(value) + override fun visitJcChar(value: JcChar): T = defaultVisitJcValue(value) + override fun visitJcShort(value: JcShort): T = defaultVisitJcValue(value) + override fun visitJcInt(value: JcInt): T = defaultVisitJcValue(value) + override fun visitJcLong(value: JcLong): T = defaultVisitJcValue(value) + override fun visitJcFloat(value: JcFloat): T = defaultVisitJcValue(value) + override fun visitJcDouble(value: JcDouble): T = defaultVisitJcValue(value) + override fun visitJcNullConstant(value: JcNullConstant): T = defaultVisitJcValue(value) + override fun visitJcStringConstant(value: JcStringConstant): T = defaultVisitJcValue(value) + override fun visitJcClassConstant(value: JcClassConstant): T = defaultVisitJcValue(value) + override fun visitJcMethodConstant(value: JcMethodConstant): T = defaultVisitJcValue(value) + override fun visitJcMethodType(value: JcMethodType): T = defaultVisitJcValue(value) + } +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt index 5e6335193..c171630ca 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt @@ -19,18 +19,15 @@ package org.jacodb.api.cfg abstract class TypedExprResolver : AbstractFullExprSetCollector() { - val result = hashSetOf() + val result: MutableSet = hashSetOf() } - class LocalResolver : TypedExprResolver() { - override fun ifMatches(expr: JcExpr) { if (expr is JcLocal) { result.add(expr) } } - } class ValueResolver : TypedExprResolver() { @@ -103,4 +100,4 @@ val JcGraph.values: Set collect(it) } return resolver.result - } \ No newline at end of file + } diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt index 2ddc8fa25..80e418446 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt @@ -17,6 +17,7 @@ package org.jacodb.api.cfg import org.jacodb.api.JcMethod +import org.jacodb.api.JcPrimitiveType import org.jacodb.api.JcType import org.jacodb.api.JcTypedField import org.jacodb.api.JcTypedMethod @@ -40,12 +41,14 @@ interface JcInst { val location: JcInstLocation val operands: List - val lineNumber get() = location.lineNumber + val lineNumber: Int get() = location.lineNumber fun accept(visitor: JcInstVisitor): T } -abstract class AbstractJcInst(override val location: JcInstLocation) : JcInst { +abstract class AbstractJcInst( + override val location: JcInstLocation, +) : JcInst { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -63,13 +66,13 @@ abstract class AbstractJcInst(override val location: JcInstLocation) : JcInst { } data class JcInstRef( - val index: Int + val index: Int, ) class JcAssignInst( location: JcInstLocation, val lhv: JcValue, - val rhv: JcExpr + val rhv: JcExpr, ) : AbstractJcInst(location) { override val operands: List @@ -84,8 +87,9 @@ class JcAssignInst( class JcEnterMonitorInst( location: JcInstLocation, - val monitor: JcValue + val monitor: JcValue, ) : AbstractJcInst(location) { + override val operands: List get() = listOf(monitor) @@ -98,8 +102,9 @@ class JcEnterMonitorInst( class JcExitMonitorInst( location: JcInstLocation, - val monitor: JcValue + val monitor: JcValue, ) : AbstractJcInst(location) { + override val operands: List get() = listOf(monitor) @@ -112,8 +117,9 @@ class JcExitMonitorInst( class JcCallInst( location: JcInstLocation, - val callExpr: JcCallExpr + val callExpr: JcCallExpr, ) : AbstractJcInst(location) { + override val operands: List get() = listOf(callExpr) @@ -128,8 +134,9 @@ interface JcTerminatingInst : JcInst class JcReturnInst( location: JcInstLocation, - val returnValue: JcValue? + val returnValue: JcValue?, ) : AbstractJcInst(location), JcTerminatingInst { + override val operands: List get() = listOfNotNull(returnValue) @@ -142,8 +149,9 @@ class JcReturnInst( class JcThrowInst( location: JcInstLocation, - val throwable: JcValue + val throwable: JcValue, ) : AbstractJcInst(location), JcTerminatingInst { + override val operands: List get() = listOf(throwable) @@ -158,8 +166,9 @@ class JcCatchInst( location: JcInstLocation, val throwable: JcValue, val throwableTypes: List, - val throwers: List + val throwers: List, ) : AbstractJcInst(location) { + override val operands: List get() = listOf(throwable) @@ -176,8 +185,9 @@ interface JcBranchingInst : JcInst { class JcGotoInst( location: JcInstLocation, - val target: JcInstRef + val target: JcInstRef, ) : AbstractJcInst(location), JcBranchingInst { + override val operands: List get() = emptyList() @@ -195,8 +205,9 @@ class JcIfInst( location: JcInstLocation, val condition: JcConditionExpr, val trueBranch: JcInstRef, - val falseBranch: JcInstRef + val falseBranch: JcInstRef, ) : AbstractJcInst(location), JcBranchingInst { + override val operands: List get() = listOf(condition) @@ -214,8 +225,9 @@ class JcSwitchInst( location: JcInstLocation, val key: JcValue, val branches: Map, - val default: JcInstRef + val default: JcInstRef, ) : AbstractJcInst(location), JcBranchingInst { + override val operands: List get() = listOf(key) + branches.keys @@ -244,7 +256,7 @@ interface JcBinaryExpr : JcExpr { data class JcAddExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -259,7 +271,7 @@ data class JcAddExpr( data class JcAndExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -274,7 +286,7 @@ data class JcAndExpr( data class JcCmpExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -289,7 +301,7 @@ data class JcCmpExpr( data class JcCmpgExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -304,7 +316,7 @@ data class JcCmpgExpr( data class JcCmplExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -319,7 +331,7 @@ data class JcCmplExpr( data class JcDivExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -334,7 +346,7 @@ data class JcDivExpr( data class JcMulExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -351,7 +363,7 @@ interface JcConditionExpr : JcBinaryExpr data class JcEqExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -366,7 +378,7 @@ data class JcEqExpr( data class JcNeqExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -381,7 +393,7 @@ data class JcNeqExpr( data class JcGeExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -396,7 +408,7 @@ data class JcGeExpr( data class JcGtExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -411,7 +423,7 @@ data class JcGtExpr( data class JcLeExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -426,7 +438,7 @@ data class JcLeExpr( data class JcLtExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -441,7 +453,7 @@ data class JcLtExpr( data class JcOrExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -456,7 +468,7 @@ data class JcOrExpr( data class JcRemExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -471,7 +483,7 @@ data class JcRemExpr( data class JcShlExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -486,7 +498,7 @@ data class JcShlExpr( data class JcShrExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -501,7 +513,7 @@ data class JcShrExpr( data class JcSubExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -516,7 +528,7 @@ data class JcSubExpr( data class JcUshrExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -531,7 +543,7 @@ data class JcUshrExpr( data class JcXorExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue + override val rhv: JcValue, ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -545,7 +557,7 @@ data class JcXorExpr( data class JcLengthExpr( override val type: JcType, - val array: JcValue + val array: JcValue, ) : JcExpr { override val operands: List get() = listOf(array) @@ -559,7 +571,7 @@ data class JcLengthExpr( data class JcNegExpr( override val type: JcType, - val operand: JcValue + val operand: JcValue, ) : JcExpr { override val operands: List get() = listOf(operand) @@ -573,7 +585,7 @@ data class JcNegExpr( data class JcCastExpr( override val type: JcType, - val operand: JcValue + val operand: JcValue, ) : JcExpr { override val operands: List get() = listOf(operand) @@ -586,7 +598,7 @@ data class JcCastExpr( } data class JcNewExpr( - override val type: JcType + override val type: JcType, ) : JcExpr { override val operands: List get() = emptyList() @@ -600,7 +612,7 @@ data class JcNewExpr( data class JcNewArrayExpr( override val type: JcType, - val dimensions: List + val dimensions: List, ) : JcExpr { override val operands: List @@ -615,7 +627,10 @@ data class JcNewArrayExpr( companion object { private val regexToProcessDimensions = Regex("\\[(.*?)]") - private fun arrayTypeToStringWithDimensions(typeName: String, dimensions: List): String { + private fun arrayTypeToStringWithDimensions( + typeName: String, + dimensions: List, + ): String { var curDim = 0 return regexToProcessDimensions.replace(typeName) { "[${dimensions.getOrNull(curDim++) ?: ""}]" @@ -627,7 +642,7 @@ data class JcNewArrayExpr( data class JcInstanceOfExpr( override val type: JcType, val operand: JcValue, - val targetType: JcType + val targetType: JcType, ) : JcExpr { override val operands: List get() = listOf(operand) @@ -643,7 +658,8 @@ interface JcCallExpr : JcExpr { val method: JcTypedMethod val args: List - override val type get() = method.returnType + override val type: JcType + get() = method.returnType override val operands: List get() = args @@ -651,19 +667,20 @@ interface JcCallExpr : JcExpr { interface JcInstanceCallExpr : JcCallExpr { val instance: JcValue + + // TODO: What is that? val declaredMethod: JcTypedMethod override val operands: List get() = listOf(instance) + args - } data class JcPhiExpr( override val type: JcType, val values: List, - val args: List + val args: List, ) : JcExpr { - + // TODO: shouldn't it be "values + args"? override val operands: List get() = values @@ -672,7 +689,6 @@ data class JcPhiExpr( } } - /** * JcLambdaExpr is created when we can resolve the `invokedynamic` instruction. * When Java or Kotlin compiles a code with the lambda call, it generates @@ -688,11 +704,12 @@ data class JcLambdaExpr( val callSiteMethodName: String, val callSiteArgTypes: List, val callSiteReturnType: JcType, - val callSiteArgs: List + val callSiteArgs: List, ) : JcCallExpr { - - override val method get() = bsmRef.method - override val args get() = callSiteArgs + override val method: JcTypedMethod + get() = bsmRef.method + override val args: List + get() = callSiteArgs override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcLambdaExpr(this) @@ -705,11 +722,12 @@ data class JcDynamicCallExpr( val callSiteMethodName: String, val callSiteArgTypes: List, val callSiteReturnType: JcType, - val callSiteArgs: List + val callSiteArgs: List, ) : JcCallExpr { - - override val method get() = bsmRef.method - override val args get() = callSiteArgs + override val method: JcTypedMethod + get() = bsmRef.method + override val args: List + get() = callSiteArgs override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcDynamicCallExpr(this) @@ -725,40 +743,32 @@ data class JcVirtualCallExpr( override val instance: JcValue, override val args: List, ) : JcInstanceCallExpr { - override val method: JcTypedMethod - get() { - return methodRef.method - } + get() = methodRef.method override val declaredMethod: JcTypedMethod - get() { - return methodRef.declaredMethod - } + get() = methodRef.declaredMethod override fun toString(): String = - "$instance.${methodRef.name}${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" + "$instance.${methodRef.name}${ + args.joinToString(prefix = "(", postfix = ")", separator = ", ") + }" override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcVirtualCallExpr(this) } } - data class JcStaticCallExpr( private val methodRef: TypedMethodRef, override val args: List, ) : JcCallExpr { - - override val method: JcTypedMethod get() = methodRef.method + override val method: JcTypedMethod + get() = methodRef.method override fun toString(): String = "${method.method.enclosingClass.name}.${methodRef.name}${ - args.joinToString( - prefix = "(", - postfix = ")", - separator = ", " - ) + args.joinToString(prefix = "(", postfix = ")", separator = ", ") }" override fun accept(visitor: JcExprVisitor): T { @@ -771,32 +781,32 @@ data class JcSpecialCallExpr( override val instance: JcValue, override val args: List, ) : JcInstanceCallExpr { - - override val method: JcTypedMethod get() = methodRef.method + override val method: JcTypedMethod + get() = methodRef.method override val declaredMethod: JcTypedMethod get() = method override fun toString(): String = - "$instance.${methodRef.name}${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" + "$instance.${methodRef.name}${ + args.joinToString(prefix = "(", postfix = ")", separator = ", ") + }" override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcSpecialCallExpr(this) } } - -interface JcValue : JcExpr +interface JcValue : JcExpr { + override fun accept(visitor: JcExprVisitor): T +} interface JcSimpleValue : JcValue { - override val operands: List get() = emptyList() - } data class JcThis(override val type: JcType) : JcLocal { - override val name: String get() = "this" @@ -814,7 +824,11 @@ interface JcLocal : JcSimpleValue { /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcArgument(val index: Int, override val name: String, override val type: JcType) : JcLocal { +data class JcArgument( + val index: Int, + override val name: String, + override val type: JcType, +) : JcLocal { companion object { @JvmStatic fun of(index: Int, name: String?, type: JcType): JcArgument { @@ -850,7 +864,10 @@ data class JcArgument(val index: Int, override val name: String, override val ty /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcLocalVar(val index: Int, override val name: String, override val type: JcType) : JcLocal { +data class JcLocalVar( + val index: Int,override val name: String, + override val type: JcType, +) : JcLocal { override fun toString(): String = name override fun accept(visitor: JcExprVisitor): T { @@ -880,14 +897,24 @@ interface JcComplexValue : JcValue data class JcFieldRef( val instance: JcValue?, - val field: JcTypedField + val field: JcTypedField, ) : JcComplexValue { - override val type: JcType get() = this.field.fieldType + init { + if (instance == null) { + require(field.isStatic) + } else { + require(!field.isStatic) + } + } + + override val type: JcType + get() = this.field.fieldType override val operands: List get() = instance?.let { listOf(it) }.orEmpty() - override fun toString(): String = "${instance ?: field.enclosingType.typeName}.${field.name}" + override fun toString(): String = + "${instance ?: field.enclosingType.typeName}.${field.name}" override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcFieldRef(this) @@ -897,13 +924,13 @@ data class JcFieldRef( data class JcArrayAccess( val array: JcValue, val index: JcValue, - override val type: JcType + override val type: JcType, ) : JcComplexValue { - override fun toString(): String = "$array[$index]" - override val operands: List get() = listOf(array, index) + override fun toString(): String = "$array[$index]" + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcArrayAccess(this) } @@ -911,47 +938,124 @@ data class JcArrayAccess( interface JcConstant : JcSimpleValue -interface JcNumericConstant : JcConstant { +data class JcBool( + val value: Boolean, + override val type: JcPrimitiveType, +) : JcConstant { + override fun toString(): String = "$value" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcBool(this) + } +} + +data class JcChar( + val value: Char, + override val type: JcPrimitiveType, +) : JcConstant { + override fun toString(): String = "$value" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcChar(this) + } +} +data class JcNullConstant(override val type: JcType) : JcConstant { + override fun toString(): String = "null" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcNullConstant(this) + } +} + +data class JcStringConstant( + val value: String, + override val type: JcType, +) : JcConstant { + override fun toString(): String = "\"$value\"" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcStringConstant(this) + } +} + +/** + * klass may be JcClassType or JcArrayType for constructions like byte[].class + */ +data class JcClassConstant( + val klass: JcType, + override val type: JcType, +) : JcConstant { + override fun toString(): String = "${klass.typeName}.class" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcClassConstant(this) + } +} + +data class JcMethodConstant( + val method: JcTypedMethod, + override val type: JcType, +) : JcConstant { + override fun toString(): String = + "${method.method.enclosingClass.name}::${method.name}${ + method.parameters.joinToString(prefix = "(", postfix = ")", separator = ", ") + }:${method.returnType}" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcMethodConstant(this) + } +} + +data class JcMethodType( + val argumentTypes: List, + val returnType: JcType, + override val type: JcType, +) : JcConstant { + override fun toString(): String = + "${ + argumentTypes.joinToString(prefix = "(", postfix = ")", separator = ", ") + }:${returnType}" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcMethodType(this) + } +} + +interface JcNumericConstant : JcConstant { val value: Number fun isEqual(c: JcNumericConstant): Boolean = c.value == value - fun isNotEqual(c: JcNumericConstant): Boolean = !isEqual(c) fun isLessThan(c: JcNumericConstant): Boolean - fun isLessThanOrEqual(c: JcNumericConstant): Boolean = isLessThan(c) || isEqual(c) fun isGreaterThan(c: JcNumericConstant): Boolean - fun isGreaterThanOrEqual(c: JcNumericConstant): Boolean = isGreaterThan(c) || isEqual(c) operator fun plus(c: JcNumericConstant): JcNumericConstant - operator fun minus(c: JcNumericConstant): JcNumericConstant - operator fun times(c: JcNumericConstant): JcNumericConstant - operator fun div(c: JcNumericConstant): JcNumericConstant - operator fun rem(c: JcNumericConstant): JcNumericConstant operator fun unaryMinus(): JcNumericConstant - } - -data class JcBool(val value: Boolean, override val type: JcType) : JcConstant { +data class JcByte( + override val value: Byte, + override val type: JcPrimitiveType, +) : JcNumericConstant { override fun toString(): String = "$value" - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcBool(this) + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toByte() } -} -data class JcByte(override val value: Byte, override val type: JcType) : JcNumericConstant { - override fun toString(): String = "$value" + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toByte() + } override fun plus(c: JcNumericConstant): JcNumericConstant { return JcInt(value + c.value.toByte(), type) @@ -977,29 +1081,24 @@ data class JcByte(override val value: Byte, override val type: JcType) : JcNumer return JcInt(-value, type) } - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toByte() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toByte() - } - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcByte(this) } } -data class JcChar(val value: Char, override val type: JcType) : JcConstant { +data class JcShort( + override val value: Short, + override val type: JcPrimitiveType, +) : JcNumericConstant { override fun toString(): String = "$value" - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcChar(this) + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toShort() } -} -data class JcShort(override val value: Short, override val type: JcType) : JcNumericConstant { - override fun toString(): String = "$value" + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toShort() + } override fun plus(c: JcNumericConstant): JcNumericConstant { return JcInt(value + c.value.toShort(), type) @@ -1025,22 +1124,25 @@ data class JcShort(override val value: Short, override val type: JcType) : JcNum return JcInt(-value, type) } - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toShort() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toShort() - } - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcShort(this) } } -data class JcInt(override val value: Int, override val type: JcType) : JcNumericConstant { +data class JcInt( + override val value: Int, + override val type: JcPrimitiveType, +) : JcNumericConstant { override fun toString(): String = "$value" + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toInt() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toInt() + } + override fun plus(c: JcNumericConstant): JcNumericConstant { return JcInt(value + c.value.toInt(), type) } @@ -1065,22 +1167,25 @@ data class JcInt(override val value: Int, override val type: JcType) : JcNumeric return JcInt(-value, type) } - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toInt() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toInt() - } - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcInt(this) } } -data class JcLong(override val value: Long, override val type: JcType) : JcNumericConstant { +data class JcLong( + override val value: Long, + override val type: JcPrimitiveType, +) : JcNumericConstant { override fun toString(): String = "$value" + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toLong() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toLong() + } + override fun plus(c: JcNumericConstant): JcNumericConstant { return JcLong(value + c.value.toLong(), type) } @@ -1105,22 +1210,25 @@ data class JcLong(override val value: Long, override val type: JcType) : JcNumer return JcLong(-value, type) } - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toLong() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toLong() - } - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcLong(this) } } -data class JcFloat(override val value: Float, override val type: JcType) : JcNumericConstant { +data class JcFloat( + override val value: Float, + override val type: JcPrimitiveType, +) : JcNumericConstant { override fun toString(): String = "$value" + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toFloat() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toFloat() + } + override fun plus(c: JcNumericConstant): JcNumericConstant { return JcFloat(value + c.value.toFloat(), type) } @@ -1145,22 +1253,25 @@ data class JcFloat(override val value: Float, override val type: JcType) : JcNum return JcFloat(-value, type) } - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toFloat() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toFloat() - } - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcFloat(this) } } -data class JcDouble(override val value: Double, override val type: JcType) : JcNumericConstant { +data class JcDouble( + override val value: Double, + override val type: JcPrimitiveType, +) : JcNumericConstant { override fun toString(): String = "$value" + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toDouble() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toDouble() + } + override fun plus(c: JcNumericConstant): JcNumericConstant { return JcDouble(value + c.value.toDouble(), type) } @@ -1185,78 +1296,7 @@ data class JcDouble(override val value: Double, override val type: JcType) : JcN return JcDouble(-value, type) } - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toDouble() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toDouble() - } - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcDouble(this) } } - -data class JcNullConstant(override val type: JcType) : JcConstant { - override fun toString(): String = "null" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcNullConstant(this) - } -} - -data class JcStringConstant(val value: String, override val type: JcType) : JcConstant { - override fun toString(): String = "\"$value\"" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcStringConstant(this) - } -} - -/** - * klass may be JcClassType or JcArrayType for constructions like byte[].class - */ -data class JcClassConstant(val klass: JcType, override val type: JcType) : JcConstant { - - override fun toString(): String = "${klass.typeName}.class" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcClassConstant(this) - } -} - -data class JcMethodConstant( - val method: JcTypedMethod, - override val type: JcType -) : JcConstant { - override fun toString(): String = "${method.method.enclosingClass.name}::${method.name}${ - method.parameters.joinToString( - prefix = "(", - postfix = ")", - separator = ", " - ) - }:${method.returnType}" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcMethodConstant(this) - } -} - -data class JcMethodType( - val argumentTypes: List, - val returnType: JcType, - override val type: JcType -) : JcConstant { - override fun toString(): String = "${ - argumentTypes.joinToString( - prefix = "(", - postfix = ")", - separator = ", " - ) - }:${returnType}" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcMethodType(this) - } -} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt index de445f3a2..c71c41b2f 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt @@ -16,7 +16,9 @@ package org.jacodb.api.cfg -interface JcInstVisitor { +interface JcInstVisitor { + fun visitExternalJcInst(inst: JcInst): T + fun visitJcAssignInst(inst: JcAssignInst): T fun visitJcEnterMonitorInst(inst: JcEnterMonitorInst): T fun visitJcExitMonitorInst(inst: JcExitMonitorInst): T @@ -27,199 +29,21 @@ interface JcInstVisitor { fun visitJcGotoInst(inst: JcGotoInst): T fun visitJcIfInst(inst: JcIfInst): T fun visitJcSwitchInst(inst: JcSwitchInst): T - fun visitExternalJcInst(inst: JcInst): T - -} - -@JvmDefaultWithoutCompatibility -interface DefaultJcInstVisitor : JcInstVisitor { - val defaultInstHandler: (JcInst) -> T - - - override fun visitJcAssignInst(inst: JcAssignInst): T = defaultInstHandler(inst) - - override fun visitJcEnterMonitorInst(inst: JcEnterMonitorInst): T = defaultInstHandler(inst) - - override fun visitJcExitMonitorInst(inst: JcExitMonitorInst): T = defaultInstHandler(inst) - - override fun visitJcCallInst(inst: JcCallInst): T = defaultInstHandler(inst) - - override fun visitJcReturnInst(inst: JcReturnInst): T = defaultInstHandler(inst) - - override fun visitJcThrowInst(inst: JcThrowInst): T = defaultInstHandler(inst) - - override fun visitJcCatchInst(inst: JcCatchInst): T = defaultInstHandler(inst) - - override fun visitJcGotoInst(inst: JcGotoInst): T = defaultInstHandler(inst) - - override fun visitJcIfInst(inst: JcIfInst): T = defaultInstHandler(inst) - - override fun visitJcSwitchInst(inst: JcSwitchInst): T = defaultInstHandler(inst) - - override fun visitExternalJcInst(inst: JcInst): T = defaultInstHandler(inst) -} - -interface JcExprVisitor { - fun visitJcAddExpr(expr: JcAddExpr): T - fun visitJcAndExpr(expr: JcAndExpr): T - fun visitJcCmpExpr(expr: JcCmpExpr): T - fun visitJcCmpgExpr(expr: JcCmpgExpr): T - fun visitJcCmplExpr(expr: JcCmplExpr): T - fun visitJcDivExpr(expr: JcDivExpr): T - fun visitJcMulExpr(expr: JcMulExpr): T - fun visitJcEqExpr(expr: JcEqExpr): T - fun visitJcNeqExpr(expr: JcNeqExpr): T - fun visitJcGeExpr(expr: JcGeExpr): T - fun visitJcGtExpr(expr: JcGtExpr): T - fun visitJcLeExpr(expr: JcLeExpr): T - fun visitJcLtExpr(expr: JcLtExpr): T - fun visitJcOrExpr(expr: JcOrExpr): T - fun visitJcRemExpr(expr: JcRemExpr): T - fun visitJcShlExpr(expr: JcShlExpr): T - fun visitJcShrExpr(expr: JcShrExpr): T - fun visitJcSubExpr(expr: JcSubExpr): T - fun visitJcUshrExpr(expr: JcUshrExpr): T - fun visitJcXorExpr(expr: JcXorExpr): T - fun visitJcLengthExpr(expr: JcLengthExpr): T - fun visitJcNegExpr(expr: JcNegExpr): T - fun visitJcCastExpr(expr: JcCastExpr): T - fun visitJcNewExpr(expr: JcNewExpr): T - fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T - fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T - fun visitJcLambdaExpr(expr: JcLambdaExpr): T - fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T - fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T - fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T - fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T - - fun visitJcThis(value: JcThis): T - fun visitJcArgument(value: JcArgument): T - fun visitJcLocalVar(value: JcLocalVar): T - fun visitJcFieldRef(value: JcFieldRef): T - fun visitJcArrayAccess(value: JcArrayAccess): T - fun visitJcBool(value: JcBool): T - fun visitJcByte(value: JcByte): T - fun visitJcChar(value: JcChar): T - fun visitJcShort(value: JcShort): T - fun visitJcInt(value: JcInt): T - fun visitJcLong(value: JcLong): T - fun visitJcFloat(value: JcFloat): T - fun visitJcDouble(value: JcDouble): T - fun visitJcNullConstant(value: JcNullConstant): T - fun visitJcStringConstant(value: JcStringConstant): T - fun visitJcClassConstant(value: JcClassConstant): T - fun visitJcMethodConstant(value: JcMethodConstant): T - fun visitJcMethodType(value: JcMethodType): T - fun visitJcPhiExpr(expr: JcPhiExpr): T - - fun visitExternalJcExpr(expr: JcExpr): T -} - - -@JvmDefaultWithoutCompatibility -interface DefaultJcExprVisitor : JcExprVisitor { - val defaultExprHandler: (JcExpr) -> T - - override fun visitJcAddExpr(expr: JcAddExpr): T = defaultExprHandler(expr) - - override fun visitJcAndExpr(expr: JcAndExpr): T = defaultExprHandler(expr) - - override fun visitJcCmpExpr(expr: JcCmpExpr): T = defaultExprHandler(expr) - - override fun visitJcCmpgExpr(expr: JcCmpgExpr): T = defaultExprHandler(expr) - - override fun visitJcCmplExpr(expr: JcCmplExpr): T = defaultExprHandler(expr) - - override fun visitJcDivExpr(expr: JcDivExpr): T = defaultExprHandler(expr) - - override fun visitJcMulExpr(expr: JcMulExpr): T = defaultExprHandler(expr) - - override fun visitJcEqExpr(expr: JcEqExpr): T = defaultExprHandler(expr) - - override fun visitJcNeqExpr(expr: JcNeqExpr): T = defaultExprHandler(expr) - - override fun visitJcGeExpr(expr: JcGeExpr): T = defaultExprHandler(expr) - - override fun visitJcGtExpr(expr: JcGtExpr): T = defaultExprHandler(expr) - - override fun visitJcLeExpr(expr: JcLeExpr): T = defaultExprHandler(expr) - - override fun visitJcLtExpr(expr: JcLtExpr): T = defaultExprHandler(expr) - - override fun visitJcOrExpr(expr: JcOrExpr): T = defaultExprHandler(expr) - - override fun visitJcRemExpr(expr: JcRemExpr): T = defaultExprHandler(expr) - - override fun visitJcShlExpr(expr: JcShlExpr): T = defaultExprHandler(expr) - - override fun visitJcShrExpr(expr: JcShrExpr): T = defaultExprHandler(expr) - - override fun visitJcSubExpr(expr: JcSubExpr): T = defaultExprHandler(expr) - - override fun visitJcUshrExpr(expr: JcUshrExpr): T = defaultExprHandler(expr) - - override fun visitJcXorExpr(expr: JcXorExpr): T = defaultExprHandler(expr) - - override fun visitJcLengthExpr(expr: JcLengthExpr): T = defaultExprHandler(expr) - - override fun visitJcNegExpr(expr: JcNegExpr): T = defaultExprHandler(expr) - - override fun visitJcCastExpr(expr: JcCastExpr): T = defaultExprHandler(expr) - - override fun visitJcNewExpr(expr: JcNewExpr): T = defaultExprHandler(expr) - - override fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T = defaultExprHandler(expr) - - override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T = defaultExprHandler(expr) - - override fun visitJcLambdaExpr(expr: JcLambdaExpr): T = defaultExprHandler(expr) - - override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T = defaultExprHandler(expr) - - override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T = defaultExprHandler(expr) - - override fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T = defaultExprHandler(expr) - - override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T = defaultExprHandler(expr) - - - override fun visitJcThis(value: JcThis): T = defaultExprHandler(value) - - override fun visitJcArgument(value: JcArgument): T = defaultExprHandler(value) - - override fun visitJcLocalVar(value: JcLocalVar): T = defaultExprHandler(value) - - override fun visitJcFieldRef(value: JcFieldRef): T = defaultExprHandler(value) - - override fun visitJcArrayAccess(value: JcArrayAccess): T = defaultExprHandler(value) - - override fun visitJcBool(value: JcBool): T = defaultExprHandler(value) - - override fun visitJcByte(value: JcByte): T = defaultExprHandler(value) - - override fun visitJcChar(value: JcChar): T = defaultExprHandler(value) - - override fun visitJcShort(value: JcShort): T = defaultExprHandler(value) - - override fun visitJcInt(value: JcInt): T = defaultExprHandler(value) - - override fun visitJcLong(value: JcLong): T = defaultExprHandler(value) - - override fun visitJcFloat(value: JcFloat): T = defaultExprHandler(value) - - override fun visitJcDouble(value: JcDouble): T = defaultExprHandler(value) - - override fun visitJcNullConstant(value: JcNullConstant): T = defaultExprHandler(value) - - override fun visitJcStringConstant(value: JcStringConstant): T = defaultExprHandler(value) - - override fun visitJcClassConstant(value: JcClassConstant): T = defaultExprHandler(value) - - override fun visitJcMethodConstant(value: JcMethodConstant): T = defaultExprHandler(value) - - override fun visitJcMethodType(value: JcMethodType): T = defaultExprHandler(value) - - override fun visitJcPhiExpr(expr: JcPhiExpr): T = defaultExprHandler(expr) - override fun visitExternalJcExpr(expr: JcExpr): T = defaultExprHandler(expr) + interface Default : JcInstVisitor { + fun defaultVisitJcInst(inst: JcInst): T + + override fun visitExternalJcInst(inst: JcInst): T = defaultVisitJcInst(inst) + + override fun visitJcAssignInst(inst: JcAssignInst): T = defaultVisitJcInst(inst) + override fun visitJcEnterMonitorInst(inst: JcEnterMonitorInst): T = defaultVisitJcInst(inst) + override fun visitJcExitMonitorInst(inst: JcExitMonitorInst): T = defaultVisitJcInst(inst) + override fun visitJcCallInst(inst: JcCallInst): T = defaultVisitJcInst(inst) + override fun visitJcReturnInst(inst: JcReturnInst): T = defaultVisitJcInst(inst) + override fun visitJcThrowInst(inst: JcThrowInst): T = defaultVisitJcInst(inst) + override fun visitJcCatchInst(inst: JcCatchInst): T = defaultVisitJcInst(inst) + override fun visitJcGotoInst(inst: JcGotoInst): T = defaultVisitJcInst(inst) + override fun visitJcIfInst(inst: JcIfInst): T = defaultVisitJcInst(inst) + override fun visitJcSwitchInst(inst: JcSwitchInst): T = defaultVisitJcInst(inst) + } } diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt new file mode 100644 index 000000000..1c2974a39 --- /dev/null +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.api.cfg + +interface JcRawExprVisitor { + fun visitJcRawAddExpr(expr: JcRawAddExpr): T + fun visitJcRawAndExpr(expr: JcRawAndExpr): T + fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T + fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T + fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T + fun visitJcRawDivExpr(expr: JcRawDivExpr): T + fun visitJcRawMulExpr(expr: JcRawMulExpr): T + fun visitJcRawEqExpr(expr: JcRawEqExpr): T + fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T + fun visitJcRawGeExpr(expr: JcRawGeExpr): T + fun visitJcRawGtExpr(expr: JcRawGtExpr): T + fun visitJcRawLeExpr(expr: JcRawLeExpr): T + fun visitJcRawLtExpr(expr: JcRawLtExpr): T + fun visitJcRawOrExpr(expr: JcRawOrExpr): T + fun visitJcRawRemExpr(expr: JcRawRemExpr): T + fun visitJcRawShlExpr(expr: JcRawShlExpr): T + fun visitJcRawShrExpr(expr: JcRawShrExpr): T + fun visitJcRawSubExpr(expr: JcRawSubExpr): T + fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T + fun visitJcRawXorExpr(expr: JcRawXorExpr): T + fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T + fun visitJcRawNegExpr(expr: JcRawNegExpr): T + fun visitJcRawCastExpr(expr: JcRawCastExpr): T + fun visitJcRawNewExpr(expr: JcRawNewExpr): T + fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T + fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T + fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T + fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T + fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T + fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T + fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T + + fun visitJcRawThis(value: JcRawThis): T + fun visitJcRawArgument(value: JcRawArgument): T + fun visitJcRawLocalVar(value: JcRawLocalVar): T + fun visitJcRawFieldRef(value: JcRawFieldRef): T + fun visitJcRawArrayAccess(value: JcRawArrayAccess): T + fun visitJcRawBool(value: JcRawBool): T + fun visitJcRawByte(value: JcRawByte): T + fun visitJcRawChar(value: JcRawChar): T + fun visitJcRawShort(value: JcRawShort): T + fun visitJcRawInt(value: JcRawInt): T + fun visitJcRawLong(value: JcRawLong): T + fun visitJcRawFloat(value: JcRawFloat): T + fun visitJcRawDouble(value: JcRawDouble): T + fun visitJcRawNullConstant(value: JcRawNullConstant): T + fun visitJcRawStringConstant(value: JcRawStringConstant): T + fun visitJcRawClassConstant(value: JcRawClassConstant): T + fun visitJcRawMethodConstant(value: JcRawMethodConstant): T + fun visitJcRawMethodType(value: JcRawMethodType): T + + interface Default : JcRawExprVisitor { + fun defaultVisitJcRawExpr(expr: JcRawExpr): T + fun defaultVisitJcRawValue(value: JcRawValue): T = defaultVisitJcRawExpr(value) + + override fun visitJcRawAddExpr(expr: JcRawAddExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawAndExpr(expr: JcRawAndExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawDivExpr(expr: JcRawDivExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawMulExpr(expr: JcRawMulExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawEqExpr(expr: JcRawEqExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawGeExpr(expr: JcRawGeExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawGtExpr(expr: JcRawGtExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawLeExpr(expr: JcRawLeExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawLtExpr(expr: JcRawLtExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawOrExpr(expr: JcRawOrExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawRemExpr(expr: JcRawRemExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawShlExpr(expr: JcRawShlExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawShrExpr(expr: JcRawShrExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawSubExpr(expr: JcRawSubExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawXorExpr(expr: JcRawXorExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawNegExpr(expr: JcRawNegExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawCastExpr(expr: JcRawCastExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawNewExpr(expr: JcRawNewExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T = defaultVisitJcRawExpr(expr) + override fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T = defaultVisitJcRawExpr(expr) + + override fun visitJcRawThis(value: JcRawThis): T = defaultVisitJcRawValue(value) + override fun visitJcRawArgument(value: JcRawArgument): T = defaultVisitJcRawValue(value) + override fun visitJcRawLocalVar(value: JcRawLocalVar): T = defaultVisitJcRawValue(value) + override fun visitJcRawFieldRef(value: JcRawFieldRef): T = defaultVisitJcRawValue(value) + override fun visitJcRawArrayAccess(value: JcRawArrayAccess): T = defaultVisitJcRawValue(value) + override fun visitJcRawBool(value: JcRawBool): T = defaultVisitJcRawValue(value) + override fun visitJcRawByte(value: JcRawByte): T = defaultVisitJcRawValue(value) + override fun visitJcRawChar(value: JcRawChar): T = defaultVisitJcRawValue(value) + override fun visitJcRawShort(value: JcRawShort): T = defaultVisitJcRawValue(value) + override fun visitJcRawInt(value: JcRawInt): T = defaultVisitJcRawValue(value) + override fun visitJcRawLong(value: JcRawLong): T = defaultVisitJcRawValue(value) + override fun visitJcRawFloat(value: JcRawFloat): T = defaultVisitJcRawValue(value) + override fun visitJcRawDouble(value: JcRawDouble): T = defaultVisitJcRawValue(value) + override fun visitJcRawNullConstant(value: JcRawNullConstant): T = defaultVisitJcRawValue(value) + override fun visitJcRawStringConstant(value: JcRawStringConstant): T = defaultVisitJcRawValue(value) + override fun visitJcRawClassConstant(value: JcRawClassConstant): T = defaultVisitJcRawValue(value) + override fun visitJcRawMethodConstant(value: JcRawMethodConstant): T = defaultVisitJcRawValue(value) + override fun visitJcRawMethodType(value: JcRawMethodType): T = defaultVisitJcRawValue(value) + } +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt index 21fa62116..a21a743f3 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt @@ -21,7 +21,6 @@ import org.jacodb.api.TypeName sealed interface JcRawInst { val owner: JcMethod - val operands: List fun accept(visitor: JcRawInstVisitor): T @@ -30,9 +29,8 @@ sealed interface JcRawInst { class JcRawAssignInst( override val owner: JcMethod, val lhv: JcRawValue, - val rhv: JcRawExpr + val rhv: JcRawExpr, ) : JcRawInst { - override val operands: List get() = listOf(lhv, rhv) @@ -45,7 +43,7 @@ class JcRawAssignInst( class JcRawEnterMonitorInst( override val owner: JcMethod, - val monitor: JcRawSimpleValue + val monitor: JcRawSimpleValue, ) : JcRawInst { override val operands: List get() = listOf(monitor) @@ -59,7 +57,7 @@ class JcRawEnterMonitorInst( class JcRawExitMonitorInst( override val owner: JcMethod, - val monitor: JcRawSimpleValue + val monitor: JcRawSimpleValue, ) : JcRawInst { override val operands: List get() = listOf(monitor) @@ -73,7 +71,7 @@ class JcRawExitMonitorInst( class JcRawCallInst( override val owner: JcMethod, - val callExpr: JcRawCallExpr + val callExpr: JcRawCallExpr, ) : JcRawInst { override val operands: List get() = listOf(callExpr) @@ -89,8 +87,11 @@ data class JcRawLabelRef(val name: String) { override fun toString() = name } -class JcRawLineNumberInst(override val owner: JcMethod, val lineNumber: Int, val start: JcRawLabelRef) : JcRawInst { - +class JcRawLineNumberInst( + override val owner: JcMethod, + val lineNumber: Int, + val start: JcRawLabelRef, +) : JcRawInst { override val operands: List get() = emptyList() @@ -101,10 +102,9 @@ class JcRawLineNumberInst(override val owner: JcMethod, val lineNumber: Int, val } } - class JcRawLabelInst( override val owner: JcMethod, - val name: String + val name: String, ) : JcRawInst { override val operands: List get() = emptyList() @@ -120,12 +120,13 @@ class JcRawLabelInst( class JcRawReturnInst( override val owner: JcMethod, - val returnValue: JcRawValue? + val returnValue: JcRawValue?, ) : JcRawInst { override val operands: List get() = listOfNotNull(returnValue) - override fun toString(): String = "return" + (returnValue?.let { " $it" } ?: "") + override fun toString(): String = + "return" + (returnValue?.let { " $it" } ?: "") override fun accept(visitor: JcRawInstVisitor): T { return visitor.visitJcRawReturnInst(this) @@ -134,7 +135,7 @@ class JcRawReturnInst( class JcRawThrowInst( override val owner: JcMethod, - val throwable: JcRawValue + val throwable: JcRawValue, ) : JcRawInst { override val operands: List get() = listOf(throwable) @@ -149,7 +150,7 @@ class JcRawThrowInst( data class JcRawCatchEntry( val acceptedThrowable: TypeName, val startInclusive: JcRawLabelRef, - val endExclusive: JcRawLabelRef + val endExclusive: JcRawLabelRef, ) class JcRawCatchInst( @@ -161,7 +162,8 @@ class JcRawCatchInst( override val operands: List get() = listOf(throwable) - override fun toString(): String = "catch ($throwable: ${throwable.typeName})" + override fun toString(): String = + "catch ($throwable: ${throwable.typeName})" override fun accept(visitor: JcRawInstVisitor): T { return visitor.visitJcRawCatchInst(this) @@ -174,7 +176,7 @@ sealed interface JcRawBranchingInst : JcRawInst { class JcRawGotoInst( override val owner: JcMethod, - val target: JcRawLabelRef + val target: JcRawLabelRef, ) : JcRawBranchingInst { override val operands: List get() = emptyList() @@ -193,7 +195,7 @@ data class JcRawIfInst( override val owner: JcMethod, val condition: JcRawConditionExpr, val trueBranch: JcRawLabelRef, - val falseBranch: JcRawLabelRef + val falseBranch: JcRawLabelRef, ) : JcRawBranchingInst { override val operands: List get() = listOf(condition) @@ -201,7 +203,8 @@ data class JcRawIfInst( override val successors: List get() = listOf(trueBranch, falseBranch) - override fun toString(): String = "if ($condition) goto $trueBranch else $falseBranch" + override fun toString(): String = + "if ($condition) goto $trueBranch else $falseBranch" override fun accept(visitor: JcRawInstVisitor): T { return visitor.visitJcRawIfInst(this) @@ -212,7 +215,7 @@ data class JcRawSwitchInst( override val owner: JcMethod, val key: JcRawValue, val branches: Map, - val default: JcRawLabelRef + val default: JcRawLabelRef, ) : JcRawBranchingInst { override val operands: List get() = listOf(key) + branches.keys @@ -222,7 +225,9 @@ data class JcRawSwitchInst( override fun toString(): String = buildString { append("switch ($key) { ") - branches.forEach { (option, label) -> append("$option -> $label") } + for ((option, label) in branches) { + append("$option -> $label") + } append("else -> ${default.name} }") } @@ -246,7 +251,7 @@ interface JcRawBinaryExpr : JcRawExpr { data class JcRawAddExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -261,7 +266,7 @@ data class JcRawAddExpr( data class JcRawAndExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -276,7 +281,7 @@ data class JcRawAndExpr( data class JcRawCmpExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -291,7 +296,7 @@ data class JcRawCmpExpr( data class JcRawCmpgExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -306,7 +311,7 @@ data class JcRawCmpgExpr( data class JcRawCmplExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -321,7 +326,7 @@ data class JcRawCmplExpr( data class JcRawDivExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -336,7 +341,7 @@ data class JcRawDivExpr( data class JcRawMulExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -353,7 +358,7 @@ sealed interface JcRawConditionExpr : JcRawBinaryExpr data class JcRawEqExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -368,7 +373,7 @@ data class JcRawEqExpr( data class JcRawNeqExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -383,7 +388,7 @@ data class JcRawNeqExpr( data class JcRawGeExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -398,7 +403,7 @@ data class JcRawGeExpr( data class JcRawGtExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -413,7 +418,7 @@ data class JcRawGtExpr( data class JcRawLeExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -428,7 +433,7 @@ data class JcRawLeExpr( data class JcRawLtExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -443,7 +448,7 @@ data class JcRawLtExpr( data class JcRawOrExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -458,7 +463,7 @@ data class JcRawOrExpr( data class JcRawRemExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -473,7 +478,7 @@ data class JcRawRemExpr( data class JcRawShlExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -488,7 +493,7 @@ data class JcRawShlExpr( data class JcRawShrExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -503,7 +508,7 @@ data class JcRawShrExpr( data class JcRawSubExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -518,7 +523,7 @@ data class JcRawSubExpr( data class JcRawUshrExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -533,7 +538,7 @@ data class JcRawUshrExpr( data class JcRawXorExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue + override val rhv: JcRawValue, ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -547,7 +552,7 @@ data class JcRawXorExpr( data class JcRawLengthExpr( override val typeName: TypeName, - val array: JcRawValue + val array: JcRawValue, ) : JcRawExpr { override val operands: List get() = listOf(array) @@ -561,7 +566,7 @@ data class JcRawLengthExpr( data class JcRawNegExpr( override val typeName: TypeName, - val operand: JcRawValue + val operand: JcRawValue, ) : JcRawExpr { override val operands: List get() = listOf(operand) @@ -575,7 +580,7 @@ data class JcRawNegExpr( data class JcRawCastExpr( override val typeName: TypeName, - val operand: JcRawValue + val operand: JcRawValue, ) : JcRawExpr { override val operands: List get() = listOf(operand) @@ -588,7 +593,7 @@ data class JcRawCastExpr( } data class JcRawNewExpr( - override val typeName: TypeName + override val typeName: TypeName, ) : JcRawExpr { override val operands: List get() = emptyList() @@ -602,19 +607,21 @@ data class JcRawNewExpr( data class JcRawNewArrayExpr( override val typeName: TypeName, - val dimensions: List + val dimensions: List, ) : JcRawExpr { - constructor(typeName: TypeName, length: JcRawValue) : this(typeName, listOf(length)) + constructor(typeName: TypeName, length: JcRawValue) : + this(typeName, listOf(length)) override val operands: List get() = dimensions - override fun toString(): String = "new ${arrayTypeToStringWithDimensions(typeName, dimensions)}" + override fun toString(): String = + "new ${arrayTypeToStringWithDimensions(typeName, dimensions)}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawNewArrayExpr(this) } - + companion object { private val regexToProcessDimensions = Regex("\\[(.*?)]") @@ -630,12 +637,13 @@ data class JcRawNewArrayExpr( data class JcRawInstanceOfExpr( override val typeName: TypeName, val operand: JcRawValue, - val targetType: TypeName + val targetType: TypeName, ) : JcRawExpr { override val operands: List get() = listOf(operand) - override fun toString(): String = "$operand instanceof ${targetType.typeName}" + override fun toString(): String = + "$operand instanceof ${targetType.typeName}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawInstanceOfExpr(this) @@ -649,13 +657,14 @@ sealed interface JcRawCallExpr : JcRawExpr { val returnType: TypeName val args: List - override val typeName get() = returnType + override val typeName: TypeName + get() = returnType override val operands: List get() = args } -sealed interface JcRawInstanceExpr: JcRawCallExpr { +sealed interface JcRawInstanceExpr : JcRawCallExpr { val instance: JcRawValue } @@ -685,8 +694,12 @@ data class BsmTypeArg(val typeName: TypeName) : BsmArg { override fun toString(): String = typeName.typeName } -data class BsmMethodTypeArg(val argumentTypes: List, val returnType: TypeName) : BsmArg { - override fun toString(): String = "(${argumentTypes.joinToString { it.typeName }}:${returnType.typeName})" +data class BsmMethodTypeArg( + val argumentTypes: List, + val returnType: TypeName, +) : BsmArg { + override fun toString(): String = + "(${argumentTypes.joinToString { it.typeName }}:${returnType.typeName})" } data class BsmHandle( @@ -704,14 +717,20 @@ data class JcRawDynamicCallExpr( val callSiteMethodName: String, val callSiteArgTypes: List, val callSiteReturnType: TypeName, - val callSiteArgs: List + val callSiteArgs: List, ) : JcRawCallExpr { - override val declaringClass get() = bsm.declaringClass - override val methodName get() = bsm.name - override val argumentTypes get() = bsm.argTypes - override val returnType get() = bsm.returnType - override val typeName get() = returnType - override val args get() = callSiteArgs + override val declaringClass: TypeName + get() = bsm.declaringClass + override val methodName: String + get() = bsm.name + override val argumentTypes: List + get() = bsm.argTypes + override val returnType: TypeName + get() = bsm.returnType + override val typeName: TypeName + get() = returnType + override val args: List + get() = callSiteArgs override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawDynamicCallExpr(this) @@ -730,7 +749,9 @@ data class JcRawVirtualCallExpr( get() = listOf(instance) + args override fun toString(): String = - "$instance.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" + "$instance.$methodName${ + args.joinToString(prefix = "(", postfix = ")", separator = ", ") + }" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawVirtualCallExpr(this) @@ -749,7 +770,9 @@ data class JcRawInterfaceCallExpr( get() = listOf(instance) + args override fun toString(): String = - "$instance.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" + "$instance.$methodName${ + args.joinToString(prefix = "(", postfix = ")", separator = ", ") + }" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawInterfaceCallExpr(this) @@ -765,7 +788,9 @@ data class JcRawStaticCallExpr( val isInterfaceMethodCall: Boolean = false, ) : JcRawCallExpr { override fun toString(): String = - "$declaringClass.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" + "$declaringClass.$methodName${ + args.joinToString(prefix = "(", postfix = ")", separator = ", ") + }" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawStaticCallExpr(this) @@ -781,14 +806,15 @@ data class JcRawSpecialCallExpr( override val args: List, ) : JcRawInstanceExpr { override fun toString(): String = - "$instance.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" + "$instance.$methodName${ + args.joinToString(prefix = "(", postfix = ")", separator = ", ") + }" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawSpecialCallExpr(this) } } - sealed interface JcRawValue : JcRawExpr { override val operands: List get() = emptyList() @@ -811,7 +837,11 @@ data class JcRawThis(override val typeName: TypeName) : JcRawSimpleValue { /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcRawArgument(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal { +data class JcRawArgument( + val index: Int, + override val name: String, + override val typeName: TypeName, +) : JcRawLocal { companion object { @JvmStatic fun of(index: Int, name: String?, typeName: TypeName): JcRawArgument { @@ -819,7 +849,6 @@ data class JcRawArgument(val index: Int, override val name: String, override val } } - override fun toString(): String = name override fun accept(visitor: JcRawExprVisitor): T { @@ -848,7 +877,10 @@ data class JcRawArgument(val index: Int, override val name: String, override val /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcRawLocalVar(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal { +data class JcRawLocalVar( + val index: Int,override val name: String, + override val typeName: TypeName, +) : JcRawLocal { override fun toString(): String = name override fun accept(visitor: JcRawExprVisitor): T { @@ -880,14 +912,10 @@ data class JcRawFieldRef( val instance: JcRawValue?, val declaringClass: TypeName, val fieldName: String, - override val typeName: TypeName + override val typeName: TypeName, ) : JcRawComplexValue { - constructor(declaringClass: TypeName, fieldName: String, typeName: TypeName) : this( - null, - declaringClass, - fieldName, - typeName - ) + constructor(declaringClass: TypeName, fieldName: String, typeName: TypeName) : + this(null, declaringClass, fieldName, typeName) override fun toString(): String = "${instance ?: declaringClass}.$fieldName" @@ -899,7 +927,7 @@ data class JcRawFieldRef( data class JcRawArrayAccess( val array: JcRawValue, val index: JcRawValue, - override val typeName: TypeName + override val typeName: TypeName, ) : JcRawComplexValue { override fun toString(): String = "$array[$index]" @@ -910,7 +938,10 @@ data class JcRawArrayAccess( sealed interface JcRawConstant : JcRawSimpleValue -data class JcRawBool(val value: Boolean, override val typeName: TypeName) : JcRawConstant { +data class JcRawBool( + val value: Boolean, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -918,7 +949,10 @@ data class JcRawBool(val value: Boolean, override val typeName: TypeName) : JcRa } } -data class JcRawByte(val value: Byte, override val typeName: TypeName) : JcRawConstant { +data class JcRawByte( + val value: Byte, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -926,7 +960,10 @@ data class JcRawByte(val value: Byte, override val typeName: TypeName) : JcRawCo } } -data class JcRawChar(val value: Char, override val typeName: TypeName) : JcRawConstant { +data class JcRawChar( + val value: Char, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -934,7 +971,10 @@ data class JcRawChar(val value: Char, override val typeName: TypeName) : JcRawCo } } -data class JcRawShort(val value: Short, override val typeName: TypeName) : JcRawConstant { +data class JcRawShort( + val value: Short, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -942,7 +982,10 @@ data class JcRawShort(val value: Short, override val typeName: TypeName) : JcRaw } } -data class JcRawInt(val value: Int, override val typeName: TypeName) : JcRawConstant { +data class JcRawInt( + val value: Int, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -950,7 +993,10 @@ data class JcRawInt(val value: Int, override val typeName: TypeName) : JcRawCons } } -data class JcRawLong(val value: Long, override val typeName: TypeName) : JcRawConstant { +data class JcRawLong( + val value: Long, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -958,7 +1004,10 @@ data class JcRawLong(val value: Long, override val typeName: TypeName) : JcRawCo } } -data class JcRawFloat(val value: Float, override val typeName: TypeName) : JcRawConstant { +data class JcRawFloat( + val value: Float, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -966,7 +1015,10 @@ data class JcRawFloat(val value: Float, override val typeName: TypeName) : JcRaw } } -data class JcRawDouble(val value: Double, override val typeName: TypeName) : JcRawConstant { +data class JcRawDouble( + val value: Double, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -982,7 +1034,10 @@ data class JcRawNullConstant(override val typeName: TypeName) : JcRawConstant { } } -data class JcRawStringConstant(val value: String, override val typeName: TypeName) : JcRawConstant { +data class JcRawStringConstant( + val value: String, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "\"$value\"" override fun accept(visitor: JcRawExprVisitor): T { @@ -990,7 +1045,10 @@ data class JcRawStringConstant(val value: String, override val typeName: TypeNam } } -data class JcRawClassConstant(val className: TypeName, override val typeName: TypeName) : JcRawConstant { +data class JcRawClassConstant( + val className: TypeName, + override val typeName: TypeName, +) : JcRawConstant { override fun toString(): String = "$className.class" override fun accept(visitor: JcRawExprVisitor): T { @@ -1003,10 +1061,12 @@ data class JcRawMethodConstant( val name: String, val argumentTypes: List, val returnType: TypeName, - override val typeName: TypeName + override val typeName: TypeName, ) : JcRawConstant { override fun toString(): String = - "$declaringClass.$name${argumentTypes.joinToString(prefix = "(", postfix = ")")}:$returnType" + "$declaringClass.$name${ + argumentTypes.joinToString(prefix = "(", postfix = ")") + }:$returnType" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawMethodConstant(this) @@ -1016,9 +1076,10 @@ data class JcRawMethodConstant( data class JcRawMethodType( val argumentTypes: List, val returnType: TypeName, - private val methodType: TypeName + private val methodType: TypeName, ) : JcRawConstant { - override val typeName: TypeName = methodType + override val typeName: TypeName + get() = methodType override fun toString(): String = "${argumentTypes.joinToString(prefix = "(", postfix = ")")}:$returnType" @@ -1028,18 +1089,17 @@ data class JcRawMethodType( } } +// fun JcRawInstList.lineNumberOf(inst: JcRawInst): Int? { +// val idx: Int = instructions.indexOf(inst) +// assert(idx != -1) // -//fun JcRawInstList.lineNumberOf(inst: JcRawInst): Int? { -// val idx: Int = instructions.indexOf(inst) -// assert(idx != -1) -// -// // Get index of labels and insnNode within method -// val insnIt: ListIterator = insnList.iterator(idx) -// while (insnIt.hasPrevious()) { -// val node: AbstractInsnNode = insnIt.previous() -// if (node is LineNumberNode) { -// return node as LineNumberNode -// } -// } -// return null -//} \ No newline at end of file +// // Get index of labels and insnNode within method +// val insnIt: ListIterator = insnList.iterator(idx) +// while (insnIt.hasPrevious()) { +// val node: AbstractInsnNode = insnIt.previous() +// if (node is LineNumberNode) { +// return node as LineNumberNode +// } +// } +// return null +// } diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt index 3c798cecd..7002dfb92 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt @@ -16,7 +16,7 @@ package org.jacodb.api.cfg -interface JcRawInstVisitor { +interface JcRawInstVisitor { fun visitJcRawAssignInst(inst: JcRawAssignInst): T fun visitJcRawEnterMonitorInst(inst: JcRawEnterMonitorInst): T fun visitJcRawExitMonitorInst(inst: JcRawExitMonitorInst): T @@ -29,192 +29,21 @@ interface JcRawInstVisitor { fun visitJcRawGotoInst(inst: JcRawGotoInst): T fun visitJcRawIfInst(inst: JcRawIfInst): T fun visitJcRawSwitchInst(inst: JcRawSwitchInst): T -} - -@JvmDefaultWithoutCompatibility -interface DefaultJcRawInstVisitor : JcRawInstVisitor { - val defaultInstHandler: (JcRawInst) -> T - - - override fun visitJcRawAssignInst(inst: JcRawAssignInst): T = defaultInstHandler(inst) - - override fun visitJcRawEnterMonitorInst(inst: JcRawEnterMonitorInst): T = defaultInstHandler(inst) - - override fun visitJcRawExitMonitorInst(inst: JcRawExitMonitorInst): T = defaultInstHandler(inst) - - override fun visitJcRawCallInst(inst: JcRawCallInst): T = defaultInstHandler(inst) - - override fun visitJcRawLabelInst(inst: JcRawLabelInst): T = defaultInstHandler(inst) - - override fun visitJcRawLineNumberInst(inst: JcRawLineNumberInst): T = defaultInstHandler(inst) - - override fun visitJcRawReturnInst(inst: JcRawReturnInst): T = defaultInstHandler(inst) - - override fun visitJcRawThrowInst(inst: JcRawThrowInst): T = defaultInstHandler(inst) - - override fun visitJcRawCatchInst(inst: JcRawCatchInst): T = defaultInstHandler(inst) - - override fun visitJcRawGotoInst(inst: JcRawGotoInst): T = defaultInstHandler(inst) - - override fun visitJcRawIfInst(inst: JcRawIfInst): T = defaultInstHandler(inst) - - override fun visitJcRawSwitchInst(inst: JcRawSwitchInst): T = defaultInstHandler(inst) - -} - -interface JcRawExprVisitor { - fun visitJcRawAddExpr(expr: JcRawAddExpr): T - fun visitJcRawAndExpr(expr: JcRawAndExpr): T - fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T - fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T - fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T - fun visitJcRawDivExpr(expr: JcRawDivExpr): T - fun visitJcRawMulExpr(expr: JcRawMulExpr): T - fun visitJcRawEqExpr(expr: JcRawEqExpr): T - fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T - fun visitJcRawGeExpr(expr: JcRawGeExpr): T - fun visitJcRawGtExpr(expr: JcRawGtExpr): T - fun visitJcRawLeExpr(expr: JcRawLeExpr): T - fun visitJcRawLtExpr(expr: JcRawLtExpr): T - fun visitJcRawOrExpr(expr: JcRawOrExpr): T - fun visitJcRawRemExpr(expr: JcRawRemExpr): T - fun visitJcRawShlExpr(expr: JcRawShlExpr): T - fun visitJcRawShrExpr(expr: JcRawShrExpr): T - fun visitJcRawSubExpr(expr: JcRawSubExpr): T - fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T - fun visitJcRawXorExpr(expr: JcRawXorExpr): T - fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T - fun visitJcRawNegExpr(expr: JcRawNegExpr): T - fun visitJcRawCastExpr(expr: JcRawCastExpr): T - fun visitJcRawNewExpr(expr: JcRawNewExpr): T - fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T - fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T - fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T - fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T - fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T - fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T - fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T - - fun visitJcRawThis(value: JcRawThis): T - fun visitJcRawArgument(value: JcRawArgument): T - fun visitJcRawLocalVar(value: JcRawLocalVar): T - fun visitJcRawFieldRef(value: JcRawFieldRef): T - fun visitJcRawArrayAccess(value: JcRawArrayAccess): T - fun visitJcRawBool(value: JcRawBool): T - fun visitJcRawByte(value: JcRawByte): T - fun visitJcRawChar(value: JcRawChar): T - fun visitJcRawShort(value: JcRawShort): T - fun visitJcRawInt(value: JcRawInt): T - fun visitJcRawLong(value: JcRawLong): T - fun visitJcRawFloat(value: JcRawFloat): T - fun visitJcRawDouble(value: JcRawDouble): T - fun visitJcRawNullConstant(value: JcRawNullConstant): T - fun visitJcRawStringConstant(value: JcRawStringConstant): T - fun visitJcRawClassConstant(value: JcRawClassConstant): T - fun visitJcRawMethodConstant(value: JcRawMethodConstant): T - fun visitJcRawMethodType(value: JcRawMethodType): T -} - -@JvmDefaultWithoutCompatibility -interface DefaultJcRawExprVisitor : JcRawExprVisitor { - val defaultExprHandler: (JcRawExpr) -> T - - - override fun visitJcRawAddExpr(expr: JcRawAddExpr): T = defaultExprHandler(expr) - - override fun visitJcRawAndExpr(expr: JcRawAndExpr): T = defaultExprHandler(expr) - - override fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T = defaultExprHandler(expr) - - override fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T = defaultExprHandler(expr) - - override fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T = defaultExprHandler(expr) - - override fun visitJcRawDivExpr(expr: JcRawDivExpr): T = defaultExprHandler(expr) - - override fun visitJcRawMulExpr(expr: JcRawMulExpr): T = defaultExprHandler(expr) - - override fun visitJcRawEqExpr(expr: JcRawEqExpr): T = defaultExprHandler(expr) - - override fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T = defaultExprHandler(expr) - - override fun visitJcRawGeExpr(expr: JcRawGeExpr): T = defaultExprHandler(expr) - - override fun visitJcRawGtExpr(expr: JcRawGtExpr): T = defaultExprHandler(expr) - - override fun visitJcRawLeExpr(expr: JcRawLeExpr): T = defaultExprHandler(expr) - - override fun visitJcRawLtExpr(expr: JcRawLtExpr): T = defaultExprHandler(expr) - - override fun visitJcRawOrExpr(expr: JcRawOrExpr): T = defaultExprHandler(expr) - - override fun visitJcRawRemExpr(expr: JcRawRemExpr): T = defaultExprHandler(expr) - - override fun visitJcRawShlExpr(expr: JcRawShlExpr): T = defaultExprHandler(expr) - - override fun visitJcRawShrExpr(expr: JcRawShrExpr): T = defaultExprHandler(expr) - - override fun visitJcRawSubExpr(expr: JcRawSubExpr): T = defaultExprHandler(expr) - - override fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T = defaultExprHandler(expr) - - override fun visitJcRawXorExpr(expr: JcRawXorExpr): T = defaultExprHandler(expr) - - override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T = defaultExprHandler(expr) - - override fun visitJcRawNegExpr(expr: JcRawNegExpr): T = defaultExprHandler(expr) - - override fun visitJcRawCastExpr(expr: JcRawCastExpr): T = defaultExprHandler(expr) - - override fun visitJcRawNewExpr(expr: JcRawNewExpr): T = defaultExprHandler(expr) - - override fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T = defaultExprHandler(expr) - - override fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T = defaultExprHandler(expr) - - override fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T = defaultExprHandler(expr) - - override fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T = defaultExprHandler(expr) - - override fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T = defaultExprHandler(expr) - - override fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T = defaultExprHandler(expr) - - override fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T = defaultExprHandler(expr) - - override fun visitJcRawThis(value: JcRawThis): T = defaultExprHandler(value) - - override fun visitJcRawArgument(value: JcRawArgument): T = defaultExprHandler(value) - - override fun visitJcRawLocalVar(value: JcRawLocalVar): T = defaultExprHandler(value) - - override fun visitJcRawFieldRef(value: JcRawFieldRef): T = defaultExprHandler(value) - - override fun visitJcRawArrayAccess(value: JcRawArrayAccess): T = defaultExprHandler(value) - - override fun visitJcRawBool(value: JcRawBool): T = defaultExprHandler(value) - - override fun visitJcRawByte(value: JcRawByte): T = defaultExprHandler(value) - - override fun visitJcRawChar(value: JcRawChar): T = defaultExprHandler(value) - - override fun visitJcRawShort(value: JcRawShort): T = defaultExprHandler(value) - - override fun visitJcRawInt(value: JcRawInt): T = defaultExprHandler(value) - - override fun visitJcRawLong(value: JcRawLong): T = defaultExprHandler(value) - - override fun visitJcRawFloat(value: JcRawFloat): T = defaultExprHandler(value) - - override fun visitJcRawDouble(value: JcRawDouble): T = defaultExprHandler(value) - - override fun visitJcRawNullConstant(value: JcRawNullConstant): T = defaultExprHandler(value) - - override fun visitJcRawStringConstant(value: JcRawStringConstant): T = defaultExprHandler(value) - - override fun visitJcRawClassConstant(value: JcRawClassConstant): T = defaultExprHandler(value) - - override fun visitJcRawMethodConstant(value: JcRawMethodConstant): T = defaultExprHandler(value) - override fun visitJcRawMethodType(value: JcRawMethodType): T = defaultExprHandler(value) + interface Default : JcRawInstVisitor { + fun defaultVisitJcRawInst(inst: JcRawInst): T + + override fun visitJcRawAssignInst(inst: JcRawAssignInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawEnterMonitorInst(inst: JcRawEnterMonitorInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawExitMonitorInst(inst: JcRawExitMonitorInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawCallInst(inst: JcRawCallInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawLabelInst(inst: JcRawLabelInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawLineNumberInst(inst: JcRawLineNumberInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawReturnInst(inst: JcRawReturnInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawThrowInst(inst: JcRawThrowInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawCatchInst(inst: JcRawCatchInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawGotoInst(inst: JcRawGotoInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawIfInst(inst: JcRawIfInst): T = defaultVisitJcRawInst(inst) + override fun visitJcRawSwitchInst(inst: JcRawSwitchInst): T = defaultVisitJcRawInst(inst) + } } diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt index 3e3789532..4ccc7ba21 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt @@ -18,9 +18,15 @@ package org.jacodb.api.ext -import org.jacodb.api.* +import org.jacodb.api.JcAnnotated +import org.jacodb.api.JcAnnotation +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClassType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.PredefinedPrimitives +import org.jacodb.api.throwClassNotFound import java.io.Serializable -import java.lang.Cloneable fun String.jvmName(): String { return when { @@ -68,7 +74,6 @@ fun String.jcdbName(): String { } } - val JcClasspath.objectType: JcClassType get() = findTypeOrNull() as? JcClassType ?: throwClassNotFound() @@ -81,7 +86,6 @@ val JcClasspath.cloneableClass: JcClassOrInterface val JcClasspath.serializableClass: JcClassOrInterface get() = findClass() - // call with SAFE. comparator works only on methods from one hierarchy internal object UnsafeHierarchyMethodComparator : Comparator { @@ -96,4 +100,4 @@ fun JcAnnotated.hasAnnotation(className: String): Boolean { fun JcAnnotated.annotation(className: String): JcAnnotation? { return annotations.firstOrNull { it.matches(className) } -} \ No newline at end of file +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt index 708cf8f68..4470d99d3 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt @@ -18,7 +18,16 @@ package org.jacodb.api.ext -import org.jacodb.api.* +import org.jacodb.api.JcArrayType +import org.jacodb.api.JcClassType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcPrimitiveType +import org.jacodb.api.JcRefType +import org.jacodb.api.JcType +import org.jacodb.api.JcTypedField +import org.jacodb.api.JcTypedMethod +import org.jacodb.api.TypeNotFoundException +import org.jacodb.api.throwClassNotFound import java.lang.Boolean import java.lang.Byte import java.lang.Double @@ -26,7 +35,6 @@ import java.lang.Float import java.lang.Long import java.lang.Short - val JcClassType.constructors get() = declaredMethods.filter { it.method.isConstructor } /** @@ -65,14 +73,14 @@ fun JcType.unboxIfNeeded(): JcType { @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") fun JcType.autoboxIfNeeded(): JcType { return when (this) { - classpath.boolean -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.byte -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.boolean -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.byte -> classpath.findTypeOrNull() ?: throwClassNotFound() classpath.char -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.short -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.short -> classpath.findTypeOrNull() ?: throwClassNotFound() classpath.int -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.long -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.float -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.double -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.long -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.float -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.double -> classpath.findTypeOrNull() ?: throwClassNotFound() else -> this } } @@ -156,7 +164,6 @@ fun JcType.isAssignable(declaration: JcType): kotlin.Boolean { } } - /** * find field by name * @@ -187,14 +194,15 @@ fun JcClassType.findMethodOrNull(predicate: (JcTypedMethod) -> kotlin.Boolean): return methods.firstOrNull(predicate) } -val JcTypedMethod.humanReadableSignature: String get() { - val params = parameters.joinToString(",") { it.type.typeName } - val generics = typeParameters.takeIf { it.isNotEmpty() }?.let{ - it.joinToString(prefix = "<", separator = ",", postfix = ">") { it.symbol } - } ?: "" - return "${enclosingType.typeName}#$generics$name($params):${returnType.typeName}" -} +val JcTypedMethod.humanReadableSignature: String + get() { + val params = parameters.joinToString(",") { it.type.typeName } + val generics = typeParameters.takeIf { it.isNotEmpty() }?.let { + it.joinToString(prefix = "<", separator = ",", postfix = ">") { it.symbol } + } ?: "" + return "${enclosingType.typeName}#$generics$name($params):${returnType.typeName}" + } fun JcClasspath.findType(name: String): JcType { return findTypeOrNull(name) ?: throw TypeNotFoundException(name) -} \ No newline at end of file +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt index c88cd8be6..88d32e5f6 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt @@ -18,14 +18,14 @@ package org.jacodb.api.ext.cfg -import org.jacodb.api.cfg.DefaultJcExprVisitor -import org.jacodb.api.cfg.DefaultJcInstVisitor import org.jacodb.api.cfg.JcArrayAccess import org.jacodb.api.cfg.JcCallExpr import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcExprVisitor import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstList +import org.jacodb.api.cfg.JcInstVisitor import org.jacodb.api.cfg.JcLocal import org.jacodb.api.cfg.JcRawExpr import org.jacodb.api.cfg.JcRawExprVisitor @@ -49,7 +49,6 @@ fun JcInstList.collect(visitor: JcRawInstVisitor): Collection< return instructions.map { it.accept(visitor) } } - fun > JcRawInst.applyAndGet(visitor: T, getter: (T) -> R): R { this.accept(visitor) return getter(visitor) @@ -60,72 +59,65 @@ fun > JcRawExpr.applyAndGet(visitor: T, getter: (T return getter(visitor) } +object FieldRefVisitor : + JcExprVisitor.Default, + JcInstVisitor.Default { -object FieldRefVisitor : DefaultJcExprVisitor, DefaultJcInstVisitor { - - override val defaultExprHandler: (JcExpr) -> JcFieldRef? - get() = { null } + override fun defaultVisitJcExpr(expr: JcExpr): JcFieldRef? { + return expr.operands.filterIsInstance().firstOrNull() + } - override val defaultInstHandler: (JcInst) -> JcFieldRef? - get() = { - it.operands.map { it.accept(this) }.firstOrNull { it != null } - } + override fun defaultVisitJcInst(inst: JcInst): JcFieldRef? { + return inst.operands.map { it.accept(this) }.firstOrNull { it != null } + } override fun visitJcFieldRef(value: JcFieldRef): JcFieldRef { return value } } -object ArrayAccessVisitor : DefaultJcExprVisitor, DefaultJcInstVisitor { +object ArrayAccessVisitor : + JcExprVisitor.Default, + JcInstVisitor.Default { - override val defaultExprHandler: (JcExpr) -> JcArrayAccess? - get() = { - it.operands.filterIsInstance().firstOrNull() - } + override fun defaultVisitJcExpr(expr: JcExpr): JcArrayAccess? { + return expr.operands.filterIsInstance().firstOrNull() + } - override val defaultInstHandler: (JcInst) -> JcArrayAccess? - get() = { - it.operands.map { it.accept(this) }.firstOrNull { it != null } - } + override fun defaultVisitJcInst(inst: JcInst): JcArrayAccess? { + return inst.operands.map { it.accept(this) }.firstOrNull { it != null } + } + override fun visitJcArrayAccess(value: JcArrayAccess): JcArrayAccess { + return value + } } -object CallExprVisitor : DefaultJcInstVisitor { - - override val defaultInstHandler: (JcInst) -> JcCallExpr? - get() = { - it.operands.filterIsInstance().firstOrNull() - } - +object CallExprVisitor : JcInstVisitor.Default { + override fun defaultVisitJcInst(inst: JcInst): JcCallExpr? { + return inst.operands.filterIsInstance().firstOrNull() + } } val JcInst.fieldRef: JcFieldRef? - get() { - return accept(FieldRefVisitor) - } + get() = accept(FieldRefVisitor) -val JcInst.arrayRef: JcArrayAccess? - get() { - return accept(ArrayAccessVisitor) - } +val JcInst.arrayAccess: JcArrayAccess? + get() = accept(ArrayAccessVisitor) val JcInst.callExpr: JcCallExpr? - get() { - return accept(CallExprVisitor) - } + get() = accept(CallExprVisitor) val JcInstList.locals: Set get() { - val resolver = LocalResolver().also {res -> - forEach { it.accept(res) } - } + val resolver = LocalResolver() + forEach { it.accept(resolver) } return resolver.result } val JcInstList.values: Set get() { - val resolver = ValueResolver().also {res -> - forEach { it.accept(res) } - } + val resolver = ValueResolver() + forEach { it.accept(resolver) } return resolver.result - } \ No newline at end of file + } diff --git a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt index 24900399d..8c77acd91 100644 --- a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt +++ b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt @@ -25,10 +25,10 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import mu.KLogging import org.jacodb.analysis.AnalysisConfig +import org.jacodb.analysis.engine.MethodUnitResolver import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.VulnerabilityInstance import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.MethodUnitResolver import org.jacodb.analysis.library.UnusedVariableRunnerFactory import org.jacodb.analysis.library.newNpeRunnerFactory import org.jacodb.analysis.library.newSqlInjectionRunnerFactory @@ -50,9 +50,13 @@ class AnalysisMain { fun run(args: List) = main(args.toTypedArray()) } -fun launchAnalysesByConfig(config: AnalysisConfig, graph: JcApplicationGraph, methods: List): List> { +fun launchAnalysesByConfig( + config: AnalysisConfig, + graph: JcApplicationGraph, + methods: List, +): List> { return config.analyses.mapNotNull { (analysis, options) -> - val unitResolver = options["UnitResolver"]?.let { + val unitResolver: UnitResolver = options["UnitResolver"]?.let { UnitResolver.getByName(it) } ?: MethodUnitResolver @@ -71,7 +75,6 @@ fun launchAnalysesByConfig(config: AnalysisConfig, graph: JcApplicationGraph, me } } - fun main(args: Array) { val parser = ArgParser("taint-analysis") val configFilePath by parser.option( @@ -155,4 +158,4 @@ fun main(args: Array) { outputFile.outputStream().use { fileOutputStream -> report.encodeToStream(fileOutputStream) } -} \ No newline at end of file +} diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt index abfb2c433..d655ea14e 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt @@ -19,7 +19,23 @@ package org.jacodb.impl.analysis.impl import org.jacodb.api.JcClassType import org.jacodb.api.JcClasspath import org.jacodb.api.PredefinedPrimitives -import org.jacodb.api.cfg.* +import org.jacodb.api.cfg.BsmStringArg +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcCatchInst +import org.jacodb.api.cfg.JcDynamicCallExpr +import org.jacodb.api.cfg.JcGotoInst +import org.jacodb.api.cfg.JcIfInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstList +import org.jacodb.api.cfg.JcInstRef +import org.jacodb.api.cfg.JcInstVisitor +import org.jacodb.api.cfg.JcLocalVar +import org.jacodb.api.cfg.JcStaticCallExpr +import org.jacodb.api.cfg.JcStringConstant +import org.jacodb.api.cfg.JcSwitchInst +import org.jacodb.api.cfg.JcValue +import org.jacodb.api.cfg.JcVirtualCallExpr +import org.jacodb.api.cfg.values import org.jacodb.api.ext.autoboxIfNeeded import org.jacodb.api.ext.findTypeOrNull import org.jacodb.impl.cfg.JcInstListImpl @@ -27,16 +43,19 @@ import org.jacodb.impl.cfg.VirtualMethodRefImpl import org.jacodb.impl.cfg.methodRef import kotlin.collections.set -class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list: JcInstList) : - DefaultJcInstVisitor { +class StringConcatSimplifierTransformer( + classpath: JcClasspath, + private val list: JcInstList, +) : JcInstVisitor.Default { - override val defaultInstHandler: (JcInst) -> JcInst - get() = { it } + override fun defaultVisitJcInst(inst: JcInst): JcInst { + return inst + } - private val instructionReplacements = mutableMapOf() - private val instructions = mutableListOf() - private val catchReplacements = mutableMapOf>() - private val instructionIndices = mutableMapOf() + private val instructionReplacements: MutableMap = mutableMapOf() + private val instructions: MutableList = mutableListOf() + private val catchReplacements: MutableMap> = mutableMapOf() + private val instructionIndices: MutableMap = mutableMapOf() private val stringType = classpath.findTypeOrNull() as JcClassType @@ -127,11 +146,11 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list } private fun indexOf(instRef: JcInstRef) = JcInstRef( - instructionIndices[instructionReplacements.getOrDefault(list.get(instRef.index), list.get(instRef.index))] ?: -1 + instructionIndices[instructionReplacements.getOrDefault(list[instRef.index], list[instRef.index])] ?: -1 ) private fun indicesOf(instRef: JcInstRef): List { - val index = list.get(instRef.index) + val index = list[instRef.index] return catchReplacements.getOrDefault(index, listOf(index)).map { JcInstRef(instructions.indexOf(it)) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt index ee949291d..a3b895c0a 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt @@ -24,11 +24,7 @@ import info.leadinglight.jdot.enums.Shape import info.leadinglight.jdot.impl.Util import org.jacodb.api.JcClassType import org.jacodb.api.JcClasspath -import org.jacodb.api.JcInstExtFeature -import org.jacodb.api.JcMethod import org.jacodb.api.PredefinedPrimitives -import org.jacodb.api.cfg.DefaultJcExprVisitor -import org.jacodb.api.cfg.DefaultJcInstVisitor import org.jacodb.api.cfg.JcArrayAccess import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcBasicBlock @@ -39,17 +35,17 @@ import org.jacodb.api.cfg.JcDivExpr import org.jacodb.api.cfg.JcDynamicCallExpr import org.jacodb.api.cfg.JcExitMonitorInst import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcExprVisitor import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcGotoInst import org.jacodb.api.cfg.JcGraph import org.jacodb.api.cfg.JcIfInst import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstList +import org.jacodb.api.cfg.JcInstVisitor import org.jacodb.api.cfg.JcLambdaExpr import org.jacodb.api.cfg.JcLengthExpr import org.jacodb.api.cfg.JcNewArrayExpr import org.jacodb.api.cfg.JcNewExpr -import org.jacodb.api.cfg.JcRawInst import org.jacodb.api.cfg.JcRemExpr import org.jacodb.api.cfg.JcSpecialCallExpr import org.jacodb.api.cfg.JcStaticCallExpr @@ -142,7 +138,6 @@ fun JcGraph.toFile(dotCmd: String, viewCatchConnections: Boolean = false, file: return resultingFile } - fun JcBlockGraph.view(dotCmd: String, viewerCmd: String) { Util.sh(arrayOf(viewerCmd, "file://${toFile(dotCmd)}")) } @@ -225,19 +220,24 @@ fun JcBlockGraph.toFile(dotCmd: String, file: File? = null): Path { * - all the declared checked exception types * - 'java.lang.Throwable' for any potential unchecked types */ -open class JcExceptionResolver(val classpath: JcClasspath) : DefaultJcExprVisitor>, - DefaultJcInstVisitor> { +open class JcExceptionResolver( + val classpath: JcClasspath, +) : JcExprVisitor.Default>, + JcInstVisitor.Default> { + private val throwableType = classpath.findTypeOrNull() as JcClassType private val errorType = classpath.findTypeOrNull() as JcClassType private val runtimeExceptionType = classpath.findTypeOrNull() as JcClassType private val nullPointerExceptionType = classpath.findTypeOrNull() as JcClassType private val arithmeticExceptionType = classpath.findTypeOrNull() as JcClassType - override val defaultExprHandler: (JcExpr) -> List - get() = { emptyList() } + override fun defaultVisitJcExpr(expr: JcExpr): List { + return emptyList() + } - override val defaultInstHandler: (JcInst) -> List - get() = { emptyList() } + override fun defaultVisitJcInst(inst: JcInst): List { + return emptyList() + } override fun visitJcAssignInst(inst: JcAssignInst): List { return inst.lhv.accept(this) + inst.rhv.accept(this) @@ -338,4 +338,4 @@ open class JcExceptionResolver(val classpath: JcClasspath) : DefaultJcExprVisito } } -} \ No newline at end of file +} diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt index b7d28f775..8adb41329 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt @@ -47,8 +47,7 @@ class JcGraphImpl( private val exceptionResolver = JcExceptionResolver(classpath) override val entry: JcInst get() = instructions.first() - override val exits: List get() = instructions.filterIsInstance() - + override val exits: List by lazy { instructions.filterIsInstance() } /** * returns a map of possible exceptions that may be thrown from this method @@ -106,15 +105,15 @@ class JcGraphImpl( /** * `successors` and `predecessors` represent normal control flow */ - override fun successors(node: JcInst): Set = successorMap.getOrDefault(node, emptySet()) - override fun predecessors(node: JcInst): Set = predecessorMap.getOrDefault(node, emptySet()) + override fun successors(node: JcInst): Set = successorMap[node] ?: emptySet() + override fun predecessors(node: JcInst): Set = predecessorMap[node] ?: emptySet() /** * `throwers` and `catchers` represent control flow when an exception occurs * `throwers` returns an empty set for every instruction except `JcCatchInst` */ - override fun throwers(node: JcInst): Set = throwPredecessors.getOrDefault(node, emptySet()) - override fun catchers(node: JcInst): Set = throwSuccessors.getOrDefault(node, emptySet()) + override fun throwers(node: JcInst): Set = throwPredecessors[node] ?: emptySet() + override fun catchers(node: JcInst): Set = throwSuccessors[node] ?: emptySet() override fun previous(inst: JcInstRef): JcInst = previous(inst(inst)) override fun next(inst: JcInstRef): JcInst = next(inst(inst)) @@ -140,7 +139,6 @@ class JcGraphImpl( override fun iterator(): Iterator = instructions.iterator() - private fun MutableMap>.add(key: KEY, value: VALUE) { val current = this[key] if (current == null) { @@ -151,18 +149,17 @@ class JcGraphImpl( } } - -fun JcGraph.filter(visitor: JcInstVisitor) = +fun JcGraph.filter(visitor: JcInstVisitor): JcGraph = JcGraphImpl(method, instructions.filter { it.accept(visitor) }) -fun JcGraph.filterNot(visitor: JcInstVisitor) = +fun JcGraph.filterNot(visitor: JcInstVisitor): JcGraph = JcGraphImpl(method, instructions.filterNot { it.accept(visitor) }) -fun JcGraph.map(visitor: JcInstVisitor) = +fun JcGraph.map(visitor: JcInstVisitor): JcGraph = JcGraphImpl(method, instructions.map { it.accept(visitor) }) -fun JcGraph.mapNotNull(visitor: JcInstVisitor) = +fun JcGraph.mapNotNull(visitor: JcInstVisitor): JcGraph = JcGraphImpl(method, instructions.mapNotNull { it.accept(visitor) }) -fun JcGraph.flatMap(visitor: JcInstVisitor>) = +fun JcGraph.flatMap(visitor: JcInstVisitor>): JcGraph = JcGraphImpl(method, instructions.flatMap { it.accept(visitor) }) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index 41cfe704c..a8ac8645a 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -16,15 +16,168 @@ package org.jacodb.impl.cfg -import org.jacodb.api.* -import org.jacodb.api.cfg.* -import org.jacodb.api.ext.* +import org.jacodb.api.JcClassType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.JcType +import org.jacodb.api.TypeName +import org.jacodb.api.cfg.BsmHandle +import org.jacodb.api.cfg.BsmMethodTypeArg +import org.jacodb.api.cfg.JcAddExpr +import org.jacodb.api.cfg.JcAndExpr +import org.jacodb.api.cfg.JcArgument +import org.jacodb.api.cfg.JcArrayAccess +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcBinaryExpr +import org.jacodb.api.cfg.JcBool +import org.jacodb.api.cfg.JcByte +import org.jacodb.api.cfg.JcCallExpr +import org.jacodb.api.cfg.JcCallInst +import org.jacodb.api.cfg.JcCastExpr +import org.jacodb.api.cfg.JcCatchInst +import org.jacodb.api.cfg.JcChar +import org.jacodb.api.cfg.JcClassConstant +import org.jacodb.api.cfg.JcCmpExpr +import org.jacodb.api.cfg.JcCmpgExpr +import org.jacodb.api.cfg.JcCmplExpr +import org.jacodb.api.cfg.JcConditionExpr +import org.jacodb.api.cfg.JcDivExpr +import org.jacodb.api.cfg.JcDouble +import org.jacodb.api.cfg.JcDynamicCallExpr +import org.jacodb.api.cfg.JcEnterMonitorInst +import org.jacodb.api.cfg.JcEqExpr +import org.jacodb.api.cfg.JcExitMonitorInst +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcFieldRef +import org.jacodb.api.cfg.JcFloat +import org.jacodb.api.cfg.JcGeExpr +import org.jacodb.api.cfg.JcGotoInst +import org.jacodb.api.cfg.JcGtExpr +import org.jacodb.api.cfg.JcIfInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstList +import org.jacodb.api.cfg.JcInstLocation +import org.jacodb.api.cfg.JcInstRef +import org.jacodb.api.cfg.JcInstanceOfExpr +import org.jacodb.api.cfg.JcInt +import org.jacodb.api.cfg.JcLambdaExpr +import org.jacodb.api.cfg.JcLeExpr +import org.jacodb.api.cfg.JcLengthExpr +import org.jacodb.api.cfg.JcLocalVar +import org.jacodb.api.cfg.JcLong +import org.jacodb.api.cfg.JcLtExpr +import org.jacodb.api.cfg.JcMethodConstant +import org.jacodb.api.cfg.JcMethodType +import org.jacodb.api.cfg.JcMulExpr +import org.jacodb.api.cfg.JcNegExpr +import org.jacodb.api.cfg.JcNeqExpr +import org.jacodb.api.cfg.JcNewArrayExpr +import org.jacodb.api.cfg.JcNewExpr +import org.jacodb.api.cfg.JcNullConstant +import org.jacodb.api.cfg.JcOrExpr +import org.jacodb.api.cfg.JcRawAddExpr +import org.jacodb.api.cfg.JcRawAndExpr +import org.jacodb.api.cfg.JcRawArgument +import org.jacodb.api.cfg.JcRawArrayAccess +import org.jacodb.api.cfg.JcRawAssignInst +import org.jacodb.api.cfg.JcRawBinaryExpr +import org.jacodb.api.cfg.JcRawBool +import org.jacodb.api.cfg.JcRawByte +import org.jacodb.api.cfg.JcRawCallInst +import org.jacodb.api.cfg.JcRawCastExpr +import org.jacodb.api.cfg.JcRawCatchInst +import org.jacodb.api.cfg.JcRawChar +import org.jacodb.api.cfg.JcRawClassConstant +import org.jacodb.api.cfg.JcRawCmpExpr +import org.jacodb.api.cfg.JcRawCmpgExpr +import org.jacodb.api.cfg.JcRawCmplExpr +import org.jacodb.api.cfg.JcRawDivExpr +import org.jacodb.api.cfg.JcRawDouble +import org.jacodb.api.cfg.JcRawDynamicCallExpr +import org.jacodb.api.cfg.JcRawEnterMonitorInst +import org.jacodb.api.cfg.JcRawEqExpr +import org.jacodb.api.cfg.JcRawExitMonitorInst +import org.jacodb.api.cfg.JcRawExprVisitor +import org.jacodb.api.cfg.JcRawFieldRef +import org.jacodb.api.cfg.JcRawFloat +import org.jacodb.api.cfg.JcRawGeExpr +import org.jacodb.api.cfg.JcRawGotoInst +import org.jacodb.api.cfg.JcRawGtExpr +import org.jacodb.api.cfg.JcRawIfInst +import org.jacodb.api.cfg.JcRawInst +import org.jacodb.api.cfg.JcRawInstVisitor +import org.jacodb.api.cfg.JcRawInstanceOfExpr +import org.jacodb.api.cfg.JcRawInt +import org.jacodb.api.cfg.JcRawInterfaceCallExpr +import org.jacodb.api.cfg.JcRawLabelInst +import org.jacodb.api.cfg.JcRawLabelRef +import org.jacodb.api.cfg.JcRawLeExpr +import org.jacodb.api.cfg.JcRawLengthExpr +import org.jacodb.api.cfg.JcRawLineNumberInst +import org.jacodb.api.cfg.JcRawLocalVar +import org.jacodb.api.cfg.JcRawLong +import org.jacodb.api.cfg.JcRawLtExpr +import org.jacodb.api.cfg.JcRawMethodConstant +import org.jacodb.api.cfg.JcRawMethodType +import org.jacodb.api.cfg.JcRawMulExpr +import org.jacodb.api.cfg.JcRawNegExpr +import org.jacodb.api.cfg.JcRawNeqExpr +import org.jacodb.api.cfg.JcRawNewArrayExpr +import org.jacodb.api.cfg.JcRawNewExpr +import org.jacodb.api.cfg.JcRawNullConstant +import org.jacodb.api.cfg.JcRawOrExpr +import org.jacodb.api.cfg.JcRawRemExpr +import org.jacodb.api.cfg.JcRawReturnInst +import org.jacodb.api.cfg.JcRawShlExpr +import org.jacodb.api.cfg.JcRawShort +import org.jacodb.api.cfg.JcRawShrExpr +import org.jacodb.api.cfg.JcRawSpecialCallExpr +import org.jacodb.api.cfg.JcRawStaticCallExpr +import org.jacodb.api.cfg.JcRawStringConstant +import org.jacodb.api.cfg.JcRawSubExpr +import org.jacodb.api.cfg.JcRawSwitchInst +import org.jacodb.api.cfg.JcRawThis +import org.jacodb.api.cfg.JcRawThrowInst +import org.jacodb.api.cfg.JcRawUshrExpr +import org.jacodb.api.cfg.JcRawVirtualCallExpr +import org.jacodb.api.cfg.JcRawXorExpr +import org.jacodb.api.cfg.JcRemExpr +import org.jacodb.api.cfg.JcReturnInst +import org.jacodb.api.cfg.JcShlExpr +import org.jacodb.api.cfg.JcShort +import org.jacodb.api.cfg.JcShrExpr +import org.jacodb.api.cfg.JcSpecialCallExpr +import org.jacodb.api.cfg.JcStaticCallExpr +import org.jacodb.api.cfg.JcStringConstant +import org.jacodb.api.cfg.JcSubExpr +import org.jacodb.api.cfg.JcSwitchInst +import org.jacodb.api.cfg.JcThis +import org.jacodb.api.cfg.JcThrowInst +import org.jacodb.api.cfg.JcUshrExpr +import org.jacodb.api.cfg.JcValue +import org.jacodb.api.cfg.JcVirtualCallExpr +import org.jacodb.api.cfg.JcXorExpr +import org.jacodb.api.ext.boolean +import org.jacodb.api.ext.byte +import org.jacodb.api.ext.char +import org.jacodb.api.ext.double +import org.jacodb.api.ext.findTypeOrNull +import org.jacodb.api.ext.float +import org.jacodb.api.ext.int +import org.jacodb.api.ext.long +import org.jacodb.api.ext.objectType +import org.jacodb.api.ext.short +import org.jacodb.api.ext.toType import org.jacodb.impl.cfg.util.UNINIT_THIS import org.jacodb.impl.cfg.util.lambdaMetaFactory import org.jacodb.impl.cfg.util.lambdaMetaFactoryMethodName /** This class stores state and is NOT THREAD SAFE. Use it carefully */ -class JcInstListBuilder(val method: JcMethod,val instList: JcInstList) : JcRawInstVisitor, JcRawExprVisitor { +class JcInstListBuilder( + val method: JcMethod, + val instList: JcInstList, +) : JcRawInstVisitor, + JcRawExprVisitor { val classpath: JcClasspath = method.enclosingClass.classpath @@ -175,7 +328,7 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList private fun convertBinary( expr: JcRawBinaryExpr, - handler: (JcType, JcValue, JcValue) -> JcBinaryExpr + handler: (JcType, JcValue, JcValue) -> JcBinaryExpr, ): JcBinaryExpr { val type = expr.typeName.asType() val lhv = expr.lhv.accept(this) as JcValue @@ -243,9 +396,8 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList override fun visitJcRawXorExpr(expr: JcRawXorExpr): JcExpr = convertBinary(expr) { type, lhv, rhv -> JcXorExpr(type, lhv, rhv) } - override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): JcExpr { - return JcLengthExpr(classpath.int, expr.array.accept(this) as JcValue) - } + override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): JcExpr = + JcLengthExpr(classpath.int, expr.array.accept(this) as JcValue) override fun visitJcRawNegExpr(expr: JcRawNegExpr): JcExpr = JcNegExpr(expr.typeName.asType(), expr.operand.accept(this) as JcValue) @@ -253,10 +405,14 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList override fun visitJcRawCastExpr(expr: JcRawCastExpr): JcExpr = JcCastExpr(expr.typeName.asType(), expr.operand.accept(this) as JcValue) - override fun visitJcRawNewExpr(expr: JcRawNewExpr): JcExpr = JcNewExpr(expr.typeName.asType()) + override fun visitJcRawNewExpr(expr: JcRawNewExpr): JcExpr = + JcNewExpr(expr.typeName.asType()) override fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): JcExpr = - JcNewArrayExpr(expr.typeName.asType(), expr.dimensions.map { it.accept(this) as JcValue }) + JcNewArrayExpr( + expr.typeName.asType(), + expr.dimensions.map { it.accept(this) as JcValue } + ) override fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): JcExpr = JcInstanceOfExpr(classpath.boolean, expr.operand.accept(this) as JcValue, expr.targetType.asType()) @@ -397,4 +553,4 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList value.typeName.asType() ) } -} \ No newline at end of file +} diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt index 8fe7a5485..878e4a19a 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt @@ -23,22 +23,21 @@ import org.jacodb.api.cfg.JcRawInstVisitor import org.jacodb.api.cfg.JcRawLabelInst open class JcInstListImpl( - instructions: List + instructions: List, ) : Iterable, JcInstList { - protected val _instructions = instructions.toMutableList() - + protected val _instructions: MutableList = instructions.toMutableList() override val instructions: List get() = _instructions - override val size get() = instructions.size - override val indices get() = instructions.indices - override val lastIndex get() = instructions.lastIndex + override val size: Int get() = instructions.size + override val indices: IntRange get() = instructions.indices + override val lastIndex: Int get() = instructions.lastIndex + + override operator fun get(index: Int): INST = instructions[index] + override fun getOrNull(index: Int): INST? = instructions.getOrNull(index) - override operator fun get(index: Int) = instructions[index] - override fun getOrNull(index: Int) = instructions.getOrNull(index) - fun getOrElse(index: Int, defaultValue: (Int) -> INST) = instructions.getOrElse(index, defaultValue) override fun iterator(): Iterator = instructions.iterator() - override fun toMutableList() = JcMutableInstListImpl(_instructions) + override fun toMutableList(): JcMutableInstList = JcMutableInstListImpl(_instructions) override fun toString(): String = _instructions.joinToString(separator = "\n") { when (it) { @@ -48,8 +47,8 @@ open class JcInstListImpl( } } -class JcMutableInstListImpl(instructions: List) : JcInstListImpl(instructions), - JcMutableInstList { +class JcMutableInstListImpl(instructions: List) : + JcInstListImpl(instructions), JcMutableInstList { override fun insertBefore(inst: INST, vararg newInstructions: INST) = insertBefore(inst, newInstructions.toList()) override fun insertBefore(inst: INST, newInstructions: Collection) { @@ -58,7 +57,7 @@ class JcMutableInstListImpl(instructions: List) : JcInstListImpl) { val index = _instructions.indexOf(inst) assert(index >= 0) @@ -74,18 +73,17 @@ class JcMutableInstListImpl(instructions: List) : JcInstListImpl.filter(visitor: JcRawInstVisitor) = +fun JcInstList.filter(visitor: JcRawInstVisitor): JcInstList = JcInstListImpl(instructions.filter { it.accept(visitor) }) -fun JcInstList.filterNot(visitor: JcRawInstVisitor) = +fun JcInstList.filterNot(visitor: JcRawInstVisitor): JcInstList = JcInstListImpl(instructions.filterNot { it.accept(visitor) }) -fun JcInstList.map(visitor: JcRawInstVisitor) = +fun JcInstList.map(visitor: JcRawInstVisitor): JcInstList = JcInstListImpl(instructions.map { it.accept(visitor) }) -fun JcInstList.mapNotNull(visitor: JcRawInstVisitor) = +fun JcInstList.mapNotNull(visitor: JcRawInstVisitor): JcInstList = JcInstListImpl(instructions.mapNotNull { it.accept(visitor) }) -fun JcInstList.flatMap(visitor: JcRawInstVisitor>) = +fun JcInstList.flatMap(visitor: JcRawInstVisitor>): JcInstList = JcInstListImpl(instructions.flatMap { it.accept(visitor) }) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt index d0c8137a5..ca1217950 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt @@ -20,14 +20,115 @@ import org.jacodb.api.JcMethod import org.jacodb.api.JcParameter import org.jacodb.api.PredefinedPrimitives import org.jacodb.api.TypeName -import org.jacodb.api.cfg.* -import org.jacodb.impl.cfg.util.* +import org.jacodb.api.cfg.BsmDoubleArg +import org.jacodb.api.cfg.BsmFloatArg +import org.jacodb.api.cfg.BsmHandle +import org.jacodb.api.cfg.BsmIntArg +import org.jacodb.api.cfg.BsmLongArg +import org.jacodb.api.cfg.BsmMethodTypeArg +import org.jacodb.api.cfg.BsmStringArg +import org.jacodb.api.cfg.BsmTypeArg +import org.jacodb.api.cfg.JcInstList +import org.jacodb.api.cfg.JcRawAddExpr +import org.jacodb.api.cfg.JcRawAndExpr +import org.jacodb.api.cfg.JcRawArgument +import org.jacodb.api.cfg.JcRawArrayAccess +import org.jacodb.api.cfg.JcRawAssignInst +import org.jacodb.api.cfg.JcRawBool +import org.jacodb.api.cfg.JcRawByte +import org.jacodb.api.cfg.JcRawCallExpr +import org.jacodb.api.cfg.JcRawCallInst +import org.jacodb.api.cfg.JcRawCastExpr +import org.jacodb.api.cfg.JcRawCatchInst +import org.jacodb.api.cfg.JcRawChar +import org.jacodb.api.cfg.JcRawClassConstant +import org.jacodb.api.cfg.JcRawCmpExpr +import org.jacodb.api.cfg.JcRawCmpgExpr +import org.jacodb.api.cfg.JcRawCmplExpr +import org.jacodb.api.cfg.JcRawComplexValue +import org.jacodb.api.cfg.JcRawDivExpr +import org.jacodb.api.cfg.JcRawDouble +import org.jacodb.api.cfg.JcRawDynamicCallExpr +import org.jacodb.api.cfg.JcRawEnterMonitorInst +import org.jacodb.api.cfg.JcRawEqExpr +import org.jacodb.api.cfg.JcRawExitMonitorInst +import org.jacodb.api.cfg.JcRawExpr +import org.jacodb.api.cfg.JcRawExprVisitor +import org.jacodb.api.cfg.JcRawFieldRef +import org.jacodb.api.cfg.JcRawFloat +import org.jacodb.api.cfg.JcRawGeExpr +import org.jacodb.api.cfg.JcRawGotoInst +import org.jacodb.api.cfg.JcRawGtExpr +import org.jacodb.api.cfg.JcRawIfInst +import org.jacodb.api.cfg.JcRawInst +import org.jacodb.api.cfg.JcRawInstVisitor +import org.jacodb.api.cfg.JcRawInstanceOfExpr +import org.jacodb.api.cfg.JcRawInt +import org.jacodb.api.cfg.JcRawInterfaceCallExpr +import org.jacodb.api.cfg.JcRawLabelInst +import org.jacodb.api.cfg.JcRawLabelRef +import org.jacodb.api.cfg.JcRawLeExpr +import org.jacodb.api.cfg.JcRawLengthExpr +import org.jacodb.api.cfg.JcRawLineNumberInst +import org.jacodb.api.cfg.JcRawLocalVar +import org.jacodb.api.cfg.JcRawLong +import org.jacodb.api.cfg.JcRawLtExpr +import org.jacodb.api.cfg.JcRawMethodConstant +import org.jacodb.api.cfg.JcRawMethodType +import org.jacodb.api.cfg.JcRawMulExpr +import org.jacodb.api.cfg.JcRawNegExpr +import org.jacodb.api.cfg.JcRawNeqExpr +import org.jacodb.api.cfg.JcRawNewArrayExpr +import org.jacodb.api.cfg.JcRawNewExpr +import org.jacodb.api.cfg.JcRawNullConstant +import org.jacodb.api.cfg.JcRawOrExpr +import org.jacodb.api.cfg.JcRawRemExpr +import org.jacodb.api.cfg.JcRawReturnInst +import org.jacodb.api.cfg.JcRawShlExpr +import org.jacodb.api.cfg.JcRawShort +import org.jacodb.api.cfg.JcRawShrExpr +import org.jacodb.api.cfg.JcRawSpecialCallExpr +import org.jacodb.api.cfg.JcRawStaticCallExpr +import org.jacodb.api.cfg.JcRawStringConstant +import org.jacodb.api.cfg.JcRawSubExpr +import org.jacodb.api.cfg.JcRawSwitchInst +import org.jacodb.api.cfg.JcRawThis +import org.jacodb.api.cfg.JcRawThrowInst +import org.jacodb.api.cfg.JcRawUshrExpr +import org.jacodb.api.cfg.JcRawValue +import org.jacodb.api.cfg.JcRawVirtualCallExpr +import org.jacodb.api.cfg.JcRawXorExpr +import org.jacodb.impl.cfg.util.elementType +import org.jacodb.impl.cfg.util.internalDesc +import org.jacodb.impl.cfg.util.isDWord +import org.jacodb.impl.cfg.util.isPrimitive +import org.jacodb.impl.cfg.util.jvmClassName +import org.jacodb.impl.cfg.util.jvmTypeName +import org.jacodb.impl.cfg.util.typeName import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes.H_GETSTATIC import org.objectweb.asm.Type -import org.objectweb.asm.tree.* -import java.util.ArrayList +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.InsnList +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.InvokeDynamicInsnNode +import org.objectweb.asm.tree.JumpInsnNode +import org.objectweb.asm.tree.LabelNode +import org.objectweb.asm.tree.LdcInsnNode +import org.objectweb.asm.tree.LineNumberNode +import org.objectweb.asm.tree.LocalVariableNode +import org.objectweb.asm.tree.LookupSwitchInsnNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.MultiANewArrayInsnNode +import org.objectweb.asm.tree.ParameterNode +import org.objectweb.asm.tree.TableSwitchInsnNode +import org.objectweb.asm.tree.TryCatchBlockNode +import org.objectweb.asm.tree.TypeInsnNode +import org.objectweb.asm.tree.VarInsnNode private val PredefinedPrimitives.smallIntegers get() = setOf(Boolean, Byte, Char, Short, Int) @@ -67,7 +168,7 @@ private val TypeName.typeInt class MethodNodeBuilder( val method: JcMethod, - val instList: JcInstList + val instList: JcInstList, ) : JcRawInstVisitor, JcRawExprVisitor { private var localIndex = 0 private var stackSize = 0 @@ -98,7 +199,7 @@ class MethodNodeBuilder( mn.tryCatchBlocks = tryCatchNodeList mn.maxLocals = localIndex mn.maxStack = maxStack + 1 - //At this moment, we're just copying annotations from original method without any modifications + // At this moment, we're just copying annotations from original method without any modifications with(method.asmNode()) { mn.visibleAnnotations = visibleAnnotations mn.visibleTypeAnnotations = visibleTypeAnnotations @@ -597,7 +698,6 @@ class MethodNodeBuilder( currentInsnList.add(TypeInsnNode(Opcodes.INSTANCEOF, expr.targetType.internalDesc)) } - private val BsmHandle.asAsmHandle: Handle get() = Handle( tag, @@ -773,7 +873,7 @@ class MethodNodeBuilder( override fun visitJcRawInt(value: JcRawInt) { currentInsnList.add( - when (value.value as Comparable) { + when (value.value) { in -1..5 -> InsnNode(Opcodes.ICONST_0 + value.value) in Byte.MIN_VALUE..Byte.MAX_VALUE -> IntInsnNode(Opcodes.BIPUSH, value.value) in Short.MIN_VALUE..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, value.value) @@ -795,10 +895,16 @@ class MethodNodeBuilder( override fun visitJcRawFloat(value: JcRawFloat) { currentInsnList.add( - when (value.value as Comparable) { - 0.0F -> InsnNode(Opcodes.FCONST_0) - 1.0F -> InsnNode(Opcodes.FCONST_1) - 2.0F -> InsnNode(Opcodes.FCONST_2) + // Note: The case to Any forces Float to box, which is necessary here + // since 0.0 == -0.0`, but `Box(0.0) != Box(-0.0)`. + // The latter is preferred here because we only want to + // "push constant 0.0f onto the stack" (`fconst_0` instruction) + // when `value.value` is exactly "zero" (0.0), NOT "negative zero" (-0.0). + @Suppress("USELESS_CAST") + when (value.value as Any) { + 0.0f -> InsnNode(Opcodes.FCONST_0) + 1.0f -> InsnNode(Opcodes.FCONST_1) + 2.0f -> InsnNode(Opcodes.FCONST_2) else -> LdcInsnNode(value.value) } ) @@ -807,7 +913,13 @@ class MethodNodeBuilder( override fun visitJcRawDouble(value: JcRawDouble) { currentInsnList.add( - when (value.value as Comparable) { + // Note: The case to Any forces Double to box, which is necessary here + // since 0.0 == -0.0`, but `Box(0.0) != Box(-0.0)`. + // The latter is preferred here because we only want to + // "push constant 0.0d onto the stack" (`dconst_0` instruction) + // when `value.value` is exactly "zero" (0.0), NOT "negative zero" (-0.0). + @Suppress("USELESS_CAST") + when (value.value as Any) { 0.0 -> InsnNode(Opcodes.DCONST_0) 1.0 -> InsnNode(Opcodes.DCONST_1) else -> LdcInsnNode(value.value) @@ -839,7 +951,7 @@ class MethodNodeBuilder( error("Could not load method constant $value") } - //We have to insert NOP instructions in empty basic blocks to handle situations with empty handlers of try/catch + // We have to insert NOP instructions in empty basic blocks to handle situations with empty handlers of try/catch private fun insertNopInstructions() { val firstLabelIndex = currentInsnList.indexOfFirst { it is LabelNode } val nodesBetweenLabels = mutableListOf() diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt index ad6b23165..ef581fd3e 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt @@ -16,8 +16,21 @@ package org.jacodb.impl.cfg -import org.jacodb.api.* -import org.jacodb.api.cfg.* +import org.jacodb.api.JcClassType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.JcType +import org.jacodb.api.JcTypedMethod +import org.jacodb.api.MethodNotFoundException +import org.jacodb.api.TypeName +import org.jacodb.api.cfg.JcInstLocation +import org.jacodb.api.cfg.JcRawCallExpr +import org.jacodb.api.cfg.JcRawInstanceExpr +import org.jacodb.api.cfg.JcRawLocal +import org.jacodb.api.cfg.JcRawSpecialCallExpr +import org.jacodb.api.cfg.JcRawStaticCallExpr +import org.jacodb.api.cfg.TypedMethodRef +import org.jacodb.api.cfg.VirtualTypedMethodRef import org.jacodb.api.ext.findType import org.jacodb.api.ext.jvmName import org.jacodb.impl.cfg.util.typeName @@ -26,10 +39,10 @@ import org.jacodb.impl.weakLazy import org.objectweb.asm.Type abstract class MethodSignatureRef( - val type: JcClassType, - override val name: String, - argTypes: List, - returnType: TypeName, + val type: JcClassType, + override val name: String, + argTypes: List, + returnType: TypeName, ) : TypedMethodRef { protected val description: String = buildString { @@ -83,17 +96,17 @@ abstract class MethodSignatureRef( } class TypedStaticMethodRefImpl( - type: JcClassType, - name: String, - argTypes: List, - returnType: TypeName + type: JcClassType, + name: String, + argTypes: List, + returnType: TypeName, ) : MethodSignatureRef(type, name, argTypes, returnType) { constructor(classpath: JcClasspath, raw: JcRawStaticCallExpr) : this( - classpath.findType(raw.declaringClass.typeName) as JcClassType, - raw.methodName, - raw.argumentTypes, - raw.returnType + classpath.findType(raw.declaringClass.typeName) as JcClassType, + raw.methodName, + raw.argumentTypes, + raw.returnType ) override val method: JcTypedMethod by weakLazy { @@ -102,17 +115,17 @@ class TypedStaticMethodRefImpl( } class TypedSpecialMethodRefImpl( - type: JcClassType, - name: String, - argTypes: List, - returnType: TypeName + type: JcClassType, + name: String, + argTypes: List, + returnType: TypeName, ) : MethodSignatureRef(type, name, argTypes, returnType) { constructor(classpath: JcClasspath, raw: JcRawSpecialCallExpr) : this( - classpath.findType(raw.declaringClass.typeName) as JcClassType, - raw.methodName, - raw.argumentTypes, - raw.returnType + classpath.findType(raw.declaringClass.typeName) as JcClassType, + raw.methodName, + raw.argumentTypes, + raw.returnType ) override val method: JcTypedMethod by weakLazy { @@ -122,11 +135,11 @@ class TypedSpecialMethodRefImpl( } class VirtualMethodRefImpl( - type: JcClassType, - private val actualType: JcClassType, - name: String, - argTypes: List, - returnType: TypeName + type: JcClassType, + private val actualType: JcClassType, + name: String, + argTypes: List, + returnType: TypeName, ) : MethodSignatureRef(type, name, argTypes, returnType), VirtualTypedMethodRef { companion object { @@ -147,20 +160,20 @@ class VirtualMethodRefImpl( fun of(classpath: JcClasspath, raw: JcRawCallExpr): VirtualMethodRefImpl { val (declared, actual) = raw.resolvedType(classpath) return VirtualMethodRefImpl( - declared, - actual, - raw.methodName, - raw.argumentTypes, - raw.returnType + declared, + actual, + raw.methodName, + raw.argumentTypes, + raw.returnType ) } fun of(type: JcClassType, method: JcTypedMethod): VirtualMethodRefImpl { return VirtualMethodRefImpl( - type, type, - method.name, - method.method.parameters.map { it.type }, - method.method.returnType + type, type, + method.name, + method.method.parameters.map { it.type }, + method.method.returnType ) } } @@ -174,19 +187,18 @@ class VirtualMethodRefImpl( } } - class TypedMethodRefImpl( - type: JcClassType, - name: String, - argTypes: List, - returnType: TypeName + type: JcClassType, + name: String, + argTypes: List, + returnType: TypeName, ) : MethodSignatureRef(type, name, argTypes, returnType) { constructor(classpath: JcClasspath, raw: JcRawCallExpr) : this( - classpath.findType(raw.declaringClass.typeName) as JcClassType, - raw.methodName, - raw.argumentTypes, - raw.returnType + classpath.findType(raw.declaringClass.typeName) as JcClassType, + raw.methodName, + raw.argumentTypes, + raw.returnType ) override val method: JcTypedMethod by softLazy { @@ -205,17 +217,17 @@ fun JcClasspath.methodRef(expr: JcRawCallExpr): TypedMethodRef { fun JcTypedMethod.methodRef(): TypedMethodRef { return TypedMethodRefImpl( - enclosingType as JcClassType, - method.name, - method.parameters.map { it.type }, - method.returnType + enclosingType as JcClassType, + method.name, + method.parameters.map { it.type }, + method.returnType ) } class JcInstLocationImpl( - override val method: JcMethod, - override val index: Int, - override val lineNumber: Int + override val method: JcMethod, + override val index: Int, + override val lineNumber: Int, ) : JcInstLocation { override fun toString(): String { @@ -238,5 +250,4 @@ class JcInstLocationImpl( return result } - -} \ No newline at end of file +} diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt index a47e16596..3cdd61bba 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt @@ -16,10 +16,13 @@ package org.jacodb.impl.cfg.util -import org.jacodb.api.cfg.DefaultJcRawInstVisitor import org.jacodb.api.cfg.JcRawInst +import org.jacodb.api.cfg.JcRawInstVisitor -class InstructionFilter(val predicate: (JcRawInst) -> Boolean) : DefaultJcRawInstVisitor { - override val defaultInstHandler: (JcRawInst) -> Boolean - get() = { predicate(it) } +class InstructionFilter( + val predicate: (JcRawInst) -> Boolean, +) : JcRawInstVisitor.Default { + override fun defaultVisitJcRawInst(inst: JcRawInst): Boolean { + return predicate(inst) + } } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt index 02e18cfab..c403bafb1 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt @@ -39,7 +39,7 @@ class MethodInstructionsFeature( private val JcMethod.methodFeatures get() = enclosingClass.classpath.features?.filterIsInstance().orEmpty() - + @Synchronized override fun flowGraph(method: JcMethod): JcMethodExtFeature.JcFlowGraphResult { return JcFlowGraphResultImpl(method, JcGraphImpl(method, method.instList.instructions)) } diff --git a/jacodb-core/src/test/java/org/jacodb/testing/JavaApi.java b/jacodb-core/src/test/java/org/jacodb/testing/JavaApi.java index 242ce84ed..145d69a1e 100644 --- a/jacodb-core/src/test/java/org/jacodb/testing/JavaApi.java +++ b/jacodb-core/src/test/java/org/jacodb/testing/JavaApi.java @@ -17,29 +17,28 @@ package org.jacodb.testing; import org.jacodb.api.JcDatabase; -import org.jacodb.api.cfg.JcArgument; -import org.jacodb.api.cfg.JcExpr; -import org.jacodb.api.cfg.TypedExprResolver; import org.jacodb.impl.JacoDB; import org.jacodb.impl.JcCacheSettings; import org.jacodb.impl.JcSettings; import org.jacodb.impl.features.Usages; -import org.jetbrains.annotations.NotNull; import java.time.Duration; import java.time.temporal.ChronoUnit; public class JavaApi { - private static class ArgumentResolver extends TypedExprResolver { - @Override - public void ifMatches(@NotNull JcExpr jcExpr) { - if (jcExpr instanceof JcArgument) { - getResult().add((JcArgument) jcExpr); - } - } - - } + // FIXME: does not compile because of some kinda compiler bug + // (see https://youtrack.jetbrains.com/issue/KT-15964) + // private static class ArgumentResolver extends TypedExprResolver { + // + // @Override + // public void ifMatches(JcExpr jcExpr) { + // if (jcExpr instanceof JcArgument) { + // getResult().add((JcArgument) jcExpr); + // } + // } + // + // } public static void cacheSettings() { new JcCacheSettings().types(10, Duration.of(1, ChronoUnit.MINUTES)); diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt index a092b7fd5..be36d9d91 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt @@ -16,9 +16,30 @@ package org.jacodb.testing.cfg - -import org.jacodb.api.* -import org.jacodb.api.cfg.* +import org.jacodb.api.JavaVersion +import org.jacodb.api.JcClassType +import org.jacodb.api.JcMethod +import org.jacodb.api.JcTypedMethod +import org.jacodb.api.TypeName +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcCallExpr +import org.jacodb.api.cfg.JcCallInst +import org.jacodb.api.cfg.JcCatchInst +import org.jacodb.api.cfg.JcEnterMonitorInst +import org.jacodb.api.cfg.JcExitMonitorInst +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcExprVisitor +import org.jacodb.api.cfg.JcGotoInst +import org.jacodb.api.cfg.JcGraph +import org.jacodb.api.cfg.JcIfInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstVisitor +import org.jacodb.api.cfg.JcReturnInst +import org.jacodb.api.cfg.JcSpecialCallExpr +import org.jacodb.api.cfg.JcSwitchInst +import org.jacodb.api.cfg.JcTerminatingInst +import org.jacodb.api.cfg.JcThrowInst +import org.jacodb.api.cfg.JcVirtualCallExpr import org.jacodb.api.ext.HierarchyExtension import org.jacodb.api.ext.findClass import org.jacodb.api.ext.toType @@ -34,27 +55,42 @@ import org.jacodb.impl.cfg.util.ExprMapper import org.jacodb.impl.features.classpaths.ClasspathCache import org.jacodb.impl.features.classpaths.StringConcatSimplifier import org.jacodb.impl.fs.JarLocation -import org.jacodb.testing.* +import org.jacodb.testing.WithDB +import org.jacodb.testing.asmLib +import org.jacodb.testing.guavaLib +import org.jacodb.testing.kotlinStdLib +import org.jacodb.testing.kotlinxCoroutines import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import java.io.File class OverridesResolver( - private val hierarchyExtension: HierarchyExtension -) : DefaultJcInstVisitor>, DefaultJcExprVisitor> { - override val defaultInstHandler: (JcInst) -> Sequence - get() = { emptySequence() } - override val defaultExprHandler: (JcExpr) -> Sequence - get() = { emptySequence() } - - private fun JcClassType.getMethod(name: String, argTypes: List, returnType: TypeName): JcTypedMethod { + private val hierarchyExtension: HierarchyExtension, +) : JcExprVisitor.Default>, + JcInstVisitor.Default> { + + override fun defaultVisitJcExpr(expr: JcExpr): Sequence { + return emptySequence() + } + + override fun defaultVisitJcInst(inst: JcInst): Sequence { + return emptySequence() + } + + private fun JcClassType.getMethod( + name: String, + argTypes: List, + returnType: TypeName, + ): JcTypedMethod { return methods.firstOrNull { typedMethod -> val jcMethod = typedMethod.method - jcMethod.name == name && - jcMethod.returnType.typeName == returnType.typeName && - jcMethod.parameters.map { param -> param.type.typeName } == argTypes.map { it.typeName } + jcMethod.name == name + && jcMethod.returnType.typeName == returnType.typeName + && jcMethod.parameters.map { param -> param.type.typeName } == argTypes.map { it.typeName } } ?: error("Could not find a method with correct signature") } @@ -64,6 +100,14 @@ class OverridesResolver( return klass.getMethod(name, parameters.map { it.type }, returnType) } + override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): Sequence { + return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } + } + + override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): Sequence { + return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } + } + override fun visitJcAssignInst(inst: JcAssignInst): Sequence { if (inst.rhv is JcCallExpr) return inst.rhv.accept(this) return emptySequence() @@ -73,26 +117,20 @@ class OverridesResolver( return inst.callExpr.accept(this) } - override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): Sequence { - return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } - } - - override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): Sequence { - return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } - } - } -class JcGraphChecker(val method: JcMethod, val jcGraph: JcGraph) : JcInstVisitor { +class JcGraphChecker( + val method: JcMethod, + val jcGraph: JcGraph, +) : JcInstVisitor { + fun check() { try { jcGraph.entry } catch (e: Exception) { println( "Fail on method ${method.enclosingClass.simpleName}#${method.name}(${ - method.parameters.joinToString( - "," - ) { it.type.typeName } + method.parameters.joinToString(",") { it.type.typeName } })" ) throw e @@ -129,6 +167,10 @@ class JcGraphChecker(val method: JcMethod, val jcGraph: JcGraph) : JcInstVisitor } } + override fun visitExternalJcInst(inst: JcInst) { + // Do nothing + } + override fun visitJcAssignInst(inst: JcAssignInst) { if (inst != jcGraph.entry) { assertTrue(jcGraph.predecessors(inst).isNotEmpty()) @@ -246,8 +288,6 @@ class JcGraphChecker(val method: JcMethod, val jcGraph: JcGraph) : JcInstVisitor assertTrue(jcGraph.throwers(inst).isEmpty()) } - override fun visitExternalJcInst(inst: JcInst) { - } } class IRTest : BaseInstructionsTest() { @@ -295,7 +335,6 @@ class IRTest : BaseInstructionsTest() { testClass(cp.findClass()) } - @Test fun `get ir of guava`() { runAlongLib(guavaLib) @@ -316,7 +355,6 @@ class IRTest : BaseInstructionsTest() { runAlongLib(kotlinStdLib, false) } - @AfterEach fun printStats() { cp.features!!.filterIsInstance().forEach { diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/NpeExamples.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/NpeExamples.java index 6679be09d..4476b58fc 100644 --- a/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/NpeExamples.java +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/NpeExamples.java @@ -105,7 +105,7 @@ private void foo(ContainerOfSimpleClass z) { SimpleClassWithField x = z.g; x.field = null; } - + int npeOnLength() { String x = "abc"; String y = "def"; @@ -135,6 +135,19 @@ int checkedAccess(String x) { return -1; } + int checkedAccessWithField() { + SimpleClassWithField x = new SimpleClassWithField("abc"); + int s = 0; + if (x.field != null) { + s += x.field.length(); + } + x.field = null; + if (x.field != null) { + s += x.field.length(); + } + return s; + } + int consecutiveNPEs(String x, boolean flag) { int a = 0; int b = 0; @@ -299,4 +312,4 @@ int noNPEAfterAliasing() { // (because backward alias analysis can only propagate facts back, but can't kill them) return x.field.length(); } -} \ No newline at end of file +} diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/SqlInjectionExamples.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/SqlInjectionExamples.java new file mode 100644 index 000000000..68cd6b4e1 --- /dev/null +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/analysis/SqlInjectionExamples.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.testing.analysis; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +@SuppressWarnings("ALL") +public class SqlInjectionExamples { + + void bad() { + String data = System.getenv("USER"); + try ( + Connection dbConnection = DriverManager.getConnection("", "", ""); + Statement sqlStatement = dbConnection.createStatement(); + ) { + boolean result = sqlStatement.execute("insert into users (status) values ('updated') where name='" + data + "'"); + + if (result) { + System.out.println("User '" + data + "' updated successfully"); + } else { + System.out.println("Unable to update records for user '" + data + "'"); + } + } catch (SQLException e) { + System.err.println("Error: " + e); + } finally { + System.out.println("OK!"); + } + } + +} diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java new file mode 100644 index 000000000..e42fe9e08 --- /dev/null +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java @@ -0,0 +1,20 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.testing.cfg; + +public class LocalResolverExample { +} diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt index 5e91288d9..b373d690f 100644 --- a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt @@ -33,7 +33,6 @@ import kotlin.reflect.full.companionObjectInstance @Tag("lifecycle") annotation class LifecycleTest - abstract class BaseTest { protected open val cp: JcClasspath = runBlocking { @@ -61,9 +60,7 @@ val Class<*>.withDB: JcDatabaseHolder return s.withDB } - interface JcDatabaseHolder { - val classpathFeatures: List val db: JcDatabase fun cleanup() @@ -82,7 +79,17 @@ open class WithDB(vararg features: Any) : JcDatabaseHolder { override var db = runBlocking { jacodb { - // persistent("D:\\work\\jacodb\\jcdb-index.db") + val persistentLocation = System.getenv("JACODB_PERSISTENT") + if (persistentLocation != null) { + persistent(persistentLocation) + } + + // val ci = System.getenv("CI") + // println("CI=$ci") + // if (ci != "true") { + // persistent("/tmp/index.db") + // } + loadByteCode(allClasspath) useProcessJavaRuntime() keepLocalVariableNames() @@ -92,7 +99,7 @@ open class WithDB(vararg features: Any) : JcDatabaseHolder { } } - override fun cleanup() { + override fun cleanup() { db.close() } } @@ -101,7 +108,7 @@ val globalDb by lazy { WithDB(Usages, Builders, InMemoryHierarchy).db } -open class WithGlobalDB(vararg _classpathFeatures: JcClasspathFeature): JcDatabaseHolder { +open class WithGlobalDB(vararg _classpathFeatures: JcClasspathFeature) : JcDatabaseHolder { init { System.setProperty("org.jacodb.impl.storage.defaultBatchSize", "500") @@ -115,8 +122,6 @@ open class WithGlobalDB(vararg _classpathFeatures: JcClasspathFeature): JcDataba } } - - open class WithRestoredDB(vararg features: JcFeature<*, *>) : WithDB(*features) { private val jdbcLocation = Files.createTempFile("jcdb-", null).toFile().absolutePath @@ -143,4 +148,4 @@ open class WithRestoredDB(vararg features: JcFeature<*, *>) : WithDB(*features) } } -} \ No newline at end of file +} diff --git a/jacodb-taint-configuration/build.gradle.kts b/jacodb-taint-configuration/build.gradle.kts index 9acc300bf..f375bfa41 100644 --- a/jacodb-taint-configuration/build.gradle.kts +++ b/jacodb-taint-configuration/build.gradle.kts @@ -11,11 +11,13 @@ dependencies { implementation(project(":jacodb-api")) implementation(project(":jacodb-core")) implementation(testFixtures(project(":jacodb-core"))) - implementation(Libs.kotlinx_serialization_json) - testImplementation(group = "io.github.microutils", name = "kotlin-logging", version = "1.8.3") + implementation(Libs.kotlinx_serialization_core) + implementation(Libs.kotlinx_serialization_json) // for local tests only + + testImplementation(Libs.kotlin_logging) } tasks.test { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt index 4e61cdfb6..c1740a0ca 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt @@ -21,7 +21,7 @@ import org.jacodb.api.ext.packageName class ConfigurationTrie( configuration: List, - private val nameMatcher: (NameMatcher, String) -> Boolean + private val nameMatcher: (NameMatcher, String) -> Boolean, ) { private val unprocessedRules: MutableList = configuration.toMutableList() private val rootNode: RootNode = RootNode() @@ -153,7 +153,7 @@ class ConfigurationTrie( } private data class Leaf( - override val value: String + override val value: String, ) : Node() { override val children: MutableMap get() = error("Leaf nodes do not have children") diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt index 78ec0f1a9..f046475e8 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt @@ -19,25 +19,35 @@ package org.jacodb.taint.configuration import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -interface PositionResolver { +fun interface PositionResolver { fun resolve(position: Position): R } @Serializable sealed interface Position -@Serializable -@SerialName("Argument") -data class Argument(val number: Int) : Position - @Serializable @SerialName("AnyArgument") object AnyArgument : Position +@Serializable +@SerialName("Argument") +data class Argument(@SerialName("number") val index: Int) : Position + @Serializable @SerialName("This") -object ThisArgument : Position +object This : Position { + override fun toString(): String = javaClass.simpleName +} @Serializable @SerialName("Result") -object Result : Position +object Result : Position { + override fun toString(): String = javaClass.simpleName +} + +@Serializable +@SerialName("ResultAnyElement") +object ResultAnyElement : Position { + override fun toString(): String = javaClass.simpleName +} diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt index 1327c441a..a4fdceb6e 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt @@ -18,10 +18,12 @@ package org.jacodb.taint.configuration import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable - +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json @Serializable sealed interface SerializedTaintConfigurationItem { + @SerialName("methodInfo") val methodInfo: FunctionMatcher fun updateMethodInfo(updatedMethodInfo: FunctionMatcher): SerializedTaintConfigurationItem = @@ -37,40 +39,67 @@ sealed interface SerializedTaintConfigurationItem { @Serializable @SerialName("EntryPointSource") data class SerializedTaintEntryPointSource( - override val methodInfo: FunctionMatcher, - val condition: Condition, - val actionsAfter: List, + @SerialName("methodInfo") override val methodInfo: FunctionMatcher, + @SerialName("condition") val condition: Condition, + @SerialName("actionsAfter") val actionsAfter: List, ) : SerializedTaintConfigurationItem @Serializable @SerialName("MethodSource") data class SerializedTaintMethodSource( - override val methodInfo: FunctionMatcher, - val condition: Condition, - val actionsAfter: List, + @SerialName("methodInfo") override val methodInfo: FunctionMatcher, + @SerialName("condition") val condition: Condition, + @SerialName("actionsAfter") val actionsAfter: List, ) : SerializedTaintConfigurationItem @Serializable @SerialName("MethodSink") data class SerializedTaintMethodSink( - val ruleNote: String, - val cwe: List, - override val methodInfo: FunctionMatcher, - val condition: Condition + @SerialName("ruleNote") val ruleNote: String, + @SerialName("cwe") val cwe: List, + @SerialName("methodInfo") override val methodInfo: FunctionMatcher, + @SerialName("condition") val condition: Condition, ) : SerializedTaintConfigurationItem @Serializable @SerialName("PassThrough") data class SerializedTaintPassThrough( - override val methodInfo: FunctionMatcher, - val condition: Condition, - val actionsAfter: List, + @SerialName("methodInfo") override val methodInfo: FunctionMatcher, + @SerialName("condition") val condition: Condition, + @SerialName("actionsAfter") val actionsAfter: List, ) : SerializedTaintConfigurationItem @Serializable @SerialName("Cleaner") data class SerializedTaintCleaner( - override val methodInfo: FunctionMatcher, - val condition: Condition, - val actionsAfter: List, + @SerialName("methodInfo") override val methodInfo: FunctionMatcher, + @SerialName("condition") val condition: Condition, + @SerialName("actionsAfter") val actionsAfter: List, ) : SerializedTaintConfigurationItem + +fun main() { + val methodSource = SerializedTaintMethodSource( + methodInfo = FunctionMatcher( + cls = ClassMatcher( + pkg = NameExactMatcher("java.util"), + classNameMatcher = NameExactMatcher("Scanner") + ), + functionName = NamePatternMatcher("findInLine|findWithinHorizon|nextLine|useDelimiter|useLocale|useRadix|skip|reset"), + parametersMatchers = emptyList(), + returnTypeMatcher = AnyTypeMatcher, + applyToOverrides = true, + functionLabel = null, + modifier = -1, + exclude = emptyList() + ), + condition = ConstantTrue, + actionsAfter = listOf( + CopyAllMarks(from = This, to = Result) + ) + ) + val json = Json { + prettyPrint = true + } + val methodSourceJson = json.encodeToString(methodSource) + println("methodSource.toJson() = $methodSourceJson") +} diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt index b3b6210f7..c76337c25 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt @@ -14,50 +14,83 @@ * limitations under the License. */ +@file:Suppress("PublicApiImplicitType") + package org.jacodb.taint.configuration import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass -interface TaintActionVisitor { +interface TaintActionVisitor { fun visit(action: CopyAllMarks): R fun visit(action: CopyMark): R fun visit(action: AssignMark): R fun visit(action: RemoveAllMarks): R fun visit(action: RemoveMark): R + + fun visit(action: Action): R } interface Action { fun accept(visitor: TaintActionVisitor): R } +val actionModule = SerializersModule { + polymorphic(Action::class) { + subclass(CopyAllMarks::class) + subclass(CopyMark::class) + subclass(AssignMark::class) + subclass(RemoveAllMarks::class) + subclass(RemoveMark::class) + } +} + // TODO add marks for aliases (if you pass an object and return it from the function) + @Serializable @SerialName("CopyAllMarks") -data class CopyAllMarks(val from: Position, val to: Position) : Action { +data class CopyAllMarks( + val from: Position, + val to: Position, +) : Action { override fun accept(visitor: TaintActionVisitor): R = visitor.visit(this) } @Serializable -@SerialName("AssignMark") -data class AssignMark(val position: Position, val mark: TaintMark) : Action { +@SerialName("CopyMark") +data class CopyMark( + val mark: TaintMark, + val from: Position, + val to: Position, +) : Action { override fun accept(visitor: TaintActionVisitor): R = visitor.visit(this) } @Serializable -@SerialName("RemoveAllMarks") -data class RemoveAllMarks(val position: Position) : Action { +@SerialName("AssignMark") +data class AssignMark( + val mark: TaintMark, + val position: Position, +) : Action { override fun accept(visitor: TaintActionVisitor): R = visitor.visit(this) } @Serializable -@SerialName("RemoveMark") -data class RemoveMark(val position: Position, val mark: TaintMark) : Action { +@SerialName("RemoveAllMarks") +data class RemoveAllMarks( + val position: Position, +) : Action { override fun accept(visitor: TaintActionVisitor): R = visitor.visit(this) } @Serializable -@SerialName("CopyMark") -data class CopyMark(val from: Position, val to: Position, val mark: TaintMark) : Action { +@SerialName("RemoveMark") +data class RemoveMark( + val mark: TaintMark, + val position: Position, +) : Action { override fun accept(visitor: TaintActionVisitor): R = visitor.visit(this) } diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt index a775a55be..6ab5f2aa4 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt @@ -14,13 +14,18 @@ * limitations under the License. */ +@file:Suppress("PublicApiImplicitType") + package org.jacodb.taint.configuration import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass import org.jacodb.api.JcType -interface ConditionVisitor { +interface ConditionVisitor { fun visit(condition: And): R fun visit(condition: Or): R fun visit(condition: Not): R @@ -32,38 +37,66 @@ interface ConditionVisitor { fun visit(condition: ConstantGt): R fun visit(condition: ConstantMatches): R fun visit(condition: SourceFunctionMatches): R - fun visit(condition: CallParameterContainsMark): R + fun visit(condition: ContainsMark): R fun visit(condition: ConstantTrue): R - - // extra conditions fun visit(condition: TypeMatches): R + + // external type + fun visit(condition: Condition): R } interface Condition { fun accept(conditionVisitor: ConditionVisitor): R } +val conditionModule = SerializersModule { + polymorphic(Condition::class) { + subclass(And::class) + subclass(Or::class) + subclass(Not::class) + subclass(IsConstant::class) + subclass(IsType::class) + subclass(AnnotationType::class) + subclass(ConstantEq::class) + subclass(ConstantLt::class) + subclass(ConstantGt::class) + subclass(ConstantMatches::class) + subclass(SourceFunctionMatches::class) + subclass(ContainsMark::class) + subclass(ConstantTrue::class) + subclass(TypeMatches::class) + } +} + @Serializable @SerialName("And") -data class And(@SerialName("args") val args: List) : Condition { +data class And( + @SerialName("args") val args: List, +) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable @SerialName("Or") -data class Or(@SerialName("args") val args: List) : Condition { +data class Or( + @SerialName("args") val args: List, +) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable @SerialName("Not") -data class Not(val condition: Condition) : Condition { +data class Not( + @SerialName("condition") val arg: Condition, +) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable @SerialName("IsConstant") -data class IsConstant(val position: Position) : Condition { +data class IsConstant( + @SerialName("position") val position: Position, +) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -71,7 +104,7 @@ data class IsConstant(val position: Position) : Condition { @SerialName("IsType") data class IsType( @SerialName("position") val position: Position, - @SerialName("type") val typeMatcher: TypeMatcher + @SerialName("type") val typeMatcher: TypeMatcher, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -80,7 +113,7 @@ data class IsType( @SerialName("AnnotationType") data class AnnotationType( @SerialName("position") val position: Position, - @SerialName("type") val typeMatcher: TypeMatcher + @SerialName("type") val typeMatcher: TypeMatcher, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -89,7 +122,7 @@ data class AnnotationType( @SerialName("ConstantEq") data class ConstantEq( @SerialName("position") val position: Position, - @SerialName("constant") val value: ConstantValue + @SerialName("constant") val value: ConstantValue, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -98,7 +131,7 @@ data class ConstantEq( @SerialName("ConstantLt") data class ConstantLt( @SerialName("position") val position: Position, - @SerialName("constant") val value: ConstantValue + @SerialName("constant") val value: ConstantValue, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -107,7 +140,7 @@ data class ConstantLt( @SerialName("ConstantGt") data class ConstantGt( @SerialName("position") val position: Position, - @SerialName("constant") val value: ConstantValue + @SerialName("constant") val value: ConstantValue, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -116,7 +149,7 @@ data class ConstantGt( @SerialName("ConstantMatches") data class ConstantMatches( @SerialName("position") val position: Position, - @SerialName("pattern") val pattern: String + @SerialName("pattern") val pattern: String, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -125,17 +158,16 @@ data class ConstantMatches( @SerialName("SourceFunctionMatches") data class SourceFunctionMatches( @SerialName("position") val position: Position, - @SerialName("sourceFunction") val functionMatcher: FunctionMatcher + @SerialName("sourceFunction") val functionMatcher: FunctionMatcher, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } -// sink label @Serializable @SerialName("ContainsMark") -data class CallParameterContainsMark( +data class ContainsMark( @SerialName("position") val position: Position, - @SerialName("mark") val mark: TaintMark + @SerialName("mark") val mark: TaintMark, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -144,16 +176,21 @@ data class CallParameterContainsMark( @SerialName("ConstantTrue") object ConstantTrue : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) + + override fun toString(): String = javaClass.simpleName } -// Extra conditions @Serializable @SerialName("TypeMatches") -data class TypeMatches(val position: Position, @SerialName("type") val type: JcType) : Condition { +data class TypeMatches( + @SerialName("position") val position: Position, + @SerialName("type") val type: JcType, +) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable +@SerialName("ConstantValue") sealed interface ConstantValue @Serializable @@ -169,51 +206,64 @@ data class ConstantBooleanValue(val value: Boolean) : ConstantValue data class ConstantStringValue(val value: String) : ConstantValue @Serializable +@SerialName("NameMatcher") sealed interface NameMatcher @Serializable -@SerialName("NameMatches") -data class NamePatternMatcher(val pattern: String) : NameMatcher +@SerialName("NameIsEqualTo") +data class NameExactMatcher( + @SerialName("name") val name: String, +) : NameMatcher @Serializable -@SerialName("NameIsEqualTo") -data class NameExactMatcher(val name: String) : NameMatcher +@SerialName("NameMatches") +data class NamePatternMatcher( + @SerialName("pattern") val pattern: String, +) : NameMatcher @Serializable @SerialName("AnyNameMatches") -object AnyNameMatcher : NameMatcher +object AnyNameMatcher : NameMatcher { + override fun toString(): String = javaClass.simpleName +} @Serializable +@SerialName("TypeMatcher") sealed interface TypeMatcher +@Serializable +@SerialName("PrimitiveNameMatches") +data class PrimitiveNameMatcher(val name: String) : TypeMatcher + @Serializable @SerialName("ClassMatcher") data class ClassMatcher( @SerialName("packageMatcher") val pkg: NameMatcher, - @SerialName("classNameMatcher") val classNameMatcher: NameMatcher + @SerialName("classNameMatcher") val classNameMatcher: NameMatcher, ) : TypeMatcher -@Serializable -@SerialName("PrimitiveNameMatches") -data class PrimitiveNameMatcher(val name: String) : TypeMatcher - @Serializable @SerialName("AnyTypeMatches") -object AnyTypeMatcher : TypeMatcher +object AnyTypeMatcher : TypeMatcher { + override fun toString(): String = javaClass.simpleName +} @Serializable @SerialName("FunctionMatches") data class FunctionMatcher( - val cls: ClassMatcher, - val functionName: NameMatcher, - val parametersMatchers: List, - val returnTypeMatcher: TypeMatcher, - val applyToOverrides: Boolean, - val functionLabel: String?, - val modifier: Int, - val exclude: List + @SerialName("cls") val cls: ClassMatcher, + @SerialName("functionName") val functionName: NameMatcher, + @SerialName("parametersMatchers") val parametersMatchers: List, + @SerialName("returnTypeMatcher") val returnTypeMatcher: TypeMatcher, + @SerialName("applyToOverrides") val applyToOverrides: Boolean, + @SerialName("functionLabel") val functionLabel: String?, + @SerialName("modifier") val modifier: Int, + @SerialName("exclude") val exclude: List, ) @Serializable @SerialName("ParameterMatches") -data class ParameterMatcher(val index: Int, val typeMatcher: TypeMatcher) +data class ParameterMatcher( + @SerialName("index") val index: Int, + @SerialName("typeMatcher") val typeMatcher: TypeMatcher, +) diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt index 1412322bb..1f7e25bbc 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt @@ -14,34 +14,45 @@ * limitations under the License. */ +@file:Suppress("PublicApiImplicitType") + package org.jacodb.taint.configuration import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.modules.subclass -import org.jacodb.api.* -import org.jacodb.api.ext.* +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClasspathFeature +import org.jacodb.api.JcMethod +import org.jacodb.api.JcPrimitiveType +import org.jacodb.api.PredefinedPrimitives +import org.jacodb.api.TypeName +import org.jacodb.api.ext.allSuperHierarchySequence +import org.jacodb.api.ext.boolean +import org.jacodb.api.ext.byte +import org.jacodb.api.ext.char +import org.jacodb.api.ext.double +import org.jacodb.api.ext.float +import org.jacodb.api.ext.int +import org.jacodb.api.ext.long +import org.jacodb.api.ext.short +import org.jacodb.impl.cfg.util.isArray import java.nio.file.Path import kotlin.io.path.readText - class TaintConfigurationFeature private constructor( jsonConfig: String, - additionalSerializersModule: SerializersModule? + additionalSerializersModule: SerializersModule?, ) : JcClasspathFeature { private val rulesByClass: MutableMap> = hashMapOf() private val rulesForMethod: MutableMap> = hashMapOf() private val compiledRegex: MutableMap = hashMapOf() private val configurationTrie: ConfigurationTrie by lazy { - val serializers = additionalSerializersModule?.let { - SerializersModule { - include(defaultSerializationModule) - include(it) - } - } ?: defaultSerializationModule + val serializers = SerializersModule { + include(defaultSerializationModule) + additionalSerializersModule?.let { include(it) } + } val json = Json { classDiscriminator = CLASS_DISCRIMINATOR @@ -49,19 +60,36 @@ class TaintConfigurationFeature private constructor( prettyPrint = true } - val configuration = json.decodeFromString>(jsonConfig).map { - when (it) { - is SerializedTaintCleaner -> it.copy(condition = it.condition.accept(ConditionSimplifier)) - is SerializedTaintEntryPointSource -> it.copy(condition = it.condition.accept(ConditionSimplifier)) - is SerializedTaintMethodSink -> it.copy(condition = it.condition.accept(ConditionSimplifier)) - is SerializedTaintMethodSource -> it.copy(condition = it.condition.accept(ConditionSimplifier)) - is SerializedTaintPassThrough -> it.copy(condition = it.condition.accept(ConditionSimplifier)) + val configuration = json + .decodeFromString>(jsonConfig) + .map { + when (it) { + is SerializedTaintEntryPointSource -> it.copy( + condition = it.condition.accept(ConditionSimplifier()) + ) + + is SerializedTaintMethodSource -> it.copy( + condition = it.condition.accept(ConditionSimplifier()) + ) + + is SerializedTaintMethodSink -> it.copy( + condition = it.condition.accept(ConditionSimplifier()) + ) + + is SerializedTaintPassThrough -> it.copy( + condition = it.condition.accept(ConditionSimplifier()) + ) + + is SerializedTaintCleaner -> it.copy( + condition = it.condition.accept(ConditionSimplifier()) + ) + } } - } ConfigurationTrie(configuration, ::matches) } + @Synchronized fun getConfigForMethod(method: JcMethod): List = resolveConfigForMethod(method) @@ -84,7 +112,6 @@ class TaintConfigurationFeature private constructor( return primitiveTypesSet!! } - private fun resolveConfigForMethod(method: JcMethod): List { val taintConfigurationItems = rulesForMethod[method] if (taintConfigurationItems != null) { @@ -92,15 +119,10 @@ class TaintConfigurationFeature private constructor( } val classRules = getClassRules(method.enclosingClass) - val destination = mutableListOf() - classRules.mapNotNullTo(destination) { - val functionMatcher = it.methodInfo - if (!functionMatcher.matches(method)) return@mapNotNullTo null - it.resolveForMethod(method) } @@ -113,14 +135,13 @@ class TaintConfigurationFeature private constructor( rules.mapNotNullTo(destination) { val methodInfo = it.methodInfo if (!methodInfo.applyToOverrides || !methodInfo.matches(method)) return@mapNotNullTo null - it.resolveForMethod(method) } } - rulesForMethod[method] = destination.distinct() - - return rulesForMethod.getValue(method) + val rules = destination.distinct() + rulesForMethod[method] = rules + return rules } private fun getClassRules(clazz: JcClassOrInterface) = rulesByClass.getOrPut(clazz) { @@ -131,18 +152,15 @@ class TaintConfigurationFeature private constructor( val functionNameMatcher = functionName val functionName = if (method.isConstructor) "init^" else method.name val functionNameMatches = matches(functionNameMatcher, functionName) - if (!functionNameMatches) return false val parameterMatches = parametersMatchers.all { val parameter = method.parameters.getOrNull(it.index) ?: return@all false it.typeMatcher.matches(parameter.type) } - if (!parameterMatches) return false val returnTypeMatches = returnTypeMatcher.matches(method.returnType) - if (!returnTypeMatches) return false // TODO add function's label processing @@ -152,7 +170,6 @@ class TaintConfigurationFeature private constructor( } val isExcluded = exclude.any { it.matches(method) } - return !isExcluded } @@ -163,9 +180,7 @@ class TaintConfigurationFeature private constructor( private fun ClassMatcher.matches(pkgName: String, className: String): Boolean { val packageMatches = matches(pkg, pkgName) - if (!packageMatches) return false - return matches(classNameMatcher, className) } @@ -179,7 +194,6 @@ class TaintConfigurationFeature private constructor( } } - private fun TypeMatcher.matches(typeName: TypeName): Boolean = matches(typeName.typeName) private fun TypeMatcher.matches(typeName: String): Boolean = @@ -191,57 +205,82 @@ class TaintConfigurationFeature private constructor( private fun SerializedTaintConfigurationItem.resolveForMethod(method: JcMethod): TaintConfigurationItem = when (this) { - is SerializedTaintCleaner -> TaintCleaner(method, condition.resolve(method), actionsAfter.resolve(method)) is SerializedTaintEntryPointSource -> TaintEntryPointSource( method, condition.resolve(method), actionsAfter.resolve(method) ) - is SerializedTaintMethodSink -> TaintMethodSink(condition.resolve(method), method) is SerializedTaintMethodSource -> TaintMethodSource( method, condition.resolve(method), actionsAfter.resolve(method) ) + is SerializedTaintMethodSink -> TaintMethodSink( + method, + ruleNote, + cwe, + condition.resolve(method) + ) + is SerializedTaintPassThrough -> TaintPassThrough( method, condition.resolve(method), actionsAfter.resolve(method) ) + + is SerializedTaintCleaner -> TaintCleaner( + method, + condition.resolve(method), + actionsAfter.resolve(method) + ) } - private fun Condition.resolve(method: JcMethod): Condition = accept(ConditionSpecializer(method)) - private fun List.resolve(method: JcMethod): List = flatMap { it.accept(ActionSpecializer(method)) } + private fun Condition.resolve(method: JcMethod): Condition = this + .accept(ConditionSpecializer(method)) + .accept(ConditionSimplifier()) + private fun List.resolve(method: JcMethod): List = + flatMap { it.accept(ActionSpecializer(method)) } - private fun specializePosition(method: JcMethod, position: Position): List { - if (!inBounds(method, position)) return emptyList() - if (position !is AnyArgument) return listOf(position) - return method.parameters.indices.map { Argument(it) }.filter { inBounds(method, it) } + private fun specializePosition(method: JcMethod, position: Position): List = when { + !inBounds(method, position) -> emptyList() + position !is AnyArgument -> listOf(position) + else -> method.parameters.indices.map { Argument(it) }.filter { inBounds(method, it) } } - private fun mkOr(conditions: List) = if (conditions.size == 1) conditions.single() else Or(conditions) - private fun mkAnd(conditions: List) = if (conditions.size == 1) conditions.single() else And(conditions) + private fun mkTrue(): Condition = ConstantTrue + private fun mkFalse(): Condition = Not(ConstantTrue) + + private fun mkOr(conditions: List) = when (conditions.size) { + 0 -> mkFalse() + 1 -> conditions.single() + else -> Or(conditions) + } + + private fun mkAnd(conditions: List) = when (conditions.size) { + 0 -> mkTrue() + 1 -> conditions.single() + else -> And(conditions) + } private fun inBounds(method: JcMethod, position: Position): Boolean = when (position) { AnyArgument -> method.parameters.isNotEmpty() - is Argument -> position.number in method.parameters.indices + is Argument -> position.index in method.parameters.indices + This -> !method.isStatic Result -> method.returnType.typeName != PredefinedPrimitives.Void - ThisArgument -> !method.isStatic + ResultAnyElement -> method.returnType.isArray } private inner class ActionSpecializer(val method: JcMethod) : TaintActionVisitor> { override fun visit(action: CopyAllMarks): List { val from = specializePosition(method, action.from) val to = specializePosition(method, action.to) - return from.flatMap { fst -> to.mapNotNull { snd -> if (fst == snd) return@mapNotNull null - CopyAllMarks(fst, snd) } } @@ -250,11 +289,9 @@ class TaintConfigurationFeature private constructor( override fun visit(action: CopyMark): List { val from = specializePosition(method, action.from) val to = specializePosition(method, action.to) - return from.flatMap { fst -> to.mapNotNull { snd -> if (fst == snd) return@mapNotNull null - action.copy(from = fst, to = snd) } } @@ -268,24 +305,29 @@ class TaintConfigurationFeature private constructor( override fun visit(action: RemoveMark): List = specializePosition(method, action.position).map { action.copy(position = it) } + + override fun visit(action: Action): List = error("Unexpected action $action") } private inner class ConditionSpecializer(val method: JcMethod) : ConditionVisitor { - override fun visit(condition: And): Condition = mkAnd(condition.args.map { it.accept(this) }) + override fun visit(condition: And): Condition = + mkAnd(condition.args.map { it.accept(this) }) - override fun visit(condition: Or): Condition = mkOr(condition.args.map { it.accept(this) }) + override fun visit(condition: Or): Condition = + mkOr(condition.args.map { it.accept(this) }) - override fun visit(condition: Not): Condition = Not(condition.condition.accept(this)) + override fun visit(condition: Not): Condition = + Not(condition.arg.accept(this)) override fun visit(condition: IsConstant): Condition = mkOr(specializePosition(method, condition.position).map { condition.copy(position = it) }) override fun visit(condition: IsType): Condition { val position = specializePosition(method, condition.position) - val typeMatcher = condition.typeMatcher + if (typeMatcher is AnyTypeMatcher) { - return mkOr(position.map { ConstantTrue }) + return mkTrue() } if (typeMatcher is PrimitiveNameMatcher) { @@ -341,7 +383,28 @@ class TaintConfigurationFeature private constructor( return mkOr(disjuncts) } - override fun visit(condition: AnnotationType): Condition = ConstantTrue // TODO("Not yet implemented") + override fun visit(condition: AnnotationType): Condition { + val positions = specializePosition(method, condition.position) + return if (positions.any { methodAnnotationMatches(it, condition.typeMatcher) }) { + mkTrue() + } else { + mkFalse() + } + } + + private fun methodAnnotationMatches(position: Position, matcher: TypeMatcher): Boolean { + when (position) { + is Argument -> { + val annotations = method.parameters.getOrNull(position.index)?.annotations + return annotations?.any { matcher.matches(it.name) } ?: false + } + + Result -> TODO("What does it mean?") + This -> TODO("What does it mean?") + AnyArgument -> error("Must not occur here") + ResultAnyElement -> error("Must not occur here") + } + } override fun visit(condition: ConstantEq): Condition = mkOr(specializePosition(method, condition.position).map { condition.copy(position = it) }) @@ -357,109 +420,94 @@ class TaintConfigurationFeature private constructor( override fun visit(condition: SourceFunctionMatches): Condition = ConstantTrue // TODO Not implemented yet - override fun visit(condition: CallParameterContainsMark): Condition = + override fun visit(condition: ContainsMark): Condition = mkOr(specializePosition(method, condition.position).map { condition.copy(position = it) }) override fun visit(condition: ConstantTrue): Condition = condition override fun visit(condition: TypeMatches): Condition = error("Must not occur here") - } - companion object { - fun fromPath( - configPath: Path, - serializersModule: SerializersModule? = null - ) = TaintConfigurationFeature(configPath.readText(), serializersModule) - - fun fromJson( - jsonConfig: String, - serializersModule: SerializersModule? = null - ) = TaintConfigurationFeature(jsonConfig, serializersModule) - - val defaultSerializationModule: SerializersModule - get() = SerializersModule { - polymorphic(Condition::class) { - subclass(And::class) - subclass(Or::class) - subclass(Not::class) - subclass(IsConstant::class) - subclass(IsType::class) - subclass(AnnotationType::class) - subclass(ConstantEq::class) - subclass(ConstantLt::class) - subclass(ConstantGt::class) - subclass(ConstantMatches::class) - subclass(SourceFunctionMatches::class) - subclass(CallParameterContainsMark::class) - subclass(ConstantTrue::class) - subclass(TypeMatches::class) - } + override fun visit(condition: Condition): Condition = condition + } - polymorphic(Action::class) { - subclass(CopyAllMarks::class) - subclass(AssignMark::class) - subclass(RemoveAllMarks::class) - subclass(RemoveMark::class) - subclass(CopyMark::class) + private inner class ConditionSimplifier : ConditionVisitor { + override fun visit(condition: And): Condition { + val queue = ArrayDeque(condition.args) + val args = mutableListOf() + while (queue.isNotEmpty()) { + val it = queue.removeFirst().accept(this) + if (it is ConstantTrue) { + // skip + } else if (it is Not && it.arg is ConstantTrue) { + return mkFalse() + } else if (it is And) { + queue += it.args + } else { + args += it } } + return mkAnd(args) + } - private const val CLASS_DISCRIMINATOR = "_" - } -} - -private object ConditionSimplifier : ConditionVisitor { - override fun visit(condition: And): Condition { - val unprocessed = condition.args.toMutableList() - val conjuncts = mutableListOf() - while (unprocessed.isNotEmpty()) { - val it = unprocessed.removeLast() - if (it is And) { - unprocessed.addAll(it.args) - continue + override fun visit(condition: Or): Condition { + val queue = ArrayDeque(condition.args) + val args = mutableListOf() + while (queue.isNotEmpty()) { + val it = queue.removeFirst().accept(this) + if (it is ConstantTrue) { + return mkTrue() + } else if (it is Not && it.arg is ConstantTrue) { + // skip + } else if (it is Or) { + queue += it.args + } else { + args += it + } } - conjuncts += it.accept(this) + return mkOr(args) } - return conjuncts.singleOrNull() ?: And(conjuncts.asReversed()) - } - - override fun visit(condition: Or): Condition { - val unprocessed = condition.args.toMutableList() - val conjuncts = mutableListOf() - while (unprocessed.isNotEmpty()) { - val it = unprocessed.removeLast() - if (it is Or) { - unprocessed.addAll(it.args) - continue + override fun visit(condition: Not): Condition { + val arg = condition.arg.accept(this) + return if (arg is Not) { + // Eliminate double negation: + arg.arg + } else { + Not(arg) } - conjuncts += it.accept(this) } - return conjuncts.singleOrNull() ?: Or(conjuncts.asReversed()) - } - - override fun visit(condition: Not): Condition = Not(condition.condition.accept(this)) - - override fun visit(condition: IsConstant): Condition = condition - - override fun visit(condition: IsType): Condition = condition - - override fun visit(condition: AnnotationType): Condition = condition - - override fun visit(condition: ConstantEq): Condition = condition - - override fun visit(condition: ConstantLt): Condition = condition - - override fun visit(condition: ConstantGt): Condition = condition + override fun visit(condition: IsConstant): Condition = condition + override fun visit(condition: IsType): Condition = condition + override fun visit(condition: AnnotationType): Condition = condition + override fun visit(condition: ConstantEq): Condition = condition + override fun visit(condition: ConstantLt): Condition = condition + override fun visit(condition: ConstantGt): Condition = condition + override fun visit(condition: ConstantMatches): Condition = condition + override fun visit(condition: SourceFunctionMatches): Condition = condition + override fun visit(condition: ContainsMark): Condition = condition + override fun visit(condition: ConstantTrue): Condition = condition + override fun visit(condition: TypeMatches): Condition = condition - override fun visit(condition: ConstantMatches): Condition = condition + override fun visit(condition: Condition): Condition = condition + } - override fun visit(condition: SourceFunctionMatches): Condition = condition + companion object { + fun fromJson( + jsonConfig: String, + serializersModule: SerializersModule? = null, + ) = TaintConfigurationFeature(jsonConfig, serializersModule) - override fun visit(condition: CallParameterContainsMark): Condition = condition + fun fromPath( + configPath: Path, + serializersModule: SerializersModule? = null, + ) = fromJson(configPath.readText(), serializersModule) - override fun visit(condition: ConstantTrue): Condition = condition + val defaultSerializationModule = SerializersModule { + include(conditionModule) + include(actionModule) + } - override fun visit(condition: TypeMatches): Condition = condition + private const val CLASS_DISCRIMINATOR = "_" + } } diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt index 9b6ad6585..436b946c3 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt @@ -16,36 +16,71 @@ package org.jacodb.taint.configuration +import org.jacodb.api.JcField import org.jacodb.api.JcMethod sealed interface TaintConfigurationItem data class TaintEntryPointSource( - val methodInfo: JcMethod, + val method: JcMethod, val condition: Condition, val actionsAfter: List, ) : TaintConfigurationItem data class TaintMethodSource( - val methodInfo: JcMethod, + val method: JcMethod, + val condition: Condition, + val actionsAfter: List, +) : TaintConfigurationItem + +data class TaintFieldSource( + val field: JcField, val condition: Condition, val actionsAfter: List, ) : TaintConfigurationItem data class TaintMethodSink( + val method: JcMethod, + val ruleNote: String, + val cwe: List, + val condition: Condition, +) : TaintConfigurationItem + +data class TaintFieldSink( + val field: JcField, val condition: Condition, - val methodInfo: JcMethod, ) : TaintConfigurationItem data class TaintPassThrough( - val methodInfo: JcMethod, + val method: JcMethod, val condition: Condition, val actionsAfter: List, ) : TaintConfigurationItem data class TaintCleaner( - val methodInfo: JcMethod, + val method: JcMethod, val condition: Condition, val actionsAfter: List, ) : TaintConfigurationItem +val TaintConfigurationItem.condition: Condition + get() = when (this) { + is TaintEntryPointSource -> condition + is TaintMethodSource -> condition + is TaintFieldSource -> condition + is TaintMethodSink -> condition + is TaintFieldSink -> condition + is TaintPassThrough -> condition + is TaintCleaner -> condition + } + +val TaintConfigurationItem.actionsAfter: List + get() = when (this) { + is TaintEntryPointSource -> actionsAfter + is TaintMethodSource -> actionsAfter + is TaintFieldSource -> actionsAfter + is TaintMethodSink -> emptyList() + is TaintFieldSink -> emptyList() + is TaintPassThrough -> actionsAfter + is TaintCleaner -> actionsAfter + } diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt index 5192fa73d..72aeeed60 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt @@ -21,4 +21,10 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("TaintMark") -data class TaintMark(val name: String) \ No newline at end of file +data class TaintMark(val name: String) { + override fun toString(): String = name + + companion object { + val NULLNESS: TaintMark = TaintMark("NULLNESS") + } +} From 103d20c3d94805f20c80d85e778f26e542b38e5d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 12 Feb 2024 12:34:27 +0300 Subject: [PATCH 002/117] Remove unnecessary accept override --- jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt index 80e418446..3a9ff4861 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt @@ -797,9 +797,7 @@ data class JcSpecialCallExpr( } } -interface JcValue : JcExpr { - override fun accept(visitor: JcExprVisitor): T -} +interface JcValue : JcExpr interface JcSimpleValue : JcValue { override val operands: List From b1a09c9738373a79c75eb00f5394f30702aa3413 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 13:35:44 +0300 Subject: [PATCH 003/117] fix: access flags for unknown methods --- .../features/classpaths/JcUnknownClass.kt | 10 ++++---- .../impl/features/classpaths/JcUnknownType.kt | 24 +++++++++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownClass.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownClass.kt index 20577c5c9..ca0442165 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownClass.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownClass.kt @@ -40,28 +40,30 @@ class JcUnknownClass(override var classpath: JcClasspath, name: String) : JcVirt class JcUnknownMethod( enclosingClass: JcClassOrInterface, name: String, + access: Int, description: String, returnType: TypeName, params: List ) : JcVirtualMethodImpl( name, + access, returnType = returnType, parameters = params.mapIndexed { index, typeName -> JcVirtualParameter(index, typeName) }, description = description ) { companion object { - fun method(type: JcClassOrInterface, name: String, description: String): JcMethod { + fun method(type: JcClassOrInterface, name: String, access: Int, description: String): JcMethod { val methodType = Type.getMethodType(description) val returnType = TypeNameImpl(methodType.returnType.className.jcdbName()) val paramsType = methodType.argumentTypes.map { TypeNameImpl(it.className.jcdbName()) } - return JcUnknownMethod(type, name, description, returnType, paramsType) + return JcUnknownMethod(type, name, access, description, returnType, paramsType) } - fun typedMethod(type: JcClassType, name: String, description: String): JcTypedMethod { + fun typedMethod(type: JcClassType, name: String, access: Int, description: String): JcTypedMethod { return JcTypedMethodImpl( type, - method(type.jcClass, name, description), + method(type.jcClass, name, access, description), JcSubstitutorImpl.empty ) } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt index bbda79e3e..12dc782bd 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt @@ -23,7 +23,11 @@ import org.jacodb.impl.types.TypeNameImpl import org.objectweb.asm.Opcodes -class JcUnknownType(override var classpath: JcClasspath, private val name: String, private val location: VirtualLocation) : JcClassType { +class JcUnknownType( + override var classpath: JcClasspath, + private val name: String, + private val location: VirtualLocation +) : JcClassType { override val lookup: JcLookup = JcUnknownTypeLookup(this) @@ -59,30 +63,36 @@ class JcUnknownType(override var classpath: JcClasspath, private val name: Strin open class JcUnknownClassLookup(val clazz: JcClassOrInterface) : JcLookup { - override fun specialMethod(name: String, description: String): JcMethod = method(name, description) - override fun staticMethod(name: String, description: String): JcMethod = method(name, description) + override fun specialMethod(name: String, description: String): JcMethod = + JcUnknownMethod.method(clazz, name, access = Opcodes.ACC_PUBLIC, description) + + override fun staticMethod(name: String, description: String): JcMethod = + JcUnknownMethod.method(clazz, name, access = Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC, description) override fun field(name: String, typeName: TypeName?): JcField { return JcUnknownField(clazz, name, typeName ?: TypeNameImpl(OBJECT_CLASS)) } override fun method(name: String, description: String): JcMethod { - return JcUnknownMethod.method(clazz, name, description) + return JcUnknownMethod.method(clazz, name, access = Opcodes.ACC_PUBLIC, description) } } open class JcUnknownTypeLookup(val type: JcClassType) : JcLookup { - override fun specialMethod(name: String, description: String): JcTypedMethod = method(name, description) - override fun staticMethod(name: String, description: String): JcTypedMethod = method(name, description) + override fun specialMethod(name: String, description: String): JcTypedMethod = + JcUnknownMethod.typedMethod(type, name, access = Opcodes.ACC_PUBLIC, description) + + override fun staticMethod(name: String, description: String): JcTypedMethod = + JcUnknownMethod.typedMethod(type, name, access = Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC, description) override fun field(name: String, typeName: TypeName?): JcTypedField { return JcUnknownField.typedField(type, name, typeName ?: TypeNameImpl(OBJECT_CLASS)) } override fun method(name: String, description: String): JcTypedMethod { - return JcUnknownMethod.typedMethod(type, name, description) + return JcUnknownMethod.typedMethod(type, name, access = Opcodes.ACC_PUBLIC, description) } } \ No newline at end of file From 3ae0bcdc234bf67f7ac78b0c96ccb4022ed8a3bf Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 14:03:57 +0300 Subject: [PATCH 004/117] fix: revert 0 --- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/Dependencies.kt | 35 +++++++------------ jacodb-analysis/README.md | 2 +- jacodb-analysis/build.gradle.kts | 1 - .../jacodb/analysis/engine/AnalyzerFactory.kt | 2 +- .../engine/BaseIfdsUnitRunnerFactory.kt | 3 +- .../analysis/engine/MainIfdsUnitManager.kt | 2 +- .../org/jacodb/analysis/ifds2/Runner.kt | 2 +- .../analysis/ifds2/taint/TaintAnalyzers.kt | 2 +- .../ifds2/taint/TaintFlowFunctions.kt | 2 +- .../analysis/ifds2/taint/TaintManager.kt | 2 +- .../analysis/ifds2/taint/npe/NpeAnalyzers.kt | 2 +- .../ifds2/taint/npe/NpeFlowFunctions.kt | 2 +- .../analysis/ifds2/taint/npe/NpeManager.kt | 2 +- .../AbstractTaintForwardFunctions.kt | 2 +- .../library/analyzers/TaintAnalyzer.kt | 2 +- .../jacodb/analysis/impl/BaseAnalysisTest.kt | 2 +- .../org/jacodb/analysis/impl/BenchRunners.kt | 2 +- .../org/jacodb/analysis/impl/Ifds2NpeTest.kt | 2 +- .../org/jacodb/analysis/impl/Ifds2SqlTest.kt | 2 +- 20 files changed, 32 insertions(+), 41 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b0bcf4903..1b1169c68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -188,7 +188,7 @@ if (!repoUrl.isNullOrEmpty()) { register("jar") { from(components["java"]) artifact(tasks.named("sourcesJar")) - // artifact(tasks.named("dokkaJavadocJar")) + artifact(tasks.named("dokkaJavadocJar")) groupId = "org.jacodb" artifactId = project.name diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index bdd08d87a..f69657368 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -2,13 +2,14 @@ import org.gradle.plugin.use.PluginDependenciesSpec + object Versions { const val asm = "9.5" const val dokka = "1.7.20" const val gradle_download = "5.3.0" const val gradle_versions = "0.47.0" - const val guava = "31.1-jre" const val hikaricp = "5.0.1" + const val guava = "31.1-jre" const val javax_activation = "1.1" const val javax_mail = "1.4.7" const val javax_servlet_api = "2.5" @@ -21,7 +22,6 @@ object Versions { const val junit = "5.9.2" const val kotlin = "1.7.21" const val kotlin_logging = "1.8.3" - const val kotlin_logging5 = "5.1.0" const val kotlinx_benchmark = "0.4.4" const val kotlinx_cli = "0.3.5" const val kotlinx_collections_immutable = "0.3.5" @@ -66,13 +66,6 @@ object Libs { version = Versions.kotlin_logging ) - // https://github.com/oshai/kotlin-logging - val kotlin_logging5 = dep( - group = "io.github.oshai", - name = "kotlin-logging", - version = Versions.kotlin_logging5 - ) - // https://github.com/qos-ch/slf4j val slf4j_simple = dep( group = "org.slf4j", @@ -107,7 +100,7 @@ object Libs { // https://github.com/Kotlin/kotlinx.collections.immutable val kotlinx_collections_immutable = dep( group = "org.jetbrains.kotlinx", - name = "kotlinx-collections-immutable", + name = "kotlinx-collections-immutable-jvm", version = Versions.kotlinx_collections_immutable ) @@ -290,48 +283,46 @@ object Libs { } object Plugins { - abstract class Plugin( - val version: String, - val id: String, - ) + + abstract class ProjectPlugin(val version: String, val id: String) // https://github.com/Kotlin/dokka - object Dokka : Plugin( + object Dokka: ProjectPlugin( version = Versions.dokka, id = "org.jetbrains.dokka" ) // https://github.com/michel-kraemer/gradle-download-task - object GradleDownload : Plugin( + object GradleDownload: ProjectPlugin( version = Versions.gradle_download, id = "de.undercouch.download" ) // https://github.com/ben-manes/gradle-versions-plugin - object GradleVersions : Plugin( + object GradleVersions: ProjectPlugin( version = Versions.gradle_versions, id = "com.github.ben-manes.versions" ) // https://github.com/Kotlin/kotlinx-benchmark - object KotlinxBenchmark : Plugin( + object KotlinxBenchmark : ProjectPlugin( version = Versions.kotlinx_benchmark, id = "org.jetbrains.kotlinx.benchmark" ) // https://github.com/CadixDev/licenser - object Licenser : Plugin( + object Licenser : ProjectPlugin( version = Versions.licenser, id = "org.cadixdev.licenser" ) // https://github.com/johnrengelman/shadow - object Shadow : Plugin( + object Shadow : ProjectPlugin( version = Versions.shadow, id = "com.github.johnrengelman.shadow" ) } -fun PluginDependenciesSpec.id(plugin: Plugins.Plugin) { +fun PluginDependenciesSpec.id(plugin: Plugins.ProjectPlugin) { id(plugin.id).version(plugin.version) -} +} \ No newline at end of file diff --git a/jacodb-analysis/README.md b/jacodb-analysis/README.md index e2ef4a788..cb6a4b854 100644 --- a/jacodb-analysis/README.md +++ b/jacodb-analysis/README.md @@ -1,4 +1,4 @@ -# The `jacodb-analysis` module +# Module `jacodb-analysis` The `jacodb-analysis` module allows launching application dataflow analyses. It contains an API to write custom analyses, and several ready-to-use analyses. diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index 7978ac1dd..c0dfa6068 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -9,7 +9,6 @@ dependencies { api(project(":jacodb-taint-configuration")) implementation(Libs.kotlin_logging) - implementation(Libs.kotlin_logging5) implementation(Libs.slf4j_simple) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.kotlinx_serialization_json) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt index 5c719c4ab..68d98c937 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt @@ -36,7 +36,7 @@ interface DomainFact * A special [DomainFact] that always holds */ object ZEROFact : DomainFact { - override fun toString(): String = "[ZERO fact]" + override fun toString() = "[ZERO fact]" } /** diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt index cc273fd05..be371f4a1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt @@ -26,12 +26,13 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext +import mu.KotlinLogging import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.ext.cfg.callExpr import java.util.concurrent.ConcurrentHashMap -private val logger = io.github.oshai.kotlinlogging.KotlinLogging.logger {} +private val logger = KotlinLogging.logger {} /** * This is a basic [IfdsUnitRunnerFactory], which creates one [BaseIfdsUnitRunner] for each [newRunner] call. diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt index f7c232aad..2c3984a12 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.engine -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index ada9f59b4..e8ca5d7c4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.ifds2 -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.getOrElse diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 310a753b8..526c0e864 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.ifds2.taint -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.ifds2.Analyzer diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt index d0b315b01..095a2bf10 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -18,7 +18,7 @@ package org.jacodb.analysis.ifds2.taint -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 9dd4be062..dcd9eb50b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.ifds2.taint -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt index ceb62686b..c7b190861 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.ifds2.taint.npe -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.ifds2.Analyzer diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt index f4f979376..94f923b29 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt @@ -18,7 +18,7 @@ package org.jacodb.analysis.ifds2.taint.npe -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index beffe61d5..fbca93f7b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.ifds2.taint.npe -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt index 6ceaafa73..a9a233ab1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt @@ -18,7 +18,7 @@ package org.jacodb.analysis.library.analyzers -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt index fcf338811..fb0ab2ea9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.library.analyzers -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index db29cf497..9abc8303b 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.impl -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import juliet.support.AbstractTestCase import kotlinx.coroutines.runBlocking import org.jacodb.analysis.engine.VulnerabilityInstance diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt index 88d339f48..10f338fdf 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt @@ -18,7 +18,7 @@ package org.jacodb.analysis.impl -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.runBlocking import org.jacodb.analysis.engine.PackageUnitResolver import org.jacodb.analysis.engine.SingletonUnit diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt index dcfaa6baf..691d0de9a 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.impl -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.runBlocking import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.JcApplicationGraphImpl diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt index 08285a29c..dc327dae9 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt @@ -16,7 +16,7 @@ package org.jacodb.analysis.impl -import io.github.oshai.kotlinlogging.KotlinLogging +import mu.KotlinLogging import kotlinx.coroutines.runBlocking import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.newApplicationGraphForAnalysis From bf97bc3a8cf4af9ad6517b3f03a6e00a49f45497 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 14:26:13 +0300 Subject: [PATCH 005/117] fix: revert 1 --- .../engine/BaseIfdsUnitRunnerFactory.kt | 13 +-- .../org/jacodb/analysis/engine/IfdsEdge.kt | 2 - .../analysis/engine/MainIfdsUnitManager.kt | 86 +------------------ 3 files changed, 7 insertions(+), 94 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt index be371f4a1..1007aa5ab 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt @@ -92,15 +92,11 @@ internal class BaseIfdsUnitRunner( ): Boolean { require(unitResolver.resolve(edge.method) == unit) - pathEdgesPreds.computeIfAbsent(edge) { ConcurrentHashMap.newKeySet() }.add(pred) - - // Update edge's reason: - edge.reason = pred.predEdge + pathEdgesPreds.computeIfAbsent(edge) { + ConcurrentHashMap.newKeySet() + }.add(pred) if (pathEdges.add(edge)) { - if (edge.from.statement.toString() == "noop" && edge.to.domainFact != ZEROFact) { - logger.trace { "Propagating $edge in ${edge.method} via ${edge.reason}" } - } workList.send(edge) analyzer.handleNewEdge(edge).forEach { manager.handleEvent(it, this) @@ -384,9 +380,6 @@ internal class BaseIfdsUnitRunner( } finally { logger.info { "Finishing ${this@BaseIfdsUnitRunner} for $unit" } logger.info { "Total ${pathEdges.size} path edges for $unit using $analyzer" } - // for ((i, edge) in pathEdges.sortedBy { it.toString() }.withIndex()) { - // logger.debug { " - [${i + 1}/${pathEdges.size}] $edge" } - // } // Post-process left-over events: withContext(NonCancellable) { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt index b7a26611b..a8551732b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt @@ -29,8 +29,6 @@ data class IfdsEdge( require(from.method == to.method) } - var reason: IfdsEdge? = null - val method: JcMethod get() = from.method } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt index 2c3984a12..8d3729c65 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt @@ -16,7 +16,6 @@ package org.jacodb.analysis.engine -import mu.KotlinLogging import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach @@ -29,11 +28,10 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import org.jacodb.analysis.library.analyzers.NpeTaintNode +import mu.KotlinLogging import org.jacodb.analysis.runAnalysis import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -import java.io.File import java.util.concurrent.ConcurrentHashMap private val logger = KotlinLogging.logger {} @@ -64,19 +62,6 @@ class MainIfdsUnitManager( private val dependencies: MutableMap> = mutableMapOf() private val dependenciesRev: MutableMap> = mutableMapOf() - // private val deleteJobs: MutableMap = ConcurrentHashMap() - // private val pathEdgesStorage: MutableMap> = ConcurrentHashMap() - - private fun getAllCallees(method: JcMethod): Set { - val result = mutableSetOf() - for (inst in method.flowGraph().instructions) { - graph.callees(inst).forEach { - result.add(it) - } - } - return result - } - private fun addStart(method: JcMethod) { val unit = unitResolver.resolve(method) // TODO: remove this unnecessary if-condition (superseded by '.add()' below): @@ -85,8 +70,6 @@ class MainIfdsUnitManager( } foundMethods.getOrPut(unit) { mutableSetOf() }.add(method) - // val dependencies = getAllCallees(method) - // dependencies.forEach { addStart(it) } } private val IfdsVertex.traceGraph: TraceGraph @@ -110,13 +93,6 @@ class MainIfdsUnitManager( val allUnits = foundMethods.keys.toList() logger.info { "Starting analysis. Number of found units: ${allUnits.size}" } - for ((i, entry) in foundMethods.entries.withIndex()) { - val (unit, methods) = entry - logger.info { "Unit [${i + 1}/${foundMethods.size}] :: $unit :: total ${methods.size} methods" } - for ((j, method) in methods.withIndex()) { - logger.info { "- Method [${j + 1}/${methods.size}] $method" } - } - } val progressLoggerJob = launch { while (isActive) { @@ -144,11 +120,6 @@ class MainIfdsUnitManager( foundMethods[unit]!!.toList() ) aliveRunners[unit] = runner - // pathEdgesStorage[unit] = when (runner) { - // is BaseIfdsUnitRunner -> runner.pathEdges - // is BidiIfdsUnitRunnerFactory.BidiIfdsUnitRunner -> (runner.forwardRunner as BaseIfdsUnitRunner).pathEdges - // else -> error("Bad runner: $runner") - // } runner.launchIn(this) } @@ -163,48 +134,6 @@ class MainIfdsUnitManager( vulnerabilitiesStorage.getCurrentFacts(method) } - logger.debug { "Total found ${foundVulnerabilities.size} sinks" } - for (vulnerability in foundVulnerabilities) { - logger.debug { "$vulnerability in ${vulnerability.method}" } - } - logger.info { "Total sinks: ${foundVulnerabilities.size}" } - - if (logger.isDebugEnabled()) { - val statsFileName = "stats.csv" - logger.debug { "Writing stats in '$statsFileName'..." } - File(statsFileName).outputStream().bufferedWriter().use { writer -> - val sep = ";" - writer.write(listOf("classname", "cwe", "method", "sink", "fact").joinToString(sep) + "\n") - for (vulnerability in foundVulnerabilities) { - val m = vulnerability.method - if (vulnerability.rule != null) { - for (cwe in vulnerability.rule.cwe) { - writer.write( - listOf( - m.enclosingClass.simpleName, - cwe, - m.name, - vulnerability.sink.statement, - vulnerability.sink.domainFact - ).joinToString(sep) { "\"$it\"" } + "\n") - } - } else if (vulnerability.sink.domainFact is NpeTaintNode) { - val cwe = 476 - writer.write( - listOf( - m.enclosingClass.simpleName, - cwe, - m.name, - vulnerability.sink.statement, - vulnerability.sink.domainFact - ).joinToString(sep) { "\"$it\"" } + "\n") - } else { - logger.warn { "Bad vulnerability without rule: $vulnerability" } - } - } - } - } - foundMethods.values.flatten().forEach { method -> for (crossUnitCall in crossUnitCallsStorage.getCurrentFacts(method)) { val calledMethod = graph.methodOf(crossUnitCall.calleeVertex.statement) @@ -317,14 +246,7 @@ class MainIfdsUnitManager( return@consumeEach } queueEmptiness[runner.unit] = event.isEmpty - // deleteJobs[runner.unit]?.run { - // logger.debug { "Cancelling the stopping of the runner for ${runner.unit}" } - // cancel() - // } if (event.isEmpty) { - // deleteJobs[runner.unit] = launch { - // logger.debug { "Going to stop the runner for ${runner.unit} in 5 seconds..." } - // delay(5.seconds) logger.info { "Stopping the runner for ${runner.unit}..." } val toDelete = mutableListOf(runner.unit) while (toDelete.isNotEmpty()) { @@ -332,8 +254,9 @@ class MainIfdsUnitManager( if (current in aliveRunners && dependencies[runner.unit].orEmpty().all { queueEmptiness[it] != false } ) { - if (aliveRunners[current] == null) continue - aliveRunners[current]!!.job?.cancel() ?: error("Runner's job is not instantiated") + val runner = aliveRunners[current] + if (runner == null) continue + runner.job?.cancel() ?: error("Runner's job is not instantiated") aliveRunners.remove(current) for (next in dependenciesRev[current].orEmpty()) { if (queueEmptiness[next] == true) { @@ -342,7 +265,6 @@ class MainIfdsUnitManager( } } } - // } } } From c5fbc8f7ebce12729d2bbb1ebe810fa4bbc657d6 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 15:12:09 +0300 Subject: [PATCH 006/117] fix: revert 2 --- .../jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt index 7c622fbde..a6f6269e1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt @@ -134,14 +134,16 @@ internal class SimplifiedJcApplicationGraph( override fun entryPoints(method: JcMethod): Sequence = try { sequenceOf(getStartInst(method)) } catch (e: Throwable) { + // we couldn't find instructions list + // TODO: maybe fix flowGraph() emptySequence() } override fun exitPoints(method: JcMethod): Sequence = try { graph.exitPoints(method) } catch (e: Throwable) { + // we couldn't find instructions list + // TODO: maybe fix flowGraph() emptySequence() } - - companion object } From 781e65a558e093e4b6c8d3f248f4af203e455b38 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 16:08:53 +0300 Subject: [PATCH 007/117] fix: revert 3 --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt | 2 +- .../src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt | 8 -------- .../kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt | 4 ++-- .../org/jacodb/analysis/ifds2/taint/TaintManager.kt | 8 +++----- .../org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 8 +++----- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt index 1acc43a4a..bf876b390 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt @@ -27,7 +27,7 @@ interface Manager { fun subscribeOnSummaryEdges( method: JcMethod, scope: CoroutineScope, - handler: suspend (Edge) -> Unit, + handler: (Edge) -> Unit, ) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index e8ca5d7c4..0416ee2b7 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -119,14 +119,6 @@ class Runner( // Handle only NEW edges: if (pathEdges.add(edge)) { - val doPrintOnlyForward = true - val doPrintZero = false - if (!doPrintOnlyForward || edge.from.statement.toString() == "noop") { - if (doPrintZero || edge.to.fact != Zero) { - logger.trace { "Propagating edge=$edge in method=${edge.method} via reason=${reason}" } - } - } - // Send edge to analyzer/manager: for (event in analyzer.handleNewEdge(edge)) { manager.handleEvent(event) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index 5275f3f8b..43593c6ea 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -74,7 +74,7 @@ class BidiRunner( override fun subscribeOnSummaryEdges( method: JcMethod, scope: CoroutineScope, - handler: suspend (TaintEdge) -> Unit, + handler: (TaintEdge) -> Unit, ) { manager.subscribeOnSummaryEdges(method, scope, handler) } @@ -107,7 +107,7 @@ class BidiRunner( override fun subscribeOnSummaryEdges( method: JcMethod, scope: CoroutineScope, - handler: suspend (TaintEdge) -> Unit, + handler: (TaintEdge) -> Unit, ) { // TODO: ignore? manager.subscribeOnSummaryEdges(method, scope, handler) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index dcd9eb50b..ac2c5aafd 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -16,7 +16,6 @@ package org.jacodb.analysis.ifds2.taint -import mu.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers @@ -24,7 +23,6 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.joinAll @@ -32,6 +30,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.yield +import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType @@ -294,12 +293,11 @@ class TaintManager( override fun subscribeOnSummaryEdges( method: JcMethod, scope: CoroutineScope, - handler: suspend (TaintEdge) -> Unit, + handler: (TaintEdge) -> Unit, ) { summaryEdgesStorage .getFacts(method) - .map { it.edge } - .onEach(handler) + .onEach { handler(it.edge) } .launchIn(scope) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index fbca93f7b..3416b9705 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -16,7 +16,6 @@ package org.jacodb.analysis.ifds2.taint.npe -import mu.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers @@ -24,7 +23,6 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.joinAll @@ -32,6 +30,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.yield +import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType @@ -273,12 +272,11 @@ class NpeManager( override fun subscribeOnSummaryEdges( method: JcMethod, scope: CoroutineScope, - handler: suspend (TaintEdge) -> Unit, + handler: (TaintEdge) -> Unit, ) { summaryEdgesStorage .getFacts(method) - .map { it.edge } - .onEach(handler) + .onEach { handler(it.edge) } .launchIn(scope) } } From 82f2101a3ff6fcc8434ccca58387e576518aee3f Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 16:46:34 +0300 Subject: [PATCH 008/117] fix: TODO --- .../org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 526c0e864..f8673c6f8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -86,12 +86,9 @@ class TaintAnalyzer( var triggeredItem: TaintMethodSink? = null for (item in config.filterIsInstance()) { defaultBehavior = false - try { - if (item.condition.accept(conditionEvaluator)) { - triggeredItem = item - break - } - } catch (_: IllegalStateException) { + if (item.condition.accept(conditionEvaluator)) { + triggeredItem = item + break } // FIXME: unconditionally let it be the sink. // triggeredItem = item @@ -99,7 +96,7 @@ class TaintAnalyzer( } if (triggeredItem != null) { // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } - val message = "SINK" // TODO + val message = triggeredItem.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) From 479be4ea18bc475fa323ffc392346ae980cb91e5 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 12 Feb 2024 18:43:42 +0300 Subject: [PATCH 009/117] fix: revert 5 --- .../ifds2/taint/TaintFlowFunctions.kt | 4 - .../analysis/ifds2/taint/npe/NpeAnalyzers.kt | 2 +- .../src/main/kotlin/org/jacodb/api/Classes.kt | 5 + .../kotlin/org/jacodb/api/cfg/JcGraphs.kt | 7 +- .../main/kotlin/org/jacodb/api/cfg/JcInst.kt | 464 ++++++++---------- .../kotlin/org/jacodb/api/cfg/JcRawInst.kt | 268 ++++------ .../kotlin/org/jacodb/api/ext/JcCommons.kt | 14 +- .../main/kotlin/org/jacodb/api/ext/JcTypes.kt | 36 +- .../src/main/kotlin/org/jacodb/cli/main.kt | 11 +- .../impl/StringConcatSimplifierTransformer.kt | 8 +- .../kotlin/org/jacodb/testing/BaseTest.kt | 16 +- jacodb-taint-configuration/build.gradle.kts | 2 +- .../taint/configuration/ConfigurationTrie.kt | 4 +- .../SerializedTaintConfigurationItem.kt | 30 +- .../jacodb/taint/configuration/TaintAction.kt | 2 - .../taint/configuration/TaintCondition.kt | 2 - .../TaintConfigurationFeature.kt | 9 +- 17 files changed, 376 insertions(+), 508 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt index 095a2bf10..ae77b4c2a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("LiftReturnOrAssignment") - package org.jacodb.analysis.ifds2.taint import mu.KotlinLogging @@ -64,7 +62,6 @@ import org.jacodb.taint.configuration.This private val logger = KotlinLogging.logger {} -@Suppress("PublicApiImplicitType") class ForwardTaintFlowFunctions( private val cp: JcClasspath, private val graph: JcApplicationGraph, @@ -507,7 +504,6 @@ class ForwardTaintFlowFunctions( } } -@Suppress("PublicApiImplicitType") class BackwardTaintFlowFunctions( private val project: JcClasspath, private val graph: JcApplicationGraph, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt index c7b190861..ccc8cb998 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt @@ -109,7 +109,7 @@ class NpeAnalyzer( } if (triggeredItem != null) { // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } - val message = "SINK" // TODO + val message = triggeredItem.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt index c37196f94..70118efd4 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/Classes.kt @@ -68,15 +68,19 @@ interface JcClassOrInterface : JcAnnotatedSymbol, JcAccessible { get() { return access and Opcodes.ACC_INTERFACE != 0 } + + } interface JcAnnotation : JcSymbol { + val visible: Boolean val jcClass: JcClassOrInterface? val values: Map fun matches(className: String): Boolean + } interface JcMethod : JcSymbol, JcAnnotatedSymbol, JcAccessible { @@ -131,6 +135,7 @@ interface JcMethod : JcSymbol, JcAnnotatedSymbol, JcAccessible { } interface JcField : JcAnnotatedSymbol, JcAccessible { + val enclosingClass: JcClassOrInterface val type: TypeName val signature: String? diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt index c171630ca..5e6335193 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcGraphs.kt @@ -19,15 +19,18 @@ package org.jacodb.api.cfg abstract class TypedExprResolver : AbstractFullExprSetCollector() { - val result: MutableSet = hashSetOf() + val result = hashSetOf() } + class LocalResolver : TypedExprResolver() { + override fun ifMatches(expr: JcExpr) { if (expr is JcLocal) { result.add(expr) } } + } class ValueResolver : TypedExprResolver() { @@ -100,4 +103,4 @@ val JcGraph.values: Set collect(it) } return resolver.result - } + } \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt index 3a9ff4861..c7f5e6d4a 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt @@ -41,14 +41,12 @@ interface JcInst { val location: JcInstLocation val operands: List - val lineNumber: Int get() = location.lineNumber + val lineNumber get() = location.lineNumber fun accept(visitor: JcInstVisitor): T } -abstract class AbstractJcInst( - override val location: JcInstLocation, -) : JcInst { +abstract class AbstractJcInst(override val location: JcInstLocation) : JcInst { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -66,13 +64,13 @@ abstract class AbstractJcInst( } data class JcInstRef( - val index: Int, + val index: Int ) class JcAssignInst( location: JcInstLocation, val lhv: JcValue, - val rhv: JcExpr, + val rhv: JcExpr ) : AbstractJcInst(location) { override val operands: List @@ -87,9 +85,8 @@ class JcAssignInst( class JcEnterMonitorInst( location: JcInstLocation, - val monitor: JcValue, + val monitor: JcValue ) : AbstractJcInst(location) { - override val operands: List get() = listOf(monitor) @@ -102,9 +99,8 @@ class JcEnterMonitorInst( class JcExitMonitorInst( location: JcInstLocation, - val monitor: JcValue, + val monitor: JcValue ) : AbstractJcInst(location) { - override val operands: List get() = listOf(monitor) @@ -117,9 +113,8 @@ class JcExitMonitorInst( class JcCallInst( location: JcInstLocation, - val callExpr: JcCallExpr, + val callExpr: JcCallExpr ) : AbstractJcInst(location) { - override val operands: List get() = listOf(callExpr) @@ -134,9 +129,8 @@ interface JcTerminatingInst : JcInst class JcReturnInst( location: JcInstLocation, - val returnValue: JcValue?, + val returnValue: JcValue? ) : AbstractJcInst(location), JcTerminatingInst { - override val operands: List get() = listOfNotNull(returnValue) @@ -149,9 +143,8 @@ class JcReturnInst( class JcThrowInst( location: JcInstLocation, - val throwable: JcValue, + val throwable: JcValue ) : AbstractJcInst(location), JcTerminatingInst { - override val operands: List get() = listOf(throwable) @@ -166,9 +159,8 @@ class JcCatchInst( location: JcInstLocation, val throwable: JcValue, val throwableTypes: List, - val throwers: List, + val throwers: List ) : AbstractJcInst(location) { - override val operands: List get() = listOf(throwable) @@ -185,9 +177,8 @@ interface JcBranchingInst : JcInst { class JcGotoInst( location: JcInstLocation, - val target: JcInstRef, + val target: JcInstRef ) : AbstractJcInst(location), JcBranchingInst { - override val operands: List get() = emptyList() @@ -205,9 +196,8 @@ class JcIfInst( location: JcInstLocation, val condition: JcConditionExpr, val trueBranch: JcInstRef, - val falseBranch: JcInstRef, + val falseBranch: JcInstRef ) : AbstractJcInst(location), JcBranchingInst { - override val operands: List get() = listOf(condition) @@ -225,9 +215,8 @@ class JcSwitchInst( location: JcInstLocation, val key: JcValue, val branches: Map, - val default: JcInstRef, + val default: JcInstRef ) : AbstractJcInst(location), JcBranchingInst { - override val operands: List get() = listOf(key) + branches.keys @@ -256,7 +245,7 @@ interface JcBinaryExpr : JcExpr { data class JcAddExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -271,7 +260,7 @@ data class JcAddExpr( data class JcAndExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -286,7 +275,7 @@ data class JcAndExpr( data class JcCmpExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -301,7 +290,7 @@ data class JcCmpExpr( data class JcCmpgExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -316,7 +305,7 @@ data class JcCmpgExpr( data class JcCmplExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -331,7 +320,7 @@ data class JcCmplExpr( data class JcDivExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -346,7 +335,7 @@ data class JcDivExpr( data class JcMulExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -363,7 +352,7 @@ interface JcConditionExpr : JcBinaryExpr data class JcEqExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -378,7 +367,7 @@ data class JcEqExpr( data class JcNeqExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -393,7 +382,7 @@ data class JcNeqExpr( data class JcGeExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -408,7 +397,7 @@ data class JcGeExpr( data class JcGtExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -423,7 +412,7 @@ data class JcGtExpr( data class JcLeExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -438,7 +427,7 @@ data class JcLeExpr( data class JcLtExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -453,7 +442,7 @@ data class JcLtExpr( data class JcOrExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -468,7 +457,7 @@ data class JcOrExpr( data class JcRemExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -483,7 +472,7 @@ data class JcRemExpr( data class JcShlExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -498,7 +487,7 @@ data class JcShlExpr( data class JcShrExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -513,7 +502,7 @@ data class JcShrExpr( data class JcSubExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -528,7 +517,7 @@ data class JcSubExpr( data class JcUshrExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -543,7 +532,7 @@ data class JcUshrExpr( data class JcXorExpr( override val type: JcType, override val lhv: JcValue, - override val rhv: JcValue, + override val rhv: JcValue ) : JcBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -557,7 +546,7 @@ data class JcXorExpr( data class JcLengthExpr( override val type: JcType, - val array: JcValue, + val array: JcValue ) : JcExpr { override val operands: List get() = listOf(array) @@ -571,7 +560,7 @@ data class JcLengthExpr( data class JcNegExpr( override val type: JcType, - val operand: JcValue, + val operand: JcValue ) : JcExpr { override val operands: List get() = listOf(operand) @@ -585,7 +574,7 @@ data class JcNegExpr( data class JcCastExpr( override val type: JcType, - val operand: JcValue, + val operand: JcValue ) : JcExpr { override val operands: List get() = listOf(operand) @@ -598,7 +587,7 @@ data class JcCastExpr( } data class JcNewExpr( - override val type: JcType, + override val type: JcType ) : JcExpr { override val operands: List get() = emptyList() @@ -612,7 +601,7 @@ data class JcNewExpr( data class JcNewArrayExpr( override val type: JcType, - val dimensions: List, + val dimensions: List ) : JcExpr { override val operands: List @@ -627,10 +616,7 @@ data class JcNewArrayExpr( companion object { private val regexToProcessDimensions = Regex("\\[(.*?)]") - private fun arrayTypeToStringWithDimensions( - typeName: String, - dimensions: List, - ): String { + private fun arrayTypeToStringWithDimensions(typeName: String, dimensions: List): String { var curDim = 0 return regexToProcessDimensions.replace(typeName) { "[${dimensions.getOrNull(curDim++) ?: ""}]" @@ -642,7 +628,7 @@ data class JcNewArrayExpr( data class JcInstanceOfExpr( override val type: JcType, val operand: JcValue, - val targetType: JcType, + val targetType: JcType ) : JcExpr { override val operands: List get() = listOf(operand) @@ -658,8 +644,7 @@ interface JcCallExpr : JcExpr { val method: JcTypedMethod val args: List - override val type: JcType - get() = method.returnType + override val type get() = method.returnType override val operands: List get() = args @@ -667,20 +652,19 @@ interface JcCallExpr : JcExpr { interface JcInstanceCallExpr : JcCallExpr { val instance: JcValue - - // TODO: What is that? val declaredMethod: JcTypedMethod override val operands: List get() = listOf(instance) + args + } data class JcPhiExpr( override val type: JcType, val values: List, - val args: List, + val args: List ) : JcExpr { - // TODO: shouldn't it be "values + args"? + override val operands: List get() = values @@ -689,6 +673,7 @@ data class JcPhiExpr( } } + /** * JcLambdaExpr is created when we can resolve the `invokedynamic` instruction. * When Java or Kotlin compiles a code with the lambda call, it generates @@ -704,12 +689,11 @@ data class JcLambdaExpr( val callSiteMethodName: String, val callSiteArgTypes: List, val callSiteReturnType: JcType, - val callSiteArgs: List, + val callSiteArgs: List ) : JcCallExpr { - override val method: JcTypedMethod - get() = bsmRef.method - override val args: List - get() = callSiteArgs + + override val method get() = bsmRef.method + override val args get() = callSiteArgs override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcLambdaExpr(this) @@ -722,12 +706,11 @@ data class JcDynamicCallExpr( val callSiteMethodName: String, val callSiteArgTypes: List, val callSiteReturnType: JcType, - val callSiteArgs: List, + val callSiteArgs: List ) : JcCallExpr { - override val method: JcTypedMethod - get() = bsmRef.method - override val args: List - get() = callSiteArgs + + override val method get() = bsmRef.method + override val args get() = callSiteArgs override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcDynamicCallExpr(this) @@ -743,32 +726,40 @@ data class JcVirtualCallExpr( override val instance: JcValue, override val args: List, ) : JcInstanceCallExpr { + override val method: JcTypedMethod - get() = methodRef.method + get() { + return methodRef.method + } override val declaredMethod: JcTypedMethod - get() = methodRef.declaredMethod + get() { + return methodRef.declaredMethod + } override fun toString(): String = - "$instance.${methodRef.name}${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") - }" + "$instance.${methodRef.name}${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcVirtualCallExpr(this) } } + data class JcStaticCallExpr( private val methodRef: TypedMethodRef, override val args: List, ) : JcCallExpr { - override val method: JcTypedMethod - get() = methodRef.method + + override val method: JcTypedMethod get() = methodRef.method override fun toString(): String = "${method.method.enclosingClass.name}.${methodRef.name}${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") + args.joinToString( + prefix = "(", + postfix = ")", + separator = ", " + ) }" override fun accept(visitor: JcExprVisitor): T { @@ -781,30 +772,32 @@ data class JcSpecialCallExpr( override val instance: JcValue, override val args: List, ) : JcInstanceCallExpr { - override val method: JcTypedMethod - get() = methodRef.method + + override val method: JcTypedMethod get() = methodRef.method override val declaredMethod: JcTypedMethod get() = method override fun toString(): String = - "$instance.${methodRef.name}${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") - }" + "$instance.${methodRef.name}${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcSpecialCallExpr(this) } } + interface JcValue : JcExpr interface JcSimpleValue : JcValue { + override val operands: List get() = emptyList() + } data class JcThis(override val type: JcType) : JcLocal { + override val name: String get() = "this" @@ -822,11 +815,7 @@ interface JcLocal : JcSimpleValue { /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcArgument( - val index: Int, - override val name: String, - override val type: JcType, -) : JcLocal { +data class JcArgument(val index: Int, override val name: String, override val type: JcType) : JcLocal { companion object { @JvmStatic fun of(index: Int, name: String?, type: JcType): JcArgument { @@ -862,10 +851,7 @@ data class JcArgument( /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcLocalVar( - val index: Int,override val name: String, - override val type: JcType, -) : JcLocal { +data class JcLocalVar(val index: Int, override val name: String, override val type: JcType) : JcLocal { override fun toString(): String = name override fun accept(visitor: JcExprVisitor): T { @@ -895,24 +881,14 @@ interface JcComplexValue : JcValue data class JcFieldRef( val instance: JcValue?, - val field: JcTypedField, + val field: JcTypedField ) : JcComplexValue { - init { - if (instance == null) { - require(field.isStatic) - } else { - require(!field.isStatic) - } - } - - override val type: JcType - get() = this.field.fieldType + override val type: JcType get() = this.field.fieldType override val operands: List get() = instance?.let { listOf(it) }.orEmpty() - override fun toString(): String = - "${instance ?: field.enclosingType.typeName}.${field.name}" + override fun toString(): String = "${instance ?: field.enclosingType.typeName}.${field.name}" override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcFieldRef(this) @@ -922,13 +898,13 @@ data class JcFieldRef( data class JcArrayAccess( val array: JcValue, val index: JcValue, - override val type: JcType, + override val type: JcType ) : JcComplexValue { + override fun toString(): String = "$array[$index]" + override val operands: List get() = listOf(array, index) - override fun toString(): String = "$array[$index]" - override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcArrayAccess(this) } @@ -936,124 +912,47 @@ data class JcArrayAccess( interface JcConstant : JcSimpleValue -data class JcBool( - val value: Boolean, - override val type: JcPrimitiveType, -) : JcConstant { - override fun toString(): String = "$value" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcBool(this) - } -} - -data class JcChar( - val value: Char, - override val type: JcPrimitiveType, -) : JcConstant { - override fun toString(): String = "$value" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcChar(this) - } -} - -data class JcNullConstant(override val type: JcType) : JcConstant { - override fun toString(): String = "null" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcNullConstant(this) - } -} - -data class JcStringConstant( - val value: String, - override val type: JcType, -) : JcConstant { - override fun toString(): String = "\"$value\"" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcStringConstant(this) - } -} - -/** - * klass may be JcClassType or JcArrayType for constructions like byte[].class - */ -data class JcClassConstant( - val klass: JcType, - override val type: JcType, -) : JcConstant { - override fun toString(): String = "${klass.typeName}.class" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcClassConstant(this) - } -} - -data class JcMethodConstant( - val method: JcTypedMethod, - override val type: JcType, -) : JcConstant { - override fun toString(): String = - "${method.method.enclosingClass.name}::${method.name}${ - method.parameters.joinToString(prefix = "(", postfix = ")", separator = ", ") - }:${method.returnType}" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcMethodConstant(this) - } -} - -data class JcMethodType( - val argumentTypes: List, - val returnType: JcType, - override val type: JcType, -) : JcConstant { - override fun toString(): String = - "${ - argumentTypes.joinToString(prefix = "(", postfix = ")", separator = ", ") - }:${returnType}" - - override fun accept(visitor: JcExprVisitor): T { - return visitor.visitJcMethodType(this) - } -} - interface JcNumericConstant : JcConstant { + val value: Number fun isEqual(c: JcNumericConstant): Boolean = c.value == value + fun isNotEqual(c: JcNumericConstant): Boolean = !isEqual(c) fun isLessThan(c: JcNumericConstant): Boolean + fun isLessThanOrEqual(c: JcNumericConstant): Boolean = isLessThan(c) || isEqual(c) fun isGreaterThan(c: JcNumericConstant): Boolean + fun isGreaterThanOrEqual(c: JcNumericConstant): Boolean = isGreaterThan(c) || isEqual(c) operator fun plus(c: JcNumericConstant): JcNumericConstant + operator fun minus(c: JcNumericConstant): JcNumericConstant + operator fun times(c: JcNumericConstant): JcNumericConstant + operator fun div(c: JcNumericConstant): JcNumericConstant + operator fun rem(c: JcNumericConstant): JcNumericConstant operator fun unaryMinus(): JcNumericConstant + } -data class JcByte( - override val value: Byte, - override val type: JcPrimitiveType, -) : JcNumericConstant { + +data class JcBool(val value: Boolean, override val type: JcPrimitiveType) : JcConstant { override fun toString(): String = "$value" - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toByte() + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcBool(this) } +} - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toByte() - } +data class JcByte(override val value: Byte, override val type: JcPrimitiveType) : JcNumericConstant { + override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { return JcInt(value + c.value.toByte(), type) @@ -1079,24 +978,29 @@ data class JcByte( return JcInt(-value, type) } + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toByte() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toByte() + } + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcByte(this) } } -data class JcShort( - override val value: Short, - override val type: JcPrimitiveType, -) : JcNumericConstant { +data class JcChar(val value: Char, override val type: JcPrimitiveType) : JcConstant { override fun toString(): String = "$value" - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toShort() + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcChar(this) } +} - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toShort() - } +data class JcShort(override val value: Short, override val type: JcPrimitiveType) : JcNumericConstant { + override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { return JcInt(value + c.value.toShort(), type) @@ -1122,25 +1026,22 @@ data class JcShort( return JcInt(-value, type) } + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toShort() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toShort() + } + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcShort(this) } } -data class JcInt( - override val value: Int, - override val type: JcPrimitiveType, -) : JcNumericConstant { +data class JcInt(override val value: Int, override val type: JcPrimitiveType) : JcNumericConstant { override fun toString(): String = "$value" - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toInt() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toInt() - } - override fun plus(c: JcNumericConstant): JcNumericConstant { return JcInt(value + c.value.toInt(), type) } @@ -1165,6 +1066,14 @@ data class JcInt( return JcInt(-value, type) } + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toInt() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toInt() + } + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcInt(this) } @@ -1176,14 +1085,6 @@ data class JcLong( ) : JcNumericConstant { override fun toString(): String = "$value" - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toLong() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toLong() - } - override fun plus(c: JcNumericConstant): JcNumericConstant { return JcLong(value + c.value.toLong(), type) } @@ -1208,6 +1109,14 @@ data class JcLong( return JcLong(-value, type) } + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toLong() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toLong() + } + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcLong(this) } @@ -1219,14 +1128,6 @@ data class JcFloat( ) : JcNumericConstant { override fun toString(): String = "$value" - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toFloat() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toFloat() - } - override fun plus(c: JcNumericConstant): JcNumericConstant { return JcFloat(value + c.value.toFloat(), type) } @@ -1251,6 +1152,14 @@ data class JcFloat( return JcFloat(-value, type) } + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toFloat() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toFloat() + } + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcFloat(this) } @@ -1262,14 +1171,6 @@ data class JcDouble( ) : JcNumericConstant { override fun toString(): String = "$value" - override fun isLessThan(c: JcNumericConstant): Boolean { - return value < c.value.toDouble() - } - - override fun isGreaterThan(c: JcNumericConstant): Boolean { - return value > c.value.toDouble() - } - override fun plus(c: JcNumericConstant): JcNumericConstant { return JcDouble(value + c.value.toDouble(), type) } @@ -1294,7 +1195,78 @@ data class JcDouble( return JcDouble(-value, type) } + override fun isLessThan(c: JcNumericConstant): Boolean { + return value < c.value.toDouble() + } + + override fun isGreaterThan(c: JcNumericConstant): Boolean { + return value > c.value.toDouble() + } + override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcDouble(this) } } + +data class JcNullConstant(override val type: JcType) : JcConstant { + override fun toString(): String = "null" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcNullConstant(this) + } +} + +data class JcStringConstant(val value: String, override val type: JcType) : JcConstant { + override fun toString(): String = "\"$value\"" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcStringConstant(this) + } +} + +/** + * klass may be JcClassType or JcArrayType for constructions like byte[].class + */ +data class JcClassConstant(val klass: JcType, override val type: JcType) : JcConstant { + + override fun toString(): String = "${klass.typeName}.class" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcClassConstant(this) + } +} + +data class JcMethodConstant( + val method: JcTypedMethod, + override val type: JcType +) : JcConstant { + override fun toString(): String = "${method.method.enclosingClass.name}::${method.name}${ + method.parameters.joinToString( + prefix = "(", + postfix = ")", + separator = ", " + ) + }:${method.returnType}" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcMethodConstant(this) + } +} + +data class JcMethodType( + val argumentTypes: List, + val returnType: JcType, + override val type: JcType +) : JcConstant { + override fun toString(): String = "${ + argumentTypes.joinToString( + prefix = "(", + postfix = ")", + separator = ", " + ) + }:${returnType}" + + override fun accept(visitor: JcExprVisitor): T { + return visitor.visitJcMethodType(this) + } +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt index a21a743f3..9c35b4f7c 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt @@ -21,6 +21,7 @@ import org.jacodb.api.TypeName sealed interface JcRawInst { val owner: JcMethod + val operands: List fun accept(visitor: JcRawInstVisitor): T @@ -29,8 +30,9 @@ sealed interface JcRawInst { class JcRawAssignInst( override val owner: JcMethod, val lhv: JcRawValue, - val rhv: JcRawExpr, + val rhv: JcRawExpr ) : JcRawInst { + override val operands: List get() = listOf(lhv, rhv) @@ -43,7 +45,7 @@ class JcRawAssignInst( class JcRawEnterMonitorInst( override val owner: JcMethod, - val monitor: JcRawSimpleValue, + val monitor: JcRawSimpleValue ) : JcRawInst { override val operands: List get() = listOf(monitor) @@ -57,7 +59,7 @@ class JcRawEnterMonitorInst( class JcRawExitMonitorInst( override val owner: JcMethod, - val monitor: JcRawSimpleValue, + val monitor: JcRawSimpleValue ) : JcRawInst { override val operands: List get() = listOf(monitor) @@ -71,7 +73,7 @@ class JcRawExitMonitorInst( class JcRawCallInst( override val owner: JcMethod, - val callExpr: JcRawCallExpr, + val callExpr: JcRawCallExpr ) : JcRawInst { override val operands: List get() = listOf(callExpr) @@ -87,11 +89,8 @@ data class JcRawLabelRef(val name: String) { override fun toString() = name } -class JcRawLineNumberInst( - override val owner: JcMethod, - val lineNumber: Int, - val start: JcRawLabelRef, -) : JcRawInst { +class JcRawLineNumberInst(override val owner: JcMethod, val lineNumber: Int, val start: JcRawLabelRef) : JcRawInst { + override val operands: List get() = emptyList() @@ -102,9 +101,10 @@ class JcRawLineNumberInst( } } + class JcRawLabelInst( override val owner: JcMethod, - val name: String, + val name: String ) : JcRawInst { override val operands: List get() = emptyList() @@ -120,13 +120,12 @@ class JcRawLabelInst( class JcRawReturnInst( override val owner: JcMethod, - val returnValue: JcRawValue?, + val returnValue: JcRawValue? ) : JcRawInst { override val operands: List get() = listOfNotNull(returnValue) - override fun toString(): String = - "return" + (returnValue?.let { " $it" } ?: "") + override fun toString(): String = "return" + (returnValue?.let { " $it" } ?: "") override fun accept(visitor: JcRawInstVisitor): T { return visitor.visitJcRawReturnInst(this) @@ -135,7 +134,7 @@ class JcRawReturnInst( class JcRawThrowInst( override val owner: JcMethod, - val throwable: JcRawValue, + val throwable: JcRawValue ) : JcRawInst { override val operands: List get() = listOf(throwable) @@ -150,7 +149,7 @@ class JcRawThrowInst( data class JcRawCatchEntry( val acceptedThrowable: TypeName, val startInclusive: JcRawLabelRef, - val endExclusive: JcRawLabelRef, + val endExclusive: JcRawLabelRef ) class JcRawCatchInst( @@ -162,8 +161,7 @@ class JcRawCatchInst( override val operands: List get() = listOf(throwable) - override fun toString(): String = - "catch ($throwable: ${throwable.typeName})" + override fun toString(): String = "catch ($throwable: ${throwable.typeName})" override fun accept(visitor: JcRawInstVisitor): T { return visitor.visitJcRawCatchInst(this) @@ -176,7 +174,7 @@ sealed interface JcRawBranchingInst : JcRawInst { class JcRawGotoInst( override val owner: JcMethod, - val target: JcRawLabelRef, + val target: JcRawLabelRef ) : JcRawBranchingInst { override val operands: List get() = emptyList() @@ -195,7 +193,7 @@ data class JcRawIfInst( override val owner: JcMethod, val condition: JcRawConditionExpr, val trueBranch: JcRawLabelRef, - val falseBranch: JcRawLabelRef, + val falseBranch: JcRawLabelRef ) : JcRawBranchingInst { override val operands: List get() = listOf(condition) @@ -203,8 +201,7 @@ data class JcRawIfInst( override val successors: List get() = listOf(trueBranch, falseBranch) - override fun toString(): String = - "if ($condition) goto $trueBranch else $falseBranch" + override fun toString(): String = "if ($condition) goto $trueBranch else $falseBranch" override fun accept(visitor: JcRawInstVisitor): T { return visitor.visitJcRawIfInst(this) @@ -215,7 +212,7 @@ data class JcRawSwitchInst( override val owner: JcMethod, val key: JcRawValue, val branches: Map, - val default: JcRawLabelRef, + val default: JcRawLabelRef ) : JcRawBranchingInst { override val operands: List get() = listOf(key) + branches.keys @@ -225,9 +222,7 @@ data class JcRawSwitchInst( override fun toString(): String = buildString { append("switch ($key) { ") - for ((option, label) in branches) { - append("$option -> $label") - } + branches.forEach { (option, label) -> append("$option -> $label") } append("else -> ${default.name} }") } @@ -251,7 +246,7 @@ interface JcRawBinaryExpr : JcRawExpr { data class JcRawAddExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -266,7 +261,7 @@ data class JcRawAddExpr( data class JcRawAndExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -281,7 +276,7 @@ data class JcRawAndExpr( data class JcRawCmpExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -296,7 +291,7 @@ data class JcRawCmpExpr( data class JcRawCmpgExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -311,7 +306,7 @@ data class JcRawCmpgExpr( data class JcRawCmplExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -326,7 +321,7 @@ data class JcRawCmplExpr( data class JcRawDivExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -341,7 +336,7 @@ data class JcRawDivExpr( data class JcRawMulExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -358,7 +353,7 @@ sealed interface JcRawConditionExpr : JcRawBinaryExpr data class JcRawEqExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -373,7 +368,7 @@ data class JcRawEqExpr( data class JcRawNeqExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -388,7 +383,7 @@ data class JcRawNeqExpr( data class JcRawGeExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -403,7 +398,7 @@ data class JcRawGeExpr( data class JcRawGtExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -418,7 +413,7 @@ data class JcRawGtExpr( data class JcRawLeExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -433,7 +428,7 @@ data class JcRawLeExpr( data class JcRawLtExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawConditionExpr { override val operands: List get() = listOf(lhv, rhv) @@ -448,7 +443,7 @@ data class JcRawLtExpr( data class JcRawOrExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -463,7 +458,7 @@ data class JcRawOrExpr( data class JcRawRemExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -478,7 +473,7 @@ data class JcRawRemExpr( data class JcRawShlExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -493,7 +488,7 @@ data class JcRawShlExpr( data class JcRawShrExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -508,7 +503,7 @@ data class JcRawShrExpr( data class JcRawSubExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -523,7 +518,7 @@ data class JcRawSubExpr( data class JcRawUshrExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -538,7 +533,7 @@ data class JcRawUshrExpr( data class JcRawXorExpr( override val typeName: TypeName, override val lhv: JcRawValue, - override val rhv: JcRawValue, + override val rhv: JcRawValue ) : JcRawBinaryExpr { override val operands: List get() = listOf(lhv, rhv) @@ -552,7 +547,7 @@ data class JcRawXorExpr( data class JcRawLengthExpr( override val typeName: TypeName, - val array: JcRawValue, + val array: JcRawValue ) : JcRawExpr { override val operands: List get() = listOf(array) @@ -566,7 +561,7 @@ data class JcRawLengthExpr( data class JcRawNegExpr( override val typeName: TypeName, - val operand: JcRawValue, + val operand: JcRawValue ) : JcRawExpr { override val operands: List get() = listOf(operand) @@ -580,7 +575,7 @@ data class JcRawNegExpr( data class JcRawCastExpr( override val typeName: TypeName, - val operand: JcRawValue, + val operand: JcRawValue ) : JcRawExpr { override val operands: List get() = listOf(operand) @@ -593,7 +588,7 @@ data class JcRawCastExpr( } data class JcRawNewExpr( - override val typeName: TypeName, + override val typeName: TypeName ) : JcRawExpr { override val operands: List get() = emptyList() @@ -607,16 +602,14 @@ data class JcRawNewExpr( data class JcRawNewArrayExpr( override val typeName: TypeName, - val dimensions: List, + val dimensions: List ) : JcRawExpr { - constructor(typeName: TypeName, length: JcRawValue) : - this(typeName, listOf(length)) + constructor(typeName: TypeName, length: JcRawValue) : this(typeName, listOf(length)) override val operands: List get() = dimensions - override fun toString(): String = - "new ${arrayTypeToStringWithDimensions(typeName, dimensions)}" + override fun toString(): String = "new ${arrayTypeToStringWithDimensions(typeName, dimensions)}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawNewArrayExpr(this) @@ -637,13 +630,12 @@ data class JcRawNewArrayExpr( data class JcRawInstanceOfExpr( override val typeName: TypeName, val operand: JcRawValue, - val targetType: TypeName, + val targetType: TypeName ) : JcRawExpr { override val operands: List get() = listOf(operand) - override fun toString(): String = - "$operand instanceof ${targetType.typeName}" + override fun toString(): String = "$operand instanceof ${targetType.typeName}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawInstanceOfExpr(this) @@ -657,14 +649,13 @@ sealed interface JcRawCallExpr : JcRawExpr { val returnType: TypeName val args: List - override val typeName: TypeName - get() = returnType + override val typeName get() = returnType override val operands: List get() = args } -sealed interface JcRawInstanceExpr : JcRawCallExpr { +sealed interface JcRawInstanceExpr: JcRawCallExpr { val instance: JcRawValue } @@ -694,12 +685,8 @@ data class BsmTypeArg(val typeName: TypeName) : BsmArg { override fun toString(): String = typeName.typeName } -data class BsmMethodTypeArg( - val argumentTypes: List, - val returnType: TypeName, -) : BsmArg { - override fun toString(): String = - "(${argumentTypes.joinToString { it.typeName }}:${returnType.typeName})" +data class BsmMethodTypeArg(val argumentTypes: List, val returnType: TypeName) : BsmArg { + override fun toString(): String = "(${argumentTypes.joinToString { it.typeName }}:${returnType.typeName})" } data class BsmHandle( @@ -717,20 +704,14 @@ data class JcRawDynamicCallExpr( val callSiteMethodName: String, val callSiteArgTypes: List, val callSiteReturnType: TypeName, - val callSiteArgs: List, + val callSiteArgs: List ) : JcRawCallExpr { - override val declaringClass: TypeName - get() = bsm.declaringClass - override val methodName: String - get() = bsm.name - override val argumentTypes: List - get() = bsm.argTypes - override val returnType: TypeName - get() = bsm.returnType - override val typeName: TypeName - get() = returnType - override val args: List - get() = callSiteArgs + override val declaringClass get() = bsm.declaringClass + override val methodName get() = bsm.name + override val argumentTypes get() = bsm.argTypes + override val returnType get() = bsm.returnType + override val typeName get() = returnType + override val args get() = callSiteArgs override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawDynamicCallExpr(this) @@ -749,9 +730,7 @@ data class JcRawVirtualCallExpr( get() = listOf(instance) + args override fun toString(): String = - "$instance.$methodName${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") - }" + "$instance.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawVirtualCallExpr(this) @@ -770,9 +749,7 @@ data class JcRawInterfaceCallExpr( get() = listOf(instance) + args override fun toString(): String = - "$instance.$methodName${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") - }" + "$instance.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawInterfaceCallExpr(this) @@ -788,9 +765,7 @@ data class JcRawStaticCallExpr( val isInterfaceMethodCall: Boolean = false, ) : JcRawCallExpr { override fun toString(): String = - "$declaringClass.$methodName${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") - }" + "$declaringClass.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawStaticCallExpr(this) @@ -806,15 +781,14 @@ data class JcRawSpecialCallExpr( override val args: List, ) : JcRawInstanceExpr { override fun toString(): String = - "$instance.$methodName${ - args.joinToString(prefix = "(", postfix = ")", separator = ", ") - }" + "$instance.$methodName${args.joinToString(prefix = "(", postfix = ")", separator = ", ")}" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawSpecialCallExpr(this) } } + sealed interface JcRawValue : JcRawExpr { override val operands: List get() = emptyList() @@ -837,11 +811,7 @@ data class JcRawThis(override val typeName: TypeName) : JcRawSimpleValue { /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcRawArgument( - val index: Int, - override val name: String, - override val typeName: TypeName, -) : JcRawLocal { +data class JcRawArgument(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal { companion object { @JvmStatic fun of(index: Int, name: String?, typeName: TypeName): JcRawArgument { @@ -849,6 +819,7 @@ data class JcRawArgument( } } + override fun toString(): String = name override fun accept(visitor: JcRawExprVisitor): T { @@ -877,10 +848,7 @@ data class JcRawArgument( /** * @param name isn't considered in `equals` and `hashcode` */ -data class JcRawLocalVar( - val index: Int,override val name: String, - override val typeName: TypeName, -) : JcRawLocal { +data class JcRawLocalVar(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal { override fun toString(): String = name override fun accept(visitor: JcRawExprVisitor): T { @@ -912,10 +880,14 @@ data class JcRawFieldRef( val instance: JcRawValue?, val declaringClass: TypeName, val fieldName: String, - override val typeName: TypeName, + override val typeName: TypeName ) : JcRawComplexValue { - constructor(declaringClass: TypeName, fieldName: String, typeName: TypeName) : - this(null, declaringClass, fieldName, typeName) + constructor(declaringClass: TypeName, fieldName: String, typeName: TypeName) : this( + null, + declaringClass, + fieldName, + typeName + ) override fun toString(): String = "${instance ?: declaringClass}.$fieldName" @@ -927,7 +899,7 @@ data class JcRawFieldRef( data class JcRawArrayAccess( val array: JcRawValue, val index: JcRawValue, - override val typeName: TypeName, + override val typeName: TypeName ) : JcRawComplexValue { override fun toString(): String = "$array[$index]" @@ -938,10 +910,7 @@ data class JcRawArrayAccess( sealed interface JcRawConstant : JcRawSimpleValue -data class JcRawBool( - val value: Boolean, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawBool(val value: Boolean, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -949,10 +918,7 @@ data class JcRawBool( } } -data class JcRawByte( - val value: Byte, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawByte(val value: Byte, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -960,10 +926,7 @@ data class JcRawByte( } } -data class JcRawChar( - val value: Char, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawChar(val value: Char, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -971,10 +934,7 @@ data class JcRawChar( } } -data class JcRawShort( - val value: Short, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawShort(val value: Short, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -982,10 +942,7 @@ data class JcRawShort( } } -data class JcRawInt( - val value: Int, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawInt(val value: Int, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -993,10 +950,7 @@ data class JcRawInt( } } -data class JcRawLong( - val value: Long, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawLong(val value: Long, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -1004,10 +958,7 @@ data class JcRawLong( } } -data class JcRawFloat( - val value: Float, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawFloat(val value: Float, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -1015,10 +966,7 @@ data class JcRawFloat( } } -data class JcRawDouble( - val value: Double, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawDouble(val value: Double, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$value" override fun accept(visitor: JcRawExprVisitor): T { @@ -1034,10 +982,7 @@ data class JcRawNullConstant(override val typeName: TypeName) : JcRawConstant { } } -data class JcRawStringConstant( - val value: String, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawStringConstant(val value: String, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "\"$value\"" override fun accept(visitor: JcRawExprVisitor): T { @@ -1045,10 +990,7 @@ data class JcRawStringConstant( } } -data class JcRawClassConstant( - val className: TypeName, - override val typeName: TypeName, -) : JcRawConstant { +data class JcRawClassConstant(val className: TypeName, override val typeName: TypeName) : JcRawConstant { override fun toString(): String = "$className.class" override fun accept(visitor: JcRawExprVisitor): T { @@ -1061,12 +1003,10 @@ data class JcRawMethodConstant( val name: String, val argumentTypes: List, val returnType: TypeName, - override val typeName: TypeName, + override val typeName: TypeName ) : JcRawConstant { override fun toString(): String = - "$declaringClass.$name${ - argumentTypes.joinToString(prefix = "(", postfix = ")") - }:$returnType" + "$declaringClass.$name${argumentTypes.joinToString(prefix = "(", postfix = ")")}:$returnType" override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawMethodConstant(this) @@ -1076,10 +1016,9 @@ data class JcRawMethodConstant( data class JcRawMethodType( val argumentTypes: List, val returnType: TypeName, - private val methodType: TypeName, + private val methodType: TypeName ) : JcRawConstant { - override val typeName: TypeName - get() = methodType + override val typeName: TypeName = methodType override fun toString(): String = "${argumentTypes.joinToString(prefix = "(", postfix = ")")}:$returnType" @@ -1089,17 +1028,18 @@ data class JcRawMethodType( } } -// fun JcRawInstList.lineNumberOf(inst: JcRawInst): Int? { -// val idx: Int = instructions.indexOf(inst) -// assert(idx != -1) // -// // Get index of labels and insnNode within method -// val insnIt: ListIterator = insnList.iterator(idx) -// while (insnIt.hasPrevious()) { -// val node: AbstractInsnNode = insnIt.previous() -// if (node is LineNumberNode) { -// return node as LineNumberNode -// } -// } -// return null -// } +//fun JcRawInstList.lineNumberOf(inst: JcRawInst): Int? { +// val idx: Int = instructions.indexOf(inst) +// assert(idx != -1) +// +// // Get index of labels and insnNode within method +// val insnIt: ListIterator = insnList.iterator(idx) +// while (insnIt.hasPrevious()) { +// val node: AbstractInsnNode = insnIt.previous() +// if (node is LineNumberNode) { +// return node as LineNumberNode +// } +// } +// return null +//} \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt index 4ccc7ba21..3e3789532 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcCommons.kt @@ -18,15 +18,9 @@ package org.jacodb.api.ext -import org.jacodb.api.JcAnnotated -import org.jacodb.api.JcAnnotation -import org.jacodb.api.JcClassOrInterface -import org.jacodb.api.JcClassType -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod -import org.jacodb.api.PredefinedPrimitives -import org.jacodb.api.throwClassNotFound +import org.jacodb.api.* import java.io.Serializable +import java.lang.Cloneable fun String.jvmName(): String { return when { @@ -74,6 +68,7 @@ fun String.jcdbName(): String { } } + val JcClasspath.objectType: JcClassType get() = findTypeOrNull() as? JcClassType ?: throwClassNotFound() @@ -86,6 +81,7 @@ val JcClasspath.cloneableClass: JcClassOrInterface val JcClasspath.serializableClass: JcClassOrInterface get() = findClass() + // call with SAFE. comparator works only on methods from one hierarchy internal object UnsafeHierarchyMethodComparator : Comparator { @@ -100,4 +96,4 @@ fun JcAnnotated.hasAnnotation(className: String): Boolean { fun JcAnnotated.annotation(className: String): JcAnnotation? { return annotations.firstOrNull { it.matches(className) } -} +} \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt index 4470d99d3..1e620daf5 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt @@ -28,12 +28,6 @@ import org.jacodb.api.JcTypedField import org.jacodb.api.JcTypedMethod import org.jacodb.api.TypeNotFoundException import org.jacodb.api.throwClassNotFound -import java.lang.Boolean -import java.lang.Byte -import java.lang.Double -import java.lang.Float -import java.lang.Long -import java.lang.Short val JcClassType.constructors get() = declaredMethods.filter { it.method.isConstructor } @@ -73,14 +67,14 @@ fun JcType.unboxIfNeeded(): JcType { @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") fun JcType.autoboxIfNeeded(): JcType { return when (this) { - classpath.boolean -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.byte -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.boolean -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.byte -> classpath.findTypeOrNull() ?: throwClassNotFound() classpath.char -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.short -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.short -> classpath.findTypeOrNull() ?: throwClassNotFound() classpath.int -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.long -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.float -> classpath.findTypeOrNull() ?: throwClassNotFound() - classpath.double -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.long -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.float -> classpath.findTypeOrNull() ?: throwClassNotFound() + classpath.double -> classpath.findTypeOrNull() ?: throwClassNotFound() else -> this } } @@ -164,6 +158,7 @@ fun JcType.isAssignable(declaration: JcType): kotlin.Boolean { } } + /** * find field by name * @@ -194,15 +189,14 @@ fun JcClassType.findMethodOrNull(predicate: (JcTypedMethod) -> kotlin.Boolean): return methods.firstOrNull(predicate) } -val JcTypedMethod.humanReadableSignature: String - get() { - val params = parameters.joinToString(",") { it.type.typeName } - val generics = typeParameters.takeIf { it.isNotEmpty() }?.let { - it.joinToString(prefix = "<", separator = ",", postfix = ">") { it.symbol } - } ?: "" - return "${enclosingType.typeName}#$generics$name($params):${returnType.typeName}" - } +val JcTypedMethod.humanReadableSignature: String get() { + val params = parameters.joinToString(",") { it.type.typeName } + val generics = typeParameters.takeIf { it.isNotEmpty() }?.let{ + it.joinToString(prefix = "<", separator = ",", postfix = ">") { it.symbol } + } ?: "" + return "${enclosingType.typeName}#$generics$name($params):${returnType.typeName}" +} fun JcClasspath.findType(name: String): JcType { return findTypeOrNull(name) ?: throw TypeNotFoundException(name) -} +} \ No newline at end of file diff --git a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt index 8c77acd91..c167eec0a 100644 --- a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt +++ b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt @@ -50,13 +50,9 @@ class AnalysisMain { fun run(args: List) = main(args.toTypedArray()) } -fun launchAnalysesByConfig( - config: AnalysisConfig, - graph: JcApplicationGraph, - methods: List, -): List> { +fun launchAnalysesByConfig(config: AnalysisConfig, graph: JcApplicationGraph, methods: List): List> { return config.analyses.mapNotNull { (analysis, options) -> - val unitResolver: UnitResolver = options["UnitResolver"]?.let { + val unitResolver = options["UnitResolver"]?.let { UnitResolver.getByName(it) } ?: MethodUnitResolver @@ -75,6 +71,7 @@ fun launchAnalysesByConfig( } } + fun main(args: Array) { val parser = ArgParser("taint-analysis") val configFilePath by parser.option( @@ -158,4 +155,4 @@ fun main(args: Array) { outputFile.outputStream().use { fileOutputStream -> report.encodeToStream(fileOutputStream) } -} +} \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt index d655ea14e..3ecba69df 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt @@ -52,10 +52,10 @@ class StringConcatSimplifierTransformer( return inst } - private val instructionReplacements: MutableMap = mutableMapOf() - private val instructions: MutableList = mutableListOf() - private val catchReplacements: MutableMap> = mutableMapOf() - private val instructionIndices: MutableMap = mutableMapOf() + private val instructionReplacements = mutableMapOf() + private val instructions = mutableListOf() + private val catchReplacements = mutableMapOf>() + private val instructionIndices = mutableMapOf() private val stringType = classpath.findTypeOrNull() as JcClassType diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt index b373d690f..0279fbdb0 100644 --- a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt @@ -33,6 +33,7 @@ import kotlin.reflect.full.companionObjectInstance @Tag("lifecycle") annotation class LifecycleTest + abstract class BaseTest { protected open val cp: JcClasspath = runBlocking { @@ -60,7 +61,9 @@ val Class<*>.withDB: JcDatabaseHolder return s.withDB } + interface JcDatabaseHolder { + val classpathFeatures: List val db: JcDatabase fun cleanup() @@ -79,17 +82,6 @@ open class WithDB(vararg features: Any) : JcDatabaseHolder { override var db = runBlocking { jacodb { - val persistentLocation = System.getenv("JACODB_PERSISTENT") - if (persistentLocation != null) { - persistent(persistentLocation) - } - - // val ci = System.getenv("CI") - // println("CI=$ci") - // if (ci != "true") { - // persistent("/tmp/index.db") - // } - loadByteCode(allClasspath) useProcessJavaRuntime() keepLocalVariableNames() @@ -122,6 +114,8 @@ open class WithGlobalDB(vararg _classpathFeatures: JcClasspathFeature) : JcDatab } } + + open class WithRestoredDB(vararg features: JcFeature<*, *>) : WithDB(*features) { private val jdbcLocation = Files.createTempFile("jcdb-", null).toFile().absolutePath diff --git a/jacodb-taint-configuration/build.gradle.kts b/jacodb-taint-configuration/build.gradle.kts index f375bfa41..87707cc30 100644 --- a/jacodb-taint-configuration/build.gradle.kts +++ b/jacodb-taint-configuration/build.gradle.kts @@ -20,4 +20,4 @@ dependencies { tasks.test { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt index c1740a0ca..4e61cdfb6 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/ConfigurationTrie.kt @@ -21,7 +21,7 @@ import org.jacodb.api.ext.packageName class ConfigurationTrie( configuration: List, - private val nameMatcher: (NameMatcher, String) -> Boolean, + private val nameMatcher: (NameMatcher, String) -> Boolean ) { private val unprocessedRules: MutableList = configuration.toMutableList() private val rootNode: RootNode = RootNode() @@ -153,7 +153,7 @@ class ConfigurationTrie( } private data class Leaf( - override val value: String, + override val value: String ) : Node() { override val children: MutableMap get() = error("Leaf nodes do not have children") diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt index a4fdceb6e..315dd6f59 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt @@ -18,8 +18,7 @@ package org.jacodb.taint.configuration import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json + @Serializable sealed interface SerializedTaintConfigurationItem { @@ -76,30 +75,3 @@ data class SerializedTaintCleaner( @SerialName("condition") val condition: Condition, @SerialName("actionsAfter") val actionsAfter: List, ) : SerializedTaintConfigurationItem - -fun main() { - val methodSource = SerializedTaintMethodSource( - methodInfo = FunctionMatcher( - cls = ClassMatcher( - pkg = NameExactMatcher("java.util"), - classNameMatcher = NameExactMatcher("Scanner") - ), - functionName = NamePatternMatcher("findInLine|findWithinHorizon|nextLine|useDelimiter|useLocale|useRadix|skip|reset"), - parametersMatchers = emptyList(), - returnTypeMatcher = AnyTypeMatcher, - applyToOverrides = true, - functionLabel = null, - modifier = -1, - exclude = emptyList() - ), - condition = ConstantTrue, - actionsAfter = listOf( - CopyAllMarks(from = This, to = Result) - ) - ) - val json = Json { - prettyPrint = true - } - val methodSourceJson = json.encodeToString(methodSource) - println("methodSource.toJson() = $methodSourceJson") -} diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt index c76337c25..6d1937fd4 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("PublicApiImplicitType") - package org.jacodb.taint.configuration import kotlinx.serialization.SerialName diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt index 6ab5f2aa4..43e2141ee 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("PublicApiImplicitType") - package org.jacodb.taint.configuration import kotlinx.serialization.SerialName diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt index 1f7e25bbc..d8e51c36f 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("PublicApiImplicitType") - package org.jacodb.taint.configuration import kotlinx.serialization.decodeFromString @@ -112,6 +110,7 @@ class TaintConfigurationFeature private constructor( return primitiveTypesSet!! } + private fun resolveConfigForMethod(method: JcMethod): List { val taintConfigurationItems = rulesForMethod[method] if (taintConfigurationItems != null) { @@ -119,9 +118,13 @@ class TaintConfigurationFeature private constructor( } val classRules = getClassRules(method.enclosingClass) + val destination = mutableListOf() + classRules.mapNotNullTo(destination) { + val functionMatcher = it.methodInfo + if (!functionMatcher.matches(method)) return@mapNotNullTo null it.resolveForMethod(method) } @@ -327,7 +330,7 @@ class TaintConfigurationFeature private constructor( val typeMatcher = condition.typeMatcher if (typeMatcher is AnyTypeMatcher) { - return mkTrue() + return mkOr(position.map { ConstantTrue }) } if (typeMatcher is PrimitiveNameMatcher) { From 5c9bcf44bc8e39983fe8803fedc98ace136118a9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 11:38:08 +0300 Subject: [PATCH 010/117] Debug messages --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt | 8 ++++++++ .../org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index 0416ee2b7..e4d1734de 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -119,6 +119,14 @@ class Runner( // Handle only NEW edges: if (pathEdges.add(edge)) { + val doPrintOnlyForward = true + val doPrintZero = false + if (!doPrintOnlyForward || edge.from.statement.toString() == "noop") { + if (doPrintZero || edge.to.fact != Zero) { + logger.trace { "Propagating edge=$edge in method=${edge.method.name} with reason=${reason}" } + } + } + // Send edge to analyzer/manager: for (event in analyzer.handleNewEdge(edge)) { manager.handleEvent(event) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index f8673c6f8..0f4bdc33e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -98,7 +98,7 @@ class TaintAnalyzer( // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } val message = triggeredItem.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) - // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) // verticesWithTraceGraphNeeded.add(edge.to) } @@ -110,7 +110,7 @@ class TaintAnalyzer( // logger.info { "Found sink at ${edge.to} in ${edge.method}" } val message = "SINK" // TODO val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) - // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) // verticesWithTraceGraphNeeded.add(edge.to) } @@ -128,6 +128,7 @@ class TaintAnalyzer( if (p == fact.variable) { val message = "Tainted loop operand" val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) + logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) } } From d57d3c4f932e0666358006ee16ad11d29553c626 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 11:46:19 +0300 Subject: [PATCH 011/117] Make handleControlEvent non-suspend --- .../org/jacodb/analysis/ifds2/Manager.kt | 2 +- .../org/jacodb/analysis/ifds2/Runner.kt | 6 ++-- .../jacodb/analysis/ifds2/taint/BidiRunner.kt | 4 +-- .../analysis/ifds2/taint/TaintManager.kt | 33 +++++++++++++------ .../jacodb/analysis/ifds2/taint/globals.kt | 2 +- .../analysis/ifds2/taint/npe/NpeManager.kt | 7 ++-- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt index bf876b390..8fa7b670a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt @@ -22,7 +22,7 @@ import org.jacodb.api.JcMethod interface Manager { fun handleEvent(event: Event) - suspend fun handleControlEvent(event: ControlEvent) + fun handleControlEvent(event: ControlEvent) fun subscribeOnSummaryEdges( method: JcMethod, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index e4d1734de..19bc848bd 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -38,7 +38,6 @@ typealias Method = JcMethod typealias Statement = JcInst interface IRunner { - val unit: UnitType suspend fun run(startMethods: List) @@ -133,7 +132,9 @@ class Runner( } // Add edge to worklist: - // workList.send(edge) + if (workList.isEmpty) { + manager.handleControlEvent(QueueEmptinessChanged(this@Runner, false)) + } workList.trySend(edge).getOrThrow() return true @@ -147,7 +148,6 @@ class Runner( val edge = workList.tryReceive().getOrElse { manager.handleControlEvent(QueueEmptinessChanged(this@Runner, true)) val edge = workList.receive() - manager.handleControlEvent(QueueEmptinessChanged(this@Runner, false)) edge } tabulationAlgorithmStep(edge, this@coroutineScope) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index 43593c6ea..72ebf5c2c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -61,7 +61,7 @@ class BidiRunner( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { forwardQueueIsEmpty = event.isEmpty @@ -94,7 +94,7 @@ class BidiRunner( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { backwardQueueIsEmpty = event.isEmpty diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index ac2c5aafd..1ab7f7eaa 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -29,8 +29,6 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.coroutines.yield -import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType @@ -72,6 +70,7 @@ class TaintManager( ): TaintRunner { check(unit !in runnerForUnit) { "Runner for $unit already exists" } + logger.debug { "Creating a new runner for $unit" } val runner = if (Globals.BIDI_RUNNER) { BidiRunner( manager = this@TaintManager, @@ -107,12 +106,24 @@ class TaintManager( return runner } + private fun getAllCallees(method: JcMethod): Set { + val result: MutableSet = hashSetOf() + for (inst in method.flowGraph().instructions) { + result += graph.callees(inst) + } + return result + } + private fun addStart(method: JcMethod) { logger.info { "Adding start method: $method" } val unit = unitResolver.resolve(method) if (unit == UnknownUnit) return - methodsForUnit.getOrPut(unit) { mutableSetOf() }.add(method) - // TODO: val isNew = (...).add(); if (isNew) { deps.forEach { addStart(it) } } + val isNew = methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) + if (isNew) { + for (dep in getAllCallees(method)) { + addStart(dep) + } + } } @OptIn(ExperimentalTime::class) @@ -166,7 +177,7 @@ class TaintManager( stopRendezvous.receive() // delay(100) // @OptIn(ExperimentalCoroutinesApi::class) - // if (runnerForUnit.values.any { !it.workList.isEmpty }) { + // if (runnerForUnit.values.any { !(it as Runner).workList.isEmpty }) { // logger.warn { "NOT all runners have empty work list" } // error("?") // } @@ -268,22 +279,24 @@ class TaintManager( val method = event.edge.method val unit = unitResolver.resolve(method) val otherRunner = runnerForUnit[unit] ?: run { - logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } - return + error("No runner for $unit") + // logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } + // return } otherRunner.submitNewEdge(event.edge) } } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { + logger.trace { "Runner ${event.runner.unit} is empty: ${event.isEmpty}" } queueIsEmpty[event.runner.unit] = event.isEmpty if (event.isEmpty) { - yield() if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { - stopRendezvous.send(Unit) + logger.debug { "All runners are empty" } + stopRendezvous.trySend(Unit).getOrNull() } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt index f8a974798..1b77ab77c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt @@ -17,6 +17,6 @@ package org.jacodb.analysis.ifds2.taint internal object Globals { - var BIDI_RUNNER = true + var BIDI_RUNNER = false var TAINTED_LOOP_BOUND_SINK = false } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 3416b9705..f68a7db7a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -29,8 +29,6 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.coroutines.yield -import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType @@ -255,14 +253,13 @@ class NpeManager( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { queueIsEmpty[event.runner.unit] = event.isEmpty if (event.isEmpty) { - yield() if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { - stopRendezvous.send(Unit) + stopRendezvous.trySend(Unit).getOrNull() } } } From 9eab66e85ae71c06f64c4b1263e53d3a030a5d2e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 11:46:32 +0300 Subject: [PATCH 012/117] Move getOverrides --- .../graph/SimplifiedJcApplicationGraph.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt index a6f6269e1..efc65b24b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/SimplifiedJcApplicationGraph.kt @@ -46,16 +46,6 @@ internal class SimplifiedJcApplicationGraph( private val cache: MutableMap> = mutableMapOf() - private fun getOverrides(method: JcMethod): List { - return if (cache.containsKey(method)) { - cache[method]!! - } else { - val res = hierarchyExtension.findOverrides(method).toList() - cache[method] = res - res - } - } - // For backward analysis we may want for method to start with "neutral" operation => // we add noop to the beginning of every method private fun getStartInst(method: JcMethod): JcNoopInst { @@ -93,6 +83,16 @@ internal class SimplifiedJcApplicationGraph( } } + private fun getOverrides(method: JcMethod): List { + return if (cache.containsKey(method)) { + cache[method]!! + } else { + val res = hierarchyExtension.findOverrides(method).toList() + cache[method] = res + res + } + } + private fun calleesUnmarked(node: JcInst): Sequence { val callees = graph.callees(node).filterNot { callee -> bannedPackagePrefixes.any { callee.enclosingClass.name.startsWith(it) } From f7f6fc8a62138a2152165f52463b40a80251cf7c Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 11:46:46 +0300 Subject: [PATCH 013/117] Add toString for units --- .../jacodb/analysis/engine/UnitResolver.kt | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt index 290a80843..6edd8b053 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt @@ -25,15 +25,31 @@ import org.jacodb.api.ext.packageName interface UnitType -data class MethodUnit(val method: JcMethod) : UnitType +data class MethodUnit(val method: JcMethod) : UnitType { + override fun toString(): String { + return "MethodUnit(${method.name})" + } +} -data class ClassUnit(val clazz: JcClassOrInterface) : UnitType +data class ClassUnit(val clazz: JcClassOrInterface) : UnitType { + override fun toString(): String { + return "ClassUnit(${clazz.simpleName})" + } +} -data class PackageUnit(val packageName: String) : UnitType +data class PackageUnit(val packageName: String) : UnitType { + override fun toString(): String { + return "PackageUnit($packageName)" + } +} -object UnknownUnit : UnitType +object UnknownUnit : UnitType { + override fun toString(): String = javaClass.simpleName +} -object SingletonUnit : UnitType +object SingletonUnit : UnitType { + override fun toString(): String = javaClass.simpleName +} /** * Sets a mapping from [JcMethod] to abstract domain [UnitType]. From 4a61f4471cb202aa79f723ba8d8a047eef63d3b9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 11:46:59 +0300 Subject: [PATCH 014/117] Fix summary flows --- .../org/jacodb/analysis/engine/SummaryStorage.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt index d255013bb..b059ab8d8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt @@ -111,18 +111,22 @@ class SummaryStorageImpl : SummaryStorage private val summaries: MutableMap> = ConcurrentHashMap() private val outFlows: MutableMap> = ConcurrentHashMap() + private fun getFlow(method: JcMethod): MutableSharedFlow { + return outFlows.getOrPut(method) { + MutableSharedFlow(replay = Int.MAX_VALUE) + } + } + override fun add(fact: T) { val isNew = summaries.computeIfAbsent(fact.method) { ConcurrentHashMap.newKeySet() }.add(fact) if (isNew) { - val flow = outFlows.computeIfAbsent(fact.method) { - MutableSharedFlow(replay = Int.MAX_VALUE) - } + val flow = getFlow(fact.method) check(flow.tryEmit(fact)) } } override fun getFacts(method: JcMethod): SharedFlow { - return outFlows[method] ?: MutableSharedFlow() + return getFlow(method) } override fun getCurrentFacts(method: JcMethod): List { From 6a8a61a0134b84d59ff66d00ef4fdb5162bd0777 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 11:58:01 +0300 Subject: [PATCH 015/117] Fix logger import --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 3 ++- .../kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 1ab7f7eaa..b84fe62c8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull +import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType @@ -218,7 +219,7 @@ class TaintManager( logger.info { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } - if (logger.isDebugEnabled()) { + if (logger.isDebugEnabled) { val statsFileName = "stats.csv" logger.debug { "Writing stats in '$statsFileName'..." } File(statsFileName).outputStream().bufferedWriter().use { writer -> diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index f68a7db7a..bf1f05147 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull +import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType From 8bf35bf74c51cc27312b367b0748d243d48d4ce6 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:19:18 +0300 Subject: [PATCH 016/117] Move entry point for taint analysis --- .../analysis/ifds2/taint/TaintManager.kt | 9 ++++++ .../org/jacodb/analysis/ifds2/taint/entry.kt | 32 ------------------- 2 files changed, 9 insertions(+), 32 deletions(-) delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index b84fe62c8..8a1659d56 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -315,3 +315,12 @@ class TaintManager( .launchIn(scope) } } + +fun runTaintAnalysis( + graph: JcApplicationGraph, + unitResolver: UnitResolver, + startMethods: List, +): List { + val manager = TaintManager(graph, unitResolver) + return manager.analyze(startMethods) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt deleted file mode 100644 index bc86a8580..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/entry.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("LiftReturnOrAssignment") - -package org.jacodb.analysis.ifds2.taint - -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph - -fun runTaintAnalysis( - graph: JcApplicationGraph, - unitResolver: UnitResolver, - startMethods: List, -): List { - val manager = TaintManager(graph, unitResolver) - return manager.analyze(startMethods) -} From 028fcf6e2e5fc5d6aa832e02caec7bfa592aa44b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:19:37 +0300 Subject: [PATCH 017/117] Delegate to basicConditionEvaluator --- .../org/jacodb/analysis/config/Condition.kt | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index df853c76f..42044d08c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -149,7 +149,7 @@ class BasicConditionEvaluator( class FactAwareConditionEvaluator( private val fact: Tainted, private val basicConditionEvaluator: BasicConditionEvaluator, -) : ConditionVisitor { +) : ConditionVisitor by basicConditionEvaluator { constructor( fact: Tainted, @@ -167,31 +167,4 @@ class FactAwareConditionEvaluator( return false } - override fun visit(condition: And): Boolean { - return condition.args.all { it.accept(this) } - } - - override fun visit(condition: Or): Boolean { - return condition.args.any { it.accept(this) } - } - - override fun visit(condition: Not): Boolean { - return !condition.arg.accept(this) - } - - override fun visit(condition: ConstantTrue): Boolean { - return true - } - - override fun visit(condition: IsConstant): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: IsType): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: AnnotationType): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantEq): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantLt): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantGt): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantMatches): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: SourceFunctionMatches): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: TypeMatches): Boolean = basicConditionEvaluator.visit(condition) - - override fun visit(condition: Condition): Boolean = basicConditionEvaluator.visit(condition) } From f75a0c35a9c7f727be04d2c16f9db49fd77b3e73 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:24:07 +0300 Subject: [PATCH 018/117] Rename Runner --- .../kotlin/org/jacodb/analysis/ifds2/Manager.kt | 2 +- .../analysis/ifds2/{Runner.kt => RunnerImpl.kt} | 16 ++++++++-------- .../jacodb/analysis/ifds2/taint/BidiRunner.kt | 4 ++-- .../jacodb/analysis/ifds2/taint/TaintManager.kt | 8 ++++---- .../analysis/ifds2/taint/npe/NpeManager.kt | 4 ++-- .../org/jacodb/analysis/ifds2/taint/types.kt | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/{Runner.kt => RunnerImpl.kt} (97%) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt index 8fa7b670a..649f19181 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt @@ -34,6 +34,6 @@ interface Manager { sealed interface ControlEvent data class QueueEmptinessChanged( - val runner: IRunner<*>, + val runner: Runner<*>, val isEmpty: Boolean, ) : ControlEvent diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt similarity index 97% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt index 19bc848bd..5fa50c749 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt @@ -37,7 +37,7 @@ private val logger = KotlinLogging.logger {} typealias Method = JcMethod typealias Statement = JcInst -interface IRunner { +interface Runner { val unit: UnitType suspend fun run(startMethods: List) @@ -45,21 +45,21 @@ interface IRunner { } @Suppress("RecursivePropertyAccessor") -val IRunner<*>.pathEdges: Set> +val Runner<*>.pathEdges: Set> get() = when (this) { - is Runner<*, *> -> pathEdges + is RunnerImpl<*, *> -> pathEdges is BidiRunner -> forwardRunner.pathEdges + backwardRunner.pathEdges else -> error("Cannot extract pathEdges for $this") } // TODO: make all fields private again -class Runner( +class RunnerImpl( internal val graph: JcApplicationGraph, internal val analyzer: Analyzer, internal val manager: Manager, internal val unitResolver: UnitResolver, override val unit: UnitType, -) : IRunner { +) : Runner { internal val flowSpace: FlowFunctions = analyzer.flowFunctions @@ -75,7 +75,7 @@ class Runner( hashMapOf() private val Edge.reasons: List - get() = this@Runner.reasons[this]!!.toList() + get() = this@RunnerImpl.reasons[this]!!.toList() override suspend fun run(startMethods: List) { for (method in startMethods) { @@ -133,7 +133,7 @@ class Runner( // Add edge to worklist: if (workList.isEmpty) { - manager.handleControlEvent(QueueEmptinessChanged(this@Runner, false)) + manager.handleControlEvent(QueueEmptinessChanged(this@RunnerImpl, false)) } workList.trySend(edge).getOrThrow() @@ -146,7 +146,7 @@ class Runner( private suspend fun tabulationAlgorithm() = coroutineScope { while (isActive) { val edge = workList.tryReceive().getOrElse { - manager.handleControlEvent(QueueEmptinessChanged(this@Runner, true)) + manager.handleControlEvent(QueueEmptinessChanged(this@RunnerImpl, true)) val edge = workList.receive() edge } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index 72ebf5c2c..fcb131317 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -24,7 +24,7 @@ import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Edge -import org.jacodb.analysis.ifds2.IRunner +import org.jacodb.analysis.ifds2.Runner import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged import org.jacodb.api.JcMethod @@ -35,7 +35,7 @@ class BidiRunner( override val unit: UnitType, newForwardRunner: (Manager) -> TaintRunner, newBackwardRunner: (Manager) -> TaintRunner, -) : IRunner { +) : Runner { @Volatile private var forwardQueueIsEmpty: Boolean = false diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 8a1659d56..afae056dd 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -38,7 +38,7 @@ import org.jacodb.analysis.graph.reversed import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.Runner +import org.jacodb.analysis.ifds2.RunnerImpl import org.jacodb.analysis.ifds2.pathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph @@ -79,7 +79,7 @@ class TaintManager( unit = unit, { manager -> val analyzer = TaintAnalyzer(graph) - Runner( + RunnerImpl( graph = graph, analyzer = analyzer, manager = manager, @@ -89,7 +89,7 @@ class TaintManager( }, { manager -> val analyzer = BackwardTaintAnalyzer(graph) - Runner( + RunnerImpl( graph = graph.reversed, analyzer = analyzer, manager = manager, @@ -100,7 +100,7 @@ class TaintManager( ) } else { val analyzer = TaintAnalyzer(graph) - Runner(graph, analyzer, this@TaintManager, unitResolver, unit) + RunnerImpl(graph, analyzer, this@TaintManager, unitResolver, unit) } runnerForUnit[unit] = runner diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index bf1f05147..bfe2dcf53 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -36,7 +36,7 @@ import org.jacodb.analysis.engine.UnitType import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.Runner +import org.jacodb.analysis.ifds2.RunnerImpl import org.jacodb.analysis.ifds2.pathEdges import org.jacodb.analysis.ifds2.taint.EdgeForOtherRunner import org.jacodb.analysis.ifds2.taint.NewSummaryEdge @@ -80,7 +80,7 @@ class NpeManager( check(unit !in runnerForUnit) { "Runner for $unit already exists" } val analyzer = NpeAnalyzer(graph) - val runner = Runner(graph, analyzer, this@NpeManager, unitResolver, unit) + val runner = RunnerImpl(graph, analyzer, this@NpeManager, unitResolver, unit) runnerForUnit[unit] = runner return runner diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt index e4eed9033..1c947ece8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt @@ -17,9 +17,9 @@ package org.jacodb.analysis.ifds2.taint import org.jacodb.analysis.ifds2.Edge -import org.jacodb.analysis.ifds2.IRunner +import org.jacodb.analysis.ifds2.Runner import org.jacodb.analysis.ifds2.Vertex typealias TaintVertex = Vertex typealias TaintEdge = Edge -typealias TaintRunner = IRunner +typealias TaintRunner = Runner From 744679bad25a1d8d2443fca5f09580b088ee1455 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:27:27 +0300 Subject: [PATCH 019/117] Use `computeIfAbsert`, switch to concurrent set --- .../src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt index 5fa50c749..35f4060fd 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt @@ -114,7 +114,7 @@ class RunnerImpl( "Propagated edge must be in the same unit" } - reasons.getOrPut(edge) { mutableSetOf() }.add(reason) + reasons.computeIfAbsent(edge) { ConcurrentHashMap.newKeySet() }.add(reason) // Handle only NEW edges: if (pathEdges.add(edge)) { From 79527b93d50f474d6e3beef9b9ed5ef2a32ac699 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:31:02 +0300 Subject: [PATCH 020/117] Make all runner fields private, restore erased types --- .../org/jacodb/analysis/ifds2/RunnerImpl.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt index 35f4060fd..edce3e92e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt @@ -16,12 +16,12 @@ package org.jacodb.analysis.ifds2 -import mu.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.getOrElse import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive +import mu.KotlinLogging import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType import org.jacodb.analysis.ifds2.taint.BidiRunner @@ -52,27 +52,20 @@ val Runner<*>.pathEdges: Set> else -> error("Cannot extract pathEdges for $this") } -// TODO: make all fields private again class RunnerImpl( - internal val graph: JcApplicationGraph, - internal val analyzer: Analyzer, - internal val manager: Manager, - internal val unitResolver: UnitResolver, + private val graph: JcApplicationGraph, + private val analyzer: Analyzer, + private val manager: Manager, + private val unitResolver: UnitResolver, override val unit: UnitType, ) : Runner { - internal val flowSpace: FlowFunctions = - analyzer.flowFunctions - internal val workList: Channel> = - Channel(Channel.UNLIMITED) - internal val pathEdges: MutableSet> = - ConcurrentHashMap.newKeySet() - internal val reasons: MutableMap, MutableSet> = - ConcurrentHashMap() - internal val summaryEdges: MutableMap, MutableSet>> = - hashMapOf() - internal val callerPathEdgeOf: MutableMap, MutableSet>> = - hashMapOf() + private val flowSpace: FlowFunctions = analyzer.flowFunctions + private val workList: Channel> = Channel(Channel.UNLIMITED) + private val pathEdges = ConcurrentHashMap.newKeySet>() + private val reasons = ConcurrentHashMap, MutableSet>() + private val summaryEdges = hashMapOf, HashSet>>() + private val callerPathEdgeOf = hashMapOf, HashSet>>() private val Edge.reasons: List get() = this@RunnerImpl.reasons[this]!!.toList() From 013d0f3188413ebd72dd734eb40a1240c090049a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:33:44 +0300 Subject: [PATCH 021/117] Reuse event instances --- .../main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt index edce3e92e..f025d17f4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt @@ -67,6 +67,9 @@ class RunnerImpl( private val summaryEdges = hashMapOf, HashSet>>() private val callerPathEdgeOf = hashMapOf, HashSet>>() + private val queueIsEmpty = QueueEmptinessChanged(runner = this, isEmpty = true) + private val queueIsNotEmpty = QueueEmptinessChanged(runner = this, isEmpty = false) + private val Edge.reasons: List get() = this@RunnerImpl.reasons[this]!!.toList() @@ -126,7 +129,7 @@ class RunnerImpl( // Add edge to worklist: if (workList.isEmpty) { - manager.handleControlEvent(QueueEmptinessChanged(this@RunnerImpl, false)) + manager.handleControlEvent(queueIsNotEmpty) } workList.trySend(edge).getOrThrow() @@ -139,7 +142,7 @@ class RunnerImpl( private suspend fun tabulationAlgorithm() = coroutineScope { while (isActive) { val edge = workList.tryReceive().getOrElse { - manager.handleControlEvent(QueueEmptinessChanged(this@RunnerImpl, true)) + manager.handleControlEvent(queueIsEmpty) val edge = workList.receive() edge } From 0d0be066e32974565a7de6718651e4e40118a639 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:35:17 +0300 Subject: [PATCH 022/117] Remove comments --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index afae056dd..6d4041ffe 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -176,12 +176,6 @@ class TaintManager( val stopper = launch(Dispatchers.IO) { logger.info { "Stopper job started" } stopRendezvous.receive() - // delay(100) - // @OptIn(ExperimentalCoroutinesApi::class) - // if (runnerForUnit.values.any { !(it as Runner).workList.isEmpty }) { - // logger.warn { "NOT all runners have empty work list" } - // error("?") - // } logger.info { "Stopping all runners..." } allJobs.forEach { it.cancel() } logger.info { "Stopper job finished" } From 749d4d8fda86a2a85a6000a41841cffca521a725 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:36:32 +0300 Subject: [PATCH 023/117] Remove IDEA-specific suppresses --- .../src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt | 2 -- .../src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt | 2 -- .../org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt | 3 --- .../main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt | 2 -- .../library/analyzers/AbstractTaintBackwardFunctions.kt | 1 - .../library/analyzers/AbstractTaintForwardFunctions.kt | 3 --- 6 files changed, 13 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt index 6edd8b053..d9710b447 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("PublicApiImplicitType") - package org.jacodb.analysis.engine import org.jacodb.analysis.runAnalysis diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt index 5b41a5a9d..d941a45c8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("LiftReturnOrAssignment") - package org.jacodb.analysis.ifds2 import org.jacodb.api.JcMethod diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt index 94f923b29..9217653e9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("LiftReturnOrAssignment") - package org.jacodb.analysis.ifds2.taint.npe import mu.KotlinLogging @@ -80,7 +78,6 @@ import org.jacodb.taint.configuration.This private val logger = KotlinLogging.logger {} -@Suppress("PublicApiImplicitType") class ForwardNpeFlowFunctions( private val cp: JcClasspath, private val graph: JcApplicationGraph, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt index 833fa4a12..176fb10ba 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("LiftReturnOrAssignment") - package org.jacodb.analysis.ifds2.taint.npe import org.jacodb.analysis.engine.UnitResolver diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt index 75683f8d9..2f26eef93 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt @@ -33,7 +33,6 @@ import org.jacodb.api.cfg.JcReturnInst import org.jacodb.api.cfg.JcValue import org.jacodb.api.ext.cfg.callExpr -@Suppress("PublicApiImplicitType") abstract class AbstractTaintBackwardFunctions( protected val graph: JcApplicationGraph, protected val maxPathLength: Int, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt index a9a233ab1..fdf3676f3 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("LiftReturnOrAssignment") - package org.jacodb.analysis.library.analyzers import mu.KotlinLogging @@ -56,7 +54,6 @@ import org.jacodb.taint.configuration.TaintPassThrough private val logger = KotlinLogging.logger {} -@Suppress("PublicApiImplicitType") abstract class AbstractTaintForwardFunctions( protected val cp: JcClasspath, ) : FlowFunctionsSpace { From 789a7b19ec0af5093bd0e124b6ee10544b0c8c32 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:37:10 +0300 Subject: [PATCH 024/117] Move NPE analysis entry point --- .../analysis/ifds2/taint/npe/NpeManager.kt | 9 ++++++ .../jacodb/analysis/ifds2/taint/npe/entry.kt | 31 ------------------- 2 files changed, 9 insertions(+), 31 deletions(-) delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index bfe2dcf53..573c8a384 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -278,3 +278,12 @@ class NpeManager( .launchIn(scope) } } + +fun runNpeAnalysis( + graph: JcApplicationGraph, + unitResolver: UnitResolver, + startMethods: List, +): List { + val manager = NpeManager(graph, unitResolver) + return manager.analyze(startMethods) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt deleted file mode 100644 index 176fb10ba..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/entry.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.ifds2.taint.npe - -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.ifds2.taint.Vulnerability -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph - -fun runNpeAnalysis( - graph: JcApplicationGraph, - unitResolver: UnitResolver, - startMethods: List, -): List { - val manager = NpeManager(graph, unitResolver) - return manager.analyze(startMethods) -} From 4e43674c311bf4cffbacf4affea75dc9b4ff6081 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:38:23 +0300 Subject: [PATCH 025/117] Move timeout to argument --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 6d4041ffe..836c9be50 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -45,6 +45,7 @@ import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.taint.configuration.TaintMark import java.io.File import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime @@ -130,6 +131,7 @@ class TaintManager( @OptIn(ExperimentalTime::class) fun analyze( startMethods: List, + timeout: Duration = 3600.seconds, ): List = runBlocking(Dispatchers.Default) { val timeStart = TimeSource.Monotonic.markNow() @@ -186,7 +188,7 @@ class TaintManager( allJobs.forEach { it.start() } // Await all runners: - withTimeoutOrNull(3600.seconds) { + withTimeoutOrNull(timeout) { allJobs.joinAll() } ?: run { allJobs.forEach { it.cancel() } From 4691ab7ffae7d1be39402af95ec184c8bcaf68ff Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:47:58 +0300 Subject: [PATCH 026/117] Fix debug --- .../analysis/ifds2/taint/TaintManager.kt | 49 +++---------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 836c9be50..6fb3fcaaf 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -207,51 +207,14 @@ class TaintManager( .flatMap { method -> vulnerabilitiesStorage.getCurrentFacts(method) } - logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } - for (vulnerability in foundVulnerabilities) { - logger.debug { "$vulnerability in ${vulnerability.method}" } - } - logger.info { "Total sinks: ${foundVulnerabilities.size}" } - - logger.info { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } - if (logger.isDebugEnabled) { - val statsFileName = "stats.csv" - logger.debug { "Writing stats in '$statsFileName'..." } - File(statsFileName).outputStream().bufferedWriter().use { writer -> - val sep = ";" - writer.write(listOf("classname", "cwe", "method", "sink", "fact").joinToString(sep) + "\n") - for (vulnerability in foundVulnerabilities) { - val m = vulnerability.method - if (vulnerability.rule != null) { - for (cwe in vulnerability.rule.cwe) { - writer.write( - listOf( - m.enclosingClass.simpleName, - cwe, - m.name, - vulnerability.sink.statement, - vulnerability.sink.fact - ).joinToString(sep) { "\"$it\"" } + "\n") - } - } else if ( - vulnerability.sink.fact is Tainted - && vulnerability.sink.fact.mark == TaintMark.NULLNESS - ) { - val cwe = 476 - writer.write( - listOf( - m.enclosingClass.simpleName, - cwe, - m.name, - vulnerability.sink.statement, - vulnerability.sink.fact - ).joinToString(sep) { "\"$it\"" } + "\n") - } else { - logger.warn { "Bad vulnerability without rule: $vulnerability" } - } - } + logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } + for (vulnerability in foundVulnerabilities) { + logger.debug { "$vulnerability in ${vulnerability.method}" } } + logger.debug { "Total sinks: ${foundVulnerabilities.size}" } + + logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } } logger.info { From d305cfcc1dcf8446879077abdd0486586f94c5f0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:48:34 +0300 Subject: [PATCH 027/117] Replace with TODO --- .../ifds2/taint/npe/NpeFlowFunctions.kt | 248 +----------------- 1 file changed, 1 insertion(+), 247 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt index 9217653e9..4ee3e6ea9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt @@ -691,250 +691,4 @@ class ForwardNpeFlowFunctions( } } -// @Suppress("PublicApiImplicitType") -// class BackwardNpeFlowFunctions( -// private val project: JcClasspath, -// private val graph: JcApplicationGraph, -// ) : FlowFunctions { -// -// override fun obtainPossibleStartFacts( -// method: JcMethod, -// ): Collection { -// return listOf(Zero) -// } -// -// private fun transmitTaintBackwardAssign( -// fact: Tainted, -// from: JcValue, -// to: JcExpr, -// ): Collection { -// val fromPath = from.toPath() -// val toPath = to.toPathOrNull() -// -// if (toPath != null) { -// // TODO: think about arrays here -// // // Adhoc taint array: -// // if (fromPath.accesses.isNotEmpty() -// // && fromPath.accesses.last() is ElementAccessor -// // && fromPath.copy(accesses = fromPath.accesses.dropLast(1)) == fact.variable -// // ) { -// // val newTaint = fact.copy(variable = toPath) -// // return setOf(fact, newTaint) -// // } -// -// val tail = fact.variable - fromPath -// if (tail != null) { -// // Both 'from' and 'to' are tainted now: -// val newPath = toPath / tail -// val newTaint = fact.copy(variable = newPath) -// return setOf(fact, newTaint) -// } -// -// if (fact.variable.startsWith(toPath)) { -// // 'to' was (sub-)tainted, but it is now overridden by 'from': -// return emptySet() -// } -// } -// -// // Pass-through: -// return setOf(fact) -// } -// -// private fun transmitTaintBackwardNormal( -// fact: Tainted, -// inst: JcInst, -// ): List { -// // Pass-through: -// return listOf(fact) -// } -// -// override fun obtainSequentFlowFunction( -// current: JcInst, -// next: JcInst, -// ) = FlowFunction { fact -> -// if (fact is Zero) { -// return@FlowFunction listOf(Zero) -// } -// check(fact is Tainted) -// -// if (current is JcAssignInst) { -// transmitTaintBackwardAssign(fact, from = current.lhv, to = current.rhv) -// } else { -// transmitTaintBackwardNormal(fact, current) -// } -// } -// -// private fun transmitTaint( -// fact: Tainted, -// from: JcValue, -// to: JcValue, -// ): Collection = buildSet { -// val fromPath = from.toPath() -// val toPath = to.toPath() -// -// val tail = (fact.variable - fromPath) ?: return@buildSet -// val newPath = toPath / tail -// val newTaint = fact.copy(variable = newPath) -// add(newTaint) -// } -// -// private fun transmitTaintArgumentActualToFormal( -// fact: Tainted, -// from: JcValue, // actual -// to: JcValue, // formal -// ): Collection = transmitTaint(fact, from, to) -// -// private fun transmitTaintArgumentFormalToActual( -// fact: Tainted, -// from: JcValue, // formal -// to: JcValue, // actual -// ): Collection = transmitTaint(fact, from, to) -// -// private fun transmitTaintInstanceToThis( -// fact: Tainted, -// from: JcValue, // instance -// to: JcThis, // this -// ): Collection = transmitTaint(fact, from, to) -// -// private fun transmitTaintThisToInstance( -// fact: Tainted, -// from: JcThis, // this -// to: JcValue, // instance -// ): Collection = transmitTaint(fact, from, to) -// -// private fun transmitTaintReturn( -// fact: Tainted, -// from: JcValue, -// to: JcValue, -// ): Collection = transmitTaint(fact, from, to) -// -// override fun obtainCallToReturnSiteFlowFunction( -// callStatement: JcInst, -// returnSite: JcInst, // FIXME: unused? -// ) = FlowFunction { fact -> -// // TODO: pass-through on invokedynamic-based String concatenation -// -// if (fact == Zero) { -// return@FlowFunction listOf(Zero) -// } -// check(fact is Tainted) -// -// val callExpr = callStatement.callExpr -// ?: error("Call statement should have non-null callExpr") -// val callee = callExpr.method.method -// -// // // FIXME: adhoc for constructors: -// // if (callee.isConstructor) { -// // return@FlowFunction listOf(fact) -// // } -// -// if (callee in graph.callees(callStatement)) { -// -// if (fact.variable.isStatic) { -// return@FlowFunction emptyList() -// } -// -// for (actual in callExpr.args) { -// // Possibly tainted actual parameter: -// if (fact.variable.startsWith(actual.toPathOrNull())) { -// return@FlowFunction emptyList() // Will be handled by summary edge -// } -// } -// -// if (callExpr is JcInstanceCallExpr) { -// // Possibly tainted instance: -// if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { -// return@FlowFunction emptyList() // Will be handled by summary edge -// } -// } -// -// } -// -// if (callStatement is JcAssignInst) { -// // Possibly tainted rhv: -// if (fact.variable.startsWith(callStatement.rhv.toPathOrNull())) { -// return@FlowFunction emptyList() // Overridden by lhv -// } -// } -// -// // The "most default" behaviour is encapsulated here: -// transmitTaintBackwardNormal(fact, callStatement) -// } -// -// override fun obtainCallToStartFlowFunction( -// callStatement: JcInst, -// calleeStart: JcInst, -// ) = FlowFunction { fact -> -// val callee = calleeStart.location.method -// -// if (fact == Zero) { -// return@FlowFunction obtainPossibleStartFacts(callee) -// } -// check(fact is Tainted) -// -// val callExpr = callStatement.callExpr -// ?: error("Call statement should have non-null callExpr") -// -// buildSet { -// // Transmit facts on arguments (from 'actual' to 'formal'): -// val actualParams = callExpr.args -// val formalParams = project.getArgumentsOf(callee) -// for ((formal, actual) in formalParams.zip(actualParams)) { -// addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) -// } -// -// // Transmit facts on instance (from 'instance' to 'this'): -// if (callExpr is JcInstanceCallExpr) { -// addAll(transmitTaintInstanceToThis(fact, from = callExpr.instance, to = callee.thisInstance)) -// } -// -// // Transmit facts on static values: -// if (fact.variable.isStatic) { -// add(fact) -// } -// -// // Transmit facts on return value (from 'returnValue' to 'lhv'): -// if (calleeStart is JcReturnInst && callStatement is JcAssignInst) { -// // Note: returnValue can be null here in some weird cases, e.g. in lambda. -// calleeStart.returnValue?.let { returnValue -> -// addAll(transmitTaintReturn(fact, from = callStatement.lhv, to = returnValue)) -// } -// } -// } -// } -// -// override fun obtainExitToReturnSiteFlowFunction( -// callStatement: JcInst, -// returnSite: JcInst, -// exitStatement: JcInst, -// ) = FlowFunction { fact -> -// if (fact == Zero) { -// return@FlowFunction listOf(Zero) -// } -// check(fact is Tainted) -// -// val callExpr = callStatement.callExpr -// ?: error("Call statement should have non-null callExpr") -// val callee = exitStatement.location.method -// -// buildSet { -// // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: -// // TODO: "if passed by-ref" part is not implemented here yet -// val actualParams = callExpr.args -// val formalParams = project.getArgumentsOf(callee) -// for ((formal, actual) in formalParams.zip(actualParams)) { -// addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) -// } -// -// // Transmit facts on instance (from 'this' to 'instance'): -// if (callExpr is JcInstanceCallExpr) { -// addAll(transmitTaintThisToInstance(fact, from = callee.thisInstance, to = callExpr.instance)) -// } -// -// // Transmit facts on static values: -// if (fact.variable.isStatic) { -// add(fact) -// } -// } -// } -// } +// TODO: class BackwardNpeFlowFunctions From aae64c582a9bfb061b99dde89cf22f4be9061188 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:49:37 +0300 Subject: [PATCH 028/117] Remove `isSkipped` --- .../jacodb/analysis/engine/AnalyzerFactory.kt | 2 -- .../org/jacodb/analysis/ifds2/Analyzer.kt | 2 -- .../org/jacodb/analysis/ifds2/RunnerImpl.kt | 6 ------ .../library/analyzers/TaintAnalyzer.kt | 21 ------------------- 4 files changed, 31 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt index 68d98c937..e7bcc0742 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt @@ -86,8 +86,6 @@ interface FlowFunctionsSpace { interface Analyzer { val flowFunctions: FlowFunctionsSpace - fun isSkipped(method: JcMethod): Boolean = false - /** * This method is called by [BaseIfdsUnitRunner] each time a new path edge is found. * diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt index b10228505..f2b197b00 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt @@ -21,8 +21,6 @@ import org.jacodb.api.JcMethod interface Analyzer { val flowFunctions: FlowFunctions - fun isSkipped(method: JcMethod): Boolean = false - fun handleNewEdge( edge: Edge, ): List diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt index f025d17f4..700d602b4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt @@ -180,12 +180,6 @@ class RunnerImpl( // Propagate through the call: for (callee in currentCallees) { - // TODO: check whether we need to analyze the callee (or it was skipped due to MethodSource) - if (analyzer.isSkipped(callee)) { - logger.info { "Skipping method $callee" } - continue - } - for (calleeStart in graph.entryPoints(callee)) { val factsAtCalleeStart = flowSpace .obtainCallToStartFlowFunction(current, calleeStart) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt index fb0ab2ea9..01f4d826a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt @@ -120,27 +120,6 @@ abstract class TaintAnalyzer( override val isMainAnalyzer: Boolean get() = true - // private val skipped: MutableMap = mutableMapOf() - // override fun isSkipped(method: JcMethod): Boolean { - // return skipped.getOrPut(method) { - // // TODO: read the config and assign True if there is a MethodSource item, and False otherwise. - // // Note: the computed value is cached. - // - // fun magic(): T = TODO() - // val current: JcInst = magic() - // val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(current)) - // - // // FIXME: we need the call itself in order to evaluate the condition - // for (item in config.items) { - // if (item is TaintMethodSource) { - // item.condition.accept(conditionEvaluator) - // } - // } - // - // TODO() - // } - // } - protected abstract fun generateDescriptionForSink(sink: IfdsVertex): VulnerabilityDescription override fun handleNewEdge(edge: IfdsEdge): List = buildList { From f41c335f7b2f353671e6757e4f50aba6677f320b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:50:26 +0300 Subject: [PATCH 029/117] Remove old unused code --- .../kotlin/org/jacodb/analysis/paths/Util.kt | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt index f0bf44e7f..cfe71d6ea 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt @@ -138,27 +138,3 @@ fun AccessPath?.isDereferencedAt(inst: JcInst): Boolean { return inst.operands.any { isDereferencedAt(it) } } - -internal fun JcValue.isTaintedWith(fact: JcValue): Boolean { - if (this == fact) { - return true - } - - return when (this) { - is JcFieldRef -> { - if (this.instance != null) { - this.instance!!.isTaintedWith(fact) - } else { - // static field - // TODO: ? - false - } - } - - is JcArrayAccess -> { - this.array.isTaintedWith(fact) - } - - else -> false - } -} From 83f0118b2543a8b66ed6f7f9fe60f73851bf0fcf Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:51:20 +0300 Subject: [PATCH 030/117] Replace with logger --- .../test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index 9abc8303b..472475782 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -16,9 +16,9 @@ package org.jacodb.analysis.impl -import mu.KotlinLogging import juliet.support.AbstractTestCase import kotlinx.coroutines.runBlocking +import mu.KotlinLogging import org.jacodb.analysis.engine.VulnerabilityInstance import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod @@ -118,7 +118,7 @@ abstract class BaseAnalysisTest : BaseTest() { } protected fun testSingleJulietClass(vulnerabilityType: String, className: String) { - println(className) + logger.info { className } val clazz = cp.findClass(className) val badMethod = clazz.methods.single { it.name == "bad" } From f0c7c835a75ea6bd7c980d9ab3f4256b4a6bfd4a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:52:32 +0300 Subject: [PATCH 031/117] Remove --- .../org/jacodb/analysis/impl/BenchRunners.kt | 315 ------------------ 1 file changed, 315 deletions(-) delete mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt deleted file mode 100644 index 10f338fdf..000000000 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BenchRunners.kt +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:OptIn(ExperimentalTime::class, ExperimentalTime::class) - -package org.jacodb.analysis.impl - -import mu.KotlinLogging -import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.engine.PackageUnitResolver -import org.jacodb.analysis.engine.SingletonUnit -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.UnknownUnit -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.ifds2.taint.runTaintAnalysis -import org.jacodb.api.JcByteCodeLocation -import org.jacodb.api.JcClassOrInterface -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcDatabase -import org.jacodb.api.JcMethod -import org.jacodb.api.ext.findClass -import org.jacodb.api.ext.packageName -import org.jacodb.impl.features.InMemoryHierarchy -import org.jacodb.impl.features.Usages -import org.jacodb.impl.features.classpaths.JcUnknownClass -import org.jacodb.impl.features.classpaths.UnknownClasses -import org.jacodb.impl.jacodb -import org.jacodb.taint.configuration.TaintConfigurationFeature -import java.io.File -import java.nio.file.Path -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.Path -import kotlin.io.path.PathWalkOption -import kotlin.io.path.div -import kotlin.io.path.extension -import kotlin.io.path.walk -import kotlin.time.DurationUnit -import kotlin.time.ExperimentalTime -import kotlin.time.TimeSource.Monotonic.markNow - -private val logger = KotlinLogging.logger {} - -object WebGoatBenchRunner { - private fun loadWebGoatBench(): BenchCp { - val webGoatDir = Path(object {}.javaClass.getResource("/webgoat")!!.path) - return loadWebAppBenchCp( - classes = webGoatDir / "classes", - dependencies = webGoatDir / "deps", - unitResolver = PackageUnitResolver - ).apply { - entrypointFilter = { method -> - if (method.enclosingClass.packageName.startsWith("org.owasp.webgoat.lessons")) { - true - } else { - false - } - } - } - } - - @JvmStatic - fun main(args: Array) { - val timeStart = markNow() - val bench = loadWebGoatBench() - bench.use { it.analyze() } - logger.info { "All done in %.1fs".format(timeStart.elapsedNow().toDouble(DurationUnit.SECONDS)) } - } -} - -object OwaspBenchRunner { - private fun loadOwaspJavaBench(): BenchCp { - val owaspJavaPath = Path(object {}.javaClass.getResource("/owasp")!!.path) - val runAll = true // make it 'false' to test on specific method(s) only - - return loadWebAppBenchCp( - classes = owaspJavaPath / "classes", - dependencies = owaspJavaPath / "deps", - // unitResolver = PackageUnitResolver - // unitResolver = SingletonUnitResolver - unitResolver = { method -> - if (method.enclosingClass.packageName.startsWith("org.owasp.benchmark.")) { - // ClassUnit(generateSequence(method.enclosingClass) { it.outerClass }.last()) - SingletonUnit - } else { - UnknownUnit - } - - // if (method.enclosingClass.packageName in bannedPackages) { - // UnknownUnit - // } else { - // SingletonUnit - // } - } - ).apply { - entrypointFilter = { method -> - if (method.enclosingClass.packageName.startsWith("org.owasp.benchmark.testcode")) { - if (runAll) { - // All methods: - true - } else { - // Specific method: - val specificMethod = "BenchmarkTest00008" - // val specificMethod = "BenchmarkTest00018" - // val specificMethod = "BenchmarkTest00024" - // val specificMethod = "BenchmarkTest00025" - // val specificMethod = "BenchmarkTest00026" - // val specificMethod = "BenchmarkTest00027" - // val specificMethod = "BenchmarkTest00032" - // val specificMethod = "BenchmarkTest00033" - // val specificMethod = "BenchmarkTest00034" - // val specificMethod = "BenchmarkTest00037" - // val specificMethod = "BenchmarkTest00043" - // val specificMethod = "BenchmarkTest00105" - if (method.enclosingClass.simpleName == specificMethod) { - true - } else { - false - } - - // // Methods with specific annotation: - // // println("Annotations of $method: ${method.enclosingClass.annotations.map{it.name}}") - // method.enclosingClass.annotations.any { annotation -> - // if (annotation.name == "javax.servlet.annotation.WebServlet") { - // // println("$method has annotation ${annotation.name} with values ${annotation.values}") - // (annotation.values["value"]!! as List)[0].startsWith("/sqli-") - // } else { - // false - // } - // } - } - } else { - false - } - } - } - } - - @JvmStatic - fun main(args: Array) { - val timeStart = markNow() - val bench = loadOwaspJavaBench() - bench.use { it.analyze() } - logger.info { "All done in %.1fs".format(timeStart.elapsedNow().toDouble(DurationUnit.SECONDS)) } - } -} - -object ShopizerBenchRunner { - private fun loadShopizerBench(): BenchCp { - val shopizerPath = Path(object {}.javaClass.getResource("/shopizer")!!.path) - return loadWebAppBenchCp( - classes = shopizerPath / "classes", - dependencies = shopizerPath / "deps", - unitResolver = PackageUnitResolver - ).apply { - entrypointFilter = { true } - } - } - - @JvmStatic - fun main(args: Array) { - val timeStart = markNow() - val bench = loadShopizerBench() - bench.use { it.analyze() } - logger.info { "All done in %.1fs".format(timeStart.elapsedNow().toDouble(DurationUnit.SECONDS)) } - } -} - -private class BenchCp( - val cp: JcClasspath, - val db: JcDatabase, - val benchLocations: List, - val unitResolver: UnitResolver, - var reportFileName: String? = null, - var entrypointFilter: (JcMethod) -> Boolean = { true }, -) : AutoCloseable { - override fun close() { - cp.close() - db.close() - } -} - -private fun loadBenchCp( - classes: List, - dependencies: List, - unitResolver: UnitResolver, -): BenchCp = runBlocking { - val cpFiles = classes + dependencies - - val db = jacodb { - useProcessJavaRuntime() - installFeatures(InMemoryHierarchy, Usages) - loadByteCode(cpFiles) - persistent("/tmp/index.db") - } - db.awaitBackgroundJobs() - - // val taintConfigFileName = "defaultTaintConfig.json" - val taintConfigFileName = "config_big.json" - val defaultConfigResource = this.javaClass.getResourceAsStream("/$taintConfigFileName")!! - var configJson = defaultConfigResource.bufferedReader().readText() - val additionalConfigResource = this.javaClass.getResourceAsStream("/additional.json") - if (additionalConfigResource != null) { - val additionalConfigJson = additionalConfigResource.bufferedReader().readText() - val configJsonLines = configJson.lines().toMutableList() - if (configJsonLines.last().isEmpty()) { - configJsonLines.removeLast() - } - check(configJsonLines.last() == "]") - val additionalConfigJsonLines = additionalConfigJson.lines().toMutableList() - if (additionalConfigJsonLines.last().isEmpty()) { - additionalConfigJsonLines.removeLast() - } - check(additionalConfigJsonLines.first() == "[") - check(additionalConfigJsonLines.last() == "]") - if (additionalConfigJsonLines.size > 2) { - configJsonLines.removeLast() - configJsonLines[configJsonLines.size - 1] = configJsonLines[configJsonLines.size - 1] + "," - for (line in additionalConfigJsonLines.subList(1, additionalConfigJsonLines.size - 1)) { - configJsonLines.add(line) - } - configJsonLines.add("]") - } - configJson = configJsonLines.joinToString("\n") - } - val configurationFeature = TaintConfigurationFeature.fromJson(configJson) - val features = listOf(configurationFeature, UnknownClasses) - val cp = db.classpath(cpFiles, features) - val locations = cp.locations.filter { it.jarOrFolder in classes } - - BenchCp(cp, db, locations, unitResolver) -} - -@OptIn(ExperimentalPathApi::class) -private fun loadWebAppBenchCp( - classes: Path, - dependencies: Path, - unitResolver: UnitResolver, -): BenchCp = - loadBenchCp( - classes = listOf(classes.toFile()), - dependencies = dependencies - .walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { it.extension == "jar" } - .map { it.toFile() } - .toList(), - unitResolver = unitResolver - ) - -private fun BenchCp.analyze() { - val useSpecificClass = false - val startMethods = if (useSpecificClass) { - val className = "org.owasp.benchmark.testcode.BenchmarkTest00043" - logger.info { "Analyzing '$className'" } - val clazz = cp.findClass(className) - clazz.publicAndProtectedMethods().toList() - } else { - logger.info { "Filtering start methods..." } - cp.publicClasses(benchLocations) - .flatMap { it.publicAndProtectedMethods() } - .filter(entrypointFilter) - .toList() - } - - logger.info { "Start the analysis with ${startMethods.size} start methods:" } - for (method in startMethods) { - logger.info { method } - } - analyzeTaint(cp, unitResolver, startMethods, reportFileName) - logger.info { "Done the analysis with ${startMethods.size} start methods" } -} - -private fun analyzeTaint( - cp: JcClasspath, - unitResolver: UnitResolver, - startMethods: List, - reportFileName: String? = null, -) { - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - runTaintAnalysis(graph, unitResolver, startMethods) - // val vulnerabilities = runAnalysis(graph, unitResolver, newSqlInjectionRunnerFactory(), startMethods) - // val report = SarifReport.fromVulnerabilities(vulnerabilities) - // File(reportFileName ?: "report.sarif").outputStream().use { fileOutputStream -> - // report.encodeToStream(fileOutputStream) - // } -} - -private fun JcClasspath.publicClasses(locations: List): Sequence = - locations - .asSequence() - .flatMap { it.classNames ?: emptySet() } - .mapNotNull { findClassOrNull(it) } - .filterNot { it is JcUnknownClass } - .filterNot { it.isAbstract || it.isInterface || it.isAnonymous } - -private fun JcClassOrInterface.publicAndProtectedMethods(): Sequence = - declaredMethods.asSequence() - .filter { it.instList.size > 0 } - // TODO: some sinks are NOT found when filtering out constructors here - // .filter { !it.isConstructor } - .filter { it.isPublic || it.isProtected } From eef22f1ffb0b3d6a45c0ce96f7aa802f38c1c689 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:57:00 +0300 Subject: [PATCH 032/117] Remove --- .../analysis/ifds2/taint/TaintAnalyzers.kt | 21 ------------------- .../jacodb/analysis/ifds2/taint/globals.kt | 1 - 2 files changed, 22 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 0f4bdc33e..509d79881 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -114,27 +114,6 @@ class TaintAnalyzer( add(NewVulnerability(vulnerability)) // verticesWithTraceGraphNeeded.add(edge.to) } - - if (Globals.TAINTED_LOOP_BOUND_SINK) { - val statement = edge.to.statement - val fact = edge.to.fact - if (statement is JcIfInst && fact is Tainted) { - val loopHeads = loopsCache.getOrPut(statement.location.method) { - statement.location.method.flowGraph().loops.map { it.head } - } - if (statement in loopHeads) { - for (s in statement.condition.operands) { - val p = s.toPath() - if (p == fact.variable) { - val message = "Tainted loop operand" - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) - logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } - add(NewVulnerability(vulnerability)) - } - } - } - } - } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt index 1b77ab77c..f117fd7cc 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt @@ -18,5 +18,4 @@ package org.jacodb.analysis.ifds2.taint internal object Globals { var BIDI_RUNNER = false - var TAINTED_LOOP_BOUND_SINK = false } From d8f1d3500e6deb59f14be2150602bb3f93c364bf Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 12:59:27 +0300 Subject: [PATCH 033/117] Remove --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 509d79881..ff20e5a28 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -53,8 +53,6 @@ class TaintAnalyzer( return false } - private val loopsCache: MutableMap> = hashMapOf() - override fun handleNewEdge( edge: TaintEdge, ): List = buildList { From 08c81d1a6657193c0b56de9a7ef584f406c94a52 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:00:32 +0300 Subject: [PATCH 034/117] Cleanup --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index ff20e5a28..9199dddce 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -93,24 +93,20 @@ class TaintAnalyzer( // break } if (triggeredItem != null) { - // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } val message = triggeredItem.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) - // verticesWithTraceGraphNeeded.add(edge.to) } } if (defaultBehavior) { // Default ("config"-less) behavior: if (isSink(edge.to.statement, edge.to.fact)) { - // logger.info { "Found sink at ${edge.to} in ${edge.method}" } val message = "SINK" // TODO val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) - // verticesWithTraceGraphNeeded.add(edge.to) } } } From f3bad35c66945ebcb9331e5e846d65ec64649da5 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:03:18 +0300 Subject: [PATCH 035/117] Remove --- jacodb-analysis/.gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 jacodb-analysis/.gitignore diff --git a/jacodb-analysis/.gitignore b/jacodb-analysis/.gitignore deleted file mode 100644 index 4c8b08b9b..000000000 --- a/jacodb-analysis/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -webgoat/ -owasp/ -shopizer/ From 88d35e9b6fdab2b8e41e488aa24fad9a8fb6c45e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:15:26 +0300 Subject: [PATCH 036/117] Rename UniRunner --- .../jacodb/analysis/ifds2/{RunnerImpl.kt => Runner.kt} | 6 +++--- .../org/jacodb/analysis/ifds2/taint/TaintManager.kt | 10 ++++------ .../org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 4 ++-- 3 files changed, 9 insertions(+), 11 deletions(-) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/{RunnerImpl.kt => Runner.kt} (98%) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt similarity index 98% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index 700d602b4..074ffc9e2 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/RunnerImpl.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -47,12 +47,12 @@ interface Runner { @Suppress("RecursivePropertyAccessor") val Runner<*>.pathEdges: Set> get() = when (this) { - is RunnerImpl<*, *> -> pathEdges + is UniRunner<*, *> -> pathEdges is BidiRunner -> forwardRunner.pathEdges + backwardRunner.pathEdges else -> error("Cannot extract pathEdges for $this") } -class RunnerImpl( +class UniRunner( private val graph: JcApplicationGraph, private val analyzer: Analyzer, private val manager: Manager, @@ -71,7 +71,7 @@ class RunnerImpl( private val queueIsNotEmpty = QueueEmptinessChanged(runner = this, isEmpty = false) private val Edge.reasons: List - get() = this@RunnerImpl.reasons[this]!!.toList() + get() = this@UniRunner.reasons[this]!!.toList() override suspend fun run(startMethods: List) { for (method in startMethods) { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 6fb3fcaaf..583964a8f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -38,12 +38,10 @@ import org.jacodb.analysis.graph.reversed import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.RunnerImpl +import org.jacodb.analysis.ifds2.UniRunner import org.jacodb.analysis.ifds2.pathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.taint.configuration.TaintMark -import java.io.File import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -80,7 +78,7 @@ class TaintManager( unit = unit, { manager -> val analyzer = TaintAnalyzer(graph) - RunnerImpl( + UniRunner( graph = graph, analyzer = analyzer, manager = manager, @@ -90,7 +88,7 @@ class TaintManager( }, { manager -> val analyzer = BackwardTaintAnalyzer(graph) - RunnerImpl( + UniRunner( graph = graph.reversed, analyzer = analyzer, manager = manager, @@ -101,7 +99,7 @@ class TaintManager( ) } else { val analyzer = TaintAnalyzer(graph) - RunnerImpl(graph, analyzer, this@TaintManager, unitResolver, unit) + UniRunner(graph, analyzer, this@TaintManager, unitResolver, unit) } runnerForUnit[unit] = runner diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 573c8a384..8f06220f8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -36,7 +36,7 @@ import org.jacodb.analysis.engine.UnitType import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.RunnerImpl +import org.jacodb.analysis.ifds2.UniRunner import org.jacodb.analysis.ifds2.pathEdges import org.jacodb.analysis.ifds2.taint.EdgeForOtherRunner import org.jacodb.analysis.ifds2.taint.NewSummaryEdge @@ -80,7 +80,7 @@ class NpeManager( check(unit !in runnerForUnit) { "Runner for $unit already exists" } val analyzer = NpeAnalyzer(graph) - val runner = RunnerImpl(graph, analyzer, this@NpeManager, unitResolver, unit) + val runner = UniRunner(graph, analyzer, this@NpeManager, unitResolver, unit) runnerForUnit[unit] = runner return runner From 6045bebbeab6e74660866072552639c36c853a43 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:22:59 +0300 Subject: [PATCH 037/117] Make pathEdges internal for extension function --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index 074ffc9e2..98dcc0522 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -62,7 +62,7 @@ class UniRunner( private val flowSpace: FlowFunctions = analyzer.flowFunctions private val workList: Channel> = Channel(Channel.UNLIMITED) - private val pathEdges = ConcurrentHashMap.newKeySet>() + internal val pathEdges: MutableSet> = ConcurrentHashMap.newKeySet() private val reasons = ConcurrentHashMap, MutableSet>() private val summaryEdges = hashMapOf, HashSet>>() private val callerPathEdgeOf = hashMapOf, HashSet>>() From 0fefbb9de3264f41b29163382d4ca58c89a2ba38 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:23:15 +0300 Subject: [PATCH 038/117] Remove --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index 98dcc0522..30defaf2c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -70,9 +70,6 @@ class UniRunner( private val queueIsEmpty = QueueEmptinessChanged(runner = this, isEmpty = true) private val queueIsNotEmpty = QueueEmptinessChanged(runner = this, isEmpty = false) - private val Edge.reasons: List - get() = this@UniRunner.reasons[this]!!.toList() - override suspend fun run(startMethods: List) { for (method in startMethods) { addStart(method) From ef8f0f5cbf3448258a50799cf6e59eb495654c36 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:25:41 +0300 Subject: [PATCH 039/117] Revert "Delegate to basicConditionEvaluator" This reverts commit 028fcf6e2e5fc5d6aa832e02caec7bfa592aa44b. --- .../org/jacodb/analysis/config/Condition.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index 42044d08c..df853c76f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -149,7 +149,7 @@ class BasicConditionEvaluator( class FactAwareConditionEvaluator( private val fact: Tainted, private val basicConditionEvaluator: BasicConditionEvaluator, -) : ConditionVisitor by basicConditionEvaluator { +) : ConditionVisitor { constructor( fact: Tainted, @@ -167,4 +167,31 @@ class FactAwareConditionEvaluator( return false } + override fun visit(condition: And): Boolean { + return condition.args.all { it.accept(this) } + } + + override fun visit(condition: Or): Boolean { + return condition.args.any { it.accept(this) } + } + + override fun visit(condition: Not): Boolean { + return !condition.arg.accept(this) + } + + override fun visit(condition: ConstantTrue): Boolean { + return true + } + + override fun visit(condition: IsConstant): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: IsType): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: AnnotationType): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantEq): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantLt): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantGt): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: ConstantMatches): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: SourceFunctionMatches): Boolean = basicConditionEvaluator.visit(condition) + override fun visit(condition: TypeMatches): Boolean = basicConditionEvaluator.visit(condition) + + override fun visit(condition: Condition): Boolean = basicConditionEvaluator.visit(condition) } From 1d393a272d4532cba16b325511310f685f2d759a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:31:53 +0300 Subject: [PATCH 040/117] Remove --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt | 2 -- .../src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt | 3 --- 2 files changed, 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt index 10de7dacb..091a96976 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt @@ -39,8 +39,6 @@ data class Edge( } } -fun Edge.toIfds(): IfdsEdge = IfdsEdge(from.toIfds(), to.toIfds()) - sealed class Reason { object Initial : Reason() diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt index 78b58071a..d96dcfb74 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt @@ -37,6 +37,3 @@ data class Vertex( } } } - -fun Vertex.toIfds(): IfdsVertex = - IfdsVertex(statement, fact.toDomainFact()) From 6cd9c63373bd286d9b28dd7b4f5566e9c037ff69 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 13:35:09 +0300 Subject: [PATCH 041/117] Replace globals with manager constructor argument --- .../analysis/ifds2/taint/TaintManager.kt | 3 ++- .../jacodb/analysis/ifds2/taint/globals.kt | 21 ------------------- 2 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 583964a8f..36c8966de 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -54,6 +54,7 @@ private val logger = KotlinLogging.logger {} class TaintManager( private val graph: JcApplicationGraph, private val unitResolver: UnitResolver, + private val useBidiRunner: Boolean = false, ) : Manager { private val methodsForUnit: MutableMap> = hashMapOf() @@ -71,7 +72,7 @@ class TaintManager( check(unit !in runnerForUnit) { "Runner for $unit already exists" } logger.debug { "Creating a new runner for $unit" } - val runner = if (Globals.BIDI_RUNNER) { + val runner = if (useBidiRunner) { BidiRunner( manager = this@TaintManager, unitResolver = unitResolver, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt deleted file mode 100644 index f117fd7cc..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/globals.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.ifds2.taint - -internal object Globals { - var BIDI_RUNNER = false -} From 159866beda771dbf122b1a69b7ddf592009d3e61 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 14:10:44 +0300 Subject: [PATCH 042/117] Move queueIsNotEmpty --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index 30defaf2c..52bf12320 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -125,9 +125,6 @@ class UniRunner( } // Add edge to worklist: - if (workList.isEmpty) { - manager.handleControlEvent(queueIsNotEmpty) - } workList.trySend(edge).getOrThrow() return true @@ -141,6 +138,7 @@ class UniRunner( val edge = workList.tryReceive().getOrElse { manager.handleControlEvent(queueIsEmpty) val edge = workList.receive() + manager.handleControlEvent(queueIsNotEmpty) edge } tabulationAlgorithmStep(edge, this@coroutineScope) From 05e4f20e38c69566a0bae719ab1a980af7cd05e0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:17:10 +0300 Subject: [PATCH 043/117] Format --- .../main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index fcb131317..4c9c8246f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -24,9 +24,9 @@ import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Edge -import org.jacodb.analysis.ifds2.Runner import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged +import org.jacodb.analysis.ifds2.Runner import org.jacodb.api.JcMethod class BidiRunner( From 1e7529bf95603938e260e0241435c19cd691fd90 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:18:20 +0300 Subject: [PATCH 044/117] Use logger --- .../src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt index dc327dae9..e5cc27b48 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt @@ -16,8 +16,8 @@ package org.jacodb.analysis.impl -import mu.KotlinLogging import kotlinx.coroutines.runBlocking +import mu.KotlinLogging import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds2.taint.Vulnerability @@ -101,7 +101,7 @@ class Ifds2SqlTest : BaseTest() { } private fun testSingleJulietClass(className: String) { - println(className) + logger.info { className } val clazz = cp.findClass(className) val badMethod = clazz.methods.single { it.name == "bad" } From 4bd389a7eb7017ebefdaa2e127e4da143a9fb782 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:21:39 +0300 Subject: [PATCH 045/117] Remove types --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 36c8966de..d951f1393 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -57,9 +57,9 @@ class TaintManager( private val useBidiRunner: Boolean = false, ) : Manager { - private val methodsForUnit: MutableMap> = hashMapOf() - private val runnerForUnit: MutableMap = hashMapOf() - private val queueIsEmpty: MutableMap = ConcurrentHashMap() + private val methodsForUnit = hashMapOf>() + private val runnerForUnit= hashMapOf() + private val queueIsEmpty = ConcurrentHashMap() private val summaryEdgesStorage = SummaryStorageImpl() private val vulnerabilitiesStorage = SummaryStorageImpl() From b63adb8e37ee6b007a2fb5a88d5e17d0a201f647 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:21:49 +0300 Subject: [PATCH 046/117] Do not add deps --- .../org/jacodb/analysis/ifds2/taint/TaintManager.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index d951f1393..ccb94e14a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -120,11 +120,11 @@ class TaintManager( val unit = unitResolver.resolve(method) if (unit == UnknownUnit) return val isNew = methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) - if (isNew) { - for (dep in getAllCallees(method)) { - addStart(dep) - } - } + // if (isNew) { + // for (dep in getAllCallees(method)) { + // addStart(dep) + // } + // } } @OptIn(ExperimentalTime::class) From 67cf2dee7b007619f9f0e6a942adaea5d5f6c954 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:23:41 +0300 Subject: [PATCH 047/117] Ignore events for non-existing runners --- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index ccb94e14a..781ae5331 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -238,9 +238,9 @@ class TaintManager( val method = event.edge.method val unit = unitResolver.resolve(method) val otherRunner = runnerForUnit[unit] ?: run { - error("No runner for $unit") - // logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } - // return + // error("No runner for $unit") + logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } + return } otherRunner.submitNewEdge(event.edge) } From 5df901ed0e38ad031a4ece22f27db7de7d78ce90 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:35:42 +0300 Subject: [PATCH 048/117] Make handleControlEvent suspend --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt | 2 +- .../kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt | 4 ++-- .../org/jacodb/analysis/ifds2/taint/TaintManager.kt | 8 +++++--- .../org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt index 649f19181..38a97967d 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt @@ -22,7 +22,7 @@ import org.jacodb.api.JcMethod interface Manager { fun handleEvent(event: Event) - fun handleControlEvent(event: ControlEvent) + suspend fun handleControlEvent(event: ControlEvent) fun subscribeOnSummaryEdges( method: JcMethod, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index 4c9c8246f..416a8bd1c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -61,7 +61,7 @@ class BidiRunner( } } - override fun handleControlEvent(event: ControlEvent) { + override suspend fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { forwardQueueIsEmpty = event.isEmpty @@ -94,7 +94,7 @@ class BidiRunner( } } - override fun handleControlEvent(event: ControlEvent) { + override suspend fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { backwardQueueIsEmpty = event.isEmpty diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 781ae5331..a8813fc4c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.yield import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver @@ -58,7 +59,7 @@ class TaintManager( ) : Manager { private val methodsForUnit = hashMapOf>() - private val runnerForUnit= hashMapOf() + private val runnerForUnit = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() private val summaryEdgesStorage = SummaryStorageImpl() @@ -247,15 +248,16 @@ class TaintManager( } } - override fun handleControlEvent(event: ControlEvent) { + override suspend fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { logger.trace { "Runner ${event.runner.unit} is empty: ${event.isEmpty}" } queueIsEmpty[event.runner.unit] = event.isEmpty if (event.isEmpty) { + yield() if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { logger.debug { "All runners are empty" } - stopRendezvous.trySend(Unit).getOrNull() + stopRendezvous.send(Unit) } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 8f06220f8..85c8958eb 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -254,7 +254,7 @@ class NpeManager( } } - override fun handleControlEvent(event: ControlEvent) { + override suspend fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { queueIsEmpty[event.runner.unit] = event.isEmpty From e625b2fc50194e27198a3f93937e6bc23a06cb5c Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:41:34 +0300 Subject: [PATCH 049/117] Use computeIfAbsent, remove types --- .../kotlin/org/jacodb/analysis/engine/SummaryStorage.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt index b059ab8d8..31ebe2128 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt @@ -108,11 +108,11 @@ interface SummaryStorage { class SummaryStorageImpl : SummaryStorage where T : SummaryFact { - private val summaries: MutableMap> = ConcurrentHashMap() - private val outFlows: MutableMap> = ConcurrentHashMap() + private val summaries = ConcurrentHashMap>() + private val outFlows = ConcurrentHashMap>() private fun getFlow(method: JcMethod): MutableSharedFlow { - return outFlows.getOrPut(method) { + return outFlows.computeIfAbsent(method) { MutableSharedFlow(replay = Int.MAX_VALUE) } } From 668737249109f16fd3fed01682adab5b39e445c0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:46:34 +0300 Subject: [PATCH 050/117] Fix --- .../analysis/ifds2/taint/npe/NpeManager.kt | 54 ++++--------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 85c8958eb..96e5687c5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -53,6 +53,7 @@ import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.taint.configuration.TaintMark import java.io.File import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime @@ -96,6 +97,7 @@ class NpeManager( @OptIn(ExperimentalTime::class) fun analyze( startMethods: List, + timeout: Duration = 3600.seconds, ): List = runBlocking(Dispatchers.Default) { val timeStart = TimeSource.Monotonic.markNow() @@ -158,7 +160,7 @@ class NpeManager( allJobs.forEach { it.start() } // Await all runners: - withTimeoutOrNull(3600.seconds) { + withTimeoutOrNull(timeout) { allJobs.joinAll() } ?: run { allJobs.forEach { it.cancel() } @@ -177,51 +179,13 @@ class NpeManager( .flatMap { method -> vulnerabilitiesStorage.getCurrentFacts(method) } - logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } - for (vulnerability in foundVulnerabilities) { - logger.debug { "$vulnerability in ${vulnerability.method}" } - } - logger.info { "Total sinks: ${foundVulnerabilities.size}" } - - logger.info { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } - - if (logger.isDebugEnabled()) { - val statsFileName = "stats.csv" - logger.debug { "Writing stats in '$statsFileName'..." } - File(statsFileName).outputStream().bufferedWriter().use { writer -> - val sep = ";" - writer.write(listOf("classname", "cwe", "method", "sink", "fact").joinToString(sep) + "\n") - for (vulnerability in foundVulnerabilities) { - val m = vulnerability.method - if (vulnerability.rule != null) { - for (cwe in vulnerability.rule.cwe) { - writer.write( - listOf( - m.enclosingClass.simpleName, - cwe, - m.name, - vulnerability.sink.statement, - vulnerability.sink.fact - ).joinToString(sep) { "\"$it\"" } + "\n") - } - } else if ( - vulnerability.sink.fact is Tainted - && vulnerability.sink.fact.mark == TaintMark.NULLNESS - ) { - val cwe = 476 - writer.write( - listOf( - m.enclosingClass.simpleName, - cwe, - m.name, - vulnerability.sink.statement, - vulnerability.sink.fact - ).joinToString(sep) { "\"$it\"" } + "\n") - } else { - logger.warn { "Bad vulnerability without rule: $vulnerability" } - } - } + if (logger.isDebugEnabled) { + logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } + for (vulnerability in foundVulnerabilities) { + logger.debug { "$vulnerability in ${vulnerability.method}" } } + logger.debug { "Total sinks: ${foundVulnerabilities.size}" } + logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } } logger.info { From b1dd66432f36e5deda3b04da5bcc8db800ebeef9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:48:13 +0300 Subject: [PATCH 051/117] Cleanup --- .../kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 96e5687c5..d7153a6a0 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -46,12 +46,9 @@ import org.jacodb.analysis.ifds2.taint.TaintEdge import org.jacodb.analysis.ifds2.taint.TaintEvent import org.jacodb.analysis.ifds2.taint.TaintFact import org.jacodb.analysis.ifds2.taint.TaintRunner -import org.jacodb.analysis.ifds2.taint.Tainted import org.jacodb.analysis.ifds2.taint.Vulnerability import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.taint.configuration.TaintMark -import java.io.File import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds From 62ed74d2fc9ca7f4620e6666148af95fe030e297 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:49:38 +0300 Subject: [PATCH 052/117] Timeout 30s in tests --- .../test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt | 8 +++++--- .../test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt index 691d0de9a..823656ac0 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt @@ -16,13 +16,13 @@ package org.jacodb.analysis.impl -import mu.KotlinLogging import kotlinx.coroutines.runBlocking +import mu.KotlinLogging import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds2.taint.Vulnerability -import org.jacodb.analysis.ifds2.taint.npe.runNpeAnalysis +import org.jacodb.analysis.ifds2.taint.npe.NpeManager import org.jacodb.analysis.impl.BaseAnalysisTest.Companion.provideClassesForJuliet import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod @@ -45,6 +45,7 @@ import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import java.util.* import java.util.stream.Stream +import kotlin.time.Duration.Companion.seconds private val logger = KotlinLogging.logger {} @@ -289,6 +290,7 @@ class Ifds2NpeTest : BaseTest() { cp.newApplicationGraphForAnalysis() } val unitResolver = SingletonUnitResolver - return runNpeAnalysis(graph, unitResolver, methods) + val manager = NpeManager(graph, unitResolver) + return manager.analyze(methods, timeout = 30.seconds) } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt index e5cc27b48..2b8e17fe3 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt @@ -20,8 +20,8 @@ import kotlinx.coroutines.runBlocking import mu.KotlinLogging import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.newApplicationGraphForAnalysis +import org.jacodb.analysis.ifds2.taint.TaintManager import org.jacodb.analysis.ifds2.taint.Vulnerability -import org.jacodb.analysis.ifds2.taint.runTaintAnalysis import org.jacodb.analysis.impl.BaseAnalysisTest.Companion.provideClassesForJuliet import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod @@ -40,6 +40,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream +import kotlin.time.Duration.Companion.seconds private val logger = KotlinLogging.logger {} @@ -134,6 +135,7 @@ class Ifds2SqlTest : BaseTest() { cp.newApplicationGraphForAnalysis() } val unitResolver = SingletonUnitResolver - return runTaintAnalysis(graph, unitResolver, methods) + val manager = TaintManager(graph, unitResolver) + return manager.analyze(methods, timeout = 30.seconds) } } From 020df513ed52881a542f510cb61a5eab54eb692a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:55:45 +0300 Subject: [PATCH 053/117] Types --- .../org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index d7153a6a0..08856e0e6 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -63,9 +63,9 @@ class NpeManager( private val unitResolver: UnitResolver, ) : Manager { - private val methodsForUnit: MutableMap> = hashMapOf() - private val runnerForUnit: MutableMap = hashMapOf() - private val queueIsEmpty: MutableMap = ConcurrentHashMap() + private val methodsForUnit = hashMapOf>() + private val runnerForUnit = hashMapOf() + private val queueIsEmpty = ConcurrentHashMap() private val summaryEdgesStorage = SummaryStorageImpl() private val vulnerabilitiesStorage = SummaryStorageImpl() @@ -87,7 +87,7 @@ class NpeManager( private fun addStart(method: JcMethod) { logger.info { "Adding start method: $method" } val unit = unitResolver.resolve(method) - methodsForUnit.getOrPut(unit) { mutableSetOf() }.add(method) + methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) // TODO: val isNew = (...).add(); if (isNew) { deps.forEach { addStart(it) } } } From 8771a1b08de348d8e5766ab4c2acd3aa6bf4ba87 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 15:57:03 +0300 Subject: [PATCH 054/117] Cleanup --- .../org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 08856e0e6..81573ca0e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -141,12 +141,6 @@ class NpeManager( val stopper = launch(Dispatchers.IO) { logger.info { "Stopper job started" } stopRendezvous.receive() - // delay(100) - // @OptIn(ExperimentalCoroutinesApi::class) - // if (runnerForUnit.values.any { !it.workList.isEmpty }) { - // logger.warn { "NOT all runners have empty work list" } - // error("?") - // } logger.info { "Stopping all runners..." } allJobs.forEach { it.cancel() } logger.info { "Stopper job finished" } From 00b20c45f94835fd20f2cdce954813ca0aedfcb2 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 16:05:04 +0300 Subject: [PATCH 055/117] Make handleControlEvent non-suspend again --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt | 2 +- .../kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt | 4 ++-- .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 5 ++--- .../kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt index 38a97967d..649f19181 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt @@ -22,7 +22,7 @@ import org.jacodb.api.JcMethod interface Manager { fun handleEvent(event: Event) - suspend fun handleControlEvent(event: ControlEvent) + fun handleControlEvent(event: ControlEvent) fun subscribeOnSummaryEdges( method: JcMethod, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index 416a8bd1c..4c9c8246f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -61,7 +61,7 @@ class BidiRunner( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { forwardQueueIsEmpty = event.isEmpty @@ -94,7 +94,7 @@ class BidiRunner( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { backwardQueueIsEmpty = event.isEmpty diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index a8813fc4c..134cc599a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -248,16 +248,15 @@ class TaintManager( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { logger.trace { "Runner ${event.runner.unit} is empty: ${event.isEmpty}" } queueIsEmpty[event.runner.unit] = event.isEmpty if (event.isEmpty) { - yield() if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { logger.debug { "All runners are empty" } - stopRendezvous.send(Unit) + stopRendezvous.trySend(Unit).getOrNull() } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 81573ca0e..5a04e32bd 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -209,7 +209,7 @@ class NpeManager( } } - override suspend fun handleControlEvent(event: ControlEvent) { + override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { queueIsEmpty[event.runner.unit] = event.isEmpty From 85704494e558acf87b2393c1ea4880e438b9c0a9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 16:30:34 +0300 Subject: [PATCH 056/117] Log timeout --- .../main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 1 + .../kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 134cc599a..109a5081e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -191,6 +191,7 @@ class TaintManager( withTimeoutOrNull(timeout) { allJobs.joinAll() } ?: run { + logger.info { "Timeout!" } allJobs.forEach { it.cancel() } allJobs.joinAll() } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt index 5a04e32bd..e5cb56a4e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt @@ -154,6 +154,7 @@ class NpeManager( withTimeoutOrNull(timeout) { allJobs.joinAll() } ?: run { + logger.info { "Timeout!" } allJobs.forEach { it.cancel() } allJobs.joinAll() } From 0af092a5b4d693c777cca7f3e7fba10be588dd4a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 14 Feb 2024 16:42:59 +0300 Subject: [PATCH 057/117] Add explanations for unexpected IsType and AnnotationType conditions --- .../src/main/kotlin/org/jacodb/analysis/config/Condition.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index df853c76f..0a92cde15 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -77,10 +77,14 @@ class BasicConditionEvaluator( } override fun visit(condition: IsType): Boolean { + // Note: TaintConfigurationFeature.ConditionSpecializer is responsible for + // expanding IsType condition upon parsing the taint configuration. error("Unexpected condition: $condition") } override fun visit(condition: AnnotationType): Boolean { + // Note: TaintConfigurationFeature.ConditionSpecializer is responsible for + // expanding AnnotationType condition upon parsing the taint configuration. error("Unexpected condition: $condition") } From 8dbe55b205758003bfd94cee5938c5d1ad94cfdf Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 15 Feb 2024 14:38:23 +0300 Subject: [PATCH 058/117] Use isOnHeap --- .../jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt index ae77b4c2a..a272086bc 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -731,11 +731,12 @@ class BackwardTaintFlowFunctions( buildSet { // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: - // TODO: "if passed by-ref" part is not implemented here yet - val actualParams = callExpr.args - val formalParams = project.getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { - addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) + if (fact.variable.isOnHeap) { + val actualParams = callExpr.args + val formalParams = project.getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) + } } // Transmit facts on instance (from 'this' to 'instance'): From 74637616ac9d89caec872f3898f4f4475c6ba5c9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 15 Feb 2024 16:06:48 +0300 Subject: [PATCH 059/117] Revert unnecessary changes --- .gitignore | 4 - buildSrc/src/main/kotlin/Tests.kt | 2 - jacodb-analysis/build.gradle.kts | 1 - .../org/jacodb/analysis/AnalysisMain.kt | 4 +- .../org/jacodb/analysis/engine/IfdsEdge.kt | 2 +- .../org/jacodb/analysis/engine/IfdsVertex.kt | 5 +- .../org/jacodb/analysis/graph/GraphExt.kt | 91 ------------------- .../impl/custom/NullAssumptionAnalysis.kt | 7 +- .../AbstractTaintBackwardFunctions.kt | 37 ++------ .../library/analyzers/SqlInjectionAnalyzer.kt | 4 +- .../analysis/library/analyzers/TaintNode.kt | 5 +- .../analyzers/UnusedVariableAnalyzer.kt | 9 +- .../org/jacodb/analysis/sarif/DataClasses.kt | 26 ++---- .../jacodb/analysis/impl/AliasAnalysisTest.kt | 3 +- .../kotlin/org/jacodb/api/cfg/JcRawInst.kt | 2 +- .../org/jacodb/api/ext/cfg/JcInstructions.kt | 2 +- .../org/jacodb/impl/cfg/JcInstListBuilder.kt | 10 +- .../kotlin/org/jacodb/testing/BaseTest.kt | 4 +- 18 files changed, 39 insertions(+), 179 deletions(-) delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt diff --git a/.gitignore b/.gitignore index fc4236f9b..5a0a47dab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,3 @@ build/ idea-community *.db -*.sarif -*.html -*.jfr -*.csv diff --git a/buildSrc/src/main/kotlin/Tests.kt b/buildSrc/src/main/kotlin/Tests.kt index ae53310c0..873c3a628 100644 --- a/buildSrc/src/main/kotlin/Tests.kt +++ b/buildSrc/src/main/kotlin/Tests.kt @@ -1,6 +1,5 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.testing.Test -import org.gradle.api.tasks.testing.logging.TestExceptionFormat object Tests { val lifecycleTag = "lifecycle" @@ -10,7 +9,6 @@ object Tests { fun Test.setup(jacocoTestReport: TaskProvider<*>) { testLogging { events("passed", "skipped", "failed") - exceptionFormat = TestExceptionFormat.FULL } finalizedBy(jacocoTestReport) // report is always generated after tests run jvmArgs = listOf("-Xmx2g", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=heapdump.hprof") diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index c0dfa6068..1d05d0a8d 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -12,7 +12,6 @@ dependencies { implementation(Libs.slf4j_simple) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.kotlinx_serialization_json) - implementation(Libs.jdot) testImplementation(testFixtures(project(":jacodb-core"))) testImplementation(project(":jacodb-api")) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt index 9b0faa62b..24ffda9f0 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt @@ -15,7 +15,6 @@ */ @file:JvmName("AnalysisMain") - package org.jacodb.analysis import kotlinx.serialization.Serializable @@ -33,6 +32,7 @@ typealias AnalysesOptions = Map @Serializable data class AnalysisConfig(val analyses: Map) + /** * This is the entry point for every analysis. * Calling this function will find all vulnerabilities reachable from [methods]. @@ -65,7 +65,7 @@ fun runAnalysis( unitResolver: UnitResolver, ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, methods: List, - timeoutMillis: Long = Long.MAX_VALUE, + timeoutMillis: Long = Long.MAX_VALUE ): List { return MainIfdsUnitManager(graph, unitResolver, ifdsUnitRunnerFactory, methods, timeoutMillis).analyze() } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt index a8551732b..595321ca4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt @@ -47,5 +47,5 @@ sealed interface PredecessorKind { */ data class PathEdgePredecessor( val predEdge: IfdsEdge, - val kind: PredecessorKind, + val kind: PredecessorKind ) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt index 9b69c5166..0e1605273 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt @@ -19,10 +19,7 @@ package org.jacodb.analysis.engine import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst -data class IfdsVertex( - val statement: JcInst, - val domainFact: DomainFact, -) { +data class IfdsVertex(val statement: JcInst, val domainFact: DomainFact) { val method: JcMethod get() = statement.location.method } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt deleted file mode 100644 index 4536a0f07..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/graph/GraphExt.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.graph - -import info.leadinglight.jdot.Edge -import info.leadinglight.jdot.Graph -import info.leadinglight.jdot.Node -import info.leadinglight.jdot.enums.Color -import info.leadinglight.jdot.enums.Shape -import info.leadinglight.jdot.impl.Util -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcInst -import java.io.File -import java.nio.file.Files -import java.nio.file.Path - -fun JcApplicationGraph.dfs( - node: JcInst, - method: JcMethod, - visited: MutableSet, -) { - if (visited.add(node)) { - for (next in successors(node)) { - if (next.location.method == method) { - dfs(next, method, visited) - } - } - } -} - -fun JcApplicationGraph.view(method: JcMethod, dotCmd: String, viewerCmd: String) { - Util.sh(arrayOf(viewerCmd, "file://${toFile(method, dotCmd)}")) -} - -fun JcApplicationGraph.toFile( - method: JcMethod, - dotCmd: String, - file: File? = null, -): Path { - Graph.setDefaultCmd(dotCmd) - - val graph = Graph("jcApplicationGraph") - graph.setBgColor(Color.X11.transparent) - graph.setFontSize(12.0) - graph.setFontName("monospace") - - val allInstructions: MutableSet = hashSetOf() - for (start in entryPoints(method)) { - dfs(start, method, allInstructions) - } - - val nodes = mutableMapOf() - for ((index, inst) in allInstructions.withIndex()) { - val node = Node("$index") - .setShape(Shape.box) - .setLabel(inst.toString().replace("\"", "\\\"")) - .setFontSize(12.0) - nodes[inst] = node - graph.addNode(node) - } - - for ((inst, node) in nodes) { - for (next in successors(inst)) { - if (next in nodes) { - val edge = Edge(node.name, nodes[next]!!.name) - graph.addEdge(edge) - } - } - } - - val outFile = graph.dot2file("svg") - val newFile = "${outFile.removeSuffix("out")}svg" - val resultingFile = file?.toPath() ?: File(newFile).toPath() - Files.move(File(outFile).toPath(), resultingFile) - return resultingFile -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt index ead16f5d8..610c92e2c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/impl/custom/NullAssumptionAnalysis.kt @@ -28,10 +28,11 @@ import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstanceCallExpr import org.jacodb.api.cfg.JcLocal import org.jacodb.api.cfg.JcValue -import org.jacodb.api.ext.cfg.arrayAccess +import org.jacodb.api.ext.cfg.arrayRef import org.jacodb.api.ext.cfg.callExpr import org.jacodb.api.ext.cfg.fieldRef + class NullAnalysisMap : HashMap { constructor() : super() @@ -69,7 +70,7 @@ open class NullAssumptionAnalysis(graph: JcGraph) : BackwardFlowAnalysis - - abstract fun transmitDataFlowAtNormalInst( - inst: JcInst, - nextInst: JcInst, - fact: DomainFact, - ): List - - override fun obtainSequentFlowFunction( - current: JcInst, - next: JcInst, - ) = FlowFunctionInstance { fact -> + abstract fun transmitBackDataFlow(from: JcValue, to: JcExpr, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List + + abstract fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List + + override fun obtainSequentFlowFunction(current: JcInst, next: JcInst) = FlowFunctionInstance { fact -> // fact.activation != current needed here to jump over assignment where the fact appeared if (current is JcAssignInst && (fact !is TaintNode || fact.activation != current)) { transmitBackDataFlow(current.lhv, current.rhv, current, fact, dropFact = false) @@ -70,7 +57,7 @@ abstract class AbstractTaintBackwardFunctions( override fun obtainCallToStartFlowFunction( callStatement: JcInst, - callee: JcMethod, + callee: JcMethod ): FlowFunctionInstance = FlowFunctionInstance { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") @@ -154,7 +141,7 @@ abstract class AbstractTaintBackwardFunctions( override fun obtainExitToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, - exitStatement: JcInst, + exitStatement: JcInst ): FlowFunctionInstance = FlowFunctionInstance { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") val actualParams = callExpr.args @@ -167,15 +154,7 @@ abstract class AbstractTaintBackwardFunctions( } if (callExpr is JcInstanceCallExpr) { - addAll( - transmitBackDataFlow( - callee.thisInstance, - callExpr.instance, - exitStatement, - fact, - dropFact = true - ) - ) + addAll(transmitBackDataFlow(callee.thisInstance, callExpr.instance, exitStatement, fact, dropFact = true)) } if (fact is TaintNode && fact.variable.isStatic) { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt index 9b999427d..a0a13a924 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt @@ -24,7 +24,7 @@ import org.jacodb.api.analysis.JcApplicationGraph class SqlInjectionAnalyzer( graph: JcApplicationGraph, - maxPathLength: Int, + maxPathLength: Int ) : TaintAnalyzer(graph, maxPathLength) { override val generates = isSourceMethodToGenerates(sqlSourceMatchers.asMethodMatchers) override val sanitizes = isSanitizeMethodToSanitizes(sqlSanitizeMatchers.asMethodMatchers) @@ -42,7 +42,7 @@ class SqlInjectionAnalyzer( class SqlInjectionBackwardAnalyzer( graph: JcApplicationGraph, - maxPathLength: Int, + maxPathLength: Int ) : TaintBackwardAnalyzer(graph, maxPathLength) { override val generates = isSourceMethodToGenerates(sqlSourceMatchers.asMethodMatchers) override val sinks = isSinkMethodToSinks(sqlSinkMatchers.asMethodMatchers) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt index ad8bbaaa5..21f7ef790 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt @@ -68,10 +68,7 @@ abstract class TaintNode( } } -class NpeTaintNode( - variable: AccessPath, - activation: JcInst? = null, -) : TaintNode(variable, activation) { +class NpeTaintNode(variable: AccessPath, activation: JcInst? = null): TaintNode(variable, activation) { override val nodeType: String get() = "NPE" diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt index beae4a2ec..78219431d 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt @@ -77,7 +77,7 @@ class UnusedVariableAnalyzer(graph: JcApplicationGraph) : AbstractAnalyzer(graph return isUsedAt(callExpr) } if (inst is JcAssignInst) { - if (inst.lhv is JcArrayAccess && isUsedAt(inst.lhv as JcArrayAccess)) { + if (inst.lhv is JcArrayAccess && isUsedAt((inst.lhv as JcArrayAccess))) { return true } return isUsedAt(inst.rhv) && (inst.lhv !is JcLocal || inst.rhv !is JcLocal) @@ -118,7 +118,7 @@ val UnusedVariableAnalyzerFactory = AnalyzerFactory { graph -> } private class UnusedVariableForwardFunctions( - val classpath: JcClasspath, + val classpath: JcClasspath ) : FlowFunctionsSpace { override fun obtainPossibleStartFacts(startStatement: JcInst): Collection { @@ -152,9 +152,8 @@ private class UnusedVariableForwardFunctions( } if (fromPath == fact.variable) { - return@FlowFunctionInstance default + UnusedVariableNode(toPath, fact.initStatement) + return@FlowFunctionInstance default.plus(UnusedVariableNode(toPath, fact.initStatement)) } - default } @@ -167,7 +166,7 @@ private class UnusedVariableForwardFunctions( if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { return@FlowFunctionInstance listOf(ZEROFact) } - return@FlowFunctionInstance formalParams.map { UnusedVariableNode(it.toPath(), callStatement) } + ZEROFact + return@FlowFunctionInstance formalParams.map { UnusedVariableNode(it.toPath(), callStatement) }.plus(ZEROFact) } if (fact !is UnusedVariableNode) { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt index 035118275..0024c4b70 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt @@ -46,10 +46,7 @@ data class SarifPhysicalLocation(val artifactLocation: SarifArtifactLocation, va data class SarifLogicalLocation(val fullyQualifiedName: String) @Serializable -data class SarifLocation( - val physicalLocation: SarifPhysicalLocation, - val logicalLocations: List, -) { +data class SarifLocation(val physicalLocation: SarifPhysicalLocation, val logicalLocations: List) { companion object { private val JcMethod.fullyQualifiedName: String get() = "${enclosingClass.name}#${name}" @@ -101,7 +98,7 @@ data class SarifResult( val message: SarifMessage, val level: SarifSeverityLevel, val locations: List, - val codeFlows: List, + val codeFlows: List ) { companion object { fun fromVulnerabilityInstance(instance: VulnerabilityInstance, maxPathsCount: Int): SarifResult = SarifResult( @@ -143,7 +140,7 @@ data class SarifReport( @SerialName("\$schema") val schema: String, - val runs: List, + val runs: List ) { fun encodeToStream(stream: OutputStream) { json.encodeToStream(this, stream) @@ -164,7 +161,7 @@ data class SarifReport( fun fromVulnerabilities( vulnerabilities: List, - pathsCount: Int = defaultPathsCount, + pathsCount: Int = defaultPathsCount ): SarifReport = SarifReport( version = defaultVersion, schema = defaultSchema, @@ -180,20 +177,13 @@ data class SarifReport( @Serializable enum class SarifSeverityLevel { - @SerialName("error") - ERROR, - - @SerialName("warning") - WARNING, - - @SerialName("note") - NOTE + @SerialName("error") ERROR, + @SerialName("warning") WARNING, + @SerialName("note") NOTE } data class VulnerabilityDescription( val message: SarifMessage, val ruleId: String, - val level: SarifSeverityLevel = SarifSeverityLevel.WARNING, - // TODO: don't miss the triggered rule! - // TODO: add val rule: Rule, + val level: SarifSeverityLevel = SarifSeverityLevel.WARNING ) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt index e4ff210fc..b0d1d576a 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt @@ -99,7 +99,6 @@ class AliasAnalysisTest : BaseTest() { testPointerBench("pointerbench.basic.$className", must, notMay) } - @ParameterizedTest @MethodSource("provideForPointerBenchGeneralJava") fun testGeneralJava(className: String, must: List, notMay: List) { @@ -159,4 +158,4 @@ class AliasAnalysisTest : BaseTest() { return result.map { it.traceGraph.sink.statement.toString() } } -} \ No newline at end of file +} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt index 9c35b4f7c..227fece72 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt @@ -1042,4 +1042,4 @@ data class JcRawMethodType( // } // } // return null -//} \ No newline at end of file +//} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt index 88d32e5f6..b8f5181d3 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt @@ -102,7 +102,7 @@ object CallExprVisitor : JcInstVisitor.Default { val JcInst.fieldRef: JcFieldRef? get() = accept(FieldRefVisitor) -val JcInst.arrayAccess: JcArrayAccess? +val JcInst.arrayRef: JcArrayAccess? get() = accept(ArrayAccessVisitor) val JcInst.callExpr: JcCallExpr? diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index a8ac8645a..c10090001 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -328,7 +328,7 @@ class JcInstListBuilder( private fun convertBinary( expr: JcRawBinaryExpr, - handler: (JcType, JcValue, JcValue) -> JcBinaryExpr, + handler: (JcType, JcValue, JcValue) -> JcBinaryExpr ): JcBinaryExpr { val type = expr.typeName.asType() val lhv = expr.lhv.accept(this) as JcValue @@ -405,14 +405,10 @@ class JcInstListBuilder( override fun visitJcRawCastExpr(expr: JcRawCastExpr): JcExpr = JcCastExpr(expr.typeName.asType(), expr.operand.accept(this) as JcValue) - override fun visitJcRawNewExpr(expr: JcRawNewExpr): JcExpr = - JcNewExpr(expr.typeName.asType()) + override fun visitJcRawNewExpr(expr: JcRawNewExpr): JcExpr = JcNewExpr(expr.typeName.asType()) override fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): JcExpr = - JcNewArrayExpr( - expr.typeName.asType(), - expr.dimensions.map { it.accept(this) as JcValue } - ) + JcNewArrayExpr(expr.typeName.asType(), expr.dimensions.map { it.accept(this) as JcValue }) override fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): JcExpr = JcInstanceOfExpr(classpath.boolean, expr.operand.accept(this) as JcValue, expr.targetType.asType()) diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt index 0279fbdb0..0de1b555f 100644 --- a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt @@ -91,7 +91,7 @@ open class WithDB(vararg features: Any) : JcDatabaseHolder { } } - override fun cleanup() { + override fun cleanup() { db.close() } } @@ -100,7 +100,7 @@ val globalDb by lazy { WithDB(Usages, Builders, InMemoryHierarchy).db } -open class WithGlobalDB(vararg _classpathFeatures: JcClasspathFeature) : JcDatabaseHolder { +open class WithGlobalDB(vararg _classpathFeatures: JcClasspathFeature): JcDatabaseHolder { init { System.setProperty("org.jacodb.impl.storage.defaultBatchSize", "500") From bc82b6e10e662d2e6f6070c385110bd324faf6e7 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 13:19:46 +0300 Subject: [PATCH 060/117] Add mocking tests for flow functions --- buildSrc/src/main/kotlin/Dependencies.kt | 12 +- jacodb-analysis/build.gradle.kts | 1 + .../analysis/impl/TaintFlowFunctionsTest.kt | 180 ++++++++++++++++++ jacodb-core/build.gradle.kts | 1 - 4 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index f69657368..669366967 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -29,7 +29,7 @@ object Versions { const val kotlinx_metadata = "0.5.0" const val kotlinx_serialization = "1.4.1" const val licenser = "0.6.1" - const val mockito = "4.8.0" + const val mockk = "1.13.3" const val shadow = "8.1.1" const val slf4j = "1.7.36" const val soot_utbot_fork = "4.4.0-FORK-2" @@ -204,11 +204,11 @@ object Libs { version = Versions.jdot ) - // https://github.com/mockito/mockito - val mockito_core = dep( - group = "org.mockito", - name = "mockito-core", - version = Versions.mockito + // https://github.com/mockk/mockk + val mockk = dep( + group = "io.mockk", + name = "mockk", + version = Versions.mockk ) // https://github.com/JetBrains/java-annotations diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index 1d05d0a8d..c3f3b9a8c 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -21,4 +21,5 @@ dependencies { for (cweNum in listOf(89, 476, 563, 690)) { testImplementation(Libs.juliet_cwe(cweNum)) } + testImplementation(Libs.mockk) } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt new file mode 100644 index 000000000..2ee5ff956 --- /dev/null +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt @@ -0,0 +1,180 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.impl + +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.ifds2.FlowFunctions +import org.jacodb.analysis.ifds2.taint.ForwardTaintFlowFunctions +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.Zero +import org.jacodb.analysis.library.analyzers.getArgument +import org.jacodb.analysis.paths.toPath +import org.jacodb.api.JcClassType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcArgument +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcCallExpr +import org.jacodb.api.cfg.JcCallInst +import org.jacodb.api.cfg.JcLocal +import org.jacodb.api.cfg.JcLocalVar +import org.jacodb.api.cfg.JcReturnInst +import org.jacodb.api.ext.findTypeOrNull +import org.jacodb.api.ext.packageName +import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.taint.configuration.TaintMark +import org.jacodb.testing.BaseTest +import org.jacodb.testing.WithDB +import org.jacodb.testing.allClasspath +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +private val logger = mu.KotlinLogging.logger {} + +@ExtendWith(MockKExtension::class) +class TaintFlowFunctionsTest : BaseTest() { + + companion object : WithDB(Usages, InMemoryHierarchy) + + override val cp: JcClasspath = runBlocking { + val defaultConfigResource = this.javaClass.getResourceAsStream("/config_test.json") + if (defaultConfigResource != null) { + val configJson = defaultConfigResource.bufferedReader().readText() + val configurationFeature = TaintConfigurationFeature.fromJson(configJson) + db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) + } else { + super.cp + } + } + + private val stringType = cp.findTypeOrNull() as JcClassType + + @MockK + private lateinit var graph: JcApplicationGraph + + private val testMethod = mockk { + every { name } returns "test" + every { enclosingClass } returns mockk(relaxed = true) { + every { packageName } returns "com.example" + every { simpleName } returns "Example" + every { name } returns "com.example.Example" + every { superClass } returns null + every { interfaces } returns emptyList() + } + every { isConstructor } returns false + every { returnType } returns mockk(relaxed = true) + every { parameters } returns listOf( + mockk(relaxed = true) { + every { index } returns 0 + every { type } returns mockk { + every { typeName } returns "java.lang.String" + } + } + ) + } + + @Test + fun `test obtain start facts`() { + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val facts = flowSpace.obtainPossibleStartFacts(testMethod).toList() + val arg0 = cp.getArgument(testMethod.parameters[0])!! + Assertions.assertEquals(facts, listOf(Zero, Tainted(arg0.toPath(), TaintMark("EXAMPLE")))) + } + + @Test + fun `test sequential flow function`() { + // "x := y", where 'y' is tainted, should result in both 'x' and 'y' to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val y: JcLocal = JcLocalVar(2, "y", stringType) + val inst = JcAssignInst(location = mockk(), lhv = x, rhv = y) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val f = flowSpace.obtainSequentFlowFunction(inst, next = mockk()) + val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) + val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) + val facts = f.compute(yTaint).toList() + Assertions.assertEquals(facts, listOf(yTaint, xTaint)) + } + + @Test + fun `test call flow function`() { + // "x := test()", where 'test' is a source, should result in 'x' to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk() { + every { method } returns mockk { + every { method } returns testMethod + } + }) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) + val xTaint = Tainted(x.toPath(), TaintMark("EXAMPLE")) + val facts = f.compute(Zero).toList() + Assertions.assertEquals(facts, listOf(Zero, xTaint)) + } + + @Test + fun `test call to start flow function`() { + // "test(x)", where 'x' is tainted, should result in 'x' (formal argument of 'test') to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcCallInst(location = mockk(), callExpr = mockk() { + every { method } returns mockk { + every { method } returns testMethod + } + every { args } returns listOf(x) + }) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk() { + every { location } returns mockk() { + every { method } returns testMethod + } + }) + val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) + val arg0: JcArgument = cp.getArgument(testMethod.parameters[0])!! + val arg0Taint = Tainted(arg0.toPath(), TaintMark("TAINT")) + val facts = f.compute(xTaint).toList() + Assertions.assertEquals(facts, listOf(arg0Taint)) + } + + @Test + fun `test exit flow function`() { + // "x := test()" + "return y", where 'y' is tainted, should result in 'x' to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk() { + every { method } returns mockk { + every { method } returns testMethod + } + }) + val y: JcLocal = JcLocalVar(1, "y", stringType) + val exitStatement = JcReturnInst(location = mockk { + every { method } returns testMethod + }, returnValue = y) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val f = flowSpace.obtainExitToReturnSiteFlowFunction(callStatement, returnSite = mockk(), exitStatement) + val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) + val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) + val facts = f.compute(yTaint).toList() + Assertions.assertEquals(facts, listOf(xTaint)) + } +} diff --git a/jacodb-core/build.gradle.kts b/jacodb-core/build.gradle.kts index 5eab623c7..b800d11c4 100644 --- a/jacodb-core/build.gradle.kts +++ b/jacodb-core/build.gradle.kts @@ -48,7 +48,6 @@ dependencies { testFixturesImplementation(platform(Libs.junit_bom)) testFixturesImplementation(Libs.junit_jupiter) testFixturesImplementation(Libs.guava) - testFixturesImplementation(Libs.mockito_core) testFixturesImplementation(Libs.jetbrains_annotations) testFixturesImplementation(Libs.kotlinx_coroutines_core) } From 94ccf10ab9d7b37e653fc8bfb9547cbee1facfdd Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 13:26:31 +0300 Subject: [PATCH 061/117] Remove unnecessary converters to old DomainFact --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt | 9 --------- .../main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt | 11 ----------- .../org/jacodb/analysis/ifds2/taint/TaintFacts.kt | 6 ------ 3 files changed, 26 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt index 091a96976..4d4db2497 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt @@ -16,8 +16,6 @@ package org.jacodb.analysis.ifds2 -import org.jacodb.analysis.engine.IfdsEdge -import org.jacodb.analysis.ifds2.taint.TaintFact import org.jacodb.api.JcMethod data class Edge( @@ -30,13 +28,6 @@ data class Edge( val method: JcMethod get() = from.method - - companion object { - // constructor - operator fun invoke(edge: IfdsEdge): Edge { - return Edge(Vertex(edge.from), Vertex(edge.to)) - } - } } sealed class Reason { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt index d96dcfb74..a19467ec7 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt @@ -16,10 +16,6 @@ package org.jacodb.analysis.ifds2 -import org.jacodb.analysis.engine.IfdsVertex -import org.jacodb.analysis.ifds2.taint.TaintFact -import org.jacodb.analysis.ifds2.taint.toDomainFact -import org.jacodb.analysis.ifds2.taint.toFact import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst @@ -29,11 +25,4 @@ data class Vertex( ) { val method: JcMethod get() = statement.location.method - - companion object { - // constructor - operator fun invoke(vertex: IfdsVertex): Vertex { - return Vertex(vertex.statement, vertex.domainFact.toFact()) - } - } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt index f7bcafaf2..d4258905e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt @@ -37,12 +37,6 @@ data class Tainted( constructor(fact: TaintNode) : this(fact.variable, TaintMark(fact.nodeType)) } -fun DomainFact.toFact(): TaintFact = when (this) { - ZEROFact -> Zero - is TaintNode -> Tainted(this) - else -> error("Go away") // object : TaintFact {} -} - fun TaintFact.toDomainFact(): DomainFact = when (this) { Zero -> ZEROFact From ce503338699c733560e436b7c9d8df33f8b82d29 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 13:32:33 +0300 Subject: [PATCH 062/117] Add missing config for mocking tests --- .../src/test/resources/config_test.json | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 jacodb-analysis/src/test/resources/config_test.json diff --git a/jacodb-analysis/src/test/resources/config_test.json b/jacodb-analysis/src/test/resources/config_test.json new file mode 100644 index 000000000..52d09f51a --- /dev/null +++ b/jacodb-analysis/src/test/resources/config_test.json @@ -0,0 +1,87 @@ +[ + { + "_": "EntryPointSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "com.example" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "test" + }, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": false, + "functionLabel": null, + "modifier": -1, + "exclude": [] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Argument", + "number": 0 + }, + "mark": { + "name": "EXAMPLE" + } + } + ] + }, + { + "_": "MethodSource", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "com.example" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "test" + }, + "parametersMatchers": [ + ], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + }, + "applyToOverrides": true, + "functionLabel": null, + "modifier": -1, + "exclude": [ + ] + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "AssignMark", + "position": { + "_": "Result" + }, + "mark": { + "name": "EXAMPLE" + } + } + ] + } +] From 927dbb13062cddb9ff96096d3f054d95354a44e3 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 13:36:42 +0300 Subject: [PATCH 063/117] Remove trace logging --- .../analysis/ifds2/taint/TaintAnalyzers.kt | 5 +---- .../ifds2/taint/TaintFlowFunctions.kt | 10 ++-------- .../analysis/ifds2/taint/npe/NpeAnalyzers.kt | 5 +---- .../ifds2/taint/npe/NpeFlowFunctions.kt | 10 ++-------- .../AbstractTaintForwardFunctions.kt | 5 +---- .../library/analyzers/TaintAnalyzer.kt | 20 +++++++------------ 6 files changed, 14 insertions(+), 41 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 9199dddce..8cb682c99 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -65,10 +65,7 @@ class TaintAnalyzer( val callExpr = edge.to.statement.callExpr ?: return@run val callee = callExpr.method.method - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $callee" } - feature.getConfigForMethod(callee) - } ?: return@run + val config = taintConfigurationFeature?.getConfigForMethod(callee) ?: return@run // TODO: not always we want to skip sinks on Zero facts. // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt index a272086bc..707815f7a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -80,10 +80,7 @@ class ForwardTaintFlowFunctions( add(Zero) // Extract initial facts from the config: - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $method" } - feature.getConfigForMethod(method) - } + val config = taintConfigurationFeature?.getConfigForMethod(method) if (config != null) { // Note: both condition and action evaluator require a custom position resolver. val conditionEvaluator = BasicConditionEvaluator { position -> @@ -263,10 +260,7 @@ class ForwardTaintFlowFunctions( return@FlowFunction setOf(fact) } - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $callee" } - feature.getConfigForMethod(callee) - } + val config = taintConfigurationFeature?.getConfigForMethod(callee) // If 'fact' is ZeroFact, handle MethodSource. If there are no suitable MethodSource items, perform default. // For other facts (Tainted only?), handle PassThrough/Cleaner items. diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt index ccc8cb998..8fd1f8f13 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt @@ -80,10 +80,7 @@ class NpeAnalyzer( val callExpr = edge.to.statement.callExpr ?: return@run val callee = callExpr.method.method - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $callee" } - feature.getConfigForMethod(callee) - } ?: return@run + val config = taintConfigurationFeature?.getConfigForMethod(callee) ?: return@run // TODO: not always we want to skip sinks on Zero facts. // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt index 4ee3e6ea9..bced8d995 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt @@ -118,10 +118,7 @@ class ForwardNpeFlowFunctions( // } // Extract initial facts from the config: - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $method" } - feature.getConfigForMethod(method) - } + val config = taintConfigurationFeature?.getConfigForMethod(method) if (config != null) { // Note: both condition and action evaluator require a custom position resolver. val conditionEvaluator = BasicConditionEvaluator { position -> @@ -396,10 +393,7 @@ class ForwardNpeFlowFunctions( return@FlowFunction setOf(fact) } - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $callee" } - feature.getConfigForMethod(callee) - } + val config = taintConfigurationFeature?.getConfigForMethod(callee) if (fact == Zero) { return@FlowFunction buildSet { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt index fdf3676f3..6fb8def59 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt @@ -150,10 +150,7 @@ abstract class AbstractTaintForwardFunctions( return@FlowFunctionInstance setOf(fact) } - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $callee" } - feature.getConfigForMethod(callee) - } + val config = taintConfigurationFeature?.getConfigForMethod(callee) if (fact == ZEROFact) { val facts = mutableSetOf() diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt index 01f4d826a..8b810c4fe 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt @@ -113,7 +113,7 @@ abstract class TaintAnalyzer( abstract val sanitizes: (JcExpr, TaintNode) -> Boolean abstract val sinks: (JcInst) -> List - override val flowFunctions: FlowFunctionsSpace by lazy { + override val flowFunctions: TaintForwardFunctions by lazy { TaintForwardFunctions(graph, maxPathLength, generates, sanitizes) } @@ -127,11 +127,8 @@ abstract class TaintAnalyzer( val callExpr = edge.to.statement.callExpr ?: return@run false val callee = callExpr.method.method - val config = (flowFunctions as TaintForwardFunctions) - .taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $callee" } - feature.getConfigForMethod(callee) - } ?: return@run false + val config = flowFunctions.taintConfigurationFeature?.getConfigForMethod(callee) + ?: return@run false // TODO: not always we want to skip sinks on ZeroFacts. Some rules might have ConstantTrue or just true (when evaluated with ZeroFact) condition. if (edge.to.domainFact !is TaintNode) { @@ -192,7 +189,7 @@ abstract class TaintBackwardAnalyzer( override val isMainAnalyzer: Boolean get() = false - override val flowFunctions: FlowFunctionsSpace by lazy { + override val flowFunctions: TaintBackwardFunctions by lazy { TaintBackwardFunctions(graph, generates, sinks, maxPathLength) } @@ -203,7 +200,7 @@ abstract class TaintBackwardAnalyzer( } } -private class TaintForwardFunctions( +class TaintForwardFunctions( graph: JcApplicationGraph, private val maxPathLength: Int, private val generates: (JcInst) -> List, @@ -314,10 +311,7 @@ private class TaintForwardFunctions( add(ZEROFact) val method = startStatement.location.method - val config = taintConfigurationFeature?.let { feature -> - logger.trace { "Extracting config for $method" } - feature.getConfigForMethod(method) - } + val config = taintConfigurationFeature?.getConfigForMethod(method) if (config != null) { val conditionEvaluator = BasicConditionEvaluator { position -> when (position) { @@ -371,7 +365,7 @@ private class TaintForwardFunctions( } } -private class TaintBackwardFunctions( +class TaintBackwardFunctions( graph: JcApplicationGraph, val generates: (JcInst) -> List, val sinks: (JcInst) -> List, From 327af42b1d66dd7da0b227eb4a6c5d2cfcf75b82 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 15:55:34 +0300 Subject: [PATCH 064/117] Cleanup --- .../org/jacodb/analysis/config/TaintAction.kt | 35 ------------------- .../analysis/ifds2/taint/TaintAnalyzers.kt | 5 --- .../ifds2/taint/TaintFlowFunctions.kt | 29 --------------- .../analysis/ifds2/taint/TaintManager.kt | 1 - 4 files changed, 70 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt index e28a78d20..a54cf9da8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt @@ -75,38 +75,3 @@ class TaintActionEvaluator( return setOf(fact) } } - -class FactAwareTaintActionEvaluator( - private val fact: Tainted, - private val evaluator: TaintActionEvaluator, -) : TaintActionVisitor> { - - constructor( - fact: Tainted, - positionResolver: PositionResolver, - ) : this(fact, TaintActionEvaluator(positionResolver)) - - override fun visit(action: CopyAllMarks): Collection { - return evaluator.evaluate(action, fact) - } - - override fun visit(action: CopyMark): Collection { - return evaluator.evaluate(action, fact) - } - - override fun visit(action: AssignMark): Collection { - return setOf(fact, evaluator.evaluate(action)) - } - - override fun visit(action: RemoveAllMarks): Collection { - return evaluator.evaluate(action, fact) - } - - override fun visit(action: RemoveMark): Collection { - return evaluator.evaluate(action, fact) - } - - override fun visit(action: Action): Collection { - error("$this cannot handle $action") - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 8cb682c99..84b7ca6d9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -128,11 +128,6 @@ class BackwardTaintAnalyzer( return statement in graph.exitPoints(statement.location.method) } - private fun isSink(statement: JcInst, fact: TaintFact): Boolean { - // TODO - return false - } - override fun handleNewEdge( edge: TaintEdge, ): List = buildList { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt index 707815f7a..d7adf92e4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -262,19 +262,6 @@ class ForwardTaintFlowFunctions( val config = taintConfigurationFeature?.getConfigForMethod(callee) - // If 'fact' is ZeroFact, handle MethodSource. If there are no suitable MethodSource items, perform default. - // For other facts (Tainted only?), handle PassThrough/Cleaner items. - // TODO: what to do with "other facts" on CopyAllMarks/RemoveAllMarks? - - // TODO: the call-to-return flow function should also return (or somehow mark internally) - // whether we need to analyze the callee. For example, when we have MethodSource, - // PassThrough or Cleaner for a call statement, we do not need to analyze the callee at all. - // However, when we do not have such items in our config, we have to perform the whole analysis - // of the callee: calling call-to-start flow function, launching the analysis of the callee, - // awaiting for summary edges, and finally executing the exit-to-return flow function. - // In such case, the call-to-return flow function should return empty list of facts, - // since they are going to be "handled by the summary edge". - if (fact == Zero) { return@FlowFunction buildSet { add(Zero) @@ -302,12 +289,6 @@ class ForwardTaintFlowFunctions( } check(fact is Tainted) - // TODO: handle 'activation' (c.f. Boomerang) here - - // if (config == null) { - // return@FlowFunction emptyList() - // } - if (config != null) { val facts = mutableSetOf() val conditionEvaluator = FactAwareConditionEvaluator(fact, CallPositionToJcValueResolver(callStatement)) @@ -518,16 +499,6 @@ class BackwardTaintFlowFunctions( val toPath = to.toPathOrNull() if (toPath != null) { - // TODO: think about arrays here - // // Adhoc taint array: - // if (fromPath.accesses.isNotEmpty() - // && fromPath.accesses.last() is ElementAccessor - // && fromPath.copy(accesses = fromPath.accesses.dropLast(1)) == fact.variable - // ) { - // val newTaint = fact.copy(variable = toPath) - // return setOf(fact, newTaint) - // } - val tail = fact.variable - fromPath if (tail != null) { // Both 'from' and 'to' are tainted now: diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 109a5081e..032cf4e12 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.coroutines.yield import mu.KotlinLogging import org.jacodb.analysis.engine.SummaryStorageImpl import org.jacodb.analysis.engine.UnitResolver From 090adac6402a98f891c6eb7abed9344f6513b8db Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 16:24:40 +0300 Subject: [PATCH 065/117] Extract aggregates --- .../src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt | 3 ++- .../kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt | 5 +++++ .../kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt index 52bf12320..7417e88c8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt @@ -42,6 +42,7 @@ interface Runner { suspend fun run(startMethods: List) fun submitNewEdge(edge: Edge) + fun getAggregate(): Aggregate } @Suppress("RecursivePropertyAccessor") @@ -270,7 +271,7 @@ class UniRunner( return resultFacts } - private fun getAggregate(): Aggregate { + override fun getAggregate(): Aggregate { val facts = getFinalFacts() return Aggregate(pathEdges, facts, reasons) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt index 4c9c8246f..2831e7311 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType +import org.jacodb.analysis.ifds2.Aggregate import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Edge import org.jacodb.analysis.ifds2.Manager @@ -136,4 +137,8 @@ class BidiRunner( backwardRunnerJob.join() forwardRunnerJob.join() } + + override fun getAggregate(): Aggregate { + return forwardRunner.getAggregate() + } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 032cf4e12..28bafc18c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -35,6 +35,7 @@ import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.engine.UnitType import org.jacodb.analysis.engine.UnknownUnit import org.jacodb.analysis.graph.reversed +import org.jacodb.analysis.ifds2.Aggregate import org.jacodb.analysis.ifds2.ControlEvent import org.jacodb.analysis.ifds2.Manager import org.jacodb.analysis.ifds2.QueueEmptinessChanged @@ -273,6 +274,10 @@ class TaintManager( .onEach { handler(it.edge) } .launchIn(scope) } + + fun getAggregates() : Map> { + return runnerForUnit.mapValues { it.value.getAggregate() } + } } fun runTaintAnalysis( From c03165095b400fddbdad2516de988225334531b4 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 16:25:20 +0300 Subject: [PATCH 066/117] Add test with bidi runner and class unit resolver --- .../org/jacodb/analysis/impl/Ifds2SqlTest.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt index 2b8e17fe3..2fef22ec8 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt @@ -18,6 +18,7 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking import mu.KotlinLogging +import org.jacodb.analysis.engine.ClassUnitResolver import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds2.taint.TaintManager @@ -34,6 +35,7 @@ import org.jacodb.testing.BaseTest import org.jacodb.testing.WithDB import org.jacodb.testing.allClasspath import org.jacodb.testing.analysis.SqlInjectionExamples +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -101,6 +103,24 @@ class Ifds2SqlTest : BaseTest() { testSingleJulietClass(className) } + @Test + fun `test bidirectional runner and other stuff`() { + val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_51a" + val clazz = cp.findClass(className) + val badMethod = clazz.methods.single { it.name == "bad" } + val graph = runBlocking { + cp.newApplicationGraphForAnalysis() + } + val unitResolver = ClassUnitResolver(true) + val manager = TaintManager(graph, unitResolver, useBidiRunner = true) + val sinks = manager.analyze(listOf(badMethod), timeout = 30.seconds) + assertTrue(sinks.isNotEmpty()) + val aggregates = manager.getAggregates() + logger.debug { "Aggregates: $aggregates" } + val graphs = aggregates.mapValues { it.value.buildTraceGraph(sinks.first().sink) } + assertTrue(graphs.isNotEmpty()) + } + private fun testSingleJulietClass(className: String) { logger.info { className } From 901e25514d84510933a40555c5f4e4372931c40b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 16:25:38 +0300 Subject: [PATCH 067/117] Fix visited --- .../main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt index fdd688e34..689dd01fa 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt @@ -31,9 +31,8 @@ class Aggregate( fun buildTraceGraph(sink: Vertex): TraceGraph { val sources: MutableSet> = hashSetOf() - val edges: MutableMap, MutableSet>> = - hashMapOf() - val visited: MutableSet> = hashSetOf() + val edges: MutableMap, MutableSet>> = hashMapOf() + val visited: MutableSet, Vertex>> = hashSetOf() fun addEdge( from: Vertex, @@ -49,10 +48,11 @@ class Aggregate( lastVertex: Vertex, stopAtMethodStart: Boolean, ) { - if (!visited.add(edge)) { + if (!visited.add(edge to lastVertex)) { return } + // Note: loop-edge represents method start if (stopAtMethodStart && edge.from == edge.to) { addEdge(edge.from, lastVertex) return @@ -112,7 +112,6 @@ class Aggregate( } is Reason.Initial -> { - // TODO: check sources.add(vertex) addEdge(edge.to, lastVertex) } From 9611d6653e8116c5eb580a71057d6bb3e37dc161 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 16:25:49 +0300 Subject: [PATCH 068/117] Start analysis of deps --- .../org/jacodb/analysis/ifds2/taint/TaintManager.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt index 28bafc18c..cef67b85a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt @@ -121,11 +121,11 @@ class TaintManager( val unit = unitResolver.resolve(method) if (unit == UnknownUnit) return val isNew = methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) - // if (isNew) { - // for (dep in getAllCallees(method)) { - // addStart(dep) - // } - // } + if (isNew) { + for (dep in getAllCallees(method)) { + addStart(dep) + } + } } @OptIn(ExperimentalTime::class) From 6f1a8d0f4774d242764639fe10fd4491825fd2dd Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 19:38:24 +0300 Subject: [PATCH 069/117] Review fixes --- .../org/jacodb/analysis/config/Condition.kt | 129 +++++++----------- .../org/jacodb/analysis/config/Position.kt | 58 +++----- .../org/jacodb/analysis/config/TaintAction.kt | 65 ++++----- .../kotlin/org/jacodb/analysis/ifds2/Trace.kt | 62 +-------- .../ifds2/taint/TaintFlowFunctions.kt | 81 +++++------ .../analysis/ifds2/taint/npe/NpeAnalyzers.kt | 41 +----- .../ifds2/taint/npe/NpeFlowFunctions.kt | 78 +++++------ .../AbstractTaintForwardFunctions.kt | 52 +++---- .../library/analyzers/TaintAnalyzer.kt | 31 ++--- .../kotlin/org/jacodb/analysis/paths/Maybe.kt | 60 ++++++++ .../analysis/impl/TaintFlowFunctionsTest.kt | 76 ++++++++--- .../src/test/resources/config_test.json | 87 ++++++++++++ 12 files changed, 420 insertions(+), 400 deletions(-) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index 0a92cde15..e7e533a45 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -17,6 +17,8 @@ package org.jacodb.analysis.config import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.paths.Maybe +import org.jacodb.analysis.paths.onSome import org.jacodb.analysis.paths.startsWith import org.jacodb.analysis.paths.toPath import org.jacodb.api.cfg.JcBool @@ -46,8 +48,8 @@ import org.jacodb.taint.configuration.PositionResolver import org.jacodb.taint.configuration.SourceFunctionMatches import org.jacodb.taint.configuration.TypeMatches -class BasicConditionEvaluator( - internal val positionResolver: PositionResolver, +open class BasicConditionEvaluator( + internal val positionResolver: PositionResolver>, ) : ConditionVisitor { // Default condition handler: @@ -72,8 +74,8 @@ class BasicConditionEvaluator( } override fun visit(condition: IsConstant): Boolean { - val value = positionResolver.resolve(condition.position) - return value is JcConstant + positionResolver.resolve(condition.position).onSome { return it is JcConstant } + return false } override fun visit(condition: IsType): Boolean { @@ -89,51 +91,59 @@ class BasicConditionEvaluator( } override fun visit(condition: ConstantEq): Boolean { - val value = positionResolver.resolve(condition.position) - return when (val constant = condition.value) { - is ConstantBooleanValue -> { - value is JcBool && value.value == constant.value - } - - is ConstantIntValue -> { - value is JcInt && value.value == constant.value - } - - is ConstantStringValue -> { - // TODO: if 'value' is not string, convert it to string and compare with 'constant.value' - value is JcStringConstant && value.value == constant.value + positionResolver.resolve(condition.position).onSome { value -> + return when (val constant = condition.value) { + is ConstantBooleanValue -> { + value is JcBool && value.value == constant.value + } + + is ConstantIntValue -> { + value is JcInt && value.value == constant.value + } + + is ConstantStringValue -> { + // TODO: if 'value' is not string, convert it to string and compare with 'constant.value' + value is JcStringConstant && value.value == constant.value + } + + else -> error("Unexpected constant: $constant") } - - else -> error("Unexpected constant: $constant") } + return false } override fun visit(condition: ConstantLt): Boolean { - val value = positionResolver.resolve(condition.position) - return when (val constant = condition.value) { - is ConstantIntValue -> { - value is JcInt && value.value < constant.value - } + positionResolver.resolve(condition.position).onSome { value -> + return when (val constant = condition.value) { + is ConstantIntValue -> { + value is JcInt && value.value < constant.value + } - else -> error("Unexpected constant: $constant") + else -> error("Unexpected constant: $constant") + } } + return false } override fun visit(condition: ConstantGt): Boolean { - val value = positionResolver.resolve(condition.position) - return when (val constant = condition.value) { - is ConstantIntValue -> { - value is JcInt && value.value > constant.value - } + positionResolver.resolve(condition.position).onSome { value -> + return when (val constant = condition.value) { + is ConstantIntValue -> { + value is JcInt && value.value > constant.value + } - else -> error("Unexpected constant: $constant") + else -> error("Unexpected constant: $constant") + } } + return false } override fun visit(condition: ConstantMatches): Boolean { - val value = positionResolver.resolve(condition.position) - val re = condition.pattern.toRegex() - return re.matches(value.toString()) + positionResolver.resolve(condition.position).onSome { value -> + val re = condition.pattern.toRegex() + return re.matches(value.toString()) + } + return false } override fun visit(condition: SourceFunctionMatches): Boolean { @@ -145,57 +155,24 @@ class BasicConditionEvaluator( } override fun visit(condition: TypeMatches): Boolean { - val value = positionResolver.resolve(condition.position) - return value.type.isAssignable(condition.type) + positionResolver.resolve(condition.position).onSome { value -> + return value.type.isAssignable(condition.type) + } + return false } } class FactAwareConditionEvaluator( private val fact: Tainted, - private val basicConditionEvaluator: BasicConditionEvaluator, -) : ConditionVisitor { - - constructor( - fact: Tainted, - positionResolver: PositionResolver, - ) : this(fact, BasicConditionEvaluator(positionResolver)) + positionResolver: PositionResolver>, +) : BasicConditionEvaluator(positionResolver) { override fun visit(condition: ContainsMark): Boolean { - if (fact.mark == condition.mark) { - val value = basicConditionEvaluator.positionResolver.resolve(condition.position) + if (fact.mark != condition.mark) return false + positionResolver.resolve(condition.position).onSome { value -> val variable = value.toPath() - if (variable.startsWith(fact.variable)) { - return true - } + return variable.startsWith(fact.variable) } return false } - - override fun visit(condition: And): Boolean { - return condition.args.all { it.accept(this) } - } - - override fun visit(condition: Or): Boolean { - return condition.args.any { it.accept(this) } - } - - override fun visit(condition: Not): Boolean { - return !condition.arg.accept(this) - } - - override fun visit(condition: ConstantTrue): Boolean { - return true - } - - override fun visit(condition: IsConstant): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: IsType): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: AnnotationType): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantEq): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantLt): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantGt): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: ConstantMatches): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: SourceFunctionMatches): Boolean = basicConditionEvaluator.visit(condition) - override fun visit(condition: TypeMatches): Boolean = basicConditionEvaluator.visit(condition) - - override fun visit(condition: Condition): Boolean = basicConditionEvaluator.visit(condition) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt index 3147fb146..6bb5c46e9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt @@ -18,6 +18,9 @@ package org.jacodb.analysis.config import org.jacodb.analysis.paths.AccessPath import org.jacodb.analysis.paths.ElementAccessor +import org.jacodb.analysis.paths.Maybe +import org.jacodb.analysis.paths.fmap +import org.jacodb.analysis.paths.toMaybe import org.jacodb.analysis.paths.toPathOrNull import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcInst @@ -34,56 +37,31 @@ import org.jacodb.taint.configuration.This class CallPositionToAccessPathResolver( private val callStatement: JcInst, -) : PositionResolver { +) : PositionResolver> { private val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - override fun resolve(position: Position): AccessPath = when (position) { - AnyArgument -> error("Unexpected $position") - - is Argument -> callExpr.args[position.index].toPathOrNull() - ?: error("Cannot resolve $position for $callStatement") - - This -> (callExpr as? JcInstanceCallExpr)?.instance?.toPathOrNull() - ?: error("Cannot resolve $position for $callStatement") - - Result -> if (callStatement is JcAssignInst) { - callStatement.lhv.toPathOrNull() - } else { - callExpr.toPathOrNull() - } ?: error("Cannot resolve $position for $callStatement") - - ResultAnyElement -> { - val path = if (callStatement is JcAssignInst) { - callStatement.lhv.toPathOrNull() - } else { - callExpr.toPathOrNull() - } ?: error("Cannot resolve $position for $callStatement") - path / ElementAccessor(null) - } + override fun resolve(position: Position): Maybe = when (position) { + AnyArgument -> Maybe.none() + is Argument -> callExpr.args[position.index].toPathOrNull().toMaybe() + This -> (callExpr as? JcInstanceCallExpr)?.instance?.toPathOrNull().toMaybe() + Result -> (callStatement as? JcAssignInst)?.lhv?.toPathOrNull().toMaybe() + ResultAnyElement -> (callStatement as? JcAssignInst)?.lhv?.toPathOrNull().toMaybe() + .fmap { it / ElementAccessor(null) } } } class CallPositionToJcValueResolver( private val callStatement: JcInst, -) : PositionResolver { +) : PositionResolver> { private val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - override fun resolve(position: Position): JcValue = when (position) { - AnyArgument -> error("Unexpected $position") - - is Argument -> callExpr.args[position.index] - - This -> (callExpr as? JcInstanceCallExpr)?.instance - ?: error("Cannot resolve $position for $callStatement") - - Result -> if (callStatement is JcAssignInst) { - callStatement.lhv - } else { - error("Cannot resolve $position for $callStatement") - } - - ResultAnyElement -> error("Cannot resolve $position for $callStatement") + override fun resolve(position: Position): Maybe = when (position) { + AnyArgument -> Maybe.none() + is Argument -> Maybe.some(callExpr.args[position.index]) + This -> (callExpr as? JcInstanceCallExpr)?.instance.toMaybe() + Result -> (callStatement as? JcAssignInst)?.lhv.toMaybe() + ResultAnyElement -> Maybe.none() } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt index a54cf9da8..c9aa007b5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt @@ -18,60 +18,53 @@ package org.jacodb.analysis.config import org.jacodb.analysis.ifds2.taint.Tainted import org.jacodb.analysis.paths.AccessPath -import org.jacodb.taint.configuration.Action +import org.jacodb.analysis.paths.Maybe +import org.jacodb.analysis.paths.fmap +import org.jacodb.analysis.paths.map import org.jacodb.taint.configuration.AssignMark import org.jacodb.taint.configuration.CopyAllMarks import org.jacodb.taint.configuration.CopyMark import org.jacodb.taint.configuration.PositionResolver import org.jacodb.taint.configuration.RemoveAllMarks import org.jacodb.taint.configuration.RemoveMark -import org.jacodb.taint.configuration.TaintActionVisitor class TaintActionEvaluator( - internal val positionResolver: PositionResolver, + private val positionResolver: PositionResolver>, ) { - fun evaluate(action: CopyAllMarks, fact: Tainted): Collection { - val from = positionResolver.resolve(action.from) - if (from == fact.variable) { - val to = positionResolver.resolve(action.to) - return setOf(fact, fact.copy(variable = to)) + fun evaluate(action: CopyAllMarks, fact: Tainted): Maybe> = + positionResolver.resolve(action.from).map { from -> + if (from != fact.variable) return@map Maybe.none() + positionResolver.resolve(action.to).fmap { to -> + setOf(fact, fact.copy(variable = to)) + } } - return setOf(fact) // TODO: empty of singleton? - // return emptySet() - } - fun evaluate(action: CopyMark, fact: Tainted): Collection { - if (fact.mark == action.mark) { - val from = positionResolver.resolve(action.from) - if (from == fact.variable) { - val to = positionResolver.resolve(action.to) - return setOf(fact, fact.copy(variable = to)) + fun evaluate(action: CopyMark, fact: Tainted): Maybe> { + if (fact.mark != action.mark) return Maybe.none() + return positionResolver.resolve(action.from).map { from -> + if (from != fact.variable) return@map Maybe.none() + positionResolver.resolve(action.to).fmap { to -> + setOf(fact, fact.copy(variable = to)) } } - return setOf(fact) // TODO: empty or singleton? - // return emptySet() } - fun evaluate(action: AssignMark): Tainted { - val variable = positionResolver.resolve(action.position) - return Tainted(variable, action.mark) - } + fun evaluate(action: AssignMark): Maybe> = + positionResolver.resolve(action.position).fmap { variable -> + setOf(Tainted(variable, action.mark)) + } - fun evaluate(action: RemoveAllMarks, fact: Tainted): Collection { - val variable = positionResolver.resolve(action.position) - if (variable == fact.variable) { - return emptySet() + fun evaluate(action: RemoveAllMarks, fact: Tainted): Maybe> = + positionResolver.resolve(action.position).map { variable -> + if (variable != fact.variable) return@map Maybe.none() + Maybe.some(emptySet()) } - return setOf(fact) - } - fun evaluate(action: RemoveMark, fact: Tainted): Collection { - if (fact.mark == action.mark) { - val variable = positionResolver.resolve(action.position) - if (variable == fact.variable) { - return emptySet() - } + fun evaluate(action: RemoveMark, fact: Tainted): Maybe> { + if (fact.mark != action.mark) return Maybe.none() + return positionResolver.resolve(action.position).map { variable -> + if (variable != fact.variable) return@map Maybe.none() + Maybe.some(emptySet()) } - return setOf(fact) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt index b082c014b..7894244da 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt @@ -20,64 +20,4 @@ data class TraceGraph( val sink: Vertex, val sources: Set>, val edges: Map, Set>>, -) { - - /** - * Returns all traces from [sources] to [sink]. - */ - fun getAllTraces(): Sequence>> = sequence { - for (v in sources) { - yieldAll(getAllTraces(mutableListOf(v))) - } - } - - private fun getAllTraces( - trace: MutableList>, - ): Sequence>> = sequence { - val v = trace.last() - if (v == sink) { - yield(trace.toList()) // copy list - return@sequence - } - for (u in edges[v].orEmpty()) { - if (u !in trace) { - trace.add(u) - yieldAll(getAllTraces(trace)) - trace.removeLast() - } - } - } - - /** - * Merges two graphs. - * - * [sink] will be chosen from receiver, and edges from both graphs will be merged. - * Also, all edges from [upGraph]'s sink to [entryPoints] will be added - * (these are edges "connecting" [upGraph] with receiver). - * - * Informally, this method extends receiver's traces from one side using [upGraph]. - */ - fun mergeWithUpGraph( - upGraph: TraceGraph, - entryPoints: Set>, - ): TraceGraph { - val validEntryPoints = entryPoints.intersect(edges.keys) - if (validEntryPoints.isEmpty()) return this - - val newSources = sources + upGraph.sources - val newEdges = edges.toMutableMap() - for ((source, destinations) in upGraph.edges) { - newEdges[source] = newEdges.getOrDefault(source, emptySet()) + destinations - } - newEdges[upGraph.sink] = newEdges.getOrDefault(upGraph.sink, emptySet()) + validEntryPoints - return TraceGraph(sink, newSources, newEdges) - } - - companion object { - fun bySink( - sink: Vertex, - ): TraceGraph { - return TraceGraph(sink, setOf(sink), emptyMap()) - } - } -} +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt index d7adf92e4..8d6b0fee6 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt @@ -28,8 +28,11 @@ import org.jacodb.analysis.library.analyzers.getArgument import org.jacodb.analysis.library.analyzers.getArgumentsOf import org.jacodb.analysis.library.analyzers.thisInstance import org.jacodb.analysis.paths.ElementAccessor +import org.jacodb.analysis.paths.Maybe import org.jacodb.analysis.paths.minus +import org.jacodb.analysis.paths.onSome import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toMaybe import org.jacodb.analysis.paths.toPath import org.jacodb.analysis.paths.toPathOrNull import org.jacodb.api.JcClasspath @@ -85,12 +88,12 @@ class ForwardTaintFlowFunctions( // Note: both condition and action evaluator require a custom position resolver. val conditionEvaluator = BasicConditionEvaluator { position -> when (position) { - This -> method.thisInstance + This -> Maybe.some(method.thisInstance) - is Argument -> run { + is Argument -> { val p = method.parameters[position.index] - cp.getArgument(p) - } ?: error("Cannot resolve $position for $method") + cp.getArgument(p).toMaybe() + } AnyArgument -> error("Unexpected $position") Result -> error("Unexpected $position") @@ -99,13 +102,12 @@ class ForwardTaintFlowFunctions( } val actionEvaluator = TaintActionEvaluator { position -> when (position) { - This -> method.thisInstance.toPathOrNull() - ?: error("Cannot resolve $position for $method") + This -> method.thisInstance.toPathOrNull().toMaybe() - is Argument -> run { + is Argument -> { val p = method.parameters[position.index] - cp.getArgument(p)?.toPathOrNull() - } ?: error("Cannot resolve $position for $method") + cp.getArgument(p)?.toPathOrNull().toMaybe() + } AnyArgument -> error("Unexpected $position") Result -> error("Unexpected $position") @@ -117,13 +119,16 @@ class ForwardTaintFlowFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - when (action) { + val result = when (action) { is AssignMark -> { - add(actionEvaluator.evaluate(action)) + actionEvaluator.evaluate(action) } else -> error("$action is not supported for $item") } + result.onSome { + addAll(it) + } } } } @@ -274,13 +279,13 @@ class ForwardTaintFlowFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - when (action) { - is AssignMark -> { - add(actionEvaluator.evaluate(action)) - } - + val result = when (action) { + is AssignMark -> actionEvaluator.evaluate(action) else -> error("$action is not supported for $item") } + result.onSome { + addAll(it) + } } } } @@ -299,26 +304,17 @@ class ForwardTaintFlowFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - defaultBehavior = false - when (action) { - is CopyMark -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is CopyAllMarks -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is RemoveMark -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is RemoveAllMarks -> { - facts += actionEvaluator.evaluate(action, fact) - } - + val result = when (action) { + is CopyMark -> actionEvaluator.evaluate(action, fact) + is CopyAllMarks -> actionEvaluator.evaluate(action, fact) + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) else -> error("$action is not supported for $item") } + result.onSome { + facts += it + defaultBehavior = false + } } } } @@ -327,18 +323,15 @@ class ForwardTaintFlowFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - defaultBehavior = false - when (action) { - is RemoveMark -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is RemoveAllMarks -> { - facts += actionEvaluator.evaluate(action, fact) - } - + val result = when (action) { + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) else -> error("$action is not supported for $item") } + result.onSome { + facts += it + defaultBehavior = false + } } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt index 8fd1f8f13..b488620c1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt @@ -105,24 +105,20 @@ class NpeAnalyzer( // break } if (triggeredItem != null) { - // logger.info { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } + logger.trace { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } val message = triggeredItem.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) - // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) - // verticesWithTraceGraphNeeded.add(edge.to) } } if (defaultBehavior) { // Default ("config"-less) behavior: if (isSink(edge.to.statement, edge.to.fact)) { - // logger.info { "Found sink at ${edge.to} in ${edge.method}" } + logger.trace { "Found sink at ${edge.to} in ${edge.method}" } val message = "SINK" // TODO val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) - // logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) - // verticesWithTraceGraphNeeded.add(edge.to) } } } @@ -134,36 +130,3 @@ class NpeAnalyzer( add(EdgeForOtherRunner(TaintEdge(callee, callee))) } } - -// class BackwardTaintAnalyzer( -// private val graph: JcApplicationGraph, -// ) : Analyzer { -// -// override val flowFunctions: BackwardNpeFlowFunctions by lazy { -// BackwardNpeFlowFunctions(graph.classpath, graph) -// } -// -// private fun isExitPoint(statement: JcInst): Boolean { -// return statement in graph.exitPoints(statement.location.method) -// } -// -// private fun isSink(statement: JcInst, fact: TaintFact): Boolean { -// // TODO -// return false -// } -// -// override fun handleNewEdge( -// edge: TaintEdge, -// ): List = buildList { -// if (isExitPoint(edge.to.statement)) { -// add(EdgeForOtherRunner(Edge(edge.to, edge.to))) -// } -// } -// -// override fun handleCrossUnitCall( -// caller: TaintVertex, -// callee: TaintVertex, -// ): List { -// return emptyList() -// } -// } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt index bced8d995..0fb3963e0 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt @@ -32,9 +32,12 @@ import org.jacodb.analysis.library.analyzers.getArgumentsOf import org.jacodb.analysis.library.analyzers.thisInstance import org.jacodb.analysis.paths.AccessPath import org.jacodb.analysis.paths.ElementAccessor +import org.jacodb.analysis.paths.Maybe import org.jacodb.analysis.paths.isDereferencedAt import org.jacodb.analysis.paths.minus +import org.jacodb.analysis.paths.onSome import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toMaybe import org.jacodb.analysis.paths.toPath import org.jacodb.analysis.paths.toPathOrNull import org.jacodb.api.JcArrayType @@ -123,12 +126,12 @@ class ForwardNpeFlowFunctions( // Note: both condition and action evaluator require a custom position resolver. val conditionEvaluator = BasicConditionEvaluator { position -> when (position) { - This -> method.thisInstance + This -> Maybe.some( method.thisInstance) - is Argument -> run { + is Argument -> { val p = method.parameters[position.index] - cp.getArgument(p) - } ?: error("Cannot resolve $position for $method") + cp.getArgument(p).toMaybe() + } AnyArgument -> error("Unexpected $position") Result -> error("Unexpected $position") @@ -137,13 +140,12 @@ class ForwardNpeFlowFunctions( } val actionEvaluator = TaintActionEvaluator { position -> when (position) { - This -> method.thisInstance.toPathOrNull() - ?: error("Cannot resolve $position for $method") + This -> method.thisInstance.toPathOrNull().toMaybe() - is Argument -> run { + is Argument -> { val p = method.parameters[position.index] - cp.getArgument(p)?.toPathOrNull() - } ?: error("Cannot resolve $position for $method") + cp.getArgument(p)?.toPathOrNull().toMaybe() + } AnyArgument -> error("Unexpected $position") Result -> error("Unexpected $position") @@ -155,13 +157,16 @@ class ForwardNpeFlowFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - when (action) { + val result = when (action) { is AssignMark -> { - add(actionEvaluator.evaluate(action)) + actionEvaluator.evaluate(action) } else -> error("$action is not supported for $item") } + result.onSome { + addAll(it) + } } } } @@ -420,13 +425,16 @@ class ForwardNpeFlowFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - when (action) { + val result = when (action) { is AssignMark -> { - add(actionEvaluator.evaluate(action)) + actionEvaluator.evaluate(action) } else -> error("$action is not supported for $item") } + result.onSome { + addAll(it) + } } } } @@ -454,27 +462,18 @@ class ForwardNpeFlowFunctions( // Handle PassThrough config items: for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { - defaultBehavior = false for (action in item.actionsAfter) { - when (action) { - is CopyMark -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is CopyAllMarks -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is RemoveMark -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is RemoveAllMarks -> { - facts += actionEvaluator.evaluate(action, fact) - } - + val result = when (action) { + is CopyMark -> actionEvaluator.evaluate(action, fact) + is CopyAllMarks -> actionEvaluator.evaluate(action, fact) + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) else -> error("$action is not supported for $item") } + result.onSome { + facts += it + defaultBehavior = false + } } } } @@ -482,19 +481,16 @@ class ForwardNpeFlowFunctions( // Handle Cleaner config items: for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { - defaultBehavior = false for (action in item.actionsAfter) { - when (action) { - is RemoveMark -> { - facts += actionEvaluator.evaluate(action, fact) - } - - is RemoveAllMarks -> { - facts += actionEvaluator.evaluate(action, fact) - } - + val result = when (action) { + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) else -> error("$action is not supported for $item") } + result.onSome { + facts += it + defaultBehavior = false + } } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt index 6fb8def59..4abe2b4a6 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt @@ -28,6 +28,7 @@ import org.jacodb.analysis.engine.FlowFunctionsSpace import org.jacodb.analysis.engine.ZEROFact import org.jacodb.analysis.ifds2.taint.Tainted import org.jacodb.analysis.ifds2.taint.toDomainFact +import org.jacodb.analysis.paths.onSome import org.jacodb.analysis.paths.startsWith import org.jacodb.analysis.paths.toPath import org.jacodb.analysis.paths.toPathOrNull @@ -160,13 +161,16 @@ abstract class AbstractTaintForwardFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - when (action) { + val result = when (action) { is AssignMark -> { - facts += actionEvaluator.evaluate(action) + actionEvaluator.evaluate(action) } else -> error("$action is not supported for $item") } + result.onSome { + facts += it + } } } } @@ -194,45 +198,33 @@ abstract class AbstractTaintForwardFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { - defaultBehavior = false for (action in item.actionsAfter) { - when (action) { - is CopyMark -> { - facts += actionEvaluator.evaluate(action, Tainted(fact)) - } - - is CopyAllMarks -> { - facts += actionEvaluator.evaluate(action, Tainted(fact)) - } - - is RemoveMark -> { - facts += actionEvaluator.evaluate(action, Tainted(fact)) - } - - is RemoveAllMarks -> { - facts += actionEvaluator.evaluate(action, Tainted(fact)) - } - + val result = when (action) { + is CopyMark -> actionEvaluator.evaluate(action, Tainted(fact)) + is CopyAllMarks -> actionEvaluator.evaluate(action, Tainted(fact)) + is RemoveMark -> actionEvaluator.evaluate(action, Tainted(fact)) + is RemoveAllMarks -> actionEvaluator.evaluate(action, Tainted(fact)) else -> error("$action is not supported for $item") } + result.onSome { + facts += it + defaultBehavior = false + } } } } for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { - defaultBehavior = false for (action in item.actionsAfter) { - when (action) { - is RemoveMark -> { - facts += actionEvaluator.evaluate(action, Tainted(fact)) - } - - is RemoveAllMarks -> { - facts += actionEvaluator.evaluate(action, Tainted(fact)) - } - + val result = when (action) { + is RemoveMark -> actionEvaluator.evaluate(action, Tainted(fact)) + is RemoveAllMarks -> actionEvaluator.evaluate(action, Tainted(fact)) else -> error("$action is not supported for $item") } + result.onSome { + facts += it + defaultBehavior = false + } } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt index 8b810c4fe..e9ef84e3a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt @@ -25,7 +25,6 @@ import org.jacodb.analysis.engine.AbstractAnalyzer import org.jacodb.analysis.engine.AnalysisDependentEvent import org.jacodb.analysis.engine.DomainFact import org.jacodb.analysis.engine.EdgeForOtherRunnerQuery -import org.jacodb.analysis.engine.FlowFunctionsSpace import org.jacodb.analysis.engine.IfdsEdge import org.jacodb.analysis.engine.IfdsVertex import org.jacodb.analysis.engine.NewSummaryFact @@ -33,8 +32,11 @@ import org.jacodb.analysis.engine.VulnerabilityLocation import org.jacodb.analysis.engine.ZEROFact import org.jacodb.analysis.ifds2.taint.Tainted import org.jacodb.analysis.ifds2.taint.toDomainFact +import org.jacodb.analysis.paths.Maybe import org.jacodb.analysis.paths.minus +import org.jacodb.analysis.paths.onSome import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.paths.toMaybe import org.jacodb.analysis.paths.toPath import org.jacodb.analysis.paths.toPathOrNull import org.jacodb.analysis.sarif.VulnerabilityDescription @@ -315,12 +317,12 @@ class TaintForwardFunctions( if (config != null) { val conditionEvaluator = BasicConditionEvaluator { position -> when (position) { - This -> method.thisInstance + This -> Maybe.some(method.thisInstance) is Argument -> method.flowGraph().locals .filterIsInstance() .singleOrNull { it.index == position.index } - ?: error("Cannot resolve $position for $method") + .toMaybe() AnyArgument -> error("Unexpected $position") Result -> error("Unexpected $position") @@ -329,18 +331,13 @@ class TaintForwardFunctions( } val actionEvaluator = TaintActionEvaluator { position -> when (position) { - This -> method.thisInstance.toPathOrNull() - ?: error("Cannot resolve $position for $method") + This -> method.thisInstance.toPathOrNull().toMaybe() is Argument -> { val p = method.parameters[position.index] - val t = cp.findTypeOrNull(p.type) - if (t != null) { - JcArgument.of(p.index, p.name, t).toPathOrNull() - } else { - null - } - ?: error("Cannot resolve $position for $method") + cp.findTypeOrNull(p.type) + ?.let { t -> JcArgument.of(p.index, p.name, t).toPathOrNull() } + .toMaybe() } AnyArgument -> error("Unexpected $position") @@ -351,13 +348,13 @@ class TaintForwardFunctions( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { - when (action) { - is AssignMark -> { - add(actionEvaluator.evaluate(action).toDomainFact()) - } - + val result = when (action) { + is AssignMark -> actionEvaluator.evaluate(action) else -> error("$action is not supported for $item") } + result.onSome { + addAll(it.map { fact -> fact.toDomainFact() }) + } } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt new file mode 100644 index 000000000..24eb89839 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.paths + +@JvmInline +value class Maybe private constructor( + private val rawValue: Any?, +) { + val isSome: Boolean get() = rawValue !== NONE_VALUE + val isNone: Boolean get() = rawValue === NONE_VALUE + + fun getOrThrow(): T { + check(isSome) { "Maybe is None" } + @Suppress("UNCHECKED_CAST") + return rawValue as T + } + + companion object { + private val NONE_VALUE = Any() + private val NONE = Maybe(NONE_VALUE) + + fun none(): Maybe = NONE + + fun some(value: T): Maybe = Maybe(value) + + fun from(value: T?): Maybe = if (value == null) none() else some(value) + } +} + +inline fun Maybe.map(body: (T) -> Maybe): Maybe = + if (isNone) Maybe.none() else body(getOrThrow()) + +inline fun Maybe.fmap(body: (T) -> R): Maybe = + if (isNone) Maybe.none() else Maybe.some(body(getOrThrow())) + +inline fun Maybe.onSome(body: (T) -> Unit): Maybe { + if (isSome) body(getOrThrow()) + return this +} + +inline fun Maybe.onNone(body: () -> Unit): Maybe { + if (isNone) body() + return this +} + +fun T?.toMaybe(): Maybe = Maybe.from(this) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt index 2ee5ff956..3f2a99918 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt @@ -17,8 +17,6 @@ package org.jacodb.analysis.impl import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.junit5.MockKExtension import io.mockk.mockk import kotlinx.coroutines.runBlocking import org.jacodb.analysis.ifds2.FlowFunctions @@ -36,9 +34,11 @@ import org.jacodb.api.cfg.JcArgument import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcCallExpr import org.jacodb.api.cfg.JcCallInst +import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcLocal import org.jacodb.api.cfg.JcLocalVar import org.jacodb.api.cfg.JcReturnInst +import org.jacodb.api.ext.cfg.callExpr import org.jacodb.api.ext.findTypeOrNull import org.jacodb.api.ext.packageName import org.jacodb.impl.features.InMemoryHierarchy @@ -50,11 +50,7 @@ import org.jacodb.testing.WithDB import org.jacodb.testing.allClasspath import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -private val logger = mu.KotlinLogging.logger {} - -@ExtendWith(MockKExtension::class) class TaintFlowFunctionsTest : BaseTest() { companion object : WithDB(Usages, InMemoryHierarchy) @@ -72,8 +68,11 @@ class TaintFlowFunctionsTest : BaseTest() { private val stringType = cp.findTypeOrNull() as JcClassType - @MockK - private lateinit var graph: JcApplicationGraph + private val graph: JcApplicationGraph = mockk { + every { callees(any()) } answers { + sequenceOf(arg(0).callExpr!!.method.method) + } + } private val testMethod = mockk { every { name } returns "test" @@ -101,11 +100,12 @@ class TaintFlowFunctionsTest : BaseTest() { val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val facts = flowSpace.obtainPossibleStartFacts(testMethod).toList() val arg0 = cp.getArgument(testMethod.parameters[0])!! - Assertions.assertEquals(facts, listOf(Zero, Tainted(arg0.toPath(), TaintMark("EXAMPLE")))) + val arg0Taint = Tainted(arg0.toPath(), TaintMark("EXAMPLE")) + Assertions.assertEquals(listOf(Zero, arg0Taint), facts) } @Test - fun `test sequential flow function`() { + fun `test sequential flow function assign mark`() { // "x := y", where 'y' is tainted, should result in both 'x' and 'y' to be tainted val x: JcLocal = JcLocalVar(1, "x", stringType) val y: JcLocal = JcLocalVar(2, "y", stringType) @@ -115,12 +115,12 @@ class TaintFlowFunctionsTest : BaseTest() { val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) val facts = f.compute(yTaint).toList() - Assertions.assertEquals(facts, listOf(yTaint, xTaint)) + Assertions.assertEquals(listOf(yTaint, xTaint), facts) } @Test - fun `test call flow function`() { - // "x := test()", where 'test' is a source, should result in 'x' to be tainted + fun `test call flow function assign mark`() { + // "x := test(...)", where 'test' is a source, should result in 'x' to be tainted val x: JcLocal = JcLocalVar(1, "x", stringType) val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk() { every { method } returns mockk { @@ -131,7 +131,47 @@ class TaintFlowFunctionsTest : BaseTest() { val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("EXAMPLE")) val facts = f.compute(Zero).toList() - Assertions.assertEquals(facts, listOf(Zero, xTaint)) + Assertions.assertEquals(listOf(Zero, xTaint), facts) + } + + @Test + fun `test call flow function remove mark`() { + // "test(x)", where 'x' is tainted, should result in 'x' NOT to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcCallInst(location = mockk(), callExpr = mockk() { + every { method } returns mockk { + every { method } returns testMethod + } + every { args } returns listOf(x) + }) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) + val xTaint = Tainted(x.toPath(), TaintMark("REMOVE")) + val facts = f.compute(xTaint).toList() + Assertions.assertTrue(facts.isEmpty()) + } + + @Test + fun `test call flow function copy mark`() { + // "y := test(x)" should result in 'y' to be tainted only when 'x' is tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val y: JcLocal = JcLocalVar(2, "y", stringType) + val callStatement = JcAssignInst(location = mockk(), lhv = y, rhv = mockk() { + every { method } returns mockk { + every { method } returns testMethod + } + every { args } returns listOf(x) + }) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) + val xTaint = Tainted(x.toPath(), TaintMark("COPY")) + val yTaint = Tainted(y.toPath(), TaintMark("COPY")) + val facts = f.compute(xTaint).toList() + Assertions.assertEquals(listOf(xTaint, yTaint), facts) // copy from x to y + val other: JcLocal = JcLocalVar(10, "other", stringType) + val otherTaint = Tainted(other.toPath(), TaintMark("OTHER")) + val facts2 = f.compute(otherTaint).toList() + Assertions.assertEquals(listOf(otherTaint), facts2) // pass-through } @Test @@ -154,7 +194,11 @@ class TaintFlowFunctionsTest : BaseTest() { val arg0: JcArgument = cp.getArgument(testMethod.parameters[0])!! val arg0Taint = Tainted(arg0.toPath(), TaintMark("TAINT")) val facts = f.compute(xTaint).toList() - Assertions.assertEquals(facts, listOf(arg0Taint)) + Assertions.assertEquals(listOf(arg0Taint), facts) + val other: JcLocal = JcLocalVar(10, "other", stringType) + val otherTaint = Tainted(other.toPath(), TaintMark("TAINT")) + val facts2 = f.compute(otherTaint).toList() + Assertions.assertTrue(facts2.isEmpty()) } @Test @@ -175,6 +219,6 @@ class TaintFlowFunctionsTest : BaseTest() { val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) val facts = f.compute(yTaint).toList() - Assertions.assertEquals(facts, listOf(xTaint)) + Assertions.assertEquals(listOf(xTaint), facts) } } diff --git a/jacodb-analysis/src/test/resources/config_test.json b/jacodb-analysis/src/test/resources/config_test.json index 52d09f51a..86260231e 100644 --- a/jacodb-analysis/src/test/resources/config_test.json +++ b/jacodb-analysis/src/test/resources/config_test.json @@ -83,5 +83,92 @@ } } ] + }, + { + "_": "Cleaner", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "com.example" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "test" + }, + "applyToOverrides": true, + "exclude": [], + "functionLabel": null, + "modifier": -1, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + } + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "RemoveMark", + "mark": { + "name": "REMOVE" + }, + "position": { + "_": "Argument", + "number": 0 + } + } + ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "com.example" + }, + "classNameMatcher": { + "_": "NameMatches", + "pattern": ".*" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "test" + }, + "applyToOverrides": true, + "exclude": [], + "functionLabel": null, + "modifier": -1, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + } + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyMark", + "mark": { + "name": "COPY" + }, + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + } + ] } ] From 8d531e531c6ff8b22e0dcab08c126ca0bbebd468 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 19:48:54 +0300 Subject: [PATCH 070/117] Move toDomainFact --- .../org/jacodb/analysis/engine/Utils.kt | 34 +++++++++++++++++++ .../jacodb/analysis/ifds2/taint/TaintFacts.kt | 11 ------ .../AbstractTaintForwardFunctions.kt | 2 +- .../library/analyzers/TaintAnalyzer.kt | 2 +- 4 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt new file mode 100644 index 000000000..aa7157693 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.engine + +import org.jacodb.analysis.ifds2.taint.TaintFact +import org.jacodb.analysis.ifds2.taint.Tainted +import org.jacodb.analysis.ifds2.taint.Zero +import org.jacodb.analysis.library.analyzers.NpeTaintNode +import org.jacodb.analysis.library.analyzers.TaintAnalysisNode + +fun TaintFact.toDomainFact(): DomainFact = when (this) { + Zero -> ZEROFact + + is Tainted -> { + when (mark.name) { + "NPE" -> NpeTaintNode(variable) + else -> TaintAnalysisNode(variable, nodeType = mark.name) + } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt index d4258905e..7013e6b44 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt @@ -36,14 +36,3 @@ data class Tainted( ) : TaintFact { constructor(fact: TaintNode) : this(fact.variable, TaintMark(fact.nodeType)) } - -fun TaintFact.toDomainFact(): DomainFact = when (this) { - Zero -> ZEROFact - - is Tainted -> { - when (mark.name) { - "NPE" -> NpeTaintNode(variable) - else -> TaintAnalysisNode(variable, nodeType = mark.name) - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt index 4abe2b4a6..d4c9bb8e2 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt @@ -27,7 +27,7 @@ import org.jacodb.analysis.engine.FlowFunctionInstance import org.jacodb.analysis.engine.FlowFunctionsSpace import org.jacodb.analysis.engine.ZEROFact import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.ifds2.taint.toDomainFact +import org.jacodb.analysis.engine.toDomainFact import org.jacodb.analysis.paths.onSome import org.jacodb.analysis.paths.startsWith import org.jacodb.analysis.paths.toPath diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt index e9ef84e3a..df5bb5b6e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt @@ -31,7 +31,7 @@ import org.jacodb.analysis.engine.NewSummaryFact import org.jacodb.analysis.engine.VulnerabilityLocation import org.jacodb.analysis.engine.ZEROFact import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.ifds2.taint.toDomainFact +import org.jacodb.analysis.engine.toDomainFact import org.jacodb.analysis.paths.Maybe import org.jacodb.analysis.paths.minus import org.jacodb.analysis.paths.onSome From a83cf68a8e556789048e66793d0904a5b5618e3e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 19:49:31 +0300 Subject: [PATCH 071/117] Report vulnerability for each triggered rule --- .../analysis/ifds2/taint/TaintAnalyzers.kt | 22 +++++-------------- .../analysis/ifds2/taint/npe/NpeAnalyzers.kt | 18 +++++---------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt index 84b7ca6d9..b90c6a1d3 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt @@ -21,13 +21,9 @@ import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.ifds2.Analyzer import org.jacodb.analysis.ifds2.Edge -import org.jacodb.analysis.paths.toPath -import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcIfInst import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.cfg.callExpr -import org.jacodb.impl.cfg.util.loops import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintMethodSink @@ -78,22 +74,14 @@ class TaintAnalyzer( edge.to.fact, CallPositionToJcValueResolver(edge.to.statement), ) - var triggeredItem: TaintMethodSink? = null for (item in config.filterIsInstance()) { - defaultBehavior = false if (item.condition.accept(conditionEvaluator)) { - triggeredItem = item - break + defaultBehavior = false + val message = item.ruleNote + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = item) + logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + add(NewVulnerability(vulnerability)) } - // FIXME: unconditionally let it be the sink. - // triggeredItem = item - // break - } - if (triggeredItem != null) { - val message = triggeredItem.ruleNote - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) - logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } - add(NewVulnerability(vulnerability)) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt index b488620c1..97dea41de 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt @@ -93,22 +93,14 @@ class NpeAnalyzer( edge.to.fact, CallPositionToJcValueResolver(edge.to.statement), ) - var triggeredItem: TaintMethodSink? = null for (item in config.filterIsInstance()) { - defaultBehavior = false if (item.condition.accept(conditionEvaluator)) { - triggeredItem = item - break + defaultBehavior = false + logger.trace { "Found sink at ${edge.to} in ${edge.method} on $item" } + val message = item.ruleNote + val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = item) + add(NewVulnerability(vulnerability)) } - // FIXME: unconditionally let it be the sink. - // triggeredItem = item - // break - } - if (triggeredItem != null) { - logger.trace { "Found sink at ${edge.to} in ${edge.method} on $triggeredItem" } - val message = triggeredItem.ruleNote - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = triggeredItem) - add(NewVulnerability(vulnerability)) } } From b5a0db3375a52fb6e5f3c6fb2a645a315b21850b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 20:43:58 +0300 Subject: [PATCH 072/117] Increase coverage --- build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1b1169c68..faab30ea7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -114,6 +114,8 @@ allprojects { classDirectories.setFrom(files(classDirectories.files.map { fileTree(it) { excludes.add("org/jacodb/impl/storage/jooq/**") + excludes.add("org/jacodb/examples/**") + excludes.add("org/jacodb/testing/**") } })) reports { From 459c9a1f718302f86fb78ee3c33491c9565683fd Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 16 Feb 2024 21:01:17 +0300 Subject: [PATCH 073/117] Revert "Increase coverage" This reverts commit b5a0db3375a52fb6e5f3c6fb2a645a315b21850b. --- build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index faab30ea7..1b1169c68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -114,8 +114,6 @@ allprojects { classDirectories.setFrom(files(classDirectories.files.map { fileTree(it) { excludes.add("org/jacodb/impl/storage/jooq/**") - excludes.add("org/jacodb/examples/**") - excludes.add("org/jacodb/testing/**") } })) reports { From e1b00e2f4ef1267553cd2349ad31b26e423589a0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 01:56:49 +0300 Subject: [PATCH 074/117] Remove old IFDS --- buildSrc/src/main/kotlin/Dependencies.kt | 10 +- jacodb-analysis/build.gradle.kts | 1 + .../org/jacodb/analysis/AnalysisMain.kt | 71 --- .../org/jacodb/analysis/config/Condition.kt | 10 +- .../org/jacodb/analysis/config/Position.kt | 12 +- .../org/jacodb/analysis/config/TaintAction.kt | 10 +- .../analysis/engine/AbstractAnalyzer.kt | 82 ---- .../jacodb/analysis/engine/AnalyzerFactory.kt | 122 ----- .../engine/BaseIfdsUnitRunnerFactory.kt | 397 ---------------- .../engine/BidiIfdsUnitRunnerFactory.kt | 159 ------- .../org/jacodb/analysis/engine/IfdsEdge.kt | 51 --- .../org/jacodb/analysis/engine/IfdsResult.kt | 119 ----- .../jacodb/analysis/engine/IfdsUnitManager.kt | 56 --- .../analysis/engine/IfdsUnitRunnerFactory.kt | 97 ---- .../analysis/engine/MainIfdsUnitManager.kt | 275 ------------ .../org/jacodb/analysis/engine/Utils.kt | 34 -- .../analysis/engine/VulnerabilityInstance.kt | 28 -- .../analysis/{paths => ifds}/AccessPath.kt | 51 ++- .../analysis/{paths => ifds}/Accessors.kt | 14 +- .../analysis/{ifds2 => ifds}/Aggregate.kt | 7 +- .../analysis/{ifds2 => ifds}/Analyzer.kt | 4 +- .../{engine/IfdsVertex.kt => ifds/Edge.kt} | 14 +- .../analysis/{ifds2 => ifds}/FlowFunctions.kt | 2 +- .../analysis/{ifds2 => ifds}/Manager.kt | 2 +- .../jacodb/analysis/{paths => ifds}/Maybe.kt | 2 +- .../{ifds2/Edge.kt => ifds/Reason.kt} | 18 +- .../jacodb/analysis/{ifds2 => ifds}/Runner.kt | 44 +- .../SummaryStorage.kt => ifds/Summary.kt} | 78 +--- .../analysis/{engine => ifds}/TraceGraph.kt | 74 ++- .../analysis/{engine => ifds}/UnitResolver.kt | 5 +- .../jacodb/analysis/{ifds2 => ifds}/Vertex.kt | 2 +- .../jacodb/analysis/library/RunnersLibrary.kt | 52 --- .../AbstractTaintBackwardFunctions.kt | 165 ------- .../AbstractTaintForwardFunctions.kt | 334 -------------- .../library/analyzers/AliasAnalyzer.kt | 48 -- .../analysis/library/analyzers/NpeAnalyzer.kt | 333 -------------- .../library/analyzers/SqlInjectionAnalyzer.kt | 73 --- .../library/analyzers/TaintAnalyzer.kt | 423 ------------------ .../analysis/library/analyzers/TaintNode.kt | 104 ----- .../analyzers/UnusedVariableAnalyzer.kt | 193 -------- .../{ifds2/taint => }/npe/NpeAnalyzers.kt | 43 +- .../{ifds2/taint => }/npe/NpeFlowFunctions.kt | 43 +- .../{ifds2/taint => }/npe/NpeManager.kt | 48 +- .../kotlin/org/jacodb/analysis/npe/utils.kt | 57 +++ .../kotlin/org/jacodb/analysis/paths/Util.kt | 140 ------ .../org/jacodb/analysis/sarif/DataClasses.kt | 189 -------- .../kotlin/org/jacodb/analysis/sarif/Sarif.kt | 150 +++++++ .../Trace.kt => sarif/SourceFileResolver.kt} | 12 +- .../jacodb/analysis/sarif/Vulnerability.kt | 26 +- .../analysis/{ifds2 => }/taint/BidiRunner.kt | 19 +- .../{ifds2 => }/taint/TaintAnalyzers.kt | 26 +- .../analysis/{ifds2 => }/taint/TaintEvents.kt | 2 +- .../analysis/{ifds2 => }/taint/TaintFacts.kt | 15 +- .../{ifds2 => }/taint/TaintFlowFunctions.kt | 30 +- .../{ifds2 => }/taint/TaintManager.kt | 36 +- .../{ifds2 => }/taint/TaintSummaries.kt | 8 +- .../analysis/{ifds2 => }/taint/types.kt | 8 +- .../analyzers/Util.kt => util/Utils.kt} | 43 +- .../analysis/impl/JavaAnalysisApiTest.java | 17 +- .../jacodb/analysis/impl/AliasAnalysisTest.kt | 161 ------- .../jacodb/analysis/impl/BaseAnalysisTest.kt | 64 +-- .../impl/{Ifds2NpeTest.kt => IfdsNpeTest.kt} | 73 +-- .../impl/{Ifds2SqlTest.kt => IfdsSqlTest.kt} | 79 +--- .../analysis/impl/JodaDateTimeAnalysisTest.kt | 45 +- .../jacodb/analysis/impl/NpeAnalysisTest.kt | 228 ---------- .../analysis/impl/SqlInjectionAnalysisTest.kt | 78 ---- .../analysis/impl/TaintFlowFunctionsTest.kt | 39 +- .../analysis/impl/UnusedVariableTest.kt | 72 --- .../test/resources/simplelogger.properties | 2 +- jacodb-cli/build.gradle.kts | 11 - .../src/main/kotlin/org/jacodb/cli/main.kt | 158 ------- jacodb-cli/src/test/resources/config.json | 9 - .../configuration/TaintConfigurationItem.kt | 34 -- settings.gradle.kts | 1 - 74 files changed, 603 insertions(+), 4939 deletions(-) delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{paths => ifds}/AccessPath.kt (65%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{paths => ifds}/Accessors.kt (78%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => ifds}/Aggregate.kt (97%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => ifds}/Analyzer.kt (93%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{engine/IfdsVertex.kt => ifds/Edge.kt} (77%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => ifds}/FlowFunctions.kt (98%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => ifds}/Manager.kt (97%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{paths => ifds}/Maybe.kt (98%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2/Edge.kt => ifds/Reason.kt} (78%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => ifds}/Runner.kt (88%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{engine/SummaryStorage.kt => ifds/Summary.kt} (61%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{engine => ifds}/TraceGraph.kt (55%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{engine => ifds}/UnitResolver.kt (94%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => ifds}/Vertex.kt (96%) delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/RunnersLibrary.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AliasAnalyzer.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2/taint => }/npe/NpeAnalyzers.kt (73%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2/taint => }/npe/NpeFlowFunctions.kt (96%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2/taint => }/npe/NpeManager.kt (86%) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/utils.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Sarif.kt rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2/Trace.kt => sarif/SourceFileResolver.kt} (77%) rename jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt => jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt (61%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/BidiRunner.kt (92%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/TaintAnalyzers.kt (82%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/TaintEvents.kt (96%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/TaintFacts.kt (61%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/TaintFlowFunctions.kt (97%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/TaintManager.kt (90%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/TaintSummaries.kt (90%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{ifds2 => }/taint/types.kt (82%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/{library/analyzers/Util.kt => util/Utils.kt} (56%) delete mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt rename jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/{Ifds2NpeTest.kt => IfdsNpeTest.kt} (76%) rename jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/{Ifds2SqlTest.kt => IfdsSqlTest.kt} (56%) delete mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt delete mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt delete mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt delete mode 100644 jacodb-cli/build.gradle.kts delete mode 100644 jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt delete mode 100644 jacodb-cli/src/test/resources/config.json diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 669366967..2c4ebcbbb 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -30,6 +30,7 @@ object Versions { const val kotlinx_serialization = "1.4.1" const val licenser = "0.6.1" const val mockk = "1.13.3" + const val sarif4k = "0.5.0" const val shadow = "8.1.1" const val slf4j = "1.7.36" const val soot_utbot_fork = "4.4.0-FORK-2" @@ -280,6 +281,13 @@ object Libs { name = "cwe${cweNum}", version = Versions.juliet ) + + // https://github.com/detekt/sarif4k + val sarif4k = dep( + group = "io.github.detekt.sarif4k", + name = "sarif4k", + version = Versions.sarif4k + ) } object Plugins { @@ -325,4 +333,4 @@ object Plugins { fun PluginDependenciesSpec.id(plugin: Plugins.ProjectPlugin) { id(plugin.id).version(plugin.version) -} \ No newline at end of file +} diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index c3f3b9a8c..cfab5c5dc 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { implementation(Libs.slf4j_simple) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.kotlinx_serialization_json) + implementation(Libs.sarif4k) testImplementation(testFixtures(project(":jacodb-core"))) testImplementation(project(":jacodb-api")) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt deleted file mode 100644 index 24ffda9f0..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("AnalysisMain") -package org.jacodb.analysis - -import kotlinx.serialization.Serializable -import org.jacodb.analysis.engine.IfdsUnitRunnerFactory -import org.jacodb.analysis.engine.MainIfdsUnitManager -import org.jacodb.analysis.engine.SummaryStorage -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph - -typealias AnalysesOptions = Map - -@Serializable -data class AnalysisConfig(val analyses: Map) - - -/** - * This is the entry point for every analysis. - * Calling this function will find all vulnerabilities reachable from [methods]. - * - * @param graph instance of [JcApplicationGraph] that provides mixture of CFG and call graph - * (called supergraph in RHS95). - * Usually built by [newApplicationGraphForAnalysis]. - * - * @param unitResolver instance of [UnitResolver] which splits all methods into groups of methods, called units. - * Units are analyzed concurrently, one unit will be analyzed with one call to [IfdsUnitRunnerFactory.newRunner] method. - * In general, larger units mean more precise, but also more resource-consuming analysis, so [unitResolver] allows - * to reach compromise. - * It is guaranteed that [SummaryStorage] passed to all units is the same, so they can share information through it. - * However, the order of launching and terminating analysis for units is an implementation detail and may vary even for - * consecutive calls of this method with same arguments. - * - * @param ifdsUnitRunnerFactory an [IfdsUnitRunnerFactory] instance that will be launched for each unit. - * This is the main argument that defines the analysis. - * - * @param methods the list of method for analysis. - * Each vulnerability will only be reported if it is reachable from one of these. - * - * @param timeoutMillis the maximum time for analysis. - * Note that this does not include time for precalculations - * (like searching for reachable methods and splitting them into units) and postcalculations (like restoring traces), so - * the actual running time of this method may be longer. - */ -fun runAnalysis( - graph: JcApplicationGraph, - unitResolver: UnitResolver, - ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, - methods: List, - timeoutMillis: Long = Long.MAX_VALUE -): List { - return MainIfdsUnitManager(graph, unitResolver, ifdsUnitRunnerFactory, methods, timeoutMillis).analyze() -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index e7e533a45..2337204ea 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -16,11 +16,11 @@ package org.jacodb.analysis.config -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.paths.Maybe -import org.jacodb.analysis.paths.onSome -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toPath +import org.jacodb.analysis.taint.Tainted +import org.jacodb.analysis.ifds.Maybe +import org.jacodb.analysis.ifds.onSome +import org.jacodb.analysis.util.startsWith +import org.jacodb.analysis.ifds.toPath import org.jacodb.api.cfg.JcBool import org.jacodb.api.cfg.JcConstant import org.jacodb.api.cfg.JcInt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt index 6bb5c46e9..f660529d4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt @@ -16,12 +16,12 @@ package org.jacodb.analysis.config -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.analysis.paths.ElementAccessor -import org.jacodb.analysis.paths.Maybe -import org.jacodb.analysis.paths.fmap -import org.jacodb.analysis.paths.toMaybe -import org.jacodb.analysis.paths.toPathOrNull +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.ElementAccessor +import org.jacodb.analysis.ifds.Maybe +import org.jacodb.analysis.ifds.fmap +import org.jacodb.analysis.ifds.toMaybe +import org.jacodb.analysis.ifds.toPathOrNull import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstanceCallExpr diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt index c9aa007b5..fabc1987f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt @@ -16,11 +16,11 @@ package org.jacodb.analysis.config -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.analysis.paths.Maybe -import org.jacodb.analysis.paths.fmap -import org.jacodb.analysis.paths.map +import org.jacodb.analysis.taint.Tainted +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.Maybe +import org.jacodb.analysis.ifds.fmap +import org.jacodb.analysis.ifds.map import org.jacodb.taint.configuration.AssignMark import org.jacodb.taint.configuration.CopyAllMarks import org.jacodb.taint.configuration.CopyMark diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt deleted file mode 100644 index b2ba04806..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AbstractAnalyzer.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import org.jacodb.api.analysis.JcApplicationGraph -import java.util.concurrent.ConcurrentHashMap - -/** - * Handlers of [AbstractAnalyzer] produce some common events like new [SummaryEdgeFact]s, new [CrossUnitCallFact]s, etc. - * Inheritors may override all these handlers, and also they can extend them by calling the super-method and adding - * their own event - * - * @property verticesWithTraceGraphNeeded For all vertices added to this set, - * a [TraceGraphFact] will be produced at [handleIfdsResult] - * - * @property isMainAnalyzer Iff this property is set to true, handlers will - * 1. Produce [NewSummaryFact] events with [SummaryEdgeFact]s and [CrossUnitCallFact]s - * 2. Will produce [EdgeForOtherRunnerQuery] for each cross-unit call - * - * Usually this should be set to true for forward analyzers (which are expected to tell anything they found), - * but in backward analyzers this should be set to false - */ -abstract class AbstractAnalyzer( - protected val graph: JcApplicationGraph, -) : Analyzer { - protected val verticesWithTraceGraphNeeded: MutableSet = ConcurrentHashMap.newKeySet() - - abstract val isMainAnalyzer: Boolean - - /** - * If the edge is start-to-end and [isMainAnalyzer] is true, - * produces a [NewSummaryFact] with this summary edge. - * Otherwise, returns empty list. - */ - override fun handleNewEdge(edge: IfdsEdge): List { - return if (isMainAnalyzer && edge.to.statement in graph.exitPoints(edge.method)) { - listOf(NewSummaryFact(SummaryEdgeFact(edge))) - } else { - emptyList() - } - } - - /** - * If [isMainAnalyzer] is set to true, produces a [NewSummaryFact] with given [fact] - * and also produces [EdgeForOtherRunnerQuery] - */ - override fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List { - return if (isMainAnalyzer) { - verticesWithTraceGraphNeeded.add(fact.callerVertex) - listOf(NewSummaryFact(fact), EdgeForOtherRunnerQuery(IfdsEdge(fact.calleeVertex, fact.calleeVertex))) - } else { - emptyList() - } - } - - /** - * Produces trace graphs for all vertices added to [verticesWithTraceGraphNeeded] - */ - override fun handleIfdsResult(ifdsResult: IfdsResult): List { - val traceGraphs = verticesWithTraceGraphNeeded.map { - ifdsResult.resolveTraceGraph(it) - } - - return traceGraphs.map { - NewSummaryFact(TraceGraphFact(it)) - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt deleted file mode 100644 index e7bcc0742..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcInst - -/** - * Interface for flow functions -- mappings of kind DomainFact -> Collection of DomainFacts - */ -fun interface FlowFunctionInstance { - fun compute(fact: DomainFact): Collection -} - -/** - * An interface with which facts appearing in analyses should be marked - */ -interface DomainFact - -/** - * A special [DomainFact] that always holds - */ -object ZEROFact : DomainFact { - override fun toString() = "[ZERO fact]" -} - -/** - * Implementations of the interface should provide all four kinds of flow functions mentioned in RHS95, - * thus fully describing how the facts are propagated through the supergraph. - */ -interface FlowFunctionsSpace { - /** - * @return facts that may hold when analysis is started from [startStatement] - * (these are needed to initiate worklist in ifds analysis) - */ - fun obtainPossibleStartFacts(startStatement: JcInst): Collection - - fun obtainSequentFlowFunction( - current: JcInst, - next: JcInst, - ): FlowFunctionInstance - - fun obtainCallToStartFlowFunction( - callStatement: JcInst, - callee: JcMethod, - ): FlowFunctionInstance - - fun obtainCallToReturnFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - graph: JcApplicationGraph, - ): FlowFunctionInstance - - fun obtainExitToReturnSiteFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - exitStatement: JcInst, - ): FlowFunctionInstance -} - -/** - * [Analyzer] interface describes how facts are propagated and how [AnalysisDependentEvent]s are - * produced by these facts during the run of tabulation algorithm by [BaseIfdsUnitRunner]. - * - * Note that methods and properties of this interface may be accessed concurrently from different - * threads, so the implementations should be thread-safe. - * - * @property flowFunctions a [FlowFunctionsSpace] instance that describes how facts are generated - * and propagated during run of tabulation algorithm. - */ -interface Analyzer { - val flowFunctions: FlowFunctionsSpace - - /** - * This method is called by [BaseIfdsUnitRunner] each time a new path edge is found. - * - * @return [AnalysisDependentEvent]s that are produced by this edge. - * Usually these are [NewSummaryFact] events with [SummaryEdgeFact] or [VulnerabilityLocation] facts - */ - fun handleNewEdge(edge: IfdsEdge): List - - /** - * This method is called by [BaseIfdsUnitRunner] each time a new cross-unit called is observed. - * - * @return [AnalysisDependentEvent]s that are produced by this [fact]. - */ - fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List - - /** - * This method is called once by [BaseIfdsUnitRunner] when the propagation of facts is finished - * (normally or due to cancellation). - * - * @return [AnalysisDependentEvent]s that should be processed after the facts propagation was completed - * (usually these are some [NewSummaryFact]s). - */ - fun handleIfdsResult(ifdsResult: IfdsResult): List -} - -/** - * A functional interface that allows to produce [Analyzer] by [JcApplicationGraph]. - * - * It simplifies instantiation of [IfdsUnitRunnerFactory]s because this way you don't have to pass graph and reversed - * graph to analyzers' constructors directly, relying on runner to do it by itself. - */ -fun interface AnalyzerFactory { - fun newAnalyzer(graph: JcApplicationGraph): Analyzer -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt deleted file mode 100644 index 1007aa5ab..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BaseIfdsUnitRunnerFactory.kt +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.isActive -import kotlinx.coroutines.withContext -import mu.KotlinLogging -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.ext.cfg.callExpr -import java.util.concurrent.ConcurrentHashMap - -private val logger = KotlinLogging.logger {} - -/** - * This is a basic [IfdsUnitRunnerFactory], which creates one [BaseIfdsUnitRunner] for each [newRunner] call. - * - * @property analyzerFactory used to build [Analyzer] instance, which then will be used by launched [BaseIfdsUnitRunner]. - */ -class BaseIfdsUnitRunnerFactory( - private val analyzerFactory: AnalyzerFactory, -) : IfdsUnitRunnerFactory { - override fun newRunner( - graph: JcApplicationGraph, - manager: IfdsUnitManager, - unitResolver: UnitResolver, - unit: UnitType, - startMethods: List, - ): IfdsUnitRunner { - val analyzer = analyzerFactory.newAnalyzer(graph) - return BaseIfdsUnitRunner(graph, analyzer, manager, unitResolver, unit, startMethods) - } -} - -/** - * Encapsulates a launch of tabulation algorithm, described in RHS'95, for one unit. - */ -internal class BaseIfdsUnitRunner( - private val graph: JcApplicationGraph, - private val analyzer: Analyzer, - private val manager: IfdsUnitManager, - private val unitResolver: UnitResolver, - unit: UnitType, - private val startMethods: List, -) : AbstractIfdsUnitRunner(unit) { - - internal val pathEdges: MutableSet = ConcurrentHashMap.newKeySet() - private val summaryEdges: MutableMap> = mutableMapOf() - private val callSitesOf: MutableMap> = mutableMapOf() - private val pathEdgesPreds: MutableMap> = ConcurrentHashMap() - - private val flowSpace = analyzer.flowFunctions - - /** - * Queue containing all unprocessed path edges. - */ - private val workList = Channel(Channel.UNLIMITED) - - /** - * This method should be called each time new path edge is observed. - * It will check if the edge is new and, if success, add it to [workList] - * and summarize all [SummaryFact]s produces by the edge. - * - * @param edge the new path edge - * @param pred the description of predecessor of the edge - */ - private suspend fun propagate( - edge: IfdsEdge, - pred: PathEdgePredecessor, - ): Boolean { - require(unitResolver.resolve(edge.method) == unit) - - pathEdgesPreds.computeIfAbsent(edge) { - ConcurrentHashMap.newKeySet() - }.add(pred) - - if (pathEdges.add(edge)) { - workList.send(edge) - analyzer.handleNewEdge(edge).forEach { - manager.handleEvent(it, this) - } - return true - } - return false - } - - private val JcMethod.isExtern: Boolean - get() = unitResolver.resolve(this) != unit - - /** - * Implementation of tabulation algorithm, based on RHS'95. - * - * It slightly differs from the original in the following: - * - * - We do not analyze the whole supergraph (represented by [graph]), but only the methods that belong to our [unit]; - * - Path edges are added to [workList] not only by the main cycle, but they can also be obtained from [manager]; - * - By "summary edge" we understand the path edge from the start node of the method to its exit node. - * - The supergraph is explored dynamically, and we do not inverse flow functions when new summary edge is found, - * i.e. the extension from Chapter 4 of NLR'10 is implemented. - */ - private suspend fun runTabulationAlgorithm(): Unit = coroutineScope { - while (isActive) { - val currentEdge = workList.tryReceive().getOrNull() ?: run { - manager.handleEvent(QueueEmptinessChanged(true), this@BaseIfdsUnitRunner) - workList.receive().also { - manager.handleEvent(QueueEmptinessChanged(false), this@BaseIfdsUnitRunner) - } - } - - val (startVertex, currentVertex) = currentEdge - val (current, currentFact) = currentVertex - - val currentCallees = graph.callees(current).toList() - // val currentIsCall = currentCallees.isNotEmpty() - val currentIsCall = current.callExpr != null - val currentIsExit = current in graph.exitPoints(graph.methodOf(current)) - - if (currentIsCall) { - // Propagating through the call-to-return-site edge (lines 17-19 in RHS'95). - // - // START main :: (s, d1) - // | - // | (path edge) - // | - // CALL p :: (n, d2) - // : - // : (call-to-return-site edge) - // : - // RETURN FROM p :: (ret(n), d3) - // - // New path edge: - // (s -> n) + (n -> ret(n)) ==> (s -> ret(n)) - // - // Below: - // startVertex == (s, d1) - // currentVertex = (current, currentFact) == (n, d2) - // returnSiteVertex = (returnSite, returnSiteFact) == (ret(n), d3) - // newEdge == ((s,d1) -> (ret(n), d3)) - // - for (returnSite in graph.successors(current)) { - val factsAtReturnSite = flowSpace - .obtainCallToReturnFlowFunction(current, returnSite, graph) - .compute(currentFact) - for (returnSiteFact in factsAtReturnSite) { - val returnSiteVertex = IfdsVertex(returnSite, returnSiteFact) - val newEdge = IfdsEdge(startVertex, returnSiteVertex) - val predecessor = PathEdgePredecessor(currentEdge, PredecessorKind.Sequent) - propagate(newEdge, predecessor) - } - } - - // Propagating through the call. - // - // START main :: (s, d1) - // | - // CALL p :: (n, d2) - // : \ - // : \ - // : START p :: (s_p, d3) - // : | - // : EXIT p :: (e_p, d4) - // : / - // : / - // RETURN FROM p :: (ret(n), d5) - // - // New path edge: - // (s -> n) + (n -> s_p) + (s_p ~> e_p) + (e_p -> ret(n)) ==> (s -> ret(n)) - // - // Below: - // startVertex == (s, d1) - // currentVertex = (current, currentFact) == (n, d2) - // calleeStartVertex = (calleeStart, calleeStartFact) == (s_p, d3) - // exitVertex = (exit, exitFact) == (e_p, d4) - // returnSiteVertex = (returnSite, returnSiteFact) == (ret(n), d5) - // newEdge == ((s, d1) -> (ret(n), d5)) - // - for (callee in currentCallees) { - val factsAtCalleeStart = flowSpace - .obtainCallToStartFlowFunction(current, callee) - .compute(currentFact) - for (calleeStartFact in factsAtCalleeStart) { - for (calleeStart in graph.entryPoints(callee)) { - val calleeStartVertex = IfdsVertex(calleeStart, calleeStartFact) - - // Handle callee exit vertex: - val handleExitVertex: suspend (IfdsVertex) -> Unit = - { (exit, exitFact) -> - val exitVertex = IfdsVertex(exit, exitFact) - for (returnSite in graph.successors(current)) { - val finalFacts = flowSpace - .obtainExitToReturnSiteFlowFunction(current, returnSite, exit) - .compute(exitFact) - for (returnSiteFact in finalFacts) { - val returnSiteVertex = IfdsVertex(returnSite, returnSiteFact) - val newEdge = IfdsEdge(startVertex, returnSiteVertex) - val summaryEdge = IfdsEdge(calleeStartVertex, exitVertex) - val predecessor = PathEdgePredecessor( - currentEdge, - PredecessorKind.ThroughSummary(summaryEdge) - ) - propagate(newEdge, predecessor) - } - } - } - - if (callee.isExtern) { - // Notify about the cross-unit call: - analyzer - .handleNewCrossUnitCall(CrossUnitCallFact(currentVertex, calleeStartVertex)) - .forEach { event -> - manager.handleEvent(event, this@BaseIfdsUnitRunner) - } - - // Wait (asynchronously, via Flow) for summary edges and handle them: - val summaries = flow { - val event = SubscriptionForSummaryEdges(callee, this@flow) - manager.handleEvent(event, this@BaseIfdsUnitRunner) - } - summaries - .filter { it.from == calleeStartVertex } - .map { it.to } - .onEach(handleExitVertex) - .launchIn(this) - } else { - // Save info about the call for summary-facts that will be found later: - callSitesOf.getOrPut(calleeStartVertex) { mutableSetOf() }.add(currentEdge) - - // Initiate analysis for callee: - val newEdge = IfdsEdge(calleeStartVertex, calleeStartVertex) - val predecessor = PathEdgePredecessor(currentEdge, PredecessorKind.CallToStart) - propagate(newEdge, predecessor) - - // Handle already-found summary edges: - // Note: `.toList()` is needed below to avoid ConcurrentModificationException - val exits = summaryEdges[calleeStartVertex].orEmpty().toList() - for (vertex in exits) { - handleExitVertex(vertex) - } - } - } - } - } - } else { - if (currentIsExit) { - // Propagating through the newly found summary edge (lines 22-31 of RHS'95). - // - // START outer :: (s_c, d3) - // | - // CALL p :: (c, d4) - // : \ - // : \ - // : START p :: (s_p, d1) - // : | - // : EXIT p :: (e_p, d2) - // : / - // : / - // RETURN FROM p :: (ret(c), d5) - // - // New path edge: - // (s -> e) + (c -> s_p) + (c -> ret(c)) + (s_c -> c) ==> (s_c -> ret(c)) - // - // Below: - // startVertex == (s_p, d1) - // currentVertex = (current, currentFact) == (e_p, d2) - // callerPathEdge == ((s_c, d3) -> (c, d4)) - // callerVertex = (caller, callerFact) == (c, d4) - // returnSiteVertex = (returnSite, returnSiteFact) == (ret(c), d5) - // newEdge == ((s_p, d3) -> (ret(n), d5)) - // - // TODO: rewrite this in a more reactive way (?) - for (callerPathEdge in callSitesOf[startVertex].orEmpty()) { - val caller = callerPathEdge.to.statement - for (returnSite in graph.successors(caller)) { - val factsAtReturnSite = flowSpace - .obtainExitToReturnSiteFlowFunction(caller, returnSite, current) - .compute(currentFact) - for (returnSiteFact in factsAtReturnSite) { - val returnSiteVertex = IfdsVertex(returnSite, returnSiteFact) - val newEdge = IfdsEdge(callerPathEdge.from, returnSiteVertex) - val predecessor = PathEdgePredecessor( - callerPathEdge, - PredecessorKind.ThroughSummary(currentEdge) - ) - propagate(newEdge, predecessor) - } - } - } - summaryEdges.getOrPut(startVertex) { mutableSetOf() }.add(currentVertex) - } - - // Simple propagation through the intra-procedural edge (lines 34-36 of RHS'95). - // Note that generally speaking, exit vertices may have successors (in case of exceptional flow, etc.), - // so this part should be done for exit vertices as well. - // - // START main :: (s, d1) - // | - // | (path edge) - // | - // INSTRUCTION :: (n, d2) - // | - // | (path edge) - // | - // INSTRUCTION :: (m, d3) - // - // New path edge: - // (s -> n) + (n -> m) ==> (s -> m) - // - // Below: - // startVertex == (s, d1) - // currentVertex == (current, currentFact) == (n, d2) - // nextVertex == (next, nextFact) == (m, d3) - // newEdge = (startVertex, nextVertex) == ((s,d1) -> (m, d3)) - // - for (next in graph.successors(current)) { - val factsAtNext = flowSpace - .obtainSequentFlowFunction(current, next) - .compute(currentFact) - for (nextFact in factsAtNext) { - val nextVertex = IfdsVertex(next, nextFact) - val newEdge = IfdsEdge(startVertex, nextVertex) - val predecessor = PathEdgePredecessor(currentEdge, PredecessorKind.Sequent) - propagate(newEdge, predecessor) - } - } - } - } - } - - private val ifdsResult: IfdsResult by lazy { - val allEdges = pathEdges.toList() - val resultFacts = allEdges - .groupBy({ it.to.statement }, { it.to.domainFact }) - .mapValues { (_, facts) -> facts.toSet() } - IfdsResult(allEdges, resultFacts, pathEdgesPreds) - } - - /** - * Performs some initialization and runs the tabulation algorithm, sending all relevant events to the [manager]. - */ - override suspend fun run() = coroutineScope { - try { - // Add initial facts to workList: - for (method in startMethods) { - require(unitResolver.resolve(method) == unit) - for (startStatement in graph.entryPoints(method)) { - val startFacts = flowSpace.obtainPossibleStartFacts(startStatement) - for (startFact in startFacts) { - val vertex = IfdsVertex(startStatement, startFact) - val edge = IfdsEdge(vertex, vertex) // loop - val predecessor = PathEdgePredecessor(edge, PredecessorKind.NoPredecessor) - propagate(edge, predecessor) - } - } - } - - // Run the tabulation algorithm: - runTabulationAlgorithm() - } finally { - logger.info { "Finishing ${this@BaseIfdsUnitRunner} for $unit" } - logger.info { "Total ${pathEdges.size} path edges for $unit using $analyzer" } - - // Post-process left-over events: - withContext(NonCancellable) { - analyzer.handleIfdsResult(ifdsResult).forEach { - manager.handleEvent(it, this@BaseIfdsUnitRunner) - } - } - } - } - - override suspend fun submitNewEdge(edge: IfdsEdge) { - val predecessor = PathEdgePredecessor(edge, PredecessorKind.Unknown) - propagate(edge, predecessor) - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt deleted file mode 100644 index d3ab439c1..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/BidiIfdsUnitRunnerFactory.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import org.jacodb.analysis.graph.BackwardJcApplicationGraph -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph - -/** - * This factory produces composite runners. Each of them launches two runners (backward and forward) - * on the same unit with the same startMethods. - * - * Backward runner is launched on the reversed application graph, - * while forward runner is launched on the direct graph. - * - * Both runners will be given their own managers with the following policy: - * - all [NewSummaryFact] events are delegated to the outer manager - * - [EdgeForOtherRunnerQuery] events are submitted to the other runner if the corresponding edge - * belongs to the same unit, otherwise they are transmitted to the outer manager (for forward runner) - * or ignored (for backward runner) - * - Queue is thought to be empty when queues of both forward and backward runners are empty. - * The [QueueEmptinessChanged] event is sent to outer manager correspondingly. - * - [SubscriptionForSummaryEdges] event is delegated to the outer manager for forward runner and - * is ignored for backward runner - * - * @param forwardRunnerFactory a factory that produces forward runner for each [newRunner] call - * @param backwardRunnerFactory a factory that produces backward runner for each [newRunner] call - * @param isParallel if true, the produced composite runner will launch backward and forward runners in parallel. - * Otherwise, the backward runner will be executed first, and after it, the forward runner will be executed. - */ -class BidiIfdsUnitRunnerFactory( - private val forwardRunnerFactory: IfdsUnitRunnerFactory, - private val backwardRunnerFactory: IfdsUnitRunnerFactory, - private val isParallel: Boolean = true, -) : IfdsUnitRunnerFactory { - - override fun newRunner( - graph: JcApplicationGraph, - manager: IfdsUnitManager, - unitResolver: UnitResolver, - unit: UnitType, - startMethods: List, - ): IfdsUnitRunner = BidiIfdsUnitRunner(graph, manager, unitResolver, unit, startMethods) - - internal inner class BidiIfdsUnitRunner( - graph: JcApplicationGraph, - private val manager: IfdsUnitManager, - private val unitResolver: UnitResolver, - unit: UnitType, - startMethods: List, - ) : AbstractIfdsUnitRunner(unit) { - - @Volatile - private var forwardQueueIsEmpty: Boolean = false - - @Volatile - private var backwardQueueIsEmpty: Boolean = false - - private val forwardManager: IfdsUnitManager = object : IfdsUnitManager by manager { - override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { - when (event) { - is EdgeForOtherRunnerQuery -> { - if (unitResolver.resolve(event.edge.method) == unit) { - // Submit new edge directly to the backward runner: - backwardRunner.submitNewEdge(event.edge) - } else { - // Submit new edge via the manager: - manager.handleEvent(event, this@BidiIfdsUnitRunner) - } - } - - is NewSummaryFact -> { - manager.handleEvent(event, this@BidiIfdsUnitRunner) - } - - is QueueEmptinessChanged -> { - forwardQueueIsEmpty = event.isEmpty - val newEvent = QueueEmptinessChanged(backwardQueueIsEmpty && forwardQueueIsEmpty) - manager.handleEvent(newEvent, this@BidiIfdsUnitRunner) - } - - is SubscriptionForSummaryEdges -> { - manager.handleEvent(event, this@BidiIfdsUnitRunner) - } - } - } - } - - private val backwardManager: IfdsUnitManager = object : IfdsUnitManager { - override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { - when (event) { - is EdgeForOtherRunnerQuery -> { - if (unitResolver.resolve(event.edge.method) == unit) { - forwardRunner.submitNewEdge(event.edge) - } - } - - is NewSummaryFact -> { - manager.handleEvent(event, this@BidiIfdsUnitRunner) - } - - is QueueEmptinessChanged -> { - backwardQueueIsEmpty = event.isEmpty - if (!isParallel && event.isEmpty) { - runner.job?.cancel() ?: error("Runner job is not instantiated") - } - val newEvent = QueueEmptinessChanged(backwardQueueIsEmpty && forwardQueueIsEmpty) - manager.handleEvent(newEvent, this@BidiIfdsUnitRunner) - } - - is SubscriptionForSummaryEdges -> {} - } - } - } - - internal val backwardRunner: IfdsUnitRunner = backwardRunnerFactory - .newRunner(BackwardJcApplicationGraph(graph), backwardManager, unitResolver, unit, startMethods) - - internal val forwardRunner: IfdsUnitRunner = forwardRunnerFactory - .newRunner(graph, forwardManager, unitResolver, unit, startMethods) - - override suspend fun submitNewEdge(edge: IfdsEdge) { - forwardRunner.submitNewEdge(edge) - } - - override suspend fun run() = coroutineScope { - val backwardRunnerJob: Job = backwardRunner.launchIn(this) - val forwardRunnerJob: Job - - if (isParallel) { - forwardRunnerJob = forwardRunner.launchIn(this) - - backwardRunnerJob.join() - forwardRunnerJob.join() - } else { - backwardRunnerJob.join() - - forwardRunnerJob = forwardRunner.launchIn(this) - forwardRunnerJob.join() - } - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt deleted file mode 100644 index 595321ca4..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsEdge.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import org.jacodb.api.JcMethod - -/** - * Represents a directed (from [from] to [to]) edge between two ifds vertices - */ -data class IfdsEdge( - val from: IfdsVertex, - val to: IfdsVertex, -) { - init { - require(from.method == to.method) - } - - val method: JcMethod - get() = from.method -} - -sealed interface PredecessorKind { - object NoPredecessor : PredecessorKind - object Unknown : PredecessorKind - object Sequent : PredecessorKind - object CallToStart : PredecessorKind - class ThroughSummary(val summaryEdge: IfdsEdge) : PredecessorKind -} - -/** - * Contains info about predecessor of path edge. - * Used mainly to restore traces. - */ -data class PathEdgePredecessor( - val predEdge: IfdsEdge, - val kind: PredecessorKind -) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt deleted file mode 100644 index ce6c3761d..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsResult.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import org.jacodb.api.cfg.JcInst - -/** - * Aggregates all facts and edges found by tabulation algorithm - */ -class IfdsResult( - pathEdgeList: List, - val resultFacts: Map>, - val pathEdgesPreds: Map>, -) { - private val pathEdges = pathEdgeList.groupBy { it.to } - - private inner class TraceGraphBuilder(private val sink: IfdsVertex) { - private val sources: MutableSet = mutableSetOf() - private val edges: MutableMap> = mutableMapOf() - private val visited: MutableSet = mutableSetOf() - - private fun addEdge(from: IfdsVertex, to: IfdsVertex) { - if (from != to) { - edges.getOrPut(from) { mutableSetOf() }.add(to) - } - } - - private fun dfs(e: IfdsEdge, lastVertex: IfdsVertex, stopAtMethodStart: Boolean) { - if (e in visited) { - return - } - - visited.add(e) - - if (stopAtMethodStart && e.from == e.to) { - addEdge(e.from, lastVertex) - return - } - - val (_, v) = e - if (v.domainFact == ZEROFact) { - addEdge(v, lastVertex) - sources.add(v) - return - } - - for (pred in pathEdgesPreds[e].orEmpty()) { - when (pred.kind) { - is PredecessorKind.CallToStart -> { - if (!stopAtMethodStart) { - addEdge(pred.predEdge.to, lastVertex) - dfs(pred.predEdge, pred.predEdge.to, false) - } - } - - is PredecessorKind.Sequent -> { - if (pred.predEdge.to.domainFact == v.domainFact) { - dfs(pred.predEdge, lastVertex, stopAtMethodStart) - } else { - addEdge(pred.predEdge.to, lastVertex) - dfs(pred.predEdge, pred.predEdge.to, stopAtMethodStart) - } - } - - is PredecessorKind.ThroughSummary -> { - val summaryEdge = pred.kind.summaryEdge - addEdge(summaryEdge.to, lastVertex) // Return to next vertex - addEdge(pred.predEdge.to, summaryEdge.from) // Call to start - dfs(summaryEdge, summaryEdge.to, true) // Expand summary edge - dfs(pred.predEdge, pred.predEdge.to, stopAtMethodStart) // Continue normal analysis - } - - is PredecessorKind.Unknown -> { - addEdge(pred.predEdge.to, lastVertex) - if (pred.predEdge.from != pred.predEdge.to) { - // TODO: ideally, we should analyze the place from which the edge was given to ifds, - // for now we just go to method start - dfs(IfdsEdge(pred.predEdge.from, pred.predEdge.from), pred.predEdge.to, stopAtMethodStart) - } - } - - is PredecessorKind.NoPredecessor -> { - sources.add(v) - addEdge(pred.predEdge.to, lastVertex) - } - } - } - } - - fun build(): TraceGraph { - val initEdges = pathEdges[sink].orEmpty() - initEdges.forEach { - dfs(it, it.to, false) - } - return TraceGraph(sink, sources, edges) - } - } - - /** - * Builds a graph with traces to given [vertex]. - */ - fun resolveTraceGraph(vertex: IfdsVertex): TraceGraph { - return TraceGraphBuilder(vertex).build() - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt deleted file mode 100644 index aa9965752..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import kotlinx.coroutines.flow.FlowCollector -import org.jacodb.api.JcMethod - -/** - * Implementations of this interface manage one or more runners and should be responsible for: - * - communication between different runners, i.e. they should submit received - * [EdgeForOtherRunnerQuery] to proper runners via [IfdsUnitRunner.submitNewEdge] call - * - providing runners with summaries for other units - * - saving the [NewSummaryFact]s produced by runners - * - managing lifecycles of the launched runners - */ -interface IfdsUnitManager { - suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) -} - -// TODO: provide visitor for this interface -sealed interface IfdsUnitRunnerEvent - -data class QueueEmptinessChanged(val isEmpty: Boolean) : IfdsUnitRunnerEvent - -/** - * @property method the method for which summary edges the subscription is queried - * @property collector the [FlowCollector] to which queried summary edges should be sent to, - * somewhat similar to a callback - */ -data class SubscriptionForSummaryEdges( - val method: JcMethod, - val collector: FlowCollector, -) : IfdsUnitRunnerEvent - -/** - * A common interface for all events that are allowed to be produced by [Analyzer] - * (all others may be produced only in runners directly) - */ -sealed interface AnalysisDependentEvent : IfdsUnitRunnerEvent - -data class NewSummaryFact(val fact: SummaryFact) : AnalysisDependentEvent -data class EdgeForOtherRunnerQuery(val edge: IfdsEdge) : AnalysisDependentEvent diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt deleted file mode 100644 index 96546d52c..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsUnitRunnerFactory.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph - -/** - * Represents a runner and allows to manipulate it. - * - * By convention, runners are created by instances of [IfdsUnitRunnerFactory], - * but this creation doesn't launch anything. - * The work itself is launched by [launchIn] method, which should be called exactly once. - * This method returns a [Job] instance representing a launched coroutine. - * This [Job] could also be further obtained via [job] property. - * - * It is not recommended to implement this interface directly, instead, - * [AbstractIfdsUnitRunner] should be extended. - */ -interface IfdsUnitRunner { - val unit: UnitType - val job: Job? - - fun launchIn(scope: CoroutineScope): Job - - /** - * Submits a new [IfdsEdge] to runner's queue. Should be called only after [launchIn]. - * Note that this method can be called from different threads. - */ - suspend fun submitNewEdge(edge: IfdsEdge) -} - -/** - * [AbstractIfdsUnitRunner] contains proper implementation of [launchIn] method and [job] property. - * Inheritors should only implement [submitNewEdge] and a suspendable [run] method. - * The latter is the main method of runner, that should do all its work. - */ -abstract class AbstractIfdsUnitRunner( - final override val unit: UnitType, -) : IfdsUnitRunner { - /** - * The main method of the runner, which will be called by [launchIn] - */ - protected abstract suspend fun run() - - private var _job: Job? = null - final override val job: Job? get() = _job - - final override fun launchIn(scope: CoroutineScope): Job = scope.launch(start = CoroutineStart.LAZY) { - run() - }.also { - _job = it - it.start() - } -} - -/** - * Produces a runner for any given unit. - */ -interface IfdsUnitRunnerFactory { - /** - * Produces a runner for given [unit], using given [startMethods] as entry points. - * All start methods should belong to the [unit]. - * Note that this method DOES NOT START runner's job. - * - * @param graph provides supergraph for application (including methods, belonging to other units) - * - * @param manager [IfdsUnitManager] instance that will manage the produced runner. - * - * @param unitResolver will be used to get units of methods observed during analysis. - */ - fun newRunner( - graph: JcApplicationGraph, - manager: IfdsUnitManager, - unitResolver: UnitResolver, - unit: UnitType, - startMethods: List, - ): IfdsUnitRunner -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt deleted file mode 100644 index 8d3729c65..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/MainIfdsUnitManager.kt +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.isActive -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull -import mu.KotlinLogging -import org.jacodb.analysis.runAnalysis -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import java.util.concurrent.ConcurrentHashMap - -private val logger = KotlinLogging.logger {} - -/** - * This manager launches and manages [IfdsUnitRunner]s for all units, reachable from [startMethods]. - * It also merges [TraceGraph]s from different units giving a complete [TraceGraph] for each vulnerability. - * See [runAnalysis] for more info. - */ -class MainIfdsUnitManager( - private val graph: JcApplicationGraph, - private val unitResolver: UnitResolver, - private val ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, - private val startMethods: List, - private val timeoutMillis: Long, -) : IfdsUnitManager { - - private val foundMethods: MutableMap> = mutableMapOf() - private val crossUnitCallers: MutableMap> = mutableMapOf() - - private val summaryEdgesStorage = SummaryStorageImpl() - private val tracesStorage = SummaryStorageImpl() - private val crossUnitCallsStorage = SummaryStorageImpl() - private val vulnerabilitiesStorage = SummaryStorageImpl() - - private val aliveRunners: MutableMap = ConcurrentHashMap() - private val queueEmptiness: MutableMap = mutableMapOf() - private val dependencies: MutableMap> = mutableMapOf() - private val dependenciesRev: MutableMap> = mutableMapOf() - - private fun addStart(method: JcMethod) { - val unit = unitResolver.resolve(method) - // TODO: remove this unnecessary if-condition (superseded by '.add()' below): - if (method in foundMethods[unit].orEmpty()) { - return - } - - foundMethods.getOrPut(unit) { mutableSetOf() }.add(method) - } - - private val IfdsVertex.traceGraph: TraceGraph - get() = tracesStorage - .getCurrentFacts(method) - .map { it.graph } - .singleOrNull { it.sink == this } - ?: TraceGraph.bySink(this) - - /** - * Launches [IfdsUnitRunner] for each observed unit, handles respective jobs, - * and gathers results into list of vulnerabilities, restoring full traces - */ - fun analyze(): List = runBlocking(Dispatchers.Default) { - withTimeoutOrNull(timeoutMillis) { - logger.info { "Searching for units to analyze..." } - startMethods.forEach { - ensureActive() - addStart(it) - } - - val allUnits = foundMethods.keys.toList() - logger.info { "Starting analysis. Number of found units: ${allUnits.size}" } - - val progressLoggerJob = launch { - while (isActive) { - delay(1000) - val totalCount = allUnits.size - val aliveCount = aliveRunners.size - - logger.info { - "Current progress: ${totalCount - aliveCount} / $totalCount units completed" - } - } - } - - launch { - dispatchDependencies() - } - - // TODO: do smth smarter here - val allJobs = allUnits.map { unit -> - val runner = ifdsUnitRunnerFactory.newRunner( - graph, - this@MainIfdsUnitManager, - unitResolver, - unit, - foundMethods[unit]!!.toList() - ) - aliveRunners[unit] = runner - runner.launchIn(this) - } - - allJobs.joinAll() - eventChannel.close() - progressLoggerJob.cancel() - } - - logger.info { "All jobs completed, gathering results..." } - - val foundVulnerabilities = vulnerabilitiesStorage.knownMethods.flatMap { method -> - vulnerabilitiesStorage.getCurrentFacts(method) - } - - foundMethods.values.flatten().forEach { method -> - for (crossUnitCall in crossUnitCallsStorage.getCurrentFacts(method)) { - val calledMethod = graph.methodOf(crossUnitCall.calleeVertex.statement) - crossUnitCallers.getOrPut(calledMethod) { mutableSetOf() }.add(crossUnitCall) - } - } - - logger.info { "Restoring traces..." } - - foundVulnerabilities - .map { VulnerabilityInstance(it, extendTraceGraph(it.sink.traceGraph)) } - .filter { - it.traceGraph.sources.any { source -> - graph.methodOf(source.statement) in startMethods || source.domainFact == ZEROFact - } - } - } - - private val TraceGraph.methods: List - get() { - return (edges.keys.map { graph.methodOf(it.statement) } + - listOf(graph.methodOf(sink.statement))).distinct() - } - - /** - * Given a [traceGraph], searches for other traceGraphs (from different units) - * and merges them into given if they extend any path leading to sink. - * - * This method allows to restore traces that pass through several units. - */ - private fun extendTraceGraph(traceGraph: TraceGraph): TraceGraph { - var result = traceGraph - val methodQueue: MutableSet = traceGraph.methods.toMutableSet() - val addedMethods: MutableSet = methodQueue.toMutableSet() - while (methodQueue.isNotEmpty()) { - val method = methodQueue.first() - methodQueue.remove(method) - for (callFact in crossUnitCallers[method].orEmpty()) { - // TODO: merge calleeVertices here - val sFacts = setOf(callFact.calleeVertex) - val upGraph = callFact.callerVertex.traceGraph - val newValue = result.mergeWithUpGraph(upGraph, sFacts) - if (result != newValue) { - result = newValue - for (nMethod in upGraph.methods) { - if (nMethod !in addedMethods) { - addedMethods.add(nMethod) - methodQueue.add(nMethod) - } - } - } - } - } - return result - } - - override suspend fun handleEvent(event: IfdsUnitRunnerEvent, runner: IfdsUnitRunner) { - when (event) { - is EdgeForOtherRunnerQuery -> { - check(event.edge.from == event.edge.to) { "Edge for other runner must be a loop-edge" } - val method = event.edge.method - val unit = unitResolver.resolve(method) - val otherRunner = aliveRunners[unit] ?: return - if (otherRunner.job?.isActive == true) { - otherRunner.submitNewEdge(event.edge) - } - } - - is NewSummaryFact -> { - when (val fact = event.fact) { - is CrossUnitCallFact -> crossUnitCallsStorage.add(fact) - is SummaryEdgeFact -> summaryEdgesStorage.add(fact) - is TraceGraphFact -> tracesStorage.add(fact) - is VulnerabilityLocation -> vulnerabilitiesStorage.add(fact) - else -> error("Unexpected $fact") - } - } - - is QueueEmptinessChanged -> { - eventChannel.send(Pair(event, runner)) - } - - is SubscriptionForSummaryEdges -> { - eventChannel.send(Pair(event, runner)) - summaryEdgesStorage - .getFacts(event.method) - .map { it.edge } - .collect(event.collector) - } - } - } - - // Used to linearize all events that change dependencies or queue emptiness of runners - private val eventChannel: Channel> = - Channel(capacity = Int.MAX_VALUE) - - // TODO: replace async dispatcher with a synchronous one - private suspend fun dispatchDependencies() = coroutineScope { - eventChannel.consumeEach { (event, runner) -> - when (event) { - is SubscriptionForSummaryEdges -> { - dependencies.getOrPut(runner.unit) { mutableSetOf() } - .add(unitResolver.resolve(event.method)) - dependenciesRev.getOrPut(unitResolver.resolve(event.method)) { mutableSetOf() } - .add(runner.unit) - } - - is QueueEmptinessChanged -> { - if (runner.unit !in aliveRunners) { - return@consumeEach - } - queueEmptiness[runner.unit] = event.isEmpty - if (event.isEmpty) { - logger.info { "Stopping the runner for ${runner.unit}..." } - val toDelete = mutableListOf(runner.unit) - while (toDelete.isNotEmpty()) { - val current = toDelete.removeLast() - if (current in aliveRunners && - dependencies[runner.unit].orEmpty().all { queueEmptiness[it] != false } - ) { - val runner = aliveRunners[current] - if (runner == null) continue - runner.job?.cancel() ?: error("Runner's job is not instantiated") - aliveRunners.remove(current) - for (next in dependenciesRev[current].orEmpty()) { - if (queueEmptiness[next] == true) { - toDelete.add(next) - } - } - } - } - } - } - - else -> error("Unexpected event for dependencies dispatcher") - } - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt deleted file mode 100644 index aa7157693..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/Utils.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -import org.jacodb.analysis.ifds2.taint.TaintFact -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.ifds2.taint.Zero -import org.jacodb.analysis.library.analyzers.NpeTaintNode -import org.jacodb.analysis.library.analyzers.TaintAnalysisNode - -fun TaintFact.toDomainFact(): DomainFact = when (this) { - Zero -> ZEROFact - - is Tainted -> { - when (mark.name) { - "NPE" -> NpeTaintNode(variable) - else -> TaintAnalysisNode(variable, nodeType = mark.name) - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt deleted file mode 100644 index 37fdafcfa..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/VulnerabilityInstance.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.engine - -/** - * Represents a vulnerability (issue) found by analysis - * - * @property vulnerabilityDescription type of vulnerability as a string (e.g. "Possible NPE", "Unused variable") - * @property traceGraph contains sink, sources and traces that lead to occurrence of vulnerability - */ -data class VulnerabilityInstance( - val location: VulnerabilityLocation, - val traceGraph: TraceGraph, -) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt similarity index 65% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt index e8351f90c..e6f8d5641 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/AccessPath.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt @@ -14,17 +14,22 @@ * limitations under the License. */ -package org.jacodb.analysis.paths +package org.jacodb.analysis.ifds import org.jacodb.api.JcField -import org.jacodb.api.JcTypedField +import org.jacodb.api.cfg.JcArrayAccess +import org.jacodb.api.cfg.JcCastExpr +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcSimpleValue +import org.jacodb.api.cfg.JcValue /** * This class is used to represent an access path that is needed for problems - * where dataflow facts could be correlated with variables/values (such as NPE, uninitialized variable, etc.) + * where dataflow facts could be correlated with variables/values + * (such as NPE, uninitialized variable, etc.) */ -data class AccessPath private constructor( +data class AccessPath internal constructor( val value: JcSimpleValue?, // null for static field val accesses: List, ) { @@ -63,6 +68,12 @@ data class AccessPath private constructor( return AccessPath(value, this.accesses + accessor) } + operator fun minus(other: AccessPath): List? { + if (value != other.value) return null + if (accesses.take(other.accesses.size) != other.accesses) return null + return accesses.drop(other.accesses.size) + } + override fun toString(): String { return value.toString() + accesses.joinToString("") { it.toSuffix() } } @@ -70,14 +81,38 @@ data class AccessPath private constructor( companion object { fun from(value: JcSimpleValue): AccessPath = AccessPath(value, emptyList()) - fun fromStaticField(field: JcField): AccessPath { + fun from(field: JcField): AccessPath { require(field.isStatic) { "Expected static field" } return AccessPath(null, listOf(FieldAccessor(field))) } + } +} - fun fromStaticField(field: JcTypedField): AccessPath { - require(field.isStatic) { "Expected static field" } - return AccessPath(null, listOf(FieldAccessor(field.field))) +fun JcExpr.toPathOrNull(): AccessPath? = when (this) { + is JcSimpleValue -> AccessPath.from(this) + + is JcCastExpr -> operand.toPathOrNull() + + is JcArrayAccess -> { + array.toPathOrNull()?.let { + it / ElementAccessor(index) } } + + is JcFieldRef -> { + val instance = instance + if (instance == null) { + AccessPath.from(field.field) + } else { + instance.toPathOrNull()?.let { + it / FieldAccessor(field.field) + } + } + } + + else -> null +} + +fun JcValue.toPath(): AccessPath { + return toPathOrNull() ?: error("Unable to build access path for value $this") } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt similarity index 78% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt index 4b6f5b366..85f4d45df 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Accessors.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.paths +package org.jacodb.analysis.ifds import org.jacodb.api.JcField import org.jacodb.api.cfg.JcValue @@ -35,18 +35,6 @@ data class FieldAccessor( } } -// data class ElementAccessor( -// val index: JcValue?, // null if "any" -// ) : Accessor { -// override fun toSuffix(): String { -// return if (index == null) "[*]" else "[$index]" -// } -// -// override fun toString(): String { -// return "[$index]" -// } -// } - object ElementAccessor : Accessor { override fun toSuffix(): String { return "[*]" diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt similarity index 97% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt index 689dd01fa..3824e3952 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Aggregate.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt @@ -14,16 +14,17 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 +package org.jacodb.analysis.ifds -import org.jacodb.analysis.ifds2.taint.Zero +import org.jacodb.analysis.taint.Zero +import org.jacodb.api.cfg.JcInst /** * Aggregates all facts and edges found by the tabulation algorithm. */ class Aggregate( pathEdges: Collection>, - val facts: Map>, + val facts: Map>, val reasons: Map, Set>, ) { private val pathEdgesBySink: Map, Collection>> = diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Analyzer.kt similarity index 93% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Analyzer.kt index f2b197b00..f38a9be90 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Analyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Analyzer.kt @@ -14,9 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 - -import org.jacodb.api.JcMethod +package org.jacodb.analysis.ifds interface Analyzer { val flowFunctions: FlowFunctions diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Edge.kt similarity index 77% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Edge.kt index 0e1605273..7492be3ea 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/IfdsVertex.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Edge.kt @@ -14,12 +14,18 @@ * limitations under the License. */ -package org.jacodb.analysis.engine +package org.jacodb.analysis.ifds import org.jacodb.api.JcMethod -import org.jacodb.api.cfg.JcInst -data class IfdsVertex(val statement: JcInst, val domainFact: DomainFact) { +data class Edge( + val from: Vertex, + val to: Vertex, +) { + init { + require(from.method == to.method) + } + val method: JcMethod - get() = statement.location.method + get() = from.method } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/FlowFunctions.kt similarity index 98% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/FlowFunctions.kt index d941a45c8..7a93eaee7 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/FlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/FlowFunctions.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 +package org.jacodb.analysis.ifds import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Manager.kt similarity index 97% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Manager.kt index 649f19181..cc7b2c897 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Manager.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 +package org.jacodb.analysis.ifds import kotlinx.coroutines.CoroutineScope import org.jacodb.api.JcMethod diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt similarity index 98% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt index 24eb89839..164564730 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Maybe.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.paths +package org.jacodb.analysis.ifds @JvmInline value class Maybe private constructor( diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt similarity index 78% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt index 4d4db2497..fcf27936b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Edge.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt @@ -14,24 +14,9 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 - -import org.jacodb.api.JcMethod - -data class Edge( - val from: Vertex, - val to: Vertex, -) { - init { - require(from.method == to.method) - } - - val method: JcMethod - get() = from.method -} +package org.jacodb.analysis.ifds sealed class Reason { - object Initial : Reason() object External : Reason() @@ -48,5 +33,4 @@ sealed class Reason { val edge: Edge, val summaryEdge: Edge, ) : Reason() - } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt similarity index 88% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt index 7417e88c8..739d8929a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt @@ -14,45 +14,31 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 +package org.jacodb.analysis.ifds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.getOrElse import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive -import mu.KotlinLogging -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.UnitType -import org.jacodb.analysis.ifds2.taint.BidiRunner -import org.jacodb.analysis.ifds2.taint.Zero +import org.jacodb.analysis.graph.JcNoopInst +import org.jacodb.analysis.taint.Zero import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.cfg.callExpr import java.util.concurrent.ConcurrentHashMap -private val logger = KotlinLogging.logger {} - -typealias Method = JcMethod -typealias Statement = JcInst +private val logger = mu.KotlinLogging.logger {} interface Runner { val unit: UnitType - suspend fun run(startMethods: List) + suspend fun run(startMethods: List) fun submitNewEdge(edge: Edge) fun getAggregate(): Aggregate } -@Suppress("RecursivePropertyAccessor") -val Runner<*>.pathEdges: Set> - get() = when (this) { - is UniRunner<*, *> -> pathEdges - is BidiRunner -> forwardRunner.pathEdges + backwardRunner.pathEdges - else -> error("Cannot extract pathEdges for $this") - } - class UniRunner( private val graph: JcApplicationGraph, private val analyzer: Analyzer, @@ -63,10 +49,11 @@ class UniRunner( private val flowSpace: FlowFunctions = analyzer.flowFunctions private val workList: Channel> = Channel(Channel.UNLIMITED) - internal val pathEdges: MutableSet> = ConcurrentHashMap.newKeySet() private val reasons = ConcurrentHashMap, MutableSet>() - private val summaryEdges = hashMapOf, HashSet>>() - private val callerPathEdgeOf = hashMapOf, HashSet>>() + internal val pathEdges: MutableSet> = ConcurrentHashMap.newKeySet() + + private val summaryEdges: MutableMap, MutableSet>> = hashMapOf() + private val callerPathEdgeOf: MutableMap, MutableSet>> = hashMapOf() private val queueIsEmpty = QueueEmptinessChanged(runner = this, isEmpty = true) private val queueIsNotEmpty = QueueEmptinessChanged(runner = this, isEmpty = false) @@ -79,9 +66,6 @@ class UniRunner( tabulationAlgorithm() } - // TODO: should 'addStart' be public? - // TODO: should 'addStart' replace 'submitNewEdge'? - // TODO: inline private fun addStart(method: JcMethod) { require(unitResolver.resolve(method) == unit) val startFacts = flowSpace.obtainPossibleStartFacts(method) @@ -114,7 +98,7 @@ class UniRunner( if (pathEdges.add(edge)) { val doPrintOnlyForward = true val doPrintZero = false - if (!doPrintOnlyForward || edge.from.statement.toString() == "noop") { + if (!doPrintOnlyForward || edge.from.statement is JcNoopInst) { if (doPrintZero || edge.to.fact != Zero) { logger.trace { "Propagating edge=$edge in method=${edge.method.name} with reason=${reason}" } } @@ -146,7 +130,7 @@ class UniRunner( } } - private val Method.isExtern: Boolean + private val JcMethod.isExtern: Boolean get() = unitResolver.resolve(this) != unit private fun tabulationAlgorithmStep( @@ -263,10 +247,10 @@ class UniRunner( } } - private fun getFinalFacts(): Map> { - val resultFacts: MutableMap> = mutableMapOf() + private fun getFinalFacts(): Map> { + val resultFacts: MutableMap> = hashMapOf() for (edge in pathEdges) { - resultFacts.getOrPut(edge.to.statement) { mutableSetOf() }.add(edge.to.fact) + resultFacts.getOrPut(edge.to.statement) { hashSetOf() }.add(edge.to.fact) } return resultFacts } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt similarity index 61% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt index 31ebe2128..f1c06971d 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/SummaryStorage.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt @@ -14,76 +14,33 @@ * limitations under the License. */ -package org.jacodb.analysis.engine +package org.jacodb.analysis.ifds import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow -import org.jacodb.analysis.sarif.VulnerabilityDescription import org.jacodb.api.JcMethod -import org.jacodb.taint.configuration.TaintMethodSink import java.util.concurrent.ConcurrentHashMap /** - * A common interface for anything that should be remembered and used - * after the analysis of some unit is completed. + * A common interface for anything that should be remembered + * and used after the analysis of some unit is completed. */ -interface SummaryFact { +interface Summary { val method: JcMethod } -/** - * [SummaryFact] that denotes a possible vulnerability at [sink] - */ -data class VulnerabilityLocation( - val vulnerabilityDescription: VulnerabilityDescription, - val sink: IfdsVertex, - val edge: IfdsEdge? = null, - val rule: TaintMethodSink? = null, -) : SummaryFact { - override val method: JcMethod - get() = sink.method -} - -/** - * Denotes some start-to-end edge that should be saved for the method - */ -data class SummaryEdgeFact( - val edge: IfdsEdge, -) : SummaryFact { - override val method: JcMethod - get() = edge.method -} - -/** - * Saves info about cross-unit call. - * This info could later be used to restore full [TraceGraph]s - */ -data class CrossUnitCallFact( - val callerVertex: IfdsVertex, - val calleeVertex: IfdsVertex, -) : SummaryFact { - override val method: JcMethod - get() = callerVertex.method -} - -/** - * Wraps a [TraceGraph] that should be saved for some sink - */ -data class TraceGraphFact( - val graph: TraceGraph, -) : SummaryFact { - override val method: JcMethod - get() = graph.sink.method -} - /** * Contains summaries for many methods and allows to update them and subscribe for them. */ -interface SummaryStorage { +interface SummaryStorage { + /** + * A list of all methods for which summaries are not empty. + */ + val knownMethods: List /** - * Adds [fact] to summary of its method + * Adds [fact] to summary of its method. */ fun add(fact: T) @@ -98,19 +55,15 @@ interface SummaryStorage { * @return a list will all facts summarized for the given [method] so far. */ fun getCurrentFacts(method: JcMethod): List - - /** - * A list of all methods for which summaries are not empty. - */ - val knownMethods: List } -class SummaryStorageImpl : SummaryStorage - where T : SummaryFact { - +class SummaryStorageImpl : SummaryStorage { private val summaries = ConcurrentHashMap>() private val outFlows = ConcurrentHashMap>() + override val knownMethods: List + get() = summaries.keys.toList() + private fun getFlow(method: JcMethod): MutableSharedFlow { return outFlows.computeIfAbsent(method) { MutableSharedFlow(replay = Int.MAX_VALUE) @@ -132,7 +85,4 @@ class SummaryStorageImpl : SummaryStorage override fun getCurrentFacts(method: JcMethod): List { return getFacts(method).replayCache } - - override val knownMethods: List - get() = summaries.keys.toList() } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/TraceGraph.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt similarity index 55% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/TraceGraph.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt index 059a788f3..724af718a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/TraceGraph.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt @@ -14,46 +14,39 @@ * limitations under the License. */ -package org.jacodb.analysis.engine +package org.jacodb.analysis.ifds -/** - * A directed graph with selected [sink] and [sources], where each path from one of [sources] to [sink] is a trace. - * - * @property sink is usually some interesting vertex that we want to reach (e.g. vertex that produces vulnerability) - * @property sources are the entry points, e.g. the vertices with [ZEROFact] or method starts - */ -data class TraceGraph( - val sink: IfdsVertex, - val sources: Set, - val edges: Map>, +data class TraceGraph( + val sink: Vertex, + val sources: Set>, + val edges: Map, Set>>, ) { + /** + * Returns all traces from [sources] to [sink]. + */ + fun getAllTraces(): Sequence>> = sequence { + for (v in sources) { + yieldAll(getAllTraces(mutableListOf(v))) + } + } - private fun getAllTraces(curTrace: MutableList): Sequence> = sequence { - val v = curTrace.last() - + private fun getAllTraces( + trace: MutableList>, + ): Sequence>> = sequence { + val v = trace.last() if (v == sink) { - yield(curTrace.toList()) + yield(trace.toList()) // copy list return@sequence } - for (u in edges[v].orEmpty()) { - if (u !in curTrace) { - curTrace.add(u) - yieldAll(getAllTraces(curTrace)) - curTrace.removeLast() + if (u !in trace) { + trace.add(u) + yieldAll(getAllTraces(trace)) + trace.removeLast() } } } - /** - * Returns a sequence with all traces from [sources] to [sink] - */ - fun getAllTraces(): Sequence> = sequence { - sources.forEach { - yieldAll(getAllTraces(mutableListOf(it))) - } - } - /** * Merges two graphs. * @@ -63,22 +56,27 @@ data class TraceGraph( * * Informally, this method extends receiver's traces from one side using [upGraph]. */ - fun mergeWithUpGraph(upGraph: TraceGraph, entryPoints: Set): TraceGraph { - val validEntryPoints = entryPoints.intersect(edges.keys).ifEmpty { - return this - } + fun mergeWithUpGraph( + upGraph: TraceGraph, + entryPoints: Set>, + ): TraceGraph { + val validEntryPoints = entryPoints.intersect(edges.keys) + if (validEntryPoints.isEmpty()) return this val newSources = sources + upGraph.sources - val newEdges = edges.toMutableMap() - for ((source, dests) in upGraph.edges) { - newEdges[source] = newEdges.getOrDefault(source, emptySet()) + dests + for ((source, destinations) in upGraph.edges) { + newEdges[source] = newEdges.getOrDefault(source, emptySet()) + destinations } newEdges[upGraph.sink] = newEdges.getOrDefault(upGraph.sink, emptySet()) + validEntryPoints return TraceGraph(sink, newSources, newEdges) } companion object { - fun bySink(sink: IfdsVertex) = TraceGraph(sink, setOf(sink), emptyMap()) + fun bySink( + sink: Vertex, + ): TraceGraph { + return TraceGraph(sink, setOf(sink), emptyMap()) + } } -} \ No newline at end of file +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/UnitResolver.kt similarity index 94% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/UnitResolver.kt index d9710b447..605257f0d 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/UnitResolver.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/UnitResolver.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -package org.jacodb.analysis.engine +package org.jacodb.analysis.ifds -import org.jacodb.analysis.runAnalysis import org.jacodb.api.JcClassOrInterface import org.jacodb.api.JcMethod import org.jacodb.api.ext.packageName @@ -54,8 +53,6 @@ object SingletonUnit : UnitType { * * Therefore, it splits all methods into units, containing one or more method each * (unit is a set of methods with same value of [UnitType] returned by [resolve]). - * - * To get more info about how it is used in analysis, see [runAnalysis]. */ fun interface UnitResolver { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Vertex.kt similarity index 96% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Vertex.kt index a19467ec7..df82fb813 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Vertex.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Vertex.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 +package org.jacodb.analysis.ifds import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/RunnersLibrary.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/RunnersLibrary.kt deleted file mode 100644 index eeb479408..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/RunnersLibrary.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("RunnersLibrary") -package org.jacodb.analysis.library - -import org.jacodb.analysis.engine.BaseIfdsUnitRunnerFactory -import org.jacodb.analysis.engine.BidiIfdsUnitRunnerFactory -import org.jacodb.analysis.library.analyzers.AliasAnalyzerFactory -import org.jacodb.analysis.library.analyzers.NpeAnalyzerFactory -import org.jacodb.analysis.library.analyzers.NpePrecalcBackwardAnalyzerFactory -import org.jacodb.analysis.library.analyzers.SqlInjectionAnalyzerFactory -import org.jacodb.analysis.library.analyzers.SqlInjectionBackwardAnalyzerFactory -import org.jacodb.analysis.library.analyzers.TaintAnalysisNode -import org.jacodb.analysis.library.analyzers.TaintNode -import org.jacodb.analysis.library.analyzers.UnusedVariableAnalyzerFactory -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst - -//TODO: add docs here -val UnusedVariableRunnerFactory = BaseIfdsUnitRunnerFactory(UnusedVariableAnalyzerFactory) - -fun newSqlInjectionRunnerFactory(maxPathLength: Int = 5) = BidiIfdsUnitRunnerFactory( - BaseIfdsUnitRunnerFactory(SqlInjectionAnalyzerFactory(maxPathLength)), - BaseIfdsUnitRunnerFactory(SqlInjectionBackwardAnalyzerFactory(maxPathLength)), -) - -fun newNpeRunnerFactory(maxPathLength: Int = 5) = BidiIfdsUnitRunnerFactory( - BaseIfdsUnitRunnerFactory(NpeAnalyzerFactory(maxPathLength)), - BaseIfdsUnitRunnerFactory(NpePrecalcBackwardAnalyzerFactory(maxPathLength)), - isParallel = false -) - -fun newAliasRunnerFactory( - generates: (JcInst) -> List, - sanitizes: (JcExpr, TaintNode) -> Boolean, - sinks: (JcInst) -> List, - maxPathLength: Int = 5 -) = BaseIfdsUnitRunnerFactory(AliasAnalyzerFactory(generates, sanitizes, sinks, maxPathLength)) \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt deleted file mode 100644 index 13a8e075a..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintBackwardFunctions.kt +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.FlowFunctionInstance -import org.jacodb.analysis.engine.FlowFunctionsSpace -import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstanceCallExpr -import org.jacodb.api.cfg.JcReturnInst -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.ext.cfg.callExpr - -abstract class AbstractTaintBackwardFunctions( - protected val graph: JcApplicationGraph, - protected val maxPathLength: Int, -) : FlowFunctionsSpace { - - override fun obtainPossibleStartFacts(startStatement: JcInst): Collection { - return listOf(ZEROFact) - } - - abstract fun transmitBackDataFlow(from: JcValue, to: JcExpr, atInst: JcInst, fact: DomainFact, dropFact: Boolean): List - - abstract fun transmitDataFlowAtNormalInst(inst: JcInst, nextInst: JcInst, fact: DomainFact): List - - override fun obtainSequentFlowFunction(current: JcInst, next: JcInst) = FlowFunctionInstance { fact -> - // fact.activation != current needed here to jump over assignment where the fact appeared - if (current is JcAssignInst && (fact !is TaintNode || fact.activation != current)) { - transmitBackDataFlow(current.lhv, current.rhv, current, fact, dropFact = false) - } else { - transmitDataFlowAtNormalInst(current, next, fact) - } - } - - override fun obtainCallToStartFlowFunction( - callStatement: JcInst, - callee: JcMethod - ): FlowFunctionInstance = FlowFunctionInstance { fact -> - val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - - buildList { - // TODO: think about activation point handling for statics here - if (fact == ZEROFact || (fact is TaintNode && fact.variable.isStatic)) { - add(fact) - } - - if (callStatement is JcAssignInst) { - graph.entryPoints(callee).filterIsInstance().forEach { returnInst -> - returnInst.returnValue?.let { - addAll(transmitBackDataFlow(callStatement.lhv, it, callStatement, fact, dropFact = true)) - } - } - } - - if (callExpr is JcInstanceCallExpr) { - val thisInstance = callee.thisInstance - addAll(transmitBackDataFlow(callExpr.instance, thisInstance, callStatement, fact, dropFact = true)) - } - - val formalParams = graph.classpath.getArgumentsOf(callee) - - callExpr.args.zip(formalParams).forEach { (actual, formal) -> - // FilterNot is needed for reasons described in comment for symmetric case in - // AbstractTaintForwardFunctions.obtainExitToReturnSiteFlowFunction - addAll(transmitBackDataFlow(actual, formal, callStatement, fact, dropFact = true) - .filterNot { it is TaintNode && !it.variable.isOnHeap }) - } - } - } - - override fun obtainCallToReturnFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - graph: JcApplicationGraph, - ): FlowFunctionInstance = FlowFunctionInstance { fact -> - if (fact !is TaintNode) { - return@FlowFunctionInstance if (fact == ZEROFact) { - listOf(fact) - } else { - emptyList() - } - } - - val factPath = fact.variable - val callExpr = callStatement.callExpr ?: error("CallStatement is expected to contain callExpr") - - // TODO: check that this is legal - if (fact.activation == callStatement) { - return@FlowFunctionInstance listOf(fact) - } - - if (fact.variable.isStatic) { - return@FlowFunctionInstance emptyList() - } - - callExpr.args.forEach { - if (fact.variable.startsWith(it.toPathOrNull())) { - return@FlowFunctionInstance emptyList() - } - } - - if (callExpr is JcInstanceCallExpr) { - if (factPath.startsWith(callExpr.instance.toPathOrNull())) { - return@FlowFunctionInstance emptyList() - } - } - - if (callStatement is JcAssignInst) { - val lhvPath = callStatement.lhv.toPath() - if (factPath.startsWith(lhvPath)) { - return@FlowFunctionInstance emptyList() - } - } - - transmitDataFlowAtNormalInst(callStatement, returnSite, fact) - } - - override fun obtainExitToReturnSiteFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - exitStatement: JcInst - ): FlowFunctionInstance = FlowFunctionInstance { fact -> - val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - val actualParams = callExpr.args - val callee = graph.methodOf(exitStatement) - val formalParams = graph.classpath.getArgumentsOf(callee) - - buildList { - formalParams.zip(actualParams).forEach { (formal, actual) -> - addAll(transmitBackDataFlow(formal, actual, exitStatement, fact, dropFact = true)) - } - - if (callExpr is JcInstanceCallExpr) { - addAll(transmitBackDataFlow(callee.thisInstance, callExpr.instance, exitStatement, fact, dropFact = true)) - } - - if (fact is TaintNode && fact.variable.isStatic) { - add(fact) - } - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt deleted file mode 100644 index d4c9bb8e2..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AbstractTaintForwardFunctions.kt +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import mu.KotlinLogging -import org.jacodb.analysis.config.BasicConditionEvaluator -import org.jacodb.analysis.config.CallPositionToAccessPathResolver -import org.jacodb.analysis.config.CallPositionToJcValueResolver -import org.jacodb.analysis.config.FactAwareConditionEvaluator -import org.jacodb.analysis.config.TaintActionEvaluator -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.FlowFunctionInstance -import org.jacodb.analysis.engine.FlowFunctionsSpace -import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.engine.toDomainFact -import org.jacodb.analysis.paths.onSome -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcDynamicCallExpr -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstanceCallExpr -import org.jacodb.api.cfg.JcReturnInst -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.ext.cfg.callExpr -import org.jacodb.taint.configuration.AssignMark -import org.jacodb.taint.configuration.CopyAllMarks -import org.jacodb.taint.configuration.CopyMark -import org.jacodb.taint.configuration.RemoveAllMarks -import org.jacodb.taint.configuration.RemoveMark -import org.jacodb.taint.configuration.TaintCleaner -import org.jacodb.taint.configuration.TaintConfigurationFeature -import org.jacodb.taint.configuration.TaintMethodSource -import org.jacodb.taint.configuration.TaintPassThrough - -private val logger = KotlinLogging.logger {} - -abstract class AbstractTaintForwardFunctions( - protected val cp: JcClasspath, -) : FlowFunctionsSpace { - - internal val taintConfigurationFeature: TaintConfigurationFeature? by lazy { - cp.features - ?.singleOrNull { it is TaintConfigurationFeature } - ?.let { it as TaintConfigurationFeature } - } - - protected abstract fun transmitDataFlow( - from: JcExpr, - to: JcValue, - atInst: JcInst, - fact: DomainFact, - dropFact: Boolean, - ): List - - protected abstract fun transmitDataFlowAtNormalInst( - inst: JcInst, - nextInst: JcInst, - fact: DomainFact, - ): List - - final override fun obtainSequentFlowFunction( - current: JcInst, - next: JcInst, - ) = FlowFunctionInstance { fact -> - if (fact is TaintNode && fact.activation == current) { - listOf(fact.activatedCopy) - } else if (current is JcAssignInst) { - // Note: 'next' is ignored - transmitDataFlow(current.rhv, current.lhv, current, fact, false) - } else { - transmitDataFlowAtNormalInst(current, next, fact) - } - } - - final override fun obtainCallToStartFlowFunction( - callStatement: JcInst, - callee: JcMethod, - ) = FlowFunctionInstance { fact -> - if (fact is TaintNode && fact.activation == callStatement) { - return@FlowFunctionInstance emptyList() - } - - val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - val actualParams = callExpr.args - val formalParams = cp.getArgumentsOf(callee) - buildSet { - // TODO: when dropFact=true, consider removing (note: once!) the fact afterwards - - formalParams.zip(actualParams).forEach { (formal, actual) -> - addAll(transmitDataFlow(actual, formal, callStatement, fact, dropFact = true)) - } - - if (callExpr is JcInstanceCallExpr) { - addAll(transmitDataFlow(callExpr.instance, callee.thisInstance, callStatement, fact, dropFact = true)) - } - - if (fact is TaintNode && fact.variable.isStatic) { - add(fact) - } - - if (fact == ZEROFact) { - addAll(obtainPossibleStartFacts(callStatement)) - } - } - } - - final override fun obtainCallToReturnFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - graph: JcApplicationGraph, - ) = FlowFunctionInstance { fact -> - val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - val callee = callExpr.method.method - - // FIXME: adhoc for constructors: - if (callee.isConstructor) { - return@FlowFunctionInstance listOf(fact) - } - - // FIXME: handle taint pass-through on invokedynamic-based String concatenation: - if (fact is TaintNode && callExpr is JcDynamicCallExpr && callee.enclosingClass.name == "java.lang.invoke.StringConcatFactory" && callStatement is JcAssignInst) { - for (arg in callExpr.args) { - if (arg.toPath() == fact.variable) { - return@FlowFunctionInstance setOf( - fact, - Tainted(fact).copy(variable = callStatement.lhv.toPath()).toDomainFact() - ) - } - } - return@FlowFunctionInstance setOf(fact) - } - - val config = taintConfigurationFeature?.getConfigForMethod(callee) - - if (fact == ZEROFact) { - val facts = mutableSetOf() - if (config != null) { - val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(callStatement)) - val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is AssignMark -> { - actionEvaluator.evaluate(action) - } - - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - } - } - } - } - } - logger.debug { "call-to-return-site flow function for callee=$callee, fact=$fact returns ${facts.size} facts: $facts" } - return@FlowFunctionInstance facts.map { it.toDomainFact() } + ZEROFact - } - - if (fact !is TaintNode) { - return@FlowFunctionInstance emptyList() - } - - if (fact.activation == callStatement) { - return@FlowFunctionInstance listOf(fact.activatedCopy) - } - - if (config != null) { - val conditionEvaluator = FactAwareConditionEvaluator( - Tainted(fact), - CallPositionToJcValueResolver(callStatement) - ) - val actionEvaluator = TaintActionEvaluator(CallPositionToAccessPathResolver(callStatement)) - val facts = mutableSetOf() - var defaultBehavior = true - - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is CopyMark -> actionEvaluator.evaluate(action, Tainted(fact)) - is CopyAllMarks -> actionEvaluator.evaluate(action, Tainted(fact)) - is RemoveMark -> actionEvaluator.evaluate(action, Tainted(fact)) - is RemoveAllMarks -> actionEvaluator.evaluate(action, Tainted(fact)) - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - defaultBehavior = false - } - } - } - } - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is RemoveMark -> actionEvaluator.evaluate(action, Tainted(fact)) - is RemoveAllMarks -> actionEvaluator.evaluate(action, Tainted(fact)) - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - defaultBehavior = false - } - } - } - } - - if (!defaultBehavior) { - if (facts.size > 0) { - logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } - } - return@FlowFunctionInstance facts.map { it.toDomainFact() } - } else { - // Fall back to the default behavior, as if there were no config at all. - } - } - - // Default behavior for "analyzable" method calls is to remove ("temporarily") - // all the marks from the 'instance' and arguments, in order to allow them "pass through" - // the callee (when it is going to be analyzed), i.e. through "call-to-start" and - // "exit-to-return" flow functions. - // When we know that we are NOT going to analyze the callee, we do NOT need - // to remove any marks from 'instance' and arguments. - // Currently, "analyzability" of the callee depends on the fact that the callee - // is "accessible" through the JcApplicationGraph::callees(). - if (callee in graph.callees(callStatement)) { - - if (fact.variable.isStatic) { - return@FlowFunctionInstance emptyList() - } - - for (actual in callExpr.args) { - // Possibly tainted actual parameter: - if (fact.variable.startsWith(actual.toPathOrNull())) { - return@FlowFunctionInstance emptyList() // Will be handled by summary edge - } - } - - if (callExpr is JcInstanceCallExpr) { - // Possibly tainted instance: - if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { - return@FlowFunctionInstance emptyList() // Will be handled by summary edge - } - } - - } - - if (callStatement is JcAssignInst) { - // Possibly tainted lhv: - if (fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { - return@FlowFunctionInstance emptyList() // Overridden by rhv - } - } - - // TODO: do we even need to call this here??? - transmitDataFlowAtNormalInst(callStatement, returnSite, fact) - } - - final override fun obtainExitToReturnSiteFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - exitStatement: JcInst, - ): FlowFunctionInstance = FlowFunctionInstance { fact -> - val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - val actualParams = callExpr.args - val callee = exitStatement.location.method - // TODO: maybe we can always use fact instead of updatedFact here - val updatedFact = if (fact is TaintNode && fact.activation?.location?.method == callee) { - fact.updateActivation(callStatement) - } else { - fact - } - val formalParams = cp.getArgumentsOf(callee) - - buildList { - if (fact is TaintNode && fact.variable.isOnHeap) { - // If there is some method A::f(formal: T) that is called like a.f(actual) then - // 1. For all g^k, k >= 1, we should propagate back from formal.g^k to actual.g^k (as they are on heap) - // 2. We shouldn't propagate from formal to actual (as formal is local) - // Second case is why we need check for isOnHeap - // TODO: add test for handling of 2nd case - formalParams.zip(actualParams).forEach { (formal, actual) -> - addAll(transmitDataFlow(formal, actual, exitStatement, updatedFact, dropFact = true)) - } - } - - if (callExpr is JcInstanceCallExpr) { - addAll( - transmitDataFlow( - callee.thisInstance, - callExpr.instance, - exitStatement, - updatedFact, - dropFact = true - ) - ) - } - - if (callStatement is JcAssignInst && exitStatement is JcReturnInst) { - exitStatement.returnValue?.let { // returnValue can be null here in some weird cases, for e.g. lambda - addAll(transmitDataFlow(it, callStatement.lhv, exitStatement, updatedFact, dropFact = true)) - } - } - - if (fact is TaintNode && fact.variable.isStatic) { - add(fact) - } - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AliasAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AliasAnalyzer.kt deleted file mode 100644 index 7ceef46b9..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/AliasAnalyzer.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import org.jacodb.analysis.engine.AnalysisDependentEvent -import org.jacodb.analysis.engine.AnalyzerFactory -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.IfdsResult -import org.jacodb.analysis.engine.IfdsVertex -import org.jacodb.analysis.sarif.VulnerabilityDescription -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst - -fun AliasAnalyzerFactory( - generates: (JcInst) -> List, - sanitizes: (JcExpr, TaintNode) -> Boolean, - sinks: (JcInst) -> List, - maxPathLength: Int = 5 -) = AnalyzerFactory { graph -> - AliasAnalyzer(graph, generates, sanitizes, sinks, maxPathLength) -} - -private class AliasAnalyzer( - graph: JcApplicationGraph, - override val generates: (JcInst) -> List, - override val sanitizes: (JcExpr, TaintNode) -> Boolean, - override val sinks: (JcInst) -> List, - maxPathLength: Int, -) : TaintAnalyzer(graph, maxPathLength) { - override fun generateDescriptionForSink(sink: IfdsVertex): VulnerabilityDescription = TODO() - - override fun handleIfdsResult(ifdsResult: IfdsResult): List = TODO() -} \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt deleted file mode 100644 index a38c23ef9..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/NpeAnalyzer.kt +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import org.jacodb.analysis.engine.AbstractAnalyzer -import org.jacodb.analysis.engine.AnalysisDependentEvent -import org.jacodb.analysis.engine.AnalyzerFactory -import org.jacodb.analysis.engine.CrossUnitCallFact -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.EdgeForOtherRunnerQuery -import org.jacodb.analysis.engine.FlowFunctionsSpace -import org.jacodb.analysis.engine.IfdsEdge -import org.jacodb.analysis.engine.NewSummaryFact -import org.jacodb.analysis.engine.VulnerabilityLocation -import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.analysis.paths.ElementAccessor -import org.jacodb.analysis.paths.FieldAccessor -import org.jacodb.analysis.paths.isDereferencedAt -import org.jacodb.analysis.paths.minus -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull -import org.jacodb.analysis.sarif.SarifMessage -import org.jacodb.analysis.sarif.VulnerabilityDescription -import org.jacodb.api.JcArrayType -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcArgument -import org.jacodb.api.cfg.JcCallExpr -import org.jacodb.api.cfg.JcConstant -import org.jacodb.api.cfg.JcEqExpr -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcIfInst -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcNeqExpr -import org.jacodb.api.cfg.JcNewArrayExpr -import org.jacodb.api.cfg.JcNewExpr -import org.jacodb.api.cfg.JcNullConstant -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.cfg.locals -import org.jacodb.api.cfg.values -import org.jacodb.api.ext.fields -import org.jacodb.api.ext.isNullable - -fun NpeAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { graph -> - NpeAnalyzer(graph, maxPathLength) -} - -class NpeAnalyzer( - graph: JcApplicationGraph, - maxPathLength: Int, -) : AbstractAnalyzer(graph) { - override val flowFunctions: FlowFunctionsSpace = NpeForwardFunctions(graph.classpath, maxPathLength) - - override val isMainAnalyzer: Boolean - get() = true - - companion object { - const val ruleId: String = "npe-deref" - } - - override fun handleNewEdge(edge: IfdsEdge): List = buildList { - val (inst, fact0) = edge.to - - if (fact0 is NpeTaintNode && fact0.activation == null && fact0.variable.isDereferencedAt(inst)) { - val message = "Dereference of possibly-null ${fact0.variable}" - val desc = VulnerabilityDescription(SarifMessage(message), ruleId) - add(NewSummaryFact((VulnerabilityLocation(desc, edge.to, edge)))) - verticesWithTraceGraphNeeded.add(edge.to) - } - - addAll(super.handleNewEdge(edge)) - } - - override fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List = buildList { - add(EdgeForOtherRunnerQuery(IfdsEdge(fact.calleeVertex, fact.calleeVertex))) - addAll(super.handleNewCrossUnitCall(fact)) - } -} - -private class NpeForwardFunctions( - cp: JcClasspath, - private val maxPathLength: Int, -) : AbstractTaintForwardFunctions(cp) { - - private val JcIfInst.pathComparedWithNull: AccessPath? - get() { - val expr = condition - return if (expr.rhv is JcNullConstant) { - expr.lhv.toPathOrNull()?.limit(maxPathLength) - } else if (expr.lhv is JcNullConstant) { - expr.rhv.toPathOrNull()?.limit(maxPathLength) - } else { - null - } - } - - override fun transmitDataFlow( - from: JcExpr, - to: JcValue, - atInst: JcInst, - fact: DomainFact, - dropFact: Boolean, - ): List { - val default = if (dropFact && fact != ZEROFact) emptyList() else listOf(fact) - val toPath = to.toPathOrNull()?.limit(maxPathLength) ?: return default - - if (fact == ZEROFact) { - return if (from is JcNullConstant || (from is JcCallExpr && from.method.method.treatAsNullable)) { - listOf(ZEROFact, NpeTaintNode(toPath)) // taint is generated here - } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { - val accessors = List((from.type as JcArrayType).dimensions) { - ElementAccessor(null) - } - val path = (toPath / accessors).limit(maxPathLength) - listOf(ZEROFact, NpeTaintNode(path)) - } else { - listOf(ZEROFact) - } - } - - if (fact !is NpeTaintNode) { - return emptyList() - } - - val factPath = fact.variable - if (factPath.isDereferencedAt(atInst)) { - return emptyList() - } - - if ( - from is JcNewExpr || - from is JcNewArrayExpr || - from is JcConstant || - (from is JcCallExpr && !from.method.method.treatAsNullable) - ) { - return if (factPath.startsWith(toPath)) { - emptyList() // new kills the fact here - } else { - default - } - } - - // TODO: slightly differs from original paper, think what's correct - val fromPath = from.toPathOrNull()?.limit(maxPathLength) ?: return default - return normalFactFlow(fact, fromPath, toPath, dropFact, maxPathLength) - } - - override fun transmitDataFlowAtNormalInst( - inst: JcInst, - nextInst: JcInst, - fact: DomainFact, - ): List { - val factPath = when (fact) { - is NpeTaintNode -> fact.variable - ZEROFact -> null - else -> return emptyList() - } - - if (factPath.isDereferencedAt(inst)) { - return emptyList() - } - - if (inst !is JcIfInst) { - return listOf(fact) - } - - // Following are some ad-hoc magic for if statements to change facts after instructions like if (x != null) - val nextInstIsTrueBranch = nextInst.location.index == inst.trueBranch.index - if (fact == ZEROFact) { - if (inst.pathComparedWithNull != null) { - if ((inst.condition is JcEqExpr && nextInstIsTrueBranch) || - (inst.condition is JcNeqExpr && !nextInstIsTrueBranch) - ) { - // This is a hack: instructions like `return null` in branch of next will be considered only if - // the fact holds (otherwise we could not get there) - return listOf(NpeTaintNode(inst.pathComparedWithNull!!)) - } - } - return listOf(ZEROFact) - } - - fact as NpeTaintNode - - // This handles cases like if (x != null) expr1 else expr2, where edges to expr1 and to expr2 should be different - // (because x == null will be held at expr2 but won't be held at expr1) - val expr = inst.condition - if (inst.pathComparedWithNull != fact.variable) { - return listOf(fact) - } - - return if ((expr is JcEqExpr && nextInstIsTrueBranch) || (expr is JcNeqExpr && !nextInstIsTrueBranch)) { - // comparedPath is null in this branch - listOf(ZEROFact) - } else { - emptyList() - } - } - - override fun obtainPossibleStartFacts(startStatement: JcInst): List = buildList { - add(ZEROFact) - - val method = startStatement.location.method - - // Note that here and below we intentionally don't expand fields because this may cause - // an increase of false positives and significant performance drop - - // Possibly null arguments - this += method.flowGraph().locals - .filterIsInstance() - .filter { method.parameters[it.index].isNullable != false } - .map { NpeTaintNode(AccessPath.from(it)) } - - // Possibly null statics - // TODO: handle statics in a more general manner - this += method.enclosingClass.fields - .filter { it.isNullable != false && it.isStatic } - .map { NpeTaintNode(AccessPath.fromStaticField(it)) } - - // Possibly null public non-final fields - this += method.enclosingClass.fields - .filter { it.isNullable != false && !it.isStatic && it.isPublic && !it.isFinal } - .map { NpeTaintNode(AccessPath.from(method.thisInstance) / FieldAccessor(it)) } - } -} - -fun NpePrecalcBackwardAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { graph -> - NpePrecalcBackwardAnalyzer(graph, maxPathLength) -} - -private class NpePrecalcBackwardAnalyzer( - graph: JcApplicationGraph, - maxPathLength: Int, -) : AbstractAnalyzer(graph) { - - override val flowFunctions: FlowFunctionsSpace = NpePrecalcBackwardFunctions(graph, maxPathLength) - - override val isMainAnalyzer: Boolean - get() = false - - override fun handleNewEdge(edge: IfdsEdge): List = buildList { - if (edge.to.statement in graph.exitPoints(edge.method)) { - add(EdgeForOtherRunnerQuery((IfdsEdge(edge.to, edge.to)))) - } - } -} - -class NpePrecalcBackwardFunctions( - graph: JcApplicationGraph, - maxPathLength: Int, -) : AbstractTaintBackwardFunctions(graph, maxPathLength) { - - override fun transmitBackDataFlow( - from: JcValue, - to: JcExpr, - atInst: JcInst, - fact: DomainFact, - dropFact: Boolean, - ): List { - val thisInstance = atInst.location.method.thisInstance.toPath() - if (fact == ZEROFact) { - val derefs = atInst.values - .mapNotNull { it.toPathOrNull() } - .filter { it.isDereferencedAt(atInst) } - .filterNot { it == thisInstance } - .map { NpeTaintNode(it) } - return listOf(ZEROFact) + derefs - } - - if (fact !is TaintNode) { - return emptyList() - } - - val factPath = fact.variable - val default = if (dropFact) emptyList() else listOf(fact) - val toPath = to.toPathOrNull() ?: return default - val fromPath = from.toPathOrNull() ?: return default - - val diff = factPath.minus(fromPath) - if (diff != null) { - val newPath = (toPath / diff).limit(maxPathLength) - return listOf(fact.moveToOtherPath(newPath)).filterNot { - it.variable == thisInstance - } - } - return default - } - - override fun transmitDataFlowAtNormalInst( - inst: JcInst, - nextInst: JcInst, - fact: DomainFact, - ): List { - return listOf(fact) - } - - override fun obtainPossibleStartFacts(startStatement: JcInst): List { - val values = startStatement.values - return listOf(ZEROFact) + values - .mapNotNull { it.toPathOrNull() } - .filterNot { it == startStatement.location.method.thisInstance.toPath() } - .map { NpeTaintNode(it) } - } -} - -private val JcMethod.treatAsNullable: Boolean - get() { - if (isNullable == true) { - return true - } - return "${enclosingClass.name}.$name" in knownNullableMethods - } - -private val knownNullableMethods = listOf( - "java.lang.System.getProperty", - "java.util.Properties.getProperty" -) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt deleted file mode 100644 index a0a13a924..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/SqlInjectionAnalyzer.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import org.jacodb.analysis.engine.AnalyzerFactory -import org.jacodb.analysis.engine.IfdsVertex -import org.jacodb.analysis.sarif.SarifMessage -import org.jacodb.analysis.sarif.VulnerabilityDescription -import org.jacodb.api.analysis.JcApplicationGraph - -class SqlInjectionAnalyzer( - graph: JcApplicationGraph, - maxPathLength: Int -) : TaintAnalyzer(graph, maxPathLength) { - override val generates = isSourceMethodToGenerates(sqlSourceMatchers.asMethodMatchers) - override val sanitizes = isSanitizeMethodToSanitizes(sqlSanitizeMatchers.asMethodMatchers) - override val sinks = isSinkMethodToSinks(sqlSinkMatchers.asMethodMatchers) - - companion object { - private const val ruleId: String = "SQL-injection" - private val vulnerabilityMessage = SarifMessage("SQL query with unchecked injection") - - val vulnerabilityDescription = VulnerabilityDescription(vulnerabilityMessage, ruleId) - } - - override fun generateDescriptionForSink(sink: IfdsVertex): VulnerabilityDescription = vulnerabilityDescription -} - -class SqlInjectionBackwardAnalyzer( - graph: JcApplicationGraph, - maxPathLength: Int -) : TaintBackwardAnalyzer(graph, maxPathLength) { - override val generates = isSourceMethodToGenerates(sqlSourceMatchers.asMethodMatchers) - override val sinks = isSinkMethodToSinks(sqlSinkMatchers.asMethodMatchers) -} - -fun SqlInjectionAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { graph -> - SqlInjectionAnalyzer(graph, maxPathLength) -} - -fun SqlInjectionBackwardAnalyzerFactory(maxPathLength: Int) = AnalyzerFactory { graph -> - SqlInjectionBackwardAnalyzer(graph, maxPathLength) -} - -private val sqlSourceMatchers: List = listOf( - "java\\.io.+", // TODO - // "java\\.lang\\.System\\#getenv", // in config - // "java\\.sql\\.ResultSet#get.+" // in config -) - -private val sqlSanitizeMatchers: List = listOf( - // "java\\.sql\\.Statement#set.*", // Remove - // "java\\.sql\\.PreparedStatement#set.*" // TODO -) - -private val sqlSinkMatchers: List = listOf( - // "java\\.sql\\.Statement#execute.*", // in config - // "java\\.sql\\.PreparedStatement#execute.*", // in config -) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt deleted file mode 100644 index df5bb5b6e..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintAnalyzer.kt +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import mu.KotlinLogging -import org.jacodb.analysis.config.BasicConditionEvaluator -import org.jacodb.analysis.config.CallPositionToJcValueResolver -import org.jacodb.analysis.config.FactAwareConditionEvaluator -import org.jacodb.analysis.config.TaintActionEvaluator -import org.jacodb.analysis.engine.AbstractAnalyzer -import org.jacodb.analysis.engine.AnalysisDependentEvent -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.EdgeForOtherRunnerQuery -import org.jacodb.analysis.engine.IfdsEdge -import org.jacodb.analysis.engine.IfdsVertex -import org.jacodb.analysis.engine.NewSummaryFact -import org.jacodb.analysis.engine.VulnerabilityLocation -import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.engine.toDomainFact -import org.jacodb.analysis.paths.Maybe -import org.jacodb.analysis.paths.minus -import org.jacodb.analysis.paths.onSome -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toMaybe -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull -import org.jacodb.analysis.sarif.VulnerabilityDescription -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcArgument -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcCallExpr -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstanceCallExpr -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.cfg.locals -import org.jacodb.api.cfg.values -import org.jacodb.api.ext.cfg.callExpr -import org.jacodb.api.ext.findTypeOrNull -import org.jacodb.taint.configuration.AnyArgument -import org.jacodb.taint.configuration.Argument -import org.jacodb.taint.configuration.AssignMark -import org.jacodb.taint.configuration.Result -import org.jacodb.taint.configuration.ResultAnyElement -import org.jacodb.taint.configuration.TaintEntryPointSource -import org.jacodb.taint.configuration.TaintMethodSink -import org.jacodb.taint.configuration.This - -private val logger = KotlinLogging.logger {} - -fun isSourceMethodToGenerates(isSourceMethod: (JcMethod) -> Boolean): (JcInst) -> List { - return generates@{ inst: JcInst -> - val callExpr = inst.callExpr?.takeIf { isSourceMethod(it.method.method) } - ?: return@generates emptyList() - if (inst is JcAssignInst && isSourceMethod(callExpr.method.method)) { - listOf(TaintAnalysisNode(inst.lhv.toPath(), nodeType = "TAINT")) - } else { - emptyList() - } - } -} - -fun isSinkMethodToSinks(isSinkMethod: (JcMethod) -> Boolean): (JcInst) -> List { - return sinks@{ inst: JcInst -> - val callExpr = inst.callExpr?.takeIf { isSinkMethod(it.method.method) } - ?: return@sinks emptyList() - callExpr.values - .mapNotNull { it.toPathOrNull() } - .map { TaintAnalysisNode(it, nodeType = "TAINT") } - } -} - -fun isSanitizeMethodToSanitizes(isSanitizeMethod: (JcMethod) -> Boolean): (JcExpr, TaintNode) -> Boolean { - return sanitizes@{ expr: JcExpr, fact: TaintNode -> - if (expr !is JcCallExpr) { - false - } else { - if (isSanitizeMethod(expr.method.method) && fact.activation == null) { - expr.values.any { - it.toPathOrNull().startsWith(fact.variable) || fact.variable.startsWith(it.toPathOrNull()) - } - } else { - false - } - } - } -} - -internal val List.asMethodMatchers: (JcMethod) -> Boolean - get() = { method: JcMethod -> - any { it.toRegex().matches("${method.enclosingClass.name}#${method.name}") } - } - -abstract class TaintAnalyzer( - graph: JcApplicationGraph, - maxPathLength: Int, -) : AbstractAnalyzer(graph) { - abstract val generates: (JcInst) -> List - abstract val sanitizes: (JcExpr, TaintNode) -> Boolean - abstract val sinks: (JcInst) -> List - - override val flowFunctions: TaintForwardFunctions by lazy { - TaintForwardFunctions(graph, maxPathLength, generates, sanitizes) - } - - override val isMainAnalyzer: Boolean - get() = true - - protected abstract fun generateDescriptionForSink(sink: IfdsVertex): VulnerabilityDescription - - override fun handleNewEdge(edge: IfdsEdge): List = buildList { - val configOk = run { - val callExpr = edge.to.statement.callExpr ?: return@run false - val callee = callExpr.method.method - - val config = flowFunctions.taintConfigurationFeature?.getConfigForMethod(callee) - ?: return@run false - - // TODO: not always we want to skip sinks on ZeroFacts. Some rules might have ConstantTrue or just true (when evaluated with ZeroFact) condition. - if (edge.to.domainFact !is TaintNode) { - return@run false - } - - // Determine whether 'edge.to' is a sink via config: - val conditionEvaluator = FactAwareConditionEvaluator( - Tainted(edge.to.domainFact), - CallPositionToJcValueResolver(edge.to.statement), - ) - var isSink = false - var triggeredItem: TaintMethodSink? = null - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - isSink = true - triggeredItem = item - break - } - // FIXME: unconditionally let it be the sink. - // isSink = true - // triggeredItem = item - // break - } - if (isSink) { - val desc = generateDescriptionForSink(edge.to) - val vulnerability = VulnerabilityLocation(desc, edge.to, edge, rule = triggeredItem) - logger.info { "Found sink: $vulnerability in ${vulnerability.method}" } - add(NewSummaryFact(vulnerability)) - verticesWithTraceGraphNeeded.add(edge.to) - } - true - } - - if (!configOk) { - // "config"-less behavior: - if (edge.to.domainFact in sinks(edge.to.statement)) { - val desc = generateDescriptionForSink(edge.to) - val vulnerability = VulnerabilityLocation(desc, edge.to, edge) - logger.info { "Found sink: $vulnerability in ${vulnerability.method}" } - add(NewSummaryFact(vulnerability)) - verticesWithTraceGraphNeeded.add(edge.to) - } - } - - // "Default" behavior: - addAll(super.handleNewEdge(edge)) - } -} - -abstract class TaintBackwardAnalyzer( - graph: JcApplicationGraph, - maxPathLength: Int, -) : AbstractAnalyzer(graph) { - abstract val generates: (JcInst) -> List - abstract val sinks: (JcInst) -> List - - override val isMainAnalyzer: Boolean - get() = false - - override val flowFunctions: TaintBackwardFunctions by lazy { - TaintBackwardFunctions(graph, generates, sinks, maxPathLength) - } - - override fun handleNewEdge(edge: IfdsEdge): List = buildList { - if (edge.to.statement in graph.exitPoints(edge.method)) { - add(EdgeForOtherRunnerQuery(IfdsEdge(edge.to, edge.to))) - } - } -} - -class TaintForwardFunctions( - graph: JcApplicationGraph, - private val maxPathLength: Int, - private val generates: (JcInst) -> List, - private val sanitizes: (JcExpr, TaintNode) -> Boolean, -) : AbstractTaintForwardFunctions(graph.classpath) { - - override fun transmitDataFlow( - from: JcExpr, - to: JcValue, - atInst: JcInst, - fact: DomainFact, - dropFact: Boolean, - ): List { - if (fact == ZEROFact) { - return listOf(ZEROFact) + generates(atInst) - } - - if (fact !is TaintNode) { - return emptyList() - } - - val default: List = if ( - dropFact || - (sanitizes(from, fact) && fact.variable == (from as? JcInstanceCallExpr)?.instance?.toPath()) - ) { - emptyList() - } else { - listOf(fact) - } - - val toPath = to.toPathOrNull()?.limit(maxPathLength) ?: return default - val newPossibleTaint = if (sanitizes(from, fact)) emptyList() else listOf(fact.moveToOtherPath(toPath)) - - val fromPath = from.toPathOrNull() - if (fromPath != null) { - return if (sanitizes(from, fact)) { - default - } else if (fromPath.startsWith(fact.variable)) { - default + newPossibleTaint - } else { - normalFactFlow(fact, fromPath, toPath, dropFact, maxPathLength) - } - } - - if ( - from.values.any { - it.toPathOrNull().startsWith(fact.variable) || - fact.variable.startsWith(it.toPathOrNull()) - } - ) { - val instanceOrNull = (from as? JcInstanceCallExpr)?.instance - if (instanceOrNull != null && !sanitizes(from, fact)) { - val instancePath = instanceOrNull.toPathOrNull() - if (instancePath != null) { - return default + newPossibleTaint + fact.moveToOtherPath(instancePath) - } - } - return default + newPossibleTaint - } else if (fact.variable.startsWith(toPath)) { - return emptyList() - } - return default - } - - override fun transmitDataFlowAtNormalInst( - inst: JcInst, - nextInst: JcInst, // unused - fact: DomainFact, - ): List { - // Generate new facts: - if (fact == ZEROFact) { - return listOf(ZEROFact) + generates(inst) - } - - if (fact !is TaintNode) { - return emptyList() - } - - // Pass-through: - val callExpr = inst.callExpr ?: return listOf(fact) - if (callExpr !is JcInstanceCallExpr) { - return listOf(fact) - } - val instance = callExpr.instance - - // Sanitize: - if (instance.toPath() == fact.variable && sanitizes(callExpr, fact)) { - return emptyList() - } - - // TODO: do no do this: - // val factIsPassed = callExpr.values.any { - // it.toPathOrNull().startsWith(fact.variable) || fact.variable.startsWith(it.toPathOrNull()) - // } - // return if (factIsPassed && !sanitizes(callExpr, fact)) { - // // Pass-through, but also (?) taint the 'instance' - // listOf(fact) + fact.moveToOtherPath(instance.toPath()) - // } else { - // // Pass-through - // listOf(fact) - // } - - // Pass-through - return listOf(fact) - } - - override fun obtainPossibleStartFacts(startStatement: JcInst): List = buildList { - add(ZEROFact) - - val method = startStatement.location.method - val config = taintConfigurationFeature?.getConfigForMethod(method) - if (config != null) { - val conditionEvaluator = BasicConditionEvaluator { position -> - when (position) { - This -> Maybe.some(method.thisInstance) - - is Argument -> method.flowGraph().locals - .filterIsInstance() - .singleOrNull { it.index == position.index } - .toMaybe() - - AnyArgument -> error("Unexpected $position") - Result -> error("Unexpected $position") - ResultAnyElement -> error("Unexpected $position") - } - } - val actionEvaluator = TaintActionEvaluator { position -> - when (position) { - This -> method.thisInstance.toPathOrNull().toMaybe() - - is Argument -> { - val p = method.parameters[position.index] - cp.findTypeOrNull(p.type) - ?.let { t -> JcArgument.of(p.index, p.name, t).toPathOrNull() } - .toMaybe() - } - - AnyArgument -> error("Unexpected $position") - Result -> error("Unexpected $position") - ResultAnyElement -> error("Unexpected $position") - } - } - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is AssignMark -> actionEvaluator.evaluate(action) - else -> error("$action is not supported for $item") - } - result.onSome { - addAll(it.map { fact -> fact.toDomainFact() }) - } - } - } - } - } - } -} - -class TaintBackwardFunctions( - graph: JcApplicationGraph, - val generates: (JcInst) -> List, - val sinks: (JcInst) -> List, - maxPathLength: Int, -) : AbstractTaintBackwardFunctions(graph, maxPathLength) { - - override fun transmitBackDataFlow( - from: JcValue, - to: JcExpr, - atInst: JcInst, - fact: DomainFact, - dropFact: Boolean, - ): List { - if (fact == ZEROFact) { - return listOf(ZEROFact) + sinks(atInst) - } - - if (fact !is TaintAnalysisNode) { - return emptyList() - } - - val factPath = fact.variable - val default = if (dropFact || fact in generates(atInst)) emptyList() else listOf(fact) - val fromPath = from.toPathOrNull() ?: return default - val toPath = to.toPathOrNull() - - if (toPath != null) { - val diff = factPath.minus(fromPath) - if (diff != null) { - val newPath = (toPath / diff).limit(maxPathLength) - return listOf(fact.moveToOtherPath(newPath)) - } - } else if (factPath.startsWith(fromPath) || (to is JcInstanceCallExpr && factPath.startsWith(to.instance.toPath()))) { - return to.values.mapNotNull { it.toPathOrNull() }.map { TaintAnalysisNode(it, nodeType = "TAINT") } - } - return default - } - - override fun transmitDataFlowAtNormalInst( - inst: JcInst, - nextInst: JcInst, - fact: DomainFact, - ): List { - if (fact == ZEROFact) { - return listOf(fact) + sinks(inst) - } - if (fact !is TaintAnalysisNode) { - return emptyList() - } - - val callExpr = inst.callExpr as? JcInstanceCallExpr ?: return listOf(fact) - if (fact.variable.startsWith(callExpr.instance.toPath())) { - return inst.values.mapNotNull { it.toPathOrNull() }.map { TaintAnalysisNode(it, nodeType = "TAINT") } - } - - return listOf(fact) - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt deleted file mode 100644 index 21f7ef790..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/TaintNode.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.api.cfg.JcInst - -/** - * Abstract implementation for [DomainFact] that can be used for analysis where dataflow facts correlate with - * variables/values - * - * @property activation is the activation point, as described in ARF14. Null value means that activation point was - * passed (so, for analyses that do not use backward runner to taint aliases, [activation] will always be null). - */ -abstract class TaintNode( - val variable: AccessPath, - val activation: JcInst? = null, -) : DomainFact { - internal abstract val nodeType: String - - abstract fun updateActivation(newActivation: JcInst?): TaintNode - - abstract fun moveToOtherPath(newPath: AccessPath): TaintNode - - val activatedCopy: TaintNode - get() = updateActivation(null) - - override fun toString(): String { - return if (activation != null) { - "[$nodeType]: $variable, activation=$activation" - } else { - "[$nodeType]: $variable" - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TaintNode - - if (variable != other.variable) return false - if (activation != other.activation) return false - - return true - } - - override fun hashCode(): Int { - var result = variable.hashCode() - result = 31 * result + (activation?.hashCode() ?: 0) - return result - } -} - -class NpeTaintNode(variable: AccessPath, activation: JcInst? = null): TaintNode(variable, activation) { - override val nodeType: String - get() = "NPE" - - override fun updateActivation(newActivation: JcInst?): NpeTaintNode { - return NpeTaintNode(variable, newActivation) - } - - override fun moveToOtherPath(newPath: AccessPath): NpeTaintNode { - return NpeTaintNode(newPath, activation) - } -} - -data class UnusedVariableNode( - val variable: AccessPath, - val initStatement: JcInst, -) : DomainFact - -class TaintAnalysisNode( - variable: AccessPath, - activation: JcInst? = null, - override val nodeType: String, // = "Taint analysis" -) : TaintNode(variable, activation) { - - constructor(fact: Tainted) : this(fact.variable, nodeType = fact.mark.name) - - override fun updateActivation(newActivation: JcInst?): TaintAnalysisNode { - return TaintAnalysisNode(variable, newActivation, nodeType) - } - - override fun moveToOtherPath(newPath: AccessPath): TaintAnalysisNode { - return TaintAnalysisNode(newPath, activation, nodeType) - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt deleted file mode 100644 index 78219431d..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/UnusedVariableAnalyzer.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.library.analyzers - -import org.jacodb.analysis.engine.AbstractAnalyzer -import org.jacodb.analysis.engine.AnalysisDependentEvent -import org.jacodb.analysis.engine.AnalyzerFactory -import org.jacodb.analysis.engine.CrossUnitCallFact -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.FlowFunctionInstance -import org.jacodb.analysis.engine.FlowFunctionsSpace -import org.jacodb.analysis.engine.IfdsResult -import org.jacodb.analysis.engine.IfdsVertex -import org.jacodb.analysis.engine.NewSummaryFact -import org.jacodb.analysis.engine.VulnerabilityLocation -import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull -import org.jacodb.analysis.sarif.SarifMessage -import org.jacodb.analysis.sarif.VulnerabilityDescription -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcArrayAccess -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcBranchingInst -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcLocal -import org.jacodb.api.cfg.JcSpecialCallExpr -import org.jacodb.api.cfg.JcStaticCallExpr -import org.jacodb.api.cfg.JcTerminatingInst -import org.jacodb.api.cfg.values -import org.jacodb.api.ext.cfg.callExpr - -class UnusedVariableAnalyzer(graph: JcApplicationGraph) : AbstractAnalyzer(graph) { - override val flowFunctions: FlowFunctionsSpace = UnusedVariableForwardFunctions(graph.classpath) - - override val isMainAnalyzer: Boolean - get() = true - - companion object { - const val ruleId: String = "unused-variable" - private val vulnerabilityMessage = SarifMessage("Assigned value is unused") - - val vulnerabilityDescription = VulnerabilityDescription(vulnerabilityMessage, ruleId) - } - - private fun AccessPath.isUsedAt(expr: JcExpr): Boolean { - return this in expr.values.map { it.toPathOrNull() } - } - - private fun AccessPath.isUsedAt(inst: JcInst): Boolean { - val callExpr = inst.callExpr - - if (callExpr != null) { - // Don't count constructor calls as usages - if (callExpr.method.method.isConstructor && isUsedAt((callExpr as JcSpecialCallExpr).instance)) { - return false - } - - return isUsedAt(callExpr) - } - if (inst is JcAssignInst) { - if (inst.lhv is JcArrayAccess && isUsedAt((inst.lhv as JcArrayAccess))) { - return true - } - return isUsedAt(inst.rhv) && (inst.lhv !is JcLocal || inst.rhv !is JcLocal) - } - if (inst is JcTerminatingInst || inst is JcBranchingInst) { - return inst.operands.any { isUsedAt(it) } - } - return false - } - - override fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List { - return emptyList() - } - - override fun handleIfdsResult(ifdsResult: IfdsResult): List = buildList { - val used: MutableMap = mutableMapOf() - ifdsResult.resultFacts.forEach { (inst, facts) -> - facts.filterIsInstance().forEach { fact -> - if (fact.initStatement !in used) { - used[fact.initStatement] = false - } - - if (fact.variable.isUsedAt(inst)) { - used[fact.initStatement] = true - } - } - } - used.filterValues { !it }.keys.map { - add( - NewSummaryFact(VulnerabilityLocation(vulnerabilityDescription, IfdsVertex(it, ZEROFact))) - ) - } - } -} - -val UnusedVariableAnalyzerFactory = AnalyzerFactory { graph -> - UnusedVariableAnalyzer(graph) -} - -private class UnusedVariableForwardFunctions( - val classpath: JcClasspath -) : FlowFunctionsSpace { - - override fun obtainPossibleStartFacts(startStatement: JcInst): Collection { - return listOf(ZEROFact) - } - - override fun obtainSequentFlowFunction(current: JcInst, next: JcInst) = FlowFunctionInstance { fact -> - if (current !is JcAssignInst) { - return@FlowFunctionInstance listOf(fact) - } - - if (fact == ZEROFact) { - val toPath = current.lhv.toPathOrNull() ?: return@FlowFunctionInstance listOf(ZEROFact) - return@FlowFunctionInstance if (!toPath.isOnHeap) { - listOf(ZEROFact, UnusedVariableNode(toPath, current)) - } else { - listOf(ZEROFact) - } - } - - if (fact !is UnusedVariableNode) { - return@FlowFunctionInstance emptyList() - } - - val default = if (fact.variable == current.lhv.toPathOrNull()) emptyList() else listOf(fact) - val fromPath = current.rhv.toPathOrNull() ?: return@FlowFunctionInstance default - val toPath = current.lhv.toPathOrNull() ?: return@FlowFunctionInstance default - - if (fromPath.isOnHeap || toPath.isOnHeap) { - return@FlowFunctionInstance default - } - - if (fromPath == fact.variable) { - return@FlowFunctionInstance default.plus(UnusedVariableNode(toPath, fact.initStatement)) - } - default - } - - override fun obtainCallToStartFlowFunction(callStatement: JcInst, callee: JcMethod) = FlowFunctionInstance { fact -> - val callExpr = callStatement.callExpr ?: error("Call expr is expected to be not-null") - val formalParams = classpath.getArgumentsOf(callee) - - if (fact == ZEROFact) { - // We don't show unused parameters for virtual calls - if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { - return@FlowFunctionInstance listOf(ZEROFact) - } - return@FlowFunctionInstance formalParams.map { UnusedVariableNode(it.toPath(), callStatement) }.plus(ZEROFact) - } - - if (fact !is UnusedVariableNode) { - return@FlowFunctionInstance emptyList() - } - - emptyList() - } - - override fun obtainCallToReturnFlowFunction(callStatement: JcInst, returnSite: JcInst, graph: JcApplicationGraph) = - obtainSequentFlowFunction(callStatement, returnSite) - - override fun obtainExitToReturnSiteFlowFunction( - callStatement: JcInst, - returnSite: JcInst, - exitStatement: JcInst, - ) = FlowFunctionInstance { fact -> - if (fact == ZEROFact) { - listOf(ZEROFact) - } else { - emptyList() - } - } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt similarity index 73% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt index 97dea41de..c4468433b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt @@ -14,22 +14,20 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint.npe +package org.jacodb.analysis.npe -import mu.KotlinLogging import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator -import org.jacodb.analysis.ifds2.Analyzer -import org.jacodb.analysis.ifds2.taint.EdgeForOtherRunner -import org.jacodb.analysis.ifds2.taint.NewSummaryEdge -import org.jacodb.analysis.ifds2.taint.NewVulnerability -import org.jacodb.analysis.ifds2.taint.TaintEdge -import org.jacodb.analysis.ifds2.taint.TaintEvent -import org.jacodb.analysis.ifds2.taint.TaintFact -import org.jacodb.analysis.ifds2.taint.TaintVertex -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.ifds2.taint.Vulnerability -import org.jacodb.analysis.paths.isDereferencedAt +import org.jacodb.analysis.ifds.Analyzer +import org.jacodb.analysis.taint.EdgeForOtherRunner +import org.jacodb.analysis.taint.NewSummaryEdge +import org.jacodb.analysis.taint.NewVulnerability +import org.jacodb.analysis.taint.TaintEdge +import org.jacodb.analysis.taint.TaintEvent +import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.TaintVertex +import org.jacodb.analysis.taint.Tainted +import org.jacodb.analysis.taint.Vulnerability import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.cfg.callExpr @@ -37,7 +35,7 @@ import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintMark import org.jacodb.taint.configuration.TaintMethodSink -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} class NpeAnalyzer( private val graph: JcApplicationGraph, @@ -54,11 +52,6 @@ class NpeAnalyzer( return statement in graph.exitPoints(statement.location.method) } - private fun isSink(statement: JcInst, fact: TaintFact): Boolean { - // TODO - return false - } - override fun handleNewEdge( edge: TaintEdge, ): List = buildList { @@ -75,7 +68,6 @@ class NpeAnalyzer( } } - var defaultBehavior = true run { val callExpr = edge.to.statement.callExpr ?: return@run val callee = callExpr.method.method @@ -95,7 +87,6 @@ class NpeAnalyzer( ) for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { - defaultBehavior = false logger.trace { "Found sink at ${edge.to} in ${edge.method} on $item" } val message = item.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = item) @@ -103,16 +94,6 @@ class NpeAnalyzer( } } } - - if (defaultBehavior) { - // Default ("config"-less) behavior: - if (isSink(edge.to.statement, edge.to.fact)) { - logger.trace { "Found sink at ${edge.to} in ${edge.method}" } - val message = "SINK" // TODO - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) - add(NewVulnerability(vulnerability)) - } - } } override fun handleCrossUnitCall( diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt similarity index 96% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index 0fb3963e0..e1140c02e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -14,32 +14,29 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint.npe +package org.jacodb.analysis.npe -import mu.KotlinLogging import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.config.TaintActionEvaluator -import org.jacodb.analysis.ifds2.FlowFunction -import org.jacodb.analysis.ifds2.FlowFunctions -import org.jacodb.analysis.ifds2.taint.TaintFact -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.ifds2.taint.Zero -import org.jacodb.analysis.library.analyzers.getArgument -import org.jacodb.analysis.library.analyzers.getArgumentsOf -import org.jacodb.analysis.library.analyzers.thisInstance -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.analysis.paths.ElementAccessor -import org.jacodb.analysis.paths.Maybe -import org.jacodb.analysis.paths.isDereferencedAt -import org.jacodb.analysis.paths.minus -import org.jacodb.analysis.paths.onSome -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toMaybe -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.ElementAccessor +import org.jacodb.analysis.ifds.FlowFunction +import org.jacodb.analysis.ifds.FlowFunctions +import org.jacodb.analysis.ifds.Maybe +import org.jacodb.analysis.ifds.onSome +import org.jacodb.analysis.ifds.toMaybe +import org.jacodb.analysis.ifds.toPath +import org.jacodb.analysis.ifds.toPathOrNull +import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.Tainted +import org.jacodb.analysis.taint.Zero +import org.jacodb.analysis.util.getArgument +import org.jacodb.analysis.util.getArgumentsOf +import org.jacodb.analysis.util.startsWith +import org.jacodb.analysis.util.thisInstance import org.jacodb.api.JcArrayType import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod @@ -79,7 +76,7 @@ import org.jacodb.taint.configuration.TaintMethodSource import org.jacodb.taint.configuration.TaintPassThrough import org.jacodb.taint.configuration.This -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} class ForwardNpeFlowFunctions( private val cp: JcClasspath, @@ -101,7 +98,7 @@ class ForwardNpeFlowFunctions( for (p in method.parameters.filter { it.isNullable != false }) { val t = cp.findTypeOrNull(p.type)!! val arg = JcArgument.of(p.index, p.name, t) - val path = AccessPath.from(arg) + val path = arg.toPath() add(Tainted(path, TaintMark.NULLNESS)) } } @@ -126,7 +123,7 @@ class ForwardNpeFlowFunctions( // Note: both condition and action evaluator require a custom position resolver. val conditionEvaluator = BasicConditionEvaluator { position -> when (position) { - This -> Maybe.some( method.thisInstance) + This -> Maybe.some(method.thisInstance) is Argument -> { val p = method.parameters[position.index] diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt similarity index 86% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index e5cb56a4e..38cf1b8e5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint.npe +package org.jacodb.analysis.npe import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart @@ -29,24 +29,23 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import mu.KotlinLogging -import org.jacodb.analysis.engine.SummaryStorageImpl -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.UnitType -import org.jacodb.analysis.ifds2.ControlEvent -import org.jacodb.analysis.ifds2.Manager -import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.UniRunner -import org.jacodb.analysis.ifds2.pathEdges -import org.jacodb.analysis.ifds2.taint.EdgeForOtherRunner -import org.jacodb.analysis.ifds2.taint.NewSummaryEdge -import org.jacodb.analysis.ifds2.taint.NewVulnerability -import org.jacodb.analysis.ifds2.taint.SummaryEdge -import org.jacodb.analysis.ifds2.taint.TaintEdge -import org.jacodb.analysis.ifds2.taint.TaintEvent -import org.jacodb.analysis.ifds2.taint.TaintFact -import org.jacodb.analysis.ifds2.taint.TaintRunner -import org.jacodb.analysis.ifds2.taint.Vulnerability +import org.jacodb.analysis.ifds.ControlEvent +import org.jacodb.analysis.ifds.Manager +import org.jacodb.analysis.ifds.QueueEmptinessChanged +import org.jacodb.analysis.ifds.SummaryStorageImpl +import org.jacodb.analysis.ifds.UniRunner +import org.jacodb.analysis.ifds.UnitResolver +import org.jacodb.analysis.ifds.UnitType +import org.jacodb.analysis.taint.EdgeForOtherRunner +import org.jacodb.analysis.taint.NewSummaryEdge +import org.jacodb.analysis.taint.NewVulnerability +import org.jacodb.analysis.taint.SummaryEdge +import org.jacodb.analysis.taint.TaintEdge +import org.jacodb.analysis.taint.TaintEvent +import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.TaintRunner +import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.analysis.util.getGetPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import java.util.concurrent.ConcurrentHashMap @@ -56,7 +55,7 @@ import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime import kotlin.time.TimeSource -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} class NpeManager( private val graph: JcApplicationGraph, @@ -130,11 +129,16 @@ class NpeManager( delay(1.seconds) logger.info { "Progress: total propagated ${ - runnerForUnit.values.sumOf { it.pathEdges.size } + runnerForUnit.values.sumOf { it.getGetPathEdges().size } } path edges" } } logger.info { "Progress job finished" } + logger.info { + "Progress: total propagated ${ + runnerForUnit.values.sumOf { it.getGetPathEdges().size } + } path edges" + } } // Spawn stopper job: @@ -177,7 +181,7 @@ class NpeManager( logger.debug { "$vulnerability in ${vulnerability.method}" } } logger.debug { "Total sinks: ${foundVulnerabilities.size}" } - logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } + logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.getGetPathEdges().size }} path edges" } } logger.info { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/utils.kt new file mode 100644 index 000000000..3a0bbd27e --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/utils.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.npe + +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.toPathOrNull +import org.jacodb.analysis.util.startsWith +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcInstanceCallExpr +import org.jacodb.api.cfg.JcLengthExpr +import org.jacodb.api.cfg.values + +fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { + if (this == null) { + return false + } + + if (expr is JcInstanceCallExpr) { + val instancePath = expr.instance.toPathOrNull() + if (instancePath.startsWith(this)) { + return true + } + } + + if (expr is JcLengthExpr) { + val arrayPath = expr.array.toPathOrNull() + if (arrayPath.startsWith(this)) { + return true + } + } + + return expr.values + .mapNotNull { it.toPathOrNull() } + .any { + (it - this)?.isNotEmpty() == true + } +} + +fun AccessPath?.isDereferencedAt(inst: JcInst): Boolean { + if (this == null) return false + return inst.operands.any { isDereferencedAt(it) } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt deleted file mode 100644 index cfe71d6ea..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/paths/Util.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.paths - -import org.jacodb.api.cfg.JcArrayAccess -import org.jacodb.api.cfg.JcCastExpr -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcFieldRef -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstanceCallExpr -import org.jacodb.api.cfg.JcLengthExpr -import org.jacodb.api.cfg.JcSimpleValue -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.cfg.values - -/** - * Converts `JcExpr` (in particular, `JcValue`) to `AccessPath`. - * - For `JcSimpleValue`, this method simply wraps the value. - * - For `JcArrayAccess` and `JcFieldRef`, this method "reverses" it and recursively constructs a list of accessors (`ElementAccessor` for array access, `FieldAccessor` for field access). - * - Returns `null` when the conversion to `AccessPath` is not possible. - * - * Example: - * `x.f[0].y` is `AccessPath(value = x, accesses = [Field(f), Element(0), Field(y)])` - */ -internal fun JcExpr.toPathOrNull(): AccessPath? { - return when (this) { - is JcSimpleValue -> { - AccessPath.from(this) - } - - is JcCastExpr -> { - operand.toPathOrNull() - } - - is JcArrayAccess -> { - array.toPathOrNull()?.let { - it / listOf(ElementAccessor(index)) - } - } - - is JcFieldRef -> { - val instance = instance // enables smart cast - - if (instance == null) { - AccessPath.fromStaticField(field.field) - } else { - instance.toPathOrNull()?.let { it / FieldAccessor(field.field) } - } - } - - else -> null - } -} - -internal fun JcValue.toPath(): AccessPath { - return toPathOrNull() ?: error("Unable to build access path for value $this") -} - -// this = value.x.y[0] -// this = (value, accesses = listOf(Field, Field, Element)) -// -// other = value.x -// other = (value, accesses = listOf(Field)) -// -// (this - other) = listOf(Field, Element) = ".y[0]" -internal operator fun AccessPath?.minus(other: AccessPath): List? { - if (this == null) { - return null - } - if (value != other.value) { - return null - } - if (this.accesses.take(other.accesses.size) != other.accesses) { - return null - } - - return accesses.drop(other.accesses.size) -} - -internal fun AccessPath?.startsWith(other: AccessPath?): Boolean { - if (this == null || other == null) { - return false - } - if (this.value != other.value) { - return false - } - // Unnecessary check: - // if (this.accesses.size < other.accesses.size) { - // return false - // } - return this.accesses.take(other.accesses.size) == other.accesses -} - -fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { - if (this == null) { - return false - } - - if (expr is JcInstanceCallExpr) { - val instancePath = expr.instance.toPathOrNull() - if (instancePath.startsWith(this)) { - return true - } - } - - if (expr is JcLengthExpr) { - val arrayPath = expr.array.toPathOrNull() - if (arrayPath.startsWith(this)) { - return true - } - } - - return expr.values - .mapNotNull { it.toPathOrNull() } - .any { - (it - this)?.isNotEmpty() == true - } -} - -fun AccessPath?.isDereferencedAt(inst: JcInst): Boolean { - if (this == null) { - return false - } - - return inst.operands.any { isDereferencedAt(it) } -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt deleted file mode 100644 index 0024c4b70..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/DataClasses.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.sarif - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.encodeToStream -import org.jacodb.analysis.engine.IfdsVertex -import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.api.JcMethod -import org.jacodb.api.cfg.JcInst -import java.io.OutputStream -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.Path - -@Serializable -data class SarifMessage(val text: String) - -@Serializable -data class SarifArtifactLocation(val uri: String) - -@Serializable -data class SarifRegion(val startLine: Int) - -@Serializable -data class SarifPhysicalLocation(val artifactLocation: SarifArtifactLocation, val region: SarifRegion) - -@Serializable -data class SarifLogicalLocation(val fullyQualifiedName: String) - -@Serializable -data class SarifLocation(val physicalLocation: SarifPhysicalLocation, val logicalLocations: List) { - companion object { - private val JcMethod.fullyQualifiedName: String - get() = "${enclosingClass.name}#${name}" - - private val currentPath: Path = Paths.get("").toAbsolutePath() - - fun fromInst(inst: JcInst): SarifLocation = SarifLocation( - physicalLocation = SarifPhysicalLocation( - SarifArtifactLocation("${currentPath.relativize(Path(inst.location.method.declaration.location.path))}"), - SarifRegion(inst.location.lineNumber) - ), - logicalLocations = listOf( - SarifLogicalLocation(inst.location.method.fullyQualifiedName) - ) - ) - } -} - -@Serializable -data class SarifDomainFact(val text: String) - -@Serializable -data class SarifState(val domainFact: SarifDomainFact) - -@Serializable -data class SarifThreadFlowLocation(val location: SarifLocation, val state: SarifState) - -@Serializable -data class SarifThreadFlow(val locations: List) - -@Serializable -data class SarifCodeFlow(val threadFlows: List) { - companion object { - fun fromTrace(trace: List): SarifCodeFlow { - val threadFlow = trace.map { - SarifThreadFlowLocation( - SarifLocation.fromInst(it.statement), - SarifState(SarifDomainFact(it.domainFact.toString())) - ) - } - return SarifCodeFlow(listOf(SarifThreadFlow(threadFlow))) - } - } -} - -@Serializable -data class SarifResult( - val ruleId: String, - val message: SarifMessage, - val level: SarifSeverityLevel, - val locations: List, - val codeFlows: List -) { - companion object { - fun fromVulnerabilityInstance(instance: VulnerabilityInstance, maxPathsCount: Int): SarifResult = SarifResult( - instance.location.vulnerabilityDescription.ruleId, - instance.location.vulnerabilityDescription.message, - instance.location.vulnerabilityDescription.level, - listOf(SarifLocation.fromInst(instance.traceGraph.sink.statement)), - instance.traceGraph.getAllTraces().take(maxPathsCount).map { SarifCodeFlow.fromTrace(it) }.toList() - ) - } -} - -@Serializable -data class SarifDriver( - val name: String, - val version: String, - val informationUri: String, -) - -@Serializable -data class SarifTool(val driver: SarifDriver) - -val IfdsTool = SarifTool( - SarifDriver( - name = "JaCo-IFDS", - version = "1.2.0", - informationUri = "https://github.com/UnitTestBot/jacodb/blob/develop/jacodb-analysis/README.md" - ) -) - -@Serializable -data class SarifRun(val tool: SarifTool, val results: List) - -@OptIn(ExperimentalSerializationApi::class) -@Serializable -data class SarifReport( - val version: String, - - @SerialName("\$schema") - val schema: String, - - val runs: List -) { - fun encodeToStream(stream: OutputStream) { - json.encodeToStream(this, stream) - } - - companion object { - private const val defaultVersion = - "2.1.0" - private const val defaultSchema = - "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" - - private const val defaultPathsCount: Int = 3 - - private val json = Json { - prettyPrint = true - encodeDefaults = false - } - - fun fromVulnerabilities( - vulnerabilities: List, - pathsCount: Int = defaultPathsCount - ): SarifReport = SarifReport( - version = defaultVersion, - schema = defaultSchema, - runs = listOf( - SarifRun( - IfdsTool, - vulnerabilities.map { SarifResult.fromVulnerabilityInstance(it, pathsCount) } - ) - ) - ) - } -} - -@Serializable -enum class SarifSeverityLevel { - @SerialName("error") ERROR, - @SerialName("warning") WARNING, - @SerialName("note") NOTE -} - -data class VulnerabilityDescription( - val message: SarifMessage, - val ruleId: String, - val level: SarifSeverityLevel = SarifSeverityLevel.WARNING -) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Sarif.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Sarif.kt new file mode 100644 index 000000000..4101a14b8 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Sarif.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.sarif + +import io.github.detekt.sarif4k.ArtifactLocation +import io.github.detekt.sarif4k.CodeFlow +import io.github.detekt.sarif4k.Location +import io.github.detekt.sarif4k.LogicalLocation +import io.github.detekt.sarif4k.Message +import io.github.detekt.sarif4k.MultiformatMessageString +import io.github.detekt.sarif4k.PhysicalLocation +import io.github.detekt.sarif4k.Region +import io.github.detekt.sarif4k.Result +import io.github.detekt.sarif4k.Run +import io.github.detekt.sarif4k.SarifSchema210 +import io.github.detekt.sarif4k.ThreadFlow +import io.github.detekt.sarif4k.ThreadFlowLocation +import io.github.detekt.sarif4k.Tool +import io.github.detekt.sarif4k.ToolComponent +import io.github.detekt.sarif4k.Version +import org.jacodb.analysis.ifds.Vertex +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst +import java.io.File + +private const val SARIF_SCHEMA = + "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" +private const val JACODB_INFORMATION_URI = + "https://github.com/UnitTestBot/jacodb/blob/develop/jacodb-analysis/README.md" +private const val DEFAULT_PATH_COUNT = 3 + +fun sarifReportFromVulnerabilities( + vulnerabilities: List>, + maxPathsCount: Int = DEFAULT_PATH_COUNT, + isDeduplicate: Boolean = true, + sourceFileResolver: SourceFileResolver = SourceFileResolver { null }, +): SarifSchema210 { + return SarifSchema210( + schema = SARIF_SCHEMA, + version = Version.The210, + runs = listOf( + Run( + tool = Tool( + driver = ToolComponent( + name = "jacodb-analysis", + organization = "UnitTestBot", + version = "1.4.5", + informationURI = JACODB_INFORMATION_URI, + ) + ), + results = vulnerabilities.map { instance -> + Result( + ruleID = instance.description.ruleId, + message = Message( + text = instance.description.message + ), + level = instance.description.level, + locations = listOf(instToSarifLocation(instance.traceGraph.sink.statement, sourceFileResolver)), + codeFlows = instance.traceGraph + .getAllTraces() + .take(maxPathsCount) + .map { traceToSarifCodeFlow(it, sourceFileResolver, isDeduplicate) } + .toList(), + ) + } + ) + ) + ) +} + +private val JcMethod.fullyQualifiedName: String + get() = "${enclosingClass.name}#${name}" + +private fun instToSarifLocation(inst: JcInst, sourceFileResolver: SourceFileResolver): Location { + val sourceLocation = sourceFileResolver.resolve(inst) + ?: run { + val registeredLocation = inst.location.method.declaration.location + val classFile = inst.location.method.enclosingClass.name + .replace('.', '/') + ".class" + File(registeredLocation.path).resolve(classFile).path + } + return Location( + physicalLocation = PhysicalLocation( + artifactLocation = ArtifactLocation( + uri = sourceLocation + ), + region = Region( + startLine = inst.location.lineNumber.toLong() + ) + ), + logicalLocations = listOf( + LogicalLocation( + fullyQualifiedName = inst.location.method.fullyQualifiedName + ) + ) + ) +} + +private fun traceToSarifCodeFlow( + trace: List>, + sourceFileResolver: SourceFileResolver, + isDeduplicate: Boolean = true, +): CodeFlow { + return CodeFlow( + threadFlows = listOf( + ThreadFlow( + locations = trace.map { + ThreadFlowLocation( + location = instToSarifLocation(it.statement, sourceFileResolver), + state = mapOf( + "fact" to MultiformatMessageString( + text = it.fact.toString() + ) + ) + ) + }.let { + if (isDeduplicate) it.deduplicate() else it + } + ) + ) + ) +} + +private fun List.deduplicate(): List { + if (isEmpty()) return emptyList() + + return listOf(first()) + zipWithNext { a, b -> + val aLine = a.location!!.physicalLocation!!.region!!.startLine!! + val bLine = b.location!!.physicalLocation!!.region!!.startLine!! + if (aLine != bLine) { + b + } else { + null + } + }.filterNotNull() +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/SourceFileResolver.kt similarity index 77% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/SourceFileResolver.kt index 7894244da..c30f15486 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/Trace.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/SourceFileResolver.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2 +package org.jacodb.analysis.sarif -data class TraceGraph( - val sink: Vertex, - val sources: Set>, - val edges: Map, Set>>, -) +import org.jacodb.api.cfg.JcInst + +fun interface SourceFileResolver { + fun resolve(inst: JcInst): String? +} diff --git a/jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt similarity index 61% rename from jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt index 3e9976978..dac3f04f7 100644 --- a/jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt @@ -14,18 +14,18 @@ * limitations under the License. */ -package org.jacodb.cli +package org.jacodb.analysis.sarif -import org.jacodb.testing.analysis.NpeExamples -import org.junit.jupiter.api.Test +import io.github.detekt.sarif4k.Level +import org.jacodb.analysis.ifds.TraceGraph -class CliTest { - @Test - fun `test basic analysis cli api`() { - val args = listOf( - "-a", CliTest::class.java.getResource("/config.json")?.file ?: error("Can't find file with config"), - "-s", NpeExamples::class.java.name - ) - AnalysisMain().run(args) - } -} \ No newline at end of file +data class VulnerabilityInstance( + val traceGraph: TraceGraph, + val description: VulnerabilityDescription, +) + +data class VulnerabilityDescription( + val message: String, + val ruleId: String, + val level: Level = Level.Warning, +) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt similarity index 92% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt index 2831e7311..6f298eca9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt @@ -14,20 +14,19 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.UnitType -import org.jacodb.analysis.ifds2.Aggregate -import org.jacodb.analysis.ifds2.ControlEvent -import org.jacodb.analysis.ifds2.Edge -import org.jacodb.analysis.ifds2.Manager -import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.Runner +import org.jacodb.analysis.ifds.Aggregate +import org.jacodb.analysis.ifds.ControlEvent +import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Manager +import org.jacodb.analysis.ifds.QueueEmptinessChanged +import org.jacodb.analysis.ifds.UnitResolver +import org.jacodb.analysis.ifds.UnitType import org.jacodb.api.JcMethod class BidiRunner( @@ -36,7 +35,7 @@ class BidiRunner( override val unit: UnitType, newForwardRunner: (Manager) -> TaintRunner, newBackwardRunner: (Manager) -> TaintRunner, -) : Runner { +) : TaintRunner { @Volatile private var forwardQueueIsEmpty: Boolean = false diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt similarity index 82% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt index b90c6a1d3..e9cee3617 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt @@ -14,20 +14,19 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint -import mu.KotlinLogging import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator -import org.jacodb.analysis.ifds2.Analyzer -import org.jacodb.analysis.ifds2.Edge +import org.jacodb.analysis.ifds.Analyzer +import org.jacodb.analysis.ifds.Edge import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.cfg.callExpr import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintMethodSink -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} class TaintAnalyzer( private val graph: JcApplicationGraph, @@ -44,11 +43,6 @@ class TaintAnalyzer( return statement in graph.exitPoints(statement.location.method) } - private fun isSink(statement: JcInst, fact: TaintFact): Boolean { - // TODO - return false - } - override fun handleNewEdge( edge: TaintEdge, ): List = buildList { @@ -56,7 +50,6 @@ class TaintAnalyzer( add(NewSummaryEdge(edge)) } - var defaultBehavior = true run { val callExpr = edge.to.statement.callExpr ?: return@run val callee = callExpr.method.method @@ -76,7 +69,6 @@ class TaintAnalyzer( ) for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { - defaultBehavior = false val message = item.ruleNote val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = item) logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } @@ -84,16 +76,6 @@ class TaintAnalyzer( } } } - - if (defaultBehavior) { - // Default ("config"-less) behavior: - if (isSink(edge.to.statement, edge.to.fact)) { - val message = "SINK" // TODO - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) - logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } - add(NewVulnerability(vulnerability)) - } - } } override fun handleCrossUnitCall( diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt similarity index 96% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt index c37dc7dad..cd345ff6b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintEvents.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint sealed interface TaintEvent diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt similarity index 61% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt index 7013e6b44..5da89d340 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFacts.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt @@ -14,25 +14,18 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint -import org.jacodb.analysis.engine.DomainFact -import org.jacodb.analysis.engine.ZEROFact -import org.jacodb.analysis.library.analyzers.NpeTaintNode -import org.jacodb.analysis.library.analyzers.TaintAnalysisNode -import org.jacodb.analysis.library.analyzers.TaintNode -import org.jacodb.analysis.paths.AccessPath +import org.jacodb.analysis.ifds.AccessPath import org.jacodb.taint.configuration.TaintMark sealed interface TaintFact object Zero : TaintFact { - override fun toString(): String = this.javaClass.simpleName + override fun toString(): String = javaClass.simpleName } data class Tainted( val variable: AccessPath, val mark: TaintMark, -) : TaintFact { - constructor(fact: TaintNode) : this(fact.variable, TaintMark(fact.nodeType)) -} +) : TaintFact diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt similarity index 97% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt index 8d6b0fee6..876c56b14 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt @@ -14,27 +14,25 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint -import mu.KotlinLogging import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.config.TaintActionEvaluator -import org.jacodb.analysis.ifds2.FlowFunction -import org.jacodb.analysis.ifds2.FlowFunctions -import org.jacodb.analysis.library.analyzers.getArgument -import org.jacodb.analysis.library.analyzers.getArgumentsOf -import org.jacodb.analysis.library.analyzers.thisInstance -import org.jacodb.analysis.paths.ElementAccessor -import org.jacodb.analysis.paths.Maybe -import org.jacodb.analysis.paths.minus -import org.jacodb.analysis.paths.onSome -import org.jacodb.analysis.paths.startsWith -import org.jacodb.analysis.paths.toMaybe -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.paths.toPathOrNull +import org.jacodb.analysis.ifds.ElementAccessor +import org.jacodb.analysis.ifds.FlowFunction +import org.jacodb.analysis.ifds.FlowFunctions +import org.jacodb.analysis.ifds.Maybe +import org.jacodb.analysis.ifds.onSome +import org.jacodb.analysis.ifds.toMaybe +import org.jacodb.analysis.ifds.toPath +import org.jacodb.analysis.ifds.toPathOrNull +import org.jacodb.analysis.util.getArgument +import org.jacodb.analysis.util.getArgumentsOf +import org.jacodb.analysis.util.startsWith +import org.jacodb.analysis.util.thisInstance import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph @@ -63,7 +61,7 @@ import org.jacodb.taint.configuration.TaintMethodSource import org.jacodb.taint.configuration.TaintPassThrough import org.jacodb.taint.configuration.This -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} class ForwardTaintFlowFunctions( private val cp: JcClasspath, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt similarity index 90% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index cef67b85a..74b68bcbb 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart @@ -29,18 +29,17 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import mu.KotlinLogging -import org.jacodb.analysis.engine.SummaryStorageImpl -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.UnitType -import org.jacodb.analysis.engine.UnknownUnit import org.jacodb.analysis.graph.reversed -import org.jacodb.analysis.ifds2.Aggregate -import org.jacodb.analysis.ifds2.ControlEvent -import org.jacodb.analysis.ifds2.Manager -import org.jacodb.analysis.ifds2.QueueEmptinessChanged -import org.jacodb.analysis.ifds2.UniRunner -import org.jacodb.analysis.ifds2.pathEdges +import org.jacodb.analysis.ifds.Aggregate +import org.jacodb.analysis.ifds.ControlEvent +import org.jacodb.analysis.ifds.Manager +import org.jacodb.analysis.ifds.QueueEmptinessChanged +import org.jacodb.analysis.ifds.SummaryStorageImpl +import org.jacodb.analysis.ifds.UniRunner +import org.jacodb.analysis.ifds.UnitResolver +import org.jacodb.analysis.ifds.UnitType +import org.jacodb.analysis.ifds.UnknownUnit +import org.jacodb.analysis.util.getGetPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import java.util.concurrent.ConcurrentHashMap @@ -50,7 +49,7 @@ import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime import kotlin.time.TimeSource -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} class TaintManager( private val graph: JcApplicationGraph, @@ -167,11 +166,16 @@ class TaintManager( delay(1.seconds) logger.info { "Progress: total propagated ${ - runnerForUnit.values.sumOf { it.pathEdges.size } + runnerForUnit.values.sumOf { it.getGetPathEdges().size } } path edges" } } logger.info { "Progress job finished" } + logger.info { + "Progress: total propagated ${ + runnerForUnit.values.sumOf { it.getGetPathEdges().size } + } path edges" + } } // Spawn stopper job: @@ -215,7 +219,7 @@ class TaintManager( } logger.debug { "Total sinks: ${foundVulnerabilities.size}" } - logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.pathEdges.size }} path edges" } + logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.getGetPathEdges().size }} path edges" } } logger.info { @@ -275,7 +279,7 @@ class TaintManager( .launchIn(scope) } - fun getAggregates() : Map> { + fun getAggregates(): Map> { return runnerForUnit.mapValues { it.value.getAggregate() } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt similarity index 90% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt index 4985287f2..37dfad8fa 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/TaintSummaries.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint -import org.jacodb.analysis.engine.SummaryFact +import org.jacodb.analysis.ifds.Summary import org.jacodb.api.JcMethod import org.jacodb.taint.configuration.TaintMethodSink @@ -26,7 +26,7 @@ import org.jacodb.taint.configuration.TaintMethodSink */ data class SummaryEdge( val edge: TaintEdge, -) : SummaryFact { +) : Summary { override val method: JcMethod get() = edge.method } @@ -36,7 +36,7 @@ data class Vulnerability( val sink: TaintVertex, val edge: TaintEdge? = null, val rule: TaintMethodSink? = null, -) : SummaryFact { +) : Summary { override val method: JcMethod get() = sink.method } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt similarity index 82% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt index 1c947ece8..c6617e418 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds2/taint/types.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.jacodb.analysis.ifds2.taint +package org.jacodb.analysis.taint -import org.jacodb.analysis.ifds2.Edge -import org.jacodb.analysis.ifds2.Runner -import org.jacodb.analysis.ifds2.Vertex +import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Runner +import org.jacodb.analysis.ifds.Vertex typealias TaintVertex = Vertex typealias TaintEdge = Edge diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt similarity index 56% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt index 82cdd575b..6805e8afe 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/library/analyzers/Util.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt @@ -14,11 +14,13 @@ * limitations under the License. */ -package org.jacodb.analysis.library.analyzers +package org.jacodb.analysis.util -import org.jacodb.analysis.paths.AccessPath -import org.jacodb.analysis.paths.minus -import org.jacodb.analysis.paths.startsWith +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Runner +import org.jacodb.analysis.ifds.UniRunner +import org.jacodb.analysis.taint.BidiRunner import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.JcParameter @@ -38,29 +40,18 @@ fun JcClasspath.getArgumentsOf(method: JcMethod): List { return method.parameters.map { getArgument(it)!! } } -fun normalFactFlow( - fact: TaintNode, - fromPath: AccessPath, - toPath: AccessPath, - dropFact: Boolean, - maxPathLength: Int, -): List { - val factPath = fact.variable - val default = if (dropFact) emptyList() else listOf(fact) +fun Runner<*>.getGetPathEdges(): Set> = when (this) { + is UniRunner<*, *> -> pathEdges + is BidiRunner -> forwardRunner.getGetPathEdges() + backwardRunner.getGetPathEdges() + else -> error("Cannot extract pathEdges for $this") +} - // Second clause is important here as it saves from false positive aliases, see - // #AnalysisTest.`dereferencing copy of value saved before null assignment produce no npe` - val diff = factPath.minus(fromPath) - if (diff != null && (fact.activation == null || fromPath != factPath)) { - val newPath = (toPath / diff).limit(maxPathLength) - return default - .plus(fact.moveToOtherPath(newPath)) - .distinct() +fun AccessPath?.startsWith(other: AccessPath?): Boolean { + if (this == null || other == null) { + return false } - - if (factPath.startsWith(toPath)) { - return emptyList() + if (this.value != other.value) { + return false } - - return default + return this.accesses.take(other.accesses.size) == other.accesses } diff --git a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java index 6e9c59625..a3ba9d89c 100644 --- a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java +++ b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java @@ -16,12 +16,9 @@ package org.jacodb.analysis.impl; -import org.jacodb.analysis.AnalysisMain; -import org.jacodb.analysis.engine.IfdsUnitRunnerFactory; -import org.jacodb.analysis.engine.UnitResolver; -import org.jacodb.analysis.engine.UnitResolverKt; +import org.jacodb.analysis.ifds.UnitResolver; import org.jacodb.analysis.graph.ApplicationGraphFactory; -import org.jacodb.analysis.library.RunnersLibrary; +import org.jacodb.analysis.ifds.UnitResolverKt; import org.jacodb.api.JcClassOrInterface; import org.jacodb.api.JcClasspath; import org.jacodb.api.JcDatabase; @@ -59,15 +56,7 @@ public void testJavaAnalysisApi() throws ExecutionException, InterruptedExceptio .newApplicationGraphForAnalysisAsync(classpath, null) .get(); UnitResolver resolver = UnitResolverKt.getMethodUnitResolver(); - IfdsUnitRunnerFactory runner = RunnersLibrary.getUnusedVariableRunnerFactory(); - - AnalysisMain.runAnalysis( - applicationGraph, - resolver, - runner, - methodsToAnalyze, - Integer.MAX_VALUE - ); + // TODO: run analysis } @Test diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt deleted file mode 100644 index b0d1d576a..000000000 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/AliasAnalysisTest.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.impl - -import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.engine.MethodUnitResolver -import org.jacodb.analysis.graph.defaultBannedPackagePrefixes -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.analyzers.TaintAnalysisNode -import org.jacodb.analysis.library.analyzers.TaintNode -import org.jacodb.analysis.library.newAliasRunnerFactory -import org.jacodb.analysis.paths.toPath -import org.jacodb.analysis.runAnalysis -import org.jacodb.api.JcMethod -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.ext.cfg.callExpr -import org.jacodb.api.ext.findClass -import org.jacodb.testing.BaseTest -import org.jacodb.testing.WithGlobalDB -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream -import kotlin.streams.asStream - -@Disabled("Needs modifications after introduction of summaries") -class AliasAnalysisTest : BaseTest() { - companion object : WithGlobalDB() { - - @JvmStatic - fun provideForPointerBenchBasic(): Stream = listOf( - Arguments.of("Branching1", listOf("38"), listOf("35")), - Arguments.of("Interprocedural1", listOf("48", "49"), listOf("41", "42")), - Arguments.of("Interprocedural2", listOf("51", "52"), listOf("43", "44", "58")), - Arguments.of("Parameter1", listOf("35", "arg$0"), emptyList()), - Arguments.of("Parameter2", listOf("37", "arg$0"), emptyList()), - Arguments.of("ReturnValue1", listOf("41", "42"), emptyList()), - Arguments.of("ReturnValue2", listOf("43", "45"), listOf("44")), - Arguments.of("ReturnValue3", listOf("46"), listOf("44", "45", "47")), - Arguments.of("SimpleAlias1", listOf("37", "38"), emptyList()) - // Loops1 Loops2 and Recursion1 are not tested because it is difficult to represent them as taint problem - ) - .asSequence() - .asStream() - - @JvmStatic - fun provideForPointerBenchGeneralJava(): Stream = listOf( - Arguments.of("Exception1", listOf("37", "38"), emptyList()), - - // Null1 and Null2 are not tested because it is difficult to represent them as taint problems - // Exception2 isn't tested because it needs analysis for possibly thrown exceptions - - Arguments.of("Interface1", listOf("40", "45"), listOf("38", "42", "43")), - Arguments.of("OuterClass1", listOf("55", "51"), listOf("50", "53")), - Arguments.of("StaticVariables1", listOf("39", "42", "StaticVariables1.a"), emptyList()), - Arguments.of("SuperClasses1", listOf("38", "42"), listOf("37", "40")), - ) - .asSequence() - .asStream() - - @JvmStatic - fun provideForPointerBenchCornerCases(): Stream = listOf( - Arguments.of("AccessPath1", listOf("38.f", "39.f"), listOf("38", "39")), - Arguments.of("ContextSensitivity1", listOf("arg$0", "arg$1"), emptyList()), - Arguments.of("ContextSensitivity2", listOf("arg$0", "arg$1"), emptyList()), - Arguments.of("ContextSensitivity3", listOf("arg$0", "arg$1"), emptyList()), - Arguments.of("FieldSensitivity1", listOf("42", "46"), listOf("43", "44")), - Arguments.of("FieldSensitivity2", listOf("43", "47"), listOf("44", "45")), - Arguments.of("ObjectSensitivity1", listOf("39", "45"), listOf("37", "41", "42", "44")), - Arguments.of("ObjectSensitivity2", listOf("39", "44"), listOf("37", "41", "43")), - Arguments.of("StrongUpdate1", listOf("43", "44"), listOf("37", "38")), - Arguments.of("StrongUpdate2", listOf("44"), listOf("41")), - ) - .asSequence() - .asStream() - } - - @ParameterizedTest - @MethodSource("provideForPointerBenchBasic") - fun testBasic(className: String, must: List, notMay: List) { - testPointerBench("pointerbench.basic.$className", must, notMay) - } - - @ParameterizedTest - @MethodSource("provideForPointerBenchGeneralJava") - fun testGeneralJava(className: String, must: List, notMay: List) { - testPointerBench("pointerbench.generalJava.$className", must, notMay) - } - - @ParameterizedTest - @MethodSource("provideForPointerBenchCornerCases") - fun testCornerCases(className: String, must: List, notMay: List) { - testPointerBench("pointerbench.cornerCases.$className", must, notMay) - } - - private fun testPointerBench(className: String, must: List, notMay: List) { - val main = cp.findClass(className).declaredMethods.single { it.name == "main" } - - val res = findTaints(main) - println("For $className:\nOur result: $res\nmust: $must\nnotMay: $notMay") - - val notFoundFromMust = must.filter { it !in res } - assertEquals(emptyList(), notFoundFromMust) - - val foundFromNotMay = notMay.filter { it in res } - assertEquals(emptyList(), foundFromNotMay) - } - - private fun generates(inst: JcInst): List { - return if (inst is JcAssignInst && - inst.callExpr?.method?.name == "taint" && - inst.callExpr?.method?.method?.enclosingClass?.simpleName == "Benchmark" - ) { - listOf(TaintAnalysisNode(inst.lhv.toPath(), nodeType = "TAINT")) - } else { - emptyList() - } - } - - private fun isSanitizer(expr: JcExpr, fact: TaintNode): Boolean = TODO() - - private fun sinks(inst: JcInst): List = TODO() - - private fun findTaints(method: JcMethod): List { - val bannedPackagePrefixes = defaultBannedPackagePrefixes - .plus("pointerbench.benchmark.internal") - - val graph = runBlocking { - cp.newApplicationGraphForAnalysis( - defaultBannedPackagePrefixes + bannedPackagePrefixes - ) - } - - val result = runAnalysis( - graph, - MethodUnitResolver, - newAliasRunnerFactory(::generates, ::isSanitizer, ::sinks), - listOf(method) - ) - - return result.map { it.traceGraph.sink.statement.toString() } - } -} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index 472475782..b664d1a8f 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -18,10 +18,11 @@ package org.jacodb.analysis.impl import juliet.support.AbstractTestCase import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jacodb.analysis.engine.VulnerabilityInstance +import org.jacodb.analysis.graph.newApplicationGraphForAnalysis +import org.jacodb.analysis.taint.Vulnerability import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods import org.jacodb.impl.features.classpaths.UnknownClasses @@ -30,16 +31,17 @@ import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB import org.jacodb.testing.allClasspath -import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.params.provider.Arguments import java.util.stream.Stream import kotlin.streams.asStream -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} abstract class BaseAnalysisTest : BaseTest() { + companion object : WithGlobalDB(UnknownClasses) { + fun getJulietClasses( cweNum: Int, cweSpecificBans: List = emptyList(), @@ -85,66 +87,46 @@ abstract class BaseAnalysisTest : BaseTest() { } override val cp: JcClasspath = runBlocking { - val configPath = "config_small.json" - val defaultConfigResource = this.javaClass.getResourceAsStream("/$configPath") - if (defaultConfigResource != null) { - logger.info { "Loading '$configPath'..." } - val configJson = defaultConfigResource.bufferedReader().readText() + val configFileName = "config_small.json" + val configResource = this.javaClass.getResourceAsStream("/$configFileName") + if (configResource != null) { + val configJson = configResource.bufferedReader().readText() val configurationFeature = TaintConfigurationFeature.fromJson(configJson) - db.classpath(allClasspath, listOf(configurationFeature) + BaseAnalysisTest.classpathFeatures) + db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) } else { super.cp } } - protected abstract fun launchAnalysis(methods: List): List - - protected inline fun testOneAnalysisOnOneMethod( - vulnerabilityType: String, - methodName: String, - expectedLocations: Collection, - ) { - val method = cp.findClass().declaredMethods.single { it.name == methodName } - val sinks = findSinks(method, vulnerabilityType) - - // TODO: think about better assertions here - assertEquals(expectedLocations.size, sinks.size) - expectedLocations.forEach { expected -> - val sinksRepresentations = sinks.map { it.traceGraph.sink.toString() } - assertTrue(sinksRepresentations.any { it.contains(expected) }) { - "$expected unmatched in:\n${sinksRepresentations.joinToString("\n")}" - } + protected val graph: JcApplicationGraph by lazy { + runBlocking { + cp.newApplicationGraphForAnalysis() } } - protected fun testSingleJulietClass(vulnerabilityType: String, className: String) { + protected abstract fun findSinks(method: JcMethod): List + + protected fun testSingleJulietClass(className: String) { logger.info { className } val clazz = cp.findClass(className) val badMethod = clazz.methods.single { it.name == "bad" } val goodMethod = clazz.methods.single { it.name == "good" } - val badIssues = findSinks(badMethod, vulnerabilityType) - logger.info { "badIssues: ${badIssues.size} total" } + logger.info { "Searching for sinks in BAD method: $badMethod" } + val badIssues = findSinks(badMethod) + logger.info { "Total ${badIssues.size} issues in BAD method" } for (issue in badIssues) { logger.debug { " - $issue" } } assertTrue(badIssues.isNotEmpty()) { "Must find some sinks in 'bad' for $className" } - val goodIssues = findSinks(goodMethod, vulnerabilityType) - logger.info { "goodIssues: ${goodIssues.size} total" } + logger.info { "Searching for sinks in GOOD method: $goodMethod" } + val goodIssues = findSinks(goodMethod) + logger.info { "Total ${goodIssues.size} issues in GOOD method" } for (issue in goodIssues) { logger.debug { " - $issue" } } assertTrue(goodIssues.isEmpty()) { "Must NOT find any sinks in 'good' for $className" } } - - protected fun findSinks(method: JcMethod, vulnerabilityType: String): Set { - val vulnerabilities = launchAnalysis(listOf(method)) - val sinks = vulnerabilities - .filter { it.location.vulnerabilityDescription.ruleId == vulnerabilityType } - // .map { it.traceGraph.sink.toString() } - - return sinks.toSet() - } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt similarity index 76% rename from jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt rename to jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt index 823656ac0..5ef97d335 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2NpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt @@ -17,25 +17,17 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jacodb.analysis.engine.SingletonUnitResolver import org.jacodb.analysis.graph.JcApplicationGraphImpl -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.ifds2.taint.Vulnerability -import org.jacodb.analysis.ifds2.taint.npe.NpeManager -import org.jacodb.analysis.impl.BaseAnalysisTest.Companion.provideClassesForJuliet -import org.jacodb.api.JcClasspath +import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.npe.NpeManager +import org.jacodb.analysis.taint.Vulnerability import org.jacodb.api.JcMethod import org.jacodb.api.ext.constructors import org.jacodb.api.ext.findClass -import org.jacodb.api.ext.methods import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.Usages import org.jacodb.impl.features.usagesExt -import org.jacodb.taint.configuration.TaintConfigurationFeature -import org.jacodb.testing.BaseTest import org.jacodb.testing.WithDB -import org.jacodb.testing.allClasspath import org.jacodb.testing.analysis.NpeExamples import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Disabled @@ -47,9 +39,10 @@ import java.util.* import java.util.stream.Stream import kotlin.time.Duration.Companion.seconds -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} + +class IfdsNpeTest : BaseAnalysisTest() { -class Ifds2NpeTest : BaseTest() { companion object : WithDB(Usages, InMemoryHierarchy) { @JvmStatic fun provideClassesForJuliet476(): Stream = @@ -60,22 +53,18 @@ class Ifds2NpeTest : BaseTest() { provideClassesForJuliet(690) } - override val cp: JcClasspath = runBlocking { - val defaultConfigResource = this.javaClass.getResourceAsStream("/config_small.json") - if (defaultConfigResource != null) { - val configJson = defaultConfigResource.bufferedReader().readText() - val configurationFeature = TaintConfigurationFeature.fromJson(configJson) - db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) - } else { - super.cp - } + override fun findSinks(method: JcMethod): List { + val methods = listOf(method) + val unitResolver = SingletonUnitResolver + val manager = NpeManager(graph, unitResolver) + return manager.analyze(methods, timeout = 30.seconds) } @Test fun `fields resolving should work through interfaces`() = runBlocking { val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) val callers = graph.callers(cp.findClass().constructors[2]) - println(callers.toList().size) + logger.debug { "callers: ${callers.toList().size}" } } @Test @@ -242,30 +231,6 @@ class Ifds2NpeTest : BaseTest() { print(results) } - private fun testSingleJulietClass(className: String) { - println(className) - - val clazz = cp.findClass(className) - val badMethod = clazz.methods.single { it.name == "bad" } - val goodMethod = clazz.methods.single { it.name == "good" } - - logger.info { "Searching for sinks in BAD method: $badMethod" } - val badIssues = findSinks(badMethod) - logger.info { "Total ${badIssues.size} issues in BAD method" } - for (issue in badIssues) { - logger.debug { " - $issue" } - } - Assertions.assertTrue(badIssues.isNotEmpty()) - - logger.info { "Searching for sinks in GOOD method: $goodMethod" } - val goodIssues = findSinks(goodMethod) - logger.info { "Total ${goodIssues.size} issues in GOOD method" } - for (issue in goodIssues) { - logger.debug { " - $issue" } - } - Assertions.assertTrue(goodIssues.isEmpty()) - } - private inline fun testOneMethod( methodName: String, expectedLocations: Collection, @@ -279,18 +244,4 @@ class Ifds2NpeTest : BaseTest() { // Assertions.assertTrue(sinks.map { it.traceGraph.sink.toString() }.any { it.contains(expected) }) // } } - - private fun findSinks(method: JcMethod): List { - val vulnerabilities = launchAnalysis(listOf(method)) - return vulnerabilities - } - - private fun launchAnalysis(methods: List): List { - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - val unitResolver = SingletonUnitResolver - val manager = NpeManager(graph, unitResolver) - return manager.analyze(methods, timeout = 30.seconds) - } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt similarity index 56% rename from jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt rename to jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt index 2fef22ec8..97494fb2b 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/Ifds2SqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt @@ -16,26 +16,17 @@ package org.jacodb.analysis.impl -import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jacodb.analysis.engine.ClassUnitResolver -import org.jacodb.analysis.engine.SingletonUnitResolver -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.ifds2.taint.TaintManager -import org.jacodb.analysis.ifds2.taint.Vulnerability -import org.jacodb.analysis.impl.BaseAnalysisTest.Companion.provideClassesForJuliet -import org.jacodb.api.JcClasspath +import org.jacodb.analysis.ifds.ClassUnitResolver +import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.taint.TaintManager +import org.jacodb.analysis.taint.Vulnerability import org.jacodb.api.JcMethod import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.Usages -import org.jacodb.taint.configuration.TaintConfigurationFeature -import org.jacodb.testing.BaseTest import org.jacodb.testing.WithDB -import org.jacodb.testing.allClasspath import org.jacodb.testing.analysis.SqlInjectionExamples -import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -44,9 +35,10 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream import kotlin.time.Duration.Companion.seconds -private val logger = KotlinLogging.logger {} +private val logger = mu.KotlinLogging.logger {} + +class IfdsSqlTest : BaseAnalysisTest() { -class Ifds2SqlTest : BaseTest() { companion object : WithDB(Usages, InMemoryHierarchy) { @JvmStatic fun provideClassesForJuliet89(): Stream = provideClassesForJuliet(89, specificBansCwe89) @@ -57,16 +49,11 @@ class Ifds2SqlTest : BaseTest() { ) } - override val cp: JcClasspath = runBlocking { - val taintConfigFileName = "config_small.json" - val defaultConfigResource = this.javaClass.getResourceAsStream("/$taintConfigFileName") - if (defaultConfigResource != null) { - val configJson = defaultConfigResource.bufferedReader().readText() - val configurationFeature = TaintConfigurationFeature.fromJson(configJson) - db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) - } else { - super.cp - } + override fun findSinks(method: JcMethod): List { + val methods = listOf(method) + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + return manager.analyze(methods, timeout = 30.seconds) } @Test @@ -97,7 +84,6 @@ class Ifds2SqlTest : BaseTest() { @Test fun `test on specific Juliet instance`() { - // val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_45" val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__connect_tcp_execute_01" testSingleJulietClass(className) @@ -108,9 +94,6 @@ class Ifds2SqlTest : BaseTest() { val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_51a" val clazz = cp.findClass(className) val badMethod = clazz.methods.single { it.name == "bad" } - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } val unitResolver = ClassUnitResolver(true) val manager = TaintManager(graph, unitResolver, useBidiRunner = true) val sinks = manager.analyze(listOf(badMethod), timeout = 30.seconds) @@ -120,42 +103,4 @@ class Ifds2SqlTest : BaseTest() { val graphs = aggregates.mapValues { it.value.buildTraceGraph(sinks.first().sink) } assertTrue(graphs.isNotEmpty()) } - - private fun testSingleJulietClass(className: String) { - logger.info { className } - - val clazz = cp.findClass(className) - val badMethod = clazz.methods.single { it.name == "bad" } - val goodMethod = clazz.methods.single { it.name == "good" } - - logger.info { "Searching for sinks in BAD method: $badMethod" } - val badIssues = findSinks(badMethod) - logger.info { "Total ${badIssues.size} issues in BAD method" } - for (issue in badIssues) { - logger.debug { " - $issue" } - } - assertTrue(badIssues.isNotEmpty()) { "Must find some sinks in 'bad' for $className" } - - logger.info { "Searching for sinks in GOOD method: $goodMethod" } - val goodIssues = findSinks(goodMethod) - logger.info { "Total ${goodIssues.size} issues in GOOD method" } - for (issue in goodIssues) { - logger.debug { " - $issue" } - } - assertTrue(goodIssues.isEmpty()) { "Must NOT find any sinks in 'good' for $className" } - } - - private fun findSinks(method: JcMethod): List { - val vulnerabilities = launchAnalysis(listOf(method)) - return vulnerabilities - } - - private fun launchAnalysis(methods: List): List { - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - val unitResolver = SingletonUnitResolver - val manager = TaintManager(graph, unitResolver) - return manager.analyze(methods, timeout = 30.seconds) - } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt index a26031de5..f891a8e23 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt @@ -17,47 +17,34 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.engine.ClassUnitResolver -import org.jacodb.analysis.engine.IfdsUnitRunnerFactory -import org.jacodb.analysis.engine.MethodUnitResolver -import org.jacodb.analysis.engine.UnitResolver import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.UnusedVariableRunnerFactory -import org.jacodb.analysis.library.newNpeRunnerFactory -import org.jacodb.analysis.runAnalysis -import org.jacodb.analysis.sarif.SarifReport +import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.taint.TaintManager +import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.ext.findClass import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB import org.joda.time.DateTime import org.junit.jupiter.api.Test +import kotlin.time.Duration.Companion.seconds -class JodaDateTimeAnalysisTest : BaseTest() { - companion object : WithGlobalDB() +private val logger = mu.KotlinLogging.logger {} - private fun testOne( - unitResolver: UnitResolver, - ifdsUnitRunnerFactory: IfdsUnitRunnerFactory, - ) { - val clazz = cp.findClass() - val result = runAnalysis(graph, unitResolver, ifdsUnitRunnerFactory, clazz.declaredMethods, 60000L) +class JodaDateTimeAnalysisTest : BaseTest() { - println("Vulnerabilities found: ${result.size}") - println("Generated report:") - SarifReport.fromVulnerabilities(result).encodeToStream(System.out) - } + companion object : WithGlobalDB() - @Test - fun `test Unused variable analysis`() { - testOne(ClassUnitResolver(false), UnusedVariableRunnerFactory) + private val graph: JcApplicationGraph = runBlocking { + cp.newApplicationGraphForAnalysis() } @Test - fun `test NPE analysis`() { - testOne(MethodUnitResolver, newNpeRunnerFactory()) - } - - private val graph = runBlocking { - cp.newApplicationGraphForAnalysis() + fun `test taint analysis`() { + val clazz = cp.findClass() + val methods = clazz.declaredMethods + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + val sinks = manager.analyze(methods, timeout = 60.seconds) + logger.info { "Vulnerabilities found: ${sinks.size}" } } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt deleted file mode 100644 index 9d6d70f76..000000000 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.impl - -import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.engine.SingletonUnitResolver -import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.analysis.graph.JcApplicationGraphImpl -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.analyzers.NpeAnalyzer -import org.jacodb.analysis.library.newNpeRunnerFactory -import org.jacodb.analysis.runAnalysis -import org.jacodb.api.JcMethod -import org.jacodb.api.ext.constructors -import org.jacodb.api.ext.findClass -import org.jacodb.impl.features.usagesExt -import org.jacodb.testing.analysis.NpeExamples -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.* -import java.util.stream.Stream - -// Note: some tests might fail because of config rules on StringBuilder.append (CopyAllMarks actions). -@Disabled("Broken due to config") -class NpeAnalysisTest : BaseAnalysisTest() { - companion object { - @JvmStatic - fun provideClassesForJuliet476(): Stream = - provideClassesForJuliet(476, listOf("null_check_after_deref")) - - @JvmStatic - fun provideClassesForJuliet690(): Stream = - provideClassesForJuliet(690) - - private const val vulnerabilityType = NpeAnalyzer.ruleId - } - - @Test - fun `fields resolving should work through interfaces`() = runBlocking { - val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) - val callers = graph.callers(cp.findClass().constructors[2]) - println(callers.toList().size) - } - - @Test - fun `analyze simple NPE`() { - testOneMethod("npeOnLength", listOf("%3 = x.length()")) - } - - @Test - fun `analyze no NPE`() { - testOneMethod("noNPE", emptyList()) - } - - @Test - fun `analyze NPE after fun with two exits`() { - testOneMethod( - "npeAfterTwoExits", - listOf("%4 = x.length()", "%5 = y.length()") - ) - } - - @Test - fun `no NPE after checked access`() { - testOneMethod("checkedAccess", emptyList()) - } - - @Test - fun `consecutive NPEs handled properly`() { - testOneMethod( - "consecutiveNPEs", - listOf("a = x.length()", "c = x.length()") - ) - } - - @Test - fun `npe on virtual call when possible`() { - testOneMethod( - "possibleNPEOnVirtualCall", - listOf("%0 = x.length()") - ) - } - - @Test - fun `no npe on virtual call when impossible`() { - testOneMethod( - "noNPEOnVirtualCall", - emptyList() - ) - } - - @Test - fun `basic test for NPE on fields`() { - testOneMethod("simpleNPEOnField", listOf("len2 = second.length()")) - } - - @Disabled("Flowdroid architecture not supported for async ifds yet") - @Test - fun `simple points-to analysis`() { - testOneMethod("simplePoints2", listOf("%5 = %4.length()")) - } - - @Disabled("Flowdroid architecture not supported for async ifds yet") - @Test - fun `complex aliasing`() { - testOneMethod("complexAliasing", listOf("%6 = %5.length()")) - } - - @Disabled("Flowdroid architecture not supported for async ifds yet") - @Test - fun `context injection in points-to`() { - testOneMethod( - "contextInjection", - listOf("%6 = %5.length()", "%3 = %2.length()") - ) - } - - @Disabled("Flowdroid architecture not supported for async ifds yet") - @Test - fun `activation points maintain flow sensitivity`() { - testOneMethod("flowSensitive", listOf("%8 = %7.length()")) - } - - @Test - fun `overridden null assignment in callee don't affect next caller's instructions`() { - testOneMethod("overriddenNullInCallee", emptyList()) - } - - @Test - fun `recursive classes handled correctly`() { - testOneMethod( - "recursiveClass", - listOf("%10 = %9.toString()", "%15 = %14.toString()") - ) - } - - @Test - fun `NPE on uninitialized array element dereferencing`() { - testOneMethod("simpleArrayNPE", listOf("b = %4.length()")) - } - - @Test - fun `no NPE on array element dereferencing after initialization`() { - testOneMethod("noNPEAfterArrayInit", emptyList()) - } - - @Disabled("Flowdroid architecture not supported for async ifds yet") - @Test - fun `array aliasing`() { - testOneMethod("arrayAliasing", listOf("%5 = %4.length()")) - } - - @Disabled("Flowdroid architecture not supported for async ifds yet") - @Test - fun `mixed array and class aliasing`() { - testOneMethod("mixedArrayClassAliasing", listOf("%13 = %12.length()")) - } - - @Test - fun `dereferencing field of null object`() { - testOneMethod("npeOnFieldDeref", listOf("s = a.field")) - } - - @Test - fun `dereferencing copy of value saved before null assignment produce no npe`() { - testOneMethod("copyBeforeNullAssignment", emptyList()) - } - - @Test - fun `assigning null to copy doesn't affect original value`() { - testOneMethod("nullAssignmentToCopy", emptyList()) - } - - @ParameterizedTest - @MethodSource("provideClassesForJuliet476") - fun `test on Juliet's CWE 476`(className: String) { - testJuliet(className) - } - - @ParameterizedTest - @MethodSource("provideClassesForJuliet690") - fun `test on Juliet's CWE 690`(className: String) { - testJuliet(className) - } - - @Test - fun `test on specific Juliet's CWE 476`() { - val className = "juliet.testcases.CWE476_NULL_Pointer_Dereference.CWE476_NULL_Pointer_Dereference__Integer_01" - - testSingleJulietClass(vulnerabilityType, className) - } - - @Test - fun `analyse something`() { - val testingMethod = cp.findClass().declaredMethods.single { it.name == "id" } - val results = testingMethod.flowGraph() - print(results) - } - - private fun testJuliet(className: String) = testSingleJulietClass(vulnerabilityType, className) - - private inline fun testOneMethod(methodName: String, expectedLocations: Collection) = - testOneAnalysisOnOneMethod(vulnerabilityType, methodName, expectedLocations) - - override fun launchAnalysis(methods: List): List { - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - return runAnalysis(graph, SingletonUnitResolver, newNpeRunnerFactory(), methods) - } -} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt deleted file mode 100644 index af39bee67..000000000 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/SqlInjectionAnalysisTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.impl - -import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.engine.SingletonUnitResolver -import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.analyzers.SqlInjectionAnalyzer -import org.jacodb.analysis.library.newSqlInjectionRunnerFactory -import org.jacodb.analysis.runAnalysis -import org.jacodb.api.JcMethod -import org.jacodb.api.ext.findClass -import org.jacodb.impl.features.InMemoryHierarchy -import org.jacodb.impl.features.Usages -import org.jacodb.testing.WithDB -import org.jacodb.testing.analysis.SqlInjectionExamples -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream - -class SqlInjectionAnalysisTest : BaseAnalysisTest() { - companion object : WithDB(Usages, InMemoryHierarchy) { - @JvmStatic - fun provideClassesForJuliet89(): Stream = provideClassesForJuliet(89, specificBansCwe89) - - private val vulnerabilityType = SqlInjectionAnalyzer.vulnerabilityDescription.ruleId - private val specificBansCwe89: List = listOf( - // Not working yet (#156) - "s03", "s04" - ) - } - - @Test - fun `simple SQL injection`() { - val methodName = "bad" - val method = cp.findClass().declaredMethods.single { it.name == methodName } - val sinks = findSinks(method, vulnerabilityType) - assertTrue(sinks.isNotEmpty()) - } - - @ParameterizedTest - @MethodSource("provideClassesForJuliet89") - fun `test on Juliet's CWE 89`(className: String) { - testSingleJulietClass(vulnerabilityType, className) - } - - @Test - fun `test on specific Juliet's CWE 89`() { - val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_01" - - testSingleJulietClass(vulnerabilityType, className) - } - - override fun launchAnalysis(methods: List): List { - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - return runAnalysis(graph, SingletonUnitResolver, newSqlInjectionRunnerFactory(), methods) - } -} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt index 3f2a99918..7c5146de3 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt @@ -19,13 +19,13 @@ package org.jacodb.analysis.impl import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.ifds2.FlowFunctions -import org.jacodb.analysis.ifds2.taint.ForwardTaintFlowFunctions -import org.jacodb.analysis.ifds2.taint.TaintFact -import org.jacodb.analysis.ifds2.taint.Tainted -import org.jacodb.analysis.ifds2.taint.Zero -import org.jacodb.analysis.library.analyzers.getArgument -import org.jacodb.analysis.paths.toPath +import org.jacodb.analysis.ifds.FlowFunctions +import org.jacodb.analysis.util.getArgument +import org.jacodb.analysis.ifds.toPath +import org.jacodb.analysis.taint.ForwardTaintFlowFunctions +import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.Tainted +import org.jacodb.analysis.taint.Zero import org.jacodb.api.JcClassType import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod @@ -56,9 +56,10 @@ class TaintFlowFunctionsTest : BaseTest() { companion object : WithDB(Usages, InMemoryHierarchy) override val cp: JcClasspath = runBlocking { - val defaultConfigResource = this.javaClass.getResourceAsStream("/config_test.json") - if (defaultConfigResource != null) { - val configJson = defaultConfigResource.bufferedReader().readText() + val configFileName = "config_test.json" + val configResource = this.javaClass.getResourceAsStream("/$configFileName") + if (configResource != null) { + val configJson = configResource.bufferedReader().readText() val configurationFeature = TaintConfigurationFeature.fromJson(configJson) db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) } else { @@ -66,14 +67,14 @@ class TaintFlowFunctionsTest : BaseTest() { } } - private val stringType = cp.findTypeOrNull() as JcClassType - private val graph: JcApplicationGraph = mockk { every { callees(any()) } answers { sequenceOf(arg(0).callExpr!!.method.method) } } + private val stringType = cp.findTypeOrNull() as JcClassType + private val testMethod = mockk { every { name } returns "test" every { enclosingClass } returns mockk(relaxed = true) { @@ -122,7 +123,7 @@ class TaintFlowFunctionsTest : BaseTest() { fun `test call flow function assign mark`() { // "x := test(...)", where 'test' is a source, should result in 'x' to be tainted val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk() { + val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { every { method } returns mockk { every { method } returns testMethod } @@ -138,7 +139,7 @@ class TaintFlowFunctionsTest : BaseTest() { fun `test call flow function remove mark`() { // "test(x)", where 'x' is tainted, should result in 'x' NOT to be tainted val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcCallInst(location = mockk(), callExpr = mockk() { + val callStatement = JcCallInst(location = mockk(), callExpr = mockk { every { method } returns mockk { every { method } returns testMethod } @@ -156,7 +157,7 @@ class TaintFlowFunctionsTest : BaseTest() { // "y := test(x)" should result in 'y' to be tainted only when 'x' is tainted val x: JcLocal = JcLocalVar(1, "x", stringType) val y: JcLocal = JcLocalVar(2, "y", stringType) - val callStatement = JcAssignInst(location = mockk(), lhv = y, rhv = mockk() { + val callStatement = JcAssignInst(location = mockk(), lhv = y, rhv = mockk { every { method } returns mockk { every { method } returns testMethod } @@ -178,15 +179,15 @@ class TaintFlowFunctionsTest : BaseTest() { fun `test call to start flow function`() { // "test(x)", where 'x' is tainted, should result in 'x' (formal argument of 'test') to be tainted val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcCallInst(location = mockk(), callExpr = mockk() { + val callStatement = JcCallInst(location = mockk(), callExpr = mockk { every { method } returns mockk { every { method } returns testMethod } every { args } returns listOf(x) }) val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) - val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk() { - every { location } returns mockk() { + val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk { + every { location } returns mockk { every { method } returns testMethod } }) @@ -205,7 +206,7 @@ class TaintFlowFunctionsTest : BaseTest() { fun `test exit flow function`() { // "x := test()" + "return y", where 'y' is tainted, should result in 'x' to be tainted val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk() { + val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { every { method } returns mockk { every { method } returns testMethod } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt deleted file mode 100644 index 665d4ef2d..000000000 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/UnusedVariableTest.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.analysis.impl - -import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.engine.SingletonUnitResolver -import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.UnusedVariableRunnerFactory -import org.jacodb.analysis.library.analyzers.UnusedVariableAnalyzer -import org.jacodb.analysis.runAnalysis -import org.jacodb.api.JcMethod -import org.jacodb.impl.features.InMemoryHierarchy -import org.jacodb.impl.features.Usages -import org.jacodb.testing.WithDB -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream - -class UnusedVariableTest : BaseAnalysisTest() { - companion object : WithDB(Usages, InMemoryHierarchy) { - @JvmStatic - fun provideClassesForJuliet563(): Stream = provideClassesForJuliet( - 563, listOf( - // Unused variables are already optimized out by cfg - "unused_uninit_variable_", - "unused_init_variable_int", - "unused_init_variable_long", - "unused_init_variable_String_", - - // Unused variable is generated by cfg (!!) - "unused_value_StringBuilder_17", - - // Expected answers are strange, seems to be problem in tests - "_12", - - // The variable isn't expected to be detected as unused actually - "_81" - ) - ) - - private const val vulnerabilityType = UnusedVariableAnalyzer.ruleId - } - - @ParameterizedTest - @MethodSource("provideClassesForJuliet563") - fun `test on Juliet's CWE 563`(className: String) { - testSingleJulietClass(vulnerabilityType, className) - } - - override fun launchAnalysis(methods: List): List { - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - return runAnalysis(graph, SingletonUnitResolver, UnusedVariableRunnerFactory, methods) - } -} diff --git a/jacodb-analysis/src/test/resources/simplelogger.properties b/jacodb-analysis/src/test/resources/simplelogger.properties index 7cfd85f92..d26a546cf 100644 --- a/jacodb-analysis/src/test/resources/simplelogger.properties +++ b/jacodb-analysis/src/test/resources/simplelogger.properties @@ -10,7 +10,7 @@ org.slf4j.simpleLogger.defaultLogLevel=info # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, the default logging detail level is used. org.slf4j.simpleLogger.log.org.jacodb.analysis.engine.BaseIfdsUnitRunnerFactory=debug -org.slf4j.simpleLogger.log.org.jacodb.analysis.ifds2=debug +org.slf4j.simpleLogger.log.org.jacodb.analysis.ifds=debug # Set to true if you want the current date and time to be included in output messages. # Default is false, and will output the number of milliseconds elapsed since startup. diff --git a/jacodb-cli/build.gradle.kts b/jacodb-cli/build.gradle.kts deleted file mode 100644 index e39e519d0..000000000 --- a/jacodb-cli/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -dependencies { - api(project(":jacodb-core")) - api(project(":jacodb-analysis")) - api(project(":jacodb-api")) - - implementation(Libs.kotlin_logging) - implementation(Libs.kotlinx_cli) - implementation(Libs.kotlinx_serialization_json) - - testImplementation(testFixtures(project(":jacodb-core"))) -} diff --git a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt deleted file mode 100644 index c167eec0a..000000000 --- a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.cli - -import kotlinx.cli.ArgParser -import kotlinx.cli.ArgType -import kotlinx.cli.default -import kotlinx.cli.required -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import mu.KLogging -import org.jacodb.analysis.AnalysisConfig -import org.jacodb.analysis.engine.MethodUnitResolver -import org.jacodb.analysis.engine.UnitResolver -import org.jacodb.analysis.engine.VulnerabilityInstance -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.library.UnusedVariableRunnerFactory -import org.jacodb.analysis.library.newNpeRunnerFactory -import org.jacodb.analysis.library.newSqlInjectionRunnerFactory -import org.jacodb.analysis.runAnalysis -import org.jacodb.analysis.sarif.SarifReport -import org.jacodb.api.JcClassOrInterface -import org.jacodb.api.JcClassProcessingTask -import org.jacodb.api.JcMethod -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.impl.features.InMemoryHierarchy -import org.jacodb.impl.features.Usages -import org.jacodb.impl.jacodb -import java.io.File -import java.util.concurrent.ConcurrentHashMap - -private val logger = object : KLogging() {}.logger - -class AnalysisMain { - fun run(args: List) = main(args.toTypedArray()) -} - -fun launchAnalysesByConfig(config: AnalysisConfig, graph: JcApplicationGraph, methods: List): List> { - return config.analyses.mapNotNull { (analysis, options) -> - val unitResolver = options["UnitResolver"]?.let { - UnitResolver.getByName(it) - } ?: MethodUnitResolver - - val runner = when (analysis) { - "NPE" -> newNpeRunnerFactory() - "Unused" -> UnusedVariableRunnerFactory - "SQL" -> newSqlInjectionRunnerFactory() - else -> { - logger.error { "Unknown analysis type: $analysis" } - return@mapNotNull null - } - } - - logger.info { "Launching analysis $analysis" } - runAnalysis(graph, unitResolver, runner, methods) - } -} - - -fun main(args: Array) { - val parser = ArgParser("taint-analysis") - val configFilePath by parser.option( - ArgType.String, - fullName = "analysisConf", - shortName = "a", - description = "File with analysis configuration in JSON format" - ).required() - val dbLocation by parser.option( - ArgType.String, - fullName = "dbLocation", - shortName = "l", - description = "Location of SQLite database for storing bytecode data" - ) - val startClasses by parser.option( - ArgType.String, - fullName = "start", - shortName = "s", - description = "classes from which to start the analysis" - ).required() - val outputPath by parser.option( - ArgType.String, - fullName = "output", - shortName = "o", - description = "File where analysis report (in SARIF format) will be written. File will be created if not exists. Existing file will be overwritten." - ).default("report.sarif") - val classpath by parser.option( - ArgType.String, - fullName = "classpath", - shortName = "cp", - description = "Classpath for analysis. Used by JacoDB." - ).default(System.getProperty("java.class.path")) - - parser.parse(args) - - val outputFile = File(outputPath) - - if (outputFile.exists() && outputFile.isDirectory) { - throw IllegalArgumentException("Provided path for output file is directory, please provide correct path") - } else if (outputFile.exists()) { - logger.info { "Output file $outputFile already exists, results will be overwritten" } - } - - val configFile = File(configFilePath) - if (!configFile.isFile) { - throw IllegalArgumentException("Can't find provided config file $configFilePath") - } - val config = Json.decodeFromString(configFile.readText()) - - val classpathAsFiles = classpath.split(File.pathSeparatorChar).sorted().map { File(it) } - - val cp = runBlocking { - val jacodb = jacodb { - loadByteCode(classpathAsFiles) - dbLocation?.let { - persistent(it) - } - installFeatures(InMemoryHierarchy, Usages) - } - jacodb.classpath(classpathAsFiles) - } - - val startClassesAsList = startClasses.split(";") - val startJcClasses = ConcurrentHashMap.newKeySet() - cp.executeAsync(object : JcClassProcessingTask { - override fun process(clazz: JcClassOrInterface) { - if (startClassesAsList.any { clazz.name.startsWith(it) }) { - startJcClasses.add(clazz) - } - } - }).get() - val startJcMethods = startJcClasses.flatMap { it.declaredMethods }.filter { !it.isPrivate } - - val graph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - - val vulnerabilities = launchAnalysesByConfig(config, graph, startJcMethods).flatten() - val report = SarifReport.fromVulnerabilities(vulnerabilities) - - outputFile.outputStream().use { fileOutputStream -> - report.encodeToStream(fileOutputStream) - } -} \ No newline at end of file diff --git a/jacodb-cli/src/test/resources/config.json b/jacodb-cli/src/test/resources/config.json deleted file mode 100644 index 21a1020ab..000000000 --- a/jacodb-cli/src/test/resources/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "analyses": { - "NPE": {}, - "Unused": { - "UnitResolver": "class" - }, - "SQL": {} - } -} \ No newline at end of file diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt index 436b946c3..6828d85e4 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationItem.kt @@ -16,7 +16,6 @@ package org.jacodb.taint.configuration -import org.jacodb.api.JcField import org.jacodb.api.JcMethod sealed interface TaintConfigurationItem @@ -33,12 +32,6 @@ data class TaintMethodSource( val actionsAfter: List, ) : TaintConfigurationItem -data class TaintFieldSource( - val field: JcField, - val condition: Condition, - val actionsAfter: List, -) : TaintConfigurationItem - data class TaintMethodSink( val method: JcMethod, val ruleNote: String, @@ -46,11 +39,6 @@ data class TaintMethodSink( val condition: Condition, ) : TaintConfigurationItem -data class TaintFieldSink( - val field: JcField, - val condition: Condition, -) : TaintConfigurationItem - data class TaintPassThrough( val method: JcMethod, val condition: Condition, @@ -62,25 +50,3 @@ data class TaintCleaner( val condition: Condition, val actionsAfter: List, ) : TaintConfigurationItem - -val TaintConfigurationItem.condition: Condition - get() = when (this) { - is TaintEntryPointSource -> condition - is TaintMethodSource -> condition - is TaintFieldSource -> condition - is TaintMethodSink -> condition - is TaintFieldSink -> condition - is TaintPassThrough -> condition - is TaintCleaner -> condition - } - -val TaintConfigurationItem.actionsAfter: List - get() = when (this) { - is TaintEntryPointSource -> actionsAfter - is TaintMethodSource -> actionsAfter - is TaintFieldSource -> actionsAfter - is TaintMethodSink -> emptyList() - is TaintFieldSink -> emptyList() - is TaintPassThrough -> actionsAfter - is TaintCleaner -> actionsAfter - } diff --git a/settings.gradle.kts b/settings.gradle.kts index d8aec8ff1..b0081ccf6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,5 @@ include("jacodb-core") include("jacodb-analysis") include("jacodb-examples") include("jacodb-benchmarks") -include("jacodb-cli") include("jacodb-approximations") include("jacodb-taint-configuration") From e4c5cc81572d0271048ae4fad343e397e3f4fc4a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 02:32:22 +0300 Subject: [PATCH 075/117] Cleanup logs --- .../org/jacodb/analysis/npe/NpeManager.kt | 20 +++++++----------- .../org/jacodb/analysis/taint/TaintManager.kt | 21 +++++++------------ 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index 38cf1b8e5..f090535c8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -124,30 +124,21 @@ class NpeManager( // Spawn progress job: val progress = launch(Dispatchers.IO) { - logger.info { "Progress job started" } while (isActive) { delay(1.seconds) logger.info { - "Progress: total propagated ${ + "Progress: propagated ${ runnerForUnit.values.sumOf { it.getGetPathEdges().size } } path edges" } } - logger.info { "Progress job finished" } - logger.info { - "Progress: total propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } - } path edges" - } } // Spawn stopper job: val stopper = launch(Dispatchers.IO) { - logger.info { "Stopper job started" } stopRendezvous.receive() logger.info { "Stopping all runners..." } allJobs.forEach { it.cancel() } - logger.info { "Stopper job finished" } } // Start all runner jobs: @@ -180,10 +171,13 @@ class NpeManager( for (vulnerability in foundVulnerabilities) { logger.debug { "$vulnerability in ${vulnerability.method}" } } - logger.debug { "Total sinks: ${foundVulnerabilities.size}" } - logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.getGetPathEdges().size }} path edges" } } - + logger.info { "Total sinks: ${foundVulnerabilities.size}" } + logger.info { + "Total propagated ${ + runnerForUnit.values.sumOf { it.getGetPathEdges().size } + } path edges" + } logger.info { "Analysis done in %.1f s".format( timeStart.elapsedNow().toDouble(DurationUnit.SECONDS) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index 74b68bcbb..fb9eee0c0 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -161,30 +161,21 @@ class TaintManager( // Spawn progress job: val progress = launch(Dispatchers.IO) { - logger.info { "Progress job started" } while (isActive) { delay(1.seconds) logger.info { - "Progress: total propagated ${ + "Progress: propagated ${ runnerForUnit.values.sumOf { it.getGetPathEdges().size } } path edges" } } - logger.info { "Progress job finished" } - logger.info { - "Progress: total propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } - } path edges" - } } // Spawn stopper job: val stopper = launch(Dispatchers.IO) { - logger.info { "Stopper job started" } stopRendezvous.receive() logger.info { "Stopping all runners..." } allJobs.forEach { it.cancel() } - logger.info { "Stopper job finished" } } // Start all runner jobs: @@ -217,11 +208,13 @@ class TaintManager( for (vulnerability in foundVulnerabilities) { logger.debug { "$vulnerability in ${vulnerability.method}" } } - logger.debug { "Total sinks: ${foundVulnerabilities.size}" } - - logger.debug { "Total propagated ${runnerForUnit.values.sumOf { it.getGetPathEdges().size }} path edges" } } - + logger.info { "Total sinks: ${foundVulnerabilities.size}" } + logger.info { + "Total propagated ${ + runnerForUnit.values.sumOf { it.getGetPathEdges().size } + } path edges" + } logger.info { "Analysis done in %.1f s".format( timeStart.elapsedNow().toDouble(DurationUnit.SECONDS) From eef71183d07d1eb03d0b1611326f376a2a020247 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 02:34:39 +0300 Subject: [PATCH 076/117] Types --- .../src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt | 5 +++-- .../main/kotlin/org/jacodb/analysis/taint/TaintManager.kt | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index f090535c8..63073715b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -62,8 +62,8 @@ class NpeManager( private val unitResolver: UnitResolver, ) : Manager { - private val methodsForUnit = hashMapOf>() - private val runnerForUnit = hashMapOf() + private val methodsForUnit: MutableMap> = hashMapOf() + private val runnerForUnit: MutableMap = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() private val summaryEdgesStorage = SummaryStorageImpl() @@ -214,6 +214,7 @@ class NpeManager( queueIsEmpty[event.runner.unit] = event.isEmpty if (event.isEmpty) { if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { + logger.debug { "All runners are empty" } stopRendezvous.trySend(Unit).getOrNull() } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index fb9eee0c0..04eff5079 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -57,8 +57,8 @@ class TaintManager( private val useBidiRunner: Boolean = false, ) : Manager { - private val methodsForUnit = hashMapOf>() - private val runnerForUnit = hashMapOf() + private val methodsForUnit: MutableMap> = hashMapOf() + private val runnerForUnit: MutableMap = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() private val summaryEdgesStorage = SummaryStorageImpl() @@ -249,7 +249,6 @@ class TaintManager( override fun handleControlEvent(event: ControlEvent) { when (event) { is QueueEmptinessChanged -> { - logger.trace { "Runner ${event.runner.unit} is empty: ${event.isEmpty}" } queueIsEmpty[event.runner.unit] = event.isEmpty if (event.isEmpty) { if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { From ad5a03a59cc4910c01f7d18bef1ae632db652b1e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 02:35:39 +0300 Subject: [PATCH 077/117] Format --- .../src/main/kotlin/org/jacodb/analysis/config/Condition.kt | 4 ++-- .../src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index 2337204ea..43d7365a5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -16,11 +16,11 @@ package org.jacodb.analysis.config -import org.jacodb.analysis.taint.Tainted import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.onSome -import org.jacodb.analysis.util.startsWith import org.jacodb.analysis.ifds.toPath +import org.jacodb.analysis.taint.Tainted +import org.jacodb.analysis.util.startsWith import org.jacodb.api.cfg.JcBool import org.jacodb.api.cfg.JcConstant import org.jacodb.api.cfg.JcInt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt index fabc1987f..59403b099 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/TaintAction.kt @@ -16,11 +16,11 @@ package org.jacodb.analysis.config -import org.jacodb.analysis.taint.Tainted import org.jacodb.analysis.ifds.AccessPath import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.fmap import org.jacodb.analysis.ifds.map +import org.jacodb.analysis.taint.Tainted import org.jacodb.taint.configuration.AssignMark import org.jacodb.taint.configuration.CopyAllMarks import org.jacodb.taint.configuration.CopyMark From a3505395beb64866c6bb92afb1df9996ce789d58 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 02:49:07 +0300 Subject: [PATCH 078/117] Cleanup --- .../jacodb/analysis/npe/NpeFlowFunctions.kt | 16 ++----- .../analysis/taint/TaintFlowFunctions.kt | 42 +++++++++++-------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index e1140c02e..0bc034ccb 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -155,15 +155,10 @@ class ForwardNpeFlowFunctions( if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { val result = when (action) { - is AssignMark -> { - actionEvaluator.evaluate(action) - } - + is AssignMark -> actionEvaluator.evaluate(action) else -> error("$action is not supported for $item") } - result.onSome { - addAll(it) - } + result.onSome { addAll(it) } } } } @@ -423,10 +418,7 @@ class ForwardNpeFlowFunctions( if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { val result = when (action) { - is AssignMark -> { - actionEvaluator.evaluate(action) - } - + is AssignMark -> actionEvaluator.evaluate(action) else -> error("$action is not supported for $item") } result.onSome { @@ -584,7 +576,7 @@ class ForwardNpeFlowFunctions( if (callExpr is JcInstanceCallExpr) { addAll( transmitTaintInstanceToThis( - fact, + fact = fact, at = callStatement, from = callExpr.instance, to = callee.thisInstance diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt index 876c56b14..a4b4521ed 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt @@ -118,15 +118,10 @@ class ForwardTaintFlowFunctions( if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { val result = when (action) { - is AssignMark -> { - actionEvaluator.evaluate(action) - } - + is AssignMark -> actionEvaluator.evaluate(action) else -> error("$action is not supported for $item") } - result.onSome { - addAll(it) - } + result.onSome { addAll(it) } } } } @@ -281,9 +276,7 @@ class ForwardTaintFlowFunctions( is AssignMark -> actionEvaluator.evaluate(action) else -> error("$action is not supported for $item") } - result.onSome { - addAll(it) - } + result.onSome { addAll(it) } } } } @@ -591,11 +584,6 @@ class BackwardTaintFlowFunctions( ?: error("Call statement should have non-null callExpr") val callee = callExpr.method.method - // // FIXME: adhoc for constructors: - // if (callee.isConstructor) { - // return@FlowFunction listOf(fact) - // } - if (callee in graph.callees(callStatement)) { if (fact.variable.isStatic) { @@ -665,7 +653,13 @@ class BackwardTaintFlowFunctions( if (calleeStart is JcReturnInst && callStatement is JcAssignInst) { // Note: returnValue can be null here in some weird cases, e.g. in lambda. calleeStart.returnValue?.let { returnValue -> - addAll(transmitTaintReturn(fact, from = callStatement.lhv, to = returnValue)) + addAll( + transmitTaintReturn( + fact = fact, + from = callStatement.lhv, + to = returnValue + ) + ) } } } @@ -691,13 +685,25 @@ class BackwardTaintFlowFunctions( val actualParams = callExpr.args val formalParams = project.getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { - addAll(transmitTaintArgumentFormalToActual(fact, from = formal, to = actual)) + addAll( + transmitTaintArgumentFormalToActual( + fact = fact, + from = formal, + to = actual + ) + ) } } // Transmit facts on instance (from 'this' to 'instance'): if (callExpr is JcInstanceCallExpr) { - addAll(transmitTaintThisToInstance(fact, from = callee.thisInstance, to = callExpr.instance)) + addAll( + transmitTaintThisToInstance( + fact = fact, + from = callee.thisInstance, + to = callExpr.instance + ) + ) } // Transmit facts on static values: From c13092e4c18d5c3d68278ad10bdd05e3bdb1594c Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 14:13:56 +0300 Subject: [PATCH 079/117] Add tests for condition evaluator --- jacodb-analysis/build.gradle.kts | 5 +- .../org/jacodb/analysis/config/Condition.kt | 18 +- .../impl/BasicConditionEvaluatorTest.kt | 326 ++++++++++++++++++ .../taint/configuration/TaintCondition.kt | 40 +-- 4 files changed, 358 insertions(+), 31 deletions(-) create mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index cfab5c5dc..23e3bc484 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -16,11 +16,14 @@ dependencies { testImplementation(testFixtures(project(":jacodb-core"))) testImplementation(project(":jacodb-api")) + testImplementation(kotlin("test")) + testImplementation(Libs.mockk) + + // Additional deps for analysis: testImplementation(files("src/test/resources/pointerbench.jar")) testImplementation(Libs.joda_time) testImplementation(Libs.juliet_support) for (cweNum in listOf(89, 476, 563, 690)) { testImplementation(Libs.juliet_cwe(cweNum)) } - testImplementation(Libs.mockk) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index 43d7365a5..fc237dac5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -57,20 +57,20 @@ open class BasicConditionEvaluator( return false } - override fun visit(condition: And): Boolean { - return condition.args.all { it.accept(this) } - } - - override fun visit(condition: Or): Boolean { - return condition.args.any { it.accept(this) } + override fun visit(condition: ConstantTrue): Boolean { + return true } override fun visit(condition: Not): Boolean { return !condition.arg.accept(this) } - override fun visit(condition: ConstantTrue): Boolean { - return true + override fun visit(condition: And): Boolean { + return condition.args.all { it.accept(this) } + } + + override fun visit(condition: Or): Boolean { + return condition.args.any { it.accept(this) } } override fun visit(condition: IsConstant): Boolean { @@ -105,8 +105,6 @@ open class BasicConditionEvaluator( // TODO: if 'value' is not string, convert it to string and compare with 'constant.value' value is JcStringConstant && value.value == constant.value } - - else -> error("Unexpected constant: $constant") } } return false diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt new file mode 100644 index 000000000..44356d924 --- /dev/null +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt @@ -0,0 +1,326 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.impl + +import io.mockk.every +import io.mockk.mockk +import org.jacodb.analysis.config.BasicConditionEvaluator +import org.jacodb.analysis.ifds.toMaybe +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcPrimitiveType +import org.jacodb.api.JcType +import org.jacodb.api.PredefinedPrimitive +import org.jacodb.api.PredefinedPrimitives +import org.jacodb.api.cfg.JcBool +import org.jacodb.api.cfg.JcInt +import org.jacodb.api.cfg.JcStringConstant +import org.jacodb.api.cfg.JcThis +import org.jacodb.taint.configuration.And +import org.jacodb.taint.configuration.AnnotationType +import org.jacodb.taint.configuration.Argument +import org.jacodb.taint.configuration.Condition +import org.jacodb.taint.configuration.ConditionVisitor +import org.jacodb.taint.configuration.ConstantBooleanValue +import org.jacodb.taint.configuration.ConstantEq +import org.jacodb.taint.configuration.ConstantGt +import org.jacodb.taint.configuration.ConstantIntValue +import org.jacodb.taint.configuration.ConstantLt +import org.jacodb.taint.configuration.ConstantMatches +import org.jacodb.taint.configuration.ConstantStringValue +import org.jacodb.taint.configuration.ConstantTrue +import org.jacodb.taint.configuration.ContainsMark +import org.jacodb.taint.configuration.IsConstant +import org.jacodb.taint.configuration.IsType +import org.jacodb.taint.configuration.Not +import org.jacodb.taint.configuration.Or +import org.jacodb.taint.configuration.Position +import org.jacodb.taint.configuration.SourceFunctionMatches +import org.jacodb.taint.configuration.This +import org.jacodb.taint.configuration.TypeMatches +import org.junit.jupiter.api.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class BasicConditionEvaluatorTest { + + private val cp = mockk() + + private val intType: JcPrimitiveType = PredefinedPrimitive(cp, PredefinedPrimitives.Int) + private val boolType: JcPrimitiveType = PredefinedPrimitive(cp, PredefinedPrimitives.Boolean) + private val stringType = mockk { + every { classpath } returns cp + } + + private val intArg: Position = Argument(0) + private val intValue = JcInt(42, intType) + + private val boolArg: Position = Argument(1) + private val boolValue = JcBool(true, boolType) + + private val stringArg: Position = Argument(2) + private val stringValue = JcStringConstant("test", stringType) + + private val thisPos: Position = This + private val thisValue = JcThis(type = mockk()) + + private val evaluator: ConditionVisitor = BasicConditionEvaluator { position -> + when (position) { + intArg -> intValue + boolArg -> boolValue + stringArg -> stringValue + thisPos -> thisValue + else -> null + }.toMaybe() + } + + @Test + fun `True is true`() { + val condition = ConstantTrue + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `Not(True) is false`() { + val condition = Not(ConstantTrue) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `Not(Not(True)) is true`() { + val condition = Not(Not(ConstantTrue)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `And(True) is true`() { + val condition = And(listOf(ConstantTrue, ConstantTrue, ConstantTrue)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `And(Not(True)) is false`() { + val condition = And(listOf(ConstantTrue, ConstantTrue, Not(ConstantTrue))) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `Or(Not(True)) is false`() { + val condition = Or(listOf(Not(ConstantTrue), Not(ConstantTrue), Not(ConstantTrue))) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `Or(True) is true`() { + val condition = Or(listOf(Not(ConstantTrue), Not(ConstantTrue), ConstantTrue)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `IsConstant(int) is true`() { + val condition = IsConstant(intArg) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `IsConstant(bool) is true`() { + val condition = IsConstant(boolArg) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `IsConstant(this) is false`() { + val condition = IsConstant(thisPos) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `IsConstant(unresolved) is false`() { + val condition = IsConstant(position = mockk()) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `IsType in unexpected`() { + val condition = mockk() + assertFailsWith { + evaluator.visit(condition) + } + } + + @Test + fun `AnnotationType in unexpected`() { + val condition = mockk() + assertFailsWith { + evaluator.visit(condition) + } + } + + @Test + fun `ConstantEq(intArg(42), 42) is true`() { + val condition = ConstantEq(intArg, ConstantIntValue(42)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantEq(intArg(42), 999) is false`() { + val condition = ConstantEq(intArg, ConstantIntValue(999)) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantEq(boolArg(true), true) is true`() { + val condition = ConstantEq(boolArg, ConstantBooleanValue(true)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantEq(boolArg(true), false) is false`() { + val condition = ConstantEq(boolArg, ConstantBooleanValue(false)) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantEq(stringArg('test'), 'test') is true`() { + val condition = ConstantEq(stringArg, ConstantStringValue("test")) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantEq(stringArg('test'), 'other') is false`() { + val condition = ConstantEq(stringArg, ConstantStringValue("other")) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantEq(unresolved, any) is false`() { + val condition = ConstantEq(position = mockk(), value = mockk()) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantLt(intArg(42), 999) is true`() { + val condition = ConstantLt(intArg, ConstantIntValue(999)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantLt(intArg(42), 5) is false`() { + val condition = ConstantLt(intArg, ConstantIntValue(5)) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantLt(unresolved, any) is false`() { + val condition = ConstantLt(position = mockk(), value = mockk()) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantGt(intArg(42), 5) is true`() { + val condition = ConstantGt(intArg, ConstantIntValue(5)) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantGt(intArg(42), 999) is false`() { + val condition = ConstantGt(intArg, ConstantIntValue(999)) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantGt(unresolved, any) is false`() { + val condition = ConstantGt(position = mockk(), value = mockk()) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `ConstantMatches(intArg(42), '42') is true`() { + val condition = ConstantMatches(intArg, "42") + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantMatches(intArg(42), 'd+') is true`() { + val condition = ConstantMatches(intArg, "\\d+") + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantMatches(stringArg('test'), 'test') is true`() { + val condition = ConstantMatches(stringArg, "\"test\"") + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantMatches(stringArg('test'), 'w+') is true`() { + val condition = ConstantMatches(stringArg, "\"\\w+\"") + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `ConstantMatches(unresolved, any) is false`() { + val condition = ConstantMatches(position = mockk(), pattern = ".*") + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `SourceFunctionMatches is not implemented yet`() { + val condition = mockk() + assertFailsWith { + evaluator.visit(condition) + } + } + + @Test + fun `ContainsMark is not supported by basic evaluator`() { + val condition = mockk() + assertFailsWith { + evaluator.visit(condition) + } + } + + @Test + fun `TypeMatches(intArg, Int) is true`() { + val condition = TypeMatches(intArg, intType) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `TypeMatches(boolArg, Boolean) is true`() { + val condition = TypeMatches(boolArg, boolType) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `TypeMatches(stringArg, String) is true`() { + val condition = TypeMatches(stringArg, stringType) + assertTrue(evaluator.visit(condition)) + } + + @Test + fun `TypeMatches(unresolved, any) is false`() { + val condition = TypeMatches(position = mockk(), type = mockk()) + assertFalse(evaluator.visit(condition)) + } + + @Test + fun `external Condition is false`() { + val condition: Condition = mockk() + assertFalse(evaluator.visit(condition)) + } +} diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt index 43e2141ee..eb2a49566 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt @@ -24,9 +24,10 @@ import kotlinx.serialization.modules.subclass import org.jacodb.api.JcType interface ConditionVisitor { + fun visit(condition: ConstantTrue): R + fun visit(condition: Not): R fun visit(condition: And): R fun visit(condition: Or): R - fun visit(condition: Not): R fun visit(condition: IsConstant): R fun visit(condition: IsType): R fun visit(condition: AnnotationType): R @@ -36,7 +37,6 @@ interface ConditionVisitor { fun visit(condition: ConstantMatches): R fun visit(condition: SourceFunctionMatches): R fun visit(condition: ContainsMark): R - fun visit(condition: ConstantTrue): R fun visit(condition: TypeMatches): R // external type @@ -49,9 +49,10 @@ interface Condition { val conditionModule = SerializersModule { polymorphic(Condition::class) { + subclass(ConstantTrue::class) + subclass(Not::class) subclass(And::class) subclass(Or::class) - subclass(Not::class) subclass(IsConstant::class) subclass(IsType::class) subclass(AnnotationType::class) @@ -61,31 +62,38 @@ val conditionModule = SerializersModule { subclass(ConstantMatches::class) subclass(SourceFunctionMatches::class) subclass(ContainsMark::class) - subclass(ConstantTrue::class) subclass(TypeMatches::class) } } @Serializable -@SerialName("And") -data class And( - @SerialName("args") val args: List, +@SerialName("ConstantTrue") +object ConstantTrue : Condition { + override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) + + override fun toString(): String = javaClass.simpleName +} + +@Serializable +@SerialName("Not") +data class Not( + @SerialName("condition") val arg: Condition, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable -@SerialName("Or") -data class Or( +@SerialName("And") +data class And( @SerialName("args") val args: List, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable -@SerialName("Not") -data class Not( - @SerialName("condition") val arg: Condition, +@SerialName("Or") +data class Or( + @SerialName("args") val args: List, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -170,14 +178,6 @@ data class ContainsMark( override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } -@Serializable -@SerialName("ConstantTrue") -object ConstantTrue : Condition { - override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) - - override fun toString(): String = javaClass.simpleName -} - @Serializable @SerialName("TypeMatches") data class TypeMatches( From 473c9f7a8750cc1ae273c583717564521b63b4da Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 14:14:18 +0300 Subject: [PATCH 080/117] Fix type --- jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt index 1e620daf5..e24d6fbd6 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt @@ -89,7 +89,7 @@ val JcArrayType.deepestElementType: JcType return type } -fun JcType.isAssignable(declaration: JcType): kotlin.Boolean { +fun JcType.isAssignable(declaration: JcType): Boolean { val nullType = classpath.nullType if (this == declaration) { return true @@ -183,7 +183,7 @@ fun JcClassType.findMethodOrNull(name: String, desc: String): JcTypedMethod? { * * This method doesn't support [org.jacodb.impl.features.classpaths.UnknownClasses] feature. */ -fun JcClassType.findMethodOrNull(predicate: (JcTypedMethod) -> kotlin.Boolean): JcTypedMethod? { +fun JcClassType.findMethodOrNull(predicate: (JcTypedMethod) -> Boolean): JcTypedMethod? { // let's find method based on strict hierarchy // if method is not found then it's defined in interfaces return methods.firstOrNull(predicate) @@ -199,4 +199,4 @@ val JcTypedMethod.humanReadableSignature: String get() { fun JcClasspath.findType(name: String): JcType { return findTypeOrNull(name) ?: throw TypeNotFoundException(name) -} \ No newline at end of file +} From b7f0da5ff43064a9b80bac9544ad37396dac6e87 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 14:14:26 +0300 Subject: [PATCH 081/117] Add toString for AnyArgument --- .../main/kotlin/org/jacodb/taint/configuration/Position.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt index f046475e8..ef9d35e83 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt @@ -28,7 +28,9 @@ sealed interface Position @Serializable @SerialName("AnyArgument") -object AnyArgument : Position +object AnyArgument : Position { + override fun toString(): String = javaClass.simpleName +} @Serializable @SerialName("Argument") From a81add414138d85cfa32d950a321f501e58f19fa Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 14:23:51 +0300 Subject: [PATCH 082/117] Test ContainsMark condition via FactAwareConditionEvaluator --- ...uatorTest.kt => ConditionEvaluatorTest.kt} | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) rename jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/{BasicConditionEvaluatorTest.kt => ConditionEvaluatorTest.kt} (90%) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt similarity index 90% rename from jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt rename to jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt index 44356d924..b2e87d87e 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BasicConditionEvaluatorTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt @@ -19,7 +19,11 @@ package org.jacodb.analysis.impl import io.mockk.every import io.mockk.mockk import org.jacodb.analysis.config.BasicConditionEvaluator +import org.jacodb.analysis.config.FactAwareConditionEvaluator +import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.toMaybe +import org.jacodb.analysis.ifds.toPath +import org.jacodb.analysis.taint.Tainted import org.jacodb.api.JcClasspath import org.jacodb.api.JcPrimitiveType import org.jacodb.api.JcType @@ -29,6 +33,7 @@ import org.jacodb.api.cfg.JcBool import org.jacodb.api.cfg.JcInt import org.jacodb.api.cfg.JcStringConstant import org.jacodb.api.cfg.JcThis +import org.jacodb.api.cfg.JcValue import org.jacodb.taint.configuration.And import org.jacodb.taint.configuration.AnnotationType import org.jacodb.taint.configuration.Argument @@ -49,6 +54,7 @@ import org.jacodb.taint.configuration.Not import org.jacodb.taint.configuration.Or import org.jacodb.taint.configuration.Position import org.jacodb.taint.configuration.SourceFunctionMatches +import org.jacodb.taint.configuration.TaintMark import org.jacodb.taint.configuration.This import org.jacodb.taint.configuration.TypeMatches import org.junit.jupiter.api.Test @@ -56,7 +62,7 @@ import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue -class BasicConditionEvaluatorTest { +class ConditionEvaluatorTest { private val cp = mockk() @@ -78,7 +84,7 @@ class BasicConditionEvaluatorTest { private val thisPos: Position = This private val thisValue = JcThis(type = mockk()) - private val evaluator: ConditionVisitor = BasicConditionEvaluator { position -> + private val positionResolver: (position: Position) -> Maybe = { position -> when (position) { intArg -> intValue boolArg -> boolValue @@ -87,6 +93,7 @@ class BasicConditionEvaluatorTest { else -> null }.toMaybe() } + private val evaluator: ConditionVisitor = BasicConditionEvaluator(positionResolver) @Test fun `True is true`() { @@ -323,4 +330,15 @@ class BasicConditionEvaluatorTest { val condition: Condition = mockk() assertFalse(evaluator.visit(condition)) } + + @Test + fun `FactAwareConditionEvaluator supports ContainsMark`() { + val fact = Tainted(intValue.toPath(), TaintMark("FOO")) + val factAwareEvaluator = FactAwareConditionEvaluator(fact, positionResolver) + assertTrue(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("FOO")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("BAR")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("FOO")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("BAR")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(position = mockk(), TaintMark("FOO")))) + } } From da3fdd57fd624a004ccb953bc689841df4dcf8d0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sat, 17 Feb 2024 14:34:15 +0300 Subject: [PATCH 083/117] Fix BaseAnalysisTest --- .../jacodb/analysis/impl/BaseAnalysisTest.kt | 6 +++--- .../org/jacodb/analysis/impl/IfdsNpeTest.kt | 5 ++--- .../org/jacodb/analysis/impl/IfdsSqlTest.kt | 5 ++--- .../analysis/impl/JodaDateTimeAnalysisTest.kt | 19 +++++++++---------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index b664d1a8f..2cce441a4 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -104,7 +104,7 @@ abstract class BaseAnalysisTest : BaseTest() { } } - protected abstract fun findSinks(method: JcMethod): List + protected abstract fun findSinks(methods: List): List protected fun testSingleJulietClass(className: String) { logger.info { className } @@ -114,7 +114,7 @@ abstract class BaseAnalysisTest : BaseTest() { val goodMethod = clazz.methods.single { it.name == "good" } logger.info { "Searching for sinks in BAD method: $badMethod" } - val badIssues = findSinks(badMethod) + val badIssues = findSinks(listOf(badMethod)) logger.info { "Total ${badIssues.size} issues in BAD method" } for (issue in badIssues) { logger.debug { " - $issue" } @@ -122,7 +122,7 @@ abstract class BaseAnalysisTest : BaseTest() { assertTrue(badIssues.isNotEmpty()) { "Must find some sinks in 'bad' for $className" } logger.info { "Searching for sinks in GOOD method: $goodMethod" } - val goodIssues = findSinks(goodMethod) + val goodIssues = findSinks(listOf(goodMethod)) logger.info { "Total ${goodIssues.size} issues in GOOD method" } for (issue in goodIssues) { logger.debug { " - $issue" } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt index 5ef97d335..07a816eda 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt @@ -53,8 +53,7 @@ class IfdsNpeTest : BaseAnalysisTest() { provideClassesForJuliet(690) } - override fun findSinks(method: JcMethod): List { - val methods = listOf(method) + override fun findSinks(methods: List): List { val unitResolver = SingletonUnitResolver val manager = NpeManager(graph, unitResolver) return manager.analyze(methods, timeout = 30.seconds) @@ -236,7 +235,7 @@ class IfdsNpeTest : BaseAnalysisTest() { expectedLocations: Collection, ) { val method = cp.findClass().declaredMethods.single { it.name == methodName } - val sinks = findSinks(method) + val sinks = findSinks(listOf(method)) // TODO: think about better assertions here Assertions.assertEquals(expectedLocations.size, sinks.size) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt index 97494fb2b..95e758d15 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt @@ -49,8 +49,7 @@ class IfdsSqlTest : BaseAnalysisTest() { ) } - override fun findSinks(method: JcMethod): List { - val methods = listOf(method) + override fun findSinks(methods: List): List { val unitResolver = SingletonUnitResolver val manager = TaintManager(graph, unitResolver) return manager.analyze(methods, timeout = 30.seconds) @@ -60,7 +59,7 @@ class IfdsSqlTest : BaseAnalysisTest() { fun `simple SQL injection`() { val methodName = "bad" val method = cp.findClass().declaredMethods.single { it.name == methodName } - val sinks = findSinks(method) + val sinks = findSinks(listOf(method)) assertTrue(sinks.isNotEmpty()) } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt index f891a8e23..32ad60c2e 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt @@ -16,13 +16,11 @@ package org.jacodb.analysis.impl -import kotlinx.coroutines.runBlocking -import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds.SingletonUnitResolver import org.jacodb.analysis.taint.TaintManager -import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.api.JcMethod import org.jacodb.api.ext.findClass -import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB import org.joda.time.DateTime import org.junit.jupiter.api.Test @@ -30,21 +28,22 @@ import kotlin.time.Duration.Companion.seconds private val logger = mu.KotlinLogging.logger {} -class JodaDateTimeAnalysisTest : BaseTest() { +class JodaDateTimeAnalysisTest : BaseAnalysisTest() { companion object : WithGlobalDB() - private val graph: JcApplicationGraph = runBlocking { - cp.newApplicationGraphForAnalysis() - } - @Test fun `test taint analysis`() { val clazz = cp.findClass() val methods = clazz.declaredMethods + val sinks = findSinks(methods) + logger.info { "Vulnerabilities found: ${sinks.size}" } + } + + override fun findSinks(methods: List): List { val unitResolver = SingletonUnitResolver val manager = TaintManager(graph, unitResolver) val sinks = manager.analyze(methods, timeout = 60.seconds) - logger.info { "Vulnerabilities found: ${sinks.size}" } + return sinks } } From 261bff336a47ed4f42f1565f4bd077b1ac2bdf82 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sun, 18 Feb 2024 01:14:31 +0300 Subject: [PATCH 084/117] Add unused variable analysis --- .../org/jacodb/analysis/ifds/AccessPath.kt | 8 +- .../org/jacodb/analysis/unused/Analyzer.kt | 46 ++++ .../org/jacodb/analysis/unused/Events.kt | 29 ++ .../org/jacodb/analysis/unused/Facts.kt | 31 +++ .../jacodb/analysis/unused/FlowFunctions.kt | 120 +++++++++ .../org/jacodb/analysis/unused/Manager.kt | 253 ++++++++++++++++++ .../org/jacodb/analysis/unused/Summary.kt | 38 +++ .../analysis/impl/JodaDateTimeAnalysisTest.kt | 41 ++- 8 files changed, 559 insertions(+), 7 deletions(-) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt index e6f8d5641..dfeb3d4e9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt @@ -89,9 +89,13 @@ data class AccessPath internal constructor( } fun JcExpr.toPathOrNull(): AccessPath? = when (this) { - is JcSimpleValue -> AccessPath.from(this) - + is JcValue -> toPathOrNull() is JcCastExpr -> operand.toPathOrNull() + else -> null +} + +fun JcValue.toPathOrNull(): AccessPath? = when (this) { + is JcSimpleValue -> AccessPath.from(this) is JcArrayAccess -> { array.toPathOrNull()?.let { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt new file mode 100644 index 000000000..722e9364c --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.Analyzer +import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Vertex +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcInst + +class UnusedVariableAnalyzer( + private val graph: JcApplicationGraph, +) : Analyzer { + + override val flowFunctions: UnusedVariableFlowFunctions by lazy { + UnusedVariableFlowFunctions(graph) + } + + private fun isExitPoint(statement: JcInst): Boolean { + return statement in graph.exitPoints(statement.location.method) + } + + override fun handleNewEdge(edge: Edge): List = buildList { + if (isExitPoint(edge.to.statement)) { + add(NewSummaryEdge(edge)) + } + } + + override fun handleCrossUnitCall(caller: Vertex, callee: Vertex): List { + return emptyList() + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt new file mode 100644 index 000000000..698494c9f --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.Edge + +sealed interface Event + +data class NewSummaryEdge( + val edge: Edge, +) : Event + +data class NewVulnerability( + val vulnerability: Vulnerability, +) : Event diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt new file mode 100644 index 000000000..a17eccec2 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.api.cfg.JcInst + +sealed interface Fact + +object Zero : Fact { + override fun toString(): String = javaClass.simpleName +} + +data class UnusedVariable( + val variable: AccessPath, + val initStatement: JcInst, +) : Fact diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt new file mode 100644 index 000000000..f6df4f46a --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.FlowFunction +import org.jacodb.analysis.ifds.FlowFunctions +import org.jacodb.analysis.ifds.toPath +import org.jacodb.analysis.ifds.toPathOrNull +import org.jacodb.analysis.util.getArgumentsOf +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcSpecialCallExpr +import org.jacodb.api.cfg.JcStaticCallExpr +import org.jacodb.api.ext.cfg.callExpr + +class UnusedVariableFlowFunctions( + private val graph: JcApplicationGraph, +) : FlowFunctions { + private val cp: JcClasspath + get() = graph.classpath + + override fun obtainPossibleStartFacts( + method: JcMethod, + ): Collection { + return setOf(Zero) + } + + override fun obtainSequentFlowFunction( + current: JcInst, + next: JcInst, + ) = FlowFunction { fact -> + if (current !is JcAssignInst) { + return@FlowFunction setOf(fact) + } + + if (fact == Zero) { + val toPath = current.lhv.toPath() + if (!toPath.isOnHeap) { + return@FlowFunction setOf(Zero, UnusedVariable(toPath, current)) + } else { + return@FlowFunction setOf(Zero) + } + } + check(fact is UnusedVariable) + + val toPath = current.lhv.toPath() + val default = if (toPath == fact.variable) emptySet() else setOf(fact) + val fromPath = current.rhv.toPathOrNull() + ?: return@FlowFunction default + + if (fromPath.isOnHeap || toPath.isOnHeap) { + return@FlowFunction default + } + + if (fromPath == fact.variable) { + return@FlowFunction default + fact.copy(variable = toPath) + } + + default + } + + override fun obtainCallToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + ) = obtainSequentFlowFunction(callStatement, returnSite) + + override fun obtainCallToStartFlowFunction( + callStatement: JcInst, + calleeStart: JcInst, + ) = FlowFunction { fact -> + val callExpr = callStatement.callExpr + ?: error("Call statement should have non-null callExpr") + + if (fact == Zero) { + if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { + return@FlowFunction setOf(Zero) + } + return@FlowFunction buildSet { + add(Zero) + val callee = calleeStart.location.method + val formalParams = cp.getArgumentsOf(callee) + for (formal in formalParams) { + add(UnusedVariable(formal.toPath(), callStatement)) + } + } + } + check(fact is UnusedVariable) + + emptySet() + } + + override fun obtainExitToReturnSiteFlowFunction( + callStatement: JcInst, + returnSite: JcInst, + exitStatement: JcInst, + ) = FlowFunction { fact -> + if (fact == Zero) { + setOf(Zero) + } else { + emptySet() + } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt new file mode 100644 index 000000000..1e618832d --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import org.jacodb.analysis.ifds.Aggregate +import org.jacodb.analysis.ifds.ControlEvent +import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Manager +import org.jacodb.analysis.ifds.QueueEmptinessChanged +import org.jacodb.analysis.ifds.Runner +import org.jacodb.analysis.ifds.SummaryStorageImpl +import org.jacodb.analysis.ifds.UniRunner +import org.jacodb.analysis.ifds.UnitResolver +import org.jacodb.analysis.ifds.UnitType +import org.jacodb.analysis.ifds.UnknownUnit +import org.jacodb.analysis.util.getGetPathEdges +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +private val logger = mu.KotlinLogging.logger {} + +class UnusedVariableManager( + private val graph: JcApplicationGraph, + private val unitResolver: UnitResolver, +) : Manager { + + private val methodsForUnit: MutableMap> = hashMapOf() + private val runnerForUnit: MutableMap> = hashMapOf() + private val queueIsEmpty = ConcurrentHashMap() + + private val summaryEdgesStorage = SummaryStorageImpl() + private val vulnerabilitiesStorage = SummaryStorageImpl() + + private val stopRendezvous = Channel(Channel.RENDEZVOUS) + + private fun newRunner( + unit: UnitType, + ): Runner { + check(unit !in runnerForUnit) { "Runner for $unit already exists" } + + logger.debug { "Creating a new runner for $unit" } + val analyzer = UnusedVariableAnalyzer(graph) + val runner = UniRunner( + graph = graph, + analyzer = analyzer, + manager = this@UnusedVariableManager, + unitResolver = unitResolver, + unit = unit + ) + + runnerForUnit[unit] = runner + return runner + } + + private fun getAllCallees(method: JcMethod): Set { + val result: MutableSet = hashSetOf() + for (inst in method.flowGraph().instructions) { + result += graph.callees(inst) + } + return result + } + + private fun addStart(method: JcMethod) { + logger.info { "Adding start method: $method" } + val unit = unitResolver.resolve(method) + if (unit == UnknownUnit) return + val isNew = methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) + if (isNew) { + for (dep in getAllCallees(method)) { + addStart(dep) + } + } + } + + @OptIn(ExperimentalTime::class) + fun analyze( + startMethods: List, + timeout: Duration = 3600.seconds, + ): List = runBlocking { + val timeStart = TimeSource.Monotonic.markNow() + + // Add start methods: + for (method in startMethods) { + addStart(method) + } + + // Determine all units: + val allUnits = methodsForUnit.keys.toList() + logger.info { + "Starting analysis of ${ + methodsForUnit.values.sumOf { it.size } + } methods in ${allUnits.size} units" + } + + // Spawn runner jobs: + val allJobs = allUnits.map { unit -> + // Create the runner: + val runner = newRunner(unit) + + // Start the runner: + launch(start = CoroutineStart.LAZY) { + val methods = methodsForUnit[unit]!!.toList() + runner.run(methods) + } + } + + // Spawn progress job: + val progress = launch(Dispatchers.IO) { + while (isActive) { + delay(1.seconds) + logger.info { + "Progress: propagated ${ + runnerForUnit.values.sumOf { it.getGetPathEdges().size } + } path edges" + } + } + } + + // Spawn stopper job: + val stopper = launch(Dispatchers.IO) { + stopRendezvous.receive() + logger.info { "Stopping all runners..." } + allJobs.forEach { it.cancel() } + } + + // Start all runner jobs: + val timeStartJobs = TimeSource.Monotonic.markNow() + allJobs.forEach { it.start() } + + // Await all runners: + withTimeoutOrNull(timeout) { + allJobs.joinAll() + } ?: run { + logger.info { "Timeout!" } + allJobs.forEach { it.cancel() } + allJobs.joinAll() + } + progress.cancelAndJoin() + stopper.cancelAndJoin() + logger.info { + "All ${allJobs.size} jobs completed in %.1f s".format( + timeStartJobs.elapsedNow().toDouble(DurationUnit.SECONDS) + ) + } + + // Extract found vulnerabilities (sinks): + val foundVulnerabilities = vulnerabilitiesStorage.knownMethods + .flatMap { method -> + vulnerabilitiesStorage.getCurrentFacts(method) + } + if (logger.isDebugEnabled) { + logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } + for (vulnerability in foundVulnerabilities) { + logger.debug { "$vulnerability in ${vulnerability.method}" } + } + } + logger.info { "Total sinks: ${foundVulnerabilities.size}" } + logger.info { + "Total propagated ${ + runnerForUnit.values.sumOf { it.getGetPathEdges().size } + } path edges" + } + logger.info { + "Analysis done in %.1f s".format( + timeStart.elapsedNow().toDouble(DurationUnit.SECONDS) + ) + } + foundVulnerabilities + } + + override fun handleEvent(event: Event) { + when (event) { + is NewSummaryEdge -> { + summaryEdgesStorage.add(SummaryEdge(event.edge)) + } + + is NewVulnerability -> { + vulnerabilitiesStorage.add(event.vulnerability) + } + } + } + + override fun handleControlEvent(event: ControlEvent) { + when (event) { + is QueueEmptinessChanged -> { + queueIsEmpty[event.runner.unit] = event.isEmpty + if (event.isEmpty) { + if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { + logger.debug { "All runners are empty" } + stopRendezvous.trySend(Unit).getOrNull() + } + } + } + } + } + + override fun subscribeOnSummaryEdges( + method: JcMethod, + scope: CoroutineScope, + handler: (Edge) -> Unit, + ) { + summaryEdgesStorage + .getFacts(method) + .onEach { handler(it.edge) } + .launchIn(scope) + } + + fun getAggregates(): Map> { + return runnerForUnit.mapValues { it.value.getAggregate() } + } +} + +fun runUnusedVariableAnalysis( + graph: JcApplicationGraph, + unitResolver: UnitResolver, + startMethods: List, +): List { + val manager = UnusedVariableManager(graph, unitResolver) + return manager.analyze(startMethods) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt new file mode 100644 index 000000000..fbc6406b2 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Summary +import org.jacodb.analysis.ifds.Vertex +import org.jacodb.api.JcMethod + +data class SummaryEdge( + val edge: Edge, +) : Summary { + override val method: JcMethod + get() = edge.method +} + +data class Vulnerability( + val message: String, + val sink: Vertex, + val edge: Edge? = null, +) : Summary { + override val method: JcMethod + get() = sink.method +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt index 32ad60c2e..f89c13efa 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt @@ -16,34 +16,65 @@ package org.jacodb.analysis.impl +import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds.SingletonUnitResolver import org.jacodb.analysis.taint.TaintManager import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.analysis.unused.UnusedVariableManager +import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.ext.findClass +import org.jacodb.taint.configuration.TaintConfigurationFeature +import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB +import org.jacodb.testing.allClasspath import org.joda.time.DateTime import org.junit.jupiter.api.Test import kotlin.time.Duration.Companion.seconds private val logger = mu.KotlinLogging.logger {} -class JodaDateTimeAnalysisTest : BaseAnalysisTest() { +class JodaDateTimeAnalysisTest : BaseTest() { companion object : WithGlobalDB() + + override val cp: JcClasspath = runBlocking { + val configFileName = "config_small.json" + val configResource = this.javaClass.getResourceAsStream("/$configFileName") + if (configResource != null) { + val configJson = configResource.bufferedReader().readText() + val configurationFeature = TaintConfigurationFeature.fromJson(configJson) + db.classpath(allClasspath, listOf(configurationFeature) + classpathFeatures) + } else { + super.cp + } + } + + private val graph: JcApplicationGraph by lazy { + runBlocking { + cp.newApplicationGraphForAnalysis() + } + } @Test fun `test taint analysis`() { val clazz = cp.findClass() val methods = clazz.declaredMethods - val sinks = findSinks(methods) + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + val sinks = manager.analyze(methods, timeout = 60.seconds) logger.info { "Vulnerabilities found: ${sinks.size}" } } - override fun findSinks(methods: List): List { + @Test + fun `test unused variables analysis`() { + val clazz = cp.findClass() + val methods = clazz.declaredMethods val unitResolver = SingletonUnitResolver - val manager = TaintManager(graph, unitResolver) + val manager = UnusedVariableManager(graph, unitResolver) val sinks = manager.analyze(methods, timeout = 60.seconds) - return sinks + logger.info { "Unused variables found: ${sinks.size}" } } } From 2e30243db34a2debf1e4a79d1acf2dd638d2061a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Sun, 18 Feb 2024 01:20:30 +0300 Subject: [PATCH 085/117] Cleanup logger config --- jacodb-analysis/src/test/resources/simplelogger.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/jacodb-analysis/src/test/resources/simplelogger.properties b/jacodb-analysis/src/test/resources/simplelogger.properties index d26a546cf..de8ce921f 100644 --- a/jacodb-analysis/src/test/resources/simplelogger.properties +++ b/jacodb-analysis/src/test/resources/simplelogger.properties @@ -9,7 +9,6 @@ org.slf4j.simpleLogger.defaultLogLevel=info # Logging detail level for a SimpleLogger instance named "xxxxx". # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, the default logging detail level is used. -org.slf4j.simpleLogger.log.org.jacodb.analysis.engine.BaseIfdsUnitRunnerFactory=debug org.slf4j.simpleLogger.log.org.jacodb.analysis.ifds=debug # Set to true if you want the current date and time to be included in output messages. From fe685fd41425043390aa0f4195ada872c92db2e1 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 15:34:29 +0300 Subject: [PATCH 086/117] Fix trace graph merging --- .../org/jacodb/analysis/ifds/Aggregate.kt | 30 ++++++--------- .../kotlin/org/jacodb/analysis/ifds/Reason.kt | 16 +++++--- .../kotlin/org/jacodb/analysis/ifds/Runner.kt | 26 +++++-------- .../org/jacodb/analysis/ifds/TraceGraph.kt | 34 +++++------------ .../org/jacodb/analysis/npe/NpeAnalyzers.kt | 3 +- .../org/jacodb/analysis/npe/NpeManager.kt | 2 +- .../org/jacodb/analysis/taint/BidiRunner.kt | 9 +++-- .../jacodb/analysis/taint/TaintAnalyzers.kt | 5 ++- .../org/jacodb/analysis/taint/TaintEvents.kt | 3 ++ .../org/jacodb/analysis/taint/TaintManager.kt | 37 +++++++++++++++++-- .../org/jacodb/analysis/impl/IfdsSqlTest.kt | 8 ++-- 11 files changed, 93 insertions(+), 80 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt index 3824e3952..d44abc6ee 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt @@ -25,7 +25,7 @@ import org.jacodb.api.cfg.JcInst class Aggregate( pathEdges: Collection>, val facts: Map>, - val reasons: Map, Set>, + val reasons: Map, Set>>, ) { private val pathEdgesBySink: Map, Collection>> = pathEdges.groupByTo(HashMap()) { it.to } @@ -33,6 +33,7 @@ class Aggregate( fun buildTraceGraph(sink: Vertex): TraceGraph { val sources: MutableSet> = hashSetOf() val edges: MutableMap, MutableSet>> = hashMapOf() + val unresolvedCrossUnitCalls: MutableMap, MutableSet>> = hashMapOf() val visited: MutableSet, Vertex>> = hashSetOf() fun addEdge( @@ -69,9 +70,7 @@ class Aggregate( for (reason in reasons[edge].orEmpty()) { when (reason) { - is Reason.Sequent<*> -> { - @Suppress("UNCHECKED_CAST") - reason as Reason.Sequent + is Reason.Sequent -> { val predEdge = reason.edge if (predEdge.to.fact == vertex.fact) { dfs(predEdge, lastVertex, stopAtMethodStart) @@ -81,9 +80,7 @@ class Aggregate( } } - is Reason.CallToStart<*> -> { - @Suppress("UNCHECKED_CAST") - reason as Reason.CallToStart + is Reason.CallToStart -> { val predEdge = reason.edge if (!stopAtMethodStart) { addEdge(predEdge.to, lastVertex) @@ -91,9 +88,7 @@ class Aggregate( } } - is Reason.ThroughSummary<*> -> { - @Suppress("UNCHECKED_CAST") - reason as Reason.ThroughSummary + is Reason.ThroughSummary -> { val predEdge = reason.edge val summaryEdge = reason.summaryEdge addEdge(summaryEdge.to, lastVertex) // Return to next vertex @@ -102,14 +97,13 @@ class Aggregate( dfs(predEdge, predEdge.to, stopAtMethodStart) // Continue normal analysis } - is Reason.External -> { - // TODO: check + is Reason.CrossUnitCall -> { addEdge(edge.to, lastVertex) - if (edge.from != edge.to) { - // TODO: ideally, we should analyze the place from which the edge was given to ifds, - // for now we just go to method start - dfs(Edge(edge.from, edge.from), edge.to, stopAtMethodStart) - } + unresolvedCrossUnitCalls.getOrPut(reason.caller) { hashSetOf() }.add(edge.to) + } + + is Reason.External -> { + TODO("External reason is not supported yet") } is Reason.Initial -> { @@ -123,6 +117,6 @@ class Aggregate( for (edge in pathEdgesBySink[sink].orEmpty()) { dfs(edge, edge.to, false) } - return TraceGraph(sink, sources, edges) + return TraceGraph(sink, sources, edges, unresolvedCrossUnitCalls) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt index fcf27936b..c6600cd62 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Reason.kt @@ -16,21 +16,25 @@ package org.jacodb.analysis.ifds -sealed class Reason { - object Initial : Reason() +sealed interface Reason { + object Initial : Reason - object External : Reason() + object External : Reason + + data class CrossUnitCall( + val caller: Vertex, + ) : Reason data class Sequent( val edge: Edge, - ) : Reason() + ) : Reason data class CallToStart( val edge: Edge, - ) : Reason() + ) : Reason data class ThroughSummary( val edge: Edge, val summaryEdge: Edge, - ) : Reason() + ) : Reason } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt index 739d8929a..163c530ea 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt @@ -35,7 +35,7 @@ interface Runner { val unit: UnitType suspend fun run(startMethods: List) - fun submitNewEdge(edge: Edge) + fun submitNewEdge(edge: Edge, reason: Reason) fun getAggregate(): Aggregate } @@ -49,7 +49,7 @@ class UniRunner( private val flowSpace: FlowFunctions = analyzer.flowFunctions private val workList: Channel> = Channel(Channel.UNLIMITED) - private val reasons = ConcurrentHashMap, MutableSet>() + private val reasons = ConcurrentHashMap, MutableSet>>() internal val pathEdges: MutableSet> = ConcurrentHashMap.newKeySet() private val summaryEdges: MutableMap, MutableSet>> = hashMapOf() @@ -73,20 +73,18 @@ class UniRunner( for (start in graph.entryPoints(method)) { val vertex = Vertex(start, startFact) val edge = Edge(vertex, vertex) // loop - val reason = Reason.Initial - propagate(edge, reason) + propagate(edge, Reason.Initial) } } } - override fun submitNewEdge(edge: Edge) { - // TODO: add default-argument 'reason = Reason.External' to 'submitNewEdge' - propagate(edge, Reason.External) + override fun submitNewEdge(edge: Edge, reason: Reason) { + propagate(edge, reason) } private fun propagate( edge: Edge, - reason: Reason, + reason: Reason, ): Boolean { require(unitResolver.resolve(edge.method) == unit) { "Propagated edge must be in the same unit" @@ -153,8 +151,7 @@ class UniRunner( for (returnSiteFact in factsAtReturnSite) { val returnSiteVertex = Vertex(returnSite, returnSiteFact) val newEdge = Edge(startVertex, returnSiteVertex) - val reason = Reason.Sequent(currentEdge) - propagate(newEdge, reason) + propagate(newEdge, Reason.Sequent(currentEdge)) } } @@ -188,8 +185,7 @@ class UniRunner( // Initialize analysis of callee: run { val newEdge = Edge(calleeStartVertex, calleeStartVertex) // loop - val reason = Reason.CallToStart(currentEdge) - propagate(newEdge, reason) + propagate(newEdge, Reason.CallToStart(currentEdge)) } // Handle already-found summary edges: @@ -220,8 +216,7 @@ class UniRunner( for (nextFact in factsAtNext) { val nextVertex = Vertex(next, nextFact) val newEdge = Edge(startVertex, nextVertex) - val reason = Reason.Sequent(currentEdge) - propagate(newEdge, reason) + propagate(newEdge, Reason.Sequent(currentEdge)) } } } @@ -241,8 +236,7 @@ class UniRunner( for (returnSiteFact in finalFacts) { val returnSiteVertex = Vertex(returnSite, returnSiteFact) val newEdge = Edge(startVertex, returnSiteVertex) - val reason = Reason.ThroughSummary(currentEdge, summaryEdge) - propagate(newEdge, reason) + propagate(newEdge, Reason.ThroughSummary(currentEdge, summaryEdge)) } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt index 724af718a..60a83d380 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/TraceGraph.kt @@ -18,8 +18,9 @@ package org.jacodb.analysis.ifds data class TraceGraph( val sink: Vertex, - val sources: Set>, - val edges: Map, Set>>, + val sources: MutableSet>, + val edges: MutableMap, MutableSet>>, + val unresolvedCrossUnitCalls: Map, Set>>, ) { /** * Returns all traces from [sources] to [sink]. @@ -48,35 +49,18 @@ data class TraceGraph( } /** - * Merges two graphs. - * - * [sink] will be chosen from receiver, and edges from both graphs will be merged. - * Also, all edges from [upGraph]'s sink to [entryPoints] will be added - * (these are edges "connecting" [upGraph] with receiver). - * - * Informally, this method extends receiver's traces from one side using [upGraph]. + * Merges [upGraph] into this graph. */ fun mergeWithUpGraph( upGraph: TraceGraph, entryPoints: Set>, - ): TraceGraph { - val validEntryPoints = entryPoints.intersect(edges.keys) - if (validEntryPoints.isEmpty()) return this + ) { + sources.addAll(upGraph.sources) - val newSources = sources + upGraph.sources - val newEdges = edges.toMutableMap() - for ((source, destinations) in upGraph.edges) { - newEdges[source] = newEdges.getOrDefault(source, emptySet()) + destinations + for (edge in upGraph.edges) { + edges.getOrPut(edge.key) { hashSetOf() }.addAll(edge.value) } - newEdges[upGraph.sink] = newEdges.getOrDefault(upGraph.sink, emptySet()) + validEntryPoints - return TraceGraph(sink, newSources, newEdges) - } - companion object { - fun bySink( - sink: Vertex, - ): TraceGraph { - return TraceGraph(sink, setOf(sink), emptyMap()) - } + edges.getOrPut(upGraph.sink) { hashSetOf() }.addAll(entryPoints) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt index c4468433b..33a1c1bef 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt @@ -19,6 +19,7 @@ package org.jacodb.analysis.npe import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.ifds.Analyzer +import org.jacodb.analysis.ifds.Reason import org.jacodb.analysis.taint.EdgeForOtherRunner import org.jacodb.analysis.taint.NewSummaryEdge import org.jacodb.analysis.taint.NewVulnerability @@ -100,6 +101,6 @@ class NpeAnalyzer( caller: TaintVertex, callee: TaintVertex, ): List = buildList { - add(EdgeForOtherRunner(TaintEdge(callee, callee))) + add(EdgeForOtherRunner(TaintEdge(callee, callee), Reason.CrossUnitCall(caller))) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index 63073715b..f0c307715 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -203,7 +203,7 @@ class NpeManager( logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } return } - otherRunner.submitNewEdge(event.edge) + otherRunner.submitNewEdge(event.edge, event.reason) } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt index 6f298eca9..6bb596eed 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt @@ -25,6 +25,7 @@ import org.jacodb.analysis.ifds.ControlEvent import org.jacodb.analysis.ifds.Edge import org.jacodb.analysis.ifds.Manager import org.jacodb.analysis.ifds.QueueEmptinessChanged +import org.jacodb.analysis.ifds.Reason import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType import org.jacodb.api.JcMethod @@ -50,7 +51,7 @@ class BidiRunner( is EdgeForOtherRunner -> { if (unitResolver.resolve(event.edge.method) == unit) { // Submit new edge directly to the backward runner: - backwardRunner.submitNewEdge(event.edge) + backwardRunner.submitNewEdge(event.edge, event.reason) } else { // Submit new edge via the manager: manager.handleEvent(event) @@ -87,7 +88,7 @@ class BidiRunner( is EdgeForOtherRunner -> { check(unitResolver.resolve(event.edge.method) == unit) // Submit new edge directly to the forward runner: - forwardRunner.submitNewEdge(event.edge) + forwardRunner.submitNewEdge(event.edge, event.reason) } else -> manager.handleEvent(event) @@ -122,8 +123,8 @@ class BidiRunner( check(backwardRunner.unit == unit) } - override fun submitNewEdge(edge: Edge) { - forwardRunner.submitNewEdge(edge) + override fun submitNewEdge(edge: Edge, reason: Reason) { + forwardRunner.submitNewEdge(edge, reason) } override suspend fun run(startMethods: List) = coroutineScope { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt index e9cee3617..8080cdd91 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt @@ -20,6 +20,7 @@ import org.jacodb.analysis.config.CallPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.ifds.Analyzer import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.Reason import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.cfg.callExpr @@ -82,7 +83,7 @@ class TaintAnalyzer( caller: TaintVertex, callee: TaintVertex, ): List = buildList { - add(EdgeForOtherRunner(TaintEdge(callee, callee))) + add(EdgeForOtherRunner(TaintEdge(callee, callee), Reason.CrossUnitCall(caller))) } } @@ -102,7 +103,7 @@ class BackwardTaintAnalyzer( edge: TaintEdge, ): List = buildList { if (isExitPoint(edge.to.statement)) { - add(EdgeForOtherRunner(Edge(edge.to, edge.to))) + add(EdgeForOtherRunner(Edge(edge.to, edge.to), reason = Reason.External)) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt index cd345ff6b..d4163806b 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt @@ -16,6 +16,8 @@ package org.jacodb.analysis.taint +import org.jacodb.analysis.ifds.Reason + sealed interface TaintEvent data class NewSummaryEdge( @@ -28,6 +30,7 @@ data class NewVulnerability( data class EdgeForOtherRunner( val edge: TaintEdge, + val reason: Reason ) : TaintEvent { init { // TODO: remove this check diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index 04eff5079..fc1c774d5 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -35,10 +35,12 @@ import org.jacodb.analysis.ifds.ControlEvent import org.jacodb.analysis.ifds.Manager import org.jacodb.analysis.ifds.QueueEmptinessChanged import org.jacodb.analysis.ifds.SummaryStorageImpl +import org.jacodb.analysis.ifds.TraceGraph import org.jacodb.analysis.ifds.UniRunner import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType import org.jacodb.analysis.ifds.UnknownUnit +import org.jacodb.analysis.ifds.Vertex import org.jacodb.analysis.util.getGetPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph @@ -241,7 +243,7 @@ class TaintManager( logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } return } - otherRunner.submitNewEdge(event.edge) + otherRunner.submitNewEdge(event.edge, event.reason) } } } @@ -271,8 +273,37 @@ class TaintManager( .launchIn(scope) } - fun getAggregates(): Map> { - return runnerForUnit.mapValues { it.value.getAggregate() } + fun vulnerabilityTraceGraph(vulnerability: Vulnerability): TraceGraph { + val aggregate = getAggregateForMethod(vulnerability.method) + val initialGraph = aggregate.buildTraceGraph(vulnerability.sink) + val resultGraph = initialGraph.copy(unresolvedCrossUnitCalls = emptyMap()) + + val resolvedCrossUnitEdges = hashSetOf, Vertex>>() + val unresolvedCrossUnitCalls = initialGraph.unresolvedCrossUnitCalls.entries.toMutableList() + while (unresolvedCrossUnitCalls.isNotEmpty()) { + val (caller, callees) = unresolvedCrossUnitCalls.removeLast() + + val unresolvedCallees = hashSetOf>() + for (callee in callees) { + if (resolvedCrossUnitEdges.add(caller to callee)) { + unresolvedCallees.add(callee) + } + } + if (unresolvedCallees.isEmpty()) continue + + val callerAggregate = getAggregateForMethod(caller.method) + val callerGraph = callerAggregate.buildTraceGraph(caller) + resultGraph.mergeWithUpGraph(callerGraph, unresolvedCallees) + unresolvedCrossUnitCalls += callerGraph.unresolvedCrossUnitCalls.entries + } + + return resultGraph + } + + private fun getAggregateForMethod(method: JcMethod): Aggregate { + val unit = unitResolver.resolve(method) + val runner = runnerForUnit[unit] ?: error("No runner for $unit") + return runner.getAggregate() } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt index 95e758d15..ffabc9c3f 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt @@ -97,9 +97,9 @@ class IfdsSqlTest : BaseAnalysisTest() { val manager = TaintManager(graph, unitResolver, useBidiRunner = true) val sinks = manager.analyze(listOf(badMethod), timeout = 30.seconds) assertTrue(sinks.isNotEmpty()) - val aggregates = manager.getAggregates() - logger.debug { "Aggregates: $aggregates" } - val graphs = aggregates.mapValues { it.value.buildTraceGraph(sinks.first().sink) } - assertTrue(graphs.isNotEmpty()) + val sink = sinks.first() + val graph = manager.vulnerabilityTraceGraph(sink) + val trace = graph.getAllTraces().first() + logger.debug { "Some trace (of length ${trace.size}): $trace" } } } From f95366bf5be5685efd3929dd374b0ee990c9e1d2 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 15:53:53 +0300 Subject: [PATCH 087/117] Fix tests --- .../jacodb/analysis/impl/BaseAnalysisTest.kt | 8 ++-- .../org/jacodb/analysis/impl/IfdsNpeTest.kt | 22 +++++----- .../org/jacodb/analysis/impl/IfdsSqlTest.kt | 44 ++++++++----------- 3 files changed, 33 insertions(+), 41 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index 2cce441a4..1b8471f57 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -104,9 +104,7 @@ abstract class BaseAnalysisTest : BaseTest() { } } - protected abstract fun findSinks(methods: List): List - - protected fun testSingleJulietClass(className: String) { + protected fun testSingleJulietClass(className: String, findSinks: (JcMethod) -> List) { logger.info { className } val clazz = cp.findClass(className) @@ -114,7 +112,7 @@ abstract class BaseAnalysisTest : BaseTest() { val goodMethod = clazz.methods.single { it.name == "good" } logger.info { "Searching for sinks in BAD method: $badMethod" } - val badIssues = findSinks(listOf(badMethod)) + val badIssues = findSinks(badMethod) logger.info { "Total ${badIssues.size} issues in BAD method" } for (issue in badIssues) { logger.debug { " - $issue" } @@ -122,7 +120,7 @@ abstract class BaseAnalysisTest : BaseTest() { assertTrue(badIssues.isNotEmpty()) { "Must find some sinks in 'bad' for $className" } logger.info { "Searching for sinks in GOOD method: $goodMethod" } - val goodIssues = findSinks(listOf(goodMethod)) + val goodIssues = findSinks(goodMethod) logger.info { "Total ${goodIssues.size} issues in GOOD method" } for (issue in goodIssues) { logger.debug { " - $issue" } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt index 07a816eda..d27f551fb 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt @@ -19,7 +19,7 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.ifds.SingletonUnitResolver -import org.jacodb.analysis.npe.NpeManager +import org.jacodb.analysis.taint.TaintManager import org.jacodb.analysis.taint.Vulnerability import org.jacodb.api.JcMethod import org.jacodb.api.ext.constructors @@ -53,12 +53,6 @@ class IfdsNpeTest : BaseAnalysisTest() { provideClassesForJuliet(690) } - override fun findSinks(methods: List): List { - val unitResolver = SingletonUnitResolver - val manager = NpeManager(graph, unitResolver) - return manager.analyze(methods, timeout = 30.seconds) - } - @Test fun `fields resolving should work through interfaces`() = runBlocking { val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) @@ -201,16 +195,22 @@ class IfdsNpeTest : BaseAnalysisTest() { testOneMethod("nullAssignmentToCopy", emptyList()) } + private fun findSinks(method: JcMethod): List { + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + return manager.analyze(listOf(method), timeout = 30.seconds) + } + @ParameterizedTest @MethodSource("provideClassesForJuliet476") fun `test on Juliet's CWE 476`(className: String) { - testSingleJulietClass(className) + testSingleJulietClass(className, ::findSinks) } @ParameterizedTest @MethodSource("provideClassesForJuliet690") fun `test on Juliet's CWE 690`(className: String) { - testSingleJulietClass(className) + testSingleJulietClass(className, ::findSinks) } @Test @@ -220,7 +220,7 @@ class IfdsNpeTest : BaseAnalysisTest() { val className = "juliet.testcases.CWE690_NULL_Deref_From_Return.CWE690_NULL_Deref_From_Return__Properties_getProperty_equals_01" - testSingleJulietClass(className) + testSingleJulietClass(className, ::findSinks) } @Test @@ -235,7 +235,7 @@ class IfdsNpeTest : BaseAnalysisTest() { expectedLocations: Collection, ) { val method = cp.findClass().declaredMethods.single { it.name == methodName } - val sinks = findSinks(listOf(method)) + val sinks = findSinks(method) // TODO: think about better assertions here Assertions.assertEquals(expectedLocations.size, sinks.size) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt index ffabc9c3f..c0c34727c 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt @@ -19,8 +19,6 @@ package org.jacodb.analysis.impl import org.jacodb.analysis.ifds.ClassUnitResolver import org.jacodb.analysis.ifds.SingletonUnitResolver import org.jacodb.analysis.taint.TaintManager -import org.jacodb.analysis.taint.Vulnerability -import org.jacodb.api.JcMethod import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods import org.jacodb.impl.features.InMemoryHierarchy @@ -49,43 +47,39 @@ class IfdsSqlTest : BaseAnalysisTest() { ) } - override fun findSinks(methods: List): List { - val unitResolver = SingletonUnitResolver - val manager = TaintManager(graph, unitResolver) - return manager.analyze(methods, timeout = 30.seconds) - } - @Test fun `simple SQL injection`() { val methodName = "bad" val method = cp.findClass().declaredMethods.single { it.name == methodName } - val sinks = findSinks(listOf(method)) + val methods = listOf(method) + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + val sinks = manager.analyze(methods, timeout = 30.seconds) assertTrue(sinks.isNotEmpty()) + val sink = sinks.first() + val graph = manager.vulnerabilityTraceGraph(sink) + val trace = graph.getAllTraces().first() + assertTrue(trace.isNotEmpty()) } @ParameterizedTest @MethodSource("provideClassesForJuliet89") fun `test on Juliet's CWE 89`(className: String) { - testSingleJulietClass(className) - } - - @Test - fun `test on Juliet's CWE 89 Environment executeBatch 01`() { - val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__Environment_executeBatch_01" - testSingleJulietClass(className) - } - - @Test - fun `test on Juliet's CWE 89 database prepareStatement 01`() { - val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__database_prepareStatement_01" - testSingleJulietClass(className) + testSingleJulietClass(className) { method -> + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + manager.analyze(listOf(method), timeout = 30.seconds) + } } @Test fun `test on specific Juliet instance`() { val className = "juliet.testcases.CWE89_SQL_Injection.s01.CWE89_SQL_Injection__connect_tcp_execute_01" - - testSingleJulietClass(className) + testSingleJulietClass(className) { method -> + val unitResolver = SingletonUnitResolver + val manager = TaintManager(graph, unitResolver) + manager.analyze(listOf(method), timeout = 30.seconds) + } } @Test @@ -100,6 +94,6 @@ class IfdsSqlTest : BaseAnalysisTest() { val sink = sinks.first() val graph = manager.vulnerabilityTraceGraph(sink) val trace = graph.getAllTraces().first() - logger.debug { "Some trace (of length ${trace.size}): $trace" } + assertTrue(trace.isNotEmpty()) } } From 365b4cc30ab9286ea4b6cc2edb54824968f56f2f Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 18:07:53 +0300 Subject: [PATCH 088/117] Fix unused variable analysis --- .../ifds/{Aggregate.kt => IfdsResult.kt} | 22 +++-- .../kotlin/org/jacodb/analysis/ifds/Runner.kt | 11 +-- .../org/jacodb/analysis/ifds/Summary.kt | 15 ++++ .../org/jacodb/analysis/npe/NpeAnalyzers.kt | 10 +-- .../jacodb/analysis/npe/NpeFlowFunctions.kt | 40 ++++----- .../org/jacodb/analysis/npe/NpeManager.kt | 28 ++++--- .../org/jacodb/analysis/taint/BidiRunner.kt | 20 ++--- .../jacodb/analysis/taint/TaintAnalyzers.kt | 6 +- .../org/jacodb/analysis/taint/TaintEvents.kt | 4 +- .../org/jacodb/analysis/taint/TaintFacts.kt | 8 +- .../analysis/taint/TaintFlowFunctions.kt | 60 +++++++------- .../org/jacodb/analysis/taint/TaintManager.kt | 48 ++++++----- .../jacodb/analysis/taint/TaintSummaries.kt | 29 ++----- .../kotlin/org/jacodb/analysis/taint/types.kt | 6 +- ...{Analyzer.kt => UnusedVariableAnalyzer.kt} | 6 +- .../{Events.kt => UnusedVariableEvents.kt} | 4 +- .../{Facts.kt => UnusedVariableFacts.kt} | 8 +- ...ions.kt => UnusedVariableFlowFunctions.kt} | 28 +++---- .../{Manager.kt => UnusedVariableManager.kt} | 55 +++++++++---- ...{Summary.kt => UnusedVariableSummaries.kt} | 25 ++---- .../org/jacodb/analysis/unused/Utils.kt | 57 +++++++++++++ .../jacodb/analysis/impl/BaseAnalysisTest.kt | 4 +- .../org/jacodb/analysis/impl/IfdsNpeTest.kt | 7 +- .../jacodb/analysis/impl/IfdsUnusedTest.kt | 82 +++++++++++++++++++ .../analysis/impl/JodaDateTimeAnalysisTest.kt | 2 - .../analysis/impl/TaintFlowFunctionsTest.kt | 24 +++--- 26 files changed, 395 insertions(+), 214 deletions(-) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/{Aggregate.kt => IfdsResult.kt} (89%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/{Analyzer.kt => UnusedVariableAnalyzer.kt} (82%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/{Events.kt => UnusedVariableEvents.kt} (89%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/{Facts.kt => UnusedVariableFacts.kt} (82%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/{FlowFunctions.kt => UnusedVariableFlowFunctions.kt} (81%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/{Manager.kt => UnusedVariableManager.kt} (82%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/{Summary.kt => UnusedVariableSummaries.kt} (65%) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt create mode 100644 jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/IfdsResult.kt similarity index 89% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/IfdsResult.kt index d44abc6ee..0584541b1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Aggregate.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/IfdsResult.kt @@ -16,19 +16,28 @@ package org.jacodb.analysis.ifds -import org.jacodb.analysis.taint.Zero import org.jacodb.api.cfg.JcInst /** * Aggregates all facts and edges found by the tabulation algorithm. */ -class Aggregate( - pathEdges: Collection>, +class IfdsResult internal constructor( + val pathEdgesBySink: Map, Collection>>, val facts: Map>, val reasons: Map, Set>>, + val zeroFact: Fact?, ) { - private val pathEdgesBySink: Map, Collection>> = - pathEdges.groupByTo(HashMap()) { it.to } + constructor( + pathEdges: Collection>, + facts: Map>, + reasons: Map, Set>>, + zeroFact: Fact?, + ) : this( + pathEdges.groupByTo(HashMap()) { it.to }, + facts, + reasons, + zeroFact + ) fun buildTraceGraph(sink: Vertex): TraceGraph { val sources: MutableSet> = hashSetOf() @@ -61,8 +70,7 @@ class Aggregate( } val vertex = edge.to - // FIXME: not all domains have "Zero" fact! - if (vertex.fact == Zero) { + if (vertex.fact == zeroFact) { addEdge(vertex, lastVertex) sources.add(vertex) return diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt index 163c530ea..e0d3902a1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.channels.getOrElse import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive import org.jacodb.analysis.graph.JcNoopInst -import org.jacodb.analysis.taint.Zero +import org.jacodb.analysis.taint.TaintZeroFact import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst @@ -36,7 +36,7 @@ interface Runner { suspend fun run(startMethods: List) fun submitNewEdge(edge: Edge, reason: Reason) - fun getAggregate(): Aggregate + fun getIfdsResult(): IfdsResult } class UniRunner( @@ -45,6 +45,7 @@ class UniRunner( private val manager: Manager, private val unitResolver: UnitResolver, override val unit: UnitType, + private val zeroFact: Fact?, ) : Runner { private val flowSpace: FlowFunctions = analyzer.flowFunctions @@ -97,7 +98,7 @@ class UniRunner( val doPrintOnlyForward = true val doPrintZero = false if (!doPrintOnlyForward || edge.from.statement is JcNoopInst) { - if (doPrintZero || edge.to.fact != Zero) { + if (doPrintZero || edge.to.fact != TaintZeroFact) { logger.trace { "Propagating edge=$edge in method=${edge.method.name} with reason=${reason}" } } } @@ -249,8 +250,8 @@ class UniRunner( return resultFacts } - override fun getAggregate(): Aggregate { + override fun getIfdsResult(): IfdsResult { val facts = getFinalFacts() - return Aggregate(pathEdges, facts, reasons) + return IfdsResult(pathEdges, facts, reasons, zeroFact) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt index f1c06971d..3f0c90206 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt @@ -30,6 +30,21 @@ interface Summary { val method: JcMethod } +interface SummaryEdge : Summary { + val edge: Edge + + override val method: JcMethod + get() = edge.method +} + +interface Vulnerability : Summary { + val message: String + val sink: Vertex + + override val method: JcMethod + get() = sink.method +} + /** * Contains summaries for many methods and allows to update them and subscribe for them. */ diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt index 33a1c1bef..ccd671aa4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeAnalyzers.kt @@ -25,10 +25,10 @@ import org.jacodb.analysis.taint.NewSummaryEdge import org.jacodb.analysis.taint.NewVulnerability import org.jacodb.analysis.taint.TaintEdge import org.jacodb.analysis.taint.TaintEvent -import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.TaintDomainFact import org.jacodb.analysis.taint.TaintVertex import org.jacodb.analysis.taint.Tainted -import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.analysis.taint.TaintVulnerability import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.cfg.callExpr @@ -40,7 +40,7 @@ private val logger = mu.KotlinLogging.logger {} class NpeAnalyzer( private val graph: JcApplicationGraph, -) : Analyzer { +) : Analyzer { override val flowFunctions: ForwardNpeFlowFunctions by lazy { ForwardNpeFlowFunctions(graph.classpath, graph) @@ -63,7 +63,7 @@ class NpeAnalyzer( if (edge.to.fact is Tainted && edge.to.fact.mark == TaintMark.NULLNESS) { if (edge.to.fact.variable.isDereferencedAt(edge.to.statement)) { val message = "NPE" // TODO - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge) + val vulnerability = TaintVulnerability(message, sink = edge.to) logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) } @@ -90,7 +90,7 @@ class NpeAnalyzer( if (item.condition.accept(conditionEvaluator)) { logger.trace { "Found sink at ${edge.to} in ${edge.method} on $item" } val message = item.ruleNote - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = item) + val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) add(NewVulnerability(vulnerability)) } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index 0bc034ccb..7d4262bda 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -30,9 +30,9 @@ import org.jacodb.analysis.ifds.onSome import org.jacodb.analysis.ifds.toMaybe import org.jacodb.analysis.ifds.toPath import org.jacodb.analysis.ifds.toPathOrNull -import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.TaintDomainFact import org.jacodb.analysis.taint.Tainted -import org.jacodb.analysis.taint.Zero +import org.jacodb.analysis.taint.TaintZeroFact import org.jacodb.analysis.util.getArgument import org.jacodb.analysis.util.getArgumentsOf import org.jacodb.analysis.util.startsWith @@ -81,7 +81,7 @@ private val logger = mu.KotlinLogging.logger {} class ForwardNpeFlowFunctions( private val cp: JcClasspath, private val graph: JcApplicationGraph, -) : FlowFunctions { +) : FlowFunctions { internal val taintConfigurationFeature: TaintConfigurationFeature? by lazy { cp.features @@ -91,7 +91,7 @@ class ForwardNpeFlowFunctions( override fun obtainPossibleStartFacts( method: JcMethod, - ): Collection = buildSet { + ): Collection = buildSet { addAll(obtainPossibleStartFactsBasic(method)) // Possibly null arguments: @@ -105,9 +105,9 @@ class ForwardNpeFlowFunctions( private fun obtainPossibleStartFactsBasic( method: JcMethod, - ): Collection = buildSet { + ): Collection = buildSet { // Zero (reachability) fact always present at entrypoint: - add(Zero) + add(TaintZeroFact) // Possibly null arguments: // for (p in method.parameters.filter { it.isNullable != false }) { @@ -227,7 +227,7 @@ class ForwardNpeFlowFunctions( return listOf(fact) } - private fun generates(inst: JcInst): Collection = buildList { + private fun generates(inst: JcInst): Collection = buildList { if (inst is JcAssignInst) { val toPath = inst.lhv.toPath() val from = inst.rhv @@ -256,7 +256,7 @@ class ForwardNpeFlowFunctions( override fun obtainSequentFlowFunction( current: JcInst, next: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { if (fact.variable.isDereferencedAt(current)) { return@FlowFunction emptySet() @@ -266,7 +266,7 @@ class ForwardNpeFlowFunctions( if (current is JcIfInst) { val nextIsTrueBranch = next.location.index == current.trueBranch.index val pathComparedWithNull = current.pathComparedWithNull - if (fact == Zero) { + if (fact == TaintZeroFact) { if (pathComparedWithNull != null) { if ((current.condition is JcEqExpr && nextIsTrueBranch) || (current.condition is JcNeqExpr && !nextIsTrueBranch) @@ -284,15 +284,15 @@ class ForwardNpeFlowFunctions( } if ((expr is JcEqExpr && nextIsTrueBranch) || (expr is JcNeqExpr && !nextIsTrueBranch)) { // comparedPath is null in this branch - return@FlowFunction listOf(Zero) + return@FlowFunction listOf(TaintZeroFact) } else { return@FlowFunction emptyList() } } } - if (fact is Zero) { - return@FlowFunction listOf(Zero) + generates(current) + if (fact is TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) + generates(current) } check(fact is Tainted) @@ -362,7 +362,7 @@ class ForwardNpeFlowFunctions( override fun obtainCallToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, // FIXME: unused? - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { if (fact.variable.isDereferencedAt(callStatement)) { return@FlowFunction emptySet() @@ -392,9 +392,9 @@ class ForwardNpeFlowFunctions( val config = taintConfigurationFeature?.getConfigForMethod(callee) - if (fact == Zero) { + if (fact == TaintZeroFact) { return@FlowFunction buildSet { - add(Zero) + add(TaintZeroFact) if (callStatement is JcAssignInst) { val toPath = callStatement.lhv.toPath() @@ -545,10 +545,10 @@ class ForwardNpeFlowFunctions( override fun obtainCallToStartFlowFunction( callStatement: JcInst, calleeStart: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> val callee = calleeStart.location.method - if (fact == Zero) { + if (fact == TaintZeroFact) { // return@FlowFunction obtainPossibleStartFacts(callee) return@FlowFunction obtainPossibleStartFactsBasic(callee) } @@ -595,12 +595,12 @@ class ForwardNpeFlowFunctions( callStatement: JcInst, returnSite: JcInst, // unused exitStatement: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> // TODO: do we even need to return non-empty list for zero fact here? - if (fact == Zero) { + if (fact == TaintZeroFact) { // return@FlowFunction listOf(Zero) return@FlowFunction buildSet { - add(Zero) + add(TaintZeroFact) if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { // Note: returnValue can be null here in some weird cases, e.g. in lambda. exitStatement.returnValue?.let { returnValue -> diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index f0c307715..5eaba889f 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -39,12 +39,13 @@ import org.jacodb.analysis.ifds.UnitType import org.jacodb.analysis.taint.EdgeForOtherRunner import org.jacodb.analysis.taint.NewSummaryEdge import org.jacodb.analysis.taint.NewVulnerability -import org.jacodb.analysis.taint.SummaryEdge +import org.jacodb.analysis.taint.TaintSummaryEdge import org.jacodb.analysis.taint.TaintEdge import org.jacodb.analysis.taint.TaintEvent -import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.TaintDomainFact import org.jacodb.analysis.taint.TaintRunner -import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.analysis.taint.TaintVulnerability +import org.jacodb.analysis.taint.TaintZeroFact import org.jacodb.analysis.util.getGetPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph @@ -60,14 +61,14 @@ private val logger = mu.KotlinLogging.logger {} class NpeManager( private val graph: JcApplicationGraph, private val unitResolver: UnitResolver, -) : Manager { +) : Manager { private val methodsForUnit: MutableMap> = hashMapOf() private val runnerForUnit: MutableMap = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() - private val summaryEdgesStorage = SummaryStorageImpl() - private val vulnerabilitiesStorage = SummaryStorageImpl() + private val summaryEdgesStorage = SummaryStorageImpl() + private val vulnerabilitiesStorage = SummaryStorageImpl() private val stopRendezvous = Channel(Channel.RENDEZVOUS) @@ -77,7 +78,14 @@ class NpeManager( check(unit !in runnerForUnit) { "Runner for $unit already exists" } val analyzer = NpeAnalyzer(graph) - val runner = UniRunner(graph, analyzer, this@NpeManager, unitResolver, unit) + val runner = UniRunner( + graph = graph, + analyzer = analyzer, + manager = this@NpeManager, + unitResolver = unitResolver, + unit = unit, + zeroFact = TaintZeroFact + ) runnerForUnit[unit] = runner return runner @@ -94,7 +102,7 @@ class NpeManager( fun analyze( startMethods: List, timeout: Duration = 3600.seconds, - ): List = runBlocking(Dispatchers.Default) { + ): List = runBlocking(Dispatchers.Default) { val timeStart = TimeSource.Monotonic.markNow() // Add start methods: @@ -189,7 +197,7 @@ class NpeManager( override fun handleEvent(event: TaintEvent) { when (event) { is NewSummaryEdge -> { - summaryEdgesStorage.add(SummaryEdge(event.edge)) + summaryEdgesStorage.add(TaintSummaryEdge(event.edge)) } is NewVulnerability -> { @@ -238,7 +246,7 @@ fun runNpeAnalysis( graph: JcApplicationGraph, unitResolver: UnitResolver, startMethods: List, -): List { +): List { val manager = NpeManager(graph, unitResolver) return manager.analyze(startMethods) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt index 6bb596eed..6f3bea852 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt @@ -20,9 +20,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import org.jacodb.analysis.ifds.Aggregate import org.jacodb.analysis.ifds.ControlEvent import org.jacodb.analysis.ifds.Edge +import org.jacodb.analysis.ifds.IfdsResult import org.jacodb.analysis.ifds.Manager import org.jacodb.analysis.ifds.QueueEmptinessChanged import org.jacodb.analysis.ifds.Reason @@ -34,8 +34,8 @@ class BidiRunner( val manager: TaintManager, val unitResolver: UnitResolver, override val unit: UnitType, - newForwardRunner: (Manager) -> TaintRunner, - newBackwardRunner: (Manager) -> TaintRunner, + newForwardRunner: (Manager) -> TaintRunner, + newBackwardRunner: (Manager) -> TaintRunner, ) : TaintRunner { @Volatile @@ -44,8 +44,8 @@ class BidiRunner( @Volatile private var backwardQueueIsEmpty: Boolean = false - private val forwardManager: Manager = - object : Manager { + private val forwardManager: Manager = + object : Manager { override fun handleEvent(event: TaintEvent) { when (event) { is EdgeForOtherRunner -> { @@ -81,8 +81,8 @@ class BidiRunner( } } - private val backwardManager: Manager = - object : Manager { + private val backwardManager: Manager = + object : Manager { override fun handleEvent(event: TaintEvent) { when (event) { is EdgeForOtherRunner -> { @@ -123,7 +123,7 @@ class BidiRunner( check(backwardRunner.unit == unit) } - override fun submitNewEdge(edge: Edge, reason: Reason) { + override fun submitNewEdge(edge: Edge, reason: Reason) { forwardRunner.submitNewEdge(edge, reason) } @@ -138,7 +138,7 @@ class BidiRunner( forwardRunnerJob.join() } - override fun getAggregate(): Aggregate { - return forwardRunner.getAggregate() + override fun getIfdsResult(): IfdsResult { + return forwardRunner.getIfdsResult() } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt index 8080cdd91..b64c72cc6 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt @@ -31,7 +31,7 @@ private val logger = mu.KotlinLogging.logger {} class TaintAnalyzer( private val graph: JcApplicationGraph, -) : Analyzer { +) : Analyzer { override val flowFunctions: ForwardTaintFlowFunctions by lazy { ForwardTaintFlowFunctions(graph.classpath, graph) @@ -71,7 +71,7 @@ class TaintAnalyzer( for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { val message = item.ruleNote - val vulnerability = Vulnerability(message, sink = edge.to, edge = edge, rule = item) + val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) } @@ -89,7 +89,7 @@ class TaintAnalyzer( class BackwardTaintAnalyzer( private val graph: JcApplicationGraph, -) : Analyzer { +) : Analyzer { override val flowFunctions: BackwardTaintFlowFunctions by lazy { BackwardTaintFlowFunctions(graph.classpath, graph) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt index d4163806b..ce1e9c8dc 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintEvents.kt @@ -25,12 +25,12 @@ data class NewSummaryEdge( ) : TaintEvent data class NewVulnerability( - val vulnerability: Vulnerability, + val vulnerability: TaintVulnerability, ) : TaintEvent data class EdgeForOtherRunner( val edge: TaintEdge, - val reason: Reason + val reason: Reason ) : TaintEvent { init { // TODO: remove this check diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt index 5da89d340..0fec8b62c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFacts.kt @@ -19,13 +19,13 @@ package org.jacodb.analysis.taint import org.jacodb.analysis.ifds.AccessPath import org.jacodb.taint.configuration.TaintMark -sealed interface TaintFact +sealed interface TaintDomainFact -object Zero : TaintFact { - override fun toString(): String = javaClass.simpleName +object TaintZeroFact : TaintDomainFact { + override fun toString(): String = "Zero" } data class Tainted( val variable: AccessPath, val mark: TaintMark, -) : TaintFact +) : TaintDomainFact diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt index a4b4521ed..47fc265d6 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt @@ -66,7 +66,7 @@ private val logger = mu.KotlinLogging.logger {} class ForwardTaintFlowFunctions( private val cp: JcClasspath, private val graph: JcApplicationGraph, -) : FlowFunctions { +) : FlowFunctions { internal val taintConfigurationFeature: TaintConfigurationFeature? by lazy { cp.features @@ -76,9 +76,9 @@ class ForwardTaintFlowFunctions( override fun obtainPossibleStartFacts( method: JcMethod, - ): Collection = buildSet { + ): Collection = buildSet { // Zero (reachability) fact always present at entrypoint: - add(Zero) + add(TaintZeroFact) // Extract initial facts from the config: val config = taintConfigurationFeature?.getConfigForMethod(method) @@ -176,9 +176,9 @@ class ForwardTaintFlowFunctions( override fun obtainSequentFlowFunction( current: JcInst, next: JcInst, - ) = FlowFunction { fact -> - if (fact is Zero) { - return@FlowFunction listOf(Zero) + ) = FlowFunction { fact -> + if (fact is TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) } check(fact is Tainted) @@ -236,7 +236,7 @@ class ForwardTaintFlowFunctions( override fun obtainCallToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, // FIXME: unused? - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") val callee = callExpr.method.method @@ -260,9 +260,9 @@ class ForwardTaintFlowFunctions( val config = taintConfigurationFeature?.getConfigForMethod(callee) - if (fact == Zero) { + if (fact == TaintZeroFact) { return@FlowFunction buildSet { - add(Zero) + add(TaintZeroFact) if (config != null) { val conditionEvaluator = BasicConditionEvaluator(CallPositionToJcValueResolver(callStatement)) @@ -387,10 +387,10 @@ class ForwardTaintFlowFunctions( override fun obtainCallToStartFlowFunction( callStatement: JcInst, calleeStart: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> val callee = calleeStart.location.method - if (fact == Zero) { + if (fact == TaintZeroFact) { return@FlowFunction obtainPossibleStartFacts(callee) } check(fact is Tainted) @@ -422,9 +422,9 @@ class ForwardTaintFlowFunctions( callStatement: JcInst, returnSite: JcInst, // unused exitStatement: JcInst, - ) = FlowFunction { fact -> - if (fact == Zero) { - return@FlowFunction listOf(Zero) + ) = FlowFunction { fact -> + if (fact == TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) } check(fact is Tainted) @@ -466,19 +466,19 @@ class ForwardTaintFlowFunctions( class BackwardTaintFlowFunctions( private val project: JcClasspath, private val graph: JcApplicationGraph, -) : FlowFunctions { +) : FlowFunctions { override fun obtainPossibleStartFacts( method: JcMethod, - ): Collection { - return listOf(Zero) + ): Collection { + return listOf(TaintZeroFact) } private fun transmitTaintBackwardAssign( fact: Tainted, from: JcValue, to: JcExpr, - ): Collection { + ): Collection { val fromPath = from.toPath() val toPath = to.toPathOrNull() @@ -504,7 +504,7 @@ class BackwardTaintFlowFunctions( private fun transmitTaintBackwardNormal( fact: Tainted, inst: JcInst, - ): List { + ): List { // Pass-through: return listOf(fact) } @@ -512,9 +512,9 @@ class BackwardTaintFlowFunctions( override fun obtainSequentFlowFunction( current: JcInst, next: JcInst, - ) = FlowFunction { fact -> - if (fact is Zero) { - return@FlowFunction listOf(Zero) + ) = FlowFunction { fact -> + if (fact is TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) } check(fact is Tainted) @@ -572,11 +572,11 @@ class BackwardTaintFlowFunctions( override fun obtainCallToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, // FIXME: unused? - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> // TODO: pass-through on invokedynamic-based String concatenation - if (fact == Zero) { - return@FlowFunction listOf(Zero) + if (fact == TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) } check(fact is Tainted) @@ -620,10 +620,10 @@ class BackwardTaintFlowFunctions( override fun obtainCallToStartFlowFunction( callStatement: JcInst, calleeStart: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> val callee = calleeStart.location.method - if (fact == Zero) { + if (fact == TaintZeroFact) { return@FlowFunction obtainPossibleStartFacts(callee) } check(fact is Tainted) @@ -669,9 +669,9 @@ class BackwardTaintFlowFunctions( callStatement: JcInst, returnSite: JcInst, exitStatement: JcInst, - ) = FlowFunction { fact -> - if (fact == Zero) { - return@FlowFunction listOf(Zero) + ) = FlowFunction { fact -> + if (fact == TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) } check(fact is Tainted) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index fc1c774d5..c96ad6fb2 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -30,8 +30,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull import org.jacodb.analysis.graph.reversed -import org.jacodb.analysis.ifds.Aggregate import org.jacodb.analysis.ifds.ControlEvent +import org.jacodb.analysis.ifds.IfdsResult import org.jacodb.analysis.ifds.Manager import org.jacodb.analysis.ifds.QueueEmptinessChanged import org.jacodb.analysis.ifds.SummaryStorageImpl @@ -57,14 +57,14 @@ class TaintManager( private val graph: JcApplicationGraph, private val unitResolver: UnitResolver, private val useBidiRunner: Boolean = false, -) : Manager { +) : Manager { private val methodsForUnit: MutableMap> = hashMapOf() private val runnerForUnit: MutableMap = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() - private val summaryEdgesStorage = SummaryStorageImpl() - private val vulnerabilitiesStorage = SummaryStorageImpl() + private val summaryEdgesStorage = SummaryStorageImpl() + private val vulnerabilitiesStorage = SummaryStorageImpl() private val stopRendezvous = Channel(Channel.RENDEZVOUS) @@ -86,7 +86,8 @@ class TaintManager( analyzer = analyzer, manager = manager, unitResolver = unitResolver, - unit = unit + unit = unit, + zeroFact = TaintZeroFact ) }, { manager -> @@ -96,13 +97,21 @@ class TaintManager( analyzer = analyzer, manager = manager, unitResolver = unitResolver, - unit = unit + unit = unit, + zeroFact = TaintZeroFact ) } ) } else { val analyzer = TaintAnalyzer(graph) - UniRunner(graph, analyzer, this@TaintManager, unitResolver, unit) + UniRunner( + graph = graph, + analyzer = analyzer, + manager = this@TaintManager, + unitResolver = unitResolver, + unit = unit, + zeroFact = TaintZeroFact + ) } runnerForUnit[unit] = runner @@ -133,7 +142,7 @@ class TaintManager( fun analyze( startMethods: List, timeout: Duration = 3600.seconds, - ): List = runBlocking(Dispatchers.Default) { + ): List = runBlocking(Dispatchers.Default) { val timeStart = TimeSource.Monotonic.markNow() // Add start methods: @@ -228,7 +237,7 @@ class TaintManager( override fun handleEvent(event: TaintEvent) { when (event) { is NewSummaryEdge -> { - summaryEdgesStorage.add(SummaryEdge(event.edge)) + summaryEdgesStorage.add(TaintSummaryEdge(event.edge)) } is NewVulnerability -> { @@ -273,26 +282,27 @@ class TaintManager( .launchIn(scope) } - fun vulnerabilityTraceGraph(vulnerability: Vulnerability): TraceGraph { - val aggregate = getAggregateForMethod(vulnerability.method) - val initialGraph = aggregate.buildTraceGraph(vulnerability.sink) + fun vulnerabilityTraceGraph(vulnerability: TaintVulnerability): TraceGraph { + val result = getIfdsResultForMethod(vulnerability.method) + val initialGraph = result.buildTraceGraph(vulnerability.sink) val resultGraph = initialGraph.copy(unresolvedCrossUnitCalls = emptyMap()) - val resolvedCrossUnitEdges = hashSetOf, Vertex>>() + val resolvedCrossUnitEdges = hashSetOf, Vertex>>() val unresolvedCrossUnitCalls = initialGraph.unresolvedCrossUnitCalls.entries.toMutableList() while (unresolvedCrossUnitCalls.isNotEmpty()) { val (caller, callees) = unresolvedCrossUnitCalls.removeLast() - val unresolvedCallees = hashSetOf>() + val unresolvedCallees = hashSetOf>() for (callee in callees) { if (resolvedCrossUnitEdges.add(caller to callee)) { unresolvedCallees.add(callee) } } + if (unresolvedCallees.isEmpty()) continue - val callerAggregate = getAggregateForMethod(caller.method) - val callerGraph = callerAggregate.buildTraceGraph(caller) + val callerResult = getIfdsResultForMethod(caller.method) + val callerGraph = callerResult.buildTraceGraph(caller) resultGraph.mergeWithUpGraph(callerGraph, unresolvedCallees) unresolvedCrossUnitCalls += callerGraph.unresolvedCrossUnitCalls.entries } @@ -300,10 +310,10 @@ class TaintManager( return resultGraph } - private fun getAggregateForMethod(method: JcMethod): Aggregate { + private fun getIfdsResultForMethod(method: JcMethod): IfdsResult { val unit = unitResolver.resolve(method) val runner = runnerForUnit[unit] ?: error("No runner for $unit") - return runner.getAggregate() + return runner.getIfdsResult() } } @@ -311,7 +321,7 @@ fun runTaintAnalysis( graph: JcApplicationGraph, unitResolver: UnitResolver, startMethods: List, -): List { +): List { val manager = TaintManager(graph, unitResolver) return manager.analyze(startMethods) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt index 37dfad8fa..bb70b5349 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintSummaries.kt @@ -16,27 +16,16 @@ package org.jacodb.analysis.taint -import org.jacodb.analysis.ifds.Summary -import org.jacodb.api.JcMethod +import org.jacodb.analysis.ifds.SummaryEdge +import org.jacodb.analysis.ifds.Vulnerability import org.jacodb.taint.configuration.TaintMethodSink -/** - * Represents a path edge which starts in an entrypoint - * and ends in an exit-point of a method. - */ -data class SummaryEdge( - val edge: TaintEdge, -) : Summary { - override val method: JcMethod - get() = edge.method -} +data class TaintSummaryEdge( + override val edge: TaintEdge, +) : SummaryEdge -data class Vulnerability( - val message: String, - val sink: TaintVertex, - val edge: TaintEdge? = null, +data class TaintVulnerability( + override val message: String, + override val sink: TaintVertex, val rule: TaintMethodSink? = null, -) : Summary { - override val method: JcMethod - get() = sink.method -} +) : Vulnerability diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt index c6617e418..88be4cd3a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt @@ -20,6 +20,6 @@ import org.jacodb.analysis.ifds.Edge import org.jacodb.analysis.ifds.Runner import org.jacodb.analysis.ifds.Vertex -typealias TaintVertex = Vertex -typealias TaintEdge = Edge -typealias TaintRunner = Runner +typealias TaintVertex = Vertex +typealias TaintEdge = Edge +typealias TaintRunner = Runner diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableAnalyzer.kt similarity index 82% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableAnalyzer.kt index 722e9364c..2acf1b730 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Analyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableAnalyzer.kt @@ -24,7 +24,7 @@ import org.jacodb.api.cfg.JcInst class UnusedVariableAnalyzer( private val graph: JcApplicationGraph, -) : Analyzer { +) : Analyzer { override val flowFunctions: UnusedVariableFlowFunctions by lazy { UnusedVariableFlowFunctions(graph) @@ -34,13 +34,13 @@ class UnusedVariableAnalyzer( return statement in graph.exitPoints(statement.location.method) } - override fun handleNewEdge(edge: Edge): List = buildList { + override fun handleNewEdge(edge: Edge): List = buildList { if (isExitPoint(edge.to.statement)) { add(NewSummaryEdge(edge)) } } - override fun handleCrossUnitCall(caller: Vertex, callee: Vertex): List { + override fun handleCrossUnitCall(caller: Vertex, callee: Vertex): List { return emptyList() } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt similarity index 89% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt index 698494c9f..f2cccecce 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Events.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt @@ -21,9 +21,9 @@ import org.jacodb.analysis.ifds.Edge sealed interface Event data class NewSummaryEdge( - val edge: Edge, + val edge: Edge, ) : Event data class NewVulnerability( - val vulnerability: Vulnerability, + val vulnerability: UnusedVariableVulnerability, ) : Event diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableFacts.kt similarity index 82% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableFacts.kt index a17eccec2..4def264ad 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Facts.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableFacts.kt @@ -19,13 +19,13 @@ package org.jacodb.analysis.unused import org.jacodb.analysis.ifds.AccessPath import org.jacodb.api.cfg.JcInst -sealed interface Fact +sealed interface UnusedVariableDomainFact -object Zero : Fact { - override fun toString(): String = javaClass.simpleName +object UnusedVariableZeroFact : UnusedVariableDomainFact { + override fun toString(): String = "Zero" } data class UnusedVariable( val variable: AccessPath, val initStatement: JcInst, -) : Fact +) : UnusedVariableDomainFact diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableFlowFunctions.kt similarity index 81% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableFlowFunctions.kt index f6df4f46a..bd9e3289a 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/FlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableFlowFunctions.kt @@ -32,30 +32,30 @@ import org.jacodb.api.ext.cfg.callExpr class UnusedVariableFlowFunctions( private val graph: JcApplicationGraph, -) : FlowFunctions { +) : FlowFunctions { private val cp: JcClasspath get() = graph.classpath override fun obtainPossibleStartFacts( method: JcMethod, - ): Collection { - return setOf(Zero) + ): Collection { + return setOf(UnusedVariableZeroFact) } override fun obtainSequentFlowFunction( current: JcInst, next: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> if (current !is JcAssignInst) { return@FlowFunction setOf(fact) } - if (fact == Zero) { + if (fact == UnusedVariableZeroFact) { val toPath = current.lhv.toPath() if (!toPath.isOnHeap) { - return@FlowFunction setOf(Zero, UnusedVariable(toPath, current)) + return@FlowFunction setOf(UnusedVariableZeroFact, UnusedVariable(toPath, current)) } else { - return@FlowFunction setOf(Zero) + return@FlowFunction setOf(UnusedVariableZeroFact) } } check(fact is UnusedVariable) @@ -84,16 +84,16 @@ class UnusedVariableFlowFunctions( override fun obtainCallToStartFlowFunction( callStatement: JcInst, calleeStart: JcInst, - ) = FlowFunction { fact -> + ) = FlowFunction { fact -> val callExpr = callStatement.callExpr ?: error("Call statement should have non-null callExpr") - if (fact == Zero) { + if (fact == UnusedVariableZeroFact) { if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { - return@FlowFunction setOf(Zero) + return@FlowFunction setOf(UnusedVariableZeroFact) } return@FlowFunction buildSet { - add(Zero) + add(UnusedVariableZeroFact) val callee = calleeStart.location.method val formalParams = cp.getArgumentsOf(callee) for (formal in formalParams) { @@ -110,9 +110,9 @@ class UnusedVariableFlowFunctions( callStatement: JcInst, returnSite: JcInst, exitStatement: JcInst, - ) = FlowFunction { fact -> - if (fact == Zero) { - setOf(Zero) + ) = FlowFunction { fact -> + if (fact == UnusedVariableZeroFact) { + setOf(UnusedVariableZeroFact) } else { emptySet() } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt similarity index 82% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt index 1e618832d..fb4cf59c9 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Manager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import org.jacodb.analysis.ifds.Aggregate import org.jacodb.analysis.ifds.ControlEvent import org.jacodb.analysis.ifds.Edge import org.jacodb.analysis.ifds.Manager @@ -40,9 +39,11 @@ import org.jacodb.analysis.ifds.UniRunner import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType import org.jacodb.analysis.ifds.UnknownUnit +import org.jacodb.analysis.ifds.Vertex import org.jacodb.analysis.util.getGetPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcInst import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -55,20 +56,20 @@ private val logger = mu.KotlinLogging.logger {} class UnusedVariableManager( private val graph: JcApplicationGraph, private val unitResolver: UnitResolver, -) : Manager { +) : Manager { private val methodsForUnit: MutableMap> = hashMapOf() - private val runnerForUnit: MutableMap> = hashMapOf() + private val runnerForUnit: MutableMap> = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() - private val summaryEdgesStorage = SummaryStorageImpl() - private val vulnerabilitiesStorage = SummaryStorageImpl() + private val summaryEdgesStorage = SummaryStorageImpl() + private val vulnerabilitiesStorage = SummaryStorageImpl() private val stopRendezvous = Channel(Channel.RENDEZVOUS) private fun newRunner( unit: UnitType, - ): Runner { + ): Runner { check(unit !in runnerForUnit) { "Runner for $unit already exists" } logger.debug { "Creating a new runner for $unit" } @@ -78,7 +79,8 @@ class UnusedVariableManager( analyzer = analyzer, manager = this@UnusedVariableManager, unitResolver = unitResolver, - unit = unit + unit = unit, + zeroFact = UnusedVariableZeroFact ) runnerForUnit[unit] = runner @@ -109,7 +111,7 @@ class UnusedVariableManager( fun analyze( startMethods: List, timeout: Duration = 3600.seconds, - ): List = runBlocking { + ): List = runBlocking { val timeStart = TimeSource.Monotonic.markNow() // Add start methods: @@ -177,10 +179,31 @@ class UnusedVariableManager( } // Extract found vulnerabilities (sinks): - val foundVulnerabilities = vulnerabilitiesStorage.knownMethods - .flatMap { method -> - vulnerabilitiesStorage.getCurrentFacts(method) + val foundVulnerabilities = allUnits.flatMap { unit -> + val runner = runnerForUnit[unit] ?: error("No runner for $unit") + val result = runner.getIfdsResult() + val allFacts = result.facts + + val used = hashMapOf() + for ((inst, facts) in allFacts) { + for (fact in facts) { + if (fact is UnusedVariable) { + used.putIfAbsent(fact.initStatement, false) + if (fact.variable.isUsedAt(inst)) { + used[fact.initStatement] = true + } + } + + } + } + used.filterValues { !it }.keys.map { + UnusedVariableVulnerability( + message = "Assigned value is unused", + sink = Vertex(it, UnusedVariableZeroFact) + ) } + } + if (logger.isDebugEnabled) { logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } for (vulnerability in foundVulnerabilities) { @@ -204,7 +227,7 @@ class UnusedVariableManager( override fun handleEvent(event: Event) { when (event) { is NewSummaryEdge -> { - summaryEdgesStorage.add(SummaryEdge(event.edge)) + summaryEdgesStorage.add(UnusedVariableSummaryEdge(event.edge)) } is NewVulnerability -> { @@ -230,24 +253,20 @@ class UnusedVariableManager( override fun subscribeOnSummaryEdges( method: JcMethod, scope: CoroutineScope, - handler: (Edge) -> Unit, + handler: (Edge) -> Unit, ) { summaryEdgesStorage .getFacts(method) .onEach { handler(it.edge) } .launchIn(scope) } - - fun getAggregates(): Map> { - return runnerForUnit.mapValues { it.value.getAggregate() } - } } fun runUnusedVariableAnalysis( graph: JcApplicationGraph, unitResolver: UnitResolver, startMethods: List, -): List { +): List { val manager = UnusedVariableManager(graph, unitResolver) return manager.analyze(startMethods) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableSummaries.kt similarity index 65% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableSummaries.kt index fbc6406b2..91d203504 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Summary.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableSummaries.kt @@ -17,22 +17,15 @@ package org.jacodb.analysis.unused import org.jacodb.analysis.ifds.Edge -import org.jacodb.analysis.ifds.Summary +import org.jacodb.analysis.ifds.SummaryEdge import org.jacodb.analysis.ifds.Vertex -import org.jacodb.api.JcMethod +import org.jacodb.analysis.ifds.Vulnerability -data class SummaryEdge( - val edge: Edge, -) : Summary { - override val method: JcMethod - get() = edge.method -} +data class UnusedVariableSummaryEdge( + override val edge: Edge, +) : SummaryEdge -data class Vulnerability( - val message: String, - val sink: Vertex, - val edge: Edge? = null, -) : Summary { - override val method: JcMethod - get() = sink.method -} +data class UnusedVariableVulnerability( + override val message: String, + override val sink: Vertex, +) : Vulnerability diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt new file mode 100644 index 000000000..7a1657ffe --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.toPathOrNull +import org.jacodb.api.cfg.JcArrayAccess +import org.jacodb.api.cfg.JcAssignInst +import org.jacodb.api.cfg.JcBranchingInst +import org.jacodb.api.cfg.JcExpr +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.cfg.JcLocal +import org.jacodb.api.cfg.JcSpecialCallExpr +import org.jacodb.api.cfg.JcTerminatingInst +import org.jacodb.api.cfg.values +import org.jacodb.api.ext.cfg.callExpr + +internal fun AccessPath.isUsedAt(expr: JcExpr): Boolean { + return this in expr.values.map { it.toPathOrNull() } +} + +internal fun AccessPath.isUsedAt(inst: JcInst): Boolean { + val callExpr = inst.callExpr + + if (callExpr != null) { + // Don't count constructor calls as usages + if (callExpr.method.method.isConstructor && isUsedAt((callExpr as JcSpecialCallExpr).instance)) { + return false + } + + return isUsedAt(callExpr) + } + if (inst is JcAssignInst) { + if (inst.lhv is JcArrayAccess && isUsedAt((inst.lhv as JcArrayAccess))) { + return true + } + return isUsedAt(inst.rhv) && (inst.lhv !is JcLocal || inst.rhv !is JcLocal) + } + if (inst is JcTerminatingInst || inst is JcBranchingInst) { + return inst.operands.any { isUsedAt(it) } + } + return false +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index 1b8471f57..e85491853 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -19,7 +19,7 @@ package org.jacodb.analysis.impl import juliet.support.AbstractTestCase import kotlinx.coroutines.runBlocking import org.jacodb.analysis.graph.newApplicationGraphForAnalysis -import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.analysis.ifds.Vulnerability import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph @@ -104,7 +104,7 @@ abstract class BaseAnalysisTest : BaseTest() { } } - protected fun testSingleJulietClass(className: String, findSinks: (JcMethod) -> List) { + protected fun testSingleJulietClass(className: String, findSinks: (JcMethod) -> List>) { logger.info { className } val clazz = cp.findClass(className) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt index d27f551fb..5b79ca833 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt @@ -19,8 +19,9 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.npe.NpeManager import org.jacodb.analysis.taint.TaintManager -import org.jacodb.analysis.taint.Vulnerability +import org.jacodb.analysis.taint.TaintVulnerability import org.jacodb.api.JcMethod import org.jacodb.api.ext.constructors import org.jacodb.api.ext.findClass @@ -195,9 +196,9 @@ class IfdsNpeTest : BaseAnalysisTest() { testOneMethod("nullAssignmentToCopy", emptyList()) } - private fun findSinks(method: JcMethod): List { + private fun findSinks(method: JcMethod): List { val unitResolver = SingletonUnitResolver - val manager = TaintManager(graph, unitResolver) + val manager = NpeManager(graph, unitResolver) return manager.analyze(listOf(method), timeout = 30.seconds) } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt new file mode 100644 index 000000000..90c93bfc0 --- /dev/null +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.impl + +import org.jacodb.analysis.ifds.ClassUnitResolver +import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.taint.TaintManager +import org.jacodb.analysis.unused.UnusedVariableManager +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.methods +import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages +import org.jacodb.testing.WithDB +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import kotlin.time.Duration.Companion.seconds + +private val logger = mu.KotlinLogging.logger {} + +class IfdsUnusedTest : BaseAnalysisTest() { + + companion object : WithDB(Usages, InMemoryHierarchy) { + @JvmStatic + fun provideClassesForJuliet563(): Stream = provideClassesForJuliet( + 563, listOf( + // Unused variables are already optimized out by cfg + "unused_uninit_variable_", + "unused_init_variable_int", + "unused_init_variable_long", + "unused_init_variable_String_", + + // Unused variable is generated by cfg (!!) + "unused_value_StringBuilder_17", + + // Expected answers are strange, seems to be problem in tests + "_12", + + // The variable isn't expected to be detected as unused actually + "_81" + ) + ) + } + + @ParameterizedTest + @MethodSource("provideClassesForJuliet563") + fun `test on Juliet's CWE 563`(className: String) { + testSingleJulietClass(className) { method -> + val unitResolver = SingletonUnitResolver + val manager = UnusedVariableManager(graph, unitResolver) + manager.analyze(listOf(method), timeout = 30.seconds) + } + } + + @Test + fun `test on specific Juliet instance`() { + val className = "juliet.testcases.CWE563_Unused_Variable.CWE563_Unused_Variable__unused_init_variable_StringBuilder_01" + val clazz = cp.findClass(className) + val badMethod = clazz.methods.single { it.name == "bad" } + val unitResolver = SingletonUnitResolver + val manager = UnusedVariableManager(graph, unitResolver) + val sinks = manager.analyze(listOf(badMethod), timeout = 30.seconds) + Assertions.assertTrue(sinks.isNotEmpty()) + } +} diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt index f89c13efa..16f9e061b 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt @@ -20,10 +20,8 @@ import kotlinx.coroutines.runBlocking import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds.SingletonUnitResolver import org.jacodb.analysis.taint.TaintManager -import org.jacodb.analysis.taint.Vulnerability import org.jacodb.analysis.unused.UnusedVariableManager import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.ext.findClass import org.jacodb.taint.configuration.TaintConfigurationFeature diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt index 7c5146de3..e37c753b4 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/TaintFlowFunctionsTest.kt @@ -23,9 +23,9 @@ import org.jacodb.analysis.ifds.FlowFunctions import org.jacodb.analysis.util.getArgument import org.jacodb.analysis.ifds.toPath import org.jacodb.analysis.taint.ForwardTaintFlowFunctions -import org.jacodb.analysis.taint.TaintFact +import org.jacodb.analysis.taint.TaintDomainFact import org.jacodb.analysis.taint.Tainted -import org.jacodb.analysis.taint.Zero +import org.jacodb.analysis.taint.TaintZeroFact import org.jacodb.api.JcClassType import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod @@ -98,11 +98,11 @@ class TaintFlowFunctionsTest : BaseTest() { @Test fun `test obtain start facts`() { - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val facts = flowSpace.obtainPossibleStartFacts(testMethod).toList() val arg0 = cp.getArgument(testMethod.parameters[0])!! val arg0Taint = Tainted(arg0.toPath(), TaintMark("EXAMPLE")) - Assertions.assertEquals(listOf(Zero, arg0Taint), facts) + Assertions.assertEquals(listOf(TaintZeroFact, arg0Taint), facts) } @Test @@ -111,7 +111,7 @@ class TaintFlowFunctionsTest : BaseTest() { val x: JcLocal = JcLocalVar(1, "x", stringType) val y: JcLocal = JcLocalVar(2, "y", stringType) val inst = JcAssignInst(location = mockk(), lhv = x, rhv = y) - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val f = flowSpace.obtainSequentFlowFunction(inst, next = mockk()) val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) @@ -128,11 +128,11 @@ class TaintFlowFunctionsTest : BaseTest() { every { method } returns testMethod } }) - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("EXAMPLE")) - val facts = f.compute(Zero).toList() - Assertions.assertEquals(listOf(Zero, xTaint), facts) + val facts = f.compute(TaintZeroFact).toList() + Assertions.assertEquals(listOf(TaintZeroFact, xTaint), facts) } @Test @@ -145,7 +145,7 @@ class TaintFlowFunctionsTest : BaseTest() { } every { args } returns listOf(x) }) - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("REMOVE")) val facts = f.compute(xTaint).toList() @@ -163,7 +163,7 @@ class TaintFlowFunctionsTest : BaseTest() { } every { args } returns listOf(x) }) - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("COPY")) val yTaint = Tainted(y.toPath(), TaintMark("COPY")) @@ -185,7 +185,7 @@ class TaintFlowFunctionsTest : BaseTest() { } every { args } returns listOf(x) }) - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk { every { location } returns mockk { every { method } returns testMethod @@ -215,7 +215,7 @@ class TaintFlowFunctionsTest : BaseTest() { val exitStatement = JcReturnInst(location = mockk { every { method } returns testMethod }, returnValue = y) - val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) + val flowSpace: FlowFunctions = ForwardTaintFlowFunctions(cp, graph) val f = flowSpace.obtainExitToReturnSiteFlowFunction(callStatement, returnSite = mockk(), exitStatement) val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) From 9a6904681d0af53bb1d2802da77d4e4d9c303cfd Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 19:11:25 +0300 Subject: [PATCH 089/117] Fix SARIF, rename some files and classes --- .../analysis/npe/{utils.kt => Utils.kt} | 0 .../jacodb/analysis/sarif/Vulnerability.kt | 4 +-- .../kotlin/org/jacodb/analysis/taint/Sarif.kt | 33 +++++++++++++++++++ .../{BidiRunner.kt => TaintBidiRunner.kt} | 2 +- .../org/jacodb/analysis/taint/TaintManager.kt | 2 +- .../analysis/taint/{types.kt => Types.kt} | 0 .../kotlin/org/jacodb/analysis/util/Utils.kt | 4 +-- .../org/jacodb/analysis/impl/IfdsSqlTest.kt | 11 +++++++ .../analysis/impl/JodaDateTimeAnalysisTest.kt | 13 +++++++- 9 files changed, 62 insertions(+), 7 deletions(-) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/{utils.kt => Utils.kt} (100%) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/Sarif.kt rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/{BidiRunner.kt => TaintBidiRunner.kt} (99%) rename jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/{types.kt => Types.kt} (100%) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/Utils.kt similarity index 100% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/utils.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/Utils.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt index dac3f04f7..6d83a5ac2 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/sarif/Vulnerability.kt @@ -25,7 +25,7 @@ data class VulnerabilityInstance( ) data class VulnerabilityDescription( - val message: String, - val ruleId: String, + val ruleId: String?, + val message: String?, val level: Level = Level.Warning, ) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/Sarif.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/Sarif.kt new file mode 100644 index 000000000..7d2029b36 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/Sarif.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.taint + +import org.jacodb.analysis.ifds.TraceGraph +import org.jacodb.analysis.sarif.VulnerabilityDescription +import org.jacodb.analysis.sarif.VulnerabilityInstance + +fun TaintVulnerability.toSarif( + graph: TraceGraph, +): VulnerabilityInstance { + return VulnerabilityInstance( + graph, + VulnerabilityDescription( + ruleId = null, + message = rule?.ruleNote + ) + ) +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintBidiRunner.kt similarity index 99% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintBidiRunner.kt index 6f3bea852..dbc516663 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/BidiRunner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintBidiRunner.kt @@ -30,7 +30,7 @@ import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType import org.jacodb.api.JcMethod -class BidiRunner( +class TaintBidiRunner( val manager: TaintManager, val unitResolver: UnitResolver, override val unit: UnitType, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index c96ad6fb2..1060690aa 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -75,7 +75,7 @@ class TaintManager( logger.debug { "Creating a new runner for $unit" } val runner = if (useBidiRunner) { - BidiRunner( + TaintBidiRunner( manager = this@TaintManager, unitResolver = unitResolver, unit = unit, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/Types.kt similarity index 100% rename from jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/types.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/Types.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt index 6805e8afe..8607fc08c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt @@ -20,7 +20,7 @@ import org.jacodb.analysis.ifds.AccessPath import org.jacodb.analysis.ifds.Edge import org.jacodb.analysis.ifds.Runner import org.jacodb.analysis.ifds.UniRunner -import org.jacodb.analysis.taint.BidiRunner +import org.jacodb.analysis.taint.TaintBidiRunner import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.JcParameter @@ -42,7 +42,7 @@ fun JcClasspath.getArgumentsOf(method: JcMethod): List { fun Runner<*>.getGetPathEdges(): Set> = when (this) { is UniRunner<*, *> -> pathEdges - is BidiRunner -> forwardRunner.getGetPathEdges() + backwardRunner.getGetPathEdges() + is TaintBidiRunner -> forwardRunner.getGetPathEdges() + backwardRunner.getGetPathEdges() else -> error("Cannot extract pathEdges for $this") } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt index c0c34727c..aa88a286f 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsSqlTest.kt @@ -16,9 +16,13 @@ package org.jacodb.analysis.impl +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.jacodb.analysis.ifds.ClassUnitResolver import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.sarif.sarifReportFromVulnerabilities import org.jacodb.analysis.taint.TaintManager +import org.jacodb.analysis.taint.toSarif import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods import org.jacodb.impl.features.InMemoryHierarchy @@ -47,6 +51,10 @@ class IfdsSqlTest : BaseAnalysisTest() { ) } + private val myJson = Json { + prettyPrint = true + } + @Test fun `simple SQL injection`() { val methodName = "bad" @@ -95,5 +103,8 @@ class IfdsSqlTest : BaseAnalysisTest() { val graph = manager.vulnerabilityTraceGraph(sink) val trace = graph.getAllTraces().first() assertTrue(trace.isNotEmpty()) + val sarif = sarifReportFromVulnerabilities(listOf(sink.toSarif(graph))) + val sarifJson = myJson.encodeToString(sarif) + logger.info { "SARIF:\n$sarifJson" } } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt index 16f9e061b..8b3cb41c2 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/JodaDateTimeAnalysisTest.kt @@ -19,6 +19,7 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.npe.NpeManager import org.jacodb.analysis.taint.TaintManager import org.jacodb.analysis.unused.UnusedVariableManager import org.jacodb.api.JcClasspath @@ -37,7 +38,7 @@ private val logger = mu.KotlinLogging.logger {} class JodaDateTimeAnalysisTest : BaseTest() { companion object : WithGlobalDB() - + override val cp: JcClasspath = runBlocking { val configFileName = "config_small.json" val configResource = this.javaClass.getResourceAsStream("/$configFileName") @@ -66,6 +67,16 @@ class JodaDateTimeAnalysisTest : BaseTest() { logger.info { "Vulnerabilities found: ${sinks.size}" } } + @Test + fun `test NPE analysis`() { + val clazz = cp.findClass() + val methods = clazz.declaredMethods + val unitResolver = SingletonUnitResolver + val manager = NpeManager(graph, unitResolver) + val sinks = manager.analyze(methods, timeout = 60.seconds) + logger.info { "Vulnerabilities found: ${sinks.size}" } + } + @Test fun `test unused variables analysis`() { val clazz = cp.findClass() From 8f5a87129ea37099425e0d80bbb61795f82b2784 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 19:43:13 +0300 Subject: [PATCH 090/117] Restore CLI --- jacodb-analysis/build.gradle.kts | 2 +- .../org/jacodb/analysis/npe/NpeManager.kt | 212 +----------------- .../org/jacodb/analysis/taint/TaintManager.kt | 10 +- .../org/jacodb/analysis/unused/Sarif.kt | 28 +++ jacodb-cli/build.gradle.kts | 15 ++ .../src/main/kotlin/org/jacodb/cli/main.kt | 182 +++++++++++++++ .../src/test/kotlin/org/jacodb/cli/CliTest.kt | 31 +++ jacodb-cli/src/test/resources/config.json | 9 + settings.gradle.kts | 1 + 9 files changed, 277 insertions(+), 213 deletions(-) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Sarif.kt create mode 100644 jacodb-cli/build.gradle.kts create mode 100644 jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt create mode 100644 jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt create mode 100644 jacodb-cli/src/test/resources/config.json diff --git a/jacodb-analysis/build.gradle.kts b/jacodb-analysis/build.gradle.kts index 23e3bc484..b469f17db 100644 --- a/jacodb-analysis/build.gradle.kts +++ b/jacodb-analysis/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { implementation(Libs.slf4j_simple) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.kotlinx_serialization_json) - implementation(Libs.sarif4k) + api(Libs.sarif4k) testImplementation(testFixtures(project(":jacodb-core"))) testImplementation(project(":jacodb-api")) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index 5eaba889f..bed799cbf 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -16,63 +16,20 @@ package org.jacodb.analysis.npe -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.isActive -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull -import org.jacodb.analysis.ifds.ControlEvent -import org.jacodb.analysis.ifds.Manager -import org.jacodb.analysis.ifds.QueueEmptinessChanged -import org.jacodb.analysis.ifds.SummaryStorageImpl import org.jacodb.analysis.ifds.UniRunner import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType -import org.jacodb.analysis.taint.EdgeForOtherRunner -import org.jacodb.analysis.taint.NewSummaryEdge -import org.jacodb.analysis.taint.NewVulnerability -import org.jacodb.analysis.taint.TaintSummaryEdge -import org.jacodb.analysis.taint.TaintEdge -import org.jacodb.analysis.taint.TaintEvent -import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.analysis.taint.TaintManager import org.jacodb.analysis.taint.TaintRunner -import org.jacodb.analysis.taint.TaintVulnerability import org.jacodb.analysis.taint.TaintZeroFact -import org.jacodb.analysis.util.getGetPathEdges -import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph -import java.util.concurrent.ConcurrentHashMap -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import kotlin.time.DurationUnit -import kotlin.time.ExperimentalTime -import kotlin.time.TimeSource - -private val logger = mu.KotlinLogging.logger {} class NpeManager( - private val graph: JcApplicationGraph, - private val unitResolver: UnitResolver, -) : Manager { - - private val methodsForUnit: MutableMap> = hashMapOf() - private val runnerForUnit: MutableMap = hashMapOf() - private val queueIsEmpty = ConcurrentHashMap() - - private val summaryEdgesStorage = SummaryStorageImpl() - private val vulnerabilitiesStorage = SummaryStorageImpl() - - private val stopRendezvous = Channel(Channel.RENDEZVOUS) + graph: JcApplicationGraph, + unitResolver: UnitResolver, +) : TaintManager(graph, unitResolver, useBidiRunner = false) { - private fun newRunner( + override fun newRunner( unit: UnitType, ): TaintRunner { check(unit !in runnerForUnit) { "Runner for $unit already exists" } @@ -90,163 +47,4 @@ class NpeManager( runnerForUnit[unit] = runner return runner } - - private fun addStart(method: JcMethod) { - logger.info { "Adding start method: $method" } - val unit = unitResolver.resolve(method) - methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) - // TODO: val isNew = (...).add(); if (isNew) { deps.forEach { addStart(it) } } - } - - @OptIn(ExperimentalTime::class) - fun analyze( - startMethods: List, - timeout: Duration = 3600.seconds, - ): List = runBlocking(Dispatchers.Default) { - val timeStart = TimeSource.Monotonic.markNow() - - // Add start methods: - for (method in startMethods) { - addStart(method) - } - - // Determine all units: - val allUnits = methodsForUnit.keys.toList() - logger.info { - "Starting analysis of ${ - methodsForUnit.values.sumOf { it.size } - } methods in ${allUnits.size} units" - } - - // Spawn runner jobs: - val allJobs = allUnits.map { unit -> - // Create the runner: - val runner = newRunner(unit) - - // Start the runner: - launch(start = CoroutineStart.LAZY) { - val methods = methodsForUnit[unit]!!.toList() - runner.run(methods) - } - } - - // Spawn progress job: - val progress = launch(Dispatchers.IO) { - while (isActive) { - delay(1.seconds) - logger.info { - "Progress: propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } - } path edges" - } - } - } - - // Spawn stopper job: - val stopper = launch(Dispatchers.IO) { - stopRendezvous.receive() - logger.info { "Stopping all runners..." } - allJobs.forEach { it.cancel() } - } - - // Start all runner jobs: - val timeStartJobs = TimeSource.Monotonic.markNow() - allJobs.forEach { it.start() } - - // Await all runners: - withTimeoutOrNull(timeout) { - allJobs.joinAll() - } ?: run { - logger.info { "Timeout!" } - allJobs.forEach { it.cancel() } - allJobs.joinAll() - } - progress.cancelAndJoin() - stopper.cancelAndJoin() - logger.info { - "All ${allJobs.size} jobs completed in %.1f s".format( - timeStartJobs.elapsedNow().toDouble(DurationUnit.SECONDS) - ) - } - - // Extract found vulnerabilities (sinks): - val foundVulnerabilities = vulnerabilitiesStorage.knownMethods - .flatMap { method -> - vulnerabilitiesStorage.getCurrentFacts(method) - } - if (logger.isDebugEnabled) { - logger.debug { "Total found ${foundVulnerabilities.size} vulnerabilities" } - for (vulnerability in foundVulnerabilities) { - logger.debug { "$vulnerability in ${vulnerability.method}" } - } - } - logger.info { "Total sinks: ${foundVulnerabilities.size}" } - logger.info { - "Total propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } - } path edges" - } - logger.info { - "Analysis done in %.1f s".format( - timeStart.elapsedNow().toDouble(DurationUnit.SECONDS) - ) - } - foundVulnerabilities - } - - override fun handleEvent(event: TaintEvent) { - when (event) { - is NewSummaryEdge -> { - summaryEdgesStorage.add(TaintSummaryEdge(event.edge)) - } - - is NewVulnerability -> { - vulnerabilitiesStorage.add(event.vulnerability) - } - - is EdgeForOtherRunner -> { - val method = event.edge.method - val unit = unitResolver.resolve(method) - val otherRunner = runnerForUnit[unit] ?: run { - logger.trace { "Ignoring event=$event for non-existing runner for unit=$unit" } - return - } - otherRunner.submitNewEdge(event.edge, event.reason) - } - } - } - - override fun handleControlEvent(event: ControlEvent) { - when (event) { - is QueueEmptinessChanged -> { - queueIsEmpty[event.runner.unit] = event.isEmpty - if (event.isEmpty) { - if (runnerForUnit.keys.all { queueIsEmpty[it] == true }) { - logger.debug { "All runners are empty" } - stopRendezvous.trySend(Unit).getOrNull() - } - } - } - } - } - - override fun subscribeOnSummaryEdges( - method: JcMethod, - scope: CoroutineScope, - handler: (TaintEdge) -> Unit, - ) { - summaryEdgesStorage - .getFacts(method) - .onEach { handler(it.edge) } - .launchIn(scope) - } -} - -fun runNpeAnalysis( - graph: JcApplicationGraph, - unitResolver: UnitResolver, - startMethods: List, -): List { - val manager = NpeManager(graph, unitResolver) - return manager.analyze(startMethods) } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index 1060690aa..eb82366fe 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -53,14 +53,14 @@ import kotlin.time.TimeSource private val logger = mu.KotlinLogging.logger {} -class TaintManager( - private val graph: JcApplicationGraph, - private val unitResolver: UnitResolver, +open class TaintManager( + protected val graph: JcApplicationGraph, + protected val unitResolver: UnitResolver, private val useBidiRunner: Boolean = false, ) : Manager { private val methodsForUnit: MutableMap> = hashMapOf() - private val runnerForUnit: MutableMap = hashMapOf() + protected val runnerForUnit: MutableMap = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() private val summaryEdgesStorage = SummaryStorageImpl() @@ -68,7 +68,7 @@ class TaintManager( private val stopRendezvous = Channel(Channel.RENDEZVOUS) - private fun newRunner( + protected open fun newRunner( unit: UnitType, ): TaintRunner { check(unit !in runnerForUnit) { "Runner for $unit already exists" } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Sarif.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Sarif.kt new file mode 100644 index 000000000..85f1136f2 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Sarif.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.analysis.unused + +import org.jacodb.analysis.ifds.TraceGraph +import org.jacodb.analysis.sarif.VulnerabilityDescription +import org.jacodb.analysis.sarif.VulnerabilityInstance + +fun UnusedVariableVulnerability.toSarif(): VulnerabilityInstance { + return VulnerabilityInstance( + TraceGraph(sink, mutableSetOf(sink), mutableMapOf(), emptyMap()), + VulnerabilityDescription(ruleId = null, message = message) + ) +} diff --git a/jacodb-cli/build.gradle.kts b/jacodb-cli/build.gradle.kts new file mode 100644 index 000000000..56a8dfec6 --- /dev/null +++ b/jacodb-cli/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("plugin.serialization") +} + +dependencies { + api(project(":jacodb-core")) + api(project(":jacodb-analysis")) + api(project(":jacodb-api")) + + implementation(Libs.kotlin_logging) + implementation(Libs.kotlinx_cli) + implementation(Libs.kotlinx_serialization_json) + + testImplementation(testFixtures(project(":jacodb-core"))) +} diff --git a/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt new file mode 100644 index 000000000..2bbee2bbc --- /dev/null +++ b/jacodb-cli/src/main/kotlin/org/jacodb/cli/main.kt @@ -0,0 +1,182 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.cli + +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.default +import kotlinx.cli.required +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToStream +import org.jacodb.analysis.graph.newApplicationGraphForAnalysis +import org.jacodb.analysis.ifds.SingletonUnitResolver +import org.jacodb.analysis.ifds.UnitResolver +import org.jacodb.analysis.npe.NpeManager +import org.jacodb.analysis.sarif.VulnerabilityInstance +import org.jacodb.analysis.sarif.sarifReportFromVulnerabilities +import org.jacodb.analysis.taint.TaintManager +import org.jacodb.analysis.taint.toSarif +import org.jacodb.analysis.unused.UnusedVariableManager +import org.jacodb.analysis.unused.toSarif +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClassProcessingTask +import org.jacodb.api.JcMethod +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages +import org.jacodb.impl.jacodb +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration.Companion.seconds + +private val logger = mu.KotlinLogging.logger {} + +class AnalysisMain { + fun run(args: List) = main(args.toTypedArray()) +} + +typealias AnalysesOptions = Map + +@Serializable +data class AnalysisConfig(val analyses: Map) + +fun launchAnalysesByConfig( + config: AnalysisConfig, + graph: JcApplicationGraph, + methods: List, +): List>> { + return config.analyses.mapNotNull { (analysis, options) -> + val unitResolver = options["UnitResolver"]?.let { + UnitResolver.getByName(it) + } ?: SingletonUnitResolver + + when (analysis) { + "NPE" -> { + val manager = NpeManager(graph, unitResolver) + manager.analyze(methods, timeout = 60.seconds).map { it.toSarif(manager.vulnerabilityTraceGraph(it)) } + } + + "Unused" -> { + val manager = UnusedVariableManager(graph, unitResolver) + manager.analyze(methods, timeout = 60.seconds).map { it.toSarif() } + } + + "SQL" -> { + val manager = TaintManager(graph, unitResolver) + manager.analyze(methods, timeout = 60.seconds).map { it.toSarif(manager.vulnerabilityTraceGraph(it)) } + } + + else -> { + logger.error { "Unknown analysis type: $analysis" } + return@mapNotNull null + } + } + } +} + +@OptIn(ExperimentalSerializationApi::class) +fun main(args: Array) { + val parser = ArgParser("taint-analysis") + val configFilePath by parser.option( + ArgType.String, + fullName = "analysisConf", + shortName = "a", + description = "File with analysis configuration in JSON format" + ).required() + val dbLocation by parser.option( + ArgType.String, + fullName = "dbLocation", + shortName = "l", + description = "Location of SQLite database for storing bytecode data" + ) + val startClasses by parser.option( + ArgType.String, + fullName = "start", + shortName = "s", + description = "classes from which to start the analysis" + ).required() + val outputPath by parser.option( + ArgType.String, + fullName = "output", + shortName = "o", + description = "File where analysis report (in SARIF format) will be written. File will be created if not exists. Existing file will be overwritten." + ).default("report.sarif") + val classpath by parser.option( + ArgType.String, + fullName = "classpath", + shortName = "cp", + description = "Classpath for analysis. Used by JacoDB." + ).default(System.getProperty("java.class.path")) + + parser.parse(args) + + val outputFile = File(outputPath) + + if (outputFile.exists() && outputFile.isDirectory) { + throw IllegalArgumentException("Provided path for output file is directory, please provide correct path") + } else if (outputFile.exists()) { + logger.info { "Output file $outputFile already exists, results will be overwritten" } + } + + val configFile = File(configFilePath) + if (!configFile.isFile) { + throw IllegalArgumentException("Can't find provided config file $configFilePath") + } + val config = Json.decodeFromString(configFile.readText()) + + val classpathAsFiles = classpath.split(File.pathSeparatorChar).sorted().map { File(it) } + + val cp = runBlocking { + val jacodb = jacodb { + loadByteCode(classpathAsFiles) + dbLocation?.let { + persistent(it) + } + installFeatures(InMemoryHierarchy, Usages) + } + jacodb.classpath(classpathAsFiles) + } + + val startClassesAsList = startClasses.split(";") + val startJcClasses = ConcurrentHashMap.newKeySet() + cp.executeAsync(object : JcClassProcessingTask { + override fun process(clazz: JcClassOrInterface) { + if (startClassesAsList.any { clazz.name.startsWith(it) }) { + startJcClasses.add(clazz) + } + } + }).get() + val startJcMethods = startJcClasses.flatMap { it.declaredMethods }.filter { !it.isPrivate } + + val graph = runBlocking { + cp.newApplicationGraphForAnalysis() + } + + val vulnerabilities = launchAnalysesByConfig(config, graph, startJcMethods).flatten() + val report = sarifReportFromVulnerabilities(vulnerabilities) + val prettyJson = Json { + prettyPrint = true + } + + outputFile.outputStream().use { fileOutputStream -> + prettyJson.encodeToStream(report, fileOutputStream) + } +} diff --git a/jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt b/jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt new file mode 100644 index 000000000..3e9976978 --- /dev/null +++ b/jacodb-cli/src/test/kotlin/org/jacodb/cli/CliTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.cli + +import org.jacodb.testing.analysis.NpeExamples +import org.junit.jupiter.api.Test + +class CliTest { + @Test + fun `test basic analysis cli api`() { + val args = listOf( + "-a", CliTest::class.java.getResource("/config.json")?.file ?: error("Can't find file with config"), + "-s", NpeExamples::class.java.name + ) + AnalysisMain().run(args) + } +} \ No newline at end of file diff --git a/jacodb-cli/src/test/resources/config.json b/jacodb-cli/src/test/resources/config.json new file mode 100644 index 000000000..21a1020ab --- /dev/null +++ b/jacodb-cli/src/test/resources/config.json @@ -0,0 +1,9 @@ +{ + "analyses": { + "NPE": {}, + "Unused": { + "UnitResolver": "class" + }, + "SQL": {} + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b0081ccf6..d8aec8ff1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,5 +24,6 @@ include("jacodb-core") include("jacodb-analysis") include("jacodb-examples") include("jacodb-benchmarks") +include("jacodb-cli") include("jacodb-approximations") include("jacodb-taint-configuration") From af8161e2559703192f66085fe65b82f7c65e9242 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 19:55:34 +0300 Subject: [PATCH 091/117] Fix internal function name --- .../main/kotlin/org/jacodb/analysis/taint/TaintManager.kt | 6 +++--- .../org/jacodb/analysis/unused/UnusedVariableManager.kt | 6 +++--- .../src/main/kotlin/org/jacodb/analysis/util/Utils.kt | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index eb82366fe..c183ac6c1 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -41,7 +41,7 @@ import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType import org.jacodb.analysis.ifds.UnknownUnit import org.jacodb.analysis.ifds.Vertex -import org.jacodb.analysis.util.getGetPathEdges +import org.jacodb.analysis.util.getPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import java.util.concurrent.ConcurrentHashMap @@ -176,7 +176,7 @@ open class TaintManager( delay(1.seconds) logger.info { "Progress: propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } + runnerForUnit.values.sumOf { it.getPathEdges().size } } path edges" } } @@ -223,7 +223,7 @@ open class TaintManager( logger.info { "Total sinks: ${foundVulnerabilities.size}" } logger.info { "Total propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } + runnerForUnit.values.sumOf { it.getPathEdges().size } } path edges" } logger.info { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt index fb4cf59c9..ecf117941 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt @@ -40,7 +40,7 @@ import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType import org.jacodb.analysis.ifds.UnknownUnit import org.jacodb.analysis.ifds.Vertex -import org.jacodb.analysis.util.getGetPathEdges +import org.jacodb.analysis.util.getPathEdges import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph import org.jacodb.api.cfg.JcInst @@ -145,7 +145,7 @@ class UnusedVariableManager( delay(1.seconds) logger.info { "Progress: propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } + runnerForUnit.values.sumOf { it.getPathEdges().size } } path edges" } } @@ -213,7 +213,7 @@ class UnusedVariableManager( logger.info { "Total sinks: ${foundVulnerabilities.size}" } logger.info { "Total propagated ${ - runnerForUnit.values.sumOf { it.getGetPathEdges().size } + runnerForUnit.values.sumOf { it.getPathEdges().size } } path edges" } logger.info { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt index 8607fc08c..6b6bb924e 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/util/Utils.kt @@ -40,9 +40,9 @@ fun JcClasspath.getArgumentsOf(method: JcMethod): List { return method.parameters.map { getArgument(it)!! } } -fun Runner<*>.getGetPathEdges(): Set> = when (this) { +internal fun Runner<*>.getPathEdges(): Set> = when (this) { is UniRunner<*, *> -> pathEdges - is TaintBidiRunner -> forwardRunner.getGetPathEdges() + backwardRunner.getGetPathEdges() + is TaintBidiRunner -> forwardRunner.getPathEdges() + backwardRunner.getPathEdges() else -> error("Cannot extract pathEdges for $this") } From c2d73519b6995e81f23821b60895a720c4527db9 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 20:03:15 +0300 Subject: [PATCH 092/117] Fix --- .../kotlin/org/jacodb/analysis/npe/NpeManager.kt | 12 ++++++++++++ .../kotlin/org/jacodb/analysis/taint/TaintManager.kt | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt index bed799cbf..1b72e58ef 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeManager.kt @@ -19,11 +19,15 @@ package org.jacodb.analysis.npe import org.jacodb.analysis.ifds.UniRunner import org.jacodb.analysis.ifds.UnitResolver import org.jacodb.analysis.ifds.UnitType +import org.jacodb.analysis.ifds.UnknownUnit import org.jacodb.analysis.taint.TaintManager import org.jacodb.analysis.taint.TaintRunner import org.jacodb.analysis.taint.TaintZeroFact +import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph +private val logger = mu.KotlinLogging.logger {} + class NpeManager( graph: JcApplicationGraph, unitResolver: UnitResolver, @@ -47,4 +51,12 @@ class NpeManager( runnerForUnit[unit] = runner return runner } + + override fun addStart(method: JcMethod) { + logger.info { "Adding start method: $method" } + val unit = unitResolver.resolve(method) + if (unit == UnknownUnit) return + methodsForUnit.getOrPut(unit) { hashSetOf() }.add(method) + // Note: DO NOT add deps here! + } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index c183ac6c1..30af510bf 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -59,7 +59,7 @@ open class TaintManager( private val useBidiRunner: Boolean = false, ) : Manager { - private val methodsForUnit: MutableMap> = hashMapOf() + protected val methodsForUnit: MutableMap> = hashMapOf() protected val runnerForUnit: MutableMap = hashMapOf() private val queueIsEmpty = ConcurrentHashMap() @@ -126,7 +126,7 @@ open class TaintManager( return result } - private fun addStart(method: JcMethod) { + protected open fun addStart(method: JcMethod) { logger.info { "Adding start method: $method" } val unit = unitResolver.resolve(method) if (unit == UnknownUnit) return From ec555665ce414d3c37cb571aacc9496fdcc05f06 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 20:03:24 +0300 Subject: [PATCH 093/117] Remove unnecessary stuff --- .../test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt index 5b79ca833..e362b5545 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt @@ -224,13 +224,6 @@ class IfdsNpeTest : BaseAnalysisTest() { testSingleJulietClass(className, ::findSinks) } - @Test - fun `analyse something`() { - val testingMethod = cp.findClass().declaredMethods.single { it.name == "id" } - val results = testingMethod.flowGraph() - print(results) - } - private inline fun testOneMethod( methodName: String, expectedLocations: Collection, From f893970c0488ee1127428be3739edb0e0000e069 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 20:14:12 +0300 Subject: [PATCH 094/117] Fix NPE tests --- .../org/jacodb/analysis/impl/IfdsNpeTest.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt index e362b5545..cbbf4e952 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsNpeTest.kt @@ -63,7 +63,7 @@ class IfdsNpeTest : BaseAnalysisTest() { @Test fun `analyze simple NPE`() { - testOneMethod("npeOnLength", listOf("%3 = %0.length()")) + testOneMethod("npeOnLength", listOf("%3 = x.length()")) } @Test @@ -75,7 +75,7 @@ class IfdsNpeTest : BaseAnalysisTest() { fun `analyze NPE after fun with two exits`() { testOneMethod( "npeAfterTwoExits", - listOf("%4 = %0.length()", "%5 = %1.length()") + listOf("%4 = x.length()", "%5 = y.length()") ) } @@ -94,7 +94,7 @@ class IfdsNpeTest : BaseAnalysisTest() { fun `consecutive NPEs handled properly`() { testOneMethod( "consecutiveNPEs", - listOf("%2 = arg$0.length()", "%4 = arg$0.length()") + listOf("a = x.length()", "c = x.length()") ) } @@ -102,7 +102,7 @@ class IfdsNpeTest : BaseAnalysisTest() { fun `npe on virtual call when possible`() { testOneMethod( "possibleNPEOnVirtualCall", - listOf("%0 = arg$0.length()") + listOf("%0 = x.length()") ) } @@ -161,7 +161,7 @@ class IfdsNpeTest : BaseAnalysisTest() { @Test fun `NPE on uninitialized array element dereferencing`() { - testOneMethod("simpleArrayNPE", listOf("%5 = %4.length()")) + testOneMethod("simpleArrayNPE", listOf("b = %4.length()")) } @Test @@ -183,7 +183,7 @@ class IfdsNpeTest : BaseAnalysisTest() { @Test fun `dereferencing field of null object`() { - testOneMethod("npeOnFieldDeref", listOf("%1 = %0.field")) + testOneMethod("npeOnFieldDeref", listOf("s = a.field")) } @Test @@ -233,8 +233,8 @@ class IfdsNpeTest : BaseAnalysisTest() { // TODO: think about better assertions here Assertions.assertEquals(expectedLocations.size, sinks.size) - // expectedLocations.forEach { expected -> - // Assertions.assertTrue(sinks.map { it.traceGraph.sink.toString() }.any { it.contains(expected) }) - // } + expectedLocations.forEach { expected -> + Assertions.assertTrue(sinks.map { it.sink.toString() }.any { it.contains(expected) }) + } } } From e0839cc3c8569ffa97cf407043f81be7952ad89c Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 23:32:02 +0300 Subject: [PATCH 095/117] Fix running analysis from Java --- .../java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java index a3ba9d89c..4b5385466 100644 --- a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java +++ b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java @@ -16,9 +16,10 @@ package org.jacodb.analysis.impl; -import org.jacodb.analysis.ifds.UnitResolver; import org.jacodb.analysis.graph.ApplicationGraphFactory; +import org.jacodb.analysis.ifds.UnitResolver; import org.jacodb.analysis.ifds.UnitResolverKt; +import org.jacodb.analysis.taint.TaintManagerKt; import org.jacodb.api.JcClassOrInterface; import org.jacodb.api.JcClasspath; import org.jacodb.api.JcDatabase; @@ -55,8 +56,8 @@ public void testJavaAnalysisApi() throws ExecutionException, InterruptedExceptio JcApplicationGraph applicationGraph = ApplicationGraphFactory .newApplicationGraphForAnalysisAsync(classpath, null) .get(); - UnitResolver resolver = UnitResolverKt.getMethodUnitResolver(); - // TODO: run analysis + UnitResolver unitResolver = UnitResolverKt.getMethodUnitResolver(); + TaintManagerKt.runTaintAnalysis(applicationGraph, unitResolver, methodsToAnalyze); } @Test From b506f74ad20eeab7bea81c87c055bd6be133abc4 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 23:34:16 +0300 Subject: [PATCH 096/117] Remove ElementAccessor's dummy constructor --- .../src/main/kotlin/org/jacodb/analysis/config/Position.kt | 2 +- .../src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt | 2 +- .../src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt | 2 -- .../src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt index f660529d4..da77ba410 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt @@ -47,7 +47,7 @@ class CallPositionToAccessPathResolver( This -> (callExpr as? JcInstanceCallExpr)?.instance?.toPathOrNull().toMaybe() Result -> (callStatement as? JcAssignInst)?.lhv?.toPathOrNull().toMaybe() ResultAnyElement -> (callStatement as? JcAssignInst)?.lhv?.toPathOrNull().toMaybe() - .fmap { it / ElementAccessor(null) } + .fmap { it / ElementAccessor } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt index dfeb3d4e9..9210b0dd7 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/AccessPath.kt @@ -99,7 +99,7 @@ fun JcValue.toPathOrNull(): AccessPath? = when (this) { is JcArrayAccess -> { array.toPathOrNull()?.let { - it / ElementAccessor(index) + it / ElementAccessor } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt index 85f4d45df..91dfa4900 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt @@ -44,5 +44,3 @@ object ElementAccessor : Accessor { return "*" } } - -fun ElementAccessor(index: JcValue?) = ElementAccessor diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index 7d4262bda..4d6c035ef 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -234,7 +234,7 @@ class ForwardNpeFlowFunctions( if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { add(Tainted(toPath, TaintMark.NULLNESS)) } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { - val accessors = List((from.type as JcArrayType).dimensions) { ElementAccessor(null) } + val accessors = List((from.type as JcArrayType).dimensions) { ElementAccessor } val path = toPath / accessors add(Tainted(path, TaintMark.NULLNESS)) } From 8a6a3a0d77555801f3ef3fb54f2a7f2da6044492 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 23:34:40 +0300 Subject: [PATCH 097/117] Use non-nullable T --- .../src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt index 164564730..be4356362 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Maybe.kt @@ -37,7 +37,7 @@ value class Maybe private constructor( fun some(value: T): Maybe = Maybe(value) - fun from(value: T?): Maybe = if (value == null) none() else some(value) + fun from(value: T?): Maybe = if (value == null) none() else some(value) } } @@ -57,4 +57,4 @@ inline fun Maybe.onNone(body: () -> Unit): Maybe { return this } -fun T?.toMaybe(): Maybe = Maybe.from(this) +fun T?.toMaybe(): Maybe = Maybe.from(this) From 8ea75259f87cf8bf80bf427f2ea74742e9526d30 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 23:40:09 +0300 Subject: [PATCH 098/117] Convert ConditionSimplifier to object --- .../configuration/TaintConfigurationFeature.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt index d8e51c36f..5002db799 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt @@ -63,23 +63,23 @@ class TaintConfigurationFeature private constructor( .map { when (it) { is SerializedTaintEntryPointSource -> it.copy( - condition = it.condition.accept(ConditionSimplifier()) + condition = it.condition.accept(conditionSimplifier) ) is SerializedTaintMethodSource -> it.copy( - condition = it.condition.accept(ConditionSimplifier()) + condition = it.condition.accept(conditionSimplifier) ) is SerializedTaintMethodSink -> it.copy( - condition = it.condition.accept(ConditionSimplifier()) + condition = it.condition.accept(conditionSimplifier) ) is SerializedTaintPassThrough -> it.copy( - condition = it.condition.accept(ConditionSimplifier()) + condition = it.condition.accept(conditionSimplifier) ) is SerializedTaintCleaner -> it.copy( - condition = it.condition.accept(ConditionSimplifier()) + condition = it.condition.accept(conditionSimplifier) ) } } @@ -110,7 +110,6 @@ class TaintConfigurationFeature private constructor( return primitiveTypesSet!! } - private fun resolveConfigForMethod(method: JcMethod): List { val taintConfigurationItems = rulesForMethod[method] if (taintConfigurationItems != null) { @@ -242,7 +241,7 @@ class TaintConfigurationFeature private constructor( private fun Condition.resolve(method: JcMethod): Condition = this .accept(ConditionSpecializer(method)) - .accept(ConditionSimplifier()) + .accept(conditionSimplifier) private fun List.resolve(method: JcMethod): List = flatMap { it.accept(ActionSpecializer(method)) } @@ -433,7 +432,7 @@ class TaintConfigurationFeature private constructor( override fun visit(condition: Condition): Condition = condition } - private inner class ConditionSimplifier : ConditionVisitor { + private val conditionSimplifier = object : ConditionVisitor { override fun visit(condition: And): Condition { val queue = ArrayDeque(condition.args) val args = mutableListOf() From fcad9bd31e315fa0145139a64e6cae68e804d44d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 23:42:15 +0300 Subject: [PATCH 099/117] Compact toString --- .../org/jacodb/analysis/ifds/Accessors.kt | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt index 91dfa4900..885582f36 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Accessors.kt @@ -17,7 +17,6 @@ package org.jacodb.analysis.ifds import org.jacodb.api.JcField -import org.jacodb.api.cfg.JcValue sealed interface Accessor { fun toSuffix(): String @@ -26,21 +25,11 @@ sealed interface Accessor { data class FieldAccessor( val field: JcField, ) : Accessor { - override fun toSuffix(): String { - return ".${field.name}" - } - - override fun toString(): String { - return field.name - } + override fun toSuffix(): String = ".${field.name}" + override fun toString(): String = field.name } object ElementAccessor : Accessor { - override fun toSuffix(): String { - return "[*]" - } - - override fun toString(): String { - return "*" - } + override fun toSuffix(): String = "[*]" + override fun toString(): String = "*" } From 0ed6f485d33d1edb492f830c5f87402d58d5dab1 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Mon, 19 Feb 2024 23:56:57 +0300 Subject: [PATCH 100/117] Cleanup --- .../org/jacodb/analysis/config/Condition.kt | 5 - .../analysis/impl/ConditionEvaluatorTest.kt | 6 - .../jacodb/api/cfg/FullExprSetCollector.kt | 149 ++++++------ .../org/jacodb/api/cfg/JcExprVisitor.kt | 136 ----------- .../main/kotlin/org/jacodb/api/cfg/JcInst.kt | 26 +-- .../org/jacodb/api/cfg/JcInstVisitor.kt | 214 ++++++++++++++++-- .../org/jacodb/api/cfg/JcRawExprVisitor.kt | 126 ----------- .../kotlin/org/jacodb/api/cfg/JcRawInst.kt | 4 +- .../org/jacodb/api/cfg/JcRawInstVisitor.kt | 205 +++++++++++++++-- .../main/kotlin/org/jacodb/api/ext/JcTypes.kt | 24 +- .../org/jacodb/api/ext/cfg/JcInstructions.kt | 78 ++++--- .../impl/StringConcatSimplifierTransformer.kt | 33 +-- .../kotlin/org/jacodb/impl/cfg/GraphExt.kt | 28 +-- .../kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt | 12 +- .../org/jacodb/impl/cfg/JcInstListBuilder.kt | 168 +------------- .../org/jacodb/impl/cfg/JcInstListImpl.kt | 34 +-- .../org/jacodb/impl/cfg/MethodNodeBuilder.kt | 140 ++---------- .../org/jacodb/impl/cfg/TypedMethodRefImpl.kt | 119 +++++----- .../jacodb/impl/cfg/util/InstructionFilter.kt | 11 +- .../impl/features/classpaths/JcUnknownType.kt | 8 +- .../test/java/org/jacodb/testing/JavaApi.java | 25 +- .../kotlin/org/jacodb/testing/cfg/IRTest.kt | 102 +++------ .../testing/cfg/LocalResolverExample.java | 20 -- .../kotlin/org/jacodb/testing/BaseTest.kt | 3 +- .../jacodb/taint/configuration/Position.kt | 8 +- .../SerializedTaintConfigurationItem.kt | 33 ++- .../jacodb/taint/configuration/TaintAction.kt | 2 - .../taint/configuration/TaintCondition.kt | 62 +++-- .../TaintConfigurationFeature.kt | 6 - .../jacodb/taint/configuration/TaintMark.kt | 2 - 30 files changed, 749 insertions(+), 1040 deletions(-) delete mode 100644 jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt delete mode 100644 jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt delete mode 100644 jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index fc237dac5..2ba922302 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -52,11 +52,6 @@ open class BasicConditionEvaluator( internal val positionResolver: PositionResolver>, ) : ConditionVisitor { - // Default condition handler: - override fun visit(condition: Condition): Boolean { - return false - } - override fun visit(condition: ConstantTrue): Boolean { return true } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt index b2e87d87e..0611e483b 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/ConditionEvaluatorTest.kt @@ -325,12 +325,6 @@ class ConditionEvaluatorTest { assertFalse(evaluator.visit(condition)) } - @Test - fun `external Condition is false`() { - val condition: Condition = mockk() - assertFalse(evaluator.visit(condition)) - } - @Test fun `FactAwareConditionEvaluator supports ContainsMark`() { val fact = Tainted(intValue.toPath(), TaintMark("FOO")) diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt index 514b8a2f2..82d46085b 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/FullExprSetCollector.kt @@ -16,16 +16,15 @@ package org.jacodb.api.cfg -abstract class AbstractFullRawExprSetCollector : - JcRawExprVisitor, - JcRawInstVisitor.Default { - - override fun defaultVisitJcRawInst(inst: JcRawInst) { - inst.operands.forEach { - ifMatches(it) - it.accept(this) +abstract class AbstractFullRawExprSetCollector : JcRawExprVisitor, DefaultJcRawInstVisitor { + + override val defaultInstHandler: (JcRawInst) -> Unit + get() = { + it.operands.forEach { + ifMatches(it) + it.accept(this) + } } - } private fun visitBinaryExpr(expr: JcRawBinaryExpr) { ifMatches(expr) @@ -132,16 +131,15 @@ abstract class AbstractFullRawExprSetCollector : abstract fun ifMatches(expr: JcRawExpr) } -abstract class AbstractFullExprSetCollector : - JcExprVisitor.Default, - JcInstVisitor.Default { +abstract class AbstractFullExprSetCollector : JcExprVisitor, DefaultJcInstVisitor { - override fun defaultVisitJcInst(inst: JcInst) { - inst.operands.forEach { - ifMatches(it) - it.accept(this) + override val defaultInstHandler: (JcInst) -> Any + get() = { + it.operands.forEach { + ifMatches(it) + it.accept(this) + } } - } private fun visitBinaryExpr(expr: JcBinaryExpr) { ifMatches(expr) @@ -157,104 +155,115 @@ abstract class AbstractFullExprSetCollector : expr.args.forEach { it.accept(this) } } - override fun visitJcAddExpr(expr: JcAddExpr) = visitBinaryExpr(expr) - override fun visitJcAndExpr(expr: JcAndExpr) = visitBinaryExpr(expr) - override fun visitJcCmpExpr(expr: JcCmpExpr) = visitBinaryExpr(expr) - override fun visitJcCmpgExpr(expr: JcCmpgExpr) = visitBinaryExpr(expr) - override fun visitJcCmplExpr(expr: JcCmplExpr) = visitBinaryExpr(expr) - override fun visitJcDivExpr(expr: JcDivExpr) = visitBinaryExpr(expr) - override fun visitJcMulExpr(expr: JcMulExpr) = visitBinaryExpr(expr) - override fun visitJcEqExpr(expr: JcEqExpr) = visitBinaryExpr(expr) - override fun visitJcNeqExpr(expr: JcNeqExpr) = visitBinaryExpr(expr) - override fun visitJcGeExpr(expr: JcGeExpr) = visitBinaryExpr(expr) - override fun visitJcGtExpr(expr: JcGtExpr) = visitBinaryExpr(expr) - override fun visitJcLeExpr(expr: JcLeExpr) = visitBinaryExpr(expr) - override fun visitJcLtExpr(expr: JcLtExpr) = visitBinaryExpr(expr) - override fun visitJcOrExpr(expr: JcOrExpr) = visitBinaryExpr(expr) - override fun visitJcRemExpr(expr: JcRemExpr) = visitBinaryExpr(expr) - override fun visitJcShlExpr(expr: JcShlExpr) = visitBinaryExpr(expr) - override fun visitJcShrExpr(expr: JcShrExpr) = visitBinaryExpr(expr) - override fun visitJcSubExpr(expr: JcSubExpr) = visitBinaryExpr(expr) - override fun visitJcUshrExpr(expr: JcUshrExpr) = visitBinaryExpr(expr) - override fun visitJcXorExpr(expr: JcXorExpr) = visitBinaryExpr(expr) - - override fun visitJcLambdaExpr(expr: JcLambdaExpr) { + override fun visitJcAddExpr(expr: JcAddExpr): Any = visitBinaryExpr(expr) + override fun visitJcAndExpr(expr: JcAndExpr): Any = visitBinaryExpr(expr) + override fun visitJcCmpExpr(expr: JcCmpExpr): Any = visitBinaryExpr(expr) + override fun visitJcCmpgExpr(expr: JcCmpgExpr): Any = visitBinaryExpr(expr) + override fun visitJcCmplExpr(expr: JcCmplExpr): Any = visitBinaryExpr(expr) + override fun visitJcDivExpr(expr: JcDivExpr): Any = visitBinaryExpr(expr) + override fun visitJcMulExpr(expr: JcMulExpr): Any = visitBinaryExpr(expr) + override fun visitJcEqExpr(expr: JcEqExpr): Any = visitBinaryExpr(expr) + override fun visitJcNeqExpr(expr: JcNeqExpr): Any = visitBinaryExpr(expr) + override fun visitJcGeExpr(expr: JcGeExpr): Any = visitBinaryExpr(expr) + override fun visitJcGtExpr(expr: JcGtExpr): Any = visitBinaryExpr(expr) + override fun visitJcLeExpr(expr: JcLeExpr): Any = visitBinaryExpr(expr) + override fun visitJcLtExpr(expr: JcLtExpr): Any = visitBinaryExpr(expr) + override fun visitJcOrExpr(expr: JcOrExpr): Any = visitBinaryExpr(expr) + override fun visitJcRemExpr(expr: JcRemExpr): Any = visitBinaryExpr(expr) + override fun visitJcShlExpr(expr: JcShlExpr): Any = visitBinaryExpr(expr) + override fun visitJcShrExpr(expr: JcShrExpr): Any = visitBinaryExpr(expr) + override fun visitJcSubExpr(expr: JcSubExpr): Any = visitBinaryExpr(expr) + override fun visitJcUshrExpr(expr: JcUshrExpr): Any = visitBinaryExpr(expr) + override fun visitJcXorExpr(expr: JcXorExpr): Any = visitBinaryExpr(expr) + + override fun visitJcLambdaExpr(expr: JcLambdaExpr): Any { ifMatches(expr) expr.args.forEach { it.accept(this) } + return Unit } - override fun visitJcLengthExpr(expr: JcLengthExpr) { + override fun visitJcLengthExpr(expr: JcLengthExpr): Any { ifMatches(expr) expr.array.accept(this) + return Unit } - override fun visitJcNegExpr(expr: JcNegExpr) { + override fun visitJcNegExpr(expr: JcNegExpr): Any { ifMatches(expr) expr.operand.accept(this) + return Unit } - override fun visitJcCastExpr(expr: JcCastExpr) { + override fun visitJcCastExpr(expr: JcCastExpr): Any { ifMatches(expr) expr.operand.accept(this) + return Unit } - override fun visitJcNewExpr(expr: JcNewExpr) { + override fun visitJcNewExpr(expr: JcNewExpr): Any { ifMatches(expr) + return Unit } - override fun visitJcNewArrayExpr(expr: JcNewArrayExpr) { + override fun visitJcNewArrayExpr(expr: JcNewArrayExpr): Any { ifMatches(expr) expr.dimensions.forEach { it.accept(this) } + return Unit } - override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr) { + override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): Any { ifMatches(expr) expr.operand.accept(this) + return Unit } - override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr) { + override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): Any { ifMatches(expr) expr.args.forEach { it.accept(this) } + return Unit } - override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr) = visitCallExpr(expr) - override fun visitJcStaticCallExpr(expr: JcStaticCallExpr) = visitCallExpr(expr) - override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr) = visitCallExpr(expr) - override fun visitJcThis(value: JcThis) = ifMatches(value) - override fun visitJcArgument(value: JcArgument) = ifMatches(value) - override fun visitJcLocalVar(value: JcLocalVar) = ifMatches(value) + override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): Any = visitCallExpr(expr) + override fun visitJcStaticCallExpr(expr: JcStaticCallExpr): Any = visitCallExpr(expr) + override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): Any = visitCallExpr(expr) + override fun visitJcThis(value: JcThis): Any = ifMatches(value) + override fun visitJcArgument(value: JcArgument): Any = ifMatches(value) + override fun visitJcLocalVar(value: JcLocalVar): Any = ifMatches(value) - override fun visitJcFieldRef(value: JcFieldRef) { + override fun visitJcFieldRef(value: JcFieldRef): Any { ifMatches(value) value.instance?.accept(this) + return Unit } - override fun visitJcArrayAccess(value: JcArrayAccess) { + override fun visitJcArrayAccess(value: JcArrayAccess): Any { ifMatches(value) value.array.accept(this) value.index.accept(this) + return Unit } - override fun visitJcPhiExpr(expr: JcPhiExpr) { + override fun visitJcPhiExpr(expr: JcPhiExpr): Any { ifMatches(expr) expr.args.forEach { it.accept(this) } expr.values.forEach { it.accept(this) } + return Unit } - override fun defaultVisitJcExpr(expr: JcExpr) = ifMatches(expr) - override fun visitJcBool(value: JcBool) = ifMatches(value) - override fun visitJcByte(value: JcByte) = ifMatches(value) - override fun visitJcChar(value: JcChar) = ifMatches(value) - override fun visitJcShort(value: JcShort) = ifMatches(value) - override fun visitJcInt(value: JcInt) = ifMatches(value) - override fun visitJcLong(value: JcLong) = ifMatches(value) - override fun visitJcFloat(value: JcFloat) = ifMatches(value) - override fun visitJcDouble(value: JcDouble) = ifMatches(value) - override fun visitJcNullConstant(value: JcNullConstant) = ifMatches(value) - override fun visitJcStringConstant(value: JcStringConstant) = ifMatches(value) - override fun visitJcClassConstant(value: JcClassConstant) = ifMatches(value) - override fun visitJcMethodConstant(value: JcMethodConstant) = ifMatches(value) - override fun visitJcMethodType(value: JcMethodType) = ifMatches(value) + override fun visitExternalJcExpr(expr: JcExpr): Any = ifMatches(expr) + override fun visitJcBool(value: JcBool): Any = ifMatches(value) + override fun visitJcByte(value: JcByte): Any = ifMatches(value) + override fun visitJcChar(value: JcChar): Any = ifMatches(value) + override fun visitJcShort(value: JcShort): Any = ifMatches(value) + override fun visitJcInt(value: JcInt): Any = ifMatches(value) + override fun visitJcLong(value: JcLong): Any = ifMatches(value) + override fun visitJcFloat(value: JcFloat): Any = ifMatches(value) + override fun visitJcDouble(value: JcDouble): Any = ifMatches(value) + override fun visitJcNullConstant(value: JcNullConstant): Any = ifMatches(value) + override fun visitJcStringConstant(value: JcStringConstant): Any = ifMatches(value) + override fun visitJcClassConstant(value: JcClassConstant): Any = ifMatches(value) + override fun visitJcMethodConstant(value: JcMethodConstant): Any = ifMatches(value) + override fun visitJcMethodType(value: JcMethodType): Any = ifMatches(value) abstract fun ifMatches(expr: JcExpr) -} +} \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt deleted file mode 100644 index ade7cd284..000000000 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcExprVisitor.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.api.cfg - -interface JcExprVisitor { - fun visitExternalJcExpr(expr: JcExpr): T - - fun visitJcAddExpr(expr: JcAddExpr): T - fun visitJcAndExpr(expr: JcAndExpr): T - fun visitJcCmpExpr(expr: JcCmpExpr): T - fun visitJcCmpgExpr(expr: JcCmpgExpr): T - fun visitJcCmplExpr(expr: JcCmplExpr): T - fun visitJcDivExpr(expr: JcDivExpr): T - fun visitJcMulExpr(expr: JcMulExpr): T - fun visitJcEqExpr(expr: JcEqExpr): T - fun visitJcNeqExpr(expr: JcNeqExpr): T - fun visitJcGeExpr(expr: JcGeExpr): T - fun visitJcGtExpr(expr: JcGtExpr): T - fun visitJcLeExpr(expr: JcLeExpr): T - fun visitJcLtExpr(expr: JcLtExpr): T - fun visitJcOrExpr(expr: JcOrExpr): T - fun visitJcRemExpr(expr: JcRemExpr): T - fun visitJcShlExpr(expr: JcShlExpr): T - fun visitJcShrExpr(expr: JcShrExpr): T - fun visitJcSubExpr(expr: JcSubExpr): T - fun visitJcUshrExpr(expr: JcUshrExpr): T - fun visitJcXorExpr(expr: JcXorExpr): T - fun visitJcLengthExpr(expr: JcLengthExpr): T - fun visitJcNegExpr(expr: JcNegExpr): T - fun visitJcCastExpr(expr: JcCastExpr): T - fun visitJcNewExpr(expr: JcNewExpr): T - fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T - fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T - fun visitJcPhiExpr(expr: JcPhiExpr): T - fun visitJcLambdaExpr(expr: JcLambdaExpr): T - fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T - fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T - fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T - fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T - - fun visitExternalJcValue(value: JcValue): T - - fun visitJcThis(value: JcThis): T - fun visitJcArgument(value: JcArgument): T - fun visitJcLocalVar(value: JcLocalVar): T - fun visitJcFieldRef(value: JcFieldRef): T - fun visitJcArrayAccess(value: JcArrayAccess): T - fun visitJcBool(value: JcBool): T - fun visitJcByte(value: JcByte): T - fun visitJcChar(value: JcChar): T - fun visitJcShort(value: JcShort): T - fun visitJcInt(value: JcInt): T - fun visitJcLong(value: JcLong): T - fun visitJcFloat(value: JcFloat): T - fun visitJcDouble(value: JcDouble): T - fun visitJcNullConstant(value: JcNullConstant): T - fun visitJcStringConstant(value: JcStringConstant): T - fun visitJcClassConstant(value: JcClassConstant): T - fun visitJcMethodConstant(value: JcMethodConstant): T - fun visitJcMethodType(value: JcMethodType): T - - interface Default : JcExprVisitor { - fun defaultVisitJcExpr(expr: JcExpr): T - fun defaultVisitJcValue(value: JcValue): T = defaultVisitJcExpr(value) - - override fun visitExternalJcExpr(expr: JcExpr): T = defaultVisitJcExpr(expr) - - override fun visitJcAddExpr(expr: JcAddExpr): T = defaultVisitJcExpr(expr) - override fun visitJcAndExpr(expr: JcAndExpr): T = defaultVisitJcExpr(expr) - override fun visitJcCmpExpr(expr: JcCmpExpr): T = defaultVisitJcExpr(expr) - override fun visitJcCmpgExpr(expr: JcCmpgExpr): T = defaultVisitJcExpr(expr) - override fun visitJcCmplExpr(expr: JcCmplExpr): T = defaultVisitJcExpr(expr) - override fun visitJcDivExpr(expr: JcDivExpr): T = defaultVisitJcExpr(expr) - override fun visitJcMulExpr(expr: JcMulExpr): T = defaultVisitJcExpr(expr) - override fun visitJcEqExpr(expr: JcEqExpr): T = defaultVisitJcExpr(expr) - override fun visitJcNeqExpr(expr: JcNeqExpr): T = defaultVisitJcExpr(expr) - override fun visitJcGeExpr(expr: JcGeExpr): T = defaultVisitJcExpr(expr) - override fun visitJcGtExpr(expr: JcGtExpr): T = defaultVisitJcExpr(expr) - override fun visitJcLeExpr(expr: JcLeExpr): T = defaultVisitJcExpr(expr) - override fun visitJcLtExpr(expr: JcLtExpr): T = defaultVisitJcExpr(expr) - override fun visitJcOrExpr(expr: JcOrExpr): T = defaultVisitJcExpr(expr) - override fun visitJcRemExpr(expr: JcRemExpr): T = defaultVisitJcExpr(expr) - override fun visitJcShlExpr(expr: JcShlExpr): T = defaultVisitJcExpr(expr) - override fun visitJcShrExpr(expr: JcShrExpr): T = defaultVisitJcExpr(expr) - override fun visitJcSubExpr(expr: JcSubExpr): T = defaultVisitJcExpr(expr) - override fun visitJcUshrExpr(expr: JcUshrExpr): T = defaultVisitJcExpr(expr) - override fun visitJcXorExpr(expr: JcXorExpr): T = defaultVisitJcExpr(expr) - override fun visitJcLengthExpr(expr: JcLengthExpr): T = defaultVisitJcExpr(expr) - override fun visitJcNegExpr(expr: JcNegExpr): T = defaultVisitJcExpr(expr) - override fun visitJcCastExpr(expr: JcCastExpr): T = defaultVisitJcExpr(expr) - override fun visitJcNewExpr(expr: JcNewExpr): T = defaultVisitJcExpr(expr) - override fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T = defaultVisitJcExpr(expr) - override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T = defaultVisitJcExpr(expr) - override fun visitJcPhiExpr(expr: JcPhiExpr): T = defaultVisitJcExpr(expr) - override fun visitJcLambdaExpr(expr: JcLambdaExpr): T = defaultVisitJcExpr(expr) - override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T = defaultVisitJcExpr(expr) - override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T = defaultVisitJcExpr(expr) - override fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T = defaultVisitJcExpr(expr) - override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T = defaultVisitJcExpr(expr) - - override fun visitExternalJcValue(value: JcValue): T = defaultVisitJcValue(value) - - override fun visitJcThis(value: JcThis): T = defaultVisitJcValue(value) - override fun visitJcArgument(value: JcArgument): T = defaultVisitJcValue(value) - override fun visitJcLocalVar(value: JcLocalVar): T = defaultVisitJcValue(value) - override fun visitJcFieldRef(value: JcFieldRef): T = defaultVisitJcValue(value) - override fun visitJcArrayAccess(value: JcArrayAccess): T = defaultVisitJcValue(value) - override fun visitJcBool(value: JcBool): T = defaultVisitJcValue(value) - override fun visitJcByte(value: JcByte): T = defaultVisitJcValue(value) - override fun visitJcChar(value: JcChar): T = defaultVisitJcValue(value) - override fun visitJcShort(value: JcShort): T = defaultVisitJcValue(value) - override fun visitJcInt(value: JcInt): T = defaultVisitJcValue(value) - override fun visitJcLong(value: JcLong): T = defaultVisitJcValue(value) - override fun visitJcFloat(value: JcFloat): T = defaultVisitJcValue(value) - override fun visitJcDouble(value: JcDouble): T = defaultVisitJcValue(value) - override fun visitJcNullConstant(value: JcNullConstant): T = defaultVisitJcValue(value) - override fun visitJcStringConstant(value: JcStringConstant): T = defaultVisitJcValue(value) - override fun visitJcClassConstant(value: JcClassConstant): T = defaultVisitJcValue(value) - override fun visitJcMethodConstant(value: JcMethodConstant): T = defaultVisitJcValue(value) - override fun visitJcMethodType(value: JcMethodType): T = defaultVisitJcValue(value) - } -} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt index c7f5e6d4a..2ddc8fa25 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt @@ -17,7 +17,6 @@ package org.jacodb.api.cfg import org.jacodb.api.JcMethod -import org.jacodb.api.JcPrimitiveType import org.jacodb.api.JcType import org.jacodb.api.JcTypedField import org.jacodb.api.JcTypedMethod @@ -943,7 +942,7 @@ interface JcNumericConstant : JcConstant { } -data class JcBool(val value: Boolean, override val type: JcPrimitiveType) : JcConstant { +data class JcBool(val value: Boolean, override val type: JcType) : JcConstant { override fun toString(): String = "$value" override fun accept(visitor: JcExprVisitor): T { @@ -951,7 +950,7 @@ data class JcBool(val value: Boolean, override val type: JcPrimitiveType) : JcCo } } -data class JcByte(override val value: Byte, override val type: JcPrimitiveType) : JcNumericConstant { +data class JcByte(override val value: Byte, override val type: JcType) : JcNumericConstant { override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { @@ -991,7 +990,7 @@ data class JcByte(override val value: Byte, override val type: JcPrimitiveType) } } -data class JcChar(val value: Char, override val type: JcPrimitiveType) : JcConstant { +data class JcChar(val value: Char, override val type: JcType) : JcConstant { override fun toString(): String = "$value" override fun accept(visitor: JcExprVisitor): T { @@ -999,7 +998,7 @@ data class JcChar(val value: Char, override val type: JcPrimitiveType) : JcConst } } -data class JcShort(override val value: Short, override val type: JcPrimitiveType) : JcNumericConstant { +data class JcShort(override val value: Short, override val type: JcType) : JcNumericConstant { override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { @@ -1039,7 +1038,7 @@ data class JcShort(override val value: Short, override val type: JcPrimitiveType } } -data class JcInt(override val value: Int, override val type: JcPrimitiveType) : JcNumericConstant { +data class JcInt(override val value: Int, override val type: JcType) : JcNumericConstant { override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { @@ -1079,10 +1078,7 @@ data class JcInt(override val value: Int, override val type: JcPrimitiveType) : } } -data class JcLong( - override val value: Long, - override val type: JcPrimitiveType, -) : JcNumericConstant { +data class JcLong(override val value: Long, override val type: JcType) : JcNumericConstant { override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { @@ -1122,10 +1118,7 @@ data class JcLong( } } -data class JcFloat( - override val value: Float, - override val type: JcPrimitiveType, -) : JcNumericConstant { +data class JcFloat(override val value: Float, override val type: JcType) : JcNumericConstant { override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { @@ -1165,10 +1158,7 @@ data class JcFloat( } } -data class JcDouble( - override val value: Double, - override val type: JcPrimitiveType, -) : JcNumericConstant { +data class JcDouble(override val value: Double, override val type: JcType) : JcNumericConstant { override fun toString(): String = "$value" override fun plus(c: JcNumericConstant): JcNumericConstant { diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt index c71c41b2f..de445f3a2 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInstVisitor.kt @@ -16,9 +16,7 @@ package org.jacodb.api.cfg -interface JcInstVisitor { - fun visitExternalJcInst(inst: JcInst): T - +interface JcInstVisitor { fun visitJcAssignInst(inst: JcAssignInst): T fun visitJcEnterMonitorInst(inst: JcEnterMonitorInst): T fun visitJcExitMonitorInst(inst: JcExitMonitorInst): T @@ -29,21 +27,199 @@ interface JcInstVisitor { fun visitJcGotoInst(inst: JcGotoInst): T fun visitJcIfInst(inst: JcIfInst): T fun visitJcSwitchInst(inst: JcSwitchInst): T + fun visitExternalJcInst(inst: JcInst): T + +} + +@JvmDefaultWithoutCompatibility +interface DefaultJcInstVisitor : JcInstVisitor { + val defaultInstHandler: (JcInst) -> T + + + override fun visitJcAssignInst(inst: JcAssignInst): T = defaultInstHandler(inst) + + override fun visitJcEnterMonitorInst(inst: JcEnterMonitorInst): T = defaultInstHandler(inst) + + override fun visitJcExitMonitorInst(inst: JcExitMonitorInst): T = defaultInstHandler(inst) + + override fun visitJcCallInst(inst: JcCallInst): T = defaultInstHandler(inst) + + override fun visitJcReturnInst(inst: JcReturnInst): T = defaultInstHandler(inst) + + override fun visitJcThrowInst(inst: JcThrowInst): T = defaultInstHandler(inst) + + override fun visitJcCatchInst(inst: JcCatchInst): T = defaultInstHandler(inst) + + override fun visitJcGotoInst(inst: JcGotoInst): T = defaultInstHandler(inst) + + override fun visitJcIfInst(inst: JcIfInst): T = defaultInstHandler(inst) + + override fun visitJcSwitchInst(inst: JcSwitchInst): T = defaultInstHandler(inst) + + override fun visitExternalJcInst(inst: JcInst): T = defaultInstHandler(inst) +} + +interface JcExprVisitor { + fun visitJcAddExpr(expr: JcAddExpr): T + fun visitJcAndExpr(expr: JcAndExpr): T + fun visitJcCmpExpr(expr: JcCmpExpr): T + fun visitJcCmpgExpr(expr: JcCmpgExpr): T + fun visitJcCmplExpr(expr: JcCmplExpr): T + fun visitJcDivExpr(expr: JcDivExpr): T + fun visitJcMulExpr(expr: JcMulExpr): T + fun visitJcEqExpr(expr: JcEqExpr): T + fun visitJcNeqExpr(expr: JcNeqExpr): T + fun visitJcGeExpr(expr: JcGeExpr): T + fun visitJcGtExpr(expr: JcGtExpr): T + fun visitJcLeExpr(expr: JcLeExpr): T + fun visitJcLtExpr(expr: JcLtExpr): T + fun visitJcOrExpr(expr: JcOrExpr): T + fun visitJcRemExpr(expr: JcRemExpr): T + fun visitJcShlExpr(expr: JcShlExpr): T + fun visitJcShrExpr(expr: JcShrExpr): T + fun visitJcSubExpr(expr: JcSubExpr): T + fun visitJcUshrExpr(expr: JcUshrExpr): T + fun visitJcXorExpr(expr: JcXorExpr): T + fun visitJcLengthExpr(expr: JcLengthExpr): T + fun visitJcNegExpr(expr: JcNegExpr): T + fun visitJcCastExpr(expr: JcCastExpr): T + fun visitJcNewExpr(expr: JcNewExpr): T + fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T + fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T + fun visitJcLambdaExpr(expr: JcLambdaExpr): T + fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T + fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T + fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T + fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T + + fun visitJcThis(value: JcThis): T + fun visitJcArgument(value: JcArgument): T + fun visitJcLocalVar(value: JcLocalVar): T + fun visitJcFieldRef(value: JcFieldRef): T + fun visitJcArrayAccess(value: JcArrayAccess): T + fun visitJcBool(value: JcBool): T + fun visitJcByte(value: JcByte): T + fun visitJcChar(value: JcChar): T + fun visitJcShort(value: JcShort): T + fun visitJcInt(value: JcInt): T + fun visitJcLong(value: JcLong): T + fun visitJcFloat(value: JcFloat): T + fun visitJcDouble(value: JcDouble): T + fun visitJcNullConstant(value: JcNullConstant): T + fun visitJcStringConstant(value: JcStringConstant): T + fun visitJcClassConstant(value: JcClassConstant): T + fun visitJcMethodConstant(value: JcMethodConstant): T + fun visitJcMethodType(value: JcMethodType): T + fun visitJcPhiExpr(expr: JcPhiExpr): T + + fun visitExternalJcExpr(expr: JcExpr): T +} + + +@JvmDefaultWithoutCompatibility +interface DefaultJcExprVisitor : JcExprVisitor { + val defaultExprHandler: (JcExpr) -> T + + override fun visitJcAddExpr(expr: JcAddExpr): T = defaultExprHandler(expr) + + override fun visitJcAndExpr(expr: JcAndExpr): T = defaultExprHandler(expr) + + override fun visitJcCmpExpr(expr: JcCmpExpr): T = defaultExprHandler(expr) + + override fun visitJcCmpgExpr(expr: JcCmpgExpr): T = defaultExprHandler(expr) + + override fun visitJcCmplExpr(expr: JcCmplExpr): T = defaultExprHandler(expr) + + override fun visitJcDivExpr(expr: JcDivExpr): T = defaultExprHandler(expr) + + override fun visitJcMulExpr(expr: JcMulExpr): T = defaultExprHandler(expr) + + override fun visitJcEqExpr(expr: JcEqExpr): T = defaultExprHandler(expr) + + override fun visitJcNeqExpr(expr: JcNeqExpr): T = defaultExprHandler(expr) + + override fun visitJcGeExpr(expr: JcGeExpr): T = defaultExprHandler(expr) + + override fun visitJcGtExpr(expr: JcGtExpr): T = defaultExprHandler(expr) + + override fun visitJcLeExpr(expr: JcLeExpr): T = defaultExprHandler(expr) + + override fun visitJcLtExpr(expr: JcLtExpr): T = defaultExprHandler(expr) + + override fun visitJcOrExpr(expr: JcOrExpr): T = defaultExprHandler(expr) + + override fun visitJcRemExpr(expr: JcRemExpr): T = defaultExprHandler(expr) + + override fun visitJcShlExpr(expr: JcShlExpr): T = defaultExprHandler(expr) + + override fun visitJcShrExpr(expr: JcShrExpr): T = defaultExprHandler(expr) + + override fun visitJcSubExpr(expr: JcSubExpr): T = defaultExprHandler(expr) + + override fun visitJcUshrExpr(expr: JcUshrExpr): T = defaultExprHandler(expr) + + override fun visitJcXorExpr(expr: JcXorExpr): T = defaultExprHandler(expr) + + override fun visitJcLengthExpr(expr: JcLengthExpr): T = defaultExprHandler(expr) + + override fun visitJcNegExpr(expr: JcNegExpr): T = defaultExprHandler(expr) + + override fun visitJcCastExpr(expr: JcCastExpr): T = defaultExprHandler(expr) + + override fun visitJcNewExpr(expr: JcNewExpr): T = defaultExprHandler(expr) + + override fun visitJcNewArrayExpr(expr: JcNewArrayExpr): T = defaultExprHandler(expr) + + override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): T = defaultExprHandler(expr) + + override fun visitJcLambdaExpr(expr: JcLambdaExpr): T = defaultExprHandler(expr) + + override fun visitJcDynamicCallExpr(expr: JcDynamicCallExpr): T = defaultExprHandler(expr) + + override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): T = defaultExprHandler(expr) + + override fun visitJcStaticCallExpr(expr: JcStaticCallExpr): T = defaultExprHandler(expr) + + override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): T = defaultExprHandler(expr) + + + override fun visitJcThis(value: JcThis): T = defaultExprHandler(value) + + override fun visitJcArgument(value: JcArgument): T = defaultExprHandler(value) + + override fun visitJcLocalVar(value: JcLocalVar): T = defaultExprHandler(value) + + override fun visitJcFieldRef(value: JcFieldRef): T = defaultExprHandler(value) + + override fun visitJcArrayAccess(value: JcArrayAccess): T = defaultExprHandler(value) + + override fun visitJcBool(value: JcBool): T = defaultExprHandler(value) + + override fun visitJcByte(value: JcByte): T = defaultExprHandler(value) + + override fun visitJcChar(value: JcChar): T = defaultExprHandler(value) + + override fun visitJcShort(value: JcShort): T = defaultExprHandler(value) + + override fun visitJcInt(value: JcInt): T = defaultExprHandler(value) + + override fun visitJcLong(value: JcLong): T = defaultExprHandler(value) + + override fun visitJcFloat(value: JcFloat): T = defaultExprHandler(value) + + override fun visitJcDouble(value: JcDouble): T = defaultExprHandler(value) + + override fun visitJcNullConstant(value: JcNullConstant): T = defaultExprHandler(value) + + override fun visitJcStringConstant(value: JcStringConstant): T = defaultExprHandler(value) + + override fun visitJcClassConstant(value: JcClassConstant): T = defaultExprHandler(value) + + override fun visitJcMethodConstant(value: JcMethodConstant): T = defaultExprHandler(value) + + override fun visitJcMethodType(value: JcMethodType): T = defaultExprHandler(value) + + override fun visitJcPhiExpr(expr: JcPhiExpr): T = defaultExprHandler(expr) - interface Default : JcInstVisitor { - fun defaultVisitJcInst(inst: JcInst): T - - override fun visitExternalJcInst(inst: JcInst): T = defaultVisitJcInst(inst) - - override fun visitJcAssignInst(inst: JcAssignInst): T = defaultVisitJcInst(inst) - override fun visitJcEnterMonitorInst(inst: JcEnterMonitorInst): T = defaultVisitJcInst(inst) - override fun visitJcExitMonitorInst(inst: JcExitMonitorInst): T = defaultVisitJcInst(inst) - override fun visitJcCallInst(inst: JcCallInst): T = defaultVisitJcInst(inst) - override fun visitJcReturnInst(inst: JcReturnInst): T = defaultVisitJcInst(inst) - override fun visitJcThrowInst(inst: JcThrowInst): T = defaultVisitJcInst(inst) - override fun visitJcCatchInst(inst: JcCatchInst): T = defaultVisitJcInst(inst) - override fun visitJcGotoInst(inst: JcGotoInst): T = defaultVisitJcInst(inst) - override fun visitJcIfInst(inst: JcIfInst): T = defaultVisitJcInst(inst) - override fun visitJcSwitchInst(inst: JcSwitchInst): T = defaultVisitJcInst(inst) - } + override fun visitExternalJcExpr(expr: JcExpr): T = defaultExprHandler(expr) } diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt deleted file mode 100644 index 1c2974a39..000000000 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawExprVisitor.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.api.cfg - -interface JcRawExprVisitor { - fun visitJcRawAddExpr(expr: JcRawAddExpr): T - fun visitJcRawAndExpr(expr: JcRawAndExpr): T - fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T - fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T - fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T - fun visitJcRawDivExpr(expr: JcRawDivExpr): T - fun visitJcRawMulExpr(expr: JcRawMulExpr): T - fun visitJcRawEqExpr(expr: JcRawEqExpr): T - fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T - fun visitJcRawGeExpr(expr: JcRawGeExpr): T - fun visitJcRawGtExpr(expr: JcRawGtExpr): T - fun visitJcRawLeExpr(expr: JcRawLeExpr): T - fun visitJcRawLtExpr(expr: JcRawLtExpr): T - fun visitJcRawOrExpr(expr: JcRawOrExpr): T - fun visitJcRawRemExpr(expr: JcRawRemExpr): T - fun visitJcRawShlExpr(expr: JcRawShlExpr): T - fun visitJcRawShrExpr(expr: JcRawShrExpr): T - fun visitJcRawSubExpr(expr: JcRawSubExpr): T - fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T - fun visitJcRawXorExpr(expr: JcRawXorExpr): T - fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T - fun visitJcRawNegExpr(expr: JcRawNegExpr): T - fun visitJcRawCastExpr(expr: JcRawCastExpr): T - fun visitJcRawNewExpr(expr: JcRawNewExpr): T - fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T - fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T - fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T - fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T - fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T - fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T - fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T - - fun visitJcRawThis(value: JcRawThis): T - fun visitJcRawArgument(value: JcRawArgument): T - fun visitJcRawLocalVar(value: JcRawLocalVar): T - fun visitJcRawFieldRef(value: JcRawFieldRef): T - fun visitJcRawArrayAccess(value: JcRawArrayAccess): T - fun visitJcRawBool(value: JcRawBool): T - fun visitJcRawByte(value: JcRawByte): T - fun visitJcRawChar(value: JcRawChar): T - fun visitJcRawShort(value: JcRawShort): T - fun visitJcRawInt(value: JcRawInt): T - fun visitJcRawLong(value: JcRawLong): T - fun visitJcRawFloat(value: JcRawFloat): T - fun visitJcRawDouble(value: JcRawDouble): T - fun visitJcRawNullConstant(value: JcRawNullConstant): T - fun visitJcRawStringConstant(value: JcRawStringConstant): T - fun visitJcRawClassConstant(value: JcRawClassConstant): T - fun visitJcRawMethodConstant(value: JcRawMethodConstant): T - fun visitJcRawMethodType(value: JcRawMethodType): T - - interface Default : JcRawExprVisitor { - fun defaultVisitJcRawExpr(expr: JcRawExpr): T - fun defaultVisitJcRawValue(value: JcRawValue): T = defaultVisitJcRawExpr(value) - - override fun visitJcRawAddExpr(expr: JcRawAddExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawAndExpr(expr: JcRawAndExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawDivExpr(expr: JcRawDivExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawMulExpr(expr: JcRawMulExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawEqExpr(expr: JcRawEqExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawGeExpr(expr: JcRawGeExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawGtExpr(expr: JcRawGtExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawLeExpr(expr: JcRawLeExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawLtExpr(expr: JcRawLtExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawOrExpr(expr: JcRawOrExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawRemExpr(expr: JcRawRemExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawShlExpr(expr: JcRawShlExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawShrExpr(expr: JcRawShrExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawSubExpr(expr: JcRawSubExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawXorExpr(expr: JcRawXorExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawNegExpr(expr: JcRawNegExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawCastExpr(expr: JcRawCastExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawNewExpr(expr: JcRawNewExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T = defaultVisitJcRawExpr(expr) - override fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T = defaultVisitJcRawExpr(expr) - - override fun visitJcRawThis(value: JcRawThis): T = defaultVisitJcRawValue(value) - override fun visitJcRawArgument(value: JcRawArgument): T = defaultVisitJcRawValue(value) - override fun visitJcRawLocalVar(value: JcRawLocalVar): T = defaultVisitJcRawValue(value) - override fun visitJcRawFieldRef(value: JcRawFieldRef): T = defaultVisitJcRawValue(value) - override fun visitJcRawArrayAccess(value: JcRawArrayAccess): T = defaultVisitJcRawValue(value) - override fun visitJcRawBool(value: JcRawBool): T = defaultVisitJcRawValue(value) - override fun visitJcRawByte(value: JcRawByte): T = defaultVisitJcRawValue(value) - override fun visitJcRawChar(value: JcRawChar): T = defaultVisitJcRawValue(value) - override fun visitJcRawShort(value: JcRawShort): T = defaultVisitJcRawValue(value) - override fun visitJcRawInt(value: JcRawInt): T = defaultVisitJcRawValue(value) - override fun visitJcRawLong(value: JcRawLong): T = defaultVisitJcRawValue(value) - override fun visitJcRawFloat(value: JcRawFloat): T = defaultVisitJcRawValue(value) - override fun visitJcRawDouble(value: JcRawDouble): T = defaultVisitJcRawValue(value) - override fun visitJcRawNullConstant(value: JcRawNullConstant): T = defaultVisitJcRawValue(value) - override fun visitJcRawStringConstant(value: JcRawStringConstant): T = defaultVisitJcRawValue(value) - override fun visitJcRawClassConstant(value: JcRawClassConstant): T = defaultVisitJcRawValue(value) - override fun visitJcRawMethodConstant(value: JcRawMethodConstant): T = defaultVisitJcRawValue(value) - override fun visitJcRawMethodType(value: JcRawMethodType): T = defaultVisitJcRawValue(value) - } -} diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt index 227fece72..21fa62116 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt @@ -614,7 +614,7 @@ data class JcRawNewArrayExpr( override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawNewArrayExpr(this) } - + companion object { private val regexToProcessDimensions = Regex("\\[(.*?)]") @@ -1042,4 +1042,4 @@ data class JcRawMethodType( // } // } // return null -//} +//} \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt index 7002dfb92..3c798cecd 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInstVisitor.kt @@ -16,7 +16,7 @@ package org.jacodb.api.cfg -interface JcRawInstVisitor { +interface JcRawInstVisitor { fun visitJcRawAssignInst(inst: JcRawAssignInst): T fun visitJcRawEnterMonitorInst(inst: JcRawEnterMonitorInst): T fun visitJcRawExitMonitorInst(inst: JcRawExitMonitorInst): T @@ -29,21 +29,192 @@ interface JcRawInstVisitor { fun visitJcRawGotoInst(inst: JcRawGotoInst): T fun visitJcRawIfInst(inst: JcRawIfInst): T fun visitJcRawSwitchInst(inst: JcRawSwitchInst): T +} + +@JvmDefaultWithoutCompatibility +interface DefaultJcRawInstVisitor : JcRawInstVisitor { + val defaultInstHandler: (JcRawInst) -> T + + + override fun visitJcRawAssignInst(inst: JcRawAssignInst): T = defaultInstHandler(inst) + + override fun visitJcRawEnterMonitorInst(inst: JcRawEnterMonitorInst): T = defaultInstHandler(inst) + + override fun visitJcRawExitMonitorInst(inst: JcRawExitMonitorInst): T = defaultInstHandler(inst) + + override fun visitJcRawCallInst(inst: JcRawCallInst): T = defaultInstHandler(inst) + + override fun visitJcRawLabelInst(inst: JcRawLabelInst): T = defaultInstHandler(inst) + + override fun visitJcRawLineNumberInst(inst: JcRawLineNumberInst): T = defaultInstHandler(inst) + + override fun visitJcRawReturnInst(inst: JcRawReturnInst): T = defaultInstHandler(inst) + + override fun visitJcRawThrowInst(inst: JcRawThrowInst): T = defaultInstHandler(inst) + + override fun visitJcRawCatchInst(inst: JcRawCatchInst): T = defaultInstHandler(inst) + + override fun visitJcRawGotoInst(inst: JcRawGotoInst): T = defaultInstHandler(inst) + + override fun visitJcRawIfInst(inst: JcRawIfInst): T = defaultInstHandler(inst) + + override fun visitJcRawSwitchInst(inst: JcRawSwitchInst): T = defaultInstHandler(inst) + +} + +interface JcRawExprVisitor { + fun visitJcRawAddExpr(expr: JcRawAddExpr): T + fun visitJcRawAndExpr(expr: JcRawAndExpr): T + fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T + fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T + fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T + fun visitJcRawDivExpr(expr: JcRawDivExpr): T + fun visitJcRawMulExpr(expr: JcRawMulExpr): T + fun visitJcRawEqExpr(expr: JcRawEqExpr): T + fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T + fun visitJcRawGeExpr(expr: JcRawGeExpr): T + fun visitJcRawGtExpr(expr: JcRawGtExpr): T + fun visitJcRawLeExpr(expr: JcRawLeExpr): T + fun visitJcRawLtExpr(expr: JcRawLtExpr): T + fun visitJcRawOrExpr(expr: JcRawOrExpr): T + fun visitJcRawRemExpr(expr: JcRawRemExpr): T + fun visitJcRawShlExpr(expr: JcRawShlExpr): T + fun visitJcRawShrExpr(expr: JcRawShrExpr): T + fun visitJcRawSubExpr(expr: JcRawSubExpr): T + fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T + fun visitJcRawXorExpr(expr: JcRawXorExpr): T + fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T + fun visitJcRawNegExpr(expr: JcRawNegExpr): T + fun visitJcRawCastExpr(expr: JcRawCastExpr): T + fun visitJcRawNewExpr(expr: JcRawNewExpr): T + fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T + fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T + fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T + fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T + fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T + fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T + fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T + + fun visitJcRawThis(value: JcRawThis): T + fun visitJcRawArgument(value: JcRawArgument): T + fun visitJcRawLocalVar(value: JcRawLocalVar): T + fun visitJcRawFieldRef(value: JcRawFieldRef): T + fun visitJcRawArrayAccess(value: JcRawArrayAccess): T + fun visitJcRawBool(value: JcRawBool): T + fun visitJcRawByte(value: JcRawByte): T + fun visitJcRawChar(value: JcRawChar): T + fun visitJcRawShort(value: JcRawShort): T + fun visitJcRawInt(value: JcRawInt): T + fun visitJcRawLong(value: JcRawLong): T + fun visitJcRawFloat(value: JcRawFloat): T + fun visitJcRawDouble(value: JcRawDouble): T + fun visitJcRawNullConstant(value: JcRawNullConstant): T + fun visitJcRawStringConstant(value: JcRawStringConstant): T + fun visitJcRawClassConstant(value: JcRawClassConstant): T + fun visitJcRawMethodConstant(value: JcRawMethodConstant): T + fun visitJcRawMethodType(value: JcRawMethodType): T +} + +@JvmDefaultWithoutCompatibility +interface DefaultJcRawExprVisitor : JcRawExprVisitor { + val defaultExprHandler: (JcRawExpr) -> T + + + override fun visitJcRawAddExpr(expr: JcRawAddExpr): T = defaultExprHandler(expr) + + override fun visitJcRawAndExpr(expr: JcRawAndExpr): T = defaultExprHandler(expr) + + override fun visitJcRawCmpExpr(expr: JcRawCmpExpr): T = defaultExprHandler(expr) + + override fun visitJcRawCmpgExpr(expr: JcRawCmpgExpr): T = defaultExprHandler(expr) + + override fun visitJcRawCmplExpr(expr: JcRawCmplExpr): T = defaultExprHandler(expr) + + override fun visitJcRawDivExpr(expr: JcRawDivExpr): T = defaultExprHandler(expr) + + override fun visitJcRawMulExpr(expr: JcRawMulExpr): T = defaultExprHandler(expr) + + override fun visitJcRawEqExpr(expr: JcRawEqExpr): T = defaultExprHandler(expr) + + override fun visitJcRawNeqExpr(expr: JcRawNeqExpr): T = defaultExprHandler(expr) + + override fun visitJcRawGeExpr(expr: JcRawGeExpr): T = defaultExprHandler(expr) + + override fun visitJcRawGtExpr(expr: JcRawGtExpr): T = defaultExprHandler(expr) + + override fun visitJcRawLeExpr(expr: JcRawLeExpr): T = defaultExprHandler(expr) + + override fun visitJcRawLtExpr(expr: JcRawLtExpr): T = defaultExprHandler(expr) + + override fun visitJcRawOrExpr(expr: JcRawOrExpr): T = defaultExprHandler(expr) + + override fun visitJcRawRemExpr(expr: JcRawRemExpr): T = defaultExprHandler(expr) + + override fun visitJcRawShlExpr(expr: JcRawShlExpr): T = defaultExprHandler(expr) + + override fun visitJcRawShrExpr(expr: JcRawShrExpr): T = defaultExprHandler(expr) + + override fun visitJcRawSubExpr(expr: JcRawSubExpr): T = defaultExprHandler(expr) + + override fun visitJcRawUshrExpr(expr: JcRawUshrExpr): T = defaultExprHandler(expr) + + override fun visitJcRawXorExpr(expr: JcRawXorExpr): T = defaultExprHandler(expr) + + override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): T = defaultExprHandler(expr) + + override fun visitJcRawNegExpr(expr: JcRawNegExpr): T = defaultExprHandler(expr) + + override fun visitJcRawCastExpr(expr: JcRawCastExpr): T = defaultExprHandler(expr) + + override fun visitJcRawNewExpr(expr: JcRawNewExpr): T = defaultExprHandler(expr) + + override fun visitJcRawNewArrayExpr(expr: JcRawNewArrayExpr): T = defaultExprHandler(expr) + + override fun visitJcRawInstanceOfExpr(expr: JcRawInstanceOfExpr): T = defaultExprHandler(expr) + + override fun visitJcRawDynamicCallExpr(expr: JcRawDynamicCallExpr): T = defaultExprHandler(expr) + + override fun visitJcRawVirtualCallExpr(expr: JcRawVirtualCallExpr): T = defaultExprHandler(expr) + + override fun visitJcRawInterfaceCallExpr(expr: JcRawInterfaceCallExpr): T = defaultExprHandler(expr) + + override fun visitJcRawStaticCallExpr(expr: JcRawStaticCallExpr): T = defaultExprHandler(expr) + + override fun visitJcRawSpecialCallExpr(expr: JcRawSpecialCallExpr): T = defaultExprHandler(expr) + + override fun visitJcRawThis(value: JcRawThis): T = defaultExprHandler(value) + + override fun visitJcRawArgument(value: JcRawArgument): T = defaultExprHandler(value) + + override fun visitJcRawLocalVar(value: JcRawLocalVar): T = defaultExprHandler(value) + + override fun visitJcRawFieldRef(value: JcRawFieldRef): T = defaultExprHandler(value) + + override fun visitJcRawArrayAccess(value: JcRawArrayAccess): T = defaultExprHandler(value) + + override fun visitJcRawBool(value: JcRawBool): T = defaultExprHandler(value) + + override fun visitJcRawByte(value: JcRawByte): T = defaultExprHandler(value) + + override fun visitJcRawChar(value: JcRawChar): T = defaultExprHandler(value) + + override fun visitJcRawShort(value: JcRawShort): T = defaultExprHandler(value) + + override fun visitJcRawInt(value: JcRawInt): T = defaultExprHandler(value) + + override fun visitJcRawLong(value: JcRawLong): T = defaultExprHandler(value) + + override fun visitJcRawFloat(value: JcRawFloat): T = defaultExprHandler(value) + + override fun visitJcRawDouble(value: JcRawDouble): T = defaultExprHandler(value) + + override fun visitJcRawNullConstant(value: JcRawNullConstant): T = defaultExprHandler(value) + + override fun visitJcRawStringConstant(value: JcRawStringConstant): T = defaultExprHandler(value) + + override fun visitJcRawClassConstant(value: JcRawClassConstant): T = defaultExprHandler(value) + + override fun visitJcRawMethodConstant(value: JcRawMethodConstant): T = defaultExprHandler(value) - interface Default : JcRawInstVisitor { - fun defaultVisitJcRawInst(inst: JcRawInst): T - - override fun visitJcRawAssignInst(inst: JcRawAssignInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawEnterMonitorInst(inst: JcRawEnterMonitorInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawExitMonitorInst(inst: JcRawExitMonitorInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawCallInst(inst: JcRawCallInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawLabelInst(inst: JcRawLabelInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawLineNumberInst(inst: JcRawLineNumberInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawReturnInst(inst: JcRawReturnInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawThrowInst(inst: JcRawThrowInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawCatchInst(inst: JcRawCatchInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawGotoInst(inst: JcRawGotoInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawIfInst(inst: JcRawIfInst): T = defaultVisitJcRawInst(inst) - override fun visitJcRawSwitchInst(inst: JcRawSwitchInst): T = defaultVisitJcRawInst(inst) - } + override fun visitJcRawMethodType(value: JcRawMethodType): T = defaultExprHandler(value) } diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt index e24d6fbd6..708cf8f68 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/JcTypes.kt @@ -18,16 +18,14 @@ package org.jacodb.api.ext -import org.jacodb.api.JcArrayType -import org.jacodb.api.JcClassType -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcPrimitiveType -import org.jacodb.api.JcRefType -import org.jacodb.api.JcType -import org.jacodb.api.JcTypedField -import org.jacodb.api.JcTypedMethod -import org.jacodb.api.TypeNotFoundException -import org.jacodb.api.throwClassNotFound +import org.jacodb.api.* +import java.lang.Boolean +import java.lang.Byte +import java.lang.Double +import java.lang.Float +import java.lang.Long +import java.lang.Short + val JcClassType.constructors get() = declaredMethods.filter { it.method.isConstructor } @@ -89,7 +87,7 @@ val JcArrayType.deepestElementType: JcType return type } -fun JcType.isAssignable(declaration: JcType): Boolean { +fun JcType.isAssignable(declaration: JcType): kotlin.Boolean { val nullType = classpath.nullType if (this == declaration) { return true @@ -183,7 +181,7 @@ fun JcClassType.findMethodOrNull(name: String, desc: String): JcTypedMethod? { * * This method doesn't support [org.jacodb.impl.features.classpaths.UnknownClasses] feature. */ -fun JcClassType.findMethodOrNull(predicate: (JcTypedMethod) -> Boolean): JcTypedMethod? { +fun JcClassType.findMethodOrNull(predicate: (JcTypedMethod) -> kotlin.Boolean): JcTypedMethod? { // let's find method based on strict hierarchy // if method is not found then it's defined in interfaces return methods.firstOrNull(predicate) @@ -199,4 +197,4 @@ val JcTypedMethod.humanReadableSignature: String get() { fun JcClasspath.findType(name: String): JcType { return findTypeOrNull(name) ?: throw TypeNotFoundException(name) -} +} \ No newline at end of file diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt index b8f5181d3..c88cd8be6 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/ext/cfg/JcInstructions.kt @@ -18,14 +18,14 @@ package org.jacodb.api.ext.cfg +import org.jacodb.api.cfg.DefaultJcExprVisitor +import org.jacodb.api.cfg.DefaultJcInstVisitor import org.jacodb.api.cfg.JcArrayAccess import org.jacodb.api.cfg.JcCallExpr import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcExprVisitor import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstList -import org.jacodb.api.cfg.JcInstVisitor import org.jacodb.api.cfg.JcLocal import org.jacodb.api.cfg.JcRawExpr import org.jacodb.api.cfg.JcRawExprVisitor @@ -49,6 +49,7 @@ fun JcInstList.collect(visitor: JcRawInstVisitor): Collection< return instructions.map { it.accept(visitor) } } + fun > JcRawInst.applyAndGet(visitor: T, getter: (T) -> R): R { this.accept(visitor) return getter(visitor) @@ -59,65 +60,72 @@ fun > JcRawExpr.applyAndGet(visitor: T, getter: (T return getter(visitor) } -object FieldRefVisitor : - JcExprVisitor.Default, - JcInstVisitor.Default { - override fun defaultVisitJcExpr(expr: JcExpr): JcFieldRef? { - return expr.operands.filterIsInstance().firstOrNull() - } +object FieldRefVisitor : DefaultJcExprVisitor, DefaultJcInstVisitor { - override fun defaultVisitJcInst(inst: JcInst): JcFieldRef? { - return inst.operands.map { it.accept(this) }.firstOrNull { it != null } - } + override val defaultExprHandler: (JcExpr) -> JcFieldRef? + get() = { null } + + override val defaultInstHandler: (JcInst) -> JcFieldRef? + get() = { + it.operands.map { it.accept(this) }.firstOrNull { it != null } + } override fun visitJcFieldRef(value: JcFieldRef): JcFieldRef { return value } } -object ArrayAccessVisitor : - JcExprVisitor.Default, - JcInstVisitor.Default { +object ArrayAccessVisitor : DefaultJcExprVisitor, DefaultJcInstVisitor { - override fun defaultVisitJcExpr(expr: JcExpr): JcArrayAccess? { - return expr.operands.filterIsInstance().firstOrNull() - } + override val defaultExprHandler: (JcExpr) -> JcArrayAccess? + get() = { + it.operands.filterIsInstance().firstOrNull() + } - override fun defaultVisitJcInst(inst: JcInst): JcArrayAccess? { - return inst.operands.map { it.accept(this) }.firstOrNull { it != null } - } + override val defaultInstHandler: (JcInst) -> JcArrayAccess? + get() = { + it.operands.map { it.accept(this) }.firstOrNull { it != null } + } - override fun visitJcArrayAccess(value: JcArrayAccess): JcArrayAccess { - return value - } } -object CallExprVisitor : JcInstVisitor.Default { - override fun defaultVisitJcInst(inst: JcInst): JcCallExpr? { - return inst.operands.filterIsInstance().firstOrNull() - } +object CallExprVisitor : DefaultJcInstVisitor { + + override val defaultInstHandler: (JcInst) -> JcCallExpr? + get() = { + it.operands.filterIsInstance().firstOrNull() + } + } val JcInst.fieldRef: JcFieldRef? - get() = accept(FieldRefVisitor) + get() { + return accept(FieldRefVisitor) + } val JcInst.arrayRef: JcArrayAccess? - get() = accept(ArrayAccessVisitor) + get() { + return accept(ArrayAccessVisitor) + } val JcInst.callExpr: JcCallExpr? - get() = accept(CallExprVisitor) + get() { + return accept(CallExprVisitor) + } val JcInstList.locals: Set get() { - val resolver = LocalResolver() - forEach { it.accept(resolver) } + val resolver = LocalResolver().also {res -> + forEach { it.accept(res) } + } return resolver.result } val JcInstList.values: Set get() { - val resolver = ValueResolver() - forEach { it.accept(resolver) } + val resolver = ValueResolver().also {res -> + forEach { it.accept(res) } + } return resolver.result - } + } \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt index 3ecba69df..abfb2c433 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt @@ -19,23 +19,7 @@ package org.jacodb.impl.analysis.impl import org.jacodb.api.JcClassType import org.jacodb.api.JcClasspath import org.jacodb.api.PredefinedPrimitives -import org.jacodb.api.cfg.BsmStringArg -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcCatchInst -import org.jacodb.api.cfg.JcDynamicCallExpr -import org.jacodb.api.cfg.JcGotoInst -import org.jacodb.api.cfg.JcIfInst -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstList -import org.jacodb.api.cfg.JcInstRef -import org.jacodb.api.cfg.JcInstVisitor -import org.jacodb.api.cfg.JcLocalVar -import org.jacodb.api.cfg.JcStaticCallExpr -import org.jacodb.api.cfg.JcStringConstant -import org.jacodb.api.cfg.JcSwitchInst -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.cfg.JcVirtualCallExpr -import org.jacodb.api.cfg.values +import org.jacodb.api.cfg.* import org.jacodb.api.ext.autoboxIfNeeded import org.jacodb.api.ext.findTypeOrNull import org.jacodb.impl.cfg.JcInstListImpl @@ -43,14 +27,11 @@ import org.jacodb.impl.cfg.VirtualMethodRefImpl import org.jacodb.impl.cfg.methodRef import kotlin.collections.set -class StringConcatSimplifierTransformer( - classpath: JcClasspath, - private val list: JcInstList, -) : JcInstVisitor.Default { +class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list: JcInstList) : + DefaultJcInstVisitor { - override fun defaultVisitJcInst(inst: JcInst): JcInst { - return inst - } + override val defaultInstHandler: (JcInst) -> JcInst + get() = { it } private val instructionReplacements = mutableMapOf() private val instructions = mutableListOf() @@ -146,11 +127,11 @@ class StringConcatSimplifierTransformer( } private fun indexOf(instRef: JcInstRef) = JcInstRef( - instructionIndices[instructionReplacements.getOrDefault(list[instRef.index], list[instRef.index])] ?: -1 + instructionIndices[instructionReplacements.getOrDefault(list.get(instRef.index), list.get(instRef.index))] ?: -1 ) private fun indicesOf(instRef: JcInstRef): List { - val index = list[instRef.index] + val index = list.get(instRef.index) return catchReplacements.getOrDefault(index, listOf(index)).map { JcInstRef(instructions.indexOf(it)) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt index a3b895c0a..ee949291d 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/GraphExt.kt @@ -24,7 +24,11 @@ import info.leadinglight.jdot.enums.Shape import info.leadinglight.jdot.impl.Util import org.jacodb.api.JcClassType import org.jacodb.api.JcClasspath +import org.jacodb.api.JcInstExtFeature +import org.jacodb.api.JcMethod import org.jacodb.api.PredefinedPrimitives +import org.jacodb.api.cfg.DefaultJcExprVisitor +import org.jacodb.api.cfg.DefaultJcInstVisitor import org.jacodb.api.cfg.JcArrayAccess import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcBasicBlock @@ -35,17 +39,17 @@ import org.jacodb.api.cfg.JcDivExpr import org.jacodb.api.cfg.JcDynamicCallExpr import org.jacodb.api.cfg.JcExitMonitorInst import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcExprVisitor import org.jacodb.api.cfg.JcFieldRef import org.jacodb.api.cfg.JcGotoInst import org.jacodb.api.cfg.JcGraph import org.jacodb.api.cfg.JcIfInst import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstVisitor +import org.jacodb.api.cfg.JcInstList import org.jacodb.api.cfg.JcLambdaExpr import org.jacodb.api.cfg.JcLengthExpr import org.jacodb.api.cfg.JcNewArrayExpr import org.jacodb.api.cfg.JcNewExpr +import org.jacodb.api.cfg.JcRawInst import org.jacodb.api.cfg.JcRemExpr import org.jacodb.api.cfg.JcSpecialCallExpr import org.jacodb.api.cfg.JcStaticCallExpr @@ -138,6 +142,7 @@ fun JcGraph.toFile(dotCmd: String, viewCatchConnections: Boolean = false, file: return resultingFile } + fun JcBlockGraph.view(dotCmd: String, viewerCmd: String) { Util.sh(arrayOf(viewerCmd, "file://${toFile(dotCmd)}")) } @@ -220,24 +225,19 @@ fun JcBlockGraph.toFile(dotCmd: String, file: File? = null): Path { * - all the declared checked exception types * - 'java.lang.Throwable' for any potential unchecked types */ -open class JcExceptionResolver( - val classpath: JcClasspath, -) : JcExprVisitor.Default>, - JcInstVisitor.Default> { - +open class JcExceptionResolver(val classpath: JcClasspath) : DefaultJcExprVisitor>, + DefaultJcInstVisitor> { private val throwableType = classpath.findTypeOrNull() as JcClassType private val errorType = classpath.findTypeOrNull() as JcClassType private val runtimeExceptionType = classpath.findTypeOrNull() as JcClassType private val nullPointerExceptionType = classpath.findTypeOrNull() as JcClassType private val arithmeticExceptionType = classpath.findTypeOrNull() as JcClassType - override fun defaultVisitJcExpr(expr: JcExpr): List { - return emptyList() - } + override val defaultExprHandler: (JcExpr) -> List + get() = { emptyList() } - override fun defaultVisitJcInst(inst: JcInst): List { - return emptyList() - } + override val defaultInstHandler: (JcInst) -> List + get() = { emptyList() } override fun visitJcAssignInst(inst: JcAssignInst): List { return inst.lhv.accept(this) + inst.rhv.accept(this) @@ -338,4 +338,4 @@ open class JcExceptionResolver( } } -} +} \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt index 8adb41329..68dd869fe 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcGraphImpl.kt @@ -139,6 +139,7 @@ class JcGraphImpl( override fun iterator(): Iterator = instructions.iterator() + private fun MutableMap>.add(key: KEY, value: VALUE) { val current = this[key] if (current == null) { @@ -149,17 +150,18 @@ class JcGraphImpl( } } -fun JcGraph.filter(visitor: JcInstVisitor): JcGraph = + +fun JcGraph.filter(visitor: JcInstVisitor) = JcGraphImpl(method, instructions.filter { it.accept(visitor) }) -fun JcGraph.filterNot(visitor: JcInstVisitor): JcGraph = +fun JcGraph.filterNot(visitor: JcInstVisitor) = JcGraphImpl(method, instructions.filterNot { it.accept(visitor) }) -fun JcGraph.map(visitor: JcInstVisitor): JcGraph = +fun JcGraph.map(visitor: JcInstVisitor) = JcGraphImpl(method, instructions.map { it.accept(visitor) }) -fun JcGraph.mapNotNull(visitor: JcInstVisitor): JcGraph = +fun JcGraph.mapNotNull(visitor: JcInstVisitor) = JcGraphImpl(method, instructions.mapNotNull { it.accept(visitor) }) -fun JcGraph.flatMap(visitor: JcInstVisitor>): JcGraph = +fun JcGraph.flatMap(visitor: JcInstVisitor>) = JcGraphImpl(method, instructions.flatMap { it.accept(visitor) }) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index c10090001..41cfe704c 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -16,168 +16,15 @@ package org.jacodb.impl.cfg -import org.jacodb.api.JcClassType -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod -import org.jacodb.api.JcType -import org.jacodb.api.TypeName -import org.jacodb.api.cfg.BsmHandle -import org.jacodb.api.cfg.BsmMethodTypeArg -import org.jacodb.api.cfg.JcAddExpr -import org.jacodb.api.cfg.JcAndExpr -import org.jacodb.api.cfg.JcArgument -import org.jacodb.api.cfg.JcArrayAccess -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcBinaryExpr -import org.jacodb.api.cfg.JcBool -import org.jacodb.api.cfg.JcByte -import org.jacodb.api.cfg.JcCallExpr -import org.jacodb.api.cfg.JcCallInst -import org.jacodb.api.cfg.JcCastExpr -import org.jacodb.api.cfg.JcCatchInst -import org.jacodb.api.cfg.JcChar -import org.jacodb.api.cfg.JcClassConstant -import org.jacodb.api.cfg.JcCmpExpr -import org.jacodb.api.cfg.JcCmpgExpr -import org.jacodb.api.cfg.JcCmplExpr -import org.jacodb.api.cfg.JcConditionExpr -import org.jacodb.api.cfg.JcDivExpr -import org.jacodb.api.cfg.JcDouble -import org.jacodb.api.cfg.JcDynamicCallExpr -import org.jacodb.api.cfg.JcEnterMonitorInst -import org.jacodb.api.cfg.JcEqExpr -import org.jacodb.api.cfg.JcExitMonitorInst -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcFieldRef -import org.jacodb.api.cfg.JcFloat -import org.jacodb.api.cfg.JcGeExpr -import org.jacodb.api.cfg.JcGotoInst -import org.jacodb.api.cfg.JcGtExpr -import org.jacodb.api.cfg.JcIfInst -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstList -import org.jacodb.api.cfg.JcInstLocation -import org.jacodb.api.cfg.JcInstRef -import org.jacodb.api.cfg.JcInstanceOfExpr -import org.jacodb.api.cfg.JcInt -import org.jacodb.api.cfg.JcLambdaExpr -import org.jacodb.api.cfg.JcLeExpr -import org.jacodb.api.cfg.JcLengthExpr -import org.jacodb.api.cfg.JcLocalVar -import org.jacodb.api.cfg.JcLong -import org.jacodb.api.cfg.JcLtExpr -import org.jacodb.api.cfg.JcMethodConstant -import org.jacodb.api.cfg.JcMethodType -import org.jacodb.api.cfg.JcMulExpr -import org.jacodb.api.cfg.JcNegExpr -import org.jacodb.api.cfg.JcNeqExpr -import org.jacodb.api.cfg.JcNewArrayExpr -import org.jacodb.api.cfg.JcNewExpr -import org.jacodb.api.cfg.JcNullConstant -import org.jacodb.api.cfg.JcOrExpr -import org.jacodb.api.cfg.JcRawAddExpr -import org.jacodb.api.cfg.JcRawAndExpr -import org.jacodb.api.cfg.JcRawArgument -import org.jacodb.api.cfg.JcRawArrayAccess -import org.jacodb.api.cfg.JcRawAssignInst -import org.jacodb.api.cfg.JcRawBinaryExpr -import org.jacodb.api.cfg.JcRawBool -import org.jacodb.api.cfg.JcRawByte -import org.jacodb.api.cfg.JcRawCallInst -import org.jacodb.api.cfg.JcRawCastExpr -import org.jacodb.api.cfg.JcRawCatchInst -import org.jacodb.api.cfg.JcRawChar -import org.jacodb.api.cfg.JcRawClassConstant -import org.jacodb.api.cfg.JcRawCmpExpr -import org.jacodb.api.cfg.JcRawCmpgExpr -import org.jacodb.api.cfg.JcRawCmplExpr -import org.jacodb.api.cfg.JcRawDivExpr -import org.jacodb.api.cfg.JcRawDouble -import org.jacodb.api.cfg.JcRawDynamicCallExpr -import org.jacodb.api.cfg.JcRawEnterMonitorInst -import org.jacodb.api.cfg.JcRawEqExpr -import org.jacodb.api.cfg.JcRawExitMonitorInst -import org.jacodb.api.cfg.JcRawExprVisitor -import org.jacodb.api.cfg.JcRawFieldRef -import org.jacodb.api.cfg.JcRawFloat -import org.jacodb.api.cfg.JcRawGeExpr -import org.jacodb.api.cfg.JcRawGotoInst -import org.jacodb.api.cfg.JcRawGtExpr -import org.jacodb.api.cfg.JcRawIfInst -import org.jacodb.api.cfg.JcRawInst -import org.jacodb.api.cfg.JcRawInstVisitor -import org.jacodb.api.cfg.JcRawInstanceOfExpr -import org.jacodb.api.cfg.JcRawInt -import org.jacodb.api.cfg.JcRawInterfaceCallExpr -import org.jacodb.api.cfg.JcRawLabelInst -import org.jacodb.api.cfg.JcRawLabelRef -import org.jacodb.api.cfg.JcRawLeExpr -import org.jacodb.api.cfg.JcRawLengthExpr -import org.jacodb.api.cfg.JcRawLineNumberInst -import org.jacodb.api.cfg.JcRawLocalVar -import org.jacodb.api.cfg.JcRawLong -import org.jacodb.api.cfg.JcRawLtExpr -import org.jacodb.api.cfg.JcRawMethodConstant -import org.jacodb.api.cfg.JcRawMethodType -import org.jacodb.api.cfg.JcRawMulExpr -import org.jacodb.api.cfg.JcRawNegExpr -import org.jacodb.api.cfg.JcRawNeqExpr -import org.jacodb.api.cfg.JcRawNewArrayExpr -import org.jacodb.api.cfg.JcRawNewExpr -import org.jacodb.api.cfg.JcRawNullConstant -import org.jacodb.api.cfg.JcRawOrExpr -import org.jacodb.api.cfg.JcRawRemExpr -import org.jacodb.api.cfg.JcRawReturnInst -import org.jacodb.api.cfg.JcRawShlExpr -import org.jacodb.api.cfg.JcRawShort -import org.jacodb.api.cfg.JcRawShrExpr -import org.jacodb.api.cfg.JcRawSpecialCallExpr -import org.jacodb.api.cfg.JcRawStaticCallExpr -import org.jacodb.api.cfg.JcRawStringConstant -import org.jacodb.api.cfg.JcRawSubExpr -import org.jacodb.api.cfg.JcRawSwitchInst -import org.jacodb.api.cfg.JcRawThis -import org.jacodb.api.cfg.JcRawThrowInst -import org.jacodb.api.cfg.JcRawUshrExpr -import org.jacodb.api.cfg.JcRawVirtualCallExpr -import org.jacodb.api.cfg.JcRawXorExpr -import org.jacodb.api.cfg.JcRemExpr -import org.jacodb.api.cfg.JcReturnInst -import org.jacodb.api.cfg.JcShlExpr -import org.jacodb.api.cfg.JcShort -import org.jacodb.api.cfg.JcShrExpr -import org.jacodb.api.cfg.JcSpecialCallExpr -import org.jacodb.api.cfg.JcStaticCallExpr -import org.jacodb.api.cfg.JcStringConstant -import org.jacodb.api.cfg.JcSubExpr -import org.jacodb.api.cfg.JcSwitchInst -import org.jacodb.api.cfg.JcThis -import org.jacodb.api.cfg.JcThrowInst -import org.jacodb.api.cfg.JcUshrExpr -import org.jacodb.api.cfg.JcValue -import org.jacodb.api.cfg.JcVirtualCallExpr -import org.jacodb.api.cfg.JcXorExpr -import org.jacodb.api.ext.boolean -import org.jacodb.api.ext.byte -import org.jacodb.api.ext.char -import org.jacodb.api.ext.double -import org.jacodb.api.ext.findTypeOrNull -import org.jacodb.api.ext.float -import org.jacodb.api.ext.int -import org.jacodb.api.ext.long -import org.jacodb.api.ext.objectType -import org.jacodb.api.ext.short -import org.jacodb.api.ext.toType +import org.jacodb.api.* +import org.jacodb.api.cfg.* +import org.jacodb.api.ext.* import org.jacodb.impl.cfg.util.UNINIT_THIS import org.jacodb.impl.cfg.util.lambdaMetaFactory import org.jacodb.impl.cfg.util.lambdaMetaFactoryMethodName /** This class stores state and is NOT THREAD SAFE. Use it carefully */ -class JcInstListBuilder( - val method: JcMethod, - val instList: JcInstList, -) : JcRawInstVisitor, - JcRawExprVisitor { +class JcInstListBuilder(val method: JcMethod,val instList: JcInstList) : JcRawInstVisitor, JcRawExprVisitor { val classpath: JcClasspath = method.enclosingClass.classpath @@ -396,8 +243,9 @@ class JcInstListBuilder( override fun visitJcRawXorExpr(expr: JcRawXorExpr): JcExpr = convertBinary(expr) { type, lhv, rhv -> JcXorExpr(type, lhv, rhv) } - override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): JcExpr = - JcLengthExpr(classpath.int, expr.array.accept(this) as JcValue) + override fun visitJcRawLengthExpr(expr: JcRawLengthExpr): JcExpr { + return JcLengthExpr(classpath.int, expr.array.accept(this) as JcValue) + } override fun visitJcRawNegExpr(expr: JcRawNegExpr): JcExpr = JcNegExpr(expr.typeName.asType(), expr.operand.accept(this) as JcValue) @@ -549,4 +397,4 @@ class JcInstListBuilder( value.typeName.asType() ) } -} +} \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt index 878e4a19a..cc7a4b5d2 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt @@ -23,21 +23,22 @@ import org.jacodb.api.cfg.JcRawInstVisitor import org.jacodb.api.cfg.JcRawLabelInst open class JcInstListImpl( - instructions: List, + instructions: List ) : Iterable, JcInstList { - protected val _instructions: MutableList = instructions.toMutableList() - override val instructions: List get() = _instructions + protected val _instructions = instructions.toMutableList() - override val size: Int get() = instructions.size - override val indices: IntRange get() = instructions.indices - override val lastIndex: Int get() = instructions.lastIndex + override val instructions: List get() = _instructions - override operator fun get(index: Int): INST = instructions[index] - override fun getOrNull(index: Int): INST? = instructions.getOrNull(index) + override val size get() = instructions.size + override val indices get() = instructions.indices + override val lastIndex get() = instructions.lastIndex + override operator fun get(index: Int) = instructions[index] + override fun getOrNull(index: Int) = instructions.getOrNull(index) + fun getOrElse(index: Int, defaultValue: (Int) -> INST) = instructions.getOrElse(index, defaultValue) override fun iterator(): Iterator = instructions.iterator() - override fun toMutableList(): JcMutableInstList = JcMutableInstListImpl(_instructions) + override fun toMutableList() = JcMutableInstListImpl(_instructions) override fun toString(): String = _instructions.joinToString(separator = "\n") { when (it) { @@ -47,8 +48,8 @@ open class JcInstListImpl( } } -class JcMutableInstListImpl(instructions: List) : - JcInstListImpl(instructions), JcMutableInstList { +class JcMutableInstListImpl(instructions: List) : JcInstListImpl(instructions), + JcMutableInstList { override fun insertBefore(inst: INST, vararg newInstructions: INST) = insertBefore(inst, newInstructions.toList()) override fun insertBefore(inst: INST, newInstructions: Collection) { @@ -73,17 +74,18 @@ class JcMutableInstListImpl(instructions: List) : } } -fun JcInstList.filter(visitor: JcRawInstVisitor): JcInstList = + +fun JcInstList.filter(visitor: JcRawInstVisitor) = JcInstListImpl(instructions.filter { it.accept(visitor) }) -fun JcInstList.filterNot(visitor: JcRawInstVisitor): JcInstList = +fun JcInstList.filterNot(visitor: JcRawInstVisitor) = JcInstListImpl(instructions.filterNot { it.accept(visitor) }) -fun JcInstList.map(visitor: JcRawInstVisitor): JcInstList = +fun JcInstList.map(visitor: JcRawInstVisitor) = JcInstListImpl(instructions.map { it.accept(visitor) }) -fun JcInstList.mapNotNull(visitor: JcRawInstVisitor): JcInstList = +fun JcInstList.mapNotNull(visitor: JcRawInstVisitor) = JcInstListImpl(instructions.mapNotNull { it.accept(visitor) }) -fun JcInstList.flatMap(visitor: JcRawInstVisitor>): JcInstList = +fun JcInstList.flatMap(visitor: JcRawInstVisitor>) = JcInstListImpl(instructions.flatMap { it.accept(visitor) }) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt index ca1217950..d0c8137a5 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt @@ -20,115 +20,14 @@ import org.jacodb.api.JcMethod import org.jacodb.api.JcParameter import org.jacodb.api.PredefinedPrimitives import org.jacodb.api.TypeName -import org.jacodb.api.cfg.BsmDoubleArg -import org.jacodb.api.cfg.BsmFloatArg -import org.jacodb.api.cfg.BsmHandle -import org.jacodb.api.cfg.BsmIntArg -import org.jacodb.api.cfg.BsmLongArg -import org.jacodb.api.cfg.BsmMethodTypeArg -import org.jacodb.api.cfg.BsmStringArg -import org.jacodb.api.cfg.BsmTypeArg -import org.jacodb.api.cfg.JcInstList -import org.jacodb.api.cfg.JcRawAddExpr -import org.jacodb.api.cfg.JcRawAndExpr -import org.jacodb.api.cfg.JcRawArgument -import org.jacodb.api.cfg.JcRawArrayAccess -import org.jacodb.api.cfg.JcRawAssignInst -import org.jacodb.api.cfg.JcRawBool -import org.jacodb.api.cfg.JcRawByte -import org.jacodb.api.cfg.JcRawCallExpr -import org.jacodb.api.cfg.JcRawCallInst -import org.jacodb.api.cfg.JcRawCastExpr -import org.jacodb.api.cfg.JcRawCatchInst -import org.jacodb.api.cfg.JcRawChar -import org.jacodb.api.cfg.JcRawClassConstant -import org.jacodb.api.cfg.JcRawCmpExpr -import org.jacodb.api.cfg.JcRawCmpgExpr -import org.jacodb.api.cfg.JcRawCmplExpr -import org.jacodb.api.cfg.JcRawComplexValue -import org.jacodb.api.cfg.JcRawDivExpr -import org.jacodb.api.cfg.JcRawDouble -import org.jacodb.api.cfg.JcRawDynamicCallExpr -import org.jacodb.api.cfg.JcRawEnterMonitorInst -import org.jacodb.api.cfg.JcRawEqExpr -import org.jacodb.api.cfg.JcRawExitMonitorInst -import org.jacodb.api.cfg.JcRawExpr -import org.jacodb.api.cfg.JcRawExprVisitor -import org.jacodb.api.cfg.JcRawFieldRef -import org.jacodb.api.cfg.JcRawFloat -import org.jacodb.api.cfg.JcRawGeExpr -import org.jacodb.api.cfg.JcRawGotoInst -import org.jacodb.api.cfg.JcRawGtExpr -import org.jacodb.api.cfg.JcRawIfInst -import org.jacodb.api.cfg.JcRawInst -import org.jacodb.api.cfg.JcRawInstVisitor -import org.jacodb.api.cfg.JcRawInstanceOfExpr -import org.jacodb.api.cfg.JcRawInt -import org.jacodb.api.cfg.JcRawInterfaceCallExpr -import org.jacodb.api.cfg.JcRawLabelInst -import org.jacodb.api.cfg.JcRawLabelRef -import org.jacodb.api.cfg.JcRawLeExpr -import org.jacodb.api.cfg.JcRawLengthExpr -import org.jacodb.api.cfg.JcRawLineNumberInst -import org.jacodb.api.cfg.JcRawLocalVar -import org.jacodb.api.cfg.JcRawLong -import org.jacodb.api.cfg.JcRawLtExpr -import org.jacodb.api.cfg.JcRawMethodConstant -import org.jacodb.api.cfg.JcRawMethodType -import org.jacodb.api.cfg.JcRawMulExpr -import org.jacodb.api.cfg.JcRawNegExpr -import org.jacodb.api.cfg.JcRawNeqExpr -import org.jacodb.api.cfg.JcRawNewArrayExpr -import org.jacodb.api.cfg.JcRawNewExpr -import org.jacodb.api.cfg.JcRawNullConstant -import org.jacodb.api.cfg.JcRawOrExpr -import org.jacodb.api.cfg.JcRawRemExpr -import org.jacodb.api.cfg.JcRawReturnInst -import org.jacodb.api.cfg.JcRawShlExpr -import org.jacodb.api.cfg.JcRawShort -import org.jacodb.api.cfg.JcRawShrExpr -import org.jacodb.api.cfg.JcRawSpecialCallExpr -import org.jacodb.api.cfg.JcRawStaticCallExpr -import org.jacodb.api.cfg.JcRawStringConstant -import org.jacodb.api.cfg.JcRawSubExpr -import org.jacodb.api.cfg.JcRawSwitchInst -import org.jacodb.api.cfg.JcRawThis -import org.jacodb.api.cfg.JcRawThrowInst -import org.jacodb.api.cfg.JcRawUshrExpr -import org.jacodb.api.cfg.JcRawValue -import org.jacodb.api.cfg.JcRawVirtualCallExpr -import org.jacodb.api.cfg.JcRawXorExpr -import org.jacodb.impl.cfg.util.elementType -import org.jacodb.impl.cfg.util.internalDesc -import org.jacodb.impl.cfg.util.isDWord -import org.jacodb.impl.cfg.util.isPrimitive -import org.jacodb.impl.cfg.util.jvmClassName -import org.jacodb.impl.cfg.util.jvmTypeName -import org.jacodb.impl.cfg.util.typeName +import org.jacodb.api.cfg.* +import org.jacodb.impl.cfg.util.* import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes.H_GETSTATIC import org.objectweb.asm.Type -import org.objectweb.asm.tree.AbstractInsnNode -import org.objectweb.asm.tree.FieldInsnNode -import org.objectweb.asm.tree.InsnList -import org.objectweb.asm.tree.InsnNode -import org.objectweb.asm.tree.IntInsnNode -import org.objectweb.asm.tree.InvokeDynamicInsnNode -import org.objectweb.asm.tree.JumpInsnNode -import org.objectweb.asm.tree.LabelNode -import org.objectweb.asm.tree.LdcInsnNode -import org.objectweb.asm.tree.LineNumberNode -import org.objectweb.asm.tree.LocalVariableNode -import org.objectweb.asm.tree.LookupSwitchInsnNode -import org.objectweb.asm.tree.MethodInsnNode -import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.MultiANewArrayInsnNode -import org.objectweb.asm.tree.ParameterNode -import org.objectweb.asm.tree.TableSwitchInsnNode -import org.objectweb.asm.tree.TryCatchBlockNode -import org.objectweb.asm.tree.TypeInsnNode -import org.objectweb.asm.tree.VarInsnNode +import org.objectweb.asm.tree.* +import java.util.ArrayList private val PredefinedPrimitives.smallIntegers get() = setOf(Boolean, Byte, Char, Short, Int) @@ -168,7 +67,7 @@ private val TypeName.typeInt class MethodNodeBuilder( val method: JcMethod, - val instList: JcInstList, + val instList: JcInstList ) : JcRawInstVisitor, JcRawExprVisitor { private var localIndex = 0 private var stackSize = 0 @@ -199,7 +98,7 @@ class MethodNodeBuilder( mn.tryCatchBlocks = tryCatchNodeList mn.maxLocals = localIndex mn.maxStack = maxStack + 1 - // At this moment, we're just copying annotations from original method without any modifications + //At this moment, we're just copying annotations from original method without any modifications with(method.asmNode()) { mn.visibleAnnotations = visibleAnnotations mn.visibleTypeAnnotations = visibleTypeAnnotations @@ -698,6 +597,7 @@ class MethodNodeBuilder( currentInsnList.add(TypeInsnNode(Opcodes.INSTANCEOF, expr.targetType.internalDesc)) } + private val BsmHandle.asAsmHandle: Handle get() = Handle( tag, @@ -873,7 +773,7 @@ class MethodNodeBuilder( override fun visitJcRawInt(value: JcRawInt) { currentInsnList.add( - when (value.value) { + when (value.value as Comparable) { in -1..5 -> InsnNode(Opcodes.ICONST_0 + value.value) in Byte.MIN_VALUE..Byte.MAX_VALUE -> IntInsnNode(Opcodes.BIPUSH, value.value) in Short.MIN_VALUE..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, value.value) @@ -895,16 +795,10 @@ class MethodNodeBuilder( override fun visitJcRawFloat(value: JcRawFloat) { currentInsnList.add( - // Note: The case to Any forces Float to box, which is necessary here - // since 0.0 == -0.0`, but `Box(0.0) != Box(-0.0)`. - // The latter is preferred here because we only want to - // "push constant 0.0f onto the stack" (`fconst_0` instruction) - // when `value.value` is exactly "zero" (0.0), NOT "negative zero" (-0.0). - @Suppress("USELESS_CAST") - when (value.value as Any) { - 0.0f -> InsnNode(Opcodes.FCONST_0) - 1.0f -> InsnNode(Opcodes.FCONST_1) - 2.0f -> InsnNode(Opcodes.FCONST_2) + when (value.value as Comparable) { + 0.0F -> InsnNode(Opcodes.FCONST_0) + 1.0F -> InsnNode(Opcodes.FCONST_1) + 2.0F -> InsnNode(Opcodes.FCONST_2) else -> LdcInsnNode(value.value) } ) @@ -913,13 +807,7 @@ class MethodNodeBuilder( override fun visitJcRawDouble(value: JcRawDouble) { currentInsnList.add( - // Note: The case to Any forces Double to box, which is necessary here - // since 0.0 == -0.0`, but `Box(0.0) != Box(-0.0)`. - // The latter is preferred here because we only want to - // "push constant 0.0d onto the stack" (`dconst_0` instruction) - // when `value.value` is exactly "zero" (0.0), NOT "negative zero" (-0.0). - @Suppress("USELESS_CAST") - when (value.value as Any) { + when (value.value as Comparable) { 0.0 -> InsnNode(Opcodes.DCONST_0) 1.0 -> InsnNode(Opcodes.DCONST_1) else -> LdcInsnNode(value.value) @@ -951,7 +839,7 @@ class MethodNodeBuilder( error("Could not load method constant $value") } - // We have to insert NOP instructions in empty basic blocks to handle situations with empty handlers of try/catch + //We have to insert NOP instructions in empty basic blocks to handle situations with empty handlers of try/catch private fun insertNopInstructions() { val firstLabelIndex = currentInsnList.indexOfFirst { it is LabelNode } val nodesBetweenLabels = mutableListOf() diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt index ef581fd3e..ad6b23165 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/TypedMethodRefImpl.kt @@ -16,21 +16,8 @@ package org.jacodb.impl.cfg -import org.jacodb.api.JcClassType -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcMethod -import org.jacodb.api.JcType -import org.jacodb.api.JcTypedMethod -import org.jacodb.api.MethodNotFoundException -import org.jacodb.api.TypeName -import org.jacodb.api.cfg.JcInstLocation -import org.jacodb.api.cfg.JcRawCallExpr -import org.jacodb.api.cfg.JcRawInstanceExpr -import org.jacodb.api.cfg.JcRawLocal -import org.jacodb.api.cfg.JcRawSpecialCallExpr -import org.jacodb.api.cfg.JcRawStaticCallExpr -import org.jacodb.api.cfg.TypedMethodRef -import org.jacodb.api.cfg.VirtualTypedMethodRef +import org.jacodb.api.* +import org.jacodb.api.cfg.* import org.jacodb.api.ext.findType import org.jacodb.api.ext.jvmName import org.jacodb.impl.cfg.util.typeName @@ -39,10 +26,10 @@ import org.jacodb.impl.weakLazy import org.objectweb.asm.Type abstract class MethodSignatureRef( - val type: JcClassType, - override val name: String, - argTypes: List, - returnType: TypeName, + val type: JcClassType, + override val name: String, + argTypes: List, + returnType: TypeName, ) : TypedMethodRef { protected val description: String = buildString { @@ -96,17 +83,17 @@ abstract class MethodSignatureRef( } class TypedStaticMethodRefImpl( - type: JcClassType, - name: String, - argTypes: List, - returnType: TypeName, + type: JcClassType, + name: String, + argTypes: List, + returnType: TypeName ) : MethodSignatureRef(type, name, argTypes, returnType) { constructor(classpath: JcClasspath, raw: JcRawStaticCallExpr) : this( - classpath.findType(raw.declaringClass.typeName) as JcClassType, - raw.methodName, - raw.argumentTypes, - raw.returnType + classpath.findType(raw.declaringClass.typeName) as JcClassType, + raw.methodName, + raw.argumentTypes, + raw.returnType ) override val method: JcTypedMethod by weakLazy { @@ -115,17 +102,17 @@ class TypedStaticMethodRefImpl( } class TypedSpecialMethodRefImpl( - type: JcClassType, - name: String, - argTypes: List, - returnType: TypeName, + type: JcClassType, + name: String, + argTypes: List, + returnType: TypeName ) : MethodSignatureRef(type, name, argTypes, returnType) { constructor(classpath: JcClasspath, raw: JcRawSpecialCallExpr) : this( - classpath.findType(raw.declaringClass.typeName) as JcClassType, - raw.methodName, - raw.argumentTypes, - raw.returnType + classpath.findType(raw.declaringClass.typeName) as JcClassType, + raw.methodName, + raw.argumentTypes, + raw.returnType ) override val method: JcTypedMethod by weakLazy { @@ -135,11 +122,11 @@ class TypedSpecialMethodRefImpl( } class VirtualMethodRefImpl( - type: JcClassType, - private val actualType: JcClassType, - name: String, - argTypes: List, - returnType: TypeName, + type: JcClassType, + private val actualType: JcClassType, + name: String, + argTypes: List, + returnType: TypeName ) : MethodSignatureRef(type, name, argTypes, returnType), VirtualTypedMethodRef { companion object { @@ -160,20 +147,20 @@ class VirtualMethodRefImpl( fun of(classpath: JcClasspath, raw: JcRawCallExpr): VirtualMethodRefImpl { val (declared, actual) = raw.resolvedType(classpath) return VirtualMethodRefImpl( - declared, - actual, - raw.methodName, - raw.argumentTypes, - raw.returnType + declared, + actual, + raw.methodName, + raw.argumentTypes, + raw.returnType ) } fun of(type: JcClassType, method: JcTypedMethod): VirtualMethodRefImpl { return VirtualMethodRefImpl( - type, type, - method.name, - method.method.parameters.map { it.type }, - method.method.returnType + type, type, + method.name, + method.method.parameters.map { it.type }, + method.method.returnType ) } } @@ -187,18 +174,19 @@ class VirtualMethodRefImpl( } } + class TypedMethodRefImpl( - type: JcClassType, - name: String, - argTypes: List, - returnType: TypeName, + type: JcClassType, + name: String, + argTypes: List, + returnType: TypeName ) : MethodSignatureRef(type, name, argTypes, returnType) { constructor(classpath: JcClasspath, raw: JcRawCallExpr) : this( - classpath.findType(raw.declaringClass.typeName) as JcClassType, - raw.methodName, - raw.argumentTypes, - raw.returnType + classpath.findType(raw.declaringClass.typeName) as JcClassType, + raw.methodName, + raw.argumentTypes, + raw.returnType ) override val method: JcTypedMethod by softLazy { @@ -217,17 +205,17 @@ fun JcClasspath.methodRef(expr: JcRawCallExpr): TypedMethodRef { fun JcTypedMethod.methodRef(): TypedMethodRef { return TypedMethodRefImpl( - enclosingType as JcClassType, - method.name, - method.parameters.map { it.type }, - method.returnType + enclosingType as JcClassType, + method.name, + method.parameters.map { it.type }, + method.returnType ) } class JcInstLocationImpl( - override val method: JcMethod, - override val index: Int, - override val lineNumber: Int, + override val method: JcMethod, + override val index: Int, + override val lineNumber: Int ) : JcInstLocation { override fun toString(): String { @@ -250,4 +238,5 @@ class JcInstLocationImpl( return result } -} + +} \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt index 3cdd61bba..a47e16596 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/util/InstructionFilter.kt @@ -16,13 +16,10 @@ package org.jacodb.impl.cfg.util +import org.jacodb.api.cfg.DefaultJcRawInstVisitor import org.jacodb.api.cfg.JcRawInst -import org.jacodb.api.cfg.JcRawInstVisitor -class InstructionFilter( - val predicate: (JcRawInst) -> Boolean, -) : JcRawInstVisitor.Default { - override fun defaultVisitJcRawInst(inst: JcRawInst): Boolean { - return predicate(inst) - } +class InstructionFilter(val predicate: (JcRawInst) -> Boolean) : DefaultJcRawInstVisitor { + override val defaultInstHandler: (JcRawInst) -> Boolean + get() = { predicate(it) } } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt index 12dc782bd..03e2d2d85 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/JcUnknownType.kt @@ -23,11 +23,7 @@ import org.jacodb.impl.types.TypeNameImpl import org.objectweb.asm.Opcodes -class JcUnknownType( - override var classpath: JcClasspath, - private val name: String, - private val location: VirtualLocation -) : JcClassType { +class JcUnknownType(override var classpath: JcClasspath, private val name: String, private val location: VirtualLocation) : JcClassType { override val lookup: JcLookup = JcUnknownTypeLookup(this) @@ -95,4 +91,4 @@ open class JcUnknownTypeLookup(val type: JcClassType) : JcLookup { - // FIXME: does not compile because of some kinda compiler bug - // (see https://youtrack.jetbrains.com/issue/KT-15964) - // private static class ArgumentResolver extends TypedExprResolver { - // - // @Override - // public void ifMatches(JcExpr jcExpr) { - // if (jcExpr instanceof JcArgument) { - // getResult().add((JcArgument) jcExpr); - // } - // } - // - // } + @Override + public void ifMatches(@NotNull JcExpr jcExpr) { + if (jcExpr instanceof JcArgument) { + getResult().add((JcArgument) jcExpr); + } + } + + } public static void cacheSettings() { new JcCacheSettings().types(10, Duration.of(1, ChronoUnit.MINUTES)); diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt index be36d9d91..a092b7fd5 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt @@ -16,30 +16,9 @@ package org.jacodb.testing.cfg -import org.jacodb.api.JavaVersion -import org.jacodb.api.JcClassType -import org.jacodb.api.JcMethod -import org.jacodb.api.JcTypedMethod -import org.jacodb.api.TypeName -import org.jacodb.api.cfg.JcAssignInst -import org.jacodb.api.cfg.JcCallExpr -import org.jacodb.api.cfg.JcCallInst -import org.jacodb.api.cfg.JcCatchInst -import org.jacodb.api.cfg.JcEnterMonitorInst -import org.jacodb.api.cfg.JcExitMonitorInst -import org.jacodb.api.cfg.JcExpr -import org.jacodb.api.cfg.JcExprVisitor -import org.jacodb.api.cfg.JcGotoInst -import org.jacodb.api.cfg.JcGraph -import org.jacodb.api.cfg.JcIfInst -import org.jacodb.api.cfg.JcInst -import org.jacodb.api.cfg.JcInstVisitor -import org.jacodb.api.cfg.JcReturnInst -import org.jacodb.api.cfg.JcSpecialCallExpr -import org.jacodb.api.cfg.JcSwitchInst -import org.jacodb.api.cfg.JcTerminatingInst -import org.jacodb.api.cfg.JcThrowInst -import org.jacodb.api.cfg.JcVirtualCallExpr + +import org.jacodb.api.* +import org.jacodb.api.cfg.* import org.jacodb.api.ext.HierarchyExtension import org.jacodb.api.ext.findClass import org.jacodb.api.ext.toType @@ -55,42 +34,27 @@ import org.jacodb.impl.cfg.util.ExprMapper import org.jacodb.impl.features.classpaths.ClasspathCache import org.jacodb.impl.features.classpaths.StringConcatSimplifier import org.jacodb.impl.fs.JarLocation -import org.jacodb.testing.WithDB -import org.jacodb.testing.asmLib -import org.jacodb.testing.guavaLib -import org.jacodb.testing.kotlinStdLib -import org.jacodb.testing.kotlinxCoroutines +import org.jacodb.testing.* import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import java.io.File class OverridesResolver( - private val hierarchyExtension: HierarchyExtension, -) : JcExprVisitor.Default>, - JcInstVisitor.Default> { - - override fun defaultVisitJcExpr(expr: JcExpr): Sequence { - return emptySequence() - } - - override fun defaultVisitJcInst(inst: JcInst): Sequence { - return emptySequence() - } - - private fun JcClassType.getMethod( - name: String, - argTypes: List, - returnType: TypeName, - ): JcTypedMethod { + private val hierarchyExtension: HierarchyExtension +) : DefaultJcInstVisitor>, DefaultJcExprVisitor> { + override val defaultInstHandler: (JcInst) -> Sequence + get() = { emptySequence() } + override val defaultExprHandler: (JcExpr) -> Sequence + get() = { emptySequence() } + + private fun JcClassType.getMethod(name: String, argTypes: List, returnType: TypeName): JcTypedMethod { return methods.firstOrNull { typedMethod -> val jcMethod = typedMethod.method - jcMethod.name == name - && jcMethod.returnType.typeName == returnType.typeName - && jcMethod.parameters.map { param -> param.type.typeName } == argTypes.map { it.typeName } + jcMethod.name == name && + jcMethod.returnType.typeName == returnType.typeName && + jcMethod.parameters.map { param -> param.type.typeName } == argTypes.map { it.typeName } } ?: error("Could not find a method with correct signature") } @@ -100,14 +64,6 @@ class OverridesResolver( return klass.getMethod(name, parameters.map { it.type }, returnType) } - override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): Sequence { - return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } - } - - override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): Sequence { - return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } - } - override fun visitJcAssignInst(inst: JcAssignInst): Sequence { if (inst.rhv is JcCallExpr) return inst.rhv.accept(this) return emptySequence() @@ -117,20 +73,26 @@ class OverridesResolver( return inst.callExpr.accept(this) } -} + override fun visitJcVirtualCallExpr(expr: JcVirtualCallExpr): Sequence { + return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } + } + + override fun visitJcSpecialCallExpr(expr: JcSpecialCallExpr): Sequence { + return hierarchyExtension.findOverrides(expr.method.method).map { it.typedMethod } + } -class JcGraphChecker( - val method: JcMethod, - val jcGraph: JcGraph, -) : JcInstVisitor { +} +class JcGraphChecker(val method: JcMethod, val jcGraph: JcGraph) : JcInstVisitor { fun check() { try { jcGraph.entry } catch (e: Exception) { println( "Fail on method ${method.enclosingClass.simpleName}#${method.name}(${ - method.parameters.joinToString(",") { it.type.typeName } + method.parameters.joinToString( + "," + ) { it.type.typeName } })" ) throw e @@ -167,10 +129,6 @@ class JcGraphChecker( } } - override fun visitExternalJcInst(inst: JcInst) { - // Do nothing - } - override fun visitJcAssignInst(inst: JcAssignInst) { if (inst != jcGraph.entry) { assertTrue(jcGraph.predecessors(inst).isNotEmpty()) @@ -288,6 +246,8 @@ class JcGraphChecker( assertTrue(jcGraph.throwers(inst).isEmpty()) } + override fun visitExternalJcInst(inst: JcInst) { + } } class IRTest : BaseInstructionsTest() { @@ -335,6 +295,7 @@ class IRTest : BaseInstructionsTest() { testClass(cp.findClass()) } + @Test fun `get ir of guava`() { runAlongLib(guavaLib) @@ -355,6 +316,7 @@ class IRTest : BaseInstructionsTest() { runAlongLib(kotlinStdLib, false) } + @AfterEach fun printStats() { cp.features!!.filterIsInstance().forEach { diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java deleted file mode 100644 index e42fe9e08..000000000 --- a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/LocalResolverExample.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022 UnitTestBot contributors (utbot.org) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jacodb.testing.cfg; - -public class LocalResolverExample { -} diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt index 0de1b555f..5e91288d9 100644 --- a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt @@ -82,6 +82,7 @@ open class WithDB(vararg features: Any) : JcDatabaseHolder { override var db = runBlocking { jacodb { + // persistent("D:\\work\\jacodb\\jcdb-index.db") loadByteCode(allClasspath) useProcessJavaRuntime() keepLocalVariableNames() @@ -142,4 +143,4 @@ open class WithRestoredDB(vararg features: JcFeature<*, *>) : WithDB(*features) } } -} +} \ No newline at end of file diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt index ef9d35e83..b5b625608 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/Position.kt @@ -26,16 +26,16 @@ fun interface PositionResolver { @Serializable sealed interface Position +@Serializable +@SerialName("Argument") +data class Argument(@SerialName("number") val index: Int) : Position + @Serializable @SerialName("AnyArgument") object AnyArgument : Position { override fun toString(): String = javaClass.simpleName } -@Serializable -@SerialName("Argument") -data class Argument(@SerialName("number") val index: Int) : Position - @Serializable @SerialName("This") object This : Position { diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt index 315dd6f59..1327c441a 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/SerializedTaintConfigurationItem.kt @@ -22,7 +22,6 @@ import kotlinx.serialization.Serializable @Serializable sealed interface SerializedTaintConfigurationItem { - @SerialName("methodInfo") val methodInfo: FunctionMatcher fun updateMethodInfo(updatedMethodInfo: FunctionMatcher): SerializedTaintConfigurationItem = @@ -38,40 +37,40 @@ sealed interface SerializedTaintConfigurationItem { @Serializable @SerialName("EntryPointSource") data class SerializedTaintEntryPointSource( - @SerialName("methodInfo") override val methodInfo: FunctionMatcher, - @SerialName("condition") val condition: Condition, - @SerialName("actionsAfter") val actionsAfter: List, + override val methodInfo: FunctionMatcher, + val condition: Condition, + val actionsAfter: List, ) : SerializedTaintConfigurationItem @Serializable @SerialName("MethodSource") data class SerializedTaintMethodSource( - @SerialName("methodInfo") override val methodInfo: FunctionMatcher, - @SerialName("condition") val condition: Condition, - @SerialName("actionsAfter") val actionsAfter: List, + override val methodInfo: FunctionMatcher, + val condition: Condition, + val actionsAfter: List, ) : SerializedTaintConfigurationItem @Serializable @SerialName("MethodSink") data class SerializedTaintMethodSink( - @SerialName("ruleNote") val ruleNote: String, - @SerialName("cwe") val cwe: List, - @SerialName("methodInfo") override val methodInfo: FunctionMatcher, - @SerialName("condition") val condition: Condition, + val ruleNote: String, + val cwe: List, + override val methodInfo: FunctionMatcher, + val condition: Condition ) : SerializedTaintConfigurationItem @Serializable @SerialName("PassThrough") data class SerializedTaintPassThrough( - @SerialName("methodInfo") override val methodInfo: FunctionMatcher, - @SerialName("condition") val condition: Condition, - @SerialName("actionsAfter") val actionsAfter: List, + override val methodInfo: FunctionMatcher, + val condition: Condition, + val actionsAfter: List, ) : SerializedTaintConfigurationItem @Serializable @SerialName("Cleaner") data class SerializedTaintCleaner( - @SerialName("methodInfo") override val methodInfo: FunctionMatcher, - @SerialName("condition") val condition: Condition, - @SerialName("actionsAfter") val actionsAfter: List, + override val methodInfo: FunctionMatcher, + val condition: Condition, + val actionsAfter: List, ) : SerializedTaintConfigurationItem diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt index 6d1937fd4..9b9506958 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintAction.kt @@ -28,8 +28,6 @@ interface TaintActionVisitor { fun visit(action: AssignMark): R fun visit(action: RemoveAllMarks): R fun visit(action: RemoveMark): R - - fun visit(action: Action): R } interface Action { diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt index eb2a49566..e037856de 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintCondition.kt @@ -38,9 +38,6 @@ interface ConditionVisitor { fun visit(condition: SourceFunctionMatches): R fun visit(condition: ContainsMark): R fun visit(condition: TypeMatches): R - - // external type - fun visit(condition: Condition): R } interface Condition { @@ -85,7 +82,7 @@ data class Not( @Serializable @SerialName("And") data class And( - @SerialName("args") val args: List, + val args: List, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -93,7 +90,7 @@ data class And( @Serializable @SerialName("Or") data class Or( - @SerialName("args") val args: List, + val args: List, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -101,7 +98,7 @@ data class Or( @Serializable @SerialName("IsConstant") data class IsConstant( - @SerialName("position") val position: Position, + val position: Position, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -109,7 +106,7 @@ data class IsConstant( @Serializable @SerialName("IsType") data class IsType( - @SerialName("position") val position: Position, + val position: Position, @SerialName("type") val typeMatcher: TypeMatcher, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) @@ -118,7 +115,7 @@ data class IsType( @Serializable @SerialName("AnnotationType") data class AnnotationType( - @SerialName("position") val position: Position, + val position: Position, @SerialName("type") val typeMatcher: TypeMatcher, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) @@ -127,7 +124,7 @@ data class AnnotationType( @Serializable @SerialName("ConstantEq") data class ConstantEq( - @SerialName("position") val position: Position, + val position: Position, @SerialName("constant") val value: ConstantValue, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) @@ -136,7 +133,7 @@ data class ConstantEq( @Serializable @SerialName("ConstantLt") data class ConstantLt( - @SerialName("position") val position: Position, + val position: Position, @SerialName("constant") val value: ConstantValue, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) @@ -145,7 +142,7 @@ data class ConstantLt( @Serializable @SerialName("ConstantGt") data class ConstantGt( - @SerialName("position") val position: Position, + val position: Position, @SerialName("constant") val value: ConstantValue, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) @@ -154,8 +151,8 @@ data class ConstantGt( @Serializable @SerialName("ConstantMatches") data class ConstantMatches( - @SerialName("position") val position: Position, - @SerialName("pattern") val pattern: String, + val position: Position, + val pattern: String, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -163,7 +160,7 @@ data class ConstantMatches( @Serializable @SerialName("SourceFunctionMatches") data class SourceFunctionMatches( - @SerialName("position") val position: Position, + val position: Position, @SerialName("sourceFunction") val functionMatcher: FunctionMatcher, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) @@ -172,8 +169,8 @@ data class SourceFunctionMatches( @Serializable @SerialName("ContainsMark") data class ContainsMark( - @SerialName("position") val position: Position, - @SerialName("mark") val mark: TaintMark, + val position: Position, + val mark: TaintMark, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @@ -181,14 +178,13 @@ data class ContainsMark( @Serializable @SerialName("TypeMatches") data class TypeMatches( - @SerialName("position") val position: Position, - @SerialName("type") val type: JcType, + val position: Position, + val type: JcType, ) : Condition { override fun accept(conditionVisitor: ConditionVisitor): R = conditionVisitor.visit(this) } @Serializable -@SerialName("ConstantValue") sealed interface ConstantValue @Serializable @@ -204,19 +200,18 @@ data class ConstantBooleanValue(val value: Boolean) : ConstantValue data class ConstantStringValue(val value: String) : ConstantValue @Serializable -@SerialName("NameMatcher") sealed interface NameMatcher @Serializable @SerialName("NameIsEqualTo") data class NameExactMatcher( - @SerialName("name") val name: String, + val name: String, ) : NameMatcher @Serializable @SerialName("NameMatches") data class NamePatternMatcher( - @SerialName("pattern") val pattern: String, + val pattern: String, ) : NameMatcher @Serializable @@ -226,7 +221,6 @@ object AnyNameMatcher : NameMatcher { } @Serializable -@SerialName("TypeMatcher") sealed interface TypeMatcher @Serializable @@ -237,7 +231,7 @@ data class PrimitiveNameMatcher(val name: String) : TypeMatcher @SerialName("ClassMatcher") data class ClassMatcher( @SerialName("packageMatcher") val pkg: NameMatcher, - @SerialName("classNameMatcher") val classNameMatcher: NameMatcher, + val classNameMatcher: NameMatcher, ) : TypeMatcher @Serializable @@ -249,19 +243,19 @@ object AnyTypeMatcher : TypeMatcher { @Serializable @SerialName("FunctionMatches") data class FunctionMatcher( - @SerialName("cls") val cls: ClassMatcher, - @SerialName("functionName") val functionName: NameMatcher, - @SerialName("parametersMatchers") val parametersMatchers: List, - @SerialName("returnTypeMatcher") val returnTypeMatcher: TypeMatcher, - @SerialName("applyToOverrides") val applyToOverrides: Boolean, - @SerialName("functionLabel") val functionLabel: String?, - @SerialName("modifier") val modifier: Int, - @SerialName("exclude") val exclude: List, + val cls: ClassMatcher, + val functionName: NameMatcher, + val parametersMatchers: List, + val returnTypeMatcher: TypeMatcher, + val applyToOverrides: Boolean, + val functionLabel: String?, + val modifier: Int, + val exclude: List, ) @Serializable @SerialName("ParameterMatches") data class ParameterMatcher( - @SerialName("index") val index: Int, - @SerialName("typeMatcher") val typeMatcher: TypeMatcher, + val index: Int, + val typeMatcher: TypeMatcher, ) diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt index 5002db799..f653d3292 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt @@ -307,8 +307,6 @@ class TaintConfigurationFeature private constructor( override fun visit(action: RemoveMark): List = specializePosition(method, action.position).map { action.copy(position = it) } - - override fun visit(action: Action): List = error("Unexpected action $action") } private inner class ConditionSpecializer(val method: JcMethod) : ConditionVisitor { @@ -428,8 +426,6 @@ class TaintConfigurationFeature private constructor( override fun visit(condition: ConstantTrue): Condition = condition override fun visit(condition: TypeMatches): Condition = error("Must not occur here") - - override fun visit(condition: Condition): Condition = condition } private val conditionSimplifier = object : ConditionVisitor { @@ -490,8 +486,6 @@ class TaintConfigurationFeature private constructor( override fun visit(condition: ContainsMark): Condition = condition override fun visit(condition: ConstantTrue): Condition = condition override fun visit(condition: TypeMatches): Condition = condition - - override fun visit(condition: Condition): Condition = condition } companion object { diff --git a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt index 72aeeed60..52c2c7aed 100644 --- a/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt +++ b/jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintMark.kt @@ -16,11 +16,9 @@ package org.jacodb.taint.configuration -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -@SerialName("TaintMark") data class TaintMark(val name: String) { override fun toString(): String = name From 8cd08912f789b06460755872ccef521aa98f39df Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 12:42:14 +0300 Subject: [PATCH 101/117] Use any --- .../src/main/kotlin/org/jacodb/analysis/unused/Utils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt index 7a1657ffe..b21293831 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/Utils.kt @@ -30,7 +30,7 @@ import org.jacodb.api.cfg.values import org.jacodb.api.ext.cfg.callExpr internal fun AccessPath.isUsedAt(expr: JcExpr): Boolean { - return this in expr.values.map { it.toPathOrNull() } + return expr.values.any { it.toPathOrNull() == this } } internal fun AccessPath.isUsedAt(inst: JcInst): Boolean { From 89c12261986c3f90d97deb8e4e91fab606ff5025 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 12:43:18 +0300 Subject: [PATCH 102/117] Cleanup --- .../test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt index 90c93bfc0..e20442d2c 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/IfdsUnusedTest.kt @@ -16,9 +16,7 @@ package org.jacodb.analysis.impl -import org.jacodb.analysis.ifds.ClassUnitResolver import org.jacodb.analysis.ifds.SingletonUnitResolver -import org.jacodb.analysis.taint.TaintManager import org.jacodb.analysis.unused.UnusedVariableManager import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods @@ -33,8 +31,6 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream import kotlin.time.Duration.Companion.seconds -private val logger = mu.KotlinLogging.logger {} - class IfdsUnusedTest : BaseAnalysisTest() { companion object : WithDB(Usages, InMemoryHierarchy) { @@ -71,7 +67,8 @@ class IfdsUnusedTest : BaseAnalysisTest() { @Test fun `test on specific Juliet instance`() { - val className = "juliet.testcases.CWE563_Unused_Variable.CWE563_Unused_Variable__unused_init_variable_StringBuilder_01" + val className = + "juliet.testcases.CWE563_Unused_Variable.CWE563_Unused_Variable__unused_init_variable_StringBuilder_01" val clazz = cp.findClass(className) val badMethod = clazz.methods.single { it.name == "bad" } val unitResolver = SingletonUnitResolver From 05e992d865b2eba316b65cba39558af753ce56fa Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 12:52:47 +0300 Subject: [PATCH 103/117] Remove unnecessary NewVulnerability event for UnusedVariable analysis --- .../kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt | 4 ---- .../org/jacodb/analysis/unused/UnusedVariableManager.kt | 4 ---- 2 files changed, 8 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt index f2cccecce..7073277dd 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableEvents.kt @@ -23,7 +23,3 @@ sealed interface Event data class NewSummaryEdge( val edge: Edge, ) : Event - -data class NewVulnerability( - val vulnerability: UnusedVariableVulnerability, -) : Event diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt index ecf117941..1ed81b7d4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt @@ -229,10 +229,6 @@ class UnusedVariableManager( is NewSummaryEdge -> { summaryEdgesStorage.add(UnusedVariableSummaryEdge(event.edge)) } - - is NewVulnerability -> { - vulnerabilitiesStorage.add(event.vulnerability) - } } } From 0cabd9253f37254f1113a0a53ca7c230a68bd55d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 13:00:04 +0300 Subject: [PATCH 104/117] Remove unnecessary entry points --- .../kotlin/org/jacodb/analysis/taint/TaintManager.kt | 9 --------- .../org/jacodb/analysis/unused/UnusedVariableManager.kt | 9 --------- .../org/jacodb/analysis/impl/JavaAnalysisApiTest.java | 9 +++++++-- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index 30af510bf..47cf94e63 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -316,12 +316,3 @@ open class TaintManager( return runner.getIfdsResult() } } - -fun runTaintAnalysis( - graph: JcApplicationGraph, - unitResolver: UnitResolver, - startMethods: List, -): List { - val manager = TaintManager(graph, unitResolver) - return manager.analyze(startMethods) -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt index 1ed81b7d4..b6946fb43 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt @@ -257,12 +257,3 @@ class UnusedVariableManager( .launchIn(scope) } } - -fun runUnusedVariableAnalysis( - graph: JcApplicationGraph, - unitResolver: UnitResolver, - startMethods: List, -): List { - val manager = UnusedVariableManager(graph, unitResolver) - return manager.analyze(startMethods) -} diff --git a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java index 4b5385466..022f7a1c6 100644 --- a/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java +++ b/jacodb-analysis/src/test/java/org/jacodb/analysis/impl/JavaAnalysisApiTest.java @@ -16,10 +16,11 @@ package org.jacodb.analysis.impl; +import kotlin.time.DurationUnit; import org.jacodb.analysis.graph.ApplicationGraphFactory; import org.jacodb.analysis.ifds.UnitResolver; import org.jacodb.analysis.ifds.UnitResolverKt; -import org.jacodb.analysis.taint.TaintManagerKt; +import org.jacodb.analysis.taint.TaintManager; import org.jacodb.api.JcClassOrInterface; import org.jacodb.api.JcClasspath; import org.jacodb.api.JcDatabase; @@ -38,6 +39,9 @@ import java.util.List; import java.util.concurrent.ExecutionException; +import static kotlin.time.DurationKt.toDuration; + + public class JavaAnalysisApiTest { private static JcClasspath classpath; @@ -57,7 +61,8 @@ public void testJavaAnalysisApi() throws ExecutionException, InterruptedExceptio .newApplicationGraphForAnalysisAsync(classpath, null) .get(); UnitResolver unitResolver = UnitResolverKt.getMethodUnitResolver(); - TaintManagerKt.runTaintAnalysis(applicationGraph, unitResolver, methodsToAnalyze); + TaintManager manager = new TaintManager(applicationGraph, unitResolver, false); + manager.analyze(methodsToAnalyze, toDuration(30, DurationUnit.SECONDS)); } @Test From b7a2230bf7c9917aadd2a7d395abd6d0fd867c8d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 13:52:58 +0300 Subject: [PATCH 105/117] Add "entry point" position resolvers --- .../org/jacodb/analysis/config/Position.kt | 40 +++++++++++++++++ .../jacodb/analysis/npe/NpeFlowFunctions.kt | 43 +++---------------- .../analysis/taint/TaintFlowFunctions.kt | 41 ++---------------- 3 files changed, 49 insertions(+), 75 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt index da77ba410..be5f585b8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt @@ -22,6 +22,10 @@ import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.fmap import org.jacodb.analysis.ifds.toMaybe import org.jacodb.analysis.ifds.toPathOrNull +import org.jacodb.analysis.util.getArgument +import org.jacodb.analysis.util.thisInstance +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstanceCallExpr @@ -65,3 +69,39 @@ class CallPositionToJcValueResolver( ResultAnyElement -> Maybe.none() } } + +class EntryPointPositionToJcValueResolver( + val cp: JcClasspath, + val method: JcMethod, +) : PositionResolver> { + override fun resolve(position: Position): Maybe { + return when (position) { + This -> Maybe.some(method.thisInstance) + + is Argument -> { + val p = method.parameters[position.index] + cp.getArgument(p).toMaybe() + } + + AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + } + } +} + +class EntryPointPositionToAccessPathResolver( + val cp: JcClasspath, + val method: JcMethod, +) : PositionResolver> { + override fun resolve(position: Position): Maybe { + return when (position) { + This -> method.thisInstance.toPathOrNull().toMaybe() + + is Argument -> { + val p = method.parameters[position.index] + cp.getArgument(p)?.toPathOrNull().toMaybe() + } + + AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + } + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index 4d6c035ef..523e45ab8 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -19,21 +19,20 @@ package org.jacodb.analysis.npe import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.EntryPointPositionToAccessPathResolver +import org.jacodb.analysis.config.EntryPointPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.config.TaintActionEvaluator import org.jacodb.analysis.ifds.AccessPath import org.jacodb.analysis.ifds.ElementAccessor import org.jacodb.analysis.ifds.FlowFunction import org.jacodb.analysis.ifds.FlowFunctions -import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.onSome -import org.jacodb.analysis.ifds.toMaybe import org.jacodb.analysis.ifds.toPath import org.jacodb.analysis.ifds.toPathOrNull import org.jacodb.analysis.taint.TaintDomainFact -import org.jacodb.analysis.taint.Tainted import org.jacodb.analysis.taint.TaintZeroFact -import org.jacodb.analysis.util.getArgument +import org.jacodb.analysis.taint.Tainted import org.jacodb.analysis.util.getArgumentsOf import org.jacodb.analysis.util.startsWith import org.jacodb.analysis.util.thisInstance @@ -59,22 +58,17 @@ import org.jacodb.api.cfg.JcValue import org.jacodb.api.ext.cfg.callExpr import org.jacodb.api.ext.findTypeOrNull import org.jacodb.api.ext.isNullable -import org.jacodb.taint.configuration.AnyArgument -import org.jacodb.taint.configuration.Argument import org.jacodb.taint.configuration.AssignMark import org.jacodb.taint.configuration.CopyAllMarks import org.jacodb.taint.configuration.CopyMark import org.jacodb.taint.configuration.RemoveAllMarks import org.jacodb.taint.configuration.RemoveMark -import org.jacodb.taint.configuration.Result -import org.jacodb.taint.configuration.ResultAnyElement import org.jacodb.taint.configuration.TaintCleaner import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintEntryPointSource import org.jacodb.taint.configuration.TaintMark import org.jacodb.taint.configuration.TaintMethodSource import org.jacodb.taint.configuration.TaintPassThrough -import org.jacodb.taint.configuration.This private val logger = mu.KotlinLogging.logger {} @@ -120,35 +114,8 @@ class ForwardNpeFlowFunctions( // Extract initial facts from the config: val config = taintConfigurationFeature?.getConfigForMethod(method) if (config != null) { - // Note: both condition and action evaluator require a custom position resolver. - val conditionEvaluator = BasicConditionEvaluator { position -> - when (position) { - This -> Maybe.some(method.thisInstance) - - is Argument -> { - val p = method.parameters[position.index] - cp.getArgument(p).toMaybe() - } - - AnyArgument -> error("Unexpected $position") - Result -> error("Unexpected $position") - ResultAnyElement -> error("Unexpected $position") - } - } - val actionEvaluator = TaintActionEvaluator { position -> - when (position) { - This -> method.thisInstance.toPathOrNull().toMaybe() - - is Argument -> { - val p = method.parameters[position.index] - cp.getArgument(p)?.toPathOrNull().toMaybe() - } - - AnyArgument -> error("Unexpected $position") - Result -> error("Unexpected $position") - ResultAnyElement -> error("Unexpected $position") - } - } + val conditionEvaluator = BasicConditionEvaluator(EntryPointPositionToJcValueResolver(cp, method)) + val actionEvaluator = TaintActionEvaluator(EntryPointPositionToAccessPathResolver(cp, method)) // Handle EntryPointSource config items: for (item in config.filterIsInstance()) { diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt index 47fc265d6..139cacf5c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt @@ -19,17 +19,16 @@ package org.jacodb.analysis.taint import org.jacodb.analysis.config.BasicConditionEvaluator import org.jacodb.analysis.config.CallPositionToAccessPathResolver import org.jacodb.analysis.config.CallPositionToJcValueResolver +import org.jacodb.analysis.config.EntryPointPositionToAccessPathResolver +import org.jacodb.analysis.config.EntryPointPositionToJcValueResolver import org.jacodb.analysis.config.FactAwareConditionEvaluator import org.jacodb.analysis.config.TaintActionEvaluator import org.jacodb.analysis.ifds.ElementAccessor import org.jacodb.analysis.ifds.FlowFunction import org.jacodb.analysis.ifds.FlowFunctions -import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.onSome -import org.jacodb.analysis.ifds.toMaybe import org.jacodb.analysis.ifds.toPath import org.jacodb.analysis.ifds.toPathOrNull -import org.jacodb.analysis.util.getArgument import org.jacodb.analysis.util.getArgumentsOf import org.jacodb.analysis.util.startsWith import org.jacodb.analysis.util.thisInstance @@ -45,21 +44,16 @@ import org.jacodb.api.cfg.JcReturnInst import org.jacodb.api.cfg.JcThis import org.jacodb.api.cfg.JcValue import org.jacodb.api.ext.cfg.callExpr -import org.jacodb.taint.configuration.AnyArgument -import org.jacodb.taint.configuration.Argument import org.jacodb.taint.configuration.AssignMark import org.jacodb.taint.configuration.CopyAllMarks import org.jacodb.taint.configuration.CopyMark import org.jacodb.taint.configuration.RemoveAllMarks import org.jacodb.taint.configuration.RemoveMark -import org.jacodb.taint.configuration.Result -import org.jacodb.taint.configuration.ResultAnyElement import org.jacodb.taint.configuration.TaintCleaner import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintEntryPointSource import org.jacodb.taint.configuration.TaintMethodSource import org.jacodb.taint.configuration.TaintPassThrough -import org.jacodb.taint.configuration.This private val logger = mu.KotlinLogging.logger {} @@ -83,35 +77,8 @@ class ForwardTaintFlowFunctions( // Extract initial facts from the config: val config = taintConfigurationFeature?.getConfigForMethod(method) if (config != null) { - // Note: both condition and action evaluator require a custom position resolver. - val conditionEvaluator = BasicConditionEvaluator { position -> - when (position) { - This -> Maybe.some(method.thisInstance) - - is Argument -> { - val p = method.parameters[position.index] - cp.getArgument(p).toMaybe() - } - - AnyArgument -> error("Unexpected $position") - Result -> error("Unexpected $position") - ResultAnyElement -> error("Unexpected $position") - } - } - val actionEvaluator = TaintActionEvaluator { position -> - when (position) { - This -> method.thisInstance.toPathOrNull().toMaybe() - - is Argument -> { - val p = method.parameters[position.index] - cp.getArgument(p)?.toPathOrNull().toMaybe() - } - - AnyArgument -> error("Unexpected $position") - Result -> error("Unexpected $position") - ResultAnyElement -> error("Unexpected $position") - } - } + val conditionEvaluator = BasicConditionEvaluator(EntryPointPositionToJcValueResolver(cp, method)) + val actionEvaluator = TaintActionEvaluator(EntryPointPositionToAccessPathResolver(cp, method)) // Handle EntryPointSource config items: for (item in config.filterIsInstance()) { From 3ec515c430e438175868420f7cf281721c493ab7 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 13:53:16 +0300 Subject: [PATCH 106/117] Remove comment --- .../kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index 523e45ab8..8fb854b11 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -103,14 +103,6 @@ class ForwardNpeFlowFunctions( // Zero (reachability) fact always present at entrypoint: add(TaintZeroFact) - // Possibly null arguments: - // for (p in method.parameters.filter { it.isNullable != false }) { - // val t = cp.findTypeOrNull(p.type)!! - // val arg = JcArgument.of(p.index, p.name, t) - // val path = AccessPath.from(arg) - // add(Tainted(path, TaintMark.NULLNESS)) - // } - // Extract initial facts from the config: val config = taintConfigurationFeature?.getConfigForMethod(method) if (config != null) { From facb51a021eb585abf2ae7e8d9364b3af1eb9f11 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 13:54:46 +0300 Subject: [PATCH 107/117] Replace comment with TODO --- .../kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index 8fb854b11..beb860a89 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -133,11 +133,11 @@ class ForwardNpeFlowFunctions( val fromPath = from.toPathOrNull() if (fact.mark == TaintMark.NULLNESS) { - // if (from is JcNewExpr || - // from is JcNewArrayExpr || - // from is JcConstant || - // (from is JcCallExpr && from.method.method.isNullable != true) - // ) { + // TODO: consider + // if (from is JcNewExpr + // || from is JcNewArrayExpr + // || from is JcConstant + // || (from is JcCallExpr && from.method.method.isNullable != true)) if (fact.variable.startsWith(toPath)) { // NULLNESS is overridden: return emptySet() From 62c8770336f19fc84cdae59d025600ba2e419404 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 13:55:23 +0300 Subject: [PATCH 108/117] Remove comments --- .../kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt index beb860a89..f64c922a4 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/npe/NpeFlowFunctions.kt @@ -391,12 +391,6 @@ class ForwardNpeFlowFunctions( } check(fact is Tainted) - // TODO: handle 'activation' (c.f. Boomerang) here - - // if (config == null) { - // return@FlowFunction emptyList() - // } - if (config != null) { // FIXME: adhoc if (callee.enclosingClass.name == "java.lang.StringBuilder" && callee.name == "append") { @@ -508,7 +502,6 @@ class ForwardNpeFlowFunctions( val callee = calleeStart.location.method if (fact == TaintZeroFact) { - // return@FlowFunction obtainPossibleStartFacts(callee) return@FlowFunction obtainPossibleStartFactsBasic(callee) } check(fact is Tainted) From 13e9b538a4ffecc1cb5074bd3c7dc4341942851a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 13:57:42 +0300 Subject: [PATCH 109/117] Unify code --- .../org/jacodb/analysis/config/Position.kt | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt index be5f585b8..a6e238a36 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Position.kt @@ -74,17 +74,15 @@ class EntryPointPositionToJcValueResolver( val cp: JcClasspath, val method: JcMethod, ) : PositionResolver> { - override fun resolve(position: Position): Maybe { - return when (position) { - This -> Maybe.some(method.thisInstance) - - is Argument -> { - val p = method.parameters[position.index] - cp.getArgument(p).toMaybe() - } + override fun resolve(position: Position): Maybe = when (position) { + This -> Maybe.some(method.thisInstance) - AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + is Argument -> { + val p = method.parameters[position.index] + cp.getArgument(p).toMaybe() } + + AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") } } @@ -92,16 +90,14 @@ class EntryPointPositionToAccessPathResolver( val cp: JcClasspath, val method: JcMethod, ) : PositionResolver> { - override fun resolve(position: Position): Maybe { - return when (position) { - This -> method.thisInstance.toPathOrNull().toMaybe() - - is Argument -> { - val p = method.parameters[position.index] - cp.getArgument(p)?.toPathOrNull().toMaybe() - } + override fun resolve(position: Position): Maybe = when (position) { + This -> method.thisInstance.toPathOrNull().toMaybe() - AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + is Argument -> { + val p = method.parameters[position.index] + cp.getArgument(p)?.toPathOrNull().toMaybe() } + + AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") } } From 0f4464bc5fe39e5d210c95d247c36941e0af90bb Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 14:16:54 +0300 Subject: [PATCH 110/117] Add JvmName --- .../src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt | 1 + .../kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt index 47cf94e63..f81a1599c 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintManager.kt @@ -138,6 +138,7 @@ open class TaintManager( } } + @JvmName("analyze") // needed for Java interop because of inline class (Duration) @OptIn(ExperimentalTime::class) fun analyze( startMethods: List, diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt index b6946fb43..ea3a99d01 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/unused/UnusedVariableManager.kt @@ -107,6 +107,7 @@ class UnusedVariableManager( } } + @JvmName("analyze") // needed for Java interop because of inline class (Duration) @OptIn(ExperimentalTime::class) fun analyze( startMethods: List, From ece975a258bf1bfcc5382cc090b29f4a7ee06d7e Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 16:32:54 +0300 Subject: [PATCH 111/117] Trace --- .../src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt index e0d3902a1..a8fc4f916 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Runner.kt @@ -176,7 +176,7 @@ class UniRunner( if (summaryEdge.from == calleeStartVertex) { handleSummaryEdge(currentEdge, summaryEdge) } else { - logger.debug { "Skipping unsuitable summary edge: $summaryEdge" } + logger.trace { "Skipping unsuitable summary edge: $summaryEdge" } } } } else { From d70ed76bf000e6339fd44b3d0150921c76c568f0 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 16:34:17 +0300 Subject: [PATCH 112/117] Add PassThrough rule for Map::put --- .../src/test/resources/additional.json | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/jacodb-analysis/src/test/resources/additional.json b/jacodb-analysis/src/test/resources/additional.json index c0fdda453..3b305b3a7 100644 --- a/jacodb-analysis/src/test/resources/additional.json +++ b/jacodb-analysis/src/test/resources/additional.json @@ -519,5 +519,47 @@ } } ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "java.util" + }, + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Map" + } + }, + "functionName": { + "_": "NameMatches", + "pattern": "put" + }, + "applyToOverrides": true, + "exclude": [], + "functionLabel": null, + "modifier": -1, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + } + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 1 + }, + "to": { + "_": "This" + } + } + ] } ] From 4b720d58298ef493d170b14f607a63473f4f40a7 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 16:40:38 +0300 Subject: [PATCH 113/117] Use info logger level for found sinks --- .../src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt index b64c72cc6..ae59345cb 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintAnalyzers.kt @@ -72,7 +72,7 @@ class TaintAnalyzer( if (item.condition.accept(conditionEvaluator)) { val message = item.ruleNote val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) - logger.debug { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } + logger.info { "Found sink=${vulnerability.sink} in ${vulnerability.method}" } add(NewVulnerability(vulnerability)) } } From 41ce66addbfc2a5a0461615f63af2f831fefe10c Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 16:41:01 +0300 Subject: [PATCH 114/117] Add PassThrough for decodeBase64 --- .../src/test/resources/additional.json | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/jacodb-analysis/src/test/resources/additional.json b/jacodb-analysis/src/test/resources/additional.json index 3b305b3a7..1bb4cb1f7 100644 --- a/jacodb-analysis/src/test/resources/additional.json +++ b/jacodb-analysis/src/test/resources/additional.json @@ -561,5 +561,47 @@ } } ] + }, + { + "_": "PassThrough", + "methodInfo": { + "cls": { + "classNameMatcher": { + "_": "NameIsEqualTo", + "name": "Base64" + }, + "packageMatcher": { + "_": "NameIsEqualTo", + "name": "org.apache.commons.codec.binary" + } + }, + "functionName": { + "_": "NameIsEqualTo", + "name": "decodeBase64" + }, + "applyToOverrides": true, + "exclude": [], + "functionLabel": null, + "modifier": -1, + "parametersMatchers": [], + "returnTypeMatcher": { + "_": "AnyTypeMatches" + } + }, + "condition": { + "_": "ConstantTrue" + }, + "actionsAfter": [ + { + "_": "CopyAllMarks", + "from": { + "_": "Argument", + "number": 0 + }, + "to": { + "_": "Result" + } + } + ] } ] From 3ec78091b8d326832fd49f8758391d9cd6494c39 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 17:35:24 +0300 Subject: [PATCH 115/117] Add PassThrough rule for Map::replace --- jacodb-analysis/src/test/resources/additional.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jacodb-analysis/src/test/resources/additional.json b/jacodb-analysis/src/test/resources/additional.json index 1bb4cb1f7..4c883436e 100644 --- a/jacodb-analysis/src/test/resources/additional.json +++ b/jacodb-analysis/src/test/resources/additional.json @@ -535,7 +535,7 @@ }, "functionName": { "_": "NameMatches", - "pattern": "put" + "pattern": "put|replace" }, "applyToOverrides": true, "exclude": [], From ae76dd6b8a72fa947ccead3d279eda55a98e9057 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 21 Feb 2024 17:36:03 +0300 Subject: [PATCH 116/117] Fix ConditionMark evaluation, add adhoc for arrays --- .../org/jacodb/analysis/config/Condition.kt | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt index 2ba922302..92a765463 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/config/Condition.kt @@ -16,11 +16,12 @@ package org.jacodb.analysis.config +import org.jacodb.analysis.ifds.AccessPath +import org.jacodb.analysis.ifds.ElementAccessor import org.jacodb.analysis.ifds.Maybe import org.jacodb.analysis.ifds.onSome import org.jacodb.analysis.ifds.toPath import org.jacodb.analysis.taint.Tainted -import org.jacodb.analysis.util.startsWith import org.jacodb.api.cfg.JcBool import org.jacodb.api.cfg.JcConstant import org.jacodb.api.cfg.JcInt @@ -29,7 +30,6 @@ import org.jacodb.api.cfg.JcValue import org.jacodb.api.ext.isAssignable import org.jacodb.taint.configuration.And import org.jacodb.taint.configuration.AnnotationType -import org.jacodb.taint.configuration.Condition import org.jacodb.taint.configuration.ConditionVisitor import org.jacodb.taint.configuration.ConstantBooleanValue import org.jacodb.taint.configuration.ConstantEq @@ -164,8 +164,22 @@ class FactAwareConditionEvaluator( if (fact.mark != condition.mark) return false positionResolver.resolve(condition.position).onSome { value -> val variable = value.toPath() - return variable.startsWith(fact.variable) + + // FIXME: Adhoc for arrays + val variableWithoutStars = variable.removeTrailingElementAccessors() + val factWithoutStars = fact.variable.removeTrailingElementAccessors() + if (variableWithoutStars == factWithoutStars) return true + + return variable == fact.variable } return false } + + private fun AccessPath.removeTrailingElementAccessors(): AccessPath { + val accesses = accesses.toMutableList() + while (accesses.lastOrNull() is ElementAccessor) { + accesses.removeLast() + } + return AccessPath(value, accesses) + } } From b39519fe5158b68b843897f6f9300d5be6473362 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Fri, 23 Feb 2024 23:21:18 +0300 Subject: [PATCH 117/117] Do not override tainted arrays --- .../kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt index 139cacf5c..20bc9b455 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/analysis/taint/TaintFlowFunctions.kt @@ -35,6 +35,7 @@ import org.jacodb.analysis.util.thisInstance import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcArrayAccess import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcDynamicCallExpr import org.jacodb.api.cfg.JcExpr @@ -122,8 +123,11 @@ class ForwardTaintFlowFunctions( } } - if (fact.variable.startsWith(toPath)) { + // FIXME: pass-through tainted arrays + if (to is JcArrayAccess) { + return setOf(fact) + } // 'to' was (sub-)tainted, but it is now overridden by 'from': return emptySet() } else {