Skip to content

Commit

Permalink
More improved Go symbol resolving
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Oct 1, 2023
1 parent b35972d commit d399fd6
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
walker.strategy = Strategy::EOG_FORWARD
walker.clearCallbacks()
walker.registerHandler(this::handle)

for (tu in component.translationUnits) {
currentTU = tu
// gather all resolution start holders and their start nodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.frontends.golang
import de.fraunhofer.aisec.cpg.frontends.golang.GoStandardLibrary.Ast.BasicLit.Kind.*
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.scopes.NameScope
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.types.ObjectType
import de.fraunhofer.aisec.cpg.graph.types.PointerType
Expand Down Expand Up @@ -149,7 +150,18 @@ class ExpressionHandler(frontend: GoLanguageFrontend) :
return literal
}

return newReference(ident.name, rawNode = ident)
// If we are directly in a name scope, make sure we FQN'ize the name, to help with
// resolution; unless it is already an FQN or a package name.
var name: CharSequence = ident.name
name =
when {
name in builtins -> name
isPackageName(name) -> name
language?.namespaceDelimiter.toString() in name -> name
else -> parseName((scope as? NameScope)?.name?.fqn(ident.name) ?: ident.name)
}

return newReference(name, rawNode = ident)
}

private fun handleIndexExpr(indexExpr: GoStandardLibrary.Ast.IndexExpr): SubscriptExpression {
Expand Down Expand Up @@ -320,16 +332,7 @@ class ExpressionHandler(frontend: GoLanguageFrontend) :

// Check, if this just a regular reference to a variable with a package scope and not a
// member expression
var isMemberExpression = true
for (imp in frontend.currentFile?.imports ?: listOf()) {
// If we have an alias, we need to check it instead of the import name
val name = imp.name?.name ?: imp.importName

if (base.name.localName == name) {
// found a package name, so this is NOT a member expression
isMemberExpression = false
}
}
val isMemberExpression = !isPackageName(base.name.localName)

val ref =
if (isMemberExpression) {
Expand All @@ -345,6 +348,22 @@ class ExpressionHandler(frontend: GoLanguageFrontend) :
return ref
}

private fun isPackageName(
name: CharSequence,
): Boolean {
for (imp in frontend.currentFile?.imports ?: listOf()) {
// If we have an alias, we need to check it instead of the import name
val packageName = imp.name?.name ?: imp.importName

if (name == packageName) {
// found a package name
return true
}
}

return false
}

/**
* This function handles a ast.SliceExpr, which is an extended version of ast.IndexExpr. We are
* modelling this as a combination of a [SubscriptExpression] that contains a [RangeExpression]
Expand Down Expand Up @@ -450,4 +469,52 @@ class ExpressionHandler(frontend: GoLanguageFrontend) :

return lambda
}

companion object {
val builtins =
listOf(
"bool",
"uint8",
"uint16",
"uint32",
"uint64",
"int8",
"int16",
"int32",
"int64",
"float32",
"float64",
"complex64",
"complex128",
"string",
"int",
"uint",
"uintptr",
"byte",
"rune",
"any",
"comparable",
"iota",
"nil",
"append",
"copy",
"delete",
"len",
"cap",
"make",
"max",
"min",
"new",
"complex",
"real",
"imag",
"clear",
"close",
"panic",
"recover",
"print",
"println",
"error"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ package de.fraunhofer.aisec.cpg.frontends.golang
import de.fraunhofer.aisec.cpg.frontends.*
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.primitiveType
import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal
import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.graph.unknownType
import org.neo4j.ogm.annotation.Transient

/** The Go language. */
Expand Down Expand Up @@ -194,4 +196,32 @@ class GoLanguage :

return false
}

override fun propagateTypeOfBinaryOperation(operation: BinaryOperator): Type {
if (operation.operatorCode == "==") {
return super.propagateTypeOfBinaryOperation(operation)
}

// Deal with literals. Numeric literals can also be used in simple arithmetic of the
// underlying type is numeric
return when {
operation.lhs is Literal<*> && (operation.lhs as Literal<*>).type is NumericType -> {
val type = operation.rhs.type
if (type is NumericType || type.underlyingType is NumericType) {
type
} else {
unknownType()
}
}
operation.rhs is Literal<*> && (operation.rhs as Literal<*>).type is NumericType -> {
val type = operation.lhs.type
if (type is NumericType || type.underlyingType is NumericType) {
type
} else {
unknownType()
}
}
else -> super.propagateTypeOfBinaryOperation(operation)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1091,11 +1091,14 @@ class GoLanguageFrontendTest : BaseTest() {

@Test
fun testComplexResolution() {
val topLevel = Path.of("src", "test", "resources", "golang")
val topLevel = Path.of("src", "test", "resources", "golang", "complex_resolution")
val result =
analyze(
listOf(
topLevel.resolve("complex_resolution.go").toFile(),
// We need to keep them in this particular order, otherwise we will not resolve
// cross-package correctly yet
topLevel.resolve("util/util.go").toFile(),
topLevel.resolve("calls/calls.go").toFile(),
),
topLevel,
true
Expand All @@ -1104,6 +1107,10 @@ class GoLanguageFrontendTest : BaseTest() {
}
assertNotNull(result)

val meter = result.variables["util.Meter"]
assertNotNull(meter)
assertLocalName("Length", meter.type)

// All calls including the one to "funcy" (which is a dynamic invoke) should be resolved to
// non-inferred functions
val calls = result.calls
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package p
package calls

import "mymodule.io/complex_resolution/util"

type Something struct {
self *Something
Expand Down Expand Up @@ -34,6 +36,9 @@ func main() {
// Numeric literals can be assigned to a function accepting of any numeric type
doInt64(1)

// Numeric literals can also be used in simple arithmetic of the underlying type is numeric
doLength(5 * util.Meter)

// interface{} and any should be interchangeable
var a any = nil
old(a)
Expand Down Expand Up @@ -78,4 +83,6 @@ func doPointer(p *int) {}

func doInt64(i int64) {}

func doLength(l util.Length) {}

func old(v interface{}) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module mymodule.io/complex_resolution

go 1.21.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package util

type Length int64

func (l Length) Centimeter() int64 {
return int64(l)
}

const (
Centimeter Length = 1
Meter = 100 * Centimeter
)

0 comments on commit d399fd6

Please sign in to comment.