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 9a78528f34..f101ea0e4f 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 @@ -217,6 +217,10 @@ abstract class Language> : Node() { open fun shouldPropagateType(hasType: HasType, srcType: Type): Boolean { val nodeType = hasType.type + if (srcType is DynamicType) { + return false + } + // We only want to add certain types, in case we have a numeric type if (nodeType is NumericType) { // We do not allow to propagate non-numeric types into numeric types diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt index a0a0fde85a..1825878485 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt @@ -41,6 +41,10 @@ fun MetadataProvider?.unknownType(): Type { } } +fun LanguageProvider.dynamicType(): Type { + return DynamicType(this.language) +} + fun LanguageProvider.autoType(): Type { return AutoType(this.language) } 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 index 966a913545..611772c898 100644 --- 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 @@ -33,6 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.graph.types.HasType 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 org.neo4j.ogm.annotation.Relationship import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -208,10 +209,19 @@ class AssignExpression : val targets = findTargets(src) if (targets.size == newType.types.size) { // Set the corresponding type on the left-side - newType.types.forEachIndexed { idx, t -> lhs.getOrNull(idx)?.addAssignedType(t) } + newType.types.forEachIndexed { idx, t -> + var lhs = lhs.getOrNull(idx) ?: return + lhs.addAssignedType(t) + } } } else { - findTargets(src).forEach { it.addAssignedType(newType) } + findTargets(src).forEach { + // If the type is unknown, we can also set it + if (it.type is UnknownType) { + it.type = newType + } + it.addAssignedType(newType) + } } // If this is used as an expression, we also set the type accordingly diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt new file mode 100644 index 0000000000..891c27b9c4 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, 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.frontends.Language + +/** + * This type represents a [Type] that is dynamically determined at run-time. This is used for a + * [Language]. which has dynamic runtime typing, such as Python or Java/TypeScript. + */ +class DynamicType(override var language: Language<*>?) : Type("dynamic", language) { + override fun reference(pointer: PointerType.PointerOrigin?): Type { + TODO("Not yet implemented") + } + + override fun dereference(): Type { + TODO("Not yet implemented") + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt index 50c54c1276..3bf69e4a65 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/HasType.kt @@ -179,7 +179,7 @@ interface HasType : ContextProvider, LanguageProvider { // If we would only propagate the unknown type, we can also skip it val newType = this.type - if (newType !is UnknownType) { + if (newType !is UnknownType && newType !is DynamicType) { // Immediately inform about changes typeObserver.typeChanged(newType, this) } @@ -207,6 +207,12 @@ interface HasType : ContextProvider, LanguageProvider { class InitializerTypePropagation(private var decl: HasType, private var tupleIdx: Int = -1) : HasType.TypeObserver { override fun typeChanged(newType: Type, src: HasType) { + // We do not want to change the type of a "dynamic type" declaration, since the "type" will + // always be dynamic. + if (decl.type is DynamicType) { + return + } + if (newType is TupleType && tupleIdx != -1) { decl.type = newType.types.getOrElse(tupleIdx) { decl.unknownType() } } else { diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt index 28a6dec744..e7a397e4c1 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt @@ -123,6 +123,7 @@ class PythonAddDeclarationsPass(ctx: TranslationContext) : ComponentPass(ctx) { newVariableDeclaration(node.name) } + decl.type = node.dynamicType() decl.code = node.code decl.location = node.location decl.isImplicit = true diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index 5da30ce511..087a661a64 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -1256,6 +1256,27 @@ class PythonFrontendTest : BaseTest() { assertInvokes(call, cCompletelyDifferentFunc) } + @Test + fun testTypePropagation() { + val topLevel = Path.of("src", "test", "resources", "python") + val tu = + analyzeAndGetFirstTU( + listOf(topLevel.resolve("type_propagation.py").toFile()), + topLevel, + true + ) { + it.registerLanguage() + } + assertNotNull(tu) + + var a = tu.variables["a"] + assertNotNull(a) + println("type: {${a.type}, assigned: {${a.assignedTypes}") + + var refs = tu.refs("a") + refs.forEach { println("type: {${it.type}, assigned: {${it.assignedTypes}") } + } + class PythonValueEvaluator : ValueEvaluator() { override fun computeBinaryOpEffect( lhsValue: Any?, diff --git a/cpg-language-python/src/test/resources/python/type_propagation.py b/cpg-language-python/src/test/resources/python/type_propagation.py new file mode 100644 index 0000000000..17a2d0ee43 --- /dev/null +++ b/cpg-language-python/src/test/resources/python/type_propagation.py @@ -0,0 +1,2 @@ +a = "test" +a = 2