Repository: joernio/joern (scala)

Issue:
[Bug] `Unhandled field structure` in RecoverForJavaScriptFile
In TypeScript, usage of the `<>` type assertion syntax can lead to a `Unhandled field structure` warning.
Parsing of the `<>` type assertion syntax has been recently fixed/introduced: #2578

**To Reproduce**
Steps to reproduce the behavior:
1. Import the following code snippet in joern:
```typescript
let imgScr: string = <string>this.imageElement;
this.imageElement = new HTMLImageElement();
(<HTMLImageElement>this.imageElement).src = imgScr;
```
2. Observe the following warnings:
```
2023-04-27 12:42:16.117 WARN RecoverForJavaScriptFile: Unhandled field structure (UNKNOWN,<HTMLImageElement>this.imageElement),(FIELD_IDENTIFIER,src) @ dnd.config.ts#L3
2023-04-27 12:42:16.119 WARN RecoverForJavaScriptFile: Unhandled field structure (UNKNOWN,<HTMLImageElement>this.imageElement),(FIELD_IDENTIFIER,src) @ dnd.config.ts#L3
2023-04-27 12:42:16.139 WARN RecoverForJavaScriptFile: Unhandled field structure (UNKNOWN,<HTMLImageElement>this.imageElement),(FIELD_IDENTIFIER,src) @ dnd.config.ts#L3
2023-04-27 12:42:16.140 WARN RecoverForJavaScriptFile: Unhandled field structure (UNKNOWN,<HTMLImageElement>this.imageElement),(FIELD_IDENTIFIER,src) @ dnd.config.ts#L3
res0: Cpg = Cpg (Graph [77 nodes])
```

**Expected behavior**
Parsing should succeed.

**Desktop (please complete the following information):**
 - OS: Linux pop-os 6.0.12-76060006-generic 22.04
 - Joern Version: 1.1.1642
 - javac 11.0.17

Pull request:
* Example `<string>this.imageElement` are now handled
* `astForCastExpression` now attaches the type as a type hint if `typeFor` succeeds
* Added `HTMLImageElement` to global builtins, and it gets prefixed with `__ecma`

