Skip to content

Commit

Permalink
Fixed incorrect type of CallExpression when using tuples
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Sep 30, 2023
1 parent 4f70b8a commit 401eb48
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<FunctionDeclaration>::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<Type>, src: HasType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ internal class Project {
modulePath: String,
goos: String? = null,
goarch: String? = null,
tags: List<String> = listOf()
goVersion: Int? = null,
tags: MutableList<String> = mutableListOf()
): Project {
val project = Project()
val symbols = mutableMapOf<String, String>()
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1108,15 +1108,15 @@ 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) }
}

val funcy = result.calls["funcy"]
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) }
}
}
37 changes: 37 additions & 0 deletions cpg-language-go/src/test/resources/golang/complex_resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

0 comments on commit 401eb48

Please sign in to comment.