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 5241adaebd2..0efab22554f 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 @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.AccessValues import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import kotlin.UnsupportedOperationException import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -95,6 +96,7 @@ open class ValueEvaluator( // 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) + is AssignExpression -> return handleAssignExpression(node) } // At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe @@ -102,6 +104,15 @@ open class ValueEvaluator( return cannotEvaluate(node, this) } + /** Under certain circumstances, an assignment can also be used as an expression. */ + private fun handleAssignExpression(node: AssignExpression): Any? { + if (node.usedAsExpression) { + return node.expressionValue + } + + return null + } + /** * We are handling some basic arithmetic binary operations and string operations that are more * or less language-independent. diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index d8247333c5c..f8090e7f60b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -357,13 +357,15 @@ fun allNonLiteralsFromFlowTo(from: Node, to: Node, allPaths: List>): val noAssignmentToFrom = allPaths.none { it.any { it2 -> - if (it2 is Assignment) { - val prevMemberFrom = (from as? MemberExpression)?.prevDFG - val nextMemberTo = (it2.target as? MemberExpression)?.nextDFG - it2.target == from || - prevMemberFrom != null && - nextMemberTo != null && - prevMemberFrom.any { it3 -> nextMemberTo.contains(it3) } + if (it2 is AssignmentHolder) { + it2.assignments.any { assign -> + val prevMemberFrom = (from as? MemberExpression)?.prevDFG + val nextMemberTo = (assign.target as? MemberExpression)?.nextDFG + assign.target == from || + prevMemberFrom != null && + nextMemberTo != null && + prevMemberFrom.any { it3 -> nextMemberTo.contains(it3) } + } } else { false } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index ac3ca05e1b3..7b96cd18553 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -703,26 +703,32 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( - { it.target?.type?.isPrimitive == true }, + result.all( + { it.assignments.all { assign -> assign.target.type.isPrimitive } }, { - max(it.value) <= maxSizeOfType(it.target!!.type) && - min(it.value) >= minSizeOfType(it.target!!.type) + it.assignments.any { + max(it.value) <= maxSizeOfType(it.target.type) && + min(it.value) >= minSizeOfType(it.target.type) + } } ) assertFalse(queryTreeResult.first) + /* + TODO: This test will not work anymore because we cannot put it into a QueryTree val queryTreeResult2 = - result.allExtended( - { it.target?.type?.isPrimitive == true }, + result.allExtended( + { it.assignments.all { assign -> assign.target.type.isPrimitive } }, { - (max(it.value) le maxSizeOfType(it.target!!.type)) and - (min(it.value) ge minSizeOfType(it.target!!.type)) + QueryTree(it.assignments.any { + (max(it.value) le maxSizeOfType(it.target!!.type)) and + (min(it.value) ge minSizeOfType(it.target!!.type)) + }) } ) println(queryTreeResult2.printNicely()) - assertFalse(queryTreeResult2.value) + assertFalse(queryTreeResult2.value)*/ } @Test diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt index d0009a0493c..b2d512e70e4 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/ArgumentHolder.kt @@ -44,7 +44,22 @@ interface ArgumentHolder : Holder { /** Adds the [expression] to the list of arguments. */ fun addArgument(expression: Expression) + /** Removes the [expression] from the list of arguments. */ + fun removeArgument(expression: Expression) {} + + /** + * Replaces the existing argument specified in [old] with the one in [new]. Implementation how + * to do that might be specific to the argument holder. + * + * An indication whether this operation was successful needs to be returned. + */ + fun replaceArgument(old: Expression, new: Expression): Boolean + override operator fun plusAssign(node: Expression) { addArgument(node) } + + operator fun minusAssign(node: Expression) { + removeArgument(node) + } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java index ebabb3664f2..84ff0ee65c0 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/types/Type.java @@ -171,6 +171,7 @@ public boolean isFirstOrderType() { return this instanceof ObjectType || this instanceof UnknownType || this instanceof FunctionType + || this instanceof TupleType // TODO(oxisto): convert FunctionPointerType to second order type || this instanceof FunctionPointerType || this instanceof IncompleteType diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt index 92dd7051348..2d5c9bfeb81 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Assignment.kt @@ -25,29 +25,19 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -/** An assignment assigns a certain value (usually an [Expression]) to a certain target. */ -interface Assignment { - /** - * The target of this assignment. Note that this is intentionally nullable, because while - * [BinaryOperator] implements [Assignment], not all binary operations are assignments. Thus, - * the target is only non-null for operations that have a == operator. - */ - val target: AssignmentTarget? - - /** - * The value expression that is assigned to the target. This is intentionally nullable for the - * same reason as [target]. - */ - val value: Expression? +/** An assignment holder is a node that intentionally contains assignment edges. */ +interface AssignmentHolder { + val assignments: List } -/** - * The target of an assignment. The target is usually either a [VariableDeclaration] or a - * [DeclaredReferenceExpression]. - */ -interface AssignmentTarget : HasType +/** An assignment assigns a certain value (usually an [Expression]) to a certain target. */ +class Assignment( + /** The value expression that is assigned to the target. */ + val value: Expression, + + /** The target(s) of this assignment. */ + val target: HasType +) : PropertyEdge(value, target as Node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 8ab68560522..298a02d75ed 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -104,6 +104,31 @@ fun MetadataProvider.newUnaryOperator( return node } +/** + * Creates a new [AssignExpression]. The [MetadataProvider] receiver will be used to fill different + * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires + * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended + * argument. + */ +@JvmOverloads +fun MetadataProvider.newAssignExpression( + operatorCode: String = "=", + lhs: List = listOf(), + rhs: List = listOf(), + code: String? = null, + rawNode: Any? = null +): AssignExpression { + val node = AssignExpression() + node.applyMetadata(this, operatorCode, rawNode, code, true) + node.operatorCode = operatorCode + node.lhs = lhs + node.rhs = rhs + + log(node) + + return node +} + /** * Creates a new [NewExpression]. This is the top-most [Node] that a [LanguageFrontend] or [Handler] * should create. The [MetadataProvider] receiver will be used to fill different meta-data using diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 34dd97315c0..878f7acfd85 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -538,6 +538,15 @@ val Node?.literals: List> val Node?.refs: List get() = this.allChildren() +/** Returns all [Assignment] child edges in this graph, starting with this [Node]. */ +val Node?.assignments: List + get() { + return this?.allChildren()?.filterIsInstance()?.flatMap { + it.assignments + } + ?: listOf() + } + operator fun Expression.invoke(): N? { return this as? N } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt index b49e01c5f8d..c878c9777cd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/HasInitializer.kt @@ -38,4 +38,15 @@ interface HasInitializer : ArgumentHolder { override fun addArgument(expression: Expression) { this.initializer = expression } + + override fun removeArgument(expression: Expression) { + if (this.initializer == expression) { + this.initializer = null + } + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + this.initializer = new + return true + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 9afcfee3d79..6acf60581eb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -35,7 +35,7 @@ import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider import org.slf4j.LoggerFactory object NodeBuilder { - private val LOGGER = LoggerFactory.getLogger(NodeBuilder::class.java) + internal val LOGGER = LoggerFactory.getLogger(NodeBuilder::class.java) fun log(node: Node?) { LOGGER.trace("Creating {}", node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 3c188bbbf81..8f421e17666 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -60,7 +60,7 @@ fun LanguageFrontend.translationResult( context(TranslationResult) fun LanguageFrontend.translationUnit( - name: CharSequence, + name: CharSequence = Node.EMPTY_NAME, init: TranslationUnitDeclaration.() -> Unit ): TranslationUnitDeclaration { val node = (this@LanguageFrontend).newTranslationUnitDeclaration(name) @@ -131,6 +131,8 @@ fun LanguageFrontend.field( scopeManager.addDeclaration(node) + this@TranslationResult.components.firstOrNull()?.translationUnits?.add(node) + return node } @@ -144,13 +146,19 @@ context(DeclarationHolder) fun LanguageFrontend.function( name: CharSequence, returnType: Type = UnknownType.getUnknownType(), - init: FunctionDeclaration.() -> Unit + returnTypes: List? = null, + init: (FunctionDeclaration.() -> Unit)? = null ): FunctionDeclaration { val node = newFunctionDeclaration(name) - node.returnTypes = listOf(returnType) + + if (returnTypes != null) { + node.returnTypes = returnTypes + } else { + node.returnTypes = listOf(returnType) + } scopeManager.enterScope(node) - init(node) + init?.let { it(node) } scopeManager.leaveScope(node) scopeManager.addDeclaration(node) @@ -613,6 +621,12 @@ operator fun Expression.plus(rhs: Expression): BinaryOperator { (this@ArgumentHolder) += node + // We need to do a little trick here. Because of the evaluation order, lhs and rhs might also + // been added to the argument holders arguments (and we do not want that). However, we cannot + // prevent it, so we need to remove them again + (this@ArgumentHolder) -= node.lhs + (this@ArgumentHolder) -= node.rhs + return node } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt index edec6155b03..f2ac426dcbb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.kt @@ -34,11 +34,7 @@ import org.neo4j.ogm.annotation.Relationship /** Represents the declaration of a variable. */ class VariableDeclaration : - ValueDeclaration(), HasType.TypeListener, HasInitializer, Assignment, AssignmentTarget { - override val value: Expression? - get() { - return initializer - } + ValueDeclaration(), HasType.TypeListener, HasInitializer, AssignmentHolder { /** * We need a way to store the templateParameters that a VariableDeclaration might have before @@ -94,13 +90,12 @@ class VariableDeclaration : } val previous = type val newType = - if (src === value && value is InitializerListExpression) { + if (src === initializer && initializer is InitializerListExpression) { // Init list is seen as having an array type, but can be used ambiguously. It can be - // either - // used to initialize an array, or to initialize some objects. If it is used as an + // either used to initialize an array, or to initialize some objects. If it is used + // as an // array initializer, we need to remove the array/pointer layer from the type, - // otherwise it - // can be ignored once we have a type + // otherwise it can be ignored once we have a type if (isArray) { src.type } else if (!TypeManager.getInstance().isUnknown(type)) { @@ -130,23 +125,25 @@ class VariableDeclaration : return ToStringBuilder(this, TO_STRING_STYLE) .append("name", name) .append("location", location) - .append("initializer", value) + .append("initializer", initializer) .toString() } + override val assignments: List + get() { + return initializer?.let { listOf(Assignment(it, this)) } ?: listOf() + } + override fun equals(other: Any?): Boolean { if (this === other) { return true } return if (other !is VariableDeclaration) { false - } else super.equals(other) && value == other.value + } else super.equals(other) && initializer == other.initializer } override fun hashCode(): Int { return super.hashCode() } - - override val target: AssignmentTarget - get() = this } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index 5bde9e558df..854342b0487 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -71,6 +71,11 @@ class IfStatement : Statement(), ArgumentHolder { this.condition = expression } + override fun replaceArgument(old: Expression, new: Expression): Boolean { + this.condition = new + return true + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is IfStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ReturnStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ReturnStatement.kt index c1140118d27..c74a845c10c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ReturnStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ReturnStatement.kt @@ -34,24 +34,46 @@ import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a statement that returns out of the current function. */ class ReturnStatement : Statement(), ArgumentHolder { /** The expression whose value will be returned. */ - @field:SubGraph("AST") var returnValue: Expression? = null + @field:SubGraph("AST") var returnValues: MutableList = mutableListOf() + + /** + * A utility property to handle single-valued return statements. In case [returnValues] contains + * a single [Expression], it is returned in the getter. The setter can be used to populate + * [returnValues] with a single entry. + */ + var returnValue: Expression? + get() { + return returnValues.singleOrNull() + } + set(value) { + value?.let { returnValues = mutableListOf(it) } + } override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) - .append("returnValue", returnValue) + .append("returnValues", returnValues) .toString() } override fun addArgument(expression: Expression) { - this.returnValue = expression + this.returnValues += expression + } + + override fun removeArgument(expression: Expression) { + this.returnValues -= expression + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + this.returnValue = new + return true } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ReturnStatement) return false - return super.equals(other) && returnValue == other.returnValue + return super.equals(other) && returnValues == other.returnValues } - override fun hashCode() = Objects.hash(super.hashCode(), returnValue) + override fun hashCode() = Objects.hash(super.hashCode(), returnValues) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt new file mode 100644 index 00000000000..dd6cd6876d0 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2023, 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.graph.statements.expressions + +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.types.TupleType +import de.fraunhofer.aisec.cpg.graph.types.Type + +/** + * Represents an assignment of a group of expressions (in the simplest case: one) from the right + * hand side to the left-hand side. + * + * This is intentionally modelled as an expression, since some languages support using the resulting + * value of an assignment as an expression. For example C++ allows the following: + * ```cpp + * int a; + * int b = (a = 1); + * ``` + * + * In this example, the [type] of the [AssignExpression] is an `int`. + * + * However, since not all languages support this model, we explicitly introduce the + * [usedAsExpression]. When this property is set to true (it defaults to false), we model a dataflow + * from the (first) rhs to the [AssignExpression] itself. + */ +class AssignExpression : Expression(), AssignmentHolder, HasType.TypeListener { + + var operatorCode: String = "=" + + @field:SubGraph("AST") var lhs: List = listOf() + + @field:SubGraph("AST") + var rhs: List = listOf() + set(value) { + // Unregister any old type listeners + field.forEach { it.unregisterTypeListener(this) } + field = value + // Register this statement as a type listener for each expression + value.forEach { + it.registerTypeListener(this) + + if (it is DeclaredReferenceExpression) { + it.access = AccessValues.WRITE + } + } + } + + /** + * This property specifies, that this is actually used as an expression. Not many languages + * support that. In the regular case, an assignment is a simple statement and does not hold any + * value itself. + */ + val usedAsExpression = false + + /** + * If this node is used an expression, this property contains a reference of the [Expression] + * (of RHS), which is used to represent its value. + */ + val expressionValue: Expression? + get() { + return if (usedAsExpression) rhs.firstOrNull() else null + } + + private val isSingleValue: Boolean + get() { + return this.lhs.size == 1 && this.rhs.size == 1 + } + + /** + * We also support compound assignments in this class, but only if the appropriate compound + * operator is set and only if there is a single-value expression on both side. + */ + val isCompoundAssignment: Boolean + get() { + return arrayOf("*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=") + .contains(operatorCode) && isSingleValue + } + + override fun typeChanged(src: HasType, root: MutableList, oldType: Type) { + if (!TypeManager.isTypeSystemActive()) { + return + } + + val type = src.type + + // There are now two possibilities: Either, we have a tuple type, that we need to + // deconstruct, or we have a singular type + if (type is TupleType) { + val targets = findTargets(src) + if (targets.size == type.types.size) { + // Set the corresponding type on the left-side + type.types.forEachIndexed { idx, t -> lhs.getOrNull(idx)?.type = t } + } + } else { + findTargets(src).forEach { it.type = src.propagationType } + } + + // If this is used as an expression, we also set the type accordingly + if (usedAsExpression) { + expressionValue?.propagationType?.let { setType(it, root) } + } + } + + override fun possibleSubTypesChanged(src: HasType, root: MutableList) { + if (!TypeManager.isTypeSystemActive()) { + return + } + + // Basically, we need to find out which index on the rhs this variable belongs to and set + // the corresponding subtypes on the lhs. + val idx = rhs.indexOf(src) + if (idx == -1) { + return + } + + // Set the subtypes + lhs.getOrNull(idx)?.setPossibleSubTypes(src.possibleSubTypes, root) + } + + fun findTargets(rhsExpr: HasType): List { + val type = rhsExpr.type + + // There are now two possibilities: Either, we have a tuple type, that we need to + // deconstruct, or we have a singular type + if (type is TupleType) { + // We need to see if there is enough room on the left side. Currently, we only support + // languages that do not allow to mix tuple and non-tuple types luckily, so we can just + // assume that all arguments on the left side are assignment targets + if (lhs.size != type.types.size) { + println("Tuple type size on RHS does not match number of LHS expressions") + return listOf() + } + + return lhs + } else { + // Basically, we need to find out which index on the rhs this variable belongs to and + // set the corresponding type on the lhs. + val idx = rhs.indexOf(rhsExpr) + if (idx == -1) { + return listOf() + } + + // Set the type + return listOfNotNull(lhs.getOrNull(idx)) + } + } + + override val assignments: List + get() { + return rhs.map { rhs -> + return findTargets(rhs).map { Assignment(rhs, it) } + } + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index 08728f24713..f7977b773f0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -36,7 +36,8 @@ import org.neo4j.ogm.annotation.Transient * A binary operation expression, such as "a + b". It consists of a left hand expression (lhs), a * right hand expression (rhs) and an operatorCode. */ -class BinaryOperator : Expression(), HasType.TypeListener, Assignment, HasBase, ArgumentHolder { +class BinaryOperator : + Expression(), HasType.TypeListener, AssignmentHolder, HasBase, ArgumentHolder { /** The left-hand expression. */ @field:SubGraph("AST") var lhs: Expression = ProblemExpression("could not parse lhs") @@ -56,6 +57,14 @@ class BinaryOperator : Expression(), HasType.TypeListener, Assignment, HasBase, } /** The operator code. */ override var operatorCode: String? = null + set(value) { + field = value + if (value?.contains("=") == true) { + NodeBuilder.LOGGER.warn( + "Creating a BinaryOperator with an assignment operator code is deprecated. The class AssignExpression should be used instead." + ) + } + } fun getLhsAs(clazz: Class): T? { return if (clazz.isInstance(lhs)) clazz.cast(lhs) else null @@ -153,6 +162,15 @@ class BinaryOperator : Expression(), HasType.TypeListener, Assignment, HasBase, .toString() } + override val assignments: List + get() { + return if (isAssignment) { + listOf(Assignment(rhs, lhs)) + } else { + listOf() + } + } + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -168,15 +186,6 @@ class BinaryOperator : Expression(), HasType.TypeListener, Assignment, HasBase, override fun hashCode() = Objects.hash(super.hashCode(), lhs, rhs, operatorCode) - // We only want to supply a target if this is an assignment - override val target: AssignmentTarget? - get() = // We only want to supply a target if this is an assignment - if (isAssignment) (if (lhs is AssignmentTarget) lhs as AssignmentTarget? else null) - else null - - override val value: Expression? - get() = if (isAssignment) rhs else null - private val isAssignment: Boolean get() { // TODO(oxisto): We need to discuss, if the other operators are also assignments and if @@ -194,6 +203,18 @@ class BinaryOperator : Expression(), HasType.TypeListener, Assignment, HasBase, } } + override fun replaceArgument(old: Expression, new: Expression): Boolean { + return if (lhs == old) { + lhs = new + true + } else if (rhs == old) { + rhs = new + true + } else { + false + } + } + override val base: Expression? get() { return if (operatorCode == ".*" || operatorCode == "->*") { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 6930664e6f3..cb2d5ec97e1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -37,6 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.propertyEqualsL import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.transformIntoOutgoingPropertyEdgeList import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType +import de.fraunhofer.aisec.cpg.graph.types.TupleType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.UnknownType import de.fraunhofer.aisec.cpg.passes.CallResolver @@ -136,6 +137,21 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg argumentEdges.add(edge) } + override fun replaceArgument(old: Expression, new: Expression): Boolean { + // First, we need to find the old index + val idx = this.arguments.indexOf(old) + if (idx == -1) { + return false + } + + setArgument(idx, new) + return true + } + + override fun removeArgument(expression: Expression) { + arguments -= expression + } + /** Returns the function signature as list of types of the call arguments. */ val signature: List get() = argumentEdges.map { it.end.type } @@ -262,8 +278,12 @@ open class CallExpression : Expression(), HasType.TypeListener, SecondaryTypeEdg val previous = type val types = invokeEdges.map(PropertyEdge::end).mapNotNull { - // TODO(oxisto): Support multiple return values - it.returnTypes.firstOrNull() + if (it.returnTypes.size == 1) { + return@mapNotNull it.returnTypes.firstOrNull() + } else if (it.returnTypes.size > 1) { + return@mapNotNull TupleType(it.returnTypes) + } + null } val alternative = if (types.isNotEmpty()) types[0] else UnknownType.getUnknownType(language) val commonType = TypeManager.getInstance().getCommonType(types, this).orElse(alternative) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt index a060bb02937..524678b130d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.AssignmentTarget import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.declarations.Declaration @@ -43,7 +42,7 @@ import org.neo4j.ogm.annotation.Relationship * expression `a = b`, which itself is a [BinaryOperator], contains two [ ]s, one for the variable * `a` and one for variable `b ` * , which have been previously been declared. */ -open class DeclaredReferenceExpression : Expression(), HasType.TypeListener, AssignmentTarget { +open class DeclaredReferenceExpression : Expression(), HasType.TypeListener { /** The [Declaration]s this expression might refer to. */ @Relationship(value = "REFERS_TO") var refersTo: Declaration? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt new file mode 100644 index 00000000000..027a1e540f4 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/TupleType.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, 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.graph.types + +import de.fraunhofer.aisec.cpg.graph.Name + +/** + * Represents a tuple of types. Primarily used in resolving function calls with multiple return + * values. + */ +class TupleType(types: List) : Type() { + var types: List = listOf() + set(value) { + field = value + name = Name(value.joinToString(", ", "(", ")") { it.name.toString() }) + } + + init { + this.types = types + } + + override fun reference(pointer: PointerType.PointerOrigin?): Type { + TODO("Not yet implemented") + } + + override fun dereference(): Type { + TODO("Not yet implemented") + } + + override fun duplicate(): Type { + return TupleType(types.map { it.duplicate() }) + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index dd6d873815f..6b467d38e6d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.Assignment import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration @@ -42,6 +41,7 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn /** Adds the DFG edges for various types of nodes. */ @DependsOn(VariableUsageResolver::class) +@DependsOn(CallResolver::class) class DFGPass : Pass() { override fun accept(tr: TranslationResult) { val inferDfgForUnresolvedCalls = @@ -65,6 +65,7 @@ class DFGPass : Pass() { is CallExpression -> handleCallExpression(node, inferDfgForUnresolvedSymbols) is CastExpression -> handleCastExpression(node) is BinaryOperator -> handleBinaryOp(node, parent) + is AssignExpression -> handleAssignExpression(node) is ArrayCreationExpression -> handleArrayCreationExpression(node) is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node) is ConditionalExpression -> handleConditionalExpression(node) @@ -90,8 +91,29 @@ class DFGPass : Pass() { is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node) is VariableDeclaration -> handleVariableDeclaration(node) - // Other - is Assignment -> handleAssignment(node) + } + } + + private fun handleAssignExpression(node: AssignExpression) { + // If this is a compound assign, we also need to model a dataflow to the node itself + if (node.isCompoundAssignment) { + node.lhs.firstOrNull()?.let { + node.addPrevDFG(it) + node.addNextDFG(it) + } + node.rhs.firstOrNull()?.let { node.addPrevDFG(it) } + } else { + // Find all targets of rhs and connect them + node.rhs.forEach { + val targets = node.findTargets(it) + targets.forEach { target -> it.addNextDFG(target) } + } + } + + // If the assignment is used as an expression, we also model a data flow from the (first) + // rhs to the node itself + if (node.usedAsExpression) { + node.expressionValue?.addNextDFG(node) } } @@ -138,7 +160,7 @@ class DFGPass : Pass() { * statement. */ private fun handleReturnStatement(node: ReturnStatement) { - node.returnValue?.let { node.addPrevDFG(it) } + node.returnValues.forEach { node.addPrevDFG(it) } } /** @@ -363,11 +385,6 @@ class DFGPass : Pass() { } } - /** Adds the DFG edge to an [Assignment]. The value flows to the target. */ - private fun handleAssignment(node: Assignment) { - node.value?.let { (node.target as? Node)?.addPrevDFG(it) } - } - /** * Adds the DFG edge to a [CastExpression]. The inner expression flows to the cast expression. */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 59eb19a739a..daf1cefa830 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -120,6 +120,7 @@ open class EvaluationOrderGraphPass : Pass() { } map[ReturnStatement::class.java] = { handleReturnStatement(it as ReturnStatement) } map[BinaryOperator::class.java] = { handleBinaryOperator(it as BinaryOperator) } + map[AssignExpression::class.java] = { handleAssignExpression(it as AssignExpression) } map[UnaryOperator::class.java] = { handleUnaryOperator(it as UnaryOperator) } map[CompoundStatement::class.java] = { handleCompoundStatement(it as CompoundStatement) } map[CompoundStatementExpression::class.java] = { @@ -502,6 +503,16 @@ open class EvaluationOrderGraphPass : Pass() { pushToEOG(node) } + protected fun handleAssignExpression(node: AssignExpression) { + // Handle left hand side(s) first + node.lhs.forEach { createEOG(it) } + + // Then the right side(s) + node.rhs.forEach { createEOG(it) } + + pushToEOG(node) + } + protected fun handleCompoundStatement(node: CompoundStatement) { // not all language handle compound statements as scoping blocks, so we need to avoid // creating new scopes here diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt index fdc8ade0b8f..d7852eb94d9 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/FluentTest.kt @@ -70,6 +70,7 @@ class FluentTest { } } } + val tu = result.translationUnits.firstOrNull() // Let's assert that we did this correctly val main = result.functions["main"] @@ -168,8 +169,6 @@ class FluentTest { assertNotNull(lit2.scope) assertEquals(2, lit2.value) - // val result = TranslationResult(TranslationManager.builder().build(), scopeManager) - // result.addTranslationUnit(tu) VariableUsageResolver().accept(result) // Now the reference should be resolved diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt new file mode 100644 index 00000000000..7db194f1655 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpressionTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023, 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.graph.statements.expressions + +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.assertLocalName +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.builder.function +import de.fraunhofer.aisec.cpg.graph.builder.translationResult +import de.fraunhofer.aisec.cpg.graph.builder.translationUnit +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.types.TupleType +import de.fraunhofer.aisec.cpg.passes.DFGPass +import kotlin.test.* + +class AssignExpressionTest { + @Test + fun propagateSimple() { + with(TestLanguage()) { + val refA = newDeclaredReferenceExpression("a") + val refB = newDeclaredReferenceExpression("b") + + // Simple assignment from "b" to "a". Both types are unknown at this point + val stmt = newAssignExpression(lhs = listOf(refA), rhs = listOf(refB)) + + // Type listeners should be configured + assertContains(refB.typeListeners, stmt) + + // Suddenly, we now we know the type of b. + refB.type = parseType("MyClass") + // It should now propagate to a + assertLocalName("MyClass", refA.type) + + val assignments = stmt.assignments + assertEquals(1, assignments.size) + } + } + + @Test + fun propagateTuple() { + with(TestLanguageFrontend()) { + val result = build { + translationResult(TranslationConfiguration.builder().build()) { + translationUnit { + val func = + function( + "func", + returnTypes = listOf(parseType("MyClass"), parseType("error")) + ) + function("main") { + val refA = newDeclaredReferenceExpression("a") + val refErr = newDeclaredReferenceExpression("err") + val refFunc = newDeclaredReferenceExpression("func") + refFunc.refersTo = func + val call = newCallExpression(refFunc) + + // Assignment from "func()" to "a" and "err". + val stmt = + newAssignExpression(lhs = listOf(refA, refErr), rhs = listOf(call)) + + body = newCompoundStatement() + body as CompoundStatement += stmt + } + } + } + } + + val tu = result.translationUnits.firstOrNull() + val call = tu.calls["func"] + val func = tu.functions["func"] + val refA = tu.refs["a"] + val refErr = tu.refs["err"] + + assertNotNull(call) + assertNotNull(func) + assertNotNull(refA) + assertNotNull(refErr) + + // This should now set the correct type of the call expression + call.invokes = listOf(func) + assertIs(call.type) + + assertLocalName("MyClass", refA.type) + assertLocalName("error", refErr.type) + + // Invoke the DFG pass + DFGPass().accept(result) + + assertTrue(refA.prevDFG.contains(call)) + assertTrue(refErr.prevDFG.contains(call)) + + val assignments = tu.assignments + assertEquals(2, assignments.size) + } + } +} diff --git a/cpg-language-go/src/test/resources/log4j2.xml b/cpg-language-go/src/test/resources/log4j2.xml index ac6e67063f1..359d8071bf3 100644 --- a/cpg-language-go/src/test/resources/log4j2.xml +++ b/cpg-language-go/src/test/resources/log4j2.xml @@ -6,7 +6,7 @@ - + diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt index a4831b0685f..28591561611 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/DeclarationHandler.kt @@ -151,8 +151,7 @@ open class DeclarationHandler(lang: JavaLanguageFrontend) : frontend.processAnnotations(param, parameter) frontend.scopeManager.addDeclaration(param) } - val returnTypes = - java.util.List.of(frontend.getReturnTypeAsGoodAsPossible(methodDecl, resolvedMethod)) + val returnTypes = listOf(frontend.getReturnTypeAsGoodAsPossible(methodDecl, resolvedMethod)) functionDeclaration.returnTypes = returnTypes val type = computeType(functionDeclaration) functionDeclaration.type = type