Question @max-leuthaeuser: The last point about adding `HTMLImageElement` to built-ins, I could create a PR to extend that list based on what can be found on [Mozilla Docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement). Is this a direction we would want with these types? There is a scraper ready somewhere in the [private JoernTI](https://github.com/joernio/type-inference-models) project for this.

Resolves #2596

Patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala
index 1fdb39932262..ec7ee513bacc 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstCreator.scala
@@ -147,6 +147,7 @@ class AstCreator(
       case TSEnumDeclaration         => astForEnum(nodeInfo)
       case DeclareTypeAlias          => astForTypeAlias(nodeInfo)
       case TypeAlias                 => astForTypeAlias(nodeInfo)
+      case TSTypeAssertion           => astForCastExpression(nodeInfo)
       case TSTypeAliasDeclaration    => astForTypeAlias(nodeInfo)
       case NewExpression             => astForNewExpression(nodeInfo)
       case ThisExpression            => astForThisExpression(nodeInfo)
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala
index 3d83b96cdbc6..91a64add16ba 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala
@@ -248,13 +248,19 @@ trait AstForExpressionsCreator { this: AstCreator =>
   }
 
   protected def astForCastExpression(castExpr: BabelNodeInfo): Ast = {
-    val op      = Operators.cast
+    val op = Operators.cast
+    val typ = typeFor(castExpr) match {
+      case t if GlobalBuiltins.builtins.contains(t) => s"__ecma.$t"
+      case t                                        => t
+    }
     val lhsNode = castExpr.json("typeAnnotation")
-    val lhsAst  = Ast(createLiteralNode(code(lhsNode), None, line(lhsNode), column(lhsNode)))
-    val rhsAst  = astForNodeWithFunctionReference(castExpr.json("expression"))
-
+    val lhsAst = Ast(
+      createLiteralNode(code(lhsNode), None, line(lhsNode), column(lhsNode)).dynamicTypeHintFullName(Seq(typ))
+    )
+    val rhsAst = astForNodeWithFunctionReference(castExpr.json("expression"))
     val callNode =
       createCallNode(castExpr.code, op, DispatchTypes.STATIC_DISPATCH, castExpr.lineNumber, castExpr.columnNumber)
+        .dynamicTypeHintFullName(Seq(typ))
     val argAsts = List(lhsAst, rhsAst)
     callAst(callNode, argAsts)
   }
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/GlobalBuiltins.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/GlobalBuiltins.scala
index 7a97b2eb4c78..8b3c31910762 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/GlobalBuiltins.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/GlobalBuiltins.scala
@@ -3,17 +3,42 @@ package io.joern.jssrc2cpg.passes
 object GlobalBuiltins {
 
   val builtins: Set[String] = Set(
+    "AggregateError",
+    "Array",
+    "ArrayBuffer",
+    "AsyncFunction",
+    "AsyncGenerator",
+    "AsyncGeneratorFunction",
+    "Atomics",
+    "BigInt",
+    "BigInt64Array",
+    "BigUint64Array",
+    "Boolean",
     "Buffer.from",
-    "decodeURI",
-    "decodeURIComponent",
-    "encodeURI",
-    "encodeURIComponent",
-    "eval",
-    "fetch",
-    "isFinite",
-    "isNaN",
+    "DataView",
+    "Date",
+    "Error",
+    "EvalError",
+    "FinalizationRegistry",
+    "Float32Array",
+    "Float64Array",
+    "Function",
+    "Generator",
+    "GeneratorFunction",
+    "HTMLImageElement",
+    "Infinity",
+    "Int16Array",
+    "Int32Array",
+    "Int8Array",
+    "InternalError",
+    "Intl",
+    "JSON",
     "JSON.parse",
     "JSON.stringify",
+    "Map",
+    "Math",
+    "NaN",
+    "Number",
     "Number.isFinite",
     "Number.isInteger",
     "Number.isNaN",
@@ -27,9 +52,7 @@ object GlobalBuiltins {
     "Number.prototype.toSource",
     "Number.prototype.toString",
     "Number.prototype.valueOf",
-    "parseFloat",
-    "parseInt",
-    "uneval",
+    "Object",
     "Object.assign",
     "Object.create",
     "Object.defineProperties",
@@ -62,13 +85,58 @@ object GlobalBuiltins {
     "Object.seal",
     "Object.setPrototypeOf",
     "Object.values",
+    "Promise",
     "Promise.all",
     "Promise.allSettled",
     "Promise.any",
     "Promise.race",
     "Promise.reject",
     "Promise.resolve",
-    "localStorage.setItem"
+    "Proxy",
+    "RangeError",
+    "ReferenceError",
+    "Reflect",
+    "RegExp",
+    "Set",
+    "SharedArrayBuffer",
+    "String",
+    "Symbol",
+    "SyntaxError",
+    "TypeError",
+    "TypedArray",
+    "URIError",
+    "Uint16Array",
+    "Uint32Array",
+    "Uint8Array",
+    "Uint8ClampedArray",
+    "WeakMap",
+    "WeakRef",
+    "WeakSet",
+    "decodeURI",
+    "decodeURI",
+    "decodeURIComponent",
+    "decodeURIComponent",
+    "encodeURI",
+    "encodeURI",
+    "encodeURIComponent",
+    "encodeURIComponent",
+    "escape",
+    "eval",
+    "eval",
+    "fetch",
+    "globalThis",
+    "isFinite",
+    "isFinite",
+    "isNaN",
+    "isNaN",
+    "localStorage.setItem",
+    "parseFloat",
+    "parseFloat",
+    "parseInt",
+    "parseInt",
+    "undefined",
+    "unescape",
+    "uneval"
   )
 
 }
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/JavaScriptTypeRecovery.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/JavaScriptTypeRecovery.scala
index 933f50cfa96d..2a917b643572 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/JavaScriptTypeRecovery.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/passes/JavaScriptTypeRecovery.scala
@@ -171,8 +171,9 @@ private class RecoverForJavaScriptFile(cpg: Cpg, cu: File, builder: DiffGraphBui
     val constructorPaths = if (c.methodFullName.endsWith(".alloc")) {
       def newChildren = c.inAssignment.astSiblings.isCall.nameExact("<operator>.new").astChildren
       val possibleImportIdentifier = newChildren.isIdentifier.headOption match {
-        case Some(i) => symbolTable.get(i)
-        case None    => Set.empty[String]
+        case Some(i) if GlobalBuiltins.builtins.contains(i.name) => Set(s"__ecma.${i.name}")
+        case Some(i)                                             => symbolTable.get(i)
+        case None                                                => Set.empty[String]
       }
       val possibleConstructorPointer =
         newChildren.astChildren.isFieldIdentifier.map(f => CallAlias(f.canonicalName, Some("this"))).headOption match {
diff --git a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/passes/frontend/XTypeRecovery.scala b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/passes/frontend/XTypeRecovery.scala
index c8adc16075ab..9ba2535cf3e9 100644
--- a/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/passes/frontend/XTypeRecovery.scala
+++ b/joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/passes/frontend/XTypeRecovery.scala
@@ -469,6 +469,7 @@ abstract class RecoverForXCompilationUnit[CompilationUnitType <: AstNode](
       case Operators.alloc       => visitIdentifierAssignedToConstructor(i, c)
       case Operators.fieldAccess => visitIdentifierAssignedToFieldLoad(i, new FieldAccess(c))
       case Operators.indexAccess => visitIdentifierAssignedToIndexAcess(i, c)
+      case Operators.cast        => visitIdentifierAssignedToCast(i, c)
       case x                     => logger.debug(s"Unhandled operation $x (${c.code}) @ ${debugLocation(c)}"); Set.empty
     }
   }
@@ -746,11 +747,13 @@ abstract class RecoverForXCompilationUnit[CompilationUnitType <: AstNode](
 
   /** Visits an identifier being assigned to the result of an index access operation.
     */
-  protected def visitIdentifierAssignedToIndexAcess(i: Identifier, c: Call): Set[String] = {
-    val iaTypes = getTypesFromCall(c)
-    if (iaTypes.nonEmpty) associateTypes(i, iaTypes)
-    else Set.empty
-  }
+  protected def visitIdentifierAssignedToIndexAcess(i: Identifier, c: Call): Set[String] =
+    associateTypes(i, getTypesFromCall(c))
+
+  /** Visits an identifier that is the target of a cast operation.
+    */
+  protected def visitIdentifierAssignedToCast(i: Identifier, c: Call): Set[String] =
+    associateTypes(i, (c.typeFullName +: c.dynamicTypeHintFullName).filterNot(_ == "ANY").toSet)
 
   protected def getFieldBaseType(base: Identifier, fi: FieldIdentifier): Set[String] =
     getFieldBaseType(base.name, fi.canonicalName)


Test patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala
index 09eb94ee18af..96b84d9b1598 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/TypeRecoveryPassTests.scala
@@ -277,4 +277,19 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite {
     }
   }
 
+  "Type casts of an identifier and call receiver" should {
+    lazy val cpg = code("""
+        |let imgScr: string = <string>this.imageElement;
+        |this.imageElement = new HTMLImageElement();
+        |(<HTMLImageElement>this.imageElement).src = imgScr;
+        |""".stripMargin)
+
+    "succeed in propagating type cast identifiers" in {
+      val Some(imgSrc) = cpg.identifier("imgScr").headOption
+      imgSrc.typeFullName shouldBe "__ecma.String"
+      val Some(_tmp_0) = cpg.identifier("_tmp_0").headOption
+      _tmp_0.typeFullName shouldBe "__ecma.HTMLImageElement"
+    }
+  }
+
 }
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala
index 8f52af61ba62..a50fac03b20e 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala
@@ -60,7 +60,7 @@ class TsAstCreationPassTest extends AbstractPassTest {
     }
 
     "have correct structure for type assertion" in TsAstFixture("let emptyArray = <VNode[]>[];") { cpg =>
-      cpg.call.code.l shouldBe List("let emptyArray = <VNode[]>[]")
+      cpg.assignment.code.l shouldBe List("let emptyArray = <VNode[]>[]")
     }
 
     "have correct structure for satisfies expressions" in TsAstFixture("let x = y satisfies T;") { cpg =>