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

io.shiftleft.js2cpg.astcreation.AstNodeBuilder.scala Maven / Gradle / Ivy

package io.shiftleft.js2cpg.astcreation

import com.oracle.js.parser.ir._
import io.shiftleft.codepropertygraph.generated.nodes._
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EvaluationStrategies, Operators}
import io.shiftleft.js2cpg.datastructures.{LineAndColumn, OrderTracker}
import io.shiftleft.js2cpg.datastructures.scope.{MethodScope, Scope}
import io.shiftleft.js2cpg.passes.Defines
import io.shiftleft.js2cpg.parser.JsSource
import io.shiftleft.js2cpg.parser.JsSource.shortenCode
import overflowdb.BatchedUpdate.DiffGraphBuilder

class AstNodeBuilder(
  private val diffGraph: DiffGraphBuilder,
  private val astEdgeBuilder: AstEdgeBuilder,
  private val source: JsSource,
  private val scope: Scope
) {

  implicit def int2IntegerOpt(x: Option[Int]): Option[Integer] = x.map(java.lang.Integer.valueOf)
  implicit def int2Integer(x: Int): Integer                    = java.lang.Integer.valueOf(x)

  def codeOf(node: NewNode): String = node match {
    case node: AstNodeNew => node.code
    case _                => ""
  }

  def lineAndColumn(node: Node): LineAndColumn = {
    LineAndColumn(source.getLine(node), source.getColumn(node))
  }

  def createDependencyNode(name: String, groupId: String, version: String): NewDependency = {
    val dependency = NewDependency()
      .name(Option(name).getOrElse(""))
      .dependencyGroupId(Option(groupId).getOrElse(""))
      .version(Option(version).getOrElse(""))
    diffGraph.addNode(dependency)
    dependency
  }

  def groupIdFromImportNode(importNode: ImportNode): String = {
    importNode.getFrom match {
      case null => importNode.getModuleSpecifier.getString
      case from => from.getModuleSpecifier.getString
    }
  }

  def createParameterInNode(
    name: String,
    code: String,
    methodNode: NewMethod,
    lineAndColumnProvider: Node,
    orderTracker: OrderTracker
  ): NewMethodParameterIn = {
    val lineColumn = lineAndColumn(lineAndColumnProvider)
    val line       = lineColumn.line
    val column     = lineColumn.column
    val param = NewMethodParameterIn()
      .name(name)
      .code(shortenCode(code))
      .evaluationStrategy(EvaluationStrategies.BY_VALUE)
      .lineNumber(line)
      .columnNumber(column)
      .order(orderTracker.order)
      .typeFullName(Defines.Any)

    diffGraph.addNode(param)
    orderTracker.inc()
    astEdgeBuilder.addAstEdge(param, methodNode)
    scope.addVariable(name, param, MethodScope)
    param
  }

  def createImportNode(importNode: ImportNode): NewImport = {
    val lineColumn = lineAndColumn(importNode)
    val line       = lineColumn.line
    val column     = lineColumn.column

    val importedEntity = groupIdFromImportNode(importNode) match {
      case "" => None
      case x  => Some(x)
    }

    val node = NewImport()
      .importedEntity(importedEntity)
      .code(importNode.toString().stripSuffix(";"))
      .lineNumber(line)
      .columnNumber(column)

    diffGraph.addNode(node)
    node
  }

  private def sanitizeCode(node: Node): String = node match {
    case _: ReturnNode =>
      source.getCode(node).stripSuffix(";")
    case _: BreakNode =>
      source.getCode(node).stripSuffix(";")
    case _: ContinueNode =>
      source.getCode(node).stripSuffix(";")
    case _: ErrorNode =>
      // ErrorNode represents a runtime call; does not have a code representation
      ""
    case _ =>
      source.getCode(node)
  }

  def createUnknownNode(parserNode: Node): NewUnknown = {
    val code       = sanitizeCode(parserNode)
    val lineColumn = lineAndColumn(parserNode)
    val unknown = NewUnknown()
      .parserTypeName(parserNode.getClass.getSimpleName)
      .lineNumber(lineColumn.line)
      .columnNumber(lineColumn.column)
      .code(shortenCode(code))
      .typeFullName(Defines.Any)

    diffGraph.addNode(unknown)
    unknown
  }

  def createTypeDeclNode(
    name: String,
    fullName: String,
    astParentType: String,
    astParentFullName: String,
    inheritsFrom: Option[String]
  ): NewTypeDecl = {
    val typeDecl = NewTypeDecl()
      .name(name)
      .fullName(fullName)
      .astParentType(astParentType)
      .astParentFullName(astParentFullName)
      .isExternal(false)
      .inheritsFromTypeFullName(inheritsFrom.toList)
      .filename(source.filePath)
    diffGraph.addNode(typeDecl)
    typeDecl
  }

  def createIdentifierNode(
    name: String,
    lineAndColumnProvider: Node,
    dynamicTypeOption: Option[String]
  ): NewIdentifier = {
    val lineColumn = lineAndColumn(lineAndColumnProvider)
    val line       = lineColumn.line
    val column     = lineColumn.column

    val identifier = NewIdentifier()
      .name(name)
      .code(shortenCode(name))
      .lineNumber(line)
      .columnNumber(column)
      .typeFullName(Defines.Any)
      .dynamicTypeHintFullName(dynamicTypeOption.toList)
    diffGraph.addNode(identifier)
    identifier
  }

  def createFieldIdentifierNode(name: String, lineAndColumnProvider: Node): NewFieldIdentifier = {
    val lineColumn = lineAndColumn(lineAndColumnProvider)
    val line       = lineColumn.line
    val column     = lineColumn.column

    val fieldIdentifier = NewFieldIdentifier()
      .code(shortenCode(name))
      .canonicalName(name)
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(fieldIdentifier)
    fieldIdentifier
  }

  def createFieldAccessNode(baseId: NewNode, partId: NewNode, lineAndColumn: LineAndColumn): NewCall = {
    val call = createCallNode(
      codeOf(baseId) + "." + codeOf(partId),
      Operators.fieldAccess,
      DispatchTypes.STATIC_DISPATCH,
      lineAndColumn
    )

    astEdgeBuilder.addAstEdge(baseId, call, 1)
    astEdgeBuilder.addArgumentEdge(baseId, call, 1)

    astEdgeBuilder.addAstEdge(partId, call, 2)
    astEdgeBuilder.addArgumentEdge(partId, call, 2)

    call
  }

  def createStaticCallNode(
    code: String,
    methodName: String,
    fullName: String,
    lineAndColumn: LineAndColumn
  ): NewCall = {
    val line   = lineAndColumn.line
    val column = lineAndColumn.column
    val call = NewCall()
      .code(shortenCode(code))
      .name(methodName)
      .methodFullName(fullName)
      .dispatchType(DispatchTypes.STATIC_DISPATCH)
      .signature("")
      .lineNumber(line)
      .columnNumber(column)
      .typeFullName(Defines.Any)

    diffGraph.addNode(call)
    call
  }

  def createEqualsCallNode(lhsId: NewNode, rhsId: NewNode, lineAndColumn: LineAndColumn): NewCall = {
    val call = createCallNode(
      codeOf(lhsId) + " === " + codeOf(rhsId),
      Operators.equals,
      DispatchTypes.STATIC_DISPATCH,
      lineAndColumn
    )

    astEdgeBuilder.addAstEdge(lhsId, call, 1)
    astEdgeBuilder.addArgumentEdge(lhsId, call, 1)

    astEdgeBuilder.addAstEdge(rhsId, call, 2)
    astEdgeBuilder.addArgumentEdge(rhsId, call, 2)

    call
  }

  def createIndexAccessNode(baseId: NewNode, indexId: NewNode, lineAndColumn: LineAndColumn): NewCall = {
    val call = createCallNode(
      codeOf(baseId) + "[" + codeOf(indexId) + "]",
      Operators.indexAccess,
      DispatchTypes.STATIC_DISPATCH,
      lineAndColumn
    )

    astEdgeBuilder.addAstEdge(baseId, call, 1)
    astEdgeBuilder.addArgumentEdge(baseId, call, 1)

    astEdgeBuilder.addAstEdge(indexId, call, 2)
    astEdgeBuilder.addArgumentEdge(indexId, call, 2)
    call
  }

  def createAssignmentNode(
    destId: NewNode,
    sourceId: NewNode,
    lineAndColumn: LineAndColumn,
    withParenthesis: Boolean = false,
    customCode: String = ""
  ): NewCall = {
    val code = if (customCode.isEmpty) {
      if (withParenthesis) {
        s"(${codeOf(destId)} = ${codeOf(sourceId)})"
      } else {
        s"${codeOf(destId)} = ${codeOf(sourceId)}"
      }
    } else {
      customCode
    }

    val call =
      createCallNode(code, Operators.assignment, DispatchTypes.STATIC_DISPATCH, lineAndColumn)

    astEdgeBuilder.addAstEdge(destId, call, 1)
    astEdgeBuilder.addArgumentEdge(destId, call, 1)

    astEdgeBuilder.addAstEdge(sourceId, call, 2)
    astEdgeBuilder.addArgumentEdge(sourceId, call, 2)

    call
  }

  def createLiteralNode(code: String, lineAndColumn: LineAndColumn, dynamicTypeOption: Option[String]): NewLiteral = {
    val line   = lineAndColumn.line
    val column = lineAndColumn.column
    val literal = NewLiteral()
      .code(shortenCode(code))
      .typeFullName(Defines.Any)
      .lineNumber(line)
      .columnNumber(column)
      .dynamicTypeHintFullName(dynamicTypeOption.toList)
    diffGraph.addNode(literal)
    literal
  }

  // TODO
  // This method is a hack which does not handle complex property keys.
  // It only creates a FIELD_IDENTIFIER node in order to not break the backend
  // assumption of only having FIELD_IDENTIFIER as second argument to
  // .fieldAccess calls.
  def createPropertyKeyNode(propertyNode: PropertyNode): NewFieldIdentifier = {
    propertyNode.getKey match {
      case identNode: IdentNode =>
        createFieldIdentifierNode(identNode.getName, propertyNode.getKey)
      case literalNode: LiteralNode[_] =>
        createFieldIdentifierNode(literalNode.getValue.toString, propertyNode.getKey)
      case _ =>
        // TODO: handle other kinds of possible nodes (e.g., computed property name)
        createFieldIdentifierNode(source.getCode(propertyNode.getKey), propertyNode.getKey)
    }
  }

  def createTernaryNode(testId: NewNode, trueId: NewNode, falseId: NewNode, lineAndColumn: LineAndColumn): NewCall = {
    val code = codeOf(testId) + " ? " + codeOf(trueId) + " : " + codeOf(falseId)
    val callId =
      createCallNode(code, Operators.conditional, DispatchTypes.STATIC_DISPATCH, lineAndColumn)

    astEdgeBuilder.addAstEdge(testId, callId, 1)
    astEdgeBuilder.addArgumentEdge(testId, callId, 1)
    astEdgeBuilder.addAstEdge(trueId, callId, 2)
    astEdgeBuilder.addArgumentEdge(trueId, callId, 2)
    astEdgeBuilder.addAstEdge(falseId, callId, 3)
    astEdgeBuilder.addArgumentEdge(falseId, callId, 3)

    callId
  }

  def createCallNode(code: String, callName: String, dispatchType: String, lineAndColumn: LineAndColumn): NewCall = {
    val line   = lineAndColumn.line
    val column = lineAndColumn.column
    val call = NewCall()
      .code(shortenCode(code))
      .name(callName)
      .methodFullName(callName)
      .dispatchType(dispatchType)
      .lineNumber(line)
      .columnNumber(column)
      .typeFullName(Defines.Any)

    diffGraph.addNode(call)
    call
  }

  def createFileNode(fileName: String): NewFile = {
    val fileNode = NewFile()
      .name(fileName)
    diffGraph.addNode(fileNode)
    fileNode
  }

  def createNamespaceBlockNode(fullName: String): NewNamespaceBlock = {
    val namespaceBlock = NewNamespaceBlock()
      .name(Defines.GlobalNamespace)
      .fullName(fullName)
      .filename(source.filePath)
      .order(1)
    diffGraph.addNode(namespaceBlock)
    namespaceBlock
  }

  def createClosureBindingNode(closureBindingId: String, closureOriginalName: String): NewClosureBinding = {
    val closureBinding = NewClosureBinding()
      .closureBindingId(Some(closureBindingId))
      .evaluationStrategy(EvaluationStrategies.BY_REFERENCE)
      .closureOriginalName(Some(closureOriginalName))

    diffGraph.addNode(closureBinding)
    closureBinding
  }

  def createMethodRefNode(code: String, methodFullName: String, functionNode: FunctionNode): NewMethodRef = {
    val lineColumn = lineAndColumn(functionNode)
    val line       = lineColumn.line
    val column     = lineColumn.column
    val methodRef = NewMethodRef()
      .code(shortenCode(code))
      .methodFullName(methodFullName)
      .typeFullName(methodFullName)
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(methodRef)
    methodRef
  }

  def createTypeRefNode(code: String, typeFullName: String, classNode: ClassNode): NewTypeRef = {
    val lineColumn = lineAndColumn(classNode)
    val line       = lineColumn.line
    val column     = lineColumn.column
    val typeRef = NewTypeRef()
      .code(shortenCode(code))
      .typeFullName(typeFullName)
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(typeRef)
    typeRef
  }

  def createMethodNode(methodName: String, methodFullName: String, functionNode: FunctionNode): NewMethod = {
    val lineColumn = lineAndColumn(functionNode)
    val line       = lineColumn.line
    val column     = lineColumn.column
    val code       = shortenCode(sanitizeCode(functionNode))

    val method = NewMethod()
      .name(methodName)
      .filename(source.filePath)
      .code(code)
      .fullName(methodFullName)
      .isExternal(false)
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(method)
    method
  }

  def createModifierNode(modifierType: String): NewModifier = {
    val modifier = NewModifier()
      .modifierType(modifierType)
    diffGraph.addNode(modifier)
    modifier
  }

  def createBlockNode(node: Node, keepWholeCode: Boolean = false, customCode: Option[String] = None): NewBlock = {
    val lineColumn = lineAndColumn(node)
    val line       = lineColumn.line
    val column     = lineColumn.column
    val code = if (keepWholeCode) {
      customCode.getOrElse(sanitizeCode(node))
    } else {
      shortenCode(customCode.getOrElse(sanitizeCode(node)))
    }
    val block = NewBlock()
      .typeFullName(Defines.Any)
      .code(code)
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(block)
    block
  }

  def createMethodReturnNode(lineAndColumn: LineAndColumn): NewMethodReturn = {
    val line   = lineAndColumn.line
    val column = lineAndColumn.column
    val code   = "RET"

    val ret = NewMethodReturn()
      .code(shortenCode(code))
      .evaluationStrategy(EvaluationStrategies.BY_VALUE)
      .typeFullName(Defines.Any)
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(ret)
    ret
  }

  def createTypeNode(name: String, fullName: String): NewType = {
    val typ = NewType()
      .name(name)
      .fullName(fullName)
      .typeDeclFullName(fullName)
    diffGraph.addNode(typ)
    typ
  }

  def createBindingNode(): NewBinding = {
    val binding = NewBinding()
      .name("")
      .signature("")
    diffGraph.addNode(binding)
    binding
  }

  def createJumpTarget(caseNode: CaseNode): NewJumpTarget = {
    val jumpTarget = NewJumpTarget()
      .parserTypeName(caseNode.getClass.getSimpleName)
      .name(if (caseNode.toString().startsWith("case")) "case" else "default")
      .code(shortenCode(caseNode.toString()))
    diffGraph.addNode(jumpTarget)
    jumpTarget
  }

  def createControlStructureNode(node: Node, controlStructureType: String): NewControlStructure = {
    val controlStructure = NewControlStructure()
      .controlStructureType(controlStructureType)
      .code(shortenCode(source.getString(node)))
    diffGraph.addNode(controlStructure)
    controlStructure
  }

  def createMemberNode(name: String, node: Node, dynamicTypeOption: Option[String]): NewMember = {
    val member = NewMember()
      .code(shortenCode(source.getString(node)))
      .name(name)
      .typeFullName(Defines.Any)
      .dynamicTypeHintFullName(dynamicTypeOption.toList)
    diffGraph.addNode(member)
    member
  }

  def createLocalNode(name: String, typeFullName: String, closureBindingId: Option[String] = None): NewLocal = {
    val code = "N/A"
    val local = NewLocal()
      .code(shortenCode(code))
      .name(name)
      .typeFullName(typeFullName)
      .closureBindingId(closureBindingId)
    diffGraph.addNode(local)
    local
  }

  def createReturnNode(node: Node): NewReturn = {
    val lineColumn = lineAndColumn(node)
    val line       = lineColumn.line
    val column     = lineColumn.column
    val code       = sanitizeCode(node)
    val ret = NewReturn()
      .code(shortenCode(code))
      .lineNumber(line)
      .columnNumber(column)
    diffGraph.addNode(ret)
    ret
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy