Skip to content

Commit f8812a8

Browse files
also handle function and property decorators
1 parent 256ba1f commit f8812a8

File tree

5 files changed

+385
-29
lines changed

5 files changed

+385
-29
lines changed

joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForFunctionsCreator.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,8 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
353353
handleParameters(func.json("params").arr.toSeq, mutable.ArrayBuffer.empty[Ast], createLocals = false)
354354
}
355355

356-
val annotationExpressionAsts = decoratorExpressionElements(func).map(e => astForNodeWithFunctionReference(e.json))
357-
358-
val blockAsts = annotationExpressionAsts ++ methodBlockContent.constructorContent.flatMap(m =>
359-
methodBlockContent.typeDecl.map(astForClassMember(m, _))
360-
)
356+
val blockAsts =
357+
methodBlockContent.constructorContent.flatMap(m => methodBlockContent.typeDecl.map(astForClassMember(m, _)))
361358
setArgumentIndices(blockAsts)
362359

363360
val methodReturnNode_ = methodReturnNode(func)
@@ -459,9 +456,8 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
459456
}
460457
case _ => createBlockStatementAsts(bodyJson("body"))
461458
}
462-
val annotationExpressionAsts = decoratorExpressionElements(func).map(e => astForNodeWithFunctionReference(e.json))
463459

464-
val methodBlockChildren = annotationExpressionAsts ++ methodBlockContent.constructorContent.flatMap(m =>
460+
val methodBlockChildren = methodBlockContent.constructorContent.flatMap(m =>
465461
methodBlockContent.typeDecl.map(astForClassMember(m, _))
466462
) ++ additionalBlockStatements.toList ++ bodyStmtAsts
467463
setArgumentIndices(methodBlockChildren)

joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala

Lines changed: 311 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,10 +528,12 @@ trait AstForTypesCreator(implicit withSchemaValidation: ValidationMode) { this:
528528
clazz.columnNumber
529529
)
530530

531-
val annotationAst = createClassAnnotationAst(clazz, classIdNode)
532-
if (annotationAst.root.isDefined) {
531+
val classAnnotationAst = createClassAnnotationAst(clazz, classIdNode)
532+
val propertyAnnotationAsts = createPropertyAnnotationAsts(clazz, classIdNode)
533+
val methodAnnotationAsts = createMethodAnnotationAsts(clazz, classIdNode)
534+
if (classAnnotationAst.root.isDefined || propertyAnnotationAsts.nonEmpty || methodAnnotationAsts.nonEmpty) {
533535
val blockNode_ = blockNode(clazz)
534-
val childrenAsts = List(assignmentAst, annotationAst)
536+
val childrenAsts = List(assignmentAst, classAnnotationAst) ++ propertyAnnotationAsts ++ methodAnnotationAsts
535537
setArgumentIndices(childrenAsts)
536538
Ast(blockNode_).withChildren(childrenAsts)
537539
} else assignmentAst
@@ -555,7 +557,7 @@ trait AstForTypesCreator(implicit withSchemaValidation: ValidationMode) { this:
555557

556558
val receiverNode = identifierNode(classNodeInfo, "__decorate")
557559
scope.addVariableReference(receiverNode.name, receiverNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
558-
val thisNode = identifierNode(classNodeInfo, "this").dynamicTypeHintFullName(typeHintForThisExpression())
560+
val thisNode = identifierNode(classNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
559561
scope.addVariableReference(thisNode.name, thisNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
560562

561563
val decorateCallNode =
@@ -590,6 +592,311 @@ trait AstForTypesCreator(implicit withSchemaValidation: ValidationMode) { this:
590592
} else Ast()
591593
}
592594

595+
private def createMetadataCallTypeAst(tsMethodNodeInfo: BabelNodeInfo): Ast = {
596+
val metadataCallRecNode = identifierNode(tsMethodNodeInfo, "__metadata")
597+
scope.addVariableReference(
598+
metadataCallRecNode.name,
599+
metadataCallRecNode,
600+
Defines.Any,
601+
EvaluationStrategies.BY_REFERENCE
602+
)
603+
val metadataCallRecThisNode =
604+
identifierNode(tsMethodNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
605+
scope.addVariableReference(
606+
metadataCallRecThisNode.name,
607+
metadataCallRecThisNode,
608+
Defines.Any,
609+
EvaluationStrategies.BY_REFERENCE
610+
)
611+
612+
val designTypeLiteralNode = literalNode(tsMethodNodeInfo, "'design:type'", Defines.String)
613+
val functionLiteralNode = literalNode(tsMethodNodeInfo, "Function", Defines.Any)
614+
val metadataCallType =
615+
callNode(
616+
tsMethodNodeInfo,
617+
s"""__metadata("design:type", Function)""",
618+
"__metadata",
619+
DispatchTypes.DYNAMIC_DISPATCH
620+
)
621+
val metadataTypeArgAsts = Seq(Ast(designTypeLiteralNode), Ast(functionLiteralNode))
622+
callAst(
623+
metadataCallType,
624+
metadataTypeArgAsts,
625+
receiver = Option(Ast(metadataCallRecNode)),
626+
base = Option(Ast(metadataCallRecThisNode))
627+
)
628+
}
629+
630+
private def createMetadataCallParamTypesAst(tsMethodNodeInfo: BabelNodeInfo, numParams: Int): Ast = {
631+
val metadataCallRecNode = identifierNode(tsMethodNodeInfo, "__metadata")
632+
scope.addVariableReference(
633+
metadataCallRecNode.name,
634+
metadataCallRecNode,
635+
Defines.Any,
636+
EvaluationStrategies.BY_REFERENCE
637+
)
638+
val metadataCallThisNode =
639+
identifierNode(tsMethodNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
640+
scope.addVariableReference(
641+
metadataCallThisNode.name,
642+
metadataCallThisNode,
643+
Defines.Any,
644+
EvaluationStrategies.BY_REFERENCE
645+
)
646+
647+
val designParamTypeLiteralNode = literalNode(tsMethodNodeInfo, "'design:paramtypes'", Defines.String)
648+
649+
val objectLiteralNode = literalNode(tsMethodNodeInfo, "Object", Defines.Object)
650+
651+
val blockNode_ = blockNode(tsMethodNodeInfo)
652+
scope.pushNewBlockScope(blockNode_)
653+
localAstParentStack.push(blockNode_)
654+
655+
val tmpName = generateUnusedVariableName(usedVariableNames, "_tmp")
656+
val localTmpNode = localNode(tsMethodNodeInfo, tmpName, tmpName, Defines.Any).order(0)
657+
val tmpArrayNode = identifierNode(tsMethodNodeInfo, tmpName)
658+
diffGraph.addEdge(localAstParentStack.head, localTmpNode, EdgeTypes.AST)
659+
scope.addVariableReference(tmpName, tmpArrayNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
660+
661+
val arrayCallNode =
662+
callNode(
663+
tsMethodNodeInfo,
664+
s"${EcmaBuiltins.arrayFactory}()",
665+
EcmaBuiltins.arrayFactory,
666+
DispatchTypes.STATIC_DISPATCH
667+
)
668+
669+
val lineNumber = tsMethodNodeInfo.lineNumber
670+
val columnNumber = tsMethodNodeInfo.columnNumber
671+
val assignmentCode = s"${localTmpNode.code} = ${arrayCallNode.code}"
672+
val assignmentTmpArrayCallNode =
673+
createAssignmentCallAst(tmpArrayNode, arrayCallNode, assignmentCode, lineNumber, columnNumber)
674+
675+
val elementAsts = (0 until numParams).toList.map { _ =>
676+
val objectLiteralNode = literalNode(tsMethodNodeInfo, "Object", Defines.Object)
677+
val pushCallNode = callNode(tsMethodNodeInfo, s"$tmpName.push(Object)", "", DispatchTypes.DYNAMIC_DISPATCH)
678+
679+
val baseNode = identifierNode(tsMethodNodeInfo, tmpName)
680+
val memberNode = fieldIdentifierNode(tsMethodNodeInfo, "push", "push")
681+
val receiverNode =
682+
createFieldAccessCallAst(baseNode, memberNode, tsMethodNodeInfo.lineNumber, tsMethodNodeInfo.columnNumber)
683+
val thisPushNode = identifierNode(tsMethodNodeInfo, tmpName)
684+
callAst(
685+
pushCallNode,
686+
List(Ast(objectLiteralNode)),
687+
receiver = Option(receiverNode),
688+
base = Option(Ast(thisPushNode))
689+
)
690+
}
691+
692+
val tmpArrayReturnNode = identifierNode(tsMethodNodeInfo, tmpName)
693+
694+
scope.popScope()
695+
localAstParentStack.pop()
696+
697+
val blockChildrenAsts = assignmentTmpArrayCallNode +: elementAsts :+ Ast(tmpArrayReturnNode)
698+
setArgumentIndices(blockChildrenAsts)
699+
val blockAst_ = blockAst(blockNode_, blockChildrenAsts)
700+
701+
val metadataParamTypeCall =
702+
callNode(
703+
tsMethodNodeInfo,
704+
s"""__metadata("design:paramtypes", [${List.fill(numParams)("Object").mkString(",")}])""",
705+
"__metadata",
706+
DispatchTypes.DYNAMIC_DISPATCH
707+
)
708+
val metadataTypeArgAsts = Seq(Ast(designParamTypeLiteralNode), blockAst_)
709+
callAst(
710+
metadataParamTypeCall,
711+
metadataTypeArgAsts,
712+
receiver = Option(Ast(metadataCallRecNode)),
713+
base = Option(Ast(metadataCallThisNode))
714+
)
715+
}
716+
717+
private def createMetadataCallReturnTypeAst(tsMethodNodeInfo: BabelNodeInfo, tpe: String): Ast = {
718+
val metadataCallRecNode = identifierNode(tsMethodNodeInfo, "__metadata")
719+
scope.addVariableReference(
720+
metadataCallRecNode.name,
721+
metadataCallRecNode,
722+
Defines.Any,
723+
EvaluationStrategies.BY_REFERENCE
724+
)
725+
val metadataCallTypeRecThisNode =
726+
identifierNode(tsMethodNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
727+
scope.addVariableReference(
728+
metadataCallTypeRecThisNode.name,
729+
metadataCallTypeRecThisNode,
730+
Defines.Any,
731+
EvaluationStrategies.BY_REFERENCE
732+
)
733+
734+
val designTypeLiteralNode = literalNode(tsMethodNodeInfo, "'design:returntype'", Defines.String)
735+
val tpeLiteralNode = literalNode(tsMethodNodeInfo, tpe, Defines.Any)
736+
val metadataCallType =
737+
callNode(tsMethodNodeInfo, s"""__metadata("design:type", $tpe)""", "__metadata", DispatchTypes.DYNAMIC_DISPATCH)
738+
val metadataTypeArgAsts = Seq(Ast(designTypeLiteralNode), Ast(tpeLiteralNode))
739+
callAst(
740+
metadataCallType,
741+
metadataTypeArgAsts,
742+
receiver = Option(Ast(metadataCallRecNode)),
743+
base = Option(Ast(metadataCallTypeRecThisNode))
744+
)
745+
}
746+
747+
private def createPropertyAnnotationAsts(classNodeInfo: BabelNodeInfo, classIdNode: NewIdentifier): Seq[Ast] = {
748+
val tsProperties = classMembers(classNodeInfo).flatMap { member =>
749+
val memberNodeInfo = createBabelNodeInfo(member)
750+
if (
751+
memberNodeInfo.node == ClassProperty ||
752+
memberNodeInfo.node == ClassPrivateProperty
753+
) {
754+
Some(memberNodeInfo)
755+
} else {
756+
None
757+
}
758+
}
759+
760+
tsProperties.map { tsPropertyNodeInfo =>
761+
val decoratorAsts =
762+
decoratorExpressionElements(tsPropertyNodeInfo).map(e => astForNodeWithFunctionReference(e.json))
763+
if (decoratorAsts.nonEmpty) {
764+
val name = tsPropertyNodeInfo.node match {
765+
case ClassProperty => code(tsPropertyNodeInfo.json("key"))
766+
case ClassPrivateProperty => code(tsPropertyNodeInfo.json("key")("id"))
767+
case _ => tsPropertyNodeInfo.code
768+
}
769+
val propertyTpe = typeFor(tsPropertyNodeInfo)
770+
771+
val receiverNode = identifierNode(classNodeInfo, "__decorate")
772+
scope.addVariableReference(receiverNode.name, receiverNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
773+
val thisNode = identifierNode(classNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
774+
scope.addVariableReference(thisNode.name, thisNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
775+
776+
val classPrototypeAccessAst = createFieldAccessCallAst(
777+
identifierNode(classNodeInfo, classIdNode.name),
778+
fieldIdentifierNode(classNodeInfo, "prototype", "prototype"),
779+
classNodeInfo.lineNumber,
780+
classNodeInfo.columnNumber
781+
)
782+
val propertyNameNode = literalNode(tsPropertyNodeInfo, s"'$name'", Defines.String)
783+
val voidCallNode_ = voidCallNode(line(tsPropertyNodeInfo), column(tsPropertyNodeInfo))
784+
785+
val arg1Code = decoratorAsts.flatMap(_.root.map(codeOf)).mkString(",")
786+
val decorateCallCode =
787+
s"__decorate([$arg1Code], ${codeOf(classPrototypeAccessAst.nodes.head)}, ${propertyNameNode.code}, ${voidCallNode_.code})"
788+
789+
val decorateCallNode = callNode(classNodeInfo, decorateCallCode, "__decorate", DispatchTypes.DYNAMIC_DISPATCH)
790+
val subAst = astForDecorateArray(classNodeInfo, decoratorAsts)
791+
callAst(
792+
decorateCallNode,
793+
subAst +: Seq(classPrototypeAccessAst, Ast(propertyNameNode), Ast(voidCallNode_)),
794+
receiver = Option(Ast(receiverNode)),
795+
base = Option(Ast(thisNode))
796+
)
797+
} else {
798+
Ast()
799+
}
800+
}
801+
}
802+
803+
private def createMethodAnnotationAsts(classNodeInfo: BabelNodeInfo, classIdNode: NewIdentifier): Seq[Ast] = {
804+
val tsMethods = classMembers(classNodeInfo).flatMap { member =>
805+
val memberNodeInfo = createBabelNodeInfo(member)
806+
if (memberNodeInfo.node == ClassMethod) {
807+
Some(memberNodeInfo)
808+
} else {
809+
None
810+
}
811+
}
812+
813+
tsMethods.map { tsMethodNodeInfo =>
814+
val decoratorAsts =
815+
decoratorExpressionElements(tsMethodNodeInfo).map(e => astForNodeWithFunctionReference(e.json))
816+
val (name, fullName) = calcMethodNameAndFullName(tsMethodNodeInfo)
817+
val methodTpe = typeFor(tsMethodNodeInfo)
818+
val paramNodeInfos = if (hasKey(tsMethodNodeInfo.json, "parameters")) {
819+
tsMethodNodeInfo.json("parameters").arr.toSeq
820+
} else {
821+
tsMethodNodeInfo.json("params").arr.toSeq
822+
}
823+
val paramDecoratorAsts = paramNodeInfos.zipWithIndex.flatMap { case (value, idx) =>
824+
val paramAsts =
825+
decoratorExpressionElements(createBabelNodeInfo(value)).map(e => astForNodeWithFunctionReference(e.json))
826+
827+
if (paramAsts.isEmpty) {
828+
Seq.empty
829+
} else {
830+
val paramNodeInfo = createBabelNodeInfo(value)
831+
val receiverNode = identifierNode(paramNodeInfo, "__param")
832+
scope.addVariableReference(receiverNode.name, receiverNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
833+
val thisNode =
834+
identifierNode(paramNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
835+
scope.addVariableReference(thisNode.name, thisNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
836+
837+
paramAsts.map { p =>
838+
val callNode_ = callNode(
839+
paramNodeInfo,
840+
s"__param($idx, ${codeOf(p.nodes.head)})",
841+
"__param",
842+
DispatchTypes.DYNAMIC_DISPATCH
843+
)
844+
val idxNode = literalNode(paramNodeInfo, s"$idx", Defines.Number)
845+
val argAsts = Seq(Ast(idxNode), p)
846+
val paramDecorateCallAst =
847+
callAst(callNode_, argAsts, receiver = Option(Ast(receiverNode)), base = Option(Ast(thisNode)))
848+
paramDecorateCallAst
849+
}
850+
}
851+
}
852+
853+
if (decoratorAsts.nonEmpty || paramDecoratorAsts.nonEmpty) {
854+
val receiverNode = identifierNode(classNodeInfo, "__decorate")
855+
scope.addVariableReference(receiverNode.name, receiverNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
856+
val thisNode = identifierNode(classNodeInfo, "this").dynamicTypeHintFullName(rootTypeDecl.map(_.fullName).toSeq)
857+
scope.addVariableReference(thisNode.name, thisNode, Defines.Any, EvaluationStrategies.BY_REFERENCE)
858+
859+
val classPrototypeAccessAst = createFieldAccessCallAst(
860+
identifierNode(classNodeInfo, classIdNode.name),
861+
fieldIdentifierNode(classNodeInfo, "prototype", "prototype"),
862+
classNodeInfo.lineNumber,
863+
classNodeInfo.columnNumber
864+
)
865+
val functionNameNode = literalNode(tsMethodNodeInfo, s"'$name'", Defines.String)
866+
val nullNode = literalNode(tsMethodNodeInfo, "null", Defines.Null)
867+
val metadataCallTypeAst = createMetadataCallTypeAst(tsMethodNodeInfo)
868+
val metadataCallParamTypesAst = createMetadataCallParamTypesAst(tsMethodNodeInfo, paramNodeInfos.size)
869+
val metadataCallReturnTypeAst = createMetadataCallReturnTypeAst(tsMethodNodeInfo, methodTpe)
870+
871+
val arg1Code = decoratorAsts.flatMap(_.root.map(codeOf)).mkString(",")
872+
val arg2Code = paramDecoratorAsts.flatMap(_.root.map(codeOf)).mkString(",")
873+
val arg3Code = codeOf(metadataCallTypeAst.root.get)
874+
val arg4Code = codeOf(metadataCallParamTypesAst.root.get)
875+
val arg5Code = codeOf(metadataCallReturnTypeAst.root.get)
876+
val decorateCallCode =
877+
s"__decorate([$arg1Code, $arg2Code, $arg3Code, $arg4Code, $arg5Code], ${codeOf(classPrototypeAccessAst.nodes.head)}, ${functionNameNode.code}, null)"
878+
879+
val decorateCallNode = callNode(classNodeInfo, decorateCallCode, "__decorate", DispatchTypes.DYNAMIC_DISPATCH)
880+
val subAst = astForDecorateArray(
881+
classNodeInfo,
882+
decoratorAsts ++ paramDecoratorAsts ++ Seq(
883+
metadataCallTypeAst,
884+
metadataCallParamTypesAst,
885+
metadataCallReturnTypeAst
886+
)
887+
)
888+
callAst(
889+
decorateCallNode,
890+
subAst +: Seq(classPrototypeAccessAst, Ast(functionNameNode), Ast(nullNode)),
891+
receiver = Option(Ast(receiverNode)),
892+
base = Option(Ast(thisNode))
893+
)
894+
} else {
895+
Ast()
896+
}
897+
}
898+
}
899+
593900
private def astForDecorateArray(classNodeInfo: BabelNodeInfo, decoratorAsts: List[Ast]): Ast = {
594901
val blockNode_ = blockNode(classNodeInfo)
595902
scope.pushNewBlockScope(blockNode_)

joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsClassesAstCreationPassTests.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ class TsClassesAstCreationPassTests extends AstJsSrc2CpgSuite(".ts") {
367367
val List(credentialsParam) = cpg.parameter.nameExact("credentials").l
368368
credentialsParam.typeFullName shouldBe "Test0.ts::program:Test:run:<anon-class>0"
369369
// should not produce dangling nodes that are meant to be inside procedures
370+
371+
val x = cpg.all.collectAll[CfgNode].whereNot(_.astParent).l
370372
cpg.all.collectAll[CfgNode].whereNot(_.astParent).size shouldBe 0
371373
cpg.identifier.count(_.refsTo.size > 1) shouldBe 0
372374
cpg.identifier.whereNot(_.refsTo).size shouldBe 0

0 commit comments

Comments
 (0)