diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 04ddcdd46c7..4c3c2a14c72 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -163,6 +163,11 @@ open class ValueEvaluator( "/=" -> handleDiv(lhsValue, rhsValue, expr) "*", "*=" -> handleTimes(lhsValue, rhsValue, expr) + "<<" -> handleShiftLeft(lhsValue, rhsValue, expr) + ">>" -> handleShiftRight(lhsValue, rhsValue, expr) + "&" -> handleBitwiseAnd(lhsValue, rhsValue, expr) + "|" -> handleBitwiseOr(lhsValue, rhsValue, expr) + "^" -> handleBitwiseXor(lhsValue, rhsValue, expr) ">" -> handleGreater(lhsValue, rhsValue, expr) ">=" -> handleGEq(lhsValue, rhsValue, expr) "<" -> handleLess(lhsValue, rhsValue, expr) @@ -202,6 +207,51 @@ open class ValueEvaluator( } } + private fun handleShiftLeft(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { + return when { + // right side must always be an int + lhsValue is Int && rhsValue is Int -> lhsValue shl rhsValue + lhsValue is Long && rhsValue is Int -> lhsValue shl rhsValue + else -> cannotEvaluate(expr, this) + } + } + + private fun handleShiftRight(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { + return when { + // right side must always be an int + lhsValue is Int && rhsValue is Int -> lhsValue shr rhsValue + lhsValue is Long && rhsValue is Int -> lhsValue shr rhsValue + else -> cannotEvaluate(expr, this) + } + } + + private fun handleBitwiseAnd(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { + return when { + // left and right must be equal and only long and int are supported + lhsValue is Int && rhsValue is Int -> lhsValue and rhsValue + lhsValue is Long && rhsValue is Long -> lhsValue and rhsValue + else -> cannotEvaluate(expr, this) + } + } + + private fun handleBitwiseOr(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { + return when { + // left and right must be equal and only long and int are supported + lhsValue is Int && rhsValue is Int -> lhsValue or rhsValue + lhsValue is Long && rhsValue is Long -> lhsValue or rhsValue + else -> cannotEvaluate(expr, this) + } + } + + private fun handleBitwiseXor(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { + return when { + // left and right must be equal and only long and int are supported + lhsValue is Int && rhsValue is Int -> lhsValue xor rhsValue + lhsValue is Long && rhsValue is Long -> lhsValue xor rhsValue + else -> cannotEvaluate(expr, this) + } + } + private fun handleGreater(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? { return if (lhsValue is Number && rhsValue is Number) { lhsValue.compareTo(rhsValue) > 0 diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt index 91d8deb9b08..500144e2ab6 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt @@ -639,6 +639,106 @@ class ValueEvaluatorTest { } } + @Test + fun testHandleShiftLeft() { + with(TestHandler(TestLanguageFrontend())) { + val binOp = newBinaryOperator("<<") + // Int.plus + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(12, ValueEvaluator().evaluate(binOp)) + + // Long.plus + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(12L, ValueEvaluator().evaluate(binOp)) + + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) + assertEquals("{<<}", ValueEvaluator().evaluate(binOp)) + } + } + + @Test + fun testHandleShiftRight() { + with(TestHandler(TestLanguageFrontend())) { + val binOp = newBinaryOperator(">>") + // Int.plus + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(0, ValueEvaluator().evaluate(binOp)) + + // Long.plus + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(0L, ValueEvaluator().evaluate(binOp)) + + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) + assertEquals("{>>}", ValueEvaluator().evaluate(binOp)) + } + } + + @Test + fun testHandleBitwiseAnd() { + with(TestHandler(TestLanguageFrontend())) { + val binOp = newBinaryOperator("&") + // Int.plus + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(2, ValueEvaluator().evaluate(binOp)) + + // Long.plus + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(2L, ValueEvaluator().evaluate(binOp)) + + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) + assertEquals("{&}", ValueEvaluator().evaluate(binOp)) + } + } + + @Test + fun testHandleBitwiseOr() { + with(TestHandler(TestLanguageFrontend())) { + val binOp = newBinaryOperator("|") + // Int.plus + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(3, ValueEvaluator().evaluate(binOp)) + + // Long.plus + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(3L, ValueEvaluator().evaluate(binOp)) + + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) + assertEquals("{|}", ValueEvaluator().evaluate(binOp)) + } + } + + @Test + fun testHandleBitwiseXor() { + with(TestHandler(TestLanguageFrontend())) { + val binOp = newBinaryOperator("^") + // Int.plus + binOp.lhs = newLiteral(3, primitiveType("int")) + binOp.rhs = newLiteral(2, primitiveType("int")) + assertEquals(1, ValueEvaluator().evaluate(binOp)) + + // Long.plus + binOp.lhs = newLiteral(3L, primitiveType("long")) + binOp.rhs = newLiteral(2L, primitiveType("long")) + assertEquals(1L, ValueEvaluator().evaluate(binOp)) + + binOp.lhs = newLiteral("Hello", primitiveType("string")) + binOp.rhs = newLiteral(" world", primitiveType("string")) + assertEquals("{^}", ValueEvaluator().evaluate(binOp)) + } + } + @Test fun testHandleUnary() { with(TestHandler(TestLanguageFrontend())) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index 830bdf7f1ec..49fff98607b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -614,16 +614,16 @@ class ScopeManager : ScopeProvider { * Resolves only references to Values in the current scope, static references to other visible * records are not resolved over the ScopeManager. * - * @param scope * @param ref * @return * * TODO: We should merge this function with [.resolveFunction] */ - @JvmOverloads - fun resolveReference(ref: Reference, startScope: Scope? = currentScope): ValueDeclaration? { + fun resolveReference(ref: Reference): ValueDeclaration? { + val startScope = ref.scope + // Retrieve a unique tag for the particular reference based on the current scope - val tag = ref.buildUniqueTag(startScope) + val tag = ref.uniqueTag // If we find a match in our symbol table, we can immediately return the declaration var decl = symbolTable[tag] diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index 695b7332bc3..75aadb26443 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -164,6 +164,9 @@ abstract class Language> : Node() { "-", "*", "/" -> arithmeticOpTypePropagation(operation.lhs.type, operation.rhs.type) + "&", + "|", + "^", "<<", ">>" -> if (operation.lhs.type.isPrimitive && operation.rhs.type.isPrimitive) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt index 0c7c1922a4f..5eddd269f1d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/Reference.kt @@ -32,7 +32,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties -import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.passes.SymbolResolver @@ -160,9 +159,10 @@ open class Reference : Expression(), HasType.TypeObserver { * Its purpose is to cache symbol resolutions, similar to LLVMs system of Unified Symbol * Resolution (USR). */ - fun buildUniqueTag(startScope: Scope?): ReferenceTag { - return Objects.hash(this.name, this.resolutionHelper, startScope) - } + val uniqueTag: ReferenceTag + get() { + return Objects.hash(this.name, this.resolutionHelper, this.scope) + } } typealias ReferenceTag = Int diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index 5e3ec966b7b..827ca9425c0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -205,7 +205,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Peek into the declaration, and if it is a variable, we can proceed normally, as we // are running into the special case explained above. Otherwise, we abort here (for // now). - wouldResolveTo = scopeManager.resolveReference(current, current.scope) + wouldResolveTo = scopeManager.resolveReference(current) if (wouldResolveTo !is VariableDeclaration && wouldResolveTo !is ParameterDeclaration) { return } @@ -214,9 +214,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Only consider resolving, if the language frontend did not specify a resolution. If we // already have populated the wouldResolveTo variable, we can re-use this instead of // resolving again - var refersTo = - current.refersTo - ?: wouldResolveTo ?: scopeManager.resolveReference(current, current.scope) + var refersTo = current.refersTo ?: wouldResolveTo ?: scopeManager.resolveReference(current) var recordDeclType: Type? = null if (currentClass != null) { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt index 0f141ca244f..24415bf6ee1 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolverTest.kt @@ -31,6 +31,8 @@ import de.fraunhofer.aisec.cpg.TestUtils.assertRefersTo import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ReferenceTag import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -71,4 +73,23 @@ class SymbolResolverTest { assertNotNull(construct) assertInvokes(construct, constructor) } + + @Test + fun testUniqueTags() { + val result = GraphExamples.getConditionalExpression() + + val map = mutableMapOf>() + + val refs = result.refs + refs.forEach { + // Build a unique tag based on the scope of the reference is in (since this is usually + // the start scope) + val list = map.computeIfAbsent(it.uniqueTag) { mutableListOf() } + list += it + + // All elements in the list must have the same scope and name + assertEquals(1, list.map { ref -> ref.scope }.toSet().size) + assertEquals(1, list.map { ref -> ref.name }.toSet().size) + } + } } diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt index 62a1cb78db6..6a3be0a5646 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt @@ -303,10 +303,16 @@ class DeclarationTest { with(tu) { val values = mapOf( + "zeroShift" to Pair(0, objectType("int")), + "zeroAnd" to Pair(0, objectType("int")), "one" to Pair(1, objectType("p.custom")), "oneAsWell" to Pair(1, objectType("p.custom")), + "oneShift" to Pair(1, primitiveType("int")), "two" to Pair(2, primitiveType("int")), + "twoShift" to Pair(2, primitiveType("int")), "three" to Pair(3, primitiveType("int")), + "threeOr" to Pair(3, primitiveType("int")), + "threeXor" to Pair(3, primitiveType("int")), "four" to Pair(4, primitiveType("int")), "tenAsWell" to Pair(10, primitiveType("int")), "five" to Pair(5, primitiveType("int")), diff --git a/cpg-language-go/src/test/resources/golang/const.go b/cpg-language-go/src/test/resources/golang/const.go index bd87af22061..3338dbfa3c3 100644 --- a/cpg-language-go/src/test/resources/golang/const.go +++ b/cpg-language-go/src/test/resources/golang/const.go @@ -24,3 +24,12 @@ const ( fiveAsWell = 5 + iota*100 onehundredandfive ) + +const ( + oneShift = 1 << iota + twoShift + zeroShift = 1 >> iota + zeroAnd = oneShift & twoShift + threeOr = oneShift | twoShift + threeXor = oneShift ^ twoShift +)