Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow-less summary storage #222

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.ifds

import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference

interface Producer<T> {
fun produce(event: T)
fun subscribe(consumer: Consumer<T>)
}

fun interface Consumer<in T> {
fun consume(event: T)
}

class SyncProducer<T> : Producer<T> {
private val consumers: MutableList<Consumer<T>> = mutableListOf()
private val events: MutableList<T> = mutableListOf()

@Synchronized
override fun produce(event: T) {
for (consumer in consumers) {
consumer.consume(event)
}
events.add(event)
}

@Synchronized
override fun subscribe(consumer: Consumer<T>) {
for (event in events) {
consumer.consume(event)
}
consumers.add(consumer)
}
}

sealed interface ConsList<out T> : Iterable<T>

object Nil : ConsList<Nothing> {
override fun iterator(): Iterator<Nothing> = object : Iterator<Nothing> {
override fun hasNext(): Boolean = false

Check warning on line 56 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L55-L56

Added lines #L55 - L56 were not covered by tests
override fun next(): Nothing {
throw NoSuchElementException()

Check warning on line 58 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L58

Added line #L58 was not covered by tests
}
}

Check warning on line 60 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L60

Added line #L60 was not covered by tests

override fun toString(): String = javaClass.simpleName

Check warning on line 62 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L62

Added line #L62 was not covered by tests
}

data class Cons<out T>(
val value: T,
val tail: ConsList<T>,

Check warning on line 67 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L65-L67

Added lines #L65 - L67 were not covered by tests
) : ConsList<T> {
override fun iterator(): Iterator<T> = Iter(this)

Check warning on line 69 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L69

Added line #L69 was not covered by tests

private class Iter<T>(private var list: ConsList<T>) : Iterator<T> {

Check warning on line 71 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L71

Added line #L71 was not covered by tests
override fun hasNext(): Boolean = list !is Nil

override fun next(): T = when (val list = list) {

Check warning on line 74 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L74

Added line #L74 was not covered by tests
is Nil -> throw NoSuchElementException()
is Cons -> {
val value = list.value
this.list = list.tail
value

Check warning on line 79 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L76-L79

Added lines #L76 - L79 were not covered by tests
}
}

Check warning on line 81 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L81

Added line #L81 was not covered by tests
}
}

class NonBlockingQueue<T> {
data class Node<T>(
val value: T,
@Volatile var next: Node<T>? = null,
)

Check warning on line 89 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L85-L89

Added lines #L85 - L89 were not covered by tests

var head: Node<T>? = null

Check warning on line 91 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L91

Added line #L91 was not covered by tests
private set
val tail: AtomicReference<Node<T>> = AtomicReference(head)
val size: AtomicInteger = AtomicInteger(0)

Check warning on line 94 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L93-L94

Added lines #L93 - L94 were not covered by tests

fun add(element: T) {
val node = Node(element)

Check warning on line 97 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L97

Added line #L97 was not covered by tests
var currentTail: Node<T>?
while (true) {
currentTail = tail.get()

Check warning on line 100 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L99-L100

Added lines #L99 - L100 were not covered by tests
if (tail.compareAndSet(currentTail, node)) break
}
if (currentTail != null) {
currentTail.next = node

Check warning on line 104 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L104

Added line #L104 was not covered by tests
} else {
head = node

Check warning on line 106 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L106

Added line #L106 was not covered by tests
}
size.incrementAndGet()
}

Check warning on line 109 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L108-L109

Added lines #L108 - L109 were not covered by tests
}

