diff --git a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/passes/AstCreator.scala b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/passes/AstCreator.scala index 8b60aa740e9a..889511e6f0b0 100644 --- a/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/passes/AstCreator.scala +++ b/joern-cli/frontends/javasrc2cpg/src/main/scala/io/joern/javasrc2cpg/passes/AstCreator.scala @@ -726,11 +726,33 @@ class AstCreator(filename: String, javaParserAst: CompilationUnit, global: Globa private def astForFieldVariable(v: VariableDeclarator, fieldDeclaration: FieldDeclaration): Ast = { // TODO: Should be able to find expected type here val annotations = fieldDeclaration.getAnnotations - val typeFullName = - typeInfoCalc - .fullName(v.getType) - .orElse(scopeStack.lookupVariableType(v.getTypeAsString, wildcardFallback = true)) - .getOrElse(s"${Defines.UnresolvedNamespace}.${v.getTypeAsString}") + + // variable can be declared with generic type, so we need to get rid of the <> part of it to get the package information + // and append the <> when forming the typeFullName again + // Ex - private Consumer consumer; + // From Consumer we need to get to Consumer so splitting it by '<' and then combining with '<' to + // form typeFullName as Consumer + + val typeFullNameWithoutGenericSplit = typeInfoCalc + .fullName(v.getType) + .orElse(scopeStack.lookupVariableType(v.getTypeAsString, wildcardFallback = true)) + .getOrElse(s"${Defines.UnresolvedNamespace}.${v.getTypeAsString}") + val typeFullName = { + // Check if the typeFullName is unresolved and if it has generic information to resolve the typeFullName + if ( + typeFullNameWithoutGenericSplit + .contains(Defines.UnresolvedNamespace) && v.getTypeAsString.contains(Defines.LeftAngularBracket) + ) { + val splitByLeftAngular = v.getTypeAsString.split(Defines.LeftAngularBracket) + scopeStack.lookupVariableType(splitByLeftAngular.head, wildcardFallback = true) match { + case Some(fullName) => + fullName + splitByLeftAngular + .slice(1, splitByLeftAngular.size) + .mkString(Defines.LeftAngularBracket, Defines.LeftAngularBracket, "") + case None => typeFullNameWithoutGenericSplit + } + } else typeFullNameWithoutGenericSplit + } val name = v.getName.toString val node = memberNode(v, name, s"$typeFullName $name", typeFullName) val memberAst = Ast(node) diff --git a/joern-cli/frontends/javasrc2cpg/src/test/scala/io/joern/javasrc2cpg/querying/MemberTests.scala b/joern-cli/frontends/javasrc2cpg/src/test/scala/io/joern/javasrc2cpg/querying/MemberTests.scala index 22132a1ac9cc..b1327386e8d2 100644 --- a/joern-cli/frontends/javasrc2cpg/src/test/scala/io/joern/javasrc2cpg/querying/MemberTests.scala +++ b/joern-cli/frontends/javasrc2cpg/src/test/scala/io/joern/javasrc2cpg/querying/MemberTests.scala @@ -20,6 +20,35 @@ class NewMemberTests extends JavaSrcCode2CpgFixture { cpg.member.name("x").astChildren.size shouldBe 0 } + "member with generic class" should { + val cpg = code(""" + |import org.apache.kafka.clients.consumer.Consumer; + |public class CountryPopulationConsumer { + | + | private Consumer consumer; + | + | void foo() { + | consumer.poll(1000); + | } + |}""".stripMargin) + + "have a resolved typeFullName" in { + cpg.member + .name("consumer") + .typeFullName + .head shouldBe "org.apache.kafka.clients.consumer.Consumer" + } + + "have a resolved package name in methodFullName" in { + cpg + .call("poll") + .methodFullName + .head + .split(":") + .head shouldBe "org.apache.kafka.clients.consumer.Consumer.poll" + } + } + "enum entries with anonymous classes should not result in subtrees to the member node" in { val cpg = code(""" |enum Foo { diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/Defines.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/Defines.scala index 34e8d3589706..301cd82b55ad 100644 --- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/Defines.scala +++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/Defines.scala @@ -25,4 +25,6 @@ object Defines { // In some languages like Javascript dynamic calls do not provide any statically known // method/function interface information. In those cases please use this value. val DynamicCallUnknownFullName = "" + + val LeftAngularBracket = "<" }