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 82a5990fd2..62f26c6ce2 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 @@ -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 diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt index 488bedb528..8a389e1a58 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionHandler.kt @@ -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 @@ -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 { @@ -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) { @@ -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] @@ -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" + ) + } } 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 75dc4327a8..3ac9be6243 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 @@ -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. */ @@ -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) + } + } } 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 0a90217435..5d56ceefdd 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 @@ -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 @@ -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 diff --git a/cpg-language-go/src/test/resources/golang/complex_resolution.go b/cpg-language-go/src/test/resources/golang/complex_resolution/calls/calls.go similarity index 83% rename from cpg-language-go/src/test/resources/golang/complex_resolution.go rename to cpg-language-go/src/test/resources/golang/complex_resolution/calls/calls.go index a9cdb4349a..d24e573c41 100644 --- a/cpg-language-go/src/test/resources/golang/complex_resolution.go +++ b/cpg-language-go/src/test/resources/golang/complex_resolution/calls/calls.go @@ -1,4 +1,6 @@ -package p +package calls + +import "mymodule.io/complex_resolution/util" type Something struct { self *Something @@ -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) @@ -78,4 +83,6 @@ func doPointer(p *int) {} func doInt64(i int64) {} +func doLength(l util.Length) {} + func old(v interface{}) {} diff --git a/cpg-language-go/src/test/resources/golang/complex_resolution/go.mod b/cpg-language-go/src/test/resources/golang/complex_resolution/go.mod new file mode 100644 index 0000000000..56a450ca21 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/complex_resolution/go.mod @@ -0,0 +1,3 @@ +module mymodule.io/complex_resolution + +go 1.21.1 diff --git a/cpg-language-go/src/test/resources/golang/complex_resolution/util/util.go b/cpg-language-go/src/test/resources/golang/complex_resolution/util/util.go new file mode 100644 index 0000000000..a3445c609e --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/complex_resolution/util/util.go @@ -0,0 +1,12 @@ +package util + +type Length int64 + +func (l Length) Centimeter() int64 { + return int64(l) +} + +const ( + Centimeter Length = 1 + Meter = 100 * Centimeter +)