class ConcurrentProducer<T> : Producer<T> {
private var consumers: AtomicReference<ConsList<Consumer<T>>> = AtomicReference(Nil)
private val events: NonBlockingQueue<T> = NonBlockingQueue()

Check warning on line 114 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L112-L114

Added lines #L112 - L114 were not covered by tests

override fun produce(event: T) {
var currentConsumers: ConsList<Consumer<T>>
while (true) {

Check warning on line 118 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L118

Added line #L118 was not covered by tests
currentConsumers = consumers.get() ?: continue
if (consumers.compareAndSet(currentConsumers, null)) break
}

events.add(event)

Check warning on line 123 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L123

Added line #L123 was not covered by tests

try {

Check warning on line 125 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L125

Added line #L125 was not covered by tests
for (consumer in currentConsumers) {
consumer.consume(event)

Check warning on line 127 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L127

Added line #L127 was not covered by tests
}
} finally {
check(consumers.compareAndSet(null, currentConsumers))
}
}

Check warning on line 132 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L132

Added line #L132 was not covered by tests

override fun subscribe(consumer: Consumer<T>) {
var last: NonBlockingQueue.Node<T>? = null
while (true) {

Check warning on line 136 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L135-L136

Added lines #L135 - L136 were not covered by tests
val start = if (last != null) last.next else events.head
var current = start

Check warning on line 138 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L138

Added line #L138 was not covered by tests
while (current != null) {
last = current
consumer.consume(current.value)
current = current.next

Check warning on line 142 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L140-L142

Added lines #L140 - L142 were not covered by tests
}

val currentConsumers = consumers.get() ?: continue
if (!consumers.compareAndSet(currentConsumers, null)) continue
if (events.tail.get() === last) {
val newConsumers = Cons(consumer, currentConsumers)

Check warning on line 148 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L148

Added line #L148 was not covered by tests
check(consumers.compareAndSet(null, newConsumers))
break

Check warning on line 150 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L150

Added line #L150 was not covered by tests
} else {
check(consumers.compareAndSet(null, currentConsumers))
}
}
}

Check warning on line 155 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Producer.kt#L155

Added line #L155 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class UniRunner<Fact, Event>(

// Add edge to worklist:
workList.trySend(edge).getOrThrow()
manager.handleControlEvent(queueIsNotEmpty)

return true
}
Expand All @@ -122,7 +123,6 @@ class UniRunner<Fact, Event>(
val edge = workList.tryReceive().getOrElse {
manager.handleControlEvent(queueIsEmpty)
val edge = workList.receive()
manager.handleControlEvent(queueIsNotEmpty)
edge
}
tabulationAlgorithmStep(edge, this@coroutineScope)
Expand Down
71 changes: 45 additions & 26 deletions jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.jacodb.analysis.ifds

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import org.jacodb.api.JcMethod
Expand Down Expand Up @@ -48,56 +47,76 @@
/**
* Contains summaries for many methods and allows to update them and subscribe for them.
*/
interface SummaryStorage<T : Summary> {
class SummaryStorageWithFlows<T : Summary> {
private val summaries = ConcurrentHashMap<JcMethod, MutableSet<T>>()
private val outFlows = ConcurrentHashMap<JcMethod, MutableSharedFlow<T>>()

/**
* A list of all methods for which summaries are not empty.
* @return a list with all methods for which there are some summaries.
*/
val knownMethods: List<JcMethod>
val knownMethods: List<JcMethod>
get() = summaries.keys.toList()

private fun getFlow(method: JcMethod): MutableSharedFlow<T> {
return outFlows.computeIfAbsent(method) {
MutableSharedFlow(replay = Int.MAX_VALUE)
}
}

/**
* Adds [fact] to summary of its method.
* Adds a new [fact] to the storage.
*/
fun add(fact: T)
fun add(fact: T) {
val isNew = summaries.computeIfAbsent(fact.method) { ConcurrentHashMap.newKeySet() }.add(fact)
if (isNew) {
val flow = getFlow(fact.method)
check(flow.tryEmit(fact))
}
}

/**
* @return a flow with all facts summarized for the given [method].
* Already received facts, along with the facts that will be sent to this storage later,
* will be emitted to the returned flow.
*/
fun getFacts(method: JcMethod): Flow<T>
fun getFacts(method: JcMethod): SharedFlow<T> {
return getFlow(method)
}

/**
* @return a list will all facts summarized for the given [method] so far.
*/
fun getCurrentFacts(method: JcMethod): List<T>
fun getCurrentFacts(method: JcMethod): List<T> {
return getFacts(method).replayCache
}
}

class SummaryStorageImpl<T : Summary> : SummaryStorage<T> {
class SummaryStorageWithProducers<T : Summary>(
private val useConcurrentProducer: Boolean = false,
) {
private val summaries = ConcurrentHashMap<JcMethod, MutableSet<T>>()
private val outFlows = ConcurrentHashMap<JcMethod, MutableSharedFlow<T>>()

override val knownMethods: List<JcMethod>
get() = summaries.keys.toList()

private fun getFlow(method: JcMethod): MutableSharedFlow<T> {
return outFlows.computeIfAbsent(method) {
MutableSharedFlow(replay = Int.MAX_VALUE)
private val producers = ConcurrentHashMap<JcMethod, Producer<T>>()

private fun getProducer(method: JcMethod): Producer<T> {
return producers.computeIfAbsent(method) {
if (useConcurrentProducer) {
ConcurrentProducer()

Check warning on line 103 in jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/ifds/Summary.kt#L103

Added line #L103 was not covered by tests
} else {
SyncProducer()
}
}
}

override fun add(fact: T) {
fun add(fact: T) {
val isNew = summaries.computeIfAbsent(fact.method) { ConcurrentHashMap.newKeySet() }.add(fact)
if (isNew) {
val flow = getFlow(fact.method)
check(flow.tryEmit(fact))
val producer = getProducer(fact.method)
producer.produce(fact)
}
}

override fun getFacts(method: JcMethod): SharedFlow<T> {
return getFlow(method)
}

override fun getCurrentFacts(method: JcMethod): List<T> {
return getFacts(method).replayCache
fun subscribe(method: JcMethod, handler: (T) -> Unit) {
val producer = getProducer(method)
producer.subscribe(handler)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ 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
Expand All @@ -34,7 +32,9 @@ 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
import org.jacodb.analysis.ifds.SummaryEdge
import org.jacodb.analysis.ifds.SummaryStorageWithFlows
import org.jacodb.analysis.ifds.SummaryStorageWithProducers
import org.jacodb.analysis.ifds.TraceGraph
import org.jacodb.analysis.ifds.UniRunner
import org.jacodb.analysis.ifds.UnitResolver
Expand Down Expand Up @@ -63,8 +63,8 @@ open class TaintManager(
protected val runnerForUnit: MutableMap<UnitType, TaintRunner> = hashMapOf()
private val queueIsEmpty = ConcurrentHashMap<UnitType, Boolean>()

private val summaryEdgesStorage = SummaryStorageImpl<TaintSummaryEdge>()
private val vulnerabilitiesStorage = SummaryStorageImpl<TaintVulnerability>()
private val summaryEdgesStorage = SummaryStorageWithProducers<SummaryEdge<TaintDomainFact>>()
private val vulnerabilitiesStorage = SummaryStorageWithFlows<TaintVulnerability>()

private val stopRendezvous = Channel<Unit>(Channel.RENDEZVOUS)

Expand Down Expand Up @@ -277,10 +277,7 @@ open class TaintManager(
scope: CoroutineScope,
handler: (TaintEdge) -> Unit,
) {
summaryEdgesStorage
.getFacts(method)
.onEach { handler(it.edge) }
.launchIn(scope)
summaryEdgesStorage.subscribe(method) { handler(it.edge) }
}

fun vulnerabilityTraceGraph(vulnerability: TaintVulnerability): TraceGraph<TaintDomainFact> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ 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.SummaryStorageWithFlows
import org.jacodb.analysis.ifds.UniRunner
import org.jacodb.analysis.ifds.UnitResolver
import org.jacodb.analysis.ifds.UnitType
Expand Down Expand Up @@ -62,8 +62,7 @@ class UnusedVariableManager(
private val runnerForUnit: MutableMap<UnitType, Runner<UnusedVariableDomainFact>> = hashMapOf()
private val queueIsEmpty = ConcurrentHashMap<UnitType, Boolean>()

private val summaryEdgesStorage = SummaryStorageImpl<UnusedVariableSummaryEdge>()
private val vulnerabilitiesStorage = SummaryStorageImpl<UnusedVariableVulnerability>()
private val summaryEdgesStorage = SummaryStorageWithFlows<UnusedVariableSummaryEdge>()

private val stopRendezvous = Channel<Unit>(Channel.RENDEZVOUS)

Expand Down
41 changes: 41 additions & 0 deletions jacodb-analysis/src/test/resources/additional.json
Original file line number Diff line number Diff line change
Expand Up @@ -603,5 +603,46 @@
}
}
]
},
{
"_": "MethodSource",
"methodInfo": {
"cls": {
"classNameMatcher": {
"_": "NameIsEqualTo",
"name": "Properties"
},
"packageMatcher": {
"_": "NameIsEqualTo",
"name": "java.util"
}
},
"functionName": {
"_": "NameIsEqualTo",
"name": "getProperty"
},
"applyToOverrides": true,
"exclude": [],
"functionLabel": null,
"modifier": -1,
"parametersMatchers": [],
"returnTypeMatcher": {
"_": "AnyTypeMatches"
}
},
"condition": {
"_": "ConstantTrue"
},
"actionsAfter": [
{
"_": "AssignMark",
"position": {
"_": "Result"
},
"mark": {
"name": "PROPERTY"
}
}
]
}
]
Loading