Skip to content

Commit ee7657d

Browse files
committed
Dynamic type for python
1 parent d6371a6 commit ee7657d

File tree

8 files changed

+97
-8
lines changed

8 files changed

+97
-8
lines changed

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/TypeBuilder.kt

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ fun LanguageProvider.autoType(): Type {
4545
return AutoType(this.language)
4646
}
4747

48+
fun LanguageProvider.dynamicType(): Type {
49+
return DynamicType(this.language)
50+
}
51+
4852
fun MetadataProvider?.incompleteType(): Type {
4953
return IncompleteType()
5054
}

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/AutoType.kt

+10-3
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,17 @@ import de.fraunhofer.aisec.cpg.graph.unknownType
3030

3131
/**
3232
* This type represents a [Type] that uses auto-inference (usually from an initializer) to determine
33-
* it's actual type. It is commonly used in dynamically typed languages or in languages that have a
34-
* special keyword, such as `auto` in C++.
33+
* its actual type. It is commonly used in languages that have a special keyword, such as `auto` in
34+
* C++.
3535
*
36-
* Note: This is intentionally a distinct type and not the [UnknownType].
36+
* Things to consider:
37+
* 1) This is intentionally a distinct type and not the [UnknownType]. The type is known to the
38+
* compiler (or to us) at some point, e.g., after an assignment, but it is not specifically
39+
* specified in the source-code.
40+
* 2) This should not be used to languages that have dynamic types. Once auto-type who was assigned
41+
* to [Expression.type] is "resolved", it should be replaced by the actual type that it
42+
* represents. Contrary to that, a [DynamicType] can change its internal type representation at
43+
* any point, e.g., after the next assignment.
3744
*/
3845
class AutoType(override var language: Language<*>?) : Type("auto", language) {
3946
override fun reference(pointer: PointerType.PointerOrigin?): Type {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2024, Fraunhofer AISEC. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* $$$$$$\ $$$$$$$\ $$$$$$\
17+
* $$ __$$\ $$ __$$\ $$ __$$\
18+
* $$ / \__|$$ | $$ |$$ / \__|
19+
* $$ | $$$$$$$ |$$ |$$$$\
20+
* $$ | $$ ____/ $$ |\_$$ |
21+
* $$ | $$\ $$ | $$ | $$ |
22+
* \$$$$$ |$$ | \$$$$$ |
23+
* \______/ \__| \______/
24+
*
25+
*/
26+
package de.fraunhofer.aisec.cpg.graph.types
27+
28+
import de.fraunhofer.aisec.cpg.frontends.Language
29+
30+
/**
31+
* This type represents a [Type] that is dynamically determined at run-time. This is used for a
32+
* [Language], which has dynamic runtime typing, such as Python or Java/TypeScript.
33+
*/
34+
class DynamicType(override var language: Language<*>?) : Type("dynamic", language) {
35+
override fun reference(pointer: PointerType.PointerOrigin?): Type {
36+
TODO("Not yet implemented")
37+
}
38+
39+
override fun dereference(): Type {
40+
TODO("Not yet implemented")
41+
}
42+
}

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -528,13 +528,13 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
528528
setOf(),
529529
mapOf(),
530530
setOf(),
531-
CallResolutionResult.SuccessKind.UNRESOLVED,
531+
UNRESOLVED,
532532
source.scope,
533533
)
534534
val language = source.language
535535

536536
if (language == null) {
537-
result.success = CallResolutionResult.SuccessKind.PROBLEMATIC
537+
result.success = PROBLEMATIC
538538
return result
539539
}
540540

@@ -569,7 +569,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
569569

570570
// If we have a "problematic" result, we can stop here. In this case we cannot really
571571
// determine anything more.
572-
if (result.success == CallResolutionResult.SuccessKind.PROBLEMATIC) {
572+
if (result.success == PROBLEMATIC) {
573573
result.bestViable = result.viableFunctions
574574
return result
575575
}

cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt

+14
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ class PythonLanguage :
194194
return super.propagateTypeOfBinaryOperation(operation)
195195
}
196196

197+
override fun tryCast(
198+
type: Type,
199+
targetType: Type,
200+
hint: HasType?,
201+
targetHint: HasType?
202+
): CastResult {
203+
// We model parameter declarations without type hints as a "dynamic"-type.
204+
if (targetType is DynamicType && targetHint is ParameterDeclaration) {
205+
return DirectMatch
206+
}
207+
208+
return super.tryCast(type, targetType, hint, targetHint)
209+
}
210+
197211
companion object {
198212
/**
199213
* This is a "modifier" to differentiate parameters in functions that are "positional" only.

cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ class PythonLanguageFrontend(language: Language<PythonLanguageFrontend>, ctx: Tr
143143
override fun typeOf(type: Python.AST.AST?): Type {
144144
return when (type) {
145145
null -> {
146-
// No type information -> we return an autoType to infer things magically
147-
autoType()
146+
// No type information -> we return a dynamic type to infer things magically
147+
dynamicType()
148148
}
149149
is Python.AST.Name -> {
150150
this.typeOf(type.id)

cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt

+17
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,23 @@ class PythonFrontendTest : BaseTest() {
15891589
}
15901590
}
15911591

1592+
@Test
1593+
fun testFunctionResolution() {
1594+
val topLevel = Path.of("src", "test", "resources", "python")
1595+
val tu =
1596+
analyzeAndGetFirstTU(listOf(topLevel.resolve("foobar.py").toFile()), topLevel, true) {
1597+
it.registerLanguage<PythonLanguage>()
1598+
}
1599+
assertNotNull(tu)
1600+
1601+
// ensure, we only have two functions and no inferred ones
1602+
val functions = tu.functions
1603+
assertEquals(2, functions.size)
1604+
1605+
val inferred = functions.filter { it.isInferred }
1606+
assertTrue(inferred.isEmpty())
1607+
}
1608+
15921609
class PythonValueEvaluator : ValueEvaluator() {
15931610
override fun computeBinaryOpEffect(
15941611
lhsValue: Any?,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def foo(a):
2+
return a+1
3+
4+
def bar():
5+
return foo(42)

0 commit comments

Comments
 (0)