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

First attempt to implement bug reasoning logic for Query API #765

Merged
merged 77 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
f5987a4
First attempt to implement bug reasoning logic
KuechA Apr 20, 2022
07fcbf3
Add file
KuechA Apr 20, 2022
c92687a
Naive extension to retrieve more than a single value (in some cases)
KuechA Apr 21, 2022
22dd8b5
Fix error of refactoring
KuechA Apr 21, 2022
598f0e3
Builder for Queries
KuechA Apr 22, 2022
8e6b338
Slightly simplify the Query API
KuechA Apr 22, 2022
da9eed8
Test null pointer check with query
KuechA Apr 25, 2022
81793cd
Test for memcpy query and sizeof
KuechA Apr 25, 2022
c3e21b5
Try to compress the notation a bit
KuechA Apr 26, 2022
b0ea44b
Quick alternative using reified function
oxisto Apr 26, 2022
1260fb3
Even more fancy
oxisto Apr 26, 2022
8dbf785
more possibilites
oxisto Apr 26, 2022
0cc817e
added Assignment interface
oxisto Apr 27, 2022
65b20e0
Added forgotten stuff
oxisto Apr 27, 2022
8f9f21d
Formatting
KuechA Apr 27, 2022
c16de27
Include intermediate steps and results
KuechA May 10, 2022
4236b08
++
oxisto May 19, 2022
4121fda
Add min and max operators
KuechA May 24, 2022
a0563c2
Fix compiler error, add tests
KuechA May 24, 2022
a8132cd
Performance, doc
KuechA May 24, 2022
4c57e25
Additional testcase and second min/max function
KuechA Jun 10, 2022
90bb596
Merge branch 'master' into console/queries
KuechA Jun 10, 2022
1768636
Try to update MultiValueEvaluator
KuechA Jun 10, 2022
3175f6a
Merge branch 'console/queries' of github.com:Fraunhofer-AISEC/cpg int…
KuechA Jun 10, 2022
2b94fbc
Updated formatting
KuechA Jun 10, 2022
2cf68d4
Fix
KuechA Jun 10, 2022
ff3773c
Replace ConcreteNumberSet with NumberSet, Fix typo
KuechA Jun 10, 2022
081142a
Merge branch 'main' into console/queries
KuechA Jul 21, 2022
91e14c6
Fix formatting
KuechA Jul 21, 2022
be1a6ac
Fix errors in test
KuechA Jul 21, 2022
93ace63
Fix broken tests due to changed edge names
KuechA Jul 21, 2022
2d367aa
Keep track of intermediate steps of evaluation
KuechA Jul 22, 2022
dc64a6d
Reverte incorrect changes
KuechA Jul 25, 2022
b46331a
Extend operators of the query API
KuechA Jul 25, 2022
b1924a1
Extract shortcuts for frequently used functionality
KuechA Jul 25, 2022
cec9d75
Delete old design
KuechA Jul 25, 2022
3a029a6
Rename package and test name
KuechA Jul 25, 2022
4dfade6
Documentation
KuechA Jul 25, 2022
9ed1ade
Functions for dfg and eog traversals
KuechA Jul 25, 2022
94d7b58
Fix exists operator
KuechA Jul 26, 2022
c08cc8a
Nicer output
KuechA Aug 5, 2022
38c7486
Broken stuff
KuechA Aug 9, 2022
17b5cb2
Comparable QueryTree
KuechA Aug 9, 2022
2bea4b8
More intuitive extensions for the Query API
KuechA Aug 9, 2022
2c0c253
Simplified exists
KuechA Aug 9, 2022
933740f
Throw exception if we cannot compare anything
KuechA Aug 9, 2022
e1106f2
Fix
KuechA Aug 9, 2022
4f13f7b
Merge branch 'main' into console/queries
KuechA Aug 10, 2022
db8a154
Test for multi value evaluator, fixes
KuechA Aug 10, 2022
b6536a6
Merge branch 'console/queries' of github.com:Fraunhofer-AISEC/cpg int…
KuechA Aug 10, 2022
067e93a
More coverage in tests, Sizeof test, remove duplicate code
KuechA Aug 10, 2022
c74bc5a
Merge branch 'main' into console/queries
KuechA Aug 10, 2022
57cc01d
Fix
KuechA Aug 10, 2022
5227abd
Merge branch 'console/queries' of github.com:Fraunhofer-AISEC/cpg int…
KuechA Aug 10, 2022
6b28bdf
Tests and fixes for shortcuts
KuechA Aug 11, 2022
e127232
Test coverage++
KuechA Aug 11, 2022
c9c43f5
Test coverage++
KuechA Aug 11, 2022
4330fde
Fix
KuechA Aug 11, 2022
994e048
Readme for query api
KuechA Aug 11, 2022
df35a5d
Some fixes, more operators
KuechA Aug 12, 2022
51e4913
Tests++
KuechA Aug 12, 2022
e318e79
Fix test to match its name
KuechA Aug 12, 2022
412fe71
Javadoc, Consistency of followPrevDFGEdgesUntilHit
KuechA Aug 16, 2022
3e486ae
Rename overloaded functions to allExtended/existsExtended
KuechA Aug 16, 2022
c2e7b9e
Update Readme
KuechA Aug 16, 2022
98c8947
Javadoc++
KuechA Aug 16, 2022
b465f93
Fix broken dataflow test
KuechA Aug 16, 2022
d55e682
Move query API to cpg-analysis and merge Shortcuts with Extensions
KuechA Aug 16, 2022
2acf9c6
Some fixes, more test
KuechA Aug 16, 2022
240bdb0
A more tricky test
KuechA Aug 17, 2022
b9024e7
Update readme
KuechA Aug 17, 2022
8abbab7
Even more complex, better stability of the test
KuechA Aug 17, 2022
9ca11cc
Merge branch 'main' into console/queries
KuechA Aug 17, 2022
9fa7790
Review
KuechA Aug 18, 2022
8fd182b
Merge branch 'console/queries' of github.com:Fraunhofer-AISEC/cpg int…
KuechA Aug 18, 2022
57a91e2
forall -> for
KuechA Aug 18, 2022
38a1227
Merge branch 'main' into console/queries
KuechA Aug 19, 2022
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
@@ -0,0 +1,333 @@
/*
* Copyright (c) 2022, Fraunhofer AISEC. All rights reserved.
*
* 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 de.fraunhofer.aisec.cpg.analysis

import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.ValueEvaluator
import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.negate
import de.fraunhofer.aisec.cpg.graph.statements.ForStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.passes.astParent
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
* This [ValueEvaluator] can resolve multiple possible values of a node.
*
* It requires running the [EdgeCachePass] after the translation to add all necessary edges.
*/
class MultiValueEvaluator : ValueEvaluator() {
companion object {
const val MAX_DEPTH: Int = 20
}

override val log: Logger
get() = LoggerFactory.getLogger(MultiValueEvaluator::class.java)

override fun evaluate(node: Any?): Any? {
val result = evaluateInternal(node as? Node, 0)
return if (result is List<*> && result.all { r -> r is Number })
ConcreteNumberSet(result.map { r -> (r as Number).toLong() }.toMutableSet())
else result
}

/** Tries to evaluate this node. Anything can happen. */
override fun evaluateInternal(node: Node?, depth: Int): Any? {
if (node == null) {
return null
}

if (depth > MAX_DEPTH) {
return cannotEvaluate(node, this)
}
// Add the expression to the current path
this.path += node

when (node) {
is FieldDeclaration -> {
return evaluateInternal(node.initializer, depth + 1)
}
is ArrayCreationExpression -> return evaluateInternal(node.initializer, depth + 1)
is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1)
// For a literal, we can just take its value, and we are finished
is Literal<*> -> return node.value
is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth)
is UnaryOperator -> return handleUnaryOp(node, depth)
is BinaryOperator -> return handleBinaryOperator(node, depth)
// Casts are just a wrapper in this case, we are interested in the inner expression
is CastExpression -> return this.evaluateInternal(node.expression, depth + 1)
is ArraySubscriptionExpression -> return handleArraySubscriptionExpression(node, depth)
// While we are not handling different paths of variables with If statements, we can
// easily be partly path-sensitive in a conditional expression
is ConditionalExpression -> return handleConditionalExpression(node, depth)
}

