From 7e765d441217bebecdb7b217890c4937f316f823 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 4 Apr 2024 16:57:17 +0300 Subject: [PATCH] [ifds] feat: reasons, `runnerId` property, `IfdsResult` --- .../TaintAnalyzer.kt => DefaultAnalyzer.kt} | 19 +-- .../ifds/{taint => }/FunctionWrapper.kt | 33 ++-- .../ifds/{taint => }/IndirectionHandler.kt | 10 +- .../kotlin/org/jacodb/ifds/JcIfdsContext.kt | 57 +++++++ .../kotlin/org/jacodb/ifds/npe/Context.kt | 67 ++++++++ .../kotlin/org/jacodb/ifds/npe/Results.kt | 31 ++++ .../kotlin/org/jacodb/ifds/npe/Runners.kt | 21 +++ .../org/jacodb/ifds/npe/SystemExtensions.kt | 50 ++++++ .../kotlin/org/jacodb/ifds/taint/Context.kt | 84 ++++++++++ .../kotlin/org/jacodb/ifds/taint/Results.kt | 31 ++++ .../kotlin/org/jacodb/ifds/taint/Runners.kt | 22 +++ .../org/jacodb/ifds/taint/SystemExtensions.kt | 59 +++++++ .../org/jacodb/ifds/taint/TaintIfdsContext.kt | 84 ---------- .../kotlin/org/jacodb/ifds/unused/Context.kt | 18 +++ .../jacodb/analysis/impl/BaseAnalysisTest.kt | 4 +- .../org/jacodb/analysis/impl/IfdsNpeTest.kt | 35 +--- .../org/jacodb/analysis/impl/IfdsSqlTest.kt | 52 +++--- .../jacodb/analysis/impl/IfdsUnusedTest.kt | 25 +++ .../analysis/impl/JodaDateTimeAnalysisTest.kt | 21 ++- .../test/resources/simplelogger.properties | 4 +- .../org/jacodb/actors/impl/ActorSystemImpl.kt | 2 +- .../org/jacodb/ifds/actors/ChunkManager.kt | 12 +- .../kotlin/org/jacodb/ifds/actors/Runner.kt | 16 +- .../org/jacodb/ifds/actors/RunnerStorage.kt | 50 ++++-- .../ifds/domain/{Chunk.kt => ChunkId.kt} | 2 +- .../org/jacodb/ifds/domain/IfdsContext.kt | 8 +- .../kotlin/org/jacodb/ifds/domain/Reason.kt | 43 +++++ .../domain/{RunnerType.kt => RunnerId.kt} | 2 +- .../jacodb/ifds/messages/AnalyzerMessages.kt | 18 ++- .../jacodb/ifds/messages/CommonMessages.kt | 6 +- .../ifds/messages/IndirectionMessages.kt | 4 +- .../jacodb/ifds/messages/StorageMessages.kt | 32 ++-- .../jacodb/ifds/result/IfdsComputationData.kt | 35 ++++ .../org/jacodb/ifds/result/IfdsResult.kt | 23 +++ .../kotlin/org/jacodb/ifds/result/Merging.kt | 23 +++ .../ifds/result/SingleStorageTraceGraph.kt | 150 ++++++++++++++++++ .../org/jacodb/ifds/result/TraceGraph.kt | 30 ++++ 37 files changed, 942 insertions(+), 241 deletions(-) rename jacodb-analysis/src/main/kotlin/org/jacodb/ifds/{taint/TaintAnalyzer.kt => DefaultAnalyzer.kt} (89%) rename jacodb-analysis/src/main/kotlin/org/jacodb/ifds/{taint => }/FunctionWrapper.kt (75%) rename jacodb-analysis/src/main/kotlin/org/jacodb/ifds/{taint => }/IndirectionHandler.kt (90%) create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/JcIfdsContext.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Context.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Results.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Runners.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/SystemExtensions.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Context.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Results.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Runners.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/SystemExtensions.kt delete mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintIfdsContext.kt create mode 100644 jacodb-analysis/src/main/kotlin/org/jacodb/ifds/unused/Context.kt rename jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/{Chunk.kt => ChunkId.kt} (97%) create mode 100644 jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Reason.kt rename jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/{RunnerType.kt => RunnerId.kt} (97%) create mode 100644 jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsComputationData.kt create mode 100644 jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsResult.kt create mode 100644 jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/Merging.kt create mode 100644 jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/SingleStorageTraceGraph.kt create mode 100644 jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/TraceGraph.kt diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintAnalyzer.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/DefaultAnalyzer.kt similarity index 89% rename from jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintAnalyzer.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/ifds/DefaultAnalyzer.kt index 54c2f09f6..8e3d751f7 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintAnalyzer.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/DefaultAnalyzer.kt @@ -14,17 +14,16 @@ * limitations under the License. */ -package org.jacodb.ifds.taint +package org.jacodb.ifds import org.jacodb.ifds.domain.Analyzer import org.jacodb.ifds.domain.Edge import org.jacodb.ifds.domain.FlowFunction import org.jacodb.ifds.domain.FlowScope -import org.jacodb.ifds.domain.RunnerType +import org.jacodb.ifds.domain.RunnerId import org.jacodb.ifds.messages.AnalyzerMessage import org.jacodb.ifds.messages.CommonMessage import org.jacodb.ifds.messages.EdgeMessage -import org.jacodb.ifds.messages.NewSummaryEdge import org.jacodb.ifds.messages.NotificationOnStart import org.jacodb.ifds.messages.ResolvedCall import org.jacodb.ifds.messages.UnresolvedCall @@ -35,10 +34,10 @@ import org.jacodb.api.ext.cfg.callExpr typealias TaintFlowScope = FlowScope -class TaintAnalyzer( +class DefaultAnalyzer( private val applicationGraph: JcApplicationGraph, private val flowFunction: FlowFunction, - private val runnerType: RunnerType, + private val runnerId: RunnerId, ) : Analyzer { override fun step(message: AnalyzerMessage): Collection = buildList { when (message) { @@ -73,13 +72,12 @@ class TaintAnalyzer( when { callExpr != null -> processCall(edge) - isExit -> processExit(edge) - else -> processSequent(edge) + !isExit -> processSequent(edge) } } private fun TaintFlowScope.processCall(edge: Edge) { - val callMessage = UnresolvedCall(edge) + val callMessage = UnresolvedCall(runnerId, edge) add(callMessage) val successors = applicationGraph.successors(edge.to.stmt) @@ -91,11 +89,6 @@ class TaintAnalyzer( } } - private fun TaintFlowScope.processExit(edge: Edge) { - val summaryEdge = NewSummaryEdge(runnerType, edge) - add(summaryEdge) - } - private fun TaintFlowScope.processSequent(edge: Edge) { val successors = applicationGraph.successors(edge.to.stmt) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/FunctionWrapper.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/FunctionWrapper.kt similarity index 75% rename from jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/FunctionWrapper.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/ifds/FunctionWrapper.kt index f97808960..ff12d2210 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/FunctionWrapper.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/FunctionWrapper.kt @@ -14,23 +14,25 @@ * limitations under the License. */ -package org.jacodb.ifds.taint +package org.jacodb.ifds +import org.jacodb.analysis.ifds.Analyzer +import org.jacodb.api.cfg.JcInst import org.jacodb.ifds.domain.Edge import org.jacodb.ifds.domain.FlowFunction import org.jacodb.ifds.domain.FlowScope -import org.jacodb.ifds.domain.RunnerType +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.RunnerId import org.jacodb.ifds.domain.Vertex import org.jacodb.ifds.messages.NewEdge import org.jacodb.ifds.messages.SubscriptionOnStart -import org.jacodb.analysis.ifds.Analyzer -import org.jacodb.api.cfg.JcInst +typealias JcEventProcessor = FlowScope.(Event) -> Unit class JcFlowFunctionsAdapter( - private val runnerType: RunnerType, + private val runnerId: RunnerId, private val jcAnalyzer: Analyzer, - private val jcEventProcessor: FlowScope.(Event) -> Unit, + private val jcEventProcessor: JcEventProcessor, ) : FlowFunction { private val jcFlowFunctions = jcAnalyzer.flowFunctions @@ -40,7 +42,7 @@ class JcFlowFunctionsAdapter( .compute(edge.to.fact) .forEach { newFact -> val newEdge = Edge(edge.from, Vertex(next, newFact)) - processNewEdge(runnerType, newEdge) + processNewEdge(runnerId, newEdge, Reason.Sequent(edge)) } override fun FlowScope.callToReturn(returnSite: JcInst) = @@ -49,7 +51,7 @@ class JcFlowFunctionsAdapter( .compute(edge.to.fact) .forEach { newFact -> val newEdge = Edge(edge.from, Vertex(returnSite, newFact)) - processNewEdge(runnerType, newEdge) + processNewEdge(runnerId, newEdge, Reason.CallToReturn(edge)) } override fun FlowScope.callToStart(calleeStart: JcInst) = @@ -59,11 +61,11 @@ class JcFlowFunctionsAdapter( .forEach { newFact -> val vertex = Vertex(calleeStart, newFact) - val subscription = SubscriptionOnStart(runnerType, vertex, runnerType, edge) + val subscription = SubscriptionOnStart(runnerId, vertex, runnerId, edge) add(subscription) val newEdge = Edge(vertex, vertex) - processNewEdge(runnerType, newEdge) + processNewEdge(runnerId, newEdge, Reason.CallToStart(edge)) } override fun FlowScope.exitToReturnSite( @@ -74,23 +76,26 @@ class JcFlowFunctionsAdapter( .compute(edge.to.fact) .forEach { newFact -> val newEdge = Edge(callerEdge.from, Vertex(returnSite, newFact)) - processNewEdge(runnerType, newEdge) + processNewEdge(runnerId, newEdge, Reason.ExitToReturnSite(callerEdge, edge)) } private fun FlowScope.processNewEdge( - runnerType: RunnerType, + runnerId: RunnerId, newEdge: Edge, + reason: Reason, ) { - val edge = NewEdge(runnerType, newEdge, edge) + val edge = NewEdge(runnerId, newEdge, reason) add(edge) val jcEvents = jcAnalyzer.handleNewEdge(newEdge.toJcEdge()) for (event in jcEvents) { jcEventProcessor(event) } - } } private fun Vertex.toJcVertex() = org.jacodb.analysis.ifds.Vertex(stmt, fact) private fun Edge.toJcEdge() = org.jacodb.analysis.ifds.Edge(from.toJcVertex(), to.toJcVertex()) + +fun org.jacodb.analysis.ifds.Vertex.toVertex() = Vertex(statement, fact) +fun org.jacodb.analysis.ifds.Edge.toEdge() = Edge(from.toVertex(), to.toVertex()) diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/IndirectionHandler.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/IndirectionHandler.kt similarity index 90% rename from jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/IndirectionHandler.kt rename to jacodb-analysis/src/main/kotlin/org/jacodb/ifds/IndirectionHandler.kt index 65394297b..deb8508b6 100644 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/IndirectionHandler.kt +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/IndirectionHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jacodb.ifds.taint +package org.jacodb.ifds import org.jacodb.actors.api.Actor import org.jacodb.actors.api.ActorContext @@ -27,6 +27,7 @@ import org.jacodb.api.cfg.JcVirtualCallExpr import org.jacodb.api.ext.HierarchyExtension import org.jacodb.api.ext.cfg.callExpr import org.jacodb.api.ext.isSubClassOf +import org.jacodb.ifds.domain.RunnerId import org.jacodb.ifds.messages.CommonMessage import org.jacodb.ifds.messages.IndirectionMessage import org.jacodb.ifds.messages.ResolvedCall @@ -37,6 +38,7 @@ class IndirectionHandler( private val hierarchy: HierarchyExtension, private val bannedPackagePrefixes: List, private val parent: ActorRef, + private val runnerId: RunnerId, ) : Actor { private val cache = hashMapOf>() @@ -55,13 +57,13 @@ class IndirectionHandler( val callExpr = node.callExpr as? JcVirtualCallExpr if (callExpr == null) { for (override in callees) { - parent.send(ResolvedCall(message.edge, override)) + parent.send(ResolvedCall(runnerId, message.edge, override)) } return } val instanceClass = (callExpr.instance.type as? JcClassType)?.jcClass if (instanceClass == null) { - parent.send(ResolvedCall(message.edge, callExpr.method.method)) + parent.send(ResolvedCall(runnerId, message.edge, callExpr.method.method)) return } @@ -79,7 +81,7 @@ class IndirectionHandler( } for (override in overrides) { - parent.send(ResolvedCall(message.edge, override)) + parent.send(ResolvedCall(runnerId, message.edge, override)) } } } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/JcIfdsContext.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/JcIfdsContext.kt new file mode 100644 index 000000000..486963f93 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/JcIfdsContext.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.ifds + +import org.jacodb.actors.api.ActorRef +import org.jacodb.actors.api.Factory +import org.jacodb.api.JcClasspath +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.api.cfg.JcInst +import org.jacodb.ifds.domain.Analyzer +import org.jacodb.ifds.domain.ChunkId +import org.jacodb.ifds.domain.FlowFunction +import org.jacodb.ifds.domain.IfdsContext +import org.jacodb.ifds.domain.RunnerId +import org.jacodb.ifds.messages.CommonMessage +import org.jacodb.impl.features.HierarchyExtensionImpl + +class JcIfdsContext( + private val cp: JcClasspath, + private val graph: JcApplicationGraph, + private val bannedPackagePrefixes: List, + private val flowFunctionFactory: (ChunkId, RunnerId) -> FlowFunction, +) : IfdsContext { + data object SingleChunk : ChunkId + + override fun chunkByMessage(message: CommonMessage): ChunkId = + SingleChunk + + override fun runnerIdByMessage(message: CommonMessage): RunnerId = + message.runnerId + + override fun getAnalyzer(chunkId: ChunkId, runnerId: RunnerId): Analyzer = + DefaultAnalyzer( + graph, + flowFunctionFactory(chunkId, runnerId), + runnerId + ) + + override fun indirectionHandlerFactory(parent: ActorRef, runnerId: RunnerId) = + Factory { + IndirectionHandler(HierarchyExtensionImpl(cp), bannedPackagePrefixes, parent, runnerId) + } +} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Context.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Context.kt new file mode 100644 index 000000000..a11c2a1ca --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Context.kt @@ -0,0 +1,67 @@ +/* + * 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.ifds.npe + +import org.jacodb.analysis.npe.NpeAnalyzer +import org.jacodb.analysis.taint.EdgeForOtherRunner +import org.jacodb.analysis.taint.NewVulnerability +import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.api.JcClasspath +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.ifds.JcFlowFunctionsAdapter +import org.jacodb.ifds.JcIfdsContext +import org.jacodb.ifds.messages.NewResult +import org.jacodb.ifds.messages.NewSummaryEdge +import org.jacodb.ifds.toEdge + +fun npeIfdsContext( + cp: JcClasspath, + graph: JcApplicationGraph, + bannedPackagePrefixes: List, +): JcIfdsContext = + JcIfdsContext( + cp, + graph, + bannedPackagePrefixes + ) { _, runnerId -> + val analyzer = when (runnerId) { + is SingleRunner -> NpeAnalyzer(graph) + else -> error("Unexpected runnerId: $runnerId") + } + + JcFlowFunctionsAdapter( + runnerId, + analyzer + ) { event -> + when (event) { + is EdgeForOtherRunner -> { + error("Unexpected event: $event") + } + + is org.jacodb.analysis.taint.NewSummaryEdge -> { + val summaryEdge = NewSummaryEdge(runnerId, event.edge.toEdge()) + add(summaryEdge) + } + + is NewVulnerability -> { + val result = NewResult(runnerId, NpeVulnerability(event.vulnerability)) + add(result) + } + } + } + } + diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Results.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Results.kt new file mode 100644 index 000000000..5568a927a --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Results.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.ifds.npe + +import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.analysis.taint.TaintVulnerability +import org.jacodb.api.cfg.JcInst +import org.jacodb.ifds.domain.Vertex +import org.jacodb.ifds.toVertex +import org.jacodb.ifds.result.IfdsResult + +data class NpeVulnerability( + val vulnerability: TaintVulnerability, +) : IfdsResult { + override val vertex: Vertex + get() = vulnerability.sink.toVertex() +} \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Runners.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Runners.kt new file mode 100644 index 000000000..55a43464c --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/Runners.kt @@ -0,0 +1,21 @@ +/* + * 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.ifds.npe + +import org.jacodb.ifds.domain.RunnerId + +data object SingleRunner : RunnerId \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/SystemExtensions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/SystemExtensions.kt new file mode 100644 index 000000000..4abfa303c --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/npe/SystemExtensions.kt @@ -0,0 +1,50 @@ +/* + * 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.ifds.npe + +import org.jacodb.actors.api.ActorSystem +import org.jacodb.analysis.graph.JcApplicationGraphImpl +import org.jacodb.analysis.npe.NpeAnalyzer +import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.analysis.taint.TaintVulnerability +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst +import org.jacodb.ifds.domain.Edge +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.Vertex +import org.jacodb.ifds.messages.CommonMessage +import org.jacodb.ifds.messages.NewEdge +import org.jacodb.ifds.messages.ObtainData +import org.jacodb.impl.features.usagesExt + +suspend fun ActorSystem.startNpeAnalysis(method: JcMethod) { + val cp = method.enclosingClass.classpath + val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) + val npeAnalyzer = NpeAnalyzer(graph) + for (fact in npeAnalyzer.flowFunctions.obtainPossibleStartFacts(method)) { + for (entryPoint in graph.entryPoints(method)) { + val vertex = Vertex(entryPoint, fact) + val message = NewEdge(SingleRunner, Edge(vertex, vertex), Reason.Initial) + send(message) + } + } +} + +suspend fun ActorSystem.collectNpeResults(): List { + val ifdsComputationData = ask { ObtainData(SingleRunner, it) } + return ifdsComputationData.results.mapTo(mutableListOf()) { it.vulnerability } +} \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Context.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Context.kt new file mode 100644 index 000000000..a94f9afd6 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Context.kt @@ -0,0 +1,84 @@ +/* + * 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.ifds.taint + +import org.jacodb.analysis.taint.BackwardTaintAnalyzer +import org.jacodb.analysis.taint.EdgeForOtherRunner +import org.jacodb.analysis.taint.NewVulnerability +import org.jacodb.analysis.taint.TaintAnalyzer +import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.api.JcClasspath +import org.jacodb.api.analysis.JcApplicationGraph +import org.jacodb.ifds.JcFlowFunctionsAdapter +import org.jacodb.ifds.JcIfdsContext +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.RunnerId +import org.jacodb.ifds.messages.NewEdge +import org.jacodb.ifds.messages.NewResult +import org.jacodb.ifds.messages.NewSummaryEdge +import org.jacodb.ifds.toEdge + +private fun complementRunner(type: RunnerId): RunnerId = + when (type) { + ForwardRunner -> BackwardRunner + BackwardRunner -> ForwardRunner + else -> error("unexpected runner: $type") + } + +fun taintIfdsContext( + cp: JcClasspath, + graph: JcApplicationGraph, + bannedPackagePrefixes: List, +): JcIfdsContext = + JcIfdsContext( + cp, + graph, + bannedPackagePrefixes + ) { _, runnerId -> + val analyzer = when (runnerId) { + is ForwardRunner -> TaintAnalyzer(graph) + is BackwardRunner -> BackwardTaintAnalyzer(graph) + else -> error("Unexpected runnerId: $runnerId") + } + + JcFlowFunctionsAdapter( + runnerId, + analyzer + ) { event -> + when (event) { + is EdgeForOtherRunner -> { + val edgeForOtherRunner = + NewEdge( + complementRunner(runnerId), + event.edge.toEdge(), + Reason.FromOtherRunner(edge, runnerId) + ) + add(edgeForOtherRunner) + } + + is org.jacodb.analysis.taint.NewSummaryEdge -> { + val summaryEdge = NewSummaryEdge(runnerId, event.edge.toEdge()) + add(summaryEdge) + } + + is NewVulnerability -> { + val result = NewResult(runnerId, TaintVulnerability(event.vulnerability)) + add(result) + } + } + } + } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Results.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Results.kt new file mode 100644 index 000000000..4f78a2ce1 --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Results.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.ifds.taint + +import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.api.cfg.JcInst +import org.jacodb.ifds.domain.Vertex +import org.jacodb.ifds.result.IfdsResult +import org.jacodb.ifds.toVertex +import org.jacodb.analysis.taint.TaintVulnerability as JcTaintVulnerability + +data class TaintVulnerability( + val vulnerability: JcTaintVulnerability, +) : IfdsResult { + override val vertex: Vertex + get() = vulnerability.sink.toVertex() +} \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Runners.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Runners.kt new file mode 100644 index 000000000..2072ef83c --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/Runners.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.ifds.taint + +import org.jacodb.ifds.domain.RunnerId + +data object ForwardRunner : RunnerId +data object BackwardRunner : RunnerId \ No newline at end of file diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/SystemExtensions.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/SystemExtensions.kt new file mode 100644 index 000000000..5d728629a --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/SystemExtensions.kt @@ -0,0 +1,59 @@ +/* + * 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.ifds.taint + +import org.jacodb.actors.api.ActorSystem +import org.jacodb.analysis.graph.JcApplicationGraphImpl +import org.jacodb.analysis.npe.NpeAnalyzer +import org.jacodb.analysis.taint.TaintDomainFact +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst +import org.jacodb.ifds.domain.Edge +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.Vertex +import org.jacodb.ifds.messages.CommonMessage +import org.jacodb.ifds.messages.NewEdge +import org.jacodb.ifds.messages.ObtainData +import org.jacodb.ifds.result.IfdsComputationData +import org.jacodb.impl.features.usagesExt + +suspend fun ActorSystem.startTaintAnalysis(method: JcMethod) { + val cp = method.enclosingClass.classpath + val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) + val npeAnalyzer = NpeAnalyzer(graph) + + for (fact in npeAnalyzer.flowFunctions.obtainPossibleStartFacts(method)) { + for (entryPoint in graph.entryPoints(method)) { + val vertex = Vertex(entryPoint, fact) + val message = NewEdge(ForwardRunner, Edge(vertex, vertex), Reason.Initial) + send(message) + } + } +} + +suspend fun ActorSystem.collectTaintResults(): List = + collectTaintComputationData() + .results + .mapTo(mutableListOf()) { it.vulnerability } + +suspend fun ActorSystem.collectTaintComputationData(): IfdsComputationData = + ask { + ObtainData( + ForwardRunner, + it + ) + } diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintIfdsContext.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintIfdsContext.kt deleted file mode 100644 index 4878fb87f..000000000 --- a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/taint/TaintIfdsContext.kt +++ /dev/null @@ -1,84 +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.ifds.taint - -import org.jacodb.actors.api.ActorRef -import org.jacodb.actors.api.Factory -import org.jacodb.ifds.domain.Analyzer -import org.jacodb.ifds.domain.Chunk -import org.jacodb.ifds.domain.IfdsContext -import org.jacodb.ifds.domain.RunnerType -import org.jacodb.ifds.messages.CommonMessage -import org.jacodb.ifds.messages.NewResult -import org.jacodb.analysis.taint.EdgeForOtherRunner -import org.jacodb.analysis.taint.NewSummaryEdge -import org.jacodb.analysis.taint.NewVulnerability -import org.jacodb.analysis.taint.TaintDomainFact -import org.jacodb.analysis.taint.TaintEvent -import org.jacodb.api.JcClasspath -import org.jacodb.api.analysis.JcApplicationGraph -import org.jacodb.api.cfg.JcInst -import org.jacodb.impl.features.HierarchyExtensionImpl - -class TaintIfdsContext( - private val cp: JcClasspath, - private val graph: JcApplicationGraph, - private val jcAnalyzer: org.jacodb.analysis.ifds.Analyzer, - private val bannedPackagePrefixes: List, -) : IfdsContext { - data object Chunk1 : Chunk - data object Runner1 : RunnerType - - override fun chunkByMessage(message: CommonMessage): Chunk { - return Chunk1 - } - - override fun runnerTypeByMessage(message: CommonMessage): RunnerType { - return Runner1 - } - - override fun getAnalyzer(chunk: Chunk, type: RunnerType): Analyzer { - val wrapper = JcFlowFunctionsAdapter(Runner1, jcAnalyzer) { event -> - when (event) { - is EdgeForOtherRunner -> { - - } - - is NewSummaryEdge -> { - - } - - is NewVulnerability -> { - val result = NewResult(Runner1, event.vulnerability) - add(result) - } - } - } - - return TaintAnalyzer( - graph, - wrapper, - Runner1 - ) - } - - override fun indirectionHandlerFactory(parent: ActorRef) = - Factory { - IndirectionHandler(HierarchyExtensionImpl(cp), bannedPackagePrefixes, parent) - } - -} diff --git a/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/unused/Context.kt b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/unused/Context.kt new file mode 100644 index 000000000..05984a5fa --- /dev/null +++ b/jacodb-analysis/src/main/kotlin/org/jacodb/ifds/unused/Context.kt @@ -0,0 +1,18 @@ +/* + * 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.ifds.unused + 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 e85491853..033b639c1 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,6 +18,7 @@ package org.jacodb.analysis.impl import juliet.support.AbstractTestCase import kotlinx.coroutines.runBlocking +import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.graph.newApplicationGraphForAnalysis import org.jacodb.analysis.ifds.Vulnerability import org.jacodb.api.JcClasspath @@ -27,6 +28,7 @@ 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.impl.features.usagesExt import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB @@ -100,7 +102,7 @@ abstract class BaseAnalysisTest : BaseTest() { protected val graph: JcApplicationGraph by lazy { runBlocking { - cp.newApplicationGraphForAnalysis() + JcApplicationGraphImpl(cp, cp.usagesExt()) } } 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 df671f2be..b650fca49 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 @@ -16,21 +16,18 @@ package org.jacodb.analysis.impl -import org.jacodb.actors.impl.systemOf -import org.jacodb.ifds.actors.ProjectManager -import org.jacodb.ifds.domain.Edge -import org.jacodb.ifds.domain.Vertex -import org.jacodb.ifds.messages.NewEdge -import org.jacodb.ifds.messages.ObtainResults -import org.jacodb.ifds.taint.TaintIfdsContext import kotlinx.coroutines.runBlocking +import org.jacodb.actors.impl.systemOf import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.graph.defaultBannedPackagePrefixes -import org.jacodb.analysis.npe.NpeAnalyzer import org.jacodb.analysis.taint.TaintVulnerability import org.jacodb.api.JcMethod import org.jacodb.api.ext.constructors import org.jacodb.api.ext.findClass +import org.jacodb.ifds.actors.ProjectManager +import org.jacodb.ifds.npe.collectNpeResults +import org.jacodb.ifds.npe.npeIfdsContext +import org.jacodb.ifds.npe.startNpeAnalysis import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.Usages import org.jacodb.impl.features.usagesExt @@ -202,28 +199,12 @@ class IfdsNpeTest : BaseAnalysisTest() { } private fun findSinks(method: JcMethod): List = runBlocking { - val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) - val npeAnalyzer = NpeAnalyzer(graph) - val ifdsContext = TaintIfdsContext( - cp, - graph, - npeAnalyzer, - defaultBannedPackagePrefixes - ) - + val ifdsContext = npeIfdsContext(cp, graph, defaultBannedPackagePrefixes) val system = systemOf("ifds") { ProjectManager(ifdsContext) } - for (fact in npeAnalyzer.flowFunctions.obtainPossibleStartFacts(method)) { - for (entryPoint in graph.entryPoints(method)) { - val vertex = Vertex(entryPoint, fact) - val message = NewEdge(TaintIfdsContext.Runner1, Edge(vertex, vertex), Edge(vertex, vertex)) - system.send(message) - } - } - + system.startNpeAnalysis(method) system.awaitCompletion() - val results = system.ask { ObtainResults(it) } - results.map { it as TaintVulnerability } + system.collectNpeResults() } @ParameterizedTest 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 09ec7a4c6..607fd3163 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 @@ -20,26 +20,24 @@ import kotlinx.coroutines.runBlocking import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.jacodb.actors.impl.systemOf -import org.jacodb.analysis.graph.JcApplicationGraphImpl import org.jacodb.analysis.graph.defaultBannedPackagePrefixes 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.TaintVulnerability +import org.jacodb.analysis.taint.TaintZeroFact import org.jacodb.analysis.taint.toSarif import org.jacodb.api.JcMethod import org.jacodb.api.ext.findClass import org.jacodb.api.ext.methods import org.jacodb.ifds.actors.ProjectManager -import org.jacodb.ifds.domain.Edge -import org.jacodb.ifds.domain.Vertex -import org.jacodb.ifds.messages.NewEdge -import org.jacodb.ifds.messages.ObtainResults -import org.jacodb.ifds.taint.TaintIfdsContext +import org.jacodb.ifds.result.buildTraceGraph +import org.jacodb.ifds.taint.collectTaintComputationData +import org.jacodb.ifds.taint.collectTaintResults +import org.jacodb.ifds.taint.startTaintAnalysis +import org.jacodb.ifds.taint.taintIfdsContext import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.Usages -import org.jacodb.impl.features.usagesExt import org.jacodb.testing.WithDB import org.jacodb.testing.analysis.SqlInjectionExamples import org.junit.jupiter.api.Assertions.assertTrue @@ -69,43 +67,31 @@ class IfdsSqlTest : BaseAnalysisTest() { } @Test - fun `simple SQL injection`() { + fun `simple SQL injection`() = runBlocking { val methodName = "bad" val method = cp.findClass().declaredMethods.single { it.name == methodName } - val methods = listOf(method) - val unitResolver = SingletonUnitResolver - val manager = TaintManager(graph, unitResolver) - val sinks = manager.analyze(methods, timeout = 30.seconds) + + val ifdsContext = taintIfdsContext(cp, graph, defaultBannedPackagePrefixes) + val system = systemOf("ifds") { ProjectManager(ifdsContext) } + + system.startTaintAnalysis(method) + system.awaitCompletion() + val data = system.collectTaintComputationData() + val sinks = data.results assertTrue(sinks.isNotEmpty()) val sink = sinks.first() - val graph = manager.vulnerabilityTraceGraph(sink) + val graph = data.buildTraceGraph(sink.vertex, zeroFact = TaintZeroFact) val trace = graph.getAllTraces().first() assertTrue(trace.isNotEmpty()) } private fun findSinks(method: JcMethod): List = runBlocking { - val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) - val taintAnalyzer = org.jacodb.analysis.taint.TaintAnalyzer(graph) - val ifdsContext = TaintIfdsContext( - cp, - graph, - taintAnalyzer, - defaultBannedPackagePrefixes - ) - + val ifdsContext = taintIfdsContext(cp, graph, defaultBannedPackagePrefixes) val system = systemOf("ifds") { ProjectManager(ifdsContext) } - for (fact in taintAnalyzer.flowFunctions.obtainPossibleStartFacts(method)) { - for (entryPoint in graph.entryPoints(method)) { - val vertex = Vertex(entryPoint, fact) - val message = NewEdge(TaintIfdsContext.Runner1, Edge(vertex, vertex), Edge(vertex, vertex)) - system.send(message) - } - } - + system.startTaintAnalysis(method) system.awaitCompletion() - val results = system.ask { ObtainResults(it) } - results.map { it as TaintVulnerability } + system.collectTaintResults() } 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 e20442d2c..fc1d86516 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 @@ -55,6 +55,31 @@ class IfdsUnusedTest : BaseAnalysisTest() { ) } +// private fun findSinks(): List = runBlocking { +// val graph = JcApplicationGraphImpl(cp, cp.usagesExt()) +// val unusedVariableAnalyzer = UnusedVariableAnalyzer(graph) +// val ifdsContext = JcIfdsContext( +// cp, +// graph, +// unusedVariableAnalyzer, +// defaultBannedPackagePrefixes +// ) +// +// val system = systemOf("ifds") { ProjectManager(ifdsContext) } +// +// for (fact in unusedVariableAnalyzer.flowFunctions.obtainPossibleStartFacts(method)) { +// for (entryPoint in graph.entryPoints(method)) { +// val vertex = Vertex(entryPoint, fact) +// val message = NewEdge(JcIfdsContext.ForwardRunner, Edge(vertex, vertex), Reason.Initial) +// system.send(message) +// } +// } +// +// system.awaitCompletion() +// val results = system.ask { ObtainData(JcIfdsContext.ForwardRunner, it) } +// results.map { it as UnusedVariableVulnerability } +// } + @ParameterizedTest @MethodSource("provideClassesForJuliet563") fun `test on Juliet's CWE 563`(className: String) { 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 8b3cb41c2..72a1c3725 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,14 +17,24 @@ package org.jacodb.analysis.impl import kotlinx.coroutines.runBlocking +import org.jacodb.actors.impl.systemOf +import org.jacodb.analysis.graph.JcApplicationGraphImpl +import org.jacodb.analysis.graph.defaultBannedPackagePrefixes 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.taint.TaintVulnerability 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.ifds.actors.ProjectManager +import org.jacodb.ifds.taint.collectTaintResults +import org.jacodb.ifds.taint.startTaintAnalysis +import org.jacodb.ifds.taint.taintIfdsContext +import org.jacodb.impl.features.usagesExt import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.testing.BaseTest import org.jacodb.testing.WithGlobalDB @@ -53,10 +63,19 @@ class JodaDateTimeAnalysisTest : BaseTest() { private val graph: JcApplicationGraph by lazy { runBlocking { - cp.newApplicationGraphForAnalysis() + JcApplicationGraphImpl(cp, cp.usagesExt()) } } + private fun findSinks(method: JcMethod): List = runBlocking { + val ifdsContext = taintIfdsContext(cp, graph, defaultBannedPackagePrefixes) + val system = systemOf("ifds") { ProjectManager(ifdsContext) } + + system.startTaintAnalysis(method) + system.awaitCompletion() + system.collectTaintResults() + } + @Test fun `test taint analysis`() { val clazz = cp.findClass() diff --git a/jacodb-analysis/src/test/resources/simplelogger.properties b/jacodb-analysis/src/test/resources/simplelogger.properties index 36ae2abd2..e54e022a9 100644 --- a/jacodb-analysis/src/test/resources/simplelogger.properties +++ b/jacodb-analysis/src/test/resources/simplelogger.properties @@ -4,12 +4,12 @@ # 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=error +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.ifds=error +org.slf4j.simpleLogger.log.org.jacodb.analysis.ifds=info # 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-ifds/actors/src/main/kotlin/org/jacodb/actors/impl/ActorSystemImpl.kt b/jacodb-ifds/actors/src/main/kotlin/org/jacodb/actors/impl/ActorSystemImpl.kt index d6faa9189..05bb8074d 100644 --- a/jacodb-ifds/actors/src/main/kotlin/org/jacodb/actors/impl/ActorSystemImpl.kt +++ b/jacodb-ifds/actors/src/main/kotlin/org/jacodb/actors/impl/ActorSystemImpl.kt @@ -48,7 +48,7 @@ internal class ActorSystemImpl( override suspend fun ask(messageBuilder: (Channel) -> Message): R { watcher.send(WatcherMessage.OutOfSystemSend) - val channel = Channel(Channel.RENDEZVOUS) + val channel = Channel(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) val ack = messageBuilder(channel) user.send(ack) val received = channel.receive() diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/ChunkManager.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/ChunkManager.kt index 264a20cc6..d83c8c079 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/ChunkManager.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/ChunkManager.kt @@ -20,21 +20,21 @@ import org.jacodb.actors.api.Actor import org.jacodb.actors.api.ActorRef import org.jacodb.actors.api.ActorContext import org.jacodb.actors.impl.routing.messageKeyRouter -import org.jacodb.ifds.domain.Chunk +import org.jacodb.ifds.domain.ChunkId import org.jacodb.ifds.domain.IfdsContext import org.jacodb.ifds.messages.CommonMessage context(ActorContext) class ChunkManager( private val ifdsContext: IfdsContext, - private val chunk: Chunk, + private val chunkId: ChunkId, private val parent: ActorRef, ) : Actor { private val routerFactory = messageKeyRouter( - ifdsContext::runnerTypeByMessage - ) { runnerType -> - Runner(this@ActorContext.self, ifdsContext, chunk, runnerType) + ifdsContext::runnerIdByMessage + ) { runnerId -> + Runner(this@ActorContext.self, ifdsContext, chunkId, runnerId) } private val router = spawn( @@ -44,7 +44,7 @@ class ChunkManager( override suspend fun receive(message: CommonMessage) { when { - chunk == ifdsContext.chunkByMessage(message) -> router.send(message) + chunkId == ifdsContext.chunkByMessage(message) -> router.send(message) else -> parent.send(message) } diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/Runner.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/Runner.kt index 8f115cc1c..092b17e5f 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/Runner.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/Runner.kt @@ -20,9 +20,9 @@ import org.jacodb.actors.api.Actor import org.jacodb.actors.api.ActorContext import org.jacodb.actors.api.ActorRef import org.jacodb.actors.impl.routing.firstReadyRouter -import org.jacodb.ifds.domain.Chunk +import org.jacodb.ifds.domain.ChunkId import org.jacodb.ifds.domain.IfdsContext -import org.jacodb.ifds.domain.RunnerType +import org.jacodb.ifds.domain.RunnerId import org.jacodb.ifds.messages.AnalyzerMessage import org.jacodb.ifds.messages.CommonMessage import org.jacodb.ifds.messages.IndirectionMessage @@ -32,27 +32,27 @@ context(ActorContext) class Runner( private val parent: ActorRef, private val ifdsContext: IfdsContext, - private val chunk: Chunk, - private val runnerType: RunnerType, + private val chunkId: ChunkId, + private val runnerId: RunnerId, ) : Actor { private val routerFactory = firstReadyRouter(size = 8) { - Worker(ifdsContext.getAnalyzer(chunk, runnerType), this@ActorContext.self) + Worker(ifdsContext.getAnalyzer(chunkId, runnerId), this@ActorContext.self) } private val router = spawn("workers", factory = routerFactory) private val storage = spawn("storage") { - RunnerStorage(this@ActorContext.self, runnerType) + RunnerStorage(this@ActorContext.self, chunkId, runnerId) } private val indirectionHandler = spawn( "indirection", - factory = ifdsContext.indirectionHandlerFactory(self) + factory = ifdsContext.indirectionHandlerFactory(this@ActorContext.self, runnerId) ) override suspend fun receive(message: CommonMessage) { when { - ifdsContext.chunkByMessage(message) == chunk && ifdsContext.runnerTypeByMessage(message) == runnerType -> { + ifdsContext.chunkByMessage(message) == chunkId && ifdsContext.runnerIdByMessage(message) == runnerId -> { @Suppress("UNCHECKED_CAST") when (message) { is StorageMessage -> storage.send(message) diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/RunnerStorage.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/RunnerStorage.kt index ce60efa3f..c074b5256 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/RunnerStorage.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/actors/RunnerStorage.kt @@ -19,8 +19,10 @@ package org.jacodb.ifds.actors import org.jacodb.actors.api.Actor import org.jacodb.actors.api.ActorContext import org.jacodb.actors.api.ActorRef +import org.jacodb.ifds.domain.ChunkId import org.jacodb.ifds.domain.Edge -import org.jacodb.ifds.domain.RunnerType +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.RunnerId import org.jacodb.ifds.domain.Vertex import org.jacodb.ifds.messages.CommonMessage import org.jacodb.ifds.messages.EdgeMessage @@ -29,34 +31,37 @@ import org.jacodb.ifds.messages.NewResult import org.jacodb.ifds.messages.NewSummaryEdge import org.jacodb.ifds.messages.NotificationOnEnd import org.jacodb.ifds.messages.NotificationOnStart -import org.jacodb.ifds.messages.ObtainResults +import org.jacodb.ifds.messages.ObtainData import org.jacodb.ifds.messages.StorageMessage import org.jacodb.ifds.messages.SubscriptionOnEnd import org.jacodb.ifds.messages.SubscriptionOnStart +import org.jacodb.ifds.result.IfdsComputationData +import org.jacodb.ifds.result.IfdsResult context(ActorContext) class RunnerStorage( private val parent: ActorRef, - private val runnerType: RunnerType, + private val chunkId: ChunkId, + private val runnerId: RunnerId, ) : Actor { data class SubscriptionData( val edge: Edge, - val subscriber: RunnerType, + val subscriber: RunnerId, ) - private val endSubscribers = - HashMap, HashSet>>() private val startSubscribers = HashMap, HashSet>>() + private val endSubscribers = + HashMap, HashSet>>() private val edges = hashSetOf>() - private val reasons = hashMapOf, HashSet>>() + private val reasons = hashMapOf, HashSet>>() private val summaryEdges = hashSetOf>() private val summaryEdgesByStart = hashMapOf, HashSet>>() private val summaryEdgesByEnd = hashMapOf, HashSet>>() - private val foundResults = hashSetOf() + private val foundResults = hashSetOf>() override suspend fun receive(message: StorageMessage) { when (message) { @@ -71,8 +76,7 @@ class RunnerStorage( if (edges.add(edge)) { // new edge - parent.send(EdgeMessage(edge)) - + parent.send(EdgeMessage(runnerId, edge)) } } @@ -121,12 +125,24 @@ class RunnerStorage( .add(subscriptionData) } - is NewResult -> { + is NewResult<*, *> -> { + @Suppress("UNCHECKED_CAST") + message as NewResult foundResults.add(message.result) } - is ObtainResults -> { - message.channel.send(foundResults.toList()) + is ObtainData<*, *, *> -> { + @Suppress("UNCHECKED_CAST") + message as ObtainData> + val data = IfdsComputationData( + chunkId, + runnerId, + edges.groupByTo(hashMapOf()) { it.to }, + edges.groupByTo(hashMapOf(), { it.to.stmt }) { it.to.fact }, + reasons.toMap(hashMapOf()), + foundResults.toHashSet() + ) + message.channel.send(data) } } } @@ -139,7 +155,7 @@ class RunnerStorage( for (summaryEdge in summaries) { val notification = NotificationOnStart( subscriptionData.subscriber, - runnerType, + runnerId, summaryEdge, subscriptionData.edge ) @@ -155,7 +171,7 @@ class RunnerStorage( for (summaryEdge in summaries) { val notification = NotificationOnEnd( subscriptionData.subscriber, - runnerType, + runnerId, summaryEdge, subscriptionData.edge ) @@ -171,7 +187,7 @@ class RunnerStorage( for ((data, subscriber) in currentEdgeStartSubscribers) { val notification = NotificationOnStart( subscriber, - runnerType, + runnerId, edge, data ) @@ -186,7 +202,7 @@ class RunnerStorage( for ((data, subscriber) in currentEdgeEndSubscribers) { val notification = NotificationOnEnd( subscriber, - runnerType, + runnerId, edge, data ) diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Chunk.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/ChunkId.kt similarity index 97% rename from jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Chunk.kt rename to jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/ChunkId.kt index b3cca8949..39e11ab86 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Chunk.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/ChunkId.kt @@ -16,4 +16,4 @@ package org.jacodb.ifds.domain -interface Chunk +interface ChunkId diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/IfdsContext.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/IfdsContext.kt index 6308a6eb4..480ae449f 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/IfdsContext.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/IfdsContext.kt @@ -22,9 +22,9 @@ import org.jacodb.ifds.messages.CommonMessage import org.jacodb.ifds.messages.IndirectionMessage interface IfdsContext { - fun chunkByMessage(message: CommonMessage): Chunk - fun runnerTypeByMessage(message: CommonMessage): RunnerType + fun chunkByMessage(message: CommonMessage): ChunkId + fun runnerIdByMessage(message: CommonMessage): RunnerId - fun getAnalyzer(chunk: Chunk, type: RunnerType): Analyzer - fun indirectionHandlerFactory(parent: ActorRef): Factory + fun getAnalyzer(chunkId: ChunkId, runnerId: RunnerId): Analyzer + fun indirectionHandlerFactory(parent: ActorRef, runnerId: RunnerId): Factory } \ No newline at end of file diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Reason.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Reason.kt new file mode 100644 index 000000000..b0d818916 --- /dev/null +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/Reason.kt @@ -0,0 +1,43 @@ +/* + * 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.ifds.domain + +sealed interface Reason { + data object Initial : Reason + + data class Sequent( + val edge: Edge, + ) : Reason + + data class CallToReturn( + val edge: Edge + ) : Reason + + data class CallToStart( + val edge: Edge, + ) : Reason + + data class ExitToReturnSite( + val callerEdge: Edge, + val edge: Edge, + ) : Reason + + data class FromOtherRunner( + val edge: Edge, + val otherRunnerId: RunnerId, + ) : Reason +} diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/RunnerType.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/RunnerId.kt similarity index 97% rename from jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/RunnerType.kt rename to jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/RunnerId.kt index a04ddd64a..9b4941308 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/RunnerType.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/domain/RunnerId.kt @@ -16,4 +16,4 @@ package org.jacodb.ifds.domain -interface RunnerType \ No newline at end of file +interface RunnerId \ No newline at end of file diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/AnalyzerMessages.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/AnalyzerMessages.kt index e5b84bd1f..d036cfed0 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/AnalyzerMessages.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/AnalyzerMessages.kt @@ -17,29 +17,31 @@ package org.jacodb.ifds.messages import org.jacodb.ifds.domain.Edge -import org.jacodb.ifds.domain.RunnerType +import org.jacodb.ifds.domain.RunnerId interface AnalyzerMessage : CommonMessage data class EdgeMessage( - val edge: Edge + override val runnerId: RunnerId, + val edge: Edge, ) : AnalyzerMessage data class ResolvedCall( + override val runnerId: RunnerId, val edge: Edge, - val method: Method + val method: Method, ) : AnalyzerMessage data class NotificationOnStart( - val runnerType: RunnerType, - val author: RunnerType, + override val runnerId: RunnerId, + val author: RunnerId, val edge: Edge, val data: Edge, ) : AnalyzerMessage data class NotificationOnEnd( - val runnerType: RunnerType, - val author: RunnerType, + override val runnerId: RunnerId, + val author: RunnerId, val edge: Edge, - val data: Edge + val data: Edge, ) : AnalyzerMessage diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/CommonMessages.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/CommonMessages.kt index 273c505c2..71bfe6fe3 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/CommonMessages.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/CommonMessages.kt @@ -16,4 +16,8 @@ package org.jacodb.ifds.messages -sealed interface CommonMessage \ No newline at end of file +import org.jacodb.ifds.domain.RunnerId + +sealed interface CommonMessage { + val runnerId: RunnerId +} \ No newline at end of file diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/IndirectionMessages.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/IndirectionMessages.kt index 2029422e7..123dfa53c 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/IndirectionMessages.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/IndirectionMessages.kt @@ -17,9 +17,11 @@ package org.jacodb.ifds.messages import org.jacodb.ifds.domain.Edge +import org.jacodb.ifds.domain.RunnerId interface IndirectionMessage : CommonMessage data class UnresolvedCall( - val edge: Edge + override val runnerId: RunnerId, + val edge: Edge, ) : IndirectionMessage diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/StorageMessages.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/StorageMessages.kt index 7a4662dd0..5d7a2bade 100644 --- a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/StorageMessages.kt +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/messages/StorageMessages.kt @@ -17,42 +17,46 @@ package org.jacodb.ifds.messages import org.jacodb.ifds.domain.Edge -import org.jacodb.ifds.domain.RunnerType +import org.jacodb.ifds.domain.RunnerId import org.jacodb.ifds.domain.Vertex import kotlinx.coroutines.channels.Channel +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.result.IfdsComputationData +import org.jacodb.ifds.result.IfdsResult sealed interface StorageMessage : CommonMessage data class NewEdge( - val runnerType: RunnerType, + override val runnerId: RunnerId, val edge: Edge, - val reason: Edge, + val reason: Reason, ) : StorageMessage data class NewSummaryEdge( - val runnerType: RunnerType, + override val runnerId: RunnerId, val edge: Edge, ) : StorageMessage data class SubscriptionOnStart( - val runnerType: RunnerType, + override val runnerId: RunnerId, val startVertex: Vertex, - val subscriber: RunnerType, + val subscriber: RunnerId, val data: Edge, ) : StorageMessage data class SubscriptionOnEnd( - val runnerType: RunnerType, + override val runnerId: RunnerId, val endVertex: Vertex, - val subscriber: RunnerType, + val subscriber: RunnerId, val data: Edge, ) : StorageMessage -data class NewResult( - val author: RunnerType, - val result: Any?, +data class NewResult( + override val runnerId: RunnerId, + val result: IfdsResult, ) : StorageMessage -data class ObtainResults( - val channel: Channel> -) : StorageMessage \ No newline at end of file +data class ObtainData>( + override val runnerId: RunnerId, + val channel: Channel>, +) : StorageMessage diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsComputationData.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsComputationData.kt new file mode 100644 index 000000000..cb9b5f686 --- /dev/null +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsComputationData.kt @@ -0,0 +1,35 @@ +/* + * 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.ifds.result + +import org.jacodb.ifds.domain.ChunkId +import org.jacodb.ifds.domain.Edge +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.RunnerId +import org.jacodb.ifds.domain.Vertex + +/** + * Aggregates all facts and edges found by the tabulation algorithm. + */ +data class IfdsComputationData>( + val chunkId: ChunkId, + val runnerId: RunnerId, + val edgesByEnd: Map, Collection>>, + val facts: Map>, + val reasons: Map, Collection>>, + val results: Collection, +) diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsResult.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsResult.kt new file mode 100644 index 000000000..d0b3a71e7 --- /dev/null +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/IfdsResult.kt @@ -0,0 +1,23 @@ +/* + * 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.ifds.result + +import org.jacodb.ifds.domain.Vertex + +interface IfdsResult { + val vertex: Vertex +} \ No newline at end of file diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/Merging.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/Merging.kt new file mode 100644 index 000000000..d937af817 --- /dev/null +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/Merging.kt @@ -0,0 +1,23 @@ +/* + * 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.ifds.result + +fun > mergeIfdsResults( + ifdsResults: Collection>, +): IfdsComputationData { + TODO() +} \ No newline at end of file diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/SingleStorageTraceGraph.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/SingleStorageTraceGraph.kt new file mode 100644 index 000000000..d76e5776d --- /dev/null +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/SingleStorageTraceGraph.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.ifds.result + +import org.jacodb.ifds.domain.Edge +import org.jacodb.ifds.domain.Reason +import org.jacodb.ifds.domain.Vertex + +data class SingleStorageTraceGraph( + override val sink: Vertex, + override val sources: Set>, + override val edges: Map, MutableSet>>, +) : TraceGraph { + /** + * Returns all traces from [sources] to [sink]. + */ + override 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() + } + } + } +} + +fun IfdsComputationData.buildTraceGraph( + sink: Vertex, + zeroFact: Fact? = null, +): TraceGraph { + val sources: MutableSet> = hashSetOf() + val edges: MutableMap, MutableSet>> = hashMapOf() + val visited: MutableSet, Vertex>> = 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 to lastVertex)) { + return + } + + // Note: loop-edge represents method start + if (stopAtMethodStart && edge.from == edge.to) { + addEdge(edge.from, lastVertex) + return + } + + val vertex = edge.to + if (vertex.fact == zeroFact) { + addEdge(vertex, lastVertex) + sources.add(vertex) + return + } + + for (reason in reasons[edge].orEmpty()) { + when (reason) { + Reason.Initial -> { + sources.add(vertex) + addEdge(edge.to, lastVertex) + } + + is 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.CallToReturn -> { + 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 -> { + val predEdge = reason.edge + if (!stopAtMethodStart) { + addEdge(predEdge.to, lastVertex) + dfs(predEdge, predEdge.to, false) + } + } + + is Reason.ExitToReturnSite -> { + val predEdge = reason.callerEdge + val summaryEdge = reason.edge + addEdge(summaryEdge.from, lastVertex) + addEdge(predEdge.to, summaryEdge.from) + dfs(summaryEdge, summaryEdge.to, true) + dfs(predEdge, predEdge.to, stopAtMethodStart) + } + + is Reason.FromOtherRunner -> { + TODO("Reason from other runner is not supported yet") + } + } + } + } + + for (edge in edgesByEnd[sink].orEmpty()) { + dfs(edge, edge.to, false) + } + + return SingleStorageTraceGraph(sink, sources, edges) +} diff --git a/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/TraceGraph.kt b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/TraceGraph.kt new file mode 100644 index 000000000..19dbdcbb9 --- /dev/null +++ b/jacodb-ifds/ifds/src/main/kotlin/org/jacodb/ifds/result/TraceGraph.kt @@ -0,0 +1,30 @@ +/* + * 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.ifds.result + +import org.jacodb.ifds.domain.Vertex + +interface TraceGraph { + val sink: Vertex + val sources: Collection> + val edges: Map, Collection>> + + /** + * Returns all traces from [sources] to [sink]. + */ + fun getAllTraces(): Sequence>> +} \ No newline at end of file