Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3ceb3bd
feat(ifds): add field-insensitive BaseOnly access-path mode
Saloed Aug 19, 2026
bb21fb8
test: add a stub dependency module for regression samples
Saloed Aug 19, 2026
4f5d297
chore: tidy imports and trailing whitespace
Saloed Aug 19, 2026
3109aea
fix(analyzer): wire catch handlers from try boundaries
Saloed Aug 19, 2026
1ad0460
test: let the JVM analysis harness pick an ap mode and inspect the en…
Saloed Aug 19, 2026
7dd2e2b
test: cover source-sink fingerprints for rules sharing a sink
Saloed Aug 19, 2026
78d1330
perf(ifds): process reachability edges before taint edges
Saloed Aug 19, 2026
ce7768d
perf(analyzer): cut allocation and lock contention on the rule-lookup…
Saloed Aug 19, 2026
4ec5d76
fix(analyzer): scope the override cache by base class and prune impos…
Saloed Aug 19, 2026
b91a8b3
perf(analyzer): memoise raw call resolution per statement
Saloed Aug 19, 2026
0620f32
fix(ifds): correct state that must not survive an ap-manager switch
Saloed Aug 19, 2026
9363ce5
perf(ifds): quotient trace resolution over premises and boundaries
Saloed Aug 19, 2026
f2fdb52
feat(analyzer): stage analysis and select the full scan's rules from …
Saloed Aug 19, 2026
64e3793
fix(analyzer): preserve overloaded Spring controller entry points
Saloed Aug 19, 2026
187c101
perf(ifds): share context-independent shallow analysis
Saloed Aug 19, 2026
dfa90d2
perf(ifds): key the fact-to-fact worklist by conclusion
Saloed Aug 19, 2026
4b4e57b
perf(ifds): memoise repeated summary and precondition lookups
Saloed Aug 19, 2026
2f0e7d6
test: cover shallow-scan recall for class-static, cross-entry and get…
Saloed Aug 20, 2026
6bedc86
fix(ifds): use dedicated storage for abstract static edges
Saloed Aug 20, 2026
de6c7f3
perf(analyzer): intern resolved rule objects shared across methods
Saloed Aug 20, 2026
0ebb8bc
test: add BaseOnly end-to-end regression and fuzz coverage
Saloed Aug 19, 2026
d31f565
docs(analyzer): record what the clean branch kept, and what measureme…
Saloed Aug 20, 2026
c3b6efe
perf(ifds): hold BaseOnly memo caches through soft references
Saloed Aug 20, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ private class ConditionSimplifierImpl<A> : CommonConditionVisitor<A, CommonCondi
}

private val conditionSimplifier = ConditionSimplifierImpl<Nothing>()
private val falseCondition: CommonCondition<Nothing> = Not(CommonCondition.True)

@Suppress("UNCHECKED_CAST")
fun <A> conditionSimplifier(): CommonConditionVisitor<A, CommonCondition<A>> =
Expand All @@ -66,7 +67,9 @@ fun <A> conditionSimplifier(): CommonConditionVisitor<A, CommonCondition<A>> =
fun <A> mkTrue(): CommonCondition<A> =
CommonCondition.True as CommonCondition<A>

fun <A> mkFalse(): CommonCondition<A> = Not(mkTrue())
@Suppress("UNCHECKED_CAST")
fun <A> mkFalse(): CommonCondition<A> =
falseCondition as CommonCondition<A>

