Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playing around with a dynamic type #1668

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@

// 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)
Loading