From 854252eb5dd475bf6f2d4aaf5bcaea5ed8c3d35a Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 27 Aug 2022 23:30:22 +0200 Subject: [PATCH] Various improvements to the Go language frontend (#893) * Various improvements to the Go language frontend Mainly crash fixes and some implementation improvements * Fixes #892 * Fixes #891 * Fixes #890 * Added more Go improvements * Fixes #894 * Added support for `ast.StarExpr` Fixes #895 --- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 3 +- .../graph/declarations/RecordDeclaration.java | 9 ++ .../expressions/ProblemExpression.kt | 10 +- .../src/main/golang/declarations.go | 6 + .../src/main/golang/expressions.go | 22 +++ .../src/main/golang/frontend/handler.go | 130 ++++++++++++++---- cpg-language-go/src/main/golang/types.go | 46 ++++--- .../cpg/frontends/golang/DeclarationTest.kt | 126 +++++++++++++++++ .../cpg/frontends/golang/ExpressionTest.kt | 83 +++++++++++ .../src/test/resources/golang/embed.go | 7 + .../src/test/resources/golang/type_assert.go | 16 +++ .../src/test/resources/golang/unnamed.go | 13 ++ 12 files changed, 418 insertions(+), 53 deletions(-) create mode 100644 cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt create mode 100644 cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt create mode 100644 cpg-language-go/src/test/resources/golang/embed.go create mode 100644 cpg-language-go/src/test/resources/golang/type_assert.go create mode 100644 cpg-language-go/src/test/resources/golang/unnamed.go diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index b5208d0f4b..b95178fc2c 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -949,8 +949,7 @@ object NodeBuilder { lang: LanguageFrontend? = null, rawNode: Any? = null ): ProblemExpression { - val node = ProblemExpression() - node.problem = problem + val node = ProblemExpression(problem, type) node.setCodeAndRegion(lang, rawNode, code) log(node) return node diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.java index 27cfc61711..30dc77bb40 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/RecordDeclaration.java @@ -261,6 +261,15 @@ public void setSuperClasses(List superClasses) { this.superClasses = superClasses; } + /** + * Adds a type to the list of super classes for this record declaration. + * + * @param superClass the super class. + */ + public void addSuperClass(Type superClass) { + this.superClasses.add(superClass); + } + /** * Interfaces implemented by this class. This concept is not present in C++ * diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/ProblemExpression.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/ProblemExpression.kt index e5b778fcfa..96feee329e 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/ProblemExpression.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/ProblemExpression.kt @@ -44,15 +44,15 @@ class ProblemExpression( .toString() } - override fun equals(o: Any?): Boolean { - if (this === o) { + override fun equals(other: Any?): Boolean { + if (this === other) { return true } - if (o !is ProblemExpression) { + if (other !is ProblemExpression) { return false } - val that = o - return (super.equals(that) && problem == that.problem) + + return (super.equals(other) && problem == other.problem) } override fun hashCode(): Int { diff --git a/cpg-language-go/src/main/golang/declarations.go b/cpg-language-go/src/main/golang/declarations.go index 611476027a..abbb611b8b 100644 --- a/cpg-language-go/src/main/golang/declarations.go +++ b/cpg-language-go/src/main/golang/declarations.go @@ -162,6 +162,12 @@ func (r *RecordDeclaration) AddMethod(m *MethodDeclaration) (err error) { return } +func (r *RecordDeclaration) AddSuperClass(t *Type) (err error) { + (*jnigi.ObjectRef)(r).CallMethod(env, "addSuperClass", nil, t) + + return +} + func (r *RecordDeclaration) IsNil() bool { return (*jnigi.ObjectRef)(r).IsNil() } diff --git a/cpg-language-go/src/main/golang/expressions.go b/cpg-language-go/src/main/golang/expressions.go index 4d2e2da80f..bf64e2b52b 100644 --- a/cpg-language-go/src/main/golang/expressions.go +++ b/cpg-language-go/src/main/golang/expressions.go @@ -49,6 +49,7 @@ func (e *Expression) IsArray() bool { } type CallExpression Expression +type CastExpression Expression type NewExpression Expression type ArrayCreationExpression Expression type ArraySubscriptionExpression Expression @@ -75,6 +76,19 @@ func NewCallExpression(fset *token.FileSet, astNode ast.Node) *CallExpression { return (*CallExpression)(c) } +func NewCastExpression(fset *token.FileSet, astNode ast.Node) *CastExpression { + c, err := env.NewObject("de/fraunhofer/aisec/cpg/graph/statements/expressions/CastExpression") + if err != nil { + log.Fatal(err) + + } + + updateCode(fset, (*Node)(c), astNode) + updateLocation(fset, (*Node)(c), astNode) + + return (*CastExpression)(c) +} + func NewMemberExpression(fset *token.FileSet, astNode ast.Node) *MemberExpression { c, err := env.NewObject("de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression") if err != nil { @@ -243,6 +257,14 @@ func (c *CallExpression) SetFqn(s string) { (*jnigi.ObjectRef)(c).SetField(env, "fqn", NewString(s)) } +func (c *CastExpression) SetExpression(e *Expression) { + (*jnigi.ObjectRef)(c).CallMethod(env, "setExpression", nil, (*jnigi.ObjectRef)(e).Cast("de/fraunhofer/aisec/cpg/graph/statements/expressions/Expression")) +} + +func (c *CastExpression) SetCastType(t *Type) { + (*jnigi.ObjectRef)(c).CallMethod(env, "setCastType", nil, t) +} + func (c *MemberCallExpression) SetName(s string) { (*Node)(c).SetName(s) } diff --git a/cpg-language-go/src/main/golang/frontend/handler.go b/cpg-language-go/src/main/golang/frontend/handler.go index 9210cd1471..578779c6f7 100644 --- a/cpg-language-go/src/main/golang/frontend/handler.go +++ b/cpg-language-go/src/main/golang/frontend/handler.go @@ -193,19 +193,25 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as var recordType = this.handleType(recv.Type) - receiver = cpg.NewVariableDeclaration(fset, nil) - - // TODO: should we use the FQN here? FQNs are a mess in the CPG... - receiver.SetName(recv.Names[0].Name) - receiver.SetType(recordType) - - err := m.SetReceiver(receiver) - if err != nil { - log.Fatal(err) + // The name of the Go receiver is optional. In fact, if the name is not + // specified we probably do not need any receiver variable at all, + // because the syntax is only there to ensure that this method is part + // of the struct, but it is not modifying the receiver. + if len(recv.Names) > 0 { + receiver = cpg.NewVariableDeclaration(fset, nil) + + // TODO: should we use the FQN here? FQNs are a mess in the CPG... + receiver.SetName(recv.Names[0].Name) + receiver.SetType(recordType) + + err := m.SetReceiver(receiver) + if err != nil { + log.Fatal(err) + } } if recordType != nil { - var recordName = (*cpg.Node)(recordType).GetName() + var recordName = recordType.GetName() // TODO: this will only find methods within the current translation unit // this is a limitation that we have for C++ as well @@ -275,7 +281,7 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as } } - this.LogDebug("Function has return type %s", (*cpg.Node)(t).GetName()) + this.LogDebug("Function has return type %s", t.GetName()) f.SetType(t) @@ -283,13 +289,29 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as // this is a method; however I am not quite sure if this makes sense for // go, since we do not have a 'this', but rather a named receiver - this.LogDebug("Parsing function body") - for _, param := range funcDecl.Type.Params.List { p := cpg.NewParamVariableDeclaration(fset, param) - // TODO: more than one name? - p.SetName(param.Names[0].Name) + this.LogDebug("Parsing param: %+v", param) + + // Somehow parameters end up having no name sometimes, have not fully understood why. + if len(param.Names) > 0 { + // TODO: more than one name? + var name = param.Names[0].Name + + // If the name is an underscore, it means that the parameter is + // unnamed. In order to avoid confusing and some compatibility with + // other languages, we are just setting the name to an empty string + // in this case. + if name == "_" { + name = "" + } + + p.SetName(name) + } else { + this.LogError("Some param has no name, which is a bit weird: %+v", param) + } + p.SetType(this.handleType(param.Type)) // add parameter to scope @@ -298,6 +320,8 @@ func (this *GoLanguageFrontend) handleFuncDecl(fset *token.FileSet, funcDecl *as this.handleComments((*cpg.Node)(p), param) } + this.LogDebug("Parsing function body of %s", (*cpg.Node)(f).GetName()) + // parse body s := this.handleBlockStmt(fset, funcDecl.Body) @@ -430,7 +454,7 @@ func (this *GoLanguageFrontend) handleStructTypeSpec(fset *token.FileSet, typeDe if field.Names == nil { // retrieve the root type name - var typeName = (*cpg.Node)(t.GetRoot()).GetName() + var typeName = t.GetRoot().GetName() this.LogDebug("Handling embedded field of type %s", typeName) @@ -465,14 +489,25 @@ func (this *GoLanguageFrontend) handleInterfaceTypeSpec(fset *token.FileSet, typ if !interfaceType.Incomplete { for _, method := range interfaceType.Methods.List { - m := cpg.NewMethodDeclaration(fset, method) - t := this.handleType(method.Type) - m.SetType(t) - m.SetName(method.Names[0].Name) + // Even though this list is called "Methods", it contains all kinds + // of things, so we need to proceed with caution. Only if the + // "method" actually has a name, we declare a new method + // declaration. + if len(method.Names) > 0 { + m := cpg.NewMethodDeclaration(fset, method) + m.SetType(t) + m.SetName(method.Names[0].Name) - scope.AddDeclaration((*cpg.Declaration)(m)) + scope.AddDeclaration((*cpg.Declaration)(m)) + } else { + this.LogDebug("Adding %s as super class of interface %s", t.GetName(), (*cpg.Node)(r).GetName()) + // Otherwise, it contains either types or interfaces. For now we + // hope that it only has interfaces. We consider embedded + // interfaces as sort of super types for this interface. + r.AddSuperClass(t) + } } } @@ -626,6 +661,8 @@ func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) ( e = (*cpg.Expression)(this.handleBinaryExpr(fset, v)) case *ast.UnaryExpr: e = (*cpg.Expression)(this.handleUnaryExpr(fset, v)) + case *ast.StarExpr: + e = (*cpg.Expression)(this.handleStarExpr(fset, v)) case *ast.SelectorExpr: e = (*cpg.Expression)(this.handleSelectorExpr(fset, v)) case *ast.KeyValueExpr: @@ -636,6 +673,10 @@ func (this *GoLanguageFrontend) handleExpr(fset *token.FileSet, expr ast.Expr) ( e = (*cpg.Expression)(this.handleCompositeLit(fset, v)) case *ast.Ident: e = (*cpg.Expression)(this.handleIdent(fset, v)) + case *ast.TypeAssertExpr: + e = (*cpg.Expression)(this.handleTypeAssertExpr(fset, v)) + case *ast.ParenExpr: + e = this.handleExpr(fset, v.X) default: this.LogError("Could not parse expression of type %T: %+v", v, v) // TODO: return an error instead? @@ -991,6 +1032,23 @@ func (this *GoLanguageFrontend) handleUnaryExpr(fset *token.FileSet, unaryExpr * return u } +func (this *GoLanguageFrontend) handleStarExpr(fset *token.FileSet, unaryExpr *ast.StarExpr) *cpg.UnaryOperator { + u := cpg.NewUnaryOperator(fset, unaryExpr) + + input := this.handleExpr(fset, unaryExpr.X) + + err := u.SetOperatorCode("*") + if err != nil { + log.Fatal(err) + } + + if input != nil { + u.SetInput(input) + } + + return u +} + func (this *GoLanguageFrontend) handleSelectorExpr(fset *token.FileSet, selectorExpr *ast.SelectorExpr) *cpg.DeclaredReferenceExpression { base := this.handleExpr(fset, selectorExpr.X) @@ -1102,7 +1160,7 @@ func (this *GoLanguageFrontend) handleCompositeLit(fset *token.FileSet, lit *ast var typ = this.handleType(lit.Type) if typ != nil { - (*cpg.Node)(c).SetName((*cpg.Node)(typ).GetName()) + (*cpg.Node)(c).SetName(typ.GetName()) (*cpg.Expression)(c).SetType(typ) } @@ -1144,6 +1202,21 @@ func (this *GoLanguageFrontend) handleIdent(fset *token.FileSet, ident *ast.Iden return ref } +func (this *GoLanguageFrontend) handleTypeAssertExpr(fset *token.FileSet, assert *ast.TypeAssertExpr) *cpg.CastExpression { + cast := cpg.NewCastExpression(fset, assert) + + // Parse the inner expression + expr := this.handleExpr(fset, assert.X) + + // Parse the type + typ := this.handleType(assert.Type) + + cast.SetExpression(expr) + cast.SetCastType(typ) + + return cast +} + func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { this.LogDebug("Parsing type %T: %+v", typeExpr, typeExpr) @@ -1168,7 +1241,7 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { log.Fatal(err) } - this.LogDebug("Pointer to %s", (*cpg.Node)(t).GetName()) + this.LogDebug("Pointer to %s", t.GetName()) return t.Reference(i) case *ast.ArrayType: @@ -1180,7 +1253,7 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { log.Fatal(err) } - this.LogDebug("Array of %s", (*cpg.Node)(t).GetName()) + this.LogDebug("Array of %s", t.GetName()) return t.Reference(i) case *ast.MapType: @@ -1190,8 +1263,9 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { keyType := this.handleType(v.Key) valueType := this.handleType(v.Value) - (*cpg.ObjectType)(t).AddGeneric(keyType) - (*cpg.ObjectType)(t).AddGeneric(valueType) + // TODO(oxisto): Find a better way to represent casts + (&(cpg.ObjectType{Type: *t})).AddGeneric(keyType) + (&(cpg.ObjectType{Type: *t})).AddGeneric(valueType) return t case *ast.ChanType: @@ -1199,7 +1273,7 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { t := cpg.TypeParser_createFrom("chan", false) chanType := this.handleType(v.Value) - (*cpg.ObjectType)(t).AddGeneric(chanType) + (&(cpg.ObjectType{Type: *t})).AddGeneric(chanType) return t case *ast.FuncType: @@ -1211,7 +1285,7 @@ func (this *GoLanguageFrontend) handleType(typeExpr ast.Expr) *cpg.Type { } } - return cpg.UnknownType_getUnknown() + return &cpg.UnknownType_getUnknown().Type } func (this *GoLanguageFrontend) isBuiltinType(s string) bool { diff --git a/cpg-language-go/src/main/golang/types.go b/cpg-language-go/src/main/golang/types.go index 44d0af77ab..8d4cf0d6b6 100644 --- a/cpg-language-go/src/main/golang/types.go +++ b/cpg-language-go/src/main/golang/types.go @@ -36,13 +36,17 @@ import ( var env *jnigi.Env -type Type jnigi.ObjectRef +type Type struct{ *jnigi.ObjectRef } func (t *Type) ConvertToGo(o *jnigi.ObjectRef) error { - *t = (Type)(*o) + t.ObjectRef = o return nil } +func (t *Type) ConvertToJava() (obj *jnigi.ObjectRef, err error) { + return t.ObjectRef, nil +} + func (*Type) GetClassName() string { return "de/fraunhofer/aisec/cpg/graph/types/Type" } @@ -51,19 +55,25 @@ func (*Type) IsArray() bool { return false } -type ObjectType jnigi.ObjectRef +func (t *Type) GetName() string { + // A little bit hacky until we also convert node to a struct + return (*Node)(t.ObjectRef).GetName() +} -func (t *ObjectType) ConvertToGo(o *jnigi.ObjectRef) error { - *t = (ObjectType)(*o) - return nil +type ObjectType struct { + Type } func (*ObjectType) GetClassName() string { return "de/fraunhofer/aisec/cpg/graph/types/ObjectType" } -func (*ObjectType) IsArray() bool { - return false +type UnknownType struct { + Type +} + +func (*UnknownType) GetClassName() string { + return "de/fraunhofer/aisec/cpg/graph/types/UnknownType" } type HasType jnigi.ObjectRef @@ -83,8 +93,8 @@ func TypeParser_createFrom(s string, resolveAlias bool) *Type { return &t } -func UnknownType_getUnknown() *Type { - var t Type +func UnknownType_getUnknown() *UnknownType { + var t UnknownType err := env.CallStaticMethod("de/fraunhofer/aisec/cpg/graph/types/UnknownType", "getUnknownType", &t) if err != nil { log.Fatal(err) @@ -94,19 +104,19 @@ func UnknownType_getUnknown() *Type { return &t } -func (h *Type) GetRoot() *Type { - var t Type - err := (*jnigi.ObjectRef)(h).CallMethod(env, "getRoot", &t) +func (t *Type) GetRoot() *Type { + var root Type + err := t.CallMethod(env, "getRoot", &root) if err != nil { log.Fatal(err) } - return &t + return &root } func (t *Type) Reference(o *jnigi.ObjectRef) *Type { var refType Type - err := (*jnigi.ObjectRef)(t).CallMethod(env, "reference", &refType, (*jnigi.ObjectRef)(o).Cast("de/fraunhofer/aisec/cpg/graph/types/PointerType$PointerOrigin")) + err := t.CallMethod(env, "reference", &refType, (*jnigi.ObjectRef)(o).Cast("de/fraunhofer/aisec/cpg/graph/types/PointerType$PointerOrigin")) if err != nil { log.Fatal(err) @@ -117,7 +127,7 @@ func (t *Type) Reference(o *jnigi.ObjectRef) *Type { func (h *HasType) SetType(t *Type) { if t != nil { - (*jnigi.ObjectRef)(h).CallMethod(env, "setType", nil, (*jnigi.ObjectRef)(t).Cast("de/fraunhofer/aisec/cpg/graph/types/Type")) + (*jnigi.ObjectRef)(h).CallMethod(env, "setType", nil, t.Cast("de/fraunhofer/aisec/cpg/graph/types/Type")) } } @@ -134,8 +144,8 @@ func (h *HasType) GetType() *Type { func (t *ObjectType) AddGeneric(g *Type) { // Stupid workaround, since casting does not work. See // https://github.com/timob/jnigi/issues/60 - var objType = jnigi.WrapJObject(uintptr((*jnigi.ObjectRef)(t).JObject()), "de/fraunhofer/aisec/cpg/graph/types/ObjectType", false) - err := objType.CallMethod(env, "addGeneric", nil, (*jnigi.ObjectRef)(g).Cast("de/fraunhofer/aisec/cpg/graph/types/Type")) + var objType = jnigi.WrapJObject(uintptr(t.JObject()), "de/fraunhofer/aisec/cpg/graph/types/ObjectType", false) + err := objType.CallMethod(env, "addGeneric", nil, g.Cast("de/fraunhofer/aisec/cpg/graph/types/Type")) if err != nil { log.Fatal(err) } diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt new file mode 100644 index 0000000000..f0eb5f6e15 --- /dev/null +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/DeclarationTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.golang + +import de.fraunhofer.aisec.cpg.ExperimentalGolang +import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import java.nio.file.Path +import kotlin.test.* + +@OptIn(ExperimentalGolang::class) +class DeclarationTest { + @Test + fun testUnnamedReceiver() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("unnamed.go").toFile()), + topLevel, + true + ) { + it.registerLanguage( + GoLanguageFrontend::class.java, + GoLanguageFrontend.GOLANG_EXTENSIONS + ) + } + assertNotNull(tu) + + val main = tu.byNameOrNull("main") + assertNotNull(main) + + val myStruct = main.byNameOrNull("main.MyStruct") + assertNotNull(myStruct) + + // Receiver should be null since its unnamed + val myFunc = myStruct.byNameOrNull("MyFunc") + assertNotNull(myFunc) + assertNull(myFunc.receiver) + } + + @Test + fun testUnnamedParameter() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("unnamed.go").toFile()), + topLevel, + true + ) { + it.registerLanguage( + GoLanguageFrontend::class.java, + GoLanguageFrontend.GOLANG_EXTENSIONS + ) + } + assertNotNull(tu) + + val main = tu.byNameOrNull("main") + assertNotNull(main) + + // Parameter should be there but not have a name + val myGlobalFunc = main.byNameOrNull("MyGlobalFunc") + assertNotNull(myGlobalFunc) + + val param = myGlobalFunc.parameters.firstOrNull() + assertNotNull(param) + assertEquals("", param.name) + } + + @Test + fun testEmbeddedInterface() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("embed.go").toFile()), + topLevel, + true + ) { + it.registerLanguage( + GoLanguageFrontend::class.java, + GoLanguageFrontend.GOLANG_EXTENSIONS + ) + } + assertNotNull(tu) + + val main = tu.byNameOrNull("main") + assertNotNull(main) + + val myInterface = main.byNameOrNull("main.MyInterface") + assertNotNull(myInterface) + + val myOtherInterface = main.byNameOrNull("main.MyOtherInterface") + assertNotNull(myOtherInterface) + + // MyOtherInterface should be in the superClasses and superTypeDeclarations of MyInterface, + // since it is embedded and thus MyInterface "extends" it + assertContains(myInterface.superTypeDeclarations, myOtherInterface) + assertTrue(myInterface.superClasses.any { it.name == myOtherInterface.name }) + } +} diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt new file mode 100644 index 0000000000..33dca24c36 --- /dev/null +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.frontends.golang + +import de.fraunhofer.aisec.cpg.ExperimentalGolang +import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.graph.bodyOrNull +import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CastExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import java.nio.file.Path +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertSame +import org.junit.jupiter.api.Test + +class ExpressionTest { + @OptIn(ExperimentalGolang::class) + @Test + fun testTypeAssert() { + val topLevel = Path.of("src", "test", "resources", "golang") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("type_assert.go").toFile()), + topLevel, + true + ) { + it.registerLanguage( + GoLanguageFrontend::class.java, + GoLanguageFrontend.GOLANG_EXTENSIONS + ) + } + assertNotNull(tu) + + val main = tu.byNameOrNull("main") + assertNotNull(main) + + val mainFunc = main.byNameOrNull("main") + assertNotNull(mainFunc) + + val f = + (mainFunc.bodyOrNull(0))?.singleDeclaration + as? VariableDeclaration + assertNotNull(f) + + val s = + (mainFunc.bodyOrNull(1))?.singleDeclaration + as? VariableDeclaration + assertNotNull(s) + + val cast = s.initializer as? CastExpression + assertNotNull(cast) + assertEquals("main.MyStruct", cast.castType.name) + assertSame(f, (cast.expression as? DeclaredReferenceExpression)?.refersTo) + } +} diff --git a/cpg-language-go/src/test/resources/golang/embed.go b/cpg-language-go/src/test/resources/golang/embed.go new file mode 100644 index 0000000000..60b9e72e02 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/embed.go @@ -0,0 +1,7 @@ +package main + +type MyInterface interface { + MyOtherInterface +} + +type MyOtherInterface interface {} \ No newline at end of file diff --git a/cpg-language-go/src/test/resources/golang/type_assert.go b/cpg-language-go/src/test/resources/golang/type_assert.go new file mode 100644 index 0000000000..cbee1cb06b --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/type_assert.go @@ -0,0 +1,16 @@ +package main + +import "fmt" + +type MyStruct struct {} +type MyInterface interface { + MyFunc() +} +func (MyStruct) MyFunc() {} + +func main () { + var f MyInterface = MyStruct{} + var s = f.(MyStruct) + + fmt.Printf("%+v", s) +} \ No newline at end of file diff --git a/cpg-language-go/src/test/resources/golang/unnamed.go b/cpg-language-go/src/test/resources/golang/unnamed.go new file mode 100644 index 0000000000..df79b70673 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/unnamed.go @@ -0,0 +1,13 @@ +package main + +import "context" + +type MyStruct struct{} + +func (MyStruct) MyFunc() int { + return 1 +} + +func MyGlobalFunc(_ context.Context) int { + return 2 +}