From ee7657dc0dc2225013c4c742b76157fccb5ed035 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 3 Dec 2024 19:44:07 +0100 Subject: [PATCH] Dynamic type for python --- .../fraunhofer/aisec/cpg/graph/TypeBuilder.kt | 4 ++ .../aisec/cpg/graph/types/AutoType.kt | 13 ++++-- .../aisec/cpg/graph/types/DynamicType.kt | 42 +++++++++++++++++++ .../aisec/cpg/passes/SymbolResolver.kt | 6 +-- .../cpg/frontends/python/PythonLanguage.kt | 14 +++++++ .../python/PythonLanguageFrontend.kt | 4 +- .../frontends/python/PythonFrontendTest.kt | 17 ++++++++ .../src/test/resources/python/foobar.py | 5 +++ 8 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt create mode 100644 cpg-language-python/src/test/resources/python/foobar.py 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..87e32f1f2f 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 @@ -45,6 +45,10 @@ fun LanguageProvider.autoType(): Type { return AutoType(this.language) } +fun LanguageProvider.dynamicType(): Type { + return DynamicType(this.language) +} + fun MetadataProvider?.incompleteType(): Type { return IncompleteType() } 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 681790d96d..b0c929cbce 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..54f49cf94e --- /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/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index fdc7f0edca..9494b3fcb9 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 @@ -528,13 +528,13 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { setOf(), mapOf(), setOf(), - CallResolutionResult.SuccessKind.UNRESOLVED, + UNRESOLVED, source.scope, ) val language = source.language if (language == null) { - result.success = CallResolutionResult.SuccessKind.PROBLEMATIC + result.success = PROBLEMATIC return result } @@ -569,7 +569,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // If we have a "problematic" result, we can stop here. In this case we cannot really // determine anything more. - if (result.success == CallResolutionResult.SuccessKind.PROBLEMATIC) { + if (result.success == PROBLEMATIC) { result.bestViable = result.viableFunctions return result } 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 9537028190..2d096a9218 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 @@ -194,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 98adf9ad7c..0835390a38 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 @@ -143,8 +143,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 4f8f908227..7745d76f0b 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 @@ -1589,6 +1589,23 @@ class PythonFrontendTest : BaseTest() { } } + @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)