diff --git a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTree.kt b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTree.kt index 394e4d320..0a27af250 100644 --- a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTree.kt +++ b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTree.kt @@ -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? { @@ -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) } } @@ -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) } @@ -576,7 +582,7 @@ 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 @@ -584,9 +590,9 @@ class AccessTree( 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 @@ -602,7 +608,7 @@ class AccessTree( return manager.create(isAbstract, isFinal, accessors, accessorNodes) } - fun mergeAddDelta(other: AccessNode): Pair { + fun mergeAddDelta(other: AccessNode, foldToAny: Boolean = true): Pair { if (this === other) return this to null val isFinal = this.isFinal || other.isFinal @@ -615,13 +621,13 @@ class AccessTree( val deltaAccessorNodes = arrayListOf() 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) @@ -954,31 +960,49 @@ class AccessTree( private inline fun mergeAccessors( otherFields: IntArray?, otherNodesE: Array?, + 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?, - otherAccessors: IntArray?, + otherAccessorsE: IntArray?, otherNodesE: Array?, + foldToAny: Boolean, onOtherNode: (AccessorIdx, AccessNode) -> Unit, - merge: (AccessorIdx, AccessNode, AccessNode) -> AccessNode + merge: (AccessorIdx, AccessNode, AccessNode) -> AccessNode, ): Pair>? { - 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 @@ -1345,7 +1369,7 @@ class AccessTree( ) @JvmStatic - private fun TreeApManager.create( + fun TreeApManager.create( isAbstract: Boolean, isFinal: Boolean, accessors: IntArray?, diff --git a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTreeAnySuffixMatcher.kt b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTreeAnySuffixMatcher.kt new file mode 100644 index 000000000..38b964ac4 --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/AccessTreeAnySuffixMatcher.kt @@ -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 = Int2ObjectOpenHashMap() + ) { + 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() + 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): Pair> { + val accessorIdx = mutableListOf() + val accessorNodes = mutableListOf() + + 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() + val accessorNodes = mutableListOf() + + 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()) + } +} diff --git a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeApManager.kt b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeApManager.kt index 2224c8a2a..72cb98d19 100644 --- a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeApManager.kt +++ b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeApManager.kt @@ -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) diff --git a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeInitialFactAbstraction.kt b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeInitialFactAbstraction.kt index 044b0473e..a1602b1ea 100644 --- a/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeInitialFactAbstraction.kt +++ b/core/opentaint-dataflow-core/opentaint-dataflow/src/main/kotlin/org/opentaint/dataflow/ap/ifds/access/tree/TreeInitialFactAbstraction.kt @@ -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 @@ -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) @@ -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) { @@ -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