fun <A> mkOr(conditions: List<CommonCondition<A>>) = when (conditions.size) {
0 -> mkFalse()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.opentaint.dataflow.configuration

import kotlin.test.Test
import kotlin.test.assertSame

class ConditionFactoryTest {
@Test
fun `false condition is shared`() {
val first: Any = mkFalse<String>()
val second: Any = mkFalse<Int>()

assertSame(first, second)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.opentaint.dataflow.util;

import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.jetbrains.annotations.Nullable;

/**
* A primitive long map with point reads that tolerate a concurrent rehash.
*
* <p>The supported concurrency model is one writer and any number of readers. Removals are not
* supported. Iteration must use the captured-table helper in {@code MapUtils.kt}; the inherited
* fastutil iterators are not concurrent-read-safe.</p>
*/
public final class ConcurrentReadSafeLong2ObjectMap<V> extends Long2ObjectOpenHashMap<V> {
@Override
public @Nullable V get(long k) {
if (k == 0) {
if (!containsNullKey) return defRetValue;

do {
int n = this.n;
V[] value = this.value;
if (value.length == n + 1) return value[n];
} while (true);
}

while (true) {
long[] key = this.key;
V[] value = this.value;
int n = this.n;

// Capture a matching table generation to allow a read during rehash.
if (key.length != n + 1 || value.length != n + 1) continue;

int mask = n - 1;
int pos = (int) HashCommon.mix(k) & mask;
long curr = key[pos];
if (curr == 0) return defRetValue;

if (k == curr) return value[pos];

// There's always an unused entry.
while (true) {
pos = (pos + 1) & mask;
curr = key[pos];
if (curr == 0) return defRetValue;

if (k == curr) return value[pos];
}
}
}

@Override
public V remove(long k) {
throw new UnsupportedOperationException("Removals are not allowed");
}

public long[] getKeys() {
return this.key;
}

public V[] getValues() {
return this.value;
}

public int getN() {
return this.n;
}

public boolean getContainsNullKey() {
return this.containsNullKey;
}

private static final long serialVersionUID = 0L;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.opentaint.dataflow.util;

import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;

/**
* A primitive long set with point reads that tolerate a concurrent rehash.
*
* <p>The supported concurrency model is one writer and any number of readers. Removals are not
* supported. Iteration must use the captured-table helper in {@code MapUtils.kt}; the inherited
* fastutil iterators are not concurrent-read-safe.</p>
*/
public final class ConcurrentReadSafeLongSet extends LongOpenHashSet {
@Override
public boolean contains(long k) {
if (k == 0) return containsNull;

while (true) {
long[] key = this.key;
int n = this.n;

// Capture one complete table generation to allow a read during rehash.
if (key.length != n + 1) continue;

int mask = n - 1;
int pos = (int) HashCommon.mix(k) & mask;
long curr = key[pos];
if (curr == 0) return false;

if (k == curr) return true;

// There's always an unused entry.
while (true) {
pos = (pos + 1) & mask;
curr = key[pos];
if (curr == 0) return false;

if (k == curr) return true;
}
}
}

@Override
public boolean remove(long k) {
throw new UnsupportedOperationException("Removals are not allowed");
}

public long[] getKeys() {
return this.key;
}

public int getN() {
return this.n;
}

public boolean getContainsNull() {
return this.containsNull;
}

private static final long serialVersionUID = 0L;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,82 @@
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.jetbrains.annotations.Nullable;

/**
* A flat object-to-int map supporting one writer and multiple concurrent point readers.
*
* <p>Writes are published through a sequence counter. Readers retry if a write overlaps their
* lookup, which prevents observing a key before its primitive value or a partially published
* rehash. Removals are not supported.</p>
*/
public final class ConcurrentReadSafeObject2IntMap<K> extends Object2IntOpenHashMap<K> {
public static final int NO_VALUE = -1;

private volatile long writeSequence;

public ConcurrentReadSafeObject2IntMap() {
super();
defaultReturnValue(NO_VALUE);
}

@Override
public int getInt(@Nullable Object k) {
if (k == null) {
if (!containsNullKey) return defRetValue;

do {
int n = this.n;
int[] value = this.value;
if (value.length == n + 1) return value[n];
} while (true);
}

while (true) {
long sequenceBefore = writeSequence;
if ((sequenceBefore & 1) != 0) continue;

K[] key = this.key;
int[] value = this.value;
int n = this.n;
int result = findValue(k, key, value);

// capture arrays to allow concurrent reads
if (key.length != n + 1 || value.length != n + 1) continue;
if (sequenceBefore == writeSequence) return result;
}
}

int mask = n - 1;
private int findValue(@Nullable Object k, K[] key, int[] value) {
if (k == null) return containsNullKey ? value[value.length - 1] : defRetValue;

// The starting point.
int pos = HashCommon.mix(k.hashCode()) & mask;
int mask = key.length - 2;
int pos = HashCommon.mix(k.hashCode()) & mask;
K curr = key[pos];
if (curr == null) return defRetValue;
if (k.equals(curr)) return value[pos];

K curr = key[pos];
while (true) {
pos = (pos + 1) & mask;
curr = key[pos];
if (curr == null) return defRetValue;

if (k.equals(curr)) return value[pos];
}
}

// There's always an unused entry.
while (true) {
pos = (pos + 1) & mask;

curr = key[pos];
if (curr == null) return defRetValue;
@Override
public int put(K key, int value) {
beginWrite();
try {
return super.put(key, value);
} finally {
endWrite();
}
}

if (k.equals(curr)) return value[pos];
}
@Override
public int putIfAbsent(K key, int value) {
beginWrite();
try {
return super.putIfAbsent(key, value);
} finally {
endWrite();
}
}

private void beginWrite() {
writeSequence++;
}

private void endWrite() {
writeSequence++;
}

@Override
public int removeInt(Object k) {
throw new UnsupportedOperationException("Removals are not allowed");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface AnalysisRunner {
val methodCallResolver: MethodCallResolver

fun enqueueMethodAnalyzer(analyzer: MethodAnalyzer)
fun reprioritizeMethodAnalyzer(analyzer: MethodAnalyzer)
fun registerDelayedAnalyzer(analyzer: MethodAnalyzer)
fun addNewSummaryEdges(methodEntryPoint: MethodEntryPoint, edges: List<Edge>)
fun getPrecalculatedSummaries(methodEntryPoint: MethodEntryPoint): Pair<List<Edge>, List<InitialFactAp>>?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import org.opentaint.dataflow.ap.ifds.access.InitialFactAp
import org.opentaint.dataflow.ifds.UnitResolver
import org.opentaint.dataflow.ifds.UnitType
import org.opentaint.dataflow.util.Cancellation
import org.opentaint.dataflow.util.RefManager

interface AnalysisUnitRunnerManager {
val unitResolver: UnitResolver<CommonMethod>
val cancellation: Cancellation
val refManager: RefManager

fun getOrCreateUnitStorage(unit: UnitType): MethodSummariesUnitStorage?
fun getOrCreateUnitRunner(unit: UnitType): AnalysisRunner?
fun registerMethodCallFromUnit(method: CommonMethod, unit: UnitType)
fun registerResolvedMethodCall(caller: CommonMethod, callee: CommonMethod)

fun handleCrossUnitZeroCall(callerUnit: UnitType, methodEntryPoint: MethodEntryPoint) {
handleCrossUnitAction(callerUnit, methodEntryPoint) {
Expand Down Expand Up @@ -109,6 +112,15 @@ interface AnalysisUnitRunnerManager {
return storage.methodFactToFactSummaryEdges(methodEntryPoint, finalFactBase)
}

fun findFactToFactSummaryEdges(
methodEntryPoint: MethodEntryPoint,
finalFactPattern: FinalFactAp,
): List<Edge.FactToFact> {
val unit = unitResolver.resolve(methodEntryPoint.method)
val storage = getOrCreateUnitStorage(unit) ?: return emptyList()
return storage.methodFactToFactSummaryEdges(methodEntryPoint, finalFactPattern)
}

fun findFactNDSummaryEdges(
methodEntryPoint: MethodEntryPoint,
finalFactBase: AccessPathBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ import org.opentaint.dataflow.ap.ifds.access.InitialFactAp
import org.opentaint.ir.api.common.cfg.CommonInst

object EdgeCollection {
class UnprocessedEdgeList(
apManager: ApManager,
methodEntryPoint: MethodEntryPoint,
) {
private val zeroToZeroEdges = arrayListOf<Edge.ZeroToZero>()
private val otherEdges = EdgeList(apManager, methodEntryPoint)

val containsZeroToZeroEdges: Boolean
get() = zeroToZeroEdges.isNotEmpty()

val isEmpty: Boolean
get() = zeroToZeroEdges.isEmpty() && otherEdges.isEmpty

val size: Int
get() = zeroToZeroEdges.size + otherEdges.size

fun add(edge: Edge) {
if (edge is Edge.ZeroToZero) {
zeroToZeroEdges.add(edge)
} else {
otherEdges.add(edge)
}
}

fun removeLast(): Edge =
zeroToZeroEdges.removeLastOrNull() ?: otherEdges.removeLast()
}

class EdgeList(
private val apManager: ApManager,
private val methodEntryPoint: MethodEntryPoint
Expand Down
Loading
Loading