Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -391,25 +391,32 @@ class AccessTree(
}
}

private fun mergeAddMaybeNull(l: AccessNode?, r: AccessNode?): AccessNode? {
if (l == null)
return r
if (r == null)
return l
return r.mergeAdd(l)
}

fun getChild(accessor: AccessorIdx): AccessNode? {
if (accessor == FINAL_ACCESSOR_IDX) return manager.finalNode.takeIf { this.isFinal }

val node = getNodeByAccessor(accessor)
if (node != null) return node

val anyAccessorNode = getNodeByAccessor(ANY_ACCESSOR_IDX)
?: return null
?: return node

val anyChild = anyAccessorNode.getNodeByAccessor(accessor)
if (anyChild != null) return anyChild
var resultNode = mergeAddMaybeNull(anyChild, node)

with(manager) {
if (!anyAccessorUnrollStrategy.unrollAccessor(accessor.accessor)) return null
if (manager.isCoveredByAny(accessor)) {
val anyAccessorNoRepeats = anyAccessorNode.clearChild(accessor)
val originalAnyNoRepeats = anyAccessorNoRepeats.addParentIfPossible(ANY_ACCESSOR_IDX)
resultNode = mergeAddMaybeNull(originalAnyNoRepeats, resultNode)
}

val childWithAny = anyAccessorNode.addParentIfPossible(ANY_ACCESSOR_IDX)
val unrolled = childWithAny?.mergeAdd(anyAccessorNode) ?: anyAccessorNode
return unrolled
return resultNode
}

fun addParentIfPossible(accessor: AccessorIdx): AccessNode? {
Expand Down Expand Up @@ -536,12 +543,11 @@ class AccessTree(
}

forEachAccessor { accessor, accessorNode ->
if (accessor == ANY_ACCESSOR_IDX) {
if (accessor != ANY_ACCESSOR_IDX) {
// note: always ignore any accessor
return@forEachAccessor
dst.add(accessor)
}

dst.add(accessor)
accessorNode.collectAccessorsTo(dst)
}
}
Expand All @@ -566,7 +572,7 @@ class AccessTree(
val addedNodes = Array(uniqueAccessors.size) { uniqueAccessors[it].second() }

val mergedAccessors = mergeAccessors(
addedAccessors, addedNodes, onOtherNode = { _, _ -> }
addedAccessors, addedNodes, foldToAny = true, onOtherNode = { _, _ -> }
) { _, thisNode, otherNode ->
thisNode.mergeAdd(otherNode)
}
Expand All @@ -576,17 +582,17 @@ class AccessTree(
return manager.create(isAbstract, isFinal, mergedAccessors.first, mergedAccessors.second)
}

fun mergeAdd(other: AccessNode): AccessNode {
fun mergeAdd(other: AccessNode, foldToAny: Boolean = true): AccessNode {
if (this === other) return this

val isAbstract = this.isAbstract || other.isAbstract

val isFinal = this.isFinal || other.isFinal

val mergedAccessors = mergeAccessors(
other.accessors, other.accessorNodes, onOtherNode = { _, _ -> }
other.accessors, other.accessorNodes, foldToAny, onOtherNode = { _, _ -> }
) { _, thisNode, otherNode ->
thisNode.mergeAdd(otherNode)
thisNode.mergeAdd(otherNode, foldToAny)
}
if (
isAbstract == this.isAbstract
Expand All @@ -602,7 +608,7 @@ class AccessTree(
return manager.create(isAbstract, isFinal, accessors, accessorNodes)
}

fun mergeAddDelta(other: AccessNode): Pair<AccessNode, AccessNode?> {
fun mergeAddDelta(other: AccessNode, foldToAny: Boolean = true): Pair<AccessNode, AccessNode?> {
if (this === other) return this to null

val isFinal = this.isFinal || other.isFinal
Expand All @@ -615,13 +621,13 @@ class AccessTree(
val deltaAccessorNodes = arrayListOf<AccessNode>()

val mergedAccessors = mergeAccessors(
other.accessors, other.accessorNodes,
other.accessors, other.accessorNodes, foldToAny,
onOtherNode = { field, node ->
deltaAccessors.add(field)
deltaAccessorNodes.add(node)
}
) { field, thisNode, otherNode ->
val (addedNode, addedNodeDelta) = thisNode.mergeAddDelta(otherNode)
val (addedNode, addedNodeDelta) = thisNode.mergeAddDelta(otherNode, foldToAny)

if (addedNodeDelta != null) {
deltaAccessors.add(field)
Expand Down Expand Up @@ -954,31 +960,49 @@ class AccessTree(
private inline fun mergeAccessors(
otherFields: IntArray?,
otherNodesE: Array<AccessNode>?,
foldToAny: Boolean,
onOtherNode: (AccessorIdx, AccessNode) -> Unit,
merge: (AccessorIdx, AccessNode, AccessNode) -> AccessNode
) = mergeAccessors(accessors, accessorNodes, otherFields, otherNodesE, onOtherNode, merge)
merge: (AccessorIdx, AccessNode, AccessNode) -> AccessNode,
) = mergeAccessors(accessors, accessorNodes, otherFields, otherNodesE, foldToAny, onOtherNode, merge)

private inline fun mergeAccessors(
accessors: IntArray?,
nodes: Array<AccessNode>?,
otherAccessors: IntArray?,
otherAccessorsE: IntArray?,
otherNodesE: Array<AccessNode>?,
foldToAny: Boolean,
onOtherNode: (AccessorIdx, AccessNode) -> Unit,
merge: (AccessorIdx, AccessNode, AccessNode) -> AccessNode
merge: (AccessorIdx, AccessNode, AccessNode) -> AccessNode,
): Pair<IntArray, Array<AccessNode>>? {
if (otherAccessors == null) return null
val otherNodes = otherNodesE!!
if (otherAccessorsE == null) return null
val otherNodesBeforeAny = otherNodesE!!

if (accessors == null) {
for (i in otherAccessors.indices) {
onOtherNode(otherAccessors[i], otherNodes[i])
for (i in otherAccessorsE.indices) {
onOtherNode(otherAccessorsE[i], otherNodesBeforeAny[i])
}

return otherAccessors to otherNodes
return otherAccessorsE to otherNodesBeforeAny
}

val thisAccessors = accessors
val thisNodes = nodes!!
val thisAccessorsBeforeAny = accessors
val thisNodesBeforeAny = nodes!!

var thisAnyIdx: Int = -1
if (foldToAny)
thisAnyIdx = accessors.indexOf(ANY_ACCESSOR_IDX)
val (otherAccessors, otherNodes) =
if (thisAnyIdx >= 0)
AccessTreeAnySuffixMatcher(nodes[thisAnyIdx]).getNonMatchingNode(otherAccessorsE, otherNodesBeforeAny)
else otherAccessorsE to otherNodesBeforeAny

var otherAnyIdx: Int = -1
if (foldToAny)
otherAnyIdx = otherAccessorsE.indexOf(ANY_ACCESSOR_IDX)
val (thisAccessors, thisNodes) =
if (otherAnyIdx >= 0)
AccessTreeAnySuffixMatcher(otherNodesBeforeAny[otherAnyIdx]).getNonMatchingNode(thisAccessorsBeforeAny, thisNodesBeforeAny)
else thisAccessorsBeforeAny to thisNodesBeforeAny

var modified = false
var accessorsModified = false
Expand Down Expand Up @@ -1345,7 +1369,7 @@ class AccessTree(
)

@JvmStatic
private fun TreeApManager.create(
fun TreeApManager.create(
isAbstract: Boolean,
isFinal: Boolean,
accessors: IntArray?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package org.opentaint.dataflow.ap.ifds.access.tree

import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import org.opentaint.dataflow.ap.ifds.access.tree.AccessTree.AccessNode.Companion.create
import org.opentaint.dataflow.ap.ifds.access.util.AccessorIdx
import org.opentaint.dataflow.ap.ifds.access.util.AccessorInterner.Companion.ANY_ACCESSOR_IDX

class AccessTreeAnySuffixMatcher(suffixNode: AccessTree.AccessNode) {
private val manager = suffixNode.manager
private val root = TrieNode(isAbstract = false, isFinal = false, prefixLink = null, depth = 0)

private data class TrieNode(
val isAbstract: Boolean,
val isFinal: Boolean,
val prefixLink: TrieNode?,
val depth: Int,
val children: Int2ObjectOpenHashMap<TrieNode> = Int2ObjectOpenHashMap<TrieNode>()
) {
fun findChild(accessor: Int): TrieNode? {
val child = children.get(accessor)
if (child != null)
return child
return prefixLink?.findChild(accessor)
}

override fun toString(): String {
val abstraction = if (isAbstract) "A" else ""
val final = if (isFinal) "F" else ""
return "($abstraction$final, depth=$depth, $children)"
}

override fun equals(other: Any?): Boolean {
if (other !is TrieNode)
return false
if (this === other)
return true
if (prefixLink !== other.prefixLink)
return false
if (isAbstract != other.isAbstract || isFinal != other.isFinal || depth != other.depth)
return false
return children == other.children
}

override fun hashCode(): Int {
var result = 31 * depth
if (isAbstract) result += 17
if (isFinal) result += 13
return result * 31 + children.hashCode()
}
}

private fun AccessorIdx.coveredByAny(): Boolean =
manager.isCoveredByAny(this)

private data class RawNodeWithParent(
val node: AccessTree.AccessNode,
val accessor: AccessorIdx,
val parent: TrieNode,
val depth: Int,
val notCoveredByAny: Int?,
)

init {
if (suffixNode.accessors != null && suffixNode.accessorNodes != null) {
val unprocessed = ArrayDeque<RawNodeWithParent>()
suffixNode.forEachAccessor { accessor, accessorNode ->
val notCoveredByAny = if (accessor.coveredByAny()) null else 1
unprocessed.addLast(RawNodeWithParent(accessorNode, accessor, root, 1, notCoveredByAny))
}

while (unprocessed.isNotEmpty()) {
val (node, accessor, triePar, depth, notCoveredByAny) = unprocessed.removeFirst()
// disallowing [any]->...->[any]
check(accessor != ANY_ACCESSOR_IDX)

val curNotCoveredByAny = when {
notCoveredByAny != null -> notCoveredByAny
!accessor.coveredByAny() -> depth
else -> null
}

var prefix = triePar.prefixLink
while (prefix != null) {
val next = prefix.children.get(accessor)
if (next != null) {
val notCoveredStillInSuffix = curNotCoveredByAny == null || depth - next.depth < curNotCoveredByAny
prefix = if (notCoveredStillInSuffix) next else null
break
}
prefix = prefix.prefixLink
}
if (triePar === root) {
prefix = root
}
if (prefix == null) {
prefix = root.children.get(accessor) ?: root
}
val newTrieNode = TrieNode(
isAbstract = node.isAbstract || prefix.isAbstract,
isFinal = node.isFinal || prefix.isFinal,
prefix, depth
)
triePar.children.put(accessor, newTrieNode)

node.forEachAccessor{ accessor, accessorNode ->
unprocessed.addLast(RawNodeWithParent(accessorNode, accessor, newTrieNode, depth + 1, curNotCoveredByAny))
}
}
}
}

fun getNonMatchingNode(accessors: IntArray, nodes: Array<AccessTree.AccessNode>): Pair<IntArray, Array<AccessTree.AccessNode>> {
val accessorIdx = mutableListOf<AccessorIdx>()
val accessorNodes = mutableListOf<AccessTree.AccessNode>()

accessors.forEachIndexed { i, accessor ->
val accessorNode = nodes[i]
val afterAny = root.children.get(accessor)
val accessorCovered = accessor.coveredByAny()
if (afterAny == null && !accessorCovered) {
// two [any]-branches can be merged naturally, as those not accepted by [any]
accessorIdx.add(accessor)
accessorNodes.add(accessorNode)
}
else {
val child = getNonMatchingNode(afterAny ?: root, accessorNode, accessorCovered)
if (child != null) {
accessorIdx.add(accessor)
accessorNodes.add(child)
}
}
}

return accessorIdx.toIntArray() to accessorNodes.toTypedArray()
}

private fun getNonMatchingNode(trie: TrieNode, node: AccessTree.AccessNode, prefixCoveredByAny: Boolean): AccessTree.AccessNode? {
val accessorIdx = mutableListOf<AccessorIdx>()
val accessorNodes = mutableListOf<AccessTree.AccessNode>()

node.forEachAccessor { accessor, accessorNode ->
val prefixStillCovered = prefixCoveredByAny && (accessor == ANY_ACCESSOR_IDX || accessor.coveredByAny())
// if prefix has an accessor not covered by [any], we cannot go back to root
val fallback = if (prefixStillCovered) root else null
val next = trie.findChild(accessor) ?: fallback
if (next == null) {
// fell out of suffix
accessorIdx.add(accessor)
accessorNodes.add(accessorNode)
}
val child = next?.let { getNonMatchingNode(it, accessorNode, prefixStillCovered) }
if (child != null) {
accessorIdx.add(accessor)
accessorNodes.add(child)
}
}

val thisAbstract = node.isAbstract && !trie.isAbstract
val thisFinal = node.isFinal && !trie.isFinal

// all branches matched the any-suffix
if (!thisAbstract && !thisFinal && accessorIdx.isEmpty())
return null

return manager.create(thisAbstract, thisFinal, accessorIdx.toIntArray(), accessorNodes.toTypedArray())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class TreeApManager(
get() = interner.accessor(this)
?: error("Accessor not found: $this")

fun isCoveredByAny(accessor: AccessorIdx) =
anyAccessorUnrollStrategy.unrollAccessor(accessor.accessor)

override fun initialFactAbstraction(methodInitialStatement: CommonInst): InitialFactAbstraction =
TreeInitialFactAbstraction(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.opentaint.dataflow.ap.ifds.access.tree.AccessPath.AccessNode.Companio
import org.opentaint.dataflow.ap.ifds.access.tree.AccessTree.AccessNode.Companion.create
import org.opentaint.dataflow.ap.ifds.access.tree.AccessTree.AccessNode.Companion.createAbstractNodeFromReversedAp
import org.opentaint.dataflow.ap.ifds.access.util.AccessorIdx
import org.opentaint.dataflow.ap.ifds.access.util.AccessorInterner.Companion.ANY_ACCESSOR_IDX
import org.opentaint.dataflow.ap.ifds.access.util.AccessorInterner.Companion.FINAL_ACCESSOR_IDX
import org.opentaint.dataflow.ap.ifds.access.util.AccessorInterner.Companion.isAlwaysUnrollNext
import org.opentaint.dataflow.util.forEachInt
Expand Down Expand Up @@ -133,7 +134,7 @@ class TreeInitialFactAbstraction(
}
}

val mergedNewFacts = newFacts.reduceOrNull { acc, f -> acc.mergeAdd(f) }
val mergedNewFacts = newFacts.reduceOrNull { acc, f -> acc.mergeAdd(f, foldToAny = false) }
?: return null

return addInitialFact(mergedNewFacts, interner)
Expand Down Expand Up @@ -189,6 +190,9 @@ class TreeInitialFactAbstraction(
if (unrollAccessors.isNotEmpty()) {
unrollRequests += AnyAccessorUnrollRequest(state.currentAp, state.added, unrollAccessors)
}

val anyBranch = state.added.getChild(ANY_ACCESSOR_IDX) ?: error("impossible")
unprocessed += AbstractionState(state.analyzedTrieRoot, anyBranch, state.currentAp)
}

if (state.added.isFinal) {
Expand Down Expand Up @@ -292,7 +296,7 @@ class TreeInitialFactAbstraction(

fun addInitialFact(ap: AccessTreeNode, interner: AccessTreeSoftInterner): AccessTreeNode? {
val currentNode = added ?: manager.create()
val (updatedAddedNode, addedInitial) = currentNode.mergeAddDelta(ap)
val (updatedAddedNode, addedInitial) = currentNode.mergeAddDelta(ap, foldToAny = false)

if (addedInitial == null) return null

Expand Down
Loading