// At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe
// this helps
return cannotEvaluate(node, this)
}

/**
* We are handling some basic arithmetic binary operations and string operations that are more
* or less language-independent.
*/
override fun handleBinaryOperator(expr: BinaryOperator, depth: Int): Any? {
// Resolve lhs
val lhsValue = evaluateInternal(expr.lhs, depth + 1)
// Resolve rhs
val rhsValue = evaluateInternal(expr.rhs, depth + 1)

if (lhsValue !is List<*> && rhsValue !is List<*>) {
return computeBinaryOpEffect(lhsValue, rhsValue, expr)
}

val result = mutableListOf<Any?>()
if (lhsValue is List<*>) {
for (lhs in lhsValue) {
if (rhsValue is List<*>) {
result.addAll(rhsValue.map { r -> computeBinaryOpEffect(lhs, r, expr) })
} else {
result.add(computeBinaryOpEffect(lhs, rhsValue, expr))
}
}
} else {
result.addAll(
(rhsValue as List<*>).map { r -> computeBinaryOpEffect(lhsValue, r, expr) }
)
}

return result
}

override fun handleConditionalExpression(expr: ConditionalExpression, depth: Int): Any? {
val result = mutableListOf<Any?>()
val elseResult = evaluateInternal(expr.elseExpr, depth + 1)
val thenResult = evaluateInternal(expr.thenExpr, depth + 1)
if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult)
if (elseResult is List<*>) result.addAll(elseResult) else result.add(elseResult)
return result
}

override fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? {
return when (expr.operatorCode) {
"-" -> {
when (val input = evaluateInternal(expr.input, depth + 1)) {
is List<*> -> input.map { n -> (n as? Number)?.negate() }
is Number -> input.negate()
else -> cannotEvaluate(expr, this)
}
}
"++" -> {
if (expr.astParent is ForStatement) {
evaluateInternal(expr.input, depth + 1)
} else {
when (val input = evaluateInternal(expr.input, depth + 1)) {
is Number -> input.toLong() + 1
is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) }
else -> cannotEvaluate(expr, this)
}
}
}
"*" -> evaluateInternal(expr.input, depth + 1)
"&" -> evaluateInternal(expr.input, depth + 1)
else -> cannotEvaluate(expr, this)
}
}

/**
* Tries to compute the value of a reference. It therefore checks the incoming data flow edges.
*
* In contrast to the implementation of [ValueEvaluator], this one can handle more than one
* value.
*/
override fun handleDeclaredReferenceExpression(
expr: DeclaredReferenceExpression,
depth: Int
): List<Any?> {
// For a reference, we are interested in its last assignment into the reference
// denoted by the previous DFG edge
val prevDFG = expr.prevDFG

if (prevDFG.size == 1) {
// There's only one incoming DFG edge, so we follow this one.
val internalRes = evaluateInternal(prevDFG.first(), depth + 1)
return if (internalRes is List<*>) internalRes else mutableListOf(internalRes)
}

// We are only interested in expressions
val expressions = prevDFG.filterIsInstance<Expression>()

if (
expressions.size == 2 &&
expressions.all { e ->
(e.astParent?.astParent as? ForStatement)?.initializerStatement == e ||
(e.astParent as? ForStatement)?.iterationStatement == e
}
) {
return handleSimpleLoopVariable(expr, depth)
}

val result = mutableListOf<Any?>()
if (expressions.isEmpty()) {
// No previous expression?? Let's try with a variable declaration and its initialization
val decl = prevDFG.filterIsInstance<VariableDeclaration>()
for (declaration in decl) {
val res = evaluateInternal(declaration, depth + 1)
if (res is Collection<*>) {
result.addAll(res)
} else {
result.add(res)
}
}
}

for (expression in expressions) {
val res = evaluateInternal(expression, depth + 1)
if (res is Collection<*>) {
result.addAll(res)
} else {
result.add(res)
}
}
return result
}

private fun handleSimpleLoopVariable(
expr: DeclaredReferenceExpression,
depth: Int
): List<Any?> {
val loop =
expr.prevDFG.firstOrNull { e -> e.astParent is ForStatement }?.astParent
as? ForStatement
if (loop == null || loop.condition !is BinaryOperator) return listOf()

var loopVar =
evaluateInternal(loop.initializerStatement.declarations.first(), depth) as? Number

if (loopVar == null) return listOf()

val cond = loop.condition as BinaryOperator
val result = mutableListOf<Any?>()
var lhs =
if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) {
loopVar
} else {
evaluateInternal(cond.lhs, depth + 1)
}
var rhs =
if ((cond.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) {
loopVar
} else {
evaluateInternal(cond.rhs, depth + 1)
}

var comparisonResult = computeBinaryOpEffect(lhs, rhs, cond)
while (comparisonResult == true) {
result.add(
loopVar
) // We skip the last iteration on purpose because that last operation will be added by
// the statement which made us end up here.

val loopOp = loop.iterationStatement
loopVar =
when (loopOp) {
is BinaryOperator -> {
val opLhs =
if (
(loopOp.lhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar
} else {
loopOp.lhs
}
val opRhs =
if (
(loopOp.rhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar
} else {
loopOp.rhs
}
computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number
}
is UnaryOperator -> {
computeUnaryOpEffect(
if (
(loopOp.input as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar!!
} else {
loopOp.input
},
loopOp
)
as? Number
}
else -> {
null
}
}
if (loopVar == null) {
return result
}
// result.add(loopVar)

if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) {
lhs = loopVar
}
if ((cond.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) {
rhs = loopVar
}
comparisonResult = computeBinaryOpEffect(lhs, rhs, cond)
}
return result
}

private fun computeUnaryOpEffect(input: Any, expr: UnaryOperator): Any? {
return when (expr.operatorCode) {
"-" -> {
when (input) {
is List<*> -> input.map { n -> (n as? Number)?.negate() }
is Number -> input.negate()
else -> cannotEvaluate(expr, this)
}
}
"++" -> {
when (input) {
is Number -> input.toLong() + 1
is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) }
else -> cannotEvaluate(expr, this)
}
}
else -> cannotEvaluate(expr, this)
}
}
}
Loading