Skip to content

Commit

Permalink
[java] Resolve External Non-JDK Types with Type Arguments (joernio#4527)
Browse files Browse the repository at this point in the history
It's been known that certain types return as `ANY` when it's an external type with type arguments. However, the type would resolve when type arguments are removed.

This PR detects if the type to be resolved is a `ClassOrInterfaceType`, and if so, will only pass the type name to the type resolver, and resolve the type arguments separately. The type arguments are then placed in the `fullName` if `keepFullNames` is enabled.
  • Loading branch information
DavidBakerEffendi authored May 2, 2024
1 parent deb8842 commit 2287248
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class AstCreator(
fileContent: Option[String],
global: Global,
val symbolSolver: JavaSymbolSolver,
keepTypeArguments: Boolean
protected val keepTypeArguments: Boolean
)(implicit val withSchemaValidation: ValidationMode)
extends AstCreatorBase(filename)
with AstNodeBuilder[Node, AstCreator]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.joern.javasrc2cpg.astcreation.expressions

import com.github.javaparser.ast.Node
import com.github.javaparser.ast.`type`.ClassOrInterfaceType
import com.github.javaparser.ast.body.VariableDeclarator
import com.github.javaparser.ast.expr.AssignExpr.Operator
import com.github.javaparser.ast.expr.{AssignExpr, Expression, ObjectCreationExpr, VariableDeclarationExpr}
Expand Down Expand Up @@ -104,11 +105,26 @@ trait AstForVarDeclAndAssignsCreator { this: AstCreator =>
}

def astsForVariableDeclarator(variableDeclarator: VariableDeclarator, originNode: Node): Seq[Ast] = {

val variableDeclaratorType = variableDeclarator.getType
// If generics are in the type name, we may be unable to resolve the type
val (variableTypeString, maybeTypeArgs) = variableDeclaratorType match {
case typ: ClassOrInterfaceType =>
val typeParams = typ.getTypeArguments.toScala.map(_.asScala.flatMap(typeInfoCalc.fullName))
(typ.getName.asString(), typeParams)
case _ => (variableDeclarator.getTypeAsString, None)
}

val typeFullName = tryWithSafeStackOverflow(
scope
.lookupType(variableDeclarator.getTypeAsString, includeWildcards = false)
.lookupType(variableTypeString, includeWildcards = false)
.orElse(typeInfoCalc.fullName(variableDeclarator.getType))
).toOption.flatten
).toOption.flatten.map { typ =>
maybeTypeArgs match {
case Some(typeArgs) if keepTypeArguments => s"$typ<${typeArgs.mkString(",")}>"
case _ => typ
}
}

val (correspondingNode, localAst): (NewVariableNode, Option[Ast]) =
scope.lookupVariable(variableDeclarator.getNameAsString).variableNode.map((_, None)).getOrElse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.joern.javasrc2cpg.querying

import io.joern.javasrc2cpg.Config
import io.joern.javasrc2cpg.testfixtures.JavaSrcCode2CpgFixture
import io.shiftleft.codepropertygraph.generated.Operators
import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, Local}
import io.shiftleft.semanticcpg.language.*

Expand Down Expand Up @@ -158,29 +159,53 @@ class VarDeclTests extends JavaSrcCode2CpgFixture {
}

"generics with 'keep type arguments' config" should {
val cpg = code("""
|import java.util.ArrayList;
|import java.util.List;
|import java.util.HashMap;
|
|public class Main {
| public static void main(String[] args) {
| // Create a List of Strings
| List<String> stringList = new ArrayList<>();
| var stringIntMap = new HashMap<String, Integer>();
| }
|}
|
|""".stripMargin)
.withConfig(Config().withKeepTypeArguments(true))

"show the fully qualified type arguments for `List`" in {
"show the fully qualified type arguments for stdlib `List and `Map` objects" in {
val cpg = code("""
|import java.util.ArrayList;
|import java.util.List;
|import java.util.HashMap;
|
|public class Main {
| public static void main(String[] args) {
| // Create a List of Strings
| List<String> stringList = new ArrayList<>();
| var stringIntMap = new HashMap<String, Integer>();
| }
|}
|
|""".stripMargin)
.withConfig(Config().withKeepTypeArguments(true))

cpg.identifier("stringList").typeFullName.head shouldBe "java.util.List<java.lang.String>"
cpg.identifier("stringIntMap").typeFullName.head shouldBe "java.util.HashMap<java.lang.String,java.lang.Integer>"
}

"show the fully qualified type arguments for `Map`" in {
cpg.identifier("stringIntMap").typeFullName.head shouldBe "java.util.HashMap<java.lang.String,java.lang.Integer>"
"show the fully qualified names of external types" in {
val cpg = code("""
|import org.apache.flink.streaming.api.datastream.DataStream;
|import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
|import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;
|import org.apache.flink.streaming.util.serialization.SimpleStringSchema;
|
|import java.util.Properties;
|
|public class FlinkKafkaExample {
| public static void main() throws Exception {
| Properties kafkaProps = new Properties();
| SimpleStringSchema schema = new SimpleStringSchema();
| FlinkKafkaProducer<String> kafkaProducer = new FlinkKafkaProducer<String>("kafka-topic", schema, kafkaProps);
| }
|}
|""".stripMargin).withConfig(Config().withKeepTypeArguments(true))

cpg.call
.codeExact("new FlinkKafkaProducer<String>(\"kafka-topic\", schema, kafkaProps)")
.filterNot(_.name == Operators.alloc)
.map(_.methodFullName)
.head shouldBe "org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer<java.lang.String>.<init>:<unresolvedSignature>(3)"
}

}

}

0 comments on commit 2287248

Please sign in to comment.