Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -37,6 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference
import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.graph.unknownType
import de.fraunhofer.aisec.cpg.helpers.Util.warnWithFileLocation
import de.fraunhofer.aisec.cpg.helpers.neo4j.SimpleNameConverter
import de.fraunhofer.aisec.cpg.persistence.DoNotPersist
Expand Down Expand Up @@ -248,6 +249,26 @@ open class PythonLanguage :
primitiveType("float")
}
}
/**
* Python boolean operators 'or' and 'and' are interpreted as follows: `x or y` returns
* `x` if `x` is truthy, else `y`. `x and y` returns `x` if `x` is falsy, else `y`.
*
* Since we cannot determine the boolean value of `x`, one of the operands could be
* returned. Thus, we return `lhsType` if both operands have the same type. If either
* side is dynamic, `DynamicType` is returned, and otherwise fall back to
* `unknownType()`.
*
* See https://docs.python.org/3/reference/expressions.html#boolean-operations
* *
*/
"or",
"and" -> {
return when {
lhsType == rhsType -> lhsType
Comment thread
maximiliankaul marked this conversation as resolved.
Outdated
lhsType is DynamicType || rhsType is DynamicType -> DynamicType(this)
else -> unknownType()
}
}

// The rest behaves like other languages
else ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ class PythonAddDeclarationsPass(ctx: TranslationContext) : ComponentPass(ctx), L
}

// If this is a member expression, and we do not know the base's type, we cannot create a
// declaration
if (ref is MemberExpression && ref.base.type is UnknownType) {
// declaration. However, we need to exclude the receiver (self) because its type is
// not yet resolved at this point, but we still need to
// create fields for assignments.
if (ref is MemberExpression && ref.base.type is UnknownType && !ref.refersToReceiver) {
return null
}

Expand Down Expand Up @@ -136,11 +138,14 @@ class PythonAddDeclarationsPass(ctx: TranslationContext) : ComponentPass(ctx), L
when {
// Check, whether we are referring to a "self.X", which would create a field
scopeManager.isInRecord && scopeManager.isInFunction && ref.refersToReceiver -> {
val recordScope = scopeManager.firstScopeIsInstanceOrNull<RecordScope>()
// If the field already exists in the record scope, do not create a duplicate
if (recordScope?.symbols?.containsKey(ref.name.localName) == true) {
return null
}
// We need to temporarily jump into the scope of the current record to
// add the field. These are instance attributes
scopeManager.withScope(scopeManager.firstScopeIsInstanceOrNull<RecordScope>()) {
newField(ref.name)
}
scopeManager.withScope(recordScope) { newField(ref.name) }
}
scopeManager.isInRecord && scopeManager.isInFunction && ref is MemberExpression -> {
// If this is any other member expression, we are usually not interested in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Method
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression
import de.fraunhofer.aisec.cpg.graph.types.DynamicType
import de.fraunhofer.aisec.cpg.graph.types.ObjectType
import de.fraunhofer.aisec.cpg.test.analyze
import de.fraunhofer.aisec.cpg.test.assertFullName
import de.fraunhofer.aisec.cpg.test.assertInvokes
import de.fraunhofer.aisec.cpg.test.assertNotRefersTo
import de.fraunhofer.aisec.cpg.test.assertRefersTo
import java.io.File
Expand Down Expand Up @@ -125,4 +128,64 @@ class SymbolResolverTest {
assertNotNull(doSomething, "Expected to find a single call to 'do_something'")
assertIs<MemberCallExpression>(doSomething, "'do_something' should be a member call")
}

@Test
fun testFieldCallResolution() {
val topLevel = File("src/test/resources/python/field_call.py")
val result =
analyze(listOf(topLevel), topLevel.toPath(), true) {
it.registerLanguage<PythonLanguage>()
}
assertNotNull(result)

val clientClass = result.records["Client"]
assertNotNull(clientClass)

val serviceClass = result.records["Service"]
assertNotNull(serviceClass)

val clientField = serviceClass.fields["client"]
assertIs<Field>(clientField)

val fieldType = clientField.type
assertIs<ObjectType>(fieldType)
Comment thread
lshala marked this conversation as resolved.

val sendMethod = clientClass.methods["send"]
assertNotNull(sendMethod)

val sendCall = result.calls["send"]
assertNotNull(sendCall)
assertIs<MemberCallExpression>(sendCall)
assertInvokes(sendCall, sendMethod)
}

@Test
fun testFieldCallResolutionWithOrOperator() {
val topLevel = File("src/test/resources/python/field_call.py")
val result =
analyze(listOf(topLevel), topLevel.toPath(), true) {
it.registerLanguage<PythonLanguage>()
}
assertNotNull(result)

val clientClass = result.records["Client"]
assertNotNull(clientClass)

val serviceClass = result.records["Service2"]
assertNotNull(serviceClass)

val clientField = serviceClass.fields["client"]
assertIs<Field>(clientField)

val fieldType = clientField.type
assertIs<DynamicType>(fieldType)

val sendMethod = clientClass.methods["send"]
assertNotNull(sendMethod)

val sendCall = result.calls["send"]
assertNotNull(sendCall)
assertIs<MemberCallExpression>(sendCall)
assertInvokes(sendCall, sendMethod)
}
}
21 changes: 21 additions & 0 deletions cpg-language-python/src/test/resources/python/field_call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Client:
def send(self, data):
return data


class Service:
def __init__(self):
self.client = Client()

def send_foo(self):
result = self.client.send("foo")
return result


class Service2:
def __init__(self, client: Client):
self.client = client or Client()

def send_bar(self):
result = self.client.send("bar")
return result