All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.jssrc2cpg.astcreation.AstForExpressionsCreator.scala Maven / Gradle / Ivy

The newest version!
package io.joern.jssrc2cpg.astcreation

import io.joern.jssrc2cpg.parser.BabelAst.*
import io.joern.jssrc2cpg.parser.BabelNodeInfo
import io.joern.jssrc2cpg.passes.EcmaBuiltins
import io.joern.x2cpg.frontendspecific.jssrc2cpg.{Defines, GlobalBuiltins}
import io.joern.x2cpg.{Ast, ValidationMode}
import io.joern.x2cpg.datastructures.Stack.*
import io.shiftleft.codepropertygraph.generated.nodes.NewNode
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EdgeTypes, Operators}

import scala.util.Try

trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>

  protected def astForExpressionStatement(exprStmt: BabelNodeInfo): Ast =
    astForNodeWithFunctionReference(exprStmt.json("expression"))

  private def createBuiltinStaticCall(callExpr: BabelNodeInfo, callee: BabelNodeInfo, fullName: String): Ast = {
    val callName = callee.node match {
      case MemberExpression => code(callee.json("property"))
      case _                => callee.code
    }
    val callNode = createStaticCallNode(callExpr.code, callName, fullName, callee.lineNumber, callee.columnNumber)
    val argAsts  = astForNodes(callExpr.json("arguments").arr.toList)
    callAst(callNode, argAsts)
  }

  private def handleCallNodeArgs(
    callExpr: BabelNodeInfo,
    receiverAst: Ast,
    baseNode: NewNode,
    callName: String
  ): Ast = {
    val args      = astForNodes(callExpr.json("arguments").arr.toList)
    val callNode_ = callNode(callExpr, callExpr.code, callName, DispatchTypes.DYNAMIC_DISPATCH)
    // If the callee is a function itself, e.g. closure, then resolve this locally, if possible
    callExpr.json.obj
      .get("callee")
      .map(createBabelNodeInfo)
      .flatMap {
        case callee if callee.node.isInstanceOf[FunctionLike] => functionNodeToNameAndFullName.get(callee)
        case _                                                => None
      }
      .foreach { case (name, fullName) => callNode_.name(name).methodFullName(fullName) }
    callAst(callNode_, args, receiver = Option(receiverAst), base = Option(Ast(baseNode)))
  }

  protected def astForCallExpression(callExpr: BabelNodeInfo): Ast = {
    val callee     = createBabelNodeInfo(callExpr.json("callee"))
    val calleeCode = callee.code
    if (GlobalBuiltins.builtins.contains(calleeCode)) {
      createBuiltinStaticCall(callExpr, callee, calleeCode)
    } else {
      val (receiverAst, baseNode, callName) = callee.node match {
        case MemberExpression =>
          val base   = createBabelNodeInfo(callee.json("object"))
          val member = createBabelNodeInfo(callee.json("property"))
          base.node match {
            case ThisExpression =>
              val receiverAst = astForNodeWithFunctionReference(callee.json)
              val baseNode    = identifierNode(base, base.code).dynamicTypeHintFullName(typeHintForThisExpression())
              scope.addVariableReference(base.code, baseNode)
              (receiverAst, baseNode, member.code)
            case Identifier =>
              val receiverAst = astForNodeWithFunctionReference(callee.json)
              val baseNode    = identifierNode(base, base.code)
              scope.addVariableReference(base.code, baseNode)
              (receiverAst, baseNode, member.code)
            case _ =>
              val tmpVarName  = generateUnusedVariableName(usedVariableNames, "_tmp")
              val baseTmpNode = identifierNode(base, tmpVarName)
              scope.addVariableReference(tmpVarName, baseTmpNode)
              val baseAst = astForNodeWithFunctionReference(base.json)
              val code    = s"(${codeOf(baseTmpNode)} = ${base.code})"
              val tmpAssignmentAst =
                createAssignmentCallAst(Ast(baseTmpNode), baseAst, code, base.lineNumber, base.columnNumber)
              val memberNode = createFieldIdentifierNode(member.code, member.lineNumber, member.columnNumber)
              val fieldAccessAst =
                createFieldAccessCallAst(tmpAssignmentAst, memberNode, callee.lineNumber, callee.columnNumber)
              val thisTmpNode = identifierNode(callee, tmpVarName)
              scope.addVariableReference(tmpVarName, thisTmpNode)

              (fieldAccessAst, thisTmpNode, member.code)
          }
        case _ =>
          val receiverAst = astForNodeWithFunctionReference(callee.json)
          val thisNode    = identifierNode(callee, "this").dynamicTypeHintFullName(typeHintForThisExpression())
          scope.addVariableReference(thisNode.name, thisNode)
          (receiverAst, thisNode, calleeCode)
      }
      handleCallNodeArgs(callExpr, receiverAst, baseNode, callName)
    }
  }

  protected def astForThisExpression(thisExpr: BabelNodeInfo): Ast = {
    val dynamicTypeOption = typeHintForThisExpression(Option(thisExpr)).headOption
    val thisNode          = identifierNode(thisExpr, thisExpr.code, dynamicTypeOption.toList)
    scope.addVariableReference(thisExpr.code, thisNode)
    Ast(thisNode)
  }

  protected def astForNewExpression(newExpr: BabelNodeInfo): Ast = {
    val callee    = newExpr.json("callee")
    val blockNode = createBlockNode(newExpr)

    scope.pushNewBlockScope(blockNode)
    localAstParentStack.push(blockNode)

    val tmpAllocName      = generateUnusedVariableName(usedVariableNames, "_tmp")
    val localTmpAllocNode = localNode(newExpr, tmpAllocName, tmpAllocName, Defines.Any).order(0)
    val tmpAllocNode1     = identifierNode(newExpr, tmpAllocName)
    diffGraph.addEdge(localAstParentStack.head, localTmpAllocNode, EdgeTypes.AST)
    scope.addVariableReference(tmpAllocName, tmpAllocNode1)

    val allocCallNode = callNode(newExpr, ".alloc", Operators.alloc, DispatchTypes.STATIC_DISPATCH)
    val assignmentTmpAllocCallNode =
      createAssignmentCallAst(
        tmpAllocNode1,
        allocCallNode,
        s"$tmpAllocName = ${allocCallNode.code}",
        newExpr.lineNumber,
        newExpr.columnNumber
      )

    val tmpAllocNode2      = identifierNode(newExpr, tmpAllocName)
    val receiverNode       = astForNodeWithFunctionReference(callee)
    val callAst            = handleCallNodeArgs(newExpr, receiverNode, tmpAllocNode2, Defines.OperatorsNew)
    val tmpAllocReturnNode = Ast(identifierNode(newExpr, tmpAllocName))

    scope.popScope()
    localAstParentStack.pop()

    val blockChildren = List(assignmentTmpAllocCallNode, callAst, tmpAllocReturnNode)
    setArgumentIndices(blockChildren)
    blockAst(blockNode, blockChildren)
  }

  protected def astForMetaProperty(metaProperty: BabelNodeInfo): Ast = {
    val metaAst        = astForIdentifier(createBabelNodeInfo(metaProperty.json("meta")))
    val memberNodeInfo = createBabelNodeInfo(metaProperty.json("property"))
    val memberAst = Ast(
      createFieldIdentifierNode(memberNodeInfo.code, memberNodeInfo.lineNumber, memberNodeInfo.columnNumber)
    )
    createFieldAccessCallAst(metaAst, memberAst.nodes.head, metaProperty.lineNumber, metaProperty.columnNumber)
  }

  protected def astForMemberExpression(memberExpr: BabelNodeInfo): Ast = {
    val baseAst          = astForNodeWithFunctionReference(memberExpr.json("object"))
    val memberIsComputed = memberExpr.json("computed").bool
    val memberNodeInfo   = createBabelNodeInfo(memberExpr.json("property"))
    if (memberIsComputed) {
      val memberAst = astForNode(memberNodeInfo.json)
      createIndexAccessCallAst(baseAst, memberAst, memberExpr.lineNumber, memberExpr.columnNumber)
    } else {
      val memberAst = Ast(
        createFieldIdentifierNode(memberNodeInfo.code, memberNodeInfo.lineNumber, memberNodeInfo.columnNumber)
      )
      createFieldAccessCallAst(baseAst, memberAst.nodes.head, memberExpr.lineNumber, memberExpr.columnNumber)
    }
  }

  protected def astForAssignmentExpression(assignment: BabelNodeInfo): Ast = {
    val op = if (hasKey(assignment.json, "operator")) {
      assignment.json("operator").str match {
        case "="    => Operators.assignment
        case "+="   => Operators.assignmentPlus
        case "-="   => Operators.assignmentMinus
        case "*="   => Operators.assignmentMultiplication
        case "/="   => Operators.assignmentDivision
        case "%="   => Operators.assignmentModulo
        case "**="  => Operators.assignmentExponentiation
        case "&="   => Operators.assignmentAnd
        case "&&="  => Operators.assignmentAnd
        case "|="   => Operators.assignmentOr
        case "||="  => Operators.assignmentOr
        case "^="   => Operators.assignmentXor
        case "<<="  => Operators.assignmentShiftLeft
        case ">>="  => Operators.assignmentArithmeticShiftRight
        case ">>>=" => Operators.assignmentLogicalShiftRight
        case "??="  => Operators.notNullAssert
        case other =>
          logger.warn(s"Unknown assignment operator: '$other'")
          Operators.assignment
      }
    } else Operators.assignment

    val nodeInfo = createBabelNodeInfo(assignment.json("left"))
    nodeInfo.node match {
      case ObjectPattern | ArrayPattern =>
        val rhsAst = astForNodeWithFunctionReference(assignment.json("right"))
        astForDeconstruction(nodeInfo, rhsAst, assignment.code)
      case _ =>
        val lhsAst = astForNode(assignment.json("left"))
        val rhsAst = astForNodeWithFunctionReference(assignment.json("right"))
        val callNode_ =
          callNode(assignment, assignment.code, op, DispatchTypes.STATIC_DISPATCH)
        val argAsts = List(lhsAst, rhsAst)
        callAst(callNode_, argAsts)
    }
  }

  protected def astForConditionalExpression(ternary: BabelNodeInfo): Ast = {
    val testAst       = astForNodeWithFunctionReference(ternary.json("test"))
    val consequentAst = astForNodeWithFunctionReference(ternary.json("consequent"))
    val alternateAst  = astForNodeWithFunctionReference(ternary.json("alternate"))
    createTernaryCallAst(testAst, consequentAst, alternateAst, ternary.lineNumber, ternary.columnNumber)
  }

  protected def astForLogicalExpression(logicalExpr: BabelNodeInfo): Ast =
    astForBinaryExpression(logicalExpr)

  protected def astForTSNonNullExpression(nonNullExpr: BabelNodeInfo): Ast = {
    val op        = Operators.notNullAssert
    val callNode_ = callNode(nonNullExpr, nonNullExpr.code, op, DispatchTypes.STATIC_DISPATCH)
    val argAsts   = List(astForNodeWithFunctionReference(nonNullExpr.json("expression")))
    callAst(callNode_, argAsts)
  }

  protected def astForCastExpression(castExpr: BabelNodeInfo): Ast = {
    val op            = Operators.cast
    val lhsNode       = castExpr.json("typeAnnotation")
    val rhsAst        = astForNodeWithFunctionReference(castExpr.json("expression"))
    val possibleTypes = Seq(typeFor(castExpr))
    val lhsAst        = Ast(literalNode(castExpr, code(lhsNode), None).possibleTypes(possibleTypes))
    val node    = callNode(castExpr, castExpr.code, op, DispatchTypes.STATIC_DISPATCH).possibleTypes(possibleTypes)
    val argAsts = List(lhsAst, rhsAst)
    callAst(node, argAsts)
  }

  protected def astForBinaryExpression(binExpr: BabelNodeInfo): Ast = {
    val op = binExpr.json("operator").str match {
      case "+"          => Operators.addition
      case "-"          => Operators.subtraction
      case "/"          => Operators.division
      case "%"          => Operators.modulo
      case "*"          => Operators.multiplication
      case "**"         => Operators.exponentiation
      case "&"          => Operators.and
      case ">>"         => Operators.arithmeticShiftRight
      case ">>>"        => Operators.arithmeticShiftRight
      case "<<"         => Operators.shiftLeft
      case "^"          => Operators.xor
      case "=="         => Operators.equals
      case "==="        => Operators.equals
      case "!="         => Operators.notEquals
      case "!=="        => Operators.notEquals
      case "in"         => Operators.in
      case ">"          => Operators.greaterThan
      case "<"          => Operators.lessThan
      case ">="         => Operators.greaterEqualsThan
      case "<="         => Operators.lessEqualsThan
      case "instanceof" => Operators.instanceOf
      case "||"         => Operators.logicalOr
      case "|"          => Operators.or
      case "&&"         => Operators.logicalAnd
      // special case (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator)
      case "??"   => Operators.logicalOr
      case "case" => ".case"
      case other =>
        logger.warn(s"Unknown binary operator: '$other'")
        Operators.assignment
    }

    val lhsAst = astForNodeWithFunctionReference(binExpr.json("left"))
    val rhsAst = astForNodeWithFunctionReference(binExpr.json("right"))

    val node =
      callNode(binExpr, binExpr.code, op, DispatchTypes.STATIC_DISPATCH)
    val argAsts = List(lhsAst, rhsAst)
    callAst(node, argAsts)
  }

  protected def astForUpdateExpression(updateExpr: BabelNodeInfo): Ast = {
    val isPrefix = updateExpr.json("prefix").bool
    val op = updateExpr.json("operator").str match {
      case "++" if isPrefix => Operators.preIncrement
      case "++"             => Operators.postIncrement
      case "--" if isPrefix => Operators.preIncrement
      case "--"             => Operators.postIncrement
      case other =>
        logger.warn(s"Unknown update operator: '$other'")
        Operators.assignment
    }

    val argumentAst = astForNodeWithFunctionReference(updateExpr.json("argument"))

    val node    = callNode(updateExpr, updateExpr.code, op, DispatchTypes.STATIC_DISPATCH)
    val argAsts = List(argumentAst)
    callAst(node, argAsts)
  }

  protected def astForUnaryExpression(unaryExpr: BabelNodeInfo): Ast = {
    val op = unaryExpr.json("operator").str match {
      case "void"   => ".void"
      case "throw"  => ".throw"
      case "delete" => Operators.delete
      case "!"      => Operators.logicalNot
      case "+"      => Operators.plus
      case "-"      => Operators.minus
      case "~"      => ".bitNot"
      case "typeof" => Operators.instanceOf
      case other =>
        logger.warn(s"Unknown update operator: '$other'")
        Operators.assignment
    }

    val argumentAst = astForNodeWithFunctionReference(unaryExpr.json("argument"))

    val node    = callNode(unaryExpr, unaryExpr.code, op, DispatchTypes.STATIC_DISPATCH)
    val argAsts = List(argumentAst)
    callAst(node, argAsts)
  }

  protected def astForSequenceExpression(seq: BabelNodeInfo): Ast = {
    val blockNode = createBlockNode(seq)
    scope.pushNewBlockScope(blockNode)
    localAstParentStack.push(blockNode)
    val sequenceExpressionAsts = createBlockStatementAsts(seq.json("expressions"))
    setArgumentIndices(sequenceExpressionAsts)
    localAstParentStack.pop()
    scope.popScope()
    blockAst(blockNode, sequenceExpressionAsts)
  }

  protected def astForAwaitExpression(awaitExpr: BabelNodeInfo): Ast = {
    val node    = callNode(awaitExpr, awaitExpr.code, ".await", DispatchTypes.STATIC_DISPATCH)
    val argAsts = List(astForNodeWithFunctionReference(awaitExpr.json("argument")))
    callAst(node, argAsts)
  }

  protected def astForArrayExpression(arrExpr: BabelNodeInfo): Ast = {
    val MAX_INITIALIZERS = 1000
    val elementsJsons    = Try(arrExpr.json("elements").arr).toOption.toList.flatten
    val elements         = elementsJsons.slice(0, MAX_INITIALIZERS)
    if (elements.isEmpty) {
      Ast(
        callNode(arrExpr, s"${EcmaBuiltins.arrayFactory}()", EcmaBuiltins.arrayFactory, DispatchTypes.STATIC_DISPATCH)
      )
    } else {
      val blockNode = createBlockNode(arrExpr)
      scope.pushNewBlockScope(blockNode)
      localAstParentStack.push(blockNode)

      val tmpName      = generateUnusedVariableName(usedVariableNames, "_tmp")
      val localTmpNode = localNode(arrExpr, tmpName, tmpName, Defines.Any).order(0)
      val tmpArrayNode = identifierNode(arrExpr, tmpName)
      diffGraph.addEdge(localAstParentStack.head, localTmpNode, EdgeTypes.AST)
      scope.addVariableReference(tmpName, tmpArrayNode)

      val arrayCallNode =
        callNode(arrExpr, s"${EcmaBuiltins.arrayFactory}()", EcmaBuiltins.arrayFactory, DispatchTypes.STATIC_DISPATCH)

      val lineNumber     = arrExpr.lineNumber
      val columnNumber   = arrExpr.columnNumber
      val assignmentCode = s"${localTmpNode.code} = ${arrayCallNode.code}"
      val assignmentTmpArrayCallNode =
        createAssignmentCallAst(tmpArrayNode, arrayCallNode, assignmentCode, lineNumber, columnNumber)

      val elementAsts = elements.flatMap {
        case element if !element.isNull =>
          val elementNodeInfo     = createBabelNodeInfo(element)
          val elementLineNumber   = elementNodeInfo.lineNumber
          val elementColumnNumber = elementNodeInfo.columnNumber
          val elementCode         = elementNodeInfo.code
          val elementNode = elementNodeInfo.node match {
            case RestElement =>
              val arg1Ast = Ast(identifierNode(arrExpr, tmpName))
              astForSpreadOrRestElement(elementNodeInfo, Option(arg1Ast))
            case _ =>
              astForNodeWithFunctionReference(element)
          }

          val pushCallNode =
            callNode(elementNodeInfo, s"$tmpName.push($elementCode)", "", DispatchTypes.DYNAMIC_DISPATCH)

          val baseNode     = identifierNode(elementNodeInfo, tmpName)
          val memberNode   = createFieldIdentifierNode("push", elementLineNumber, elementColumnNumber)
          val receiverNode = createFieldAccessCallAst(baseNode, memberNode, elementLineNumber, elementColumnNumber)
          val thisPushNode = identifierNode(elementNodeInfo, tmpName)

          Option(
            callAst(pushCallNode, List(elementNode), receiver = Option(receiverNode), base = Option(Ast(thisPushNode)))
          )
        case _ => None // skip
      }

      val tmpArrayReturnNode = identifierNode(arrExpr, tmpName)

      scope.popScope()
      localAstParentStack.pop()

      val blockChildrenAsts = if (elementsJsons.sizeIs > MAX_INITIALIZERS) {
        val placeholder = literalNode(arrExpr, "", Defines.Any)
        assignmentTmpArrayCallNode +: elementAsts :+ Ast(placeholder) :+ Ast(tmpArrayReturnNode)
      } else { assignmentTmpArrayCallNode +: elementAsts :+ Ast(tmpArrayReturnNode) }
      setArgumentIndices(blockChildrenAsts)
      blockAst(blockNode, blockChildrenAsts)
    }
  }

  def astForTemplateExpression(templateExpr: BabelNodeInfo): Ast = {
    val argumentAst      = astForNodeWithFunctionReference(templateExpr.json("quasi"))
    val callName         = code(templateExpr.json("tag"))
    val callCode         = s"$callName(${codeOf(argumentAst.nodes.head)})"
    val templateExprCall = callNode(templateExpr, callCode, callName, DispatchTypes.STATIC_DISPATCH)
    val argAsts          = List(argumentAst)
    callAst(templateExprCall, argAsts)
  }

  protected def astForObjectExpression(objExpr: BabelNodeInfo): Ast = {
    val blockNode = createBlockNode(objExpr)

    scope.pushNewBlockScope(blockNode)
    localAstParentStack.push(blockNode)

    val tmpName      = generateUnusedVariableName(usedVariableNames, "_tmp")
    val localTmpNode = localNode(objExpr, tmpName, tmpName, Defines.Any).order(0)
    diffGraph.addEdge(localAstParentStack.head, localTmpNode, EdgeTypes.AST)

    val propertiesAsts = objExpr.json("properties").arr.toList.map { property =>
      val nodeInfo = createBabelNodeInfo(property)
      nodeInfo.node match {
        case SpreadElement | RestElement =>
          val arg1Ast = Ast(identifierNode(nodeInfo, tmpName))
          astForSpreadOrRestElement(nodeInfo, Option(arg1Ast))
        case _ =>
          val (lhsNode, rhsAst) = nodeInfo.node match {
            case ObjectMethod =>
              val keyName =
                if (hasKey(nodeInfo.json("key"), "name")) nodeInfo.json("key")("name").str
                else code(nodeInfo.json("key"))
              val keyNode = createFieldIdentifierNode(keyName, nodeInfo.lineNumber, nodeInfo.columnNumber)
              (keyNode, astForFunctionDeclaration(nodeInfo, shouldCreateFunctionReference = true))
            case ObjectProperty =>
              val key = createBabelNodeInfo(nodeInfo.json("key"))
              val keyName = key.node match {
                case Identifier if nodeInfo.json("computed").bool =>
                  key.code
                case _ if nodeInfo.json("computed").bool =>
                  generateUnusedVariableName(usedVariableNames, "_computed_object_property")
                case _ => key.code
              }
              val keyNode = createFieldIdentifierNode(keyName, nodeInfo.lineNumber, nodeInfo.columnNumber)
              val ast     = astForNodeWithFunctionReference(nodeInfo.json("value"))
              (keyNode, ast)
            case _ =>
              // can't happen as per https://github.com/babel/babel/blob/main/packages/babel-types/src/ast-types/generated/index.ts#L573
              // just to make the compiler happy here.
              ???
          }

          val leftHandSideTmpNode = identifierNode(nodeInfo, tmpName)
          val leftHandSideFieldAccessAst =
            createFieldAccessCallAst(leftHandSideTmpNode, lhsNode, nodeInfo.lineNumber, nodeInfo.columnNumber)

          createAssignmentCallAst(
            leftHandSideFieldAccessAst,
            rhsAst,
            s"$tmpName.${lhsNode.canonicalName} = ${codeOf(rhsAst.nodes.head)}",
            nodeInfo.lineNumber,
            nodeInfo.columnNumber
          )
      }
    }

    val tmpNode = identifierNode(objExpr, tmpName)

    scope.popScope()
    localAstParentStack.pop()

    val childrenAsts = propertiesAsts :+ Ast(tmpNode)
    setArgumentIndices(childrenAsts)
    blockAst(blockNode, childrenAsts)
  }

  protected def astForTSSatisfiesExpression(satisfiesExpr: BabelNodeInfo): Ast = {
    // Ignores the type, i.e. `x satisfies T` is understood as `x`.
    astForNode(satisfiesExpr.json("expression"))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy