Skip to content

Commit

Permalink
Dynamic type for python
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Dec 3, 2024
1 parent d6371a6 commit ee7657d
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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")

Check warning on line 36 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt#L36

Added line #L36 was not covered by tests
}

override fun dereference(): Type {
TODO("Not yet implemented")

Check warning on line 40 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/DynamicType.kt#L40

Added line #L40 was not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Check warning on line 537 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L537 was not covered by tests
return result
}

Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ class PythonLanguageFrontend(language: Language<PythonLanguageFrontend>, 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PythonLanguage>()
}
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?,
Expand Down
5 changes: 5 additions & 0 deletions cpg-language-python/src/test/resources/python/foobar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def foo(a):
return a+1

def bar():
return foo(42)

0 comments on commit ee7657d

Please sign in to comment.