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 32cf25f431..55cf9a17e9 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 @@ -49,6 +49,10 @@ fun LanguageProvider.incompleteType(): Type { return IncompleteType(this.language) } +fun LanguageProvider.dynamicType(): Type { + return DynamicType(this.language) +} + /** Returns a [PointerType] that describes an array reference to the current type. */ context(ContextProvider) fun Type.array(): Type { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt index 2dcca508e3..9cba1272b4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt @@ -30,10 +30,17 @@ import de.fraunhofer.aisec.cpg.graph.unknownType /** * This type represents a [Type] that uses auto-inference (usually from an initializer) to determine - * it's actual type. It is commonly used in dynamically typed languages or in languages that have a - * special keyword, such as `auto` in C++. + * its actual type. It is commonly used in languages that have a special keyword, such as `auto` in + * C++. * - * Note: This is intentionally a distinct type and not the [UnknownType]. + * Things to consider: + * 1) This is intentionally a distinct type and not the [UnknownType]. The type is known to the + * compiler (or to us) at some point, e.g., after an assignment, but it is not specifically + * specified in the source-code. + * 2) This should not be used to languages that have dynamic types. Once auto-type who was assigned + * to [Expression.type] is "resolved", it should be replaced by the actual type that it + * represents. Contrary to that, a [DynamicType] can change its internal type representation at + * any point, e.g., after the next assignment. */ class AutoType(override var language: Language<*>) : Type("auto", language) { override fun reference(pointer: PointerType.PointerOrigin?): Type { 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..53dc32cfb2 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt @@ -0,0 +1,43 @@ +/* + * 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 +import de.fraunhofer.aisec.cpg.graph.dynamicType + +/** + * 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 { + return dynamicType() + } + + override fun dereference(): Type { + return dynamicType() + } +} diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTest.kt index 90c63afbde..d90138e8a3 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/types/TypeTest.kt @@ -75,4 +75,13 @@ class TypeTest { assertLocalName("myNewClass***", type) } } + + @Test + fun testDynamicType() { + with(TestLanguageFrontend()) { + var type = dynamicType() + assertIs(type.reference(PointerType.PointerOrigin.ARRAY)) + assertIs(type.dereference()) + } + } } diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt index 3da8338b34..dfc3a0f2ff 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt @@ -505,7 +505,7 @@ class ExpressionHandler(frontend: PythonLanguageFrontend) : is Float, is Double -> primitiveType("float") else -> { - autoType() + unknownType() } } return newLiteral(node.value, type = tpe, rawNode = node) diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt index fe5e35f8d1..1fa6930c09 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.frontends.python import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.HasOverloadedOperation -import de.fraunhofer.aisec.cpg.graph.autoType import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration +import de.fraunhofer.aisec.cpg.graph.primitiveType import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference @@ -169,14 +169,13 @@ class PythonLanguage : ) override fun propagateTypeOfBinaryOperation(operation: BinaryOperator): Type { - val autoType = autoType() if ( operation.operatorCode == "/" && operation.lhs.type is NumericType && operation.rhs.type is NumericType ) { // In Python, the / operation automatically casts the result to a float - return getSimpleTypeOf("float") ?: autoType + return primitiveType("float") } else if ( operation.operatorCode == "//" && operation.lhs.type is NumericType && @@ -185,9 +184,9 @@ class PythonLanguage : return if (operation.lhs.type is IntegerType && operation.rhs.type is IntegerType) { // In Python, the // operation keeps the type as an int if both inputs are integers // or casts it to a float otherwise. - getSimpleTypeOf("int") ?: autoType + primitiveType("int") } else { - getSimpleTypeOf("float") ?: autoType + primitiveType("float") } } @@ -195,6 +194,20 @@ class PythonLanguage : return super.propagateTypeOfBinaryOperation(operation) } + override fun tryCast( + type: Type, + targetType: Type, + hint: HasType?, + targetHint: HasType?, + ): CastResult { + // We model parameter declarations without type hints as a "dynamic"-type. + if (targetType is DynamicType && targetHint is ParameterDeclaration) { + return DirectMatch + } + + return super.tryCast(type, targetType, hint, targetHint) + } + companion object { /** * This is a "modifier" to differentiate parameters in functions that are "positional" only. diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt index e9b6b39417..c144baa04a 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt @@ -161,8 +161,8 @@ class PythonLanguageFrontend(language: Language, ctx: Tr override fun typeOf(type: Python.AST.AST?): Type { return when (type) { null -> { - // No type information -> we return an autoType to infer things magically - autoType() + // No type information -> we return a dynamic type to infer things magically + dynamicType() } is Python.AST.Name -> { this.typeOf(type.id) 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 02ab828b73..f480841b07 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 @@ -1642,6 +1642,23 @@ class PythonFrontendTest : BaseTest() { refs.forEach { assertIsNot(it) } } + @Test + fun testFunctionResolution() { + val topLevel = Path.of("src", "test", "resources", "python") + val tu = + analyzeAndGetFirstTU(listOf(topLevel.resolve("foobar.py").toFile()), topLevel, true) { + it.registerLanguage() + } + assertNotNull(tu) + + // ensure, we only have two functions and no inferred ones + val functions = tu.functions + assertEquals(2, functions.size) + + val inferred = functions.filter { it.isInferred } + assertTrue(inferred.isEmpty()) + } + class PythonValueEvaluator : ValueEvaluator() { override fun computeBinaryOpEffect( lhsValue: Any?, diff --git a/cpg-language-python/src/test/resources/python/foobar.py b/cpg-language-python/src/test/resources/python/foobar.py new file mode 100644 index 0000000000..42112a2972 --- /dev/null +++ b/cpg-language-python/src/test/resources/python/foobar.py @@ -0,0 +1,5 @@ +def foo(a): + return a+1 + +def bar(): + return foo(42)