From 401eb482e8d12df903ff13d3ed39c8eec377cc9e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 30 Sep 2023 22:01:16 +0200 Subject: [PATCH] Fixed incorrect type of CallExpression when using tuples --- .../graph/declarations/FunctionDeclaration.kt | 2 +- .../statements/expressions/CallExpression.kt | 27 +++++--------- .../aisec/cpg/passes/SymbolResolver.kt | 9 +++++ .../aisec/cpg/frontends/golang/GoLanguage.kt | 19 +++++++++- .../frontends/golang/GoLanguageFrontend.kt | 22 +++++++++++ .../aisec/cpg/frontends/golang/GoUtils.kt | 13 ++++++- .../golang/GoLanguageFrontendTest.kt | 4 +- .../resources/golang/complex_resolution.go | 37 +++++++++++++++++++ 8 files changed, 108 insertions(+), 25 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt index b36964351f3..1f486610cb6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionDeclaration.kt @@ -140,7 +140,7 @@ open class FunctionDeclaration : ValueDeclaration(), DeclarationHolder, Resoluti } val provided = targetSignature[i] val expression = targetExpressions?.get(i) - if (!provided.isDerivedFrom(declared.type, this, expression)) { + if (!provided.isDerivedFrom(declared.type, expression, this)) { return false } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt index 0cb49764d94..987bb468dd7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/CallExpression.kt @@ -26,7 +26,6 @@ package de.fraunhofer.aisec.cpg.graph.statements.expressions import de.fraunhofer.aisec.cpg.PopulatedByPass -import de.fraunhofer.aisec.cpg.commonType import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration @@ -251,23 +250,15 @@ open class CallExpression : Expression(), HasType.TypeObserver, ArgumentHolder { return } - // TODO(oxisto): We could actually use the newType (which is a FunctionType now) - val types = - invokeEdges - .map(PropertyEdge::end) - .mapNotNull { - if (it.returnTypes.size == 1) { - return@mapNotNull it.returnTypes.firstOrNull() - } else if (it.returnTypes.size > 1) { - return@mapNotNull TupleType(it.returnTypes) - } - null - } - .toSet() - val alternative = if (types.isNotEmpty()) types.first() else unknownType() - val commonType = types.commonType ?: alternative - - this.type = commonType + if (newType !is FunctionType) { + return + } + + if (newType.returnTypes.size == 1) { + this.type = newType.returnTypes.single() + } else if (newType.returnTypes.size > 1) { + this.type = TupleType(newType.returnTypes) + } } override fun assignedTypeChanged(assignedTypes: Set, src: HasType) { 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 c6f5ac43b2f..a0863ac34b3 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 @@ -180,6 +180,15 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { if (current !is Reference || current is MemberExpression) return + // Ignore references to anonymous identifiers, if the language supports it (e.g., the _ + // identifier in Go) + if ( + language is HasAnonymousIdentifier && + current.name.localName == language.anonymousIdentifier + ) { + return + } + // For now, we need to ignore reference expressions that are directly embedded into call // expressions, because they are the "callee" property. In the future, we will use this // property to actually resolve the function call. However, there is a special case that diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt index 1da2c9c1875..75dc4327a85 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt @@ -156,13 +156,28 @@ class GoLanguage : return true } + // We accept the "nil" literal for the following super types: + // - pointers + // - interfaces + // - maps + // - slices (which we model also as a pointer type) + // - channels + // - function types + if (hint.isNil) { + return superType is PointerType || + superType.isInterface || + superType.isMap || + superType.isChannel || + superType is FunctionType + } + // We accept all kind of numbers if the literal is part of the call expression - if (hint is FunctionDeclaration && superHint is Literal<*>) { + if (superHint is FunctionDeclaration && hint is Literal<*>) { return type is NumericType && superType is NumericType } // We additionally want to emulate the behaviour of Go's interface system here - if (superType is ObjectType && superType.recordDeclaration?.kind == "interface") { + if (superType.isInterface) { var b = true val target = (type.root as? ObjectType)?.recordDeclaration diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt index 33a06366d4e..cd4e3f02587 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontend.kt @@ -36,7 +36,9 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.DeclarationSequence import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.newNamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.types.FunctionType +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.unknownType @@ -456,6 +458,26 @@ val Type?.isOverlay: Boolean return this is ObjectType && this.recordDeclaration?.kind == "overlay" } +val Type.isInterface: Boolean + get() { + return this is ObjectType && this.recordDeclaration?.kind == "interface" + } + +val Type.isMap: Boolean + get() { + return this is ObjectType && this.name.localName == "map" + } + +val Type.isChannel: Boolean + get() { + return this is ObjectType && this.name.localName == "chan" + } + +val HasType?.isNil: Boolean + get() { + return this is Literal<*> && this.name.localName == "nil" + } + /** * This function produces a Go-style function type name such as `func(int, string) string` or * `func(int) (error, string)` diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoUtils.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoUtils.kt index a06a2471b52..5919f7443ac 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoUtils.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoUtils.kt @@ -171,7 +171,8 @@ internal class Project { modulePath: String, goos: String? = null, goarch: String? = null, - tags: List = listOf() + goVersion: Int? = null, + tags: MutableList = mutableListOf() ): Project { val project = Project() val symbols = mutableMapOf() @@ -217,7 +218,15 @@ internal class Project { goos?.let { symbols["GOOS"] = it } goarch?.let { symbols["GOARCH"] = it } - tags.let { symbols["-tags"] = tags.joinToString { " " } } + + if (goVersion != null) { + // Populate tags with go-version + for (i in 1..goVersion) { + tags += "go1.$i" + } + } + + tags.let { symbols["-tags"] = tags.joinToString(" ") } // Pre-filter any files we are not building anyway based on our symbols files = files.filter { shouldBeBuild(it, symbols) }.toMutableList() diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index ee44f5fc202..0a902174356 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -1108,7 +1108,7 @@ class GoLanguageFrontendTest : BaseTest() { // non-inferred functions val calls = result.calls calls.forEach { - assertTrue(it.invokes.isNotEmpty()) + assertTrue(it.invokes.isNotEmpty(), "${it.name}'s invokes should not be empty") it.invokes.forEach { func -> assertFalse(func.isInferred) } } @@ -1116,7 +1116,7 @@ class GoLanguageFrontendTest : BaseTest() { assertNotNull(funcy) funcy.invokeEdges.all { it.getProperty(Properties.DYNAMIC_INVOKE) == true } - val refs = result.refs.filter { it.name.localName != "_" } + val refs = result.refs.filter { it.name.localName != GoLanguage().anonymousIdentifier } refs.forEach { assertNotNull(it.refersTo) } } } diff --git a/cpg-language-go/src/test/resources/golang/complex_resolution.go b/cpg-language-go/src/test/resources/golang/complex_resolution.go index 801d6f3c953..427d7245771 100644 --- a/cpg-language-go/src/test/resources/golang/complex_resolution.go +++ b/cpg-language-go/src/test/resources/golang/complex_resolution.go @@ -17,16 +17,53 @@ func main() { some.self.self.self.Do() + // A function as parameter _ = doFuncy(func() error { return nil }) + + // Passing "nil" as a function type + _ = doFuncy(nil) + + // Passing "nil" as an interface + doSomething(nil) + + // Passing "nil" as pointer + doPointer(nil) + + // Numeric literals can be assigned to a function accepting of any numeric type + doInt64(1) } func Func(args ...int) {} type Funcy func() error +type Somethinger interface { + Something() (*t, error) +} + +type s struct { +} + +type t struct { + s *s +} + +func (s *s) Do() {} + func doFuncy(funcy Funcy) (err error) { err = funcy() return } + +func doSomething(s Somethinger) { + t, err := s.Something() + + t.s.Do() + _ = err +} + +func doPointer(p *int) {} + +func doInt64(i int64) {}