From 6c2c3a59230579d929bb63881906b63591bdee7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Leuth=C3=A4user?= <1417198+max-leuthaeuser@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:14:59 +0200 Subject: [PATCH] Updated joern, cpg, and re-used x2cpg API (#337) * Updated joern, cpg, and re-used x2cpg API This is a rather large change as we have to update joern which brings in a lot of new stuff. FILE.content and offset calculation is NOT part of this PR. * More dep updates * Update build.sbt * Update build.sbt * Update build.sbt * Update build.sbt --- .github/workflows/pr.yml | 4 +- build.sbt | 11 +- project/plugins.sbt | 2 +- .../js2cpg/astcreation/AstCreator.scala | 742 ++++++++---------- .../js2cpg/astcreation/AstEdgeBuilder.scala | 2 +- .../js2cpg/astcreation/AstNodeBuilder.scala | 493 +++++------- .../io/shiftleft/js2cpg/core/Config.scala | 121 ++- .../io/shiftleft/js2cpg/core/Js2Cpg.scala | 73 +- .../io/shiftleft/js2cpg/core/Js2CpgMain.scala | 22 +- .../js2cpg/core/Js2cpgArgumentsParser.scala | 286 +++---- .../io/shiftleft/js2cpg/core/Report.scala | 105 --- .../js2cpg/datastructures/LineAndColumn.scala | 3 - .../io/shiftleft/js2cpg/io/FileUtils.scala | 4 +- .../io/shiftleft/js2cpg/io/JsFileChecks.scala | 8 +- .../io/shiftleft/js2cpg/io/PathFilter.scala | 6 +- .../js2cpg/parser/FreshJsonParser.scala | 2 +- .../js2cpg/parser/JavaScriptParser.scala | 2 +- .../io/shiftleft/js2cpg/parser/JsSource.scala | 119 +-- .../js2cpg/passes/AstCreationPass.scala | 13 +- .../js2cpg/passes/BuiltinTypesPass.scala | 2 +- .../shiftleft/js2cpg/passes/ConfigPass.scala | 8 +- .../js2cpg/passes/DependenciesPass.scala | 2 +- .../js2cpg/passes/PrivateKeyFilePass.scala | 2 +- .../js2cpg/preprocessing/NuxtTranspiler.scala | 2 +- .../preprocessing/TranspilationRunner.scala | 2 +- .../TranspilingEnvironment.scala | 2 +- .../js2cpg/preprocessing/VueTranspiler.scala | 4 +- .../js2cpg/utils/MemoryMetrics.scala | 5 +- .../js2cpg/utils/SourceWrapper.scala | 8 +- .../io/shiftleft/js2cpg/utils/TimeUtils.scala | 55 -- src/test/resources/excludes/a.js | 0 src/test/resources/excludes/folder/b.js | 0 src/test/resources/excludes/folder/c.js | 0 src/test/resources/excludes/foo.bar/d.js | 0 src/test/resources/excludes/index.js | 0 src/test/resources/excludes/tests/a.spec.js | 0 src/test/resources/excludes/tests/b.mock.js | 0 src/test/resources/excludes/tests/c.e2e.js | 0 src/test/resources/excludes/tests/d.test.js | 0 .../io/shiftleft/js2cpg/io/ExcludeTest.scala | 169 ++-- .../js2cpg/io/ExternalCommandTest.scala | 9 +- .../shiftleft/js2cpg/io/FileUtilsTest.scala | 4 +- .../shiftleft/js2cpg/parser/ParserTest.scala | 8 +- .../js2cpg/passes/AbstractPassTest.scala | 12 +- .../js2cpg/passes/BuiltinTypesPassTest.scala | 2 +- .../js2cpg/passes/CfgCreationPassTest.scala | 11 +- .../js2cpg/passes/ConfigPassTest.scala | 4 +- .../js2cpg/passes/DependenciesPassTest.scala | 10 +- .../passes/SimpleAstCreationPassTest.scala | 4 - .../TranspilationRunnerTest.scala | 20 +- 50 files changed, 1010 insertions(+), 1353 deletions(-) delete mode 100644 src/main/scala/io/shiftleft/js2cpg/core/Report.scala delete mode 100644 src/main/scala/io/shiftleft/js2cpg/datastructures/LineAndColumn.scala delete mode 100644 src/main/scala/io/shiftleft/js2cpg/utils/TimeUtils.scala delete mode 100644 src/test/resources/excludes/a.js delete mode 100644 src/test/resources/excludes/folder/b.js delete mode 100644 src/test/resources/excludes/folder/c.js delete mode 100644 src/test/resources/excludes/foo.bar/d.js delete mode 100644 src/test/resources/excludes/index.js delete mode 100644 src/test/resources/excludes/tests/a.spec.js delete mode 100644 src/test/resources/excludes/tests/b.mock.js delete mode 100644 src/test/resources/excludes/tests/c.e2e.js delete mode 100644 src/test/resources/excludes/tests/d.test.js diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7704b4526..2d2c25e28 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -20,7 +20,7 @@ jobs: java-version: '11' cache: 'sbt' - name: Compile and run tests - run: sbt clean +test + run: sbt test formatting: runs-on: ubuntu-22.04 steps: @@ -34,6 +34,6 @@ jobs: java-version: '11' cache: 'sbt' - name: Check formatting - run: sbt scalafmtCheck test:scalafmtCheck + run: sbt scalafmtCheck Test/scalafmtCheck - run: echo "Previous step failed because code is not formatted. Run 'sbt scalafmt'" if: ${{ failure() }} diff --git a/build.sbt b/build.sbt index 2983f561a..55c612d18 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ -val cpgVersion = "1.6.11" -val joernVersion = "2.0.335" +val cpgVersion = "1.6.13" +val joernVersion = "2.0.392" val gitCommitString = SettingKey[String]("gitSha") @@ -27,10 +27,11 @@ lazy val commonSettings = Seq( "io.shiftleft" %% "codepropertygraph" % cpgVersion, "io.joern" %% "x2cpg" % joernVersion, "com.github.scopt" %% "scopt" % "4.1.0", - "org.graalvm.js" % "js" % "22.3.4", - "com.fasterxml.jackson.core" % "jackson-databind" % "2.15.3", + // do not update to 23.x as this requires JDK >= 19 + "org.graalvm.js" % "js" % "22.3.5", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.1", "com.atlassian.sourcemap" % "sourcemap" % "2.0.0", - "commons-io" % "commons-io" % "2.13.0", + "commons-io" % "commons-io" % "2.16.1", "org.slf4j" % "slf4j-api" % "2.0.7", "org.apache.logging.log4j" % "log4j-slf4j2-impl" % "2.20.0" % Optional, "org.apache.logging.log4j" % "log4j-core" % "2.20.0" % Optional, diff --git a/project/plugins.sbt b/project/plugins.sbt index fa69a01f3..89d10763d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,4 +2,4 @@ addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") addSbtPlugin("io.shiftleft" % "sbt-ci-release-early" % "2.0.27") addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.12.0") diff --git a/src/main/scala/io/shiftleft/js2cpg/astcreation/AstCreator.scala b/src/main/scala/io/shiftleft/js2cpg/astcreation/AstCreator.scala index b1e34ddb3..86422f909 100644 --- a/src/main/scala/io/shiftleft/js2cpg/astcreation/AstCreator.scala +++ b/src/main/scala/io/shiftleft/js2cpg/astcreation/AstCreator.scala @@ -58,9 +58,18 @@ import io.shiftleft.codepropertygraph.generated.nodes.{ NewTypeRef } import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, DispatchTypes, ModifierTypes, Operators} -import io.shiftleft.js2cpg.datastructures.Stack._ -import io.shiftleft.js2cpg.datastructures._ -import io.shiftleft.js2cpg.datastructures.scope._ +import io.shiftleft.js2cpg.datastructures.Stack.* +import io.shiftleft.js2cpg.datastructures.scope.Scope +import io.shiftleft.js2cpg.datastructures.OrderTracker +import io.shiftleft.js2cpg.datastructures.scope.MethodScope +import io.shiftleft.js2cpg.datastructures.Parameter +import io.shiftleft.js2cpg.datastructures.scope.BlockScope +import io.shiftleft.js2cpg.datastructures.scope.BlockScopeElement +import io.shiftleft.js2cpg.datastructures.scope.MethodScopeElement +import io.shiftleft.js2cpg.datastructures.scope.ResolvedReference +import io.shiftleft.js2cpg.datastructures.scope.ScopeElement +import io.shiftleft.js2cpg.datastructures.scope.ScopeElementIterator +import io.shiftleft.js2cpg.datastructures.scope.ScopeType import io.shiftleft.js2cpg.passes.{Defines, EcmaBuiltins, PassHelpers} import io.shiftleft.js2cpg.passes.PassHelpers.ParamNodeInitKind import io.shiftleft.js2cpg.parser.{GeneralizingAstVisitor, JsSource} @@ -68,7 +77,7 @@ import overflowdb.BatchedUpdate.DiffGraphBuilder import org.slf4j.LoggerFactory import scala.collection.mutable -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* object AstCreator { @@ -80,16 +89,14 @@ object AstCreator { } -class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: Set[String]) - extends GeneralizingAstVisitor[NewNode] { +class AstCreator(val diffGraph: DiffGraphBuilder, val source: JsSource, val usedIdentNodes: Set[String]) + extends GeneralizingAstVisitor[NewNode] + with AstNodeBuilder + with AstEdgeBuilder { import AstCreator._ - private val scope = new Scope() - - private val astEdgeBuilder = new AstEdgeBuilder(diffGraph) - - private val astNodeBuilder = new AstNodeBuilder(diffGraph, astEdgeBuilder, source, scope) + protected val scope = new Scope() // Nested methods are not put in the AST where they are defined. // Instead we put them directly under the METHOD in which they are @@ -110,26 +117,23 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: private val usedVariableNames = mutable.HashMap.empty[String, Int] private def prepareFileWrapperFunction(): NewNamespaceBlock = { - val fileName = source.filePath - val fileNode = astNodeBuilder.createFileNode(fileName) - - val namespaceBlock = - astNodeBuilder.createNamespaceBlockNode(fileName + ":" + Defines.GlobalNamespace) - - astEdgeBuilder.addAstEdge(namespaceBlock, fileNode) + val fileName = source.filePath + val fileNode = createFileNode(fileName) + val namespaceBlock = createNamespaceBlockNode(fileName + ":" + Defines.GlobalNamespace) + addAstEdge(namespaceBlock, fileNode) namespaceBlock } private def addLocalToAst(local: NewLocal): Unit = { - astEdgeBuilder.addAstEdge(local, localAstParentStack.head, 0) + addAstEdge(local, localAstParentStack.head, 0) } private def addMethodToAst(method: NewMethod): Unit = { - astEdgeBuilder.addAstEdge(method, methodAstParentStack.head, 0) + addAstEdge(method, methodAstParentStack.head, 0) } private def addTypeDeclToAst(typeDecl: NewTypeDecl): Unit = { - astEdgeBuilder.addAstEdge(typeDecl, methodAstParentStack.head, 0) + addAstEdge(typeDecl, methodAstParentStack.head, 0) } /** Entry point for converting ASTs with this class. @@ -151,46 +155,46 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } module.getImports.forEach { importNode => - val groupId = astNodeBuilder.groupIdFromImportNode(importNode) + val groupId = groupIdFromImportNode(importNode) importNode.getModuleSpecifier match { case null => val defaultBinding = importNode.getImportClause.getDefaultBinding if (defaultBinding != null) { - astNodeBuilder.createDependencyNode(defaultBinding.getName, groupId, VERSION_IMPORT) + createDependencyNode(defaultBinding.getName, groupId, VERSION_IMPORT) createImportNodeAndAttachToAst(importNode) } val nameSpaceImport = importNode.getImportClause.getNameSpaceImport val namedImports = importNode.getImportClause.getNamedImports if (nameSpaceImport != null) { - astNodeBuilder.createDependencyNode(nameSpaceImport.getBindingIdentifier.getName, groupId, VERSION_IMPORT) + createDependencyNode(nameSpaceImport.getBindingIdentifier.getName, groupId, VERSION_IMPORT) createImportNodeAndAttachToAst(importNode) } else if (namedImports != null) { namedImports.getImportSpecifiers.forEach { namedImport => - astNodeBuilder.createDependencyNode(namedImport.getBindingIdentifier.getName, groupId, VERSION_IMPORT) + createDependencyNode(namedImport.getBindingIdentifier.getName, groupId, VERSION_IMPORT) createImportNodeAndAttachToAst(importNode) } } case module => - astNodeBuilder.createDependencyNode(module.getString, groupId, VERSION_IMPORT) + createDependencyNode(module.getString, groupId, VERSION_IMPORT) createImportNodeAndAttachToAst(importNode) } } } private def createImportNodeAndAttachToAst(importNode: ImportNode) = { - val impNode = astNodeBuilder.createImportNode(importNode) + val impNode = createImportNode(importNode) methodAstParentStack.headOption.collect { case namespaceBlockNode: NewNamespaceBlock => - astEdgeBuilder.addAstEdge(impNode, namespaceBlockNode) + addAstEdge(impNode, namespaceBlockNode) } } override def visit(breakNode: BreakNode): NewNode = { - astNodeBuilder.createControlStructureNode(breakNode, ControlStructureTypes.BREAK) + createControlStructureNode(breakNode, ControlStructureTypes.BREAK) } override def visit(continueNode: ContinueNode): NewNode = { - astNodeBuilder.createControlStructureNode(continueNode, ControlStructureTypes.CONTINUE) + createControlStructureNode(continueNode, ControlStructureTypes.CONTINUE) } private def createIdentifierNode(name: String, lineAndColumnProvider: Node): NewIdentifier = { @@ -205,7 +209,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: None } - astNodeBuilder.createIdentifierNode(name, lineAndColumnProvider, dynamicInstanceTypeOption) + createIdentifierNode(name, lineAndColumnProvider, dynamicInstanceTypeOption) } private def handleDestructingParameter( @@ -218,13 +222,13 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: ): Unit = { val name = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, s"param$index") - val code = paramComponents.map(source.getCode).mkString("{", ", ", "}") - astNodeBuilder.createParameterInNode(name, code, methodId, paramComponents.head, new OrderTracker(index)) + val code_ = paramComponents.map(code).mkString("{", ", ", "}") + createParameterInNode(name, code_, methodId, paramComponents.head, new OrderTracker(index)) initStatements match { case Some(initExpr: ExpressionStatement) => val destructingAssignmentId = convertDestructingAssignment(initExpr.getExpression.asInstanceOf[BinaryNode], Some(name)) - astEdgeBuilder.addAstEdge(destructingAssignmentId, blockId, blockOrder) + addAstEdge(destructingAssignmentId, blockId, blockOrder) case None => paramComponents.foreach { param => val paramName = param.getName @@ -232,14 +236,14 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val paramId = createIdentifierNode(name, param) scope.addVariableReference(name, paramId) - val localParamLocal = astNodeBuilder.createLocalNode(paramName, Defines.Any) + val localParamLocal = createLocalNode(paramName, Defines.Any) addLocalToAst(localParamLocal) scope.addVariable(paramName, localParamLocal, MethodScope) - val keyId = astNodeBuilder.createFieldIdentifierNode(paramName, param) - val accessId = astNodeBuilder.createFieldAccessNode(paramId, keyId, astNodeBuilder.lineAndColumn(param)) + val keyId = createFieldIdentifierNode(paramName, param) + val accessId = createFieldAccessCallNode(paramId, keyId, param) val assignmentCallId = - astNodeBuilder.createAssignmentNode(localParamId, accessId, astNodeBuilder.lineAndColumn(param)) - astEdgeBuilder.addAstEdge(assignmentCallId, blockId, blockOrder) + createAssignmentNode(localParamId, accessId, param) + addAstEdge(assignmentCallId, blockId, blockOrder) blockOrder.inc() } case _ => @@ -256,36 +260,36 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: blockOrder: OrderTracker ): Unit = { val name, code = PassHelpers.cleanParameterNodeName(parameter) - astNodeBuilder.createParameterInNode(name, code, methodId, parameter, new OrderTracker(index)) + createParameterInNode(name, code, methodId, parameter, new OrderTracker(index)) initStatement match { case Some(initExpr: VarNode) => val paramName = initExpr.getName.getName val localParamId = createIdentifierNode(paramName, initExpr) val rhs = createRhsForConditionalParameterInit(initExpr.getAssignmentSource, name, initExpr) val assignmentCallId = - astNodeBuilder.createAssignmentNode(localParamId, rhs, astNodeBuilder.lineAndColumn(initExpr)) - astEdgeBuilder.addAstEdge(assignmentCallId, blockId, blockOrder) + createAssignmentNode(localParamId, rhs, initExpr) + addAstEdge(assignmentCallId, blockId, blockOrder) case Some(initExpr: ExpressionStatement) => val destructingAssignmentId = convertDestructingAssignment(initExpr.getExpression.asInstanceOf[BinaryNode], Some(name)) - astEdgeBuilder.addAstEdge(destructingAssignmentId, blockId, blockOrder) + addAstEdge(destructingAssignmentId, blockId, blockOrder) case None => val paramName = name val localParamId = createIdentifierNode(paramName, parameter) - val localParamLocal = astNodeBuilder.createLocalNode(paramName, Defines.Any) + val localParamLocal = createLocalNode(paramName, Defines.Any) addLocalToAst(localParamLocal) scope.addVariable(paramName, localParamLocal, MethodScope) val paramId = createIdentifierNode(name, parameter) scope.addVariableReference(name, paramId) - val keyId = astNodeBuilder.createFieldIdentifierNode(paramName, parameter) + val keyId = createFieldIdentifierNode(paramName, parameter) - val accessId = astNodeBuilder.createFieldAccessNode(paramId, keyId, astNodeBuilder.lineAndColumn(parameter)) + val accessId = createFieldAccessCallNode(paramId, keyId, parameter) val assignmentCallId = - astNodeBuilder.createAssignmentNode(localParamId, accessId, astNodeBuilder.lineAndColumn(parameter)) - astEdgeBuilder.addAstEdge(assignmentCallId, blockId, blockOrder) + createAssignmentNode(localParamId, accessId, parameter) + addAstEdge(assignmentCallId, blockId, blockOrder) case _ => logger.debug(s"Unhandled parameter kind: $initStatement") } @@ -304,26 +308,26 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val (methodName, methodFullName) = calcMethodNameAndFullName(functionNode) - val methodId = astNodeBuilder.createMethodNode(methodName, methodFullName, functionNode) + val methodId = createMethodNode(methodName, methodFullName, functionNode) addMethodToAst(methodId) if (!functionNode.isProgram) { - val virtualModifierId = astNodeBuilder.createModifierNode(ModifierTypes.VIRTUAL) - astEdgeBuilder.addAstEdge(virtualModifierId, methodId) + val virtualModifierId = createModifierNode(ModifierTypes.VIRTUAL) + addAstEdge(virtualModifierId, methodId) } val methodRefId = if (!shouldCreateFunctionReference) { None } else { - Some(astNodeBuilder.createMethodRefNode(methodName, methodFullName, functionNode)) + Some(createMethodRefNode(methodName, methodFullName, functionNode)) } methodAstParentStack.push(methodId) val block = functionNode.getBody - val blockId = astNodeBuilder.createBlockNode(block, functionNode.isProgram) - astEdgeBuilder.addAstEdge(blockId, methodId, 1) + val blockId = createBlockNode(block, functionNode.isProgram) + addAstEdge(blockId, methodId, 1) val capturingRefId = if (shouldCreateFunctionReference) { @@ -335,15 +339,9 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val parameterOrderTracker = new OrderTracker(0) // We always create an instance parameter because in JS every function could get called with an instance. - astNodeBuilder.createParameterInNode("this", "this", methodId, functionNode, parameterOrderTracker) + createParameterInNode("this", "this", methodId, functionNode, parameterOrderTracker) functionNode.getParameters.forEach { parameter => - astNodeBuilder.createParameterInNode( - parameter.getName, - source.getString(parameter), - methodId, - parameter, - parameterOrderTracker - ) + createParameterInNode(parameter.getName, source.getString(parameter), methodId, parameter, parameterOrderTracker) } val blockOrder = new OrderTracker() @@ -370,15 +368,15 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } val methodReturnId = - astNodeBuilder.createMethodReturnNode(astNodeBuilder.lineAndColumn(functionNode)) - astEdgeBuilder.addAstEdge(methodReturnId, methodId, 2) + createMethodReturnNode(functionNode) + addAstEdge(methodReturnId, methodId, 2) val filteredFunctionBodyStatements = functionBodyStatements.filterNot(PassHelpers.isSynthetic(_, destructingParameters.flatten ++ syntheticParameters)) visitStatements( filteredFunctionBodyStatements.asJava, statementId => { - astEdgeBuilder.addAstEdge(statementId, blockId, blockOrder) + addAstEdge(statementId, blockId, blockOrder) } ) localAstParentStack.pop() @@ -387,7 +385,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: methodAstParentStack.pop() - createFunctionTypeAndTypeDecl(methodId, methodAstParentStack.head, methodName, methodFullName) + createFunctionTypeAndTypeDecl(functionNode, methodId, methodAstParentStack.head, methodName, methodFullName) (methodRefId, methodId) } @@ -405,7 +403,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: override def visit(debuggerNode: DebuggerNode): NewNode = { // If no debugging is available, the debugger statement has no effect. - astNodeBuilder.createUnknownNode(debuggerNode) + createUnknownNode(debuggerNode) } override def visit(functionNode: FunctionNode): NewNode = { @@ -420,28 +418,29 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } private def createFunctionTypeAndTypeDecl( + node: Node, methodId: NewMethod, parentNodeId: NewNode, methodName: String, methodFullName: String ): Unit = { - astNodeBuilder.createTypeNode(methodName, methodFullName) + createTypeNode(methodName, methodFullName) val astParentType = parentNodeId.label val astParentFullName = parentNodeId.properties("FULL_NAME").toString val functionTypeDeclId = - astNodeBuilder.createTypeDeclNode(methodName, methodFullName, astParentType, astParentFullName, Some(Defines.Any)) + createTypeDeclNode(node, methodName, methodFullName, astParentType, astParentFullName, Some(Defines.Any)) addTypeDeclToAst(functionTypeDeclId) // Problem for https://github.com/ShiftLeftSecurity/codescience/issues/3626 here. // As the type (thus, the signature) of the function node is unknown (i.e., ANY*) // we can't generate the correct binding with signature. - val functionBindingId = astNodeBuilder.createBindingNode() - astEdgeBuilder.addBindsEdge(functionBindingId, functionTypeDeclId) + val functionBindingId = createBindingNode() + addBindsEdge(functionBindingId, functionTypeDeclId) - astEdgeBuilder.addRefEdge(methodId, functionBindingId) + addRefEdge(methodId, functionBindingId) } override def visit(classNode: ClassNode): NewNode = { @@ -449,7 +448,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val metaTypeName = s"$typeName" val metaTypeFullName = s"$typeFullName" - astNodeBuilder.createTypeNode(typeName, typeFullName) + createTypeNode(typeName, typeFullName) // We do not need to look at classNode.getClassHeritage because // the CPG only allows us to encode inheriting from fully known @@ -460,12 +459,13 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val astParentFullName = methodAstParentStack.head.properties("FULL_NAME").toString val typeDeclId = - astNodeBuilder.createTypeDeclNode(typeName, typeFullName, astParentType, astParentFullName, inheritsFrom = None) + createTypeDeclNode(classNode, typeName, typeFullName, astParentType, astParentFullName, inheritsFrom = None) - astNodeBuilder.createTypeNode(metaTypeName, metaTypeFullName) + createTypeNode(metaTypeName, metaTypeFullName) val metaTypeDeclId = - astNodeBuilder.createTypeDeclNode( + createTypeDeclNode( + classNode, metaTypeName, metaTypeFullName, astParentType, @@ -477,7 +477,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: addTypeDeclToAst(metaTypeDeclId) val metaTypeRefId = - astNodeBuilder.createTypeRefNode(s"class $typeName", metaTypeFullName, classNode) + createTypeRefNode(s"class $typeName", metaTypeFullName, classNode) methodAstParentStack.push(typeDeclId) dynamicInstanceTypeStack.push(typeFullName) @@ -488,10 +488,10 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val constructor = classNode.getConstructor.getValue.asInstanceOf[FunctionNode] val constructorId = createFunctionNode(constructor, shouldCreateFunctionReference = false)._2 - val constructorBindingId = astNodeBuilder.createBindingNode() - astEdgeBuilder.addBindsEdge(constructorBindingId, metaTypeDeclId) + val constructorBindingId = createBindingNode() + addBindsEdge(constructorBindingId, metaTypeDeclId) - astEdgeBuilder.addRefEdge(constructorId, constructorBindingId) + addRefEdge(constructorId, constructorBindingId) val memberOrderTracker = new OrderTracker() classNode.getClassElements.forEach { classElement => @@ -507,16 +507,16 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: // identical. val functionFullName = calcMethodNameAndFullName(function)._2 val dynamicTypeHintFullName = Some(functionFullName) - astNodeBuilder.createMemberNode(memberName, classElement, dynamicTypeHintFullName) + createMemberNode(memberName, classElement, dynamicTypeHintFullName) case _ => - astNodeBuilder.createMemberNode(memberName, classElement, dynamicTypeOption = None) + createMemberNode(memberName, classElement, dynamicTypeOption = None) } if (classElement.isStatic) { // Static member belong to the meta class. - astEdgeBuilder.addAstEdge(memberId, metaTypeDeclId, memberOrderTracker) + addAstEdge(memberId, metaTypeDeclId, memberOrderTracker) } else { - astEdgeBuilder.addAstEdge(memberId, typeDeclId, memberOrderTracker) + addAstEdge(memberId, typeDeclId, memberOrderTracker) } } } @@ -533,20 +533,20 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } override def visit(ifNode: IfNode): NewNode = { - val ifNodeId = astNodeBuilder.createControlStructureNode(ifNode, ControlStructureTypes.IF) + val ifNodeId = createControlStructureNode(ifNode, ControlStructureTypes.IF) Option(ifNode.getTest).foreach { testNode => val testId = testNode.accept(this) - astEdgeBuilder.addAstEdge(testId, ifNodeId, 1) - astEdgeBuilder.addConditionEdge(testId, ifNodeId) + addAstEdge(testId, ifNodeId, 1) + addConditionEdge(testId, ifNodeId) } Option(ifNode.getPass).foreach { passNode => val passId = passNode.accept(this) - astEdgeBuilder.addAstEdge(passId, ifNodeId, 2) + addAstEdge(passId, ifNodeId, 2) } Option(ifNode.getFail).foreach { failNode => val failId = failNode.accept(this) - astEdgeBuilder.addAstEdge(failId, ifNodeId, 3) + addAstEdge(failId, ifNodeId, 3) } ifNodeId @@ -561,29 +561,29 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: ): NewCall = { val argIds = callNode.getArgs.asScala.map(_.accept(this)) - val baseCode = astNodeBuilder.codeOf(functionBaseId) + val baseCode = codeOf(functionBaseId) val propertyCode = functionPropertyId match { - case Some(id) => "." + astNodeBuilder.codeOf(id) + case Some(id) => "." + codeOf(id) case None => "" } - val argsCode = argIds.map(astNodeBuilder.codeOf).mkString("(", ", ", ")") + val argsCode = argIds.map(codeOf).mkString("(", ", ", ")") val code = s"$baseCode$propertyCode$argsCode" val callId = - astNodeBuilder.createCallNode(code, "", DispatchTypes.DYNAMIC_DISPATCH, astNodeBuilder.lineAndColumn(callNode)) + createCallNode(code, "", DispatchTypes.DYNAMIC_DISPATCH, callNode) val orderTracker = new OrderTracker(0) val argIndexTracker = new OrderTracker(0) - astEdgeBuilder.addAstEdge(receiverId, callId, orderTracker) - astEdgeBuilder.addReceiverEdge(receiverId, callId) + addAstEdge(receiverId, callId, orderTracker) + addReceiverEdge(receiverId, callId) - astEdgeBuilder.addAstEdge(baseId, callId, orderTracker) - astEdgeBuilder.addArgumentEdge(baseId, callId, argIndexTracker) + addAstEdge(baseId, callId, orderTracker) + addArgumentEdge(baseId, callId, argIndexTracker) argIds.foreach { argId => - astEdgeBuilder.addAstEdge(argId, callId, orderTracker) - astEdgeBuilder.addArgumentEdge(argId, callId, argIndexTracker) + addAstEdge(argId, callId, orderTracker) + addArgumentEdge(argId, callId, argIndexTracker) } callId @@ -597,18 +597,13 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: case identNode: IdentNode => identNode.getName } - val callId = astNodeBuilder.createStaticCallNode( - callNode.toString(), - methodName, - methodFullName, - astNodeBuilder.lineAndColumn(callNode) - ) + val callId = createStaticCallNode(callNode.toString(), methodName, methodFullName, callNode) val orderTracker = new OrderTracker() val argIndexTracker = new OrderTracker() callNode.getArgs.forEach { arg => val argId = arg.accept(this) - astEdgeBuilder.addAstEdge(argId, callId, orderTracker) - astEdgeBuilder.addArgumentEdge(argId, callId, argIndexTracker) + addAstEdge(argId, callId, orderTracker) + addArgumentEdge(argId, callId, argIndexTracker) } callId @@ -649,21 +644,12 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val baseId = base.accept(this) - val tmpAssignmentId = astNodeBuilder.createAssignmentNode( - baseTmpId, - baseId, - astNodeBuilder.lineAndColumn(base), - withParenthesis = true - ) + val tmpAssignmentId = createAssignmentNode(baseTmpId, baseId, base, withParenthesis = true) val memberId = - astNodeBuilder.createFieldIdentifierNode(functionAccessNode.getProperty, functionAccessNode) + createFieldIdentifierNode(functionAccessNode.getProperty, functionAccessNode) - val fieldAccessId = astNodeBuilder.createFieldAccessNode( - tmpAssignmentId, - memberId, - astNodeBuilder.lineAndColumn(functionAccessNode) - ) + val fieldAccessId = createFieldAccessCallNode(tmpAssignmentId, memberId, functionAccessNode) val thisTmpId = createIdentifierNode(tmpVarName, functionAccessNode) scope.addVariableReference(tmpVarName, thisTmpId) @@ -696,45 +682,45 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } else { ControlStructureTypes.WHILE } - val whileNodeId = astNodeBuilder.createControlStructureNode(whileNode, controlStructureType) + val whileNodeId = createControlStructureNode(whileNode, controlStructureType) if (whileNode.isDoWhile) { val bodyId = whileNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(bodyId, whileNodeId, 1) + addAstEdge(bodyId, whileNodeId, 1) val testId = whileNode.getTest.accept(this) - astEdgeBuilder.addAstEdge(testId, whileNodeId, 2) - astEdgeBuilder.addConditionEdge(testId, whileNodeId) + addAstEdge(testId, whileNodeId, 2) + addConditionEdge(testId, whileNodeId) } else { val testId = whileNode.getTest.accept(this) - astEdgeBuilder.addAstEdge(testId, whileNodeId, 1) - astEdgeBuilder.addConditionEdge(testId, whileNodeId) + addAstEdge(testId, whileNodeId, 1) + addConditionEdge(testId, whileNodeId) val bodyId = whileNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(bodyId, whileNodeId, 2) + addAstEdge(bodyId, whileNodeId, 2) } whileNodeId } private def createForNode(forNode: ForNode): NewControlStructure = { - val forNodeId = astNodeBuilder.createControlStructureNode(forNode, ControlStructureTypes.FOR) + val forNodeId = createControlStructureNode(forNode, ControlStructureTypes.FOR) Option(forNode.getInit).foreach { initNode => val initNodeId = initNode.accept(this) - astEdgeBuilder.addAstEdge(initNodeId, forNodeId, 1) + addAstEdge(initNodeId, forNodeId, 1) } val testNodeId = forNode.getTest match { case null => // If the for condition is empty, this ensures that there is always a condition (true) present. val testNodeId = - astNodeBuilder.createLiteralNode("true", astNodeBuilder.lineAndColumn(forNode), Some(Defines.Boolean)) + createLiteralNode("true", forNode, Some(Defines.Boolean)) testNodeId case testNode if testNode.getExpression == null => // If the for condition is empty, this ensures that there is always a condition (true) present. val testNodeId = - astNodeBuilder.createLiteralNode("true", astNodeBuilder.lineAndColumn(forNode), Some(Defines.Boolean)) + createLiteralNode("true", forNode, Some(Defines.Boolean)) testNodeId // The test of a forNode can be a JoinPredecessorExpression which does not wrap any expression. // This only happens for "for (x in y)" style loops. @@ -743,16 +729,16 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: case testNode if testNode.getExpression != null => testNode.accept(this) } - astEdgeBuilder.addAstEdge(testNodeId, forNodeId, 2) + addAstEdge(testNodeId, forNodeId, 2) Option(forNode.getModify).foreach { modifyNode => val modifyNodeId = modifyNode.accept(this) - astEdgeBuilder.addAstEdge(modifyNodeId, forNodeId, 3) + addAstEdge(modifyNodeId, forNodeId, 3) } if (forNode.getBody.getStatementCount != 0) { val bodyId = forNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(bodyId, forNodeId, 4) + addAstEdge(bodyId, forNodeId, 4) } forNodeId @@ -770,7 +756,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: private def createForInOrOfNode(forNode: ForNode): NewBlock = { // surrounding block: val blockOrder = new OrderTracker() - val blockId = astNodeBuilder.createBlockNode(forNode) + val blockId = createBlockNode(forNode) scope.pushNewBlockScope(blockId) localAstParentStack.push(blockId) @@ -780,143 +766,134 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: // _iterator assignment: val iteratorName = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_iterator") - val iteratorLocalId = astNodeBuilder.createLocalNode(iteratorName, Defines.Any) + val iteratorLocalId = createLocalNode(iteratorName, Defines.Any) addLocalToAst(iteratorLocalId) val iteratorId = createIdentifierNode(iteratorName, forNode) - val callId = astNodeBuilder.createCallNode( + val callId = createCallNode( "Object.keys(" + collectionName + ")[Symbol.iterator]()", "", DispatchTypes.DYNAMIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) + forNode ) val thisId = createIdentifierNode("this", forNode) - val indexCallId = astNodeBuilder.createCallNode( + val indexCallId = createCallNode( "Object.keys(" + collectionName + ")[Symbol.iterator]", Operators.indexAccess, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) + forNode ) - val objectKeysCallId = astNodeBuilder.createStaticCallNode( - "Object.keys(" + collectionName + ")", - "keys", - "Object.keys", - astNodeBuilder.lineAndColumn(forNode) - ) + val objectKeysCallId = + createStaticCallNode("Object.keys(" + collectionName + ")", "keys", "Object.keys", forNode) val argId = collection.accept(this) - astEdgeBuilder.addAstEdge(argId, objectKeysCallId, 1) - astEdgeBuilder.addArgumentEdge(argId, objectKeysCallId, 1) + addAstEdge(argId, objectKeysCallId, 1) + addArgumentEdge(argId, objectKeysCallId, 1) val indexBaseId = createIdentifierNode("Symbol", forNode) - val indexMemberId = astNodeBuilder.createFieldIdentifierNode("iterator", forNode) + val indexMemberId = createFieldIdentifierNode("iterator", forNode) val indexAccessId = - astNodeBuilder.createFieldAccessNode(indexBaseId, indexMemberId, astNodeBuilder.lineAndColumn(forNode)) + createFieldAccessCallNode(indexBaseId, indexMemberId, forNode) - astEdgeBuilder.addAstEdge(objectKeysCallId, indexCallId, 1) - astEdgeBuilder.addArgumentEdge(objectKeysCallId, indexCallId, 1) - astEdgeBuilder.addAstEdge(indexAccessId, indexCallId, 2) - astEdgeBuilder.addArgumentEdge(indexAccessId, indexCallId, 2) + addAstEdge(objectKeysCallId, indexCallId, 1) + addArgumentEdge(objectKeysCallId, indexCallId, 1) + addAstEdge(indexAccessId, indexCallId, 2) + addArgumentEdge(indexAccessId, indexCallId, 2) - astEdgeBuilder.addAstEdge(indexCallId, callId, 0) - astEdgeBuilder.addReceiverEdge(indexCallId, callId) + addAstEdge(indexCallId, callId, 0) + addReceiverEdge(indexCallId, callId) - astEdgeBuilder.addAstEdge(thisId, callId, 1) - astEdgeBuilder.addArgumentEdge(thisId, callId, 0) + addAstEdge(thisId, callId, 1) + addArgumentEdge(thisId, callId, 0) val iteratorAssignmentId = - astNodeBuilder.createCallNode( + createCallNode( iteratorName + " = " + "Object.keys(" + collectionName + ")[Symbol.iterator]()", Operators.assignment, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) + forNode ) - astEdgeBuilder.addAstEdge(iteratorId, iteratorAssignmentId, 1) - astEdgeBuilder.addArgumentEdge(iteratorId, iteratorAssignmentId, 1) - astEdgeBuilder.addAstEdge(callId, iteratorAssignmentId, 2) - astEdgeBuilder.addArgumentEdge(callId, iteratorAssignmentId, 2) - astEdgeBuilder.addAstEdge(iteratorAssignmentId, blockId, blockOrder) + addAstEdge(iteratorId, iteratorAssignmentId, 1) + addArgumentEdge(iteratorId, iteratorAssignmentId, 1) + addAstEdge(callId, iteratorAssignmentId, 2) + addArgumentEdge(callId, iteratorAssignmentId, 2) + addAstEdge(iteratorAssignmentId, blockId, blockOrder) // _result: val resultName = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_result") - val resultLocalId = astNodeBuilder.createLocalNode(resultName, Defines.Any) + val resultLocalId = createLocalNode(resultName, Defines.Any) addLocalToAst(resultLocalId) val resultId = createIdentifierNode(resultName, forNode) - astEdgeBuilder.addAstEdge(resultId, blockId, blockOrder) + addAstEdge(resultId, blockId, blockOrder) // loop variable: val loopVariableName = forNode.getInit.toString() - val loopVariableLocalId = astNodeBuilder.createLocalNode(loopVariableName, Defines.Any) + val loopVariableLocalId = createLocalNode(loopVariableName, Defines.Any) addLocalToAst(loopVariableLocalId) - val loopVariableId = createIdentifierNode(loopVariableName, forNode) - astEdgeBuilder.addAstEdge(loopVariableId, blockId, blockOrder) + val loopVariableId = createIdentifierNode(loopVariableName, forNode.getInit) + addAstEdge(loopVariableId, blockId, blockOrder) // while loop: val whileLoopId = - astNodeBuilder.createControlStructureNode(forNode, ControlStructureTypes.WHILE) - astEdgeBuilder.addAstEdge(whileLoopId, blockId, blockOrder) + createControlStructureNode(forNode, ControlStructureTypes.WHILE) + addAstEdge(whileLoopId, blockId, blockOrder) // while loop test: - val testCallId = astNodeBuilder.createCallNode( + val testCallId = createCallNode( "!(" + resultName + " = " + iteratorName + ".next()).done", Operators.not, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) + forNode ) - val doneBaseId = astNodeBuilder.createCallNode( + val doneBaseId = createCallNode( "(" + resultName + " = " + iteratorName + ".next())", Operators.assignment, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) + forNode ) val lhsId = createIdentifierNode(resultName, forNode) - val rhsId = astNodeBuilder.createCallNode( - iteratorName + ".next()", - "", - DispatchTypes.DYNAMIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) - ) + val rhsId = createCallNode(iteratorName + ".next()", "", DispatchTypes.DYNAMIC_DISPATCH, forNode) val nextBaseId = createIdentifierNode(iteratorName, forNode) - val nextMemberId = astNodeBuilder.createFieldIdentifierNode("next", forNode) + val nextMemberId = createFieldIdentifierNode("next", forNode) val nextReceiverId = - astNodeBuilder.createFieldAccessNode(nextBaseId, nextMemberId, astNodeBuilder.lineAndColumn(forNode)) + createFieldAccessCallNode(nextBaseId, nextMemberId, forNode) val thisNextId = createIdentifierNode(iteratorName, forNode) - astEdgeBuilder.addAstEdge(nextReceiverId, rhsId, 0) - astEdgeBuilder.addReceiverEdge(nextReceiverId, rhsId) + addAstEdge(nextReceiverId, rhsId, 0) + addReceiverEdge(nextReceiverId, rhsId) - astEdgeBuilder.addAstEdge(thisNextId, rhsId, 1) - astEdgeBuilder.addArgumentEdge(thisNextId, rhsId, 0) + addAstEdge(thisNextId, rhsId, 1) + addArgumentEdge(thisNextId, rhsId, 0) - astEdgeBuilder.addAstEdge(lhsId, doneBaseId, 1) - astEdgeBuilder.addArgumentEdge(lhsId, doneBaseId, 1) - astEdgeBuilder.addAstEdge(rhsId, doneBaseId, 2) - astEdgeBuilder.addArgumentEdge(rhsId, doneBaseId, 2) + addAstEdge(lhsId, doneBaseId, 1) + addArgumentEdge(lhsId, doneBaseId, 1) + addAstEdge(rhsId, doneBaseId, 2) + addArgumentEdge(rhsId, doneBaseId, 2) - val doneMemberId = astNodeBuilder.createFieldIdentifierNode("done", forNode) + val doneMemberId = createFieldIdentifierNode("done", forNode) - val testId = astNodeBuilder.createFieldAccessNode(doneBaseId, doneMemberId, astNodeBuilder.lineAndColumn(forNode)) + val testId = createFieldAccessCallNode(doneBaseId, doneMemberId, forNode) - astEdgeBuilder.addAstEdge(testId, testCallId, 1) - astEdgeBuilder.addArgumentEdge(testId, testCallId, 1) + addAstEdge(testId, testCallId, 1) + addArgumentEdge(testId, testCallId, 1) - astEdgeBuilder.addAstEdge(testCallId, whileLoopId, 1) - astEdgeBuilder.addConditionEdge(testCallId, whileLoopId) + addAstEdge(testCallId, whileLoopId, 1) + addConditionEdge(testCallId, whileLoopId) // while loop variable assignment: val whileLoopVariableId = @@ -924,37 +901,37 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val baseId = createIdentifierNode(resultName, forNode) - val memberId = astNodeBuilder.createFieldIdentifierNode("value", forNode) + val memberId = createFieldIdentifierNode("value", forNode) val accessId = - astNodeBuilder.createFieldAccessNode(baseId, memberId, astNodeBuilder.lineAndColumn(forNode)) + createFieldAccessCallNode(baseId, memberId, forNode) - val loopVariableAssignmentId = astNodeBuilder.createCallNode( + val loopVariableAssignmentId = createCallNode( loopVariableName + " = " + resultName + ".value", Operators.assignment, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(forNode) + forNode ) - astEdgeBuilder.addAstEdge(whileLoopVariableId, loopVariableAssignmentId, 1) - astEdgeBuilder.addArgumentEdge(whileLoopVariableId, loopVariableAssignmentId, 1) - astEdgeBuilder.addAstEdge(accessId, loopVariableAssignmentId, 2) - astEdgeBuilder.addArgumentEdge(accessId, loopVariableAssignmentId, 2) + addAstEdge(whileLoopVariableId, loopVariableAssignmentId, 1) + addArgumentEdge(whileLoopVariableId, loopVariableAssignmentId, 1) + addAstEdge(accessId, loopVariableAssignmentId, 2) + addArgumentEdge(accessId, loopVariableAssignmentId, 2) val whileLoopBlockOrder = new OrderTracker() - val whileLoopBlockId = astNodeBuilder.createBlockNode(forNode) + val whileLoopBlockId = createBlockNode(forNode) scope.pushNewBlockScope(whileLoopBlockId) localAstParentStack.push(whileLoopBlockId) - astEdgeBuilder.addAstEdge(loopVariableAssignmentId, whileLoopBlockId, whileLoopBlockOrder) + addAstEdge(loopVariableAssignmentId, whileLoopBlockId, whileLoopBlockOrder) // while loop block: if (forNode.getBody.getStatementCount != 0) { val bodyId = forNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(bodyId, whileLoopBlockId, whileLoopBlockOrder) + addAstEdge(bodyId, whileLoopBlockId, whileLoopBlockOrder) } - astEdgeBuilder.addAstEdge(whileLoopBlockId, whileLoopId, 2) + addAstEdge(whileLoopBlockId, whileLoopId, 2) scope.popScope() localAstParentStack.pop() @@ -977,7 +954,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } private def createRealBlock(block: Block): NewBlock = { - val blockId = astNodeBuilder.createBlockNode(block) + val blockId = createBlockNode(block) val orderTracker = new OrderTracker() scope.pushNewBlockScope(blockId) @@ -986,7 +963,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: visitStatements( block.getStatements, statementId => { - astEdgeBuilder.addAstEdge(statementId, blockId, orderTracker) + addAstEdge(statementId, blockId, orderTracker) } ) localAstParentStack.pop() @@ -1046,75 +1023,69 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: */ private def createArrayLiteralNode(arrayLiteralNode: ArrayLiteralNode): NewNode = { if (arrayLiteralNode.getElementExpressions.isEmpty) { - val arrayCallId = astNodeBuilder.createCallNode( + val arrayCallId = createCallNode( EcmaBuiltins.arrayFactory + "()", EcmaBuiltins.arrayFactory, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(arrayLiteralNode) + arrayLiteralNode ) arrayCallId } else { - val blockId = astNodeBuilder.createBlockNode(arrayLiteralNode) + val blockId = createBlockNode(arrayLiteralNode) scope.pushNewBlockScope(blockId) val blockOrder = new OrderTracker() localAstParentStack.push(blockId) - val tmpName = - PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_tmp") - val localTmpId = astNodeBuilder.createLocalNode(tmpName, Defines.Any) + val tmpName = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_tmp") + val localTmpId = createLocalNode(tmpName, Defines.Any) addLocalToAst(localTmpId) val tmpArrayId = createIdentifierNode(tmpName, arrayLiteralNode) - val arrayCallId = astNodeBuilder.createCallNode( + val arrayCallId = createCallNode( EcmaBuiltins.arrayFactory + "()", EcmaBuiltins.arrayFactory, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(arrayLiteralNode) + arrayLiteralNode ) val assignmentTmpArrayCallId = - astNodeBuilder.createAssignmentNode(tmpArrayId, arrayCallId, astNodeBuilder.lineAndColumn(arrayLiteralNode)) + createAssignmentNode(tmpArrayId, arrayCallId, arrayLiteralNode) - astEdgeBuilder.addAstEdge(assignmentTmpArrayCallId, blockId, blockOrder) + addAstEdge(assignmentTmpArrayCallId, blockId, blockOrder) arrayLiteralNode.getElementExpressions.forEach { case element if element != null => val elementId = element.accept(this) val pushCallId = - astNodeBuilder.createCallNode( - tmpName + s".push(${astNodeBuilder.codeOf(elementId)})", - "", - DispatchTypes.DYNAMIC_DISPATCH, - astNodeBuilder.lineAndColumn(element) - ) + createCallNode(tmpName + s".push(${codeOf(elementId)})", "", DispatchTypes.DYNAMIC_DISPATCH, element) val nextBaseId = createIdentifierNode(tmpName, element) - val nextMemberId = astNodeBuilder.createFieldIdentifierNode("push", element) + val nextMemberId = createFieldIdentifierNode("push", element) val nextReceiverId = - astNodeBuilder.createFieldAccessNode(nextBaseId, nextMemberId, astNodeBuilder.lineAndColumn(element)) + createFieldAccessCallNode(nextBaseId, nextMemberId, element) val thisPushId = createIdentifierNode(tmpName, element) - astEdgeBuilder.addAstEdge(nextReceiverId, pushCallId, 0) - astEdgeBuilder.addReceiverEdge(nextReceiverId, pushCallId) + addAstEdge(nextReceiverId, pushCallId, 0) + addReceiverEdge(nextReceiverId, pushCallId) - astEdgeBuilder.addAstEdge(thisPushId, pushCallId, 1) - astEdgeBuilder.addArgumentEdge(thisPushId, pushCallId, 0) + addAstEdge(thisPushId, pushCallId, 1) + addArgumentEdge(thisPushId, pushCallId, 0) - astEdgeBuilder.addAstEdge(elementId, pushCallId, 2) - astEdgeBuilder.addArgumentEdge(elementId, pushCallId, 1) + addAstEdge(elementId, pushCallId, 2) + addArgumentEdge(elementId, pushCallId, 1) - astEdgeBuilder.addAstEdge(pushCallId, blockId, blockOrder) + addAstEdge(pushCallId, blockId, blockOrder) case _ => // skip } val tmpArrayReturnId = createIdentifierNode(tmpName, arrayLiteralNode) - astEdgeBuilder.addAstEdge(tmpArrayReturnId, blockId, blockOrder) + addAstEdge(tmpArrayReturnId, blockId, blockOrder) scope.popScope() localAstParentStack.pop() @@ -1143,7 +1114,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: case obj => (obj.getString, None) } - astNodeBuilder.createLiteralNode(code, astNodeBuilder.lineAndColumn(literalNode), dynamicTypeOption) + createLiteralNode(code, literalNode, dynamicTypeOption) } } @@ -1155,41 +1126,41 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: override def visit(accessNode: AccessNode): NewNode = { val baseId = accessNode.getBase.accept(this) - val memberId = astNodeBuilder.createFieldIdentifierNode(accessNode.getProperty, accessNode) - val accessId = astNodeBuilder.createFieldAccessNode(baseId, memberId, astNodeBuilder.lineAndColumn(accessNode)) + val memberId = createFieldIdentifierNode(accessNode.getProperty, accessNode) + val accessId = createFieldAccessCallNode(baseId, memberId, accessNode) accessId } private def handleSwitchCase(caseNode: CaseNode, blockId: NewBlock, blockOrder: OrderTracker): Unit = { - val labelId = astNodeBuilder.createJumpTarget(caseNode) - astEdgeBuilder.addAstEdge(labelId, blockId, blockOrder) + val labelId = createJumpTarget(caseNode) + addAstEdge(labelId, blockId, blockOrder) Option(caseNode.getTest).foreach { testExpr => val testId = testExpr.accept(this) - astEdgeBuilder.addAstEdge(testId, blockId, blockOrder) + addAstEdge(testId, blockId, blockOrder) } visitStatements( caseNode.getStatements, statementId => { - astEdgeBuilder.addAstEdge(statementId, blockId, blockOrder) + addAstEdge(statementId, blockId, blockOrder) } ) } override def visit(switchNode: SwitchNode): NewNode = { val switchNodeId = - astNodeBuilder.createControlStructureNode(switchNode, ControlStructureTypes.SWITCH) + createControlStructureNode(switchNode, ControlStructureTypes.SWITCH) // We need to get the to be switched upon expression from our switchExpressionStack because // the compiler generates a synthetic let: 'let :switch = expr' and thus switchNode.getExpression // just returns an IdentNode to :switch. val switchExpression = switchExpressionStack.head val switchExpressionId = switchExpression.accept(this) - astEdgeBuilder.addAstEdge(switchExpressionId, switchNodeId, 1) - astEdgeBuilder.addConditionEdge(switchExpressionId, switchNodeId) + addAstEdge(switchExpressionId, switchNodeId, 1) + addConditionEdge(switchExpressionId, switchNodeId) - val blockId = astNodeBuilder.createBlockNode(switchNode) + val blockId = createBlockNode(switchNode) scope.pushNewBlockScope(blockId) localAstParentStack.push(blockId) @@ -1198,7 +1169,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: handleSwitchCase(caseNode, blockId, blockOrder) } - astEdgeBuilder.addAstEdge(blockId, switchNodeId, 2) + addAstEdge(blockId, switchNodeId, 2) scope.popScope() localAstParentStack.pop() @@ -1208,14 +1179,10 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: override def visit(parameterNode: ParameterNode): NewNode = { val parameterNodeId = createIdentifierNode("arguments", parameterNode) - val indexId = astNodeBuilder.createLiteralNode( - parameterNode.getIndex.toString, - astNodeBuilder.lineAndColumn(parameterNode), - Some(Defines.Number) - ) + val indexId = createLiteralNode(parameterNode.getIndex.toString, parameterNode, Some(Defines.Number)) val accessId = - astNodeBuilder.createIndexAccessNode(parameterNodeId, indexId, astNodeBuilder.lineAndColumn(parameterNode)) + createIndexAccessNode(parameterNodeId, indexId, parameterNode) accessId } @@ -1247,7 +1214,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val rhsId = binTestExpr.getRhs.accept(this) - val testCallId = astNodeBuilder.createEqualsCallNode(lhsId, rhsId, astNodeBuilder.lineAndColumn(binTestExpr)) + val testCallId = createEqualsCallNode(lhsId, rhsId, binTestExpr) testCallId case otherExpr => otherExpr.accept(this) @@ -1265,13 +1232,13 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: case _ => ternaryNode.getFalseExpression.accept(this) } } - astNodeBuilder.createTernaryNode(testId, trueId, falseId, astNodeBuilder.lineAndColumn(ternaryNode)) + createTernaryNode(testId, trueId, falseId, ternaryNode) } private def createDependencyNodeForRequire(name: String, node: Node): Unit = { PassHelpers .getRequire(node) - .foreach(id => astNodeBuilder.createDependencyNode(name, id, VERSION_REQUIRE)) + .foreach(id => createDependencyNode(name, id, VERSION_REQUIRE)) } private def createVarNodeParamNodeInitKindFalse(varNode: VarNode, assignmentSource: Expression): NewNode = { @@ -1284,7 +1251,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: (Defines.Any, "") } - val varId = astNodeBuilder.createLocalNode(varNode.getName.getName, typeFullName) + val varId = createLocalNode(varNode.getName.getName, typeFullName) addLocalToAst(varId) val scopeType = if (varNode.isLet) { BlockScope @@ -1306,7 +1273,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } val assigmentCallId = - astNodeBuilder.createAssignmentNode(destId, sourceId, astNodeBuilder.lineAndColumn(varNode), customCode = code) + createAssignmentNode(destId, sourceId, varNode, customCode = code) assigmentCallId } else { new NewCompositeNode() @@ -1319,7 +1286,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val destId = varNode.getAssignmentDest.accept(this) val rhsId = createRhsForConditionalParameterInit(varNode.getInit, varNode) val assigmentCallId = - astNodeBuilder.createAssignmentNode(destId, rhsId, astNodeBuilder.lineAndColumn(varNode)) + createAssignmentNode(destId, rhsId, varNode) assigmentCallId case ParamNodeInitKind.PLAIN => // We get here for all parameters of functions with at least one default parameter value @@ -1357,11 +1324,11 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val localTmpName = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_tmp") - val blockId = astNodeBuilder.createBlockNode(assignment) + val blockId = createBlockNode(assignment) scope.pushNewBlockScope(blockId) localAstParentStack.push(blockId) - val localId = astNodeBuilder.createLocalNode(localTmpName, Defines.Any) + val localId = createLocalNode(localTmpName, Defines.Any) addLocalToAst(localId) val tmpId = createIdentifierNode(localTmpName, rhs) @@ -1386,46 +1353,36 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } val assignmentTmpCallId = - astNodeBuilder.createAssignmentNode(tmpId, rhsId, astNodeBuilder.lineAndColumn(rhs)) + createAssignmentNode(tmpId, rhsId, rhs) - astEdgeBuilder.addAstEdge(assignmentTmpCallId, blockId, blockOrder) + addAstEdge(assignmentTmpCallId, blockId, blockOrder) def convertDestructingElement(element: Node, index: Int): NewNode = { element match { case identNode: IdentNode => val elementId = identNode.accept(this) val fieldAccessTmpId = createIdentifierNode(localTmpName, identNode) - val indexId = astNodeBuilder.createLiteralNode( - index.toString, - astNodeBuilder.lineAndColumn(identNode), - Some(Defines.Number) - ) + val indexId = createLiteralNode(index.toString, identNode, Some(Defines.Number)) val accessId = - astNodeBuilder.createIndexAccessNode(fieldAccessTmpId, indexId, astNodeBuilder.lineAndColumn(identNode)) + createIndexAccessNode(fieldAccessTmpId, indexId, identNode) val assignmentCallId = - astNodeBuilder.createAssignmentNode(elementId, accessId, astNodeBuilder.lineAndColumn(identNode)) + createAssignmentNode(elementId, accessId, identNode) assignmentCallId case propertyNode: PropertyNode if propertyNode.getValue == null && AstHelpers.getUnaryOperation( propertyNode.getKey.tokenType().toString ) == ".spreadObject" => // TODO: how to handle spread objects here? - logger.debug( - s"Using a spread object for object deconstructing is not yet supported! (${source.getCode(assignment)})" - ) - val unknownId = astNodeBuilder.createUnknownNode(propertyNode) + logger.debug(s"Using a spread object for object deconstructing is not yet supported! (${code(assignment)})") + val unknownId = createUnknownNode(propertyNode) unknownId case propertyNode: PropertyNode => val valueId = propertyNode.getValue.accept(this) val fieldAccessTmpId = createIdentifierNode(localTmpName, propertyNode) - val keyId = astNodeBuilder.createPropertyKeyNode(propertyNode) - val accessId = astNodeBuilder.createFieldAccessNode( - fieldAccessTmpId, - keyId, - astNodeBuilder.lineAndColumn(propertyNode.getKey) - ) + val keyId = createPropertyKeyNode(propertyNode) + val accessId = createFieldAccessCallNode(fieldAccessTmpId, keyId, propertyNode.getKey) val assignmentCallId = - astNodeBuilder.createAssignmentNode(valueId, accessId, astNodeBuilder.lineAndColumn(propertyNode)) + createAssignmentNode(valueId, accessId, propertyNode) assignmentCallId } @@ -1440,38 +1397,26 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val fieldAccessTmpId = createIdentifierNode(localTmpName, binaryNode) - val indexId = astNodeBuilder.createLiteralNode( - index.toString, - astNodeBuilder.lineAndColumn(binaryNode), - Some(Defines.Number) - ) + val indexId = createLiteralNode(index.toString, binaryNode, Some(Defines.Number)) val accessId = - astNodeBuilder.createIndexAccessNode(fieldAccessTmpId, indexId, astNodeBuilder.lineAndColumn(binaryNode)) + createIndexAccessNode(fieldAccessTmpId, indexId, binaryNode) - val voidCallId = astNodeBuilder.createCallNode( - "void 0", - ".void", - DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(binaryNode.getLhs) - ) + val voidCallId = + createCallNode("void 0", ".void", DispatchTypes.STATIC_DISPATCH, binaryNode.getLhs) val equalsCallId = - astNodeBuilder.createEqualsCallNode(accessId, voidCallId, astNodeBuilder.lineAndColumn(binaryNode.getRhs)) + createEqualsCallNode(accessId, voidCallId, binaryNode.getRhs) equalsCallId } val falseId = { val fieldAccessTmpId = createIdentifierNode(localTmpName, binaryNode) - val indexId = astNodeBuilder.createLiteralNode( - index.toString, - astNodeBuilder.lineAndColumn(binaryNode), - Some(Defines.Number) - ) + val indexId = createLiteralNode(index.toString, binaryNode, Some(Defines.Number)) val accessId = - astNodeBuilder.createIndexAccessNode(fieldAccessTmpId, indexId, astNodeBuilder.lineAndColumn(binaryNode)) + createIndexAccessNode(fieldAccessTmpId, indexId, binaryNode) accessId } (lhsId, testId, rhsId, falseId) @@ -1480,10 +1425,8 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: propertyNode.getKey.tokenType().toString ) == ".spreadObject" => // TODO: how to handle spread objects here? - logger.debug( - s"Using a spread object for object deconstructing is not yet supported! (${source.getCode(assignment)})" - ) - val unknownId = astNodeBuilder.createUnknownNode(propertyNode) + logger.debug(s"Using a spread object for object deconstructing is not yet supported! (${code(assignment)})") + val unknownId = createUnknownNode(propertyNode) return unknownId case propertyNode: PropertyNode => val valueAsBinaryNode = propertyNode.getValue.asInstanceOf[BinaryNode] @@ -1492,41 +1435,33 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val testId = { val fieldAccessTmpId = createIdentifierNode(localTmpName, propertyNode) - val keyId = astNodeBuilder.createPropertyKeyNode(propertyNode) + val keyId = createPropertyKeyNode(propertyNode) val accessId = - astNodeBuilder.createFieldAccessNode(fieldAccessTmpId, keyId, astNodeBuilder.lineAndColumn(propertyNode)) + createFieldAccessCallNode(fieldAccessTmpId, keyId, propertyNode) - val voidCallId = astNodeBuilder.createCallNode( - "void 0", - ".void", - DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(propertyNode.getKey) - ) + val voidCallId = + createCallNode("void 0", ".void", DispatchTypes.STATIC_DISPATCH, propertyNode.getKey) - val equalsCallId = astNodeBuilder.createEqualsCallNode( - accessId, - voidCallId, - astNodeBuilder.lineAndColumn(valueAsBinaryNode.getRhs) - ) + val equalsCallId = createEqualsCallNode(accessId, voidCallId, valueAsBinaryNode.getRhs) equalsCallId } val falseId = { val fieldAccessTmpId = createIdentifierNode(localTmpName, propertyNode) - val keyId = astNodeBuilder.createPropertyKeyNode(propertyNode) + val keyId = createPropertyKeyNode(propertyNode) val accessId = - astNodeBuilder.createFieldAccessNode(fieldAccessTmpId, keyId, astNodeBuilder.lineAndColumn(propertyNode)) + createFieldAccessCallNode(fieldAccessTmpId, keyId, propertyNode) accessId } (lhsId, testId, rhsId, falseId) } val ternaryNodeId = - astNodeBuilder.createTernaryNode(testId, trueId, falseId, astNodeBuilder.lineAndColumn(element)) + createTernaryNode(testId, trueId, falseId, element) val assignmentCallId = - astNodeBuilder.createAssignmentNode(lhsId, ternaryNodeId, astNodeBuilder.lineAndColumn(element)) + createAssignmentNode(lhsId, ternaryNodeId, element) assignmentCallId } @@ -1535,21 +1470,21 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: lhs.getElements.asScala.zipWithIndex.foreach { case (element: PropertyNode, index: Int) if element.getValue.isInstanceOf[BinaryNode] => val subTreeId = convertDestructingElementWithDefault(element, index) - astEdgeBuilder.addAstEdge(subTreeId, blockId, blockOrder) + addAstEdge(subTreeId, blockId, blockOrder) createDependencyNodeForRequire(element.getKeyName, assignment.getRhs) case (element: PropertyNode, index: Int) => val subTreeId = convertDestructingElement(element, index) - astEdgeBuilder.addAstEdge(subTreeId, blockId, blockOrder) + addAstEdge(subTreeId, blockId, blockOrder) createDependencyNodeForRequire(element.getKeyName, assignment.getRhs) } case lhs: ArrayLiteralNode => lhs.getElementExpressions.asScala.zipWithIndex.foreach { case (element: BinaryNode, index: Int) => val subTreeId = convertDestructingElementWithDefault(element, index) - astEdgeBuilder.addAstEdge(subTreeId, blockId, blockOrder) + addAstEdge(subTreeId, blockId, blockOrder) case (element: IdentNode, index: Int) => val subTreeId = convertDestructingElement(element, index) - astEdgeBuilder.addAstEdge(subTreeId, blockId, blockOrder) + addAstEdge(subTreeId, blockId, blockOrder) createDependencyNodeForRequire(element.getName, assignment.getRhs) // Skipped for array destruction assignment with ignores. The JS parser inserts null here. case (null, _) => @@ -1559,21 +1494,21 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } val returnTmpId = createIdentifierNode(localTmpName, rhs) - astEdgeBuilder.addAstEdge(returnTmpId, blockId, blockOrder) + addAstEdge(returnTmpId, blockId, blockOrder) scope.popScope() localAstParentStack.pop() blockId } - private def convertCommaOp(commaop: BinaryNode): NewBlock = { - val lhsId = commaop.getLhs.accept(this) - val rhsId = commaop.getRhs.accept(this) + private def convertCommaOp(commaOp: BinaryNode): NewBlock = { + val lhsId = commaOp.getLhs.accept(this) + val rhsId = commaOp.getRhs.accept(this) // generate the exact same code value that we used to when this was handled through `convertSimpleBinaryOp` - val code = astNodeBuilder.codeOf(lhsId) + " , " + astNodeBuilder.codeOf(rhsId) - val blockId = astNodeBuilder.createBlockNode(commaop, customCode = Some(code)) + val code = codeOf(lhsId) + " , " + codeOf(rhsId) + val blockId = createBlockNode(commaOp, customCode = Some(code)) - astEdgeBuilder.addAstEdge(lhsId, blockId, 0) - astEdgeBuilder.addAstEdge(rhsId, blockId, 1) + addAstEdge(lhsId, blockId, 0) + addAstEdge(rhsId, blockId, 1) blockId } @@ -1584,17 +1519,17 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val lhsId = binaryNode.getLhs.accept(this) val rhsId = binaryNode.getRhs.accept(this) - val callId = astNodeBuilder.createCallNode( - astNodeBuilder.codeOf(lhsId) + " " + binaryNode.tokenType + " " + astNodeBuilder.codeOf(rhsId), + val callId = createCallNode( + codeOf(lhsId) + " " + binaryNode.tokenType + " " + codeOf(rhsId), op, DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(binaryNode) + binaryNode ) - astEdgeBuilder.addAstEdge(lhsId, callId, 1) - astEdgeBuilder.addArgumentEdge(lhsId, callId, 1) - astEdgeBuilder.addAstEdge(rhsId, callId, 2) - astEdgeBuilder.addArgumentEdge(rhsId, callId, 2) + addAstEdge(lhsId, callId, 1) + addArgumentEdge(lhsId, callId, 1) + addAstEdge(rhsId, callId, 2) + addArgumentEdge(rhsId, callId, 2) callId } @@ -1602,7 +1537,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: private def createConstructorBlock(unaryNode: UnaryNode): NewBlock = { val constructorCall = unaryNode.getExpression.asInstanceOf[CallNode] - val blockId = astNodeBuilder.createBlockNode(unaryNode) + val blockId = createBlockNode(unaryNode) scope.pushNewBlockScope(blockId) val blockOrder = new OrderTracker() @@ -1610,32 +1545,27 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val tmpAllocName = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_tmp") - val localTmpAllocId = astNodeBuilder.createLocalNode(tmpAllocName, Defines.Any) + val localTmpAllocId = createLocalNode(tmpAllocName, Defines.Any) addLocalToAst(localTmpAllocId) val tmpAllocId1 = createIdentifierNode(tmpAllocName, unaryNode) - val allocId = astNodeBuilder.createCallNode( - ".alloc", - ".alloc", - DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(unaryNode) - ) + val allocId = createCallNode(".alloc", ".alloc", DispatchTypes.STATIC_DISPATCH, unaryNode) val assignmentTmpAllocCallId = - astNodeBuilder.createAssignmentNode(tmpAllocId1, allocId, astNodeBuilder.lineAndColumn(unaryNode)) + createAssignmentNode(tmpAllocId1, allocId, unaryNode) - astEdgeBuilder.addAstEdge(assignmentTmpAllocCallId, blockId, blockOrder) + addAstEdge(assignmentTmpAllocCallId, blockId, blockOrder) val tmpAllocId2 = createIdentifierNode(tmpAllocName, unaryNode) val receiverId = constructorCall.getFunction.accept(this) val callId = handleCallNodeArgs(constructorCall, receiverId, tmpAllocId2, receiverId, None) - astEdgeBuilder.addAstEdge(callId, blockId, blockOrder) + addAstEdge(callId, blockId, blockOrder) val tmpAllocReturnId = createIdentifierNode(tmpAllocName, unaryNode) - astEdgeBuilder.addAstEdge(tmpAllocReturnId, blockId, blockOrder) + addAstEdge(tmpAllocReturnId, blockId, blockOrder) scope.popScope() localAstParentStack.pop() @@ -1646,28 +1576,23 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: private def createUnaryNodeForPrefixOperation(unaryNode: UnaryNode, op: String): NewCall = { val astChildId = unaryNode.getExpression.accept(this) - val code = unaryNode.tokenType().toString + " " + astNodeBuilder.codeOf(astChildId) + val code = unaryNode.tokenType().toString + " " + codeOf(astChildId) val callId = - astNodeBuilder.createCallNode(code, op, DispatchTypes.STATIC_DISPATCH, astNodeBuilder.lineAndColumn(unaryNode)) + createCallNode(code, op, DispatchTypes.STATIC_DISPATCH, unaryNode) - astEdgeBuilder.addAstEdge(astChildId, callId, 1) - astEdgeBuilder.addArgumentEdge(astChildId, callId, 1) + addAstEdge(astChildId, callId, 1) + addArgumentEdge(astChildId, callId, 1) callId } private def createUnaryNode(unaryNode: UnaryNode, op: String): NewCall = { - val callId = astNodeBuilder.createCallNode( - unaryNode.toString, - op, - DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(unaryNode) - ) + val callId = createCallNode(unaryNode.toString, op, DispatchTypes.STATIC_DISPATCH, unaryNode) val astChildId = unaryNode.getExpression.accept(this) - astEdgeBuilder.addAstEdge(astChildId, callId, 1) - astEdgeBuilder.addArgumentEdge(astChildId, callId, 1) + addAstEdge(astChildId, callId, 1) + addArgumentEdge(astChildId, callId, 1) callId } @@ -1689,37 +1614,37 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: node.getExpressions.asScala } - val callId = astNodeBuilder.createCallNode( + val callId = createCallNode( s"__Runtime.TO_STRING(${args.mkString(",")})", "__Runtime.TO_STRING", DispatchTypes.STATIC_DISPATCH, - astNodeBuilder.lineAndColumn(templateLiteralNode) + templateLiteralNode ) val callOrder = new OrderTracker() val callArgIndex = new OrderTracker() args.foreach { expression => val argId = expression.accept(this) - astEdgeBuilder.addAstEdge(argId, callId, callOrder) - astEdgeBuilder.addArgumentEdge(argId, callId, callArgIndex) + addAstEdge(argId, callId, callOrder) + addArgumentEdge(argId, callId, callArgIndex) } callId } override def visit(ternaryNode: TernaryNode): NewNode = { - astNodeBuilder.createTernaryNode( + createTernaryNode( ternaryNode.getTest.accept(this), ternaryNode.getTrueExpression.accept(this), ternaryNode.getFalseExpression.accept(this), - astNodeBuilder.lineAndColumn(ternaryNode) + ternaryNode ) } override def visit(throwNode: ThrowNode): NewNode = { - val unknownId = astNodeBuilder.createUnknownNode(throwNode) + val unknownId = createUnknownNode(throwNode) val astChildId = throwNode.getExpression.accept(this) - astEdgeBuilder.addAstEdge(astChildId, unknownId, 1) + addAstEdge(astChildId, unknownId, 1) unknownId } @@ -1728,20 +1653,20 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: // calculated during JS runtime. How to emulate this? // (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) override def visit(withNode: WithNode): NewNode = { - val unknownId = astNodeBuilder.createUnknownNode(withNode) + val unknownId = createUnknownNode(withNode) val expressionId = withNode.getExpression.accept(this) - astEdgeBuilder.addAstEdge(expressionId, unknownId, 1) + addAstEdge(expressionId, unknownId, 1) val bodyId = withNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(bodyId, unknownId, 2) + addAstEdge(bodyId, unknownId, 2) unknownId } // TODO: Proper handling of label nodes. // Currently we lack appropriate handling for GOTOs. override def visit(labelNode: LabelNode): NewNode = { - val unknownId = astNodeBuilder.createUnknownNode(labelNode) + val unknownId = createUnknownNode(labelNode) val astChildId = labelNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(astChildId, unknownId, 1) + addAstEdge(astChildId, unknownId, 1) unknownId } @@ -1763,12 +1688,12 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } override def visit(tryNode: TryNode): NewNode = { - val tryNodeId = astNodeBuilder.createControlStructureNode(tryNode, ControlStructureTypes.TRY) + val tryNodeId = createControlStructureNode(tryNode, ControlStructureTypes.TRY) val bodyId = tryNode.getBody.accept(this) - astEdgeBuilder.addAstEdge(bodyId, tryNodeId, 1) + addAstEdge(bodyId, tryNodeId, 1) - val blockId = astNodeBuilder.createBlockNode(tryNode) + val blockId = createBlockNode(tryNode) scope.pushNewBlockScope(blockId) val blockOrder = new OrderTracker() localAstParentStack.push(blockId) @@ -1777,18 +1702,18 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: visitStatements( catchBlock.getStatements, { statementId => - astEdgeBuilder.addAstEdge(statementId, blockId, blockOrder) + addAstEdge(statementId, blockId, blockOrder) } ) } - astEdgeBuilder.addAstEdge(blockId, tryNodeId, 2) + addAstEdge(blockId, tryNodeId, 2) scope.popScope() localAstParentStack.pop() Option(tryNode.getFinallyBody).foreach { finallyBody => val finallyBodyId = finallyBody.accept(this) - astEdgeBuilder.addAstEdge(finallyBodyId, tryNodeId, 3) + addAstEdge(finallyBodyId, tryNodeId, 3) } tryNodeId @@ -1797,34 +1722,33 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: override def visit(indexNode: IndexNode): NewNode = { val baseId = indexNode.getBase.accept(this) val indexId = indexNode.getIndex.accept(this) - astNodeBuilder.createIndexAccessNode(baseId, indexId, astNodeBuilder.lineAndColumn(indexNode)) + createIndexAccessNode(baseId, indexId, indexNode) } override def visit(returnNode: ReturnNode): NewNode = { - val retId = astNodeBuilder.createReturnNode(returnNode) + val retId = createReturnNode(returnNode) Option(returnNode.getExpression).foreach { returnExpression => val retExprId = returnExpression.accept(this) - astEdgeBuilder.addAstEdge(retExprId, retId, 1) - astEdgeBuilder.addArgumentEdge(retExprId, retId, 1) + addAstEdge(retExprId, retId, 1) + addArgumentEdge(retExprId, retId, 1) } retId } override def visit(errorNode: ErrorNode): NewNode = { - astNodeBuilder.createUnknownNode(errorNode) + createUnknownNode(errorNode) } override def visit(objectNode: ObjectNode): NewNode = { - val blockId = astNodeBuilder.createBlockNode(objectNode) + val blockId = createBlockNode(objectNode) scope.pushNewBlockScope(blockId) val blockOrder = new OrderTracker() localAstParentStack.push(blockId) - val tmpName = - PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_tmp") - val localId = astNodeBuilder.createLocalNode(tmpName, Defines.Any) + val tmpName = PassHelpers.generateUnusedVariableName(usedVariableNames, usedIdentNodes, "_tmp") + val localId = createLocalNode(tmpName, Defines.Any) addLocalToAst(localId) objectNode.getElements.forEach { @@ -1838,7 +1762,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: ) == ".spreadObject" => // TODO: handling of spread objects here val exprId = element.getKey.asInstanceOf[UnaryNode].getExpression.accept(this) - astEdgeBuilder.addAstEdge(exprId, blockId, blockOrder) + addAstEdge(exprId, blockId, blockOrder) case element => val rightHandSideId = element.getValue match { case functionNode: FunctionNode => @@ -1848,19 +1772,15 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: val leftHandSideTmpId = createIdentifierNode(tmpName, element) - val keyId = astNodeBuilder.createPropertyKeyNode(element) + val keyId = createPropertyKeyNode(element) val leftHandSideFieldAccessId = - astNodeBuilder.createFieldAccessNode(leftHandSideTmpId, keyId, astNodeBuilder.lineAndColumn(element.getKey)) + createFieldAccessCallNode(leftHandSideTmpId, keyId, element.getKey) val assignmentCallId = - astNodeBuilder.createAssignmentNode( - leftHandSideFieldAccessId, - rightHandSideId, - astNodeBuilder.lineAndColumn(element) - ) + createAssignmentNode(leftHandSideFieldAccessId, rightHandSideId, element) - astEdgeBuilder.addAstEdge(assignmentCallId, blockId, blockOrder) + addAstEdge(assignmentCallId, blockId, blockOrder) // getter + setter: Option(element.getGetter).foreach(_.accept(this)) @@ -1868,7 +1788,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } val tmpId = createIdentifierNode(tmpName, objectNode) - astEdgeBuilder.addAstEdge(tmpId, blockId, blockOrder) + addAstEdge(tmpId, blockId, blockOrder) scope.popScope() localAstParentStack.pop() @@ -1907,12 +1827,12 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: case None => val methodScopeNodeId = methodScope.scopeNode val localId = - astNodeBuilder.createLocalNode(origin.variableName, Defines.Any, Some(closureBindingIdProperty)) - astEdgeBuilder.addAstEdge(localId, methodScopeNodeId, 0) + createLocalNode(origin.variableName, Defines.Any, Some(closureBindingIdProperty)) + addAstEdge(localId, methodScopeNodeId, 0) val closureBindingId = - astNodeBuilder.createClosureBindingNode(closureBindingIdProperty, origin.variableName) + createClosureBindingNode(closureBindingIdProperty, origin.variableName) - methodScope.capturingRefId.foreach(astEdgeBuilder.addCaptureEdge(closureBindingId, _)) + methodScope.capturingRefId.foreach(addCaptureEdge(closureBindingId, _)) nextReferenceId = closureBindingId @@ -1929,7 +1849,7 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: } localOrCapturedLocalIdOption.foreach { localOrCapturedLocalId => - astEdgeBuilder.addRefEdge(localOrCapturedLocalId, currentReferenceId) + addRefEdge(localOrCapturedLocalId, currentReferenceId) currentReferenceId = nextReferenceId } @@ -1943,8 +1863,8 @@ class AstCreator(diffGraph: DiffGraphBuilder, source: JsSource, usedIdentNodes: variableName: String ): (NewNode, ScopeType) = { val varId = - astNodeBuilder.createLocalNode(variableName, Defines.Any) - astEdgeBuilder.addAstEdge(varId, methodScopeNodeId, 0) + createLocalNode(variableName, Defines.Any) + addAstEdge(varId, methodScopeNodeId, 0) (varId, MethodScope) } diff --git a/src/main/scala/io/shiftleft/js2cpg/astcreation/AstEdgeBuilder.scala b/src/main/scala/io/shiftleft/js2cpg/astcreation/AstEdgeBuilder.scala index 41e179c4b..fda3d4129 100644 --- a/src/main/scala/io/shiftleft/js2cpg/astcreation/AstEdgeBuilder.scala +++ b/src/main/scala/io/shiftleft/js2cpg/astcreation/AstEdgeBuilder.scala @@ -6,7 +6,7 @@ import io.shiftleft.js2cpg.datastructures.OrderTracker import overflowdb.BatchedUpdate.DiffGraphBuilder import org.slf4j.LoggerFactory -class AstEdgeBuilder(private val diffGraph: DiffGraphBuilder) { +trait AstEdgeBuilder { this: AstCreator => private val logger = LoggerFactory.getLogger(getClass) diff --git a/src/main/scala/io/shiftleft/js2cpg/astcreation/AstNodeBuilder.scala b/src/main/scala/io/shiftleft/js2cpg/astcreation/AstNodeBuilder.scala index 6ccbdb632..9a79af905 100644 --- a/src/main/scala/io/shiftleft/js2cpg/astcreation/AstNodeBuilder.scala +++ b/src/main/scala/io/shiftleft/js2cpg/astcreation/AstNodeBuilder.scala @@ -1,39 +1,115 @@ package io.shiftleft.js2cpg.astcreation -import com.oracle.js.parser.ir._ -import io.shiftleft.codepropertygraph.generated.nodes._ +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.datastructures.OrderTracker +import io.shiftleft.js2cpg.datastructures.scope.MethodScope import io.shiftleft.js2cpg.passes.Defines import io.shiftleft.js2cpg.parser.JsSource -import io.shiftleft.js2cpg.parser.JsSource.shortenCode -import overflowdb.BatchedUpdate.DiffGraphBuilder +import io.joern.x2cpg.utils.NodeBuilders +import io.shiftleft.js2cpg.parser.JsSource.SourceMapOrigin +import org.apache.commons.lang3.StringUtils -class AstNodeBuilder( - private val diffGraph: DiffGraphBuilder, - private val astEdgeBuilder: AstEdgeBuilder, - private val source: JsSource, - private val scope: Scope -) { +trait AstNodeBuilder extends io.joern.x2cpg.AstNodeBuilder[Node, AstNodeBuilder] { this: AstCreator => - 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) + private val MinCodeLength: Int = 50 + private val DefaultMaxCodeLength: Int = 1000 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)) + override protected def line(node: Node): Option[Integer] = + source.lineFromSourceMap(node).map(java.lang.Integer.valueOf) + + override protected def lineEnd(node: Node): Option[Integer] = None // impossible with transpilation / source maps + + override protected def column(node: Node): Option[Integer] = + source.columnFromSourceMap(node).map(java.lang.Integer.valueOf) + + override protected def columnEnd(node: Node): Option[Integer] = None // impossible with transpilation / source maps + + /** @return + * the code of a node in the parsed file. If this file is the result of transpilation the original code is + * calculated from the corresponding sourcemap. Note: in this case, only the re-mapped starting line/column number + * are available. Hence, we extract only a fixed number of characters (max. until the end of the file). + */ + override protected def code(node: Node): String = codeFromSourceMap(node) + + private def codeFromSourceMap(node: Node): String = { + source.getSourceMap match { + case Some(SourceMapOrigin(_, Some(sourceMap), sourceWithLineNumbers)) => + val line = source.getLineOfSource(node.getStart) - 1 + val column = source.getColumnOfSource(node.getStart) + sourceMap.getMapping(line, column) match { + case null => + source.source.getString(node.getStart, node.getFinish - node.getStart) + case mapping => + val originLine = mapping.getSourceLine + val originColumn = mapping.getSourceColumn + val transpiledCodeLength = node.getFinish - node.getStart match { + // for some transpiled nodes the start and finish indices are wrong: + case 0 => node.toString.length + case other => other + } + sourceWithLineNumbers.get(originLine) match { + case Some(startingCodeLine) => + // Code from the origin source file was found. + val maxCodeLength = math.min(transpiledCodeLength, DefaultMaxCodeLength) + // We are extra careful: we do not want to generate empty lines. + // That can happen e.g., for synthetic return statements. + // Hence, we back up 1 char. + val startingCode = + startingCodeLine.substring(math.min(math.max(startingCodeLine.length - 1, 0), originColumn)) + calculateCode(sourceWithLineNumbers, startingCode, originLine, maxCodeLength) + case None => + // It has an actual mapping, but it is synthetic code not found in the source file. + // We return the synthetic code. + source.source.getString(node.getStart, node.getFinish - node.getStart) + } + } + case _ => + // No mapping at all. We return the node code. + source.source.getString(node.getStart, node.getFinish - node.getStart) + } + } + + /** Code field calculation: + * - We start with the re-mapped line/column number. + * - We always read at the length of the transpiled node (except if the original file ends earlier) capped at + * MAX_CODE_LENGTH. + * - If there would be more content we append ' [...]'. + */ + @scala.annotation.tailrec + private def calculateCode( + sourceWithLineNumbers: Map[Int, String], + currentLine: String, + currentLineNumber: Int, + transpiledCodeLength: Int + ): String = { + currentLine match { + case line if line.length >= transpiledCodeLength => + StringUtils.abbreviate(line, math.max(MinCodeLength, transpiledCodeLength - 1)) + case line if line.length < transpiledCodeLength && sourceWithLineNumbers.contains(currentLineNumber + 1) => + calculateCode( + sourceWithLineNumbers, + line + "\n" + sourceWithLineNumbers(currentLineNumber + 1), + currentLineNumber + 1, + transpiledCodeLength + ) + case line => + line.stripLineEnd + } } def createDependencyNode(name: String, groupId: String, version: String): NewDependency = { - val dependency = NewDependency() - .name(Option(name).getOrElse("")) - .dependencyGroupId(Option(groupId).getOrElse("")) - .version(Option(version).getOrElse("")) + val dependency = NodeBuilders.newDependencyNode( + Option(name).getOrElse(""), + Option(groupId).getOrElse(""), + Option(version).getOrElse("") + ) diffGraph.addNode(dependency) dependency } @@ -52,89 +128,69 @@ class AstNodeBuilder( 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) - + val param = parameterInNode( + lineAndColumnProvider, + name, + shortenCode(code), + orderTracker.order, + false, + EvaluationStrategies.BY_VALUE, + Defines.Any + ) diffGraph.addNode(param) orderTracker.inc() - astEdgeBuilder.addAstEdge(param, methodNode) + 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) - + val importedEntity = groupIdFromImportNode(importNode) + val code_ = importNode.toString().stripSuffix(";") + val node = newImportNode(code_, importedEntity, "", importNode) diffGraph.addNode(node) node } private def sanitizeCode(node: Node): String = node match { case _: ReturnNode => - source.getCode(node).stripSuffix(";") + code(node).stripSuffix(";") case _: BreakNode => - source.getCode(node).stripSuffix(";") + code(node).stripSuffix(";") case _: ContinueNode => - source.getCode(node).stripSuffix(";") + code(node).stripSuffix(";") case _: ErrorNode => // ErrorNode represents a runtime call; does not have a code representation "" case _ => - source.getCode(node) + code(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) - + val code = sanitizeCode(parserNode) + val unknown = unknownNode(parserNode, shortenCode(code)) diffGraph.addNode(unknown) unknown } def createTypeDeclNode( + node: Node, 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) + val typeDecl = typeDeclNode( + node, + name, + fullName, + source.filePath, + code(node), + astParentType, + astParentFullName, + inheritsFrom.toList + ) typeDecl } @@ -143,145 +199,94 @@ class AstNodeBuilder( 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) + val identifier = + identifierNode(lineAndColumnProvider, name, shortenCode(name), Defines.Any, 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) + val fieldIdentifier = fieldIdentifierNode(lineAndColumnProvider, name, shortenCode(name)) 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) - + def createFieldAccessCallNode(baseId: NewNode, partId: NewNode, node: Node): NewCall = { + val code = codeOf(baseId) + "." + codeOf(partId) + val call = callNode(node, code, Operators.fieldAccess, Operators.fieldAccess, DispatchTypes.STATIC_DISPATCH) + addAstEdge(baseId, call, 1) + addArgumentEdge(baseId, call, 1) + addAstEdge(partId, call, 2) + 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) - + def createStaticCallNode(code: String, methodName: String, fullName: String, node: Node): NewCall = { + val call = callNode( + node, + shortenCode(code), + methodName, + fullName, + DispatchTypes.STATIC_DISPATCH, + signature = Some(""), + typeFullName = Some(Defines.Any) + ) diffGraph.addNode(call) call } - def createEqualsCallNode(lhsId: NewNode, rhsId: NewNode, lineAndColumn: LineAndColumn): NewCall = { - val call = createCallNode( + def createEqualsCallNode(lhsId: NewNode, rhsId: NewNode, node: Node): NewCall = { + val call = callNode( + node, codeOf(lhsId) + " === " + codeOf(rhsId), Operators.equals, - DispatchTypes.STATIC_DISPATCH, - lineAndColumn + Operators.equals, + DispatchTypes.STATIC_DISPATCH ) - - astEdgeBuilder.addAstEdge(lhsId, call, 1) - astEdgeBuilder.addArgumentEdge(lhsId, call, 1) - - astEdgeBuilder.addAstEdge(rhsId, call, 2) - astEdgeBuilder.addArgumentEdge(rhsId, call, 2) - + addAstEdge(lhsId, call, 1) + addArgumentEdge(lhsId, call, 1) + addAstEdge(rhsId, call, 2) + addArgumentEdge(rhsId, call, 2) call } - def createIndexAccessNode(baseId: NewNode, indexId: NewNode, lineAndColumn: LineAndColumn): NewCall = { - val call = createCallNode( + def createIndexAccessNode(baseId: NewNode, indexId: NewNode, node: Node): NewCall = { + val call = callNode( + node, codeOf(baseId) + "[" + codeOf(indexId) + "]", Operators.indexAccess, - DispatchTypes.STATIC_DISPATCH, - lineAndColumn + Operators.indexAccess, + DispatchTypes.STATIC_DISPATCH ) - - astEdgeBuilder.addAstEdge(baseId, call, 1) - astEdgeBuilder.addArgumentEdge(baseId, call, 1) - - astEdgeBuilder.addAstEdge(indexId, call, 2) - astEdgeBuilder.addArgumentEdge(indexId, call, 2) + addAstEdge(baseId, call, 1) + addArgumentEdge(baseId, call, 1) + addAstEdge(indexId, call, 2) + addArgumentEdge(indexId, call, 2) call } def createAssignmentNode( destId: NewNode, sourceId: NewNode, - lineAndColumn: LineAndColumn, + node: Node, 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) + if (withParenthesis) { s"(${codeOf(destId)} = ${codeOf(sourceId)})" } + else { s"${codeOf(destId)} = ${codeOf(sourceId)}" } + } else { customCode } + val call = callNode(node, code, Operators.assignment, Operators.assignment, DispatchTypes.STATIC_DISPATCH) + addAstEdge(destId, call, 1) + addArgumentEdge(destId, call, 1) + addAstEdge(sourceId, call, 2) + 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) + def createLiteralNode(code: String, node: Node, dynamicTypeOption: Option[String]): NewLiteral = { + val literal = literalNode(node, shortenCode(code), Defines.Any, dynamicTypeOption.toList) diffGraph.addNode(literal) literal } @@ -299,44 +304,30 @@ class AstNodeBuilder( 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) + createFieldIdentifierNode(code(propertyNode.getKey), propertyNode.getKey) } } - def createTernaryNode(testId: NewNode, trueId: NewNode, falseId: NewNode, lineAndColumn: LineAndColumn): NewCall = { + def createTernaryNode(testId: NewNode, trueId: NewNode, falseId: NewNode, node: Node): 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) + val call = callNode(node, code, Operators.conditional, Operators.conditional, DispatchTypes.STATIC_DISPATCH) + addAstEdge(testId, call, 1) + addArgumentEdge(testId, call, 1) + addAstEdge(trueId, call, 2) + addArgumentEdge(trueId, call, 2) + addAstEdge(falseId, call, 3) + addArgumentEdge(falseId, call, 3) + call + } + def createCallNode(code: String, callName: String, dispatchType: String, node: Node): NewCall = { + val call = callNode(node, shortenCode(code), callName, callName, dispatchType, None, Some(Defines.Any)) diffGraph.addNode(call) call } def createFileNode(fileName: String): NewFile = { - val fileNode = NewFile() - .name(fileName) + val fileNode = NewFile().name(fileName) diffGraph.addNode(fileNode) fileNode } @@ -362,46 +353,20 @@ class AstNodeBuilder( } 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) + val methodRef = methodRefNode(functionNode, shortenCode(code), methodFullName, methodFullName) 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) + val typeRef = typeRefNode(classNode, shortenCode(code), typeFullName) 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) + val code = shortenCode(sanitizeCode(functionNode)) + val method = methodNode(functionNode, methodName, code, methodFullName, None, source.filePath) diffGraph.addNode(method) method } @@ -414,102 +379,60 @@ class AstNodeBuilder( } 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) + val code = if (keepWholeCode) { customCode.getOrElse(sanitizeCode(node)) } + else { shortenCode(customCode.getOrElse(sanitizeCode(node))) } + val block = blockNode(node, code, Defines.Any) 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) + def createMethodReturnNode(node: Node): NewMethodReturn = { + val ret = methodReturnNode(node, Defines.Any) diffGraph.addNode(ret) ret } def createTypeNode(name: String, fullName: String): NewType = { - val typ = NewType() - .name(name) - .fullName(fullName) - .typeDeclFullName(fullName) + val typ = NewType().name(name).fullName(fullName).typeDeclFullName(fullName) diffGraph.addNode(typ) typ } def createBindingNode(): NewBinding = { - val binding = NewBinding() - .name("") - .signature("") + 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())) + val name = if (caseNode.toString().startsWith("case")) "case" else "default" + val code = shortenCode(caseNode.toString()) + val jumpTarget = jumpTargetNode(caseNode, name, code, Some(caseNode.getClass.getSimpleName)) diffGraph.addNode(jumpTarget) jumpTarget } def createControlStructureNode(node: Node, controlStructureType: String): NewControlStructure = { - val controlStructure = NewControlStructure() - .controlStructureType(controlStructureType) - .code(shortenCode(source.getString(node))) + val controlStructure = controlStructureNode(node, controlStructureType, 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) + val member = memberNode(node, name, shortenCode(source.getString(node)), Defines.Any, 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) + val local = NodeBuilders.newLocalNode(shortenCode(name), typeFullName, 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) + val code = sanitizeCode(node) + val ret = returnNode(node, shortenCode(code)) diffGraph.addNode(ret) ret } diff --git a/src/main/scala/io/shiftleft/js2cpg/core/Config.scala b/src/main/scala/io/shiftleft/js2cpg/core/Config.scala index 240e0f1a5..1c199757f 100644 --- a/src/main/scala/io/shiftleft/js2cpg/core/Config.scala +++ b/src/main/scala/io/shiftleft/js2cpg/core/Config.scala @@ -1,5 +1,6 @@ package io.shiftleft.js2cpg.core +import io.joern.x2cpg.X2CpgConfig import io.shiftleft.js2cpg.io.FileDefaults import io.shiftleft.js2cpg.io.FileDefaults.VSIX_SUFFIX @@ -9,10 +10,8 @@ import io.shiftleft.js2cpg.preprocessing.TypescriptTranspiler import io.shiftleft.utils.IOUtils import scala.util.{Failure, Success, Try} -import scala.util.matching.Regex object Config { - val SL_IGNORE_FILE: String = ".slignore" val DEFAULT_TS_TYPES: Boolean = false val DEFAULT_TS_TRANSPILING: Boolean = true @@ -20,14 +19,10 @@ object Config { val DEFAULT_VUE_TRANSPILING: Boolean = true val DEFAULT_NUXT_TRANSPILING: Boolean = true val DEFAULT_TEMPLATE_TRANSPILING: Boolean = true - val DEFAULT_CPG_OUT_FILE: String = "cpg.bin.zip" - val DEFAULT_IGNORED_FILES_REGEX: Regex = "".r - val DEFAULT_IGNORED_FILES: Seq[Path] = Seq.empty val DEFAULT_IGNORE_MINIFIED: Boolean = true val DEFAULT_IGNORE_TESTS: Boolean = true val DEFAULT_IGNORE_PRIVATE_DEPS: Boolean = false val DEFAULT_PRIVATE_DEPS: Seq[String] = Seq.empty - val DEFAULT_INCLUDE_CONFIGS: Boolean = true val DEFAULT_INCLUDE_HTML: Boolean = true val DEFAULT_JVM_METRICS: Option[Int] = None val DEFAULT_MODULE_MODE: Option[String] = None @@ -37,59 +32,116 @@ object Config { } -case class Config( - srcDir: String = "", +final case class Config( tsTranspiling: Boolean = Config.DEFAULT_TS_TRANSPILING, babelTranspiling: Boolean = Config.DEFAULT_BABEL_TRANSPILING, vueTranspiling: Boolean = Config.DEFAULT_VUE_TRANSPILING, nuxtTranspiling: Boolean = Config.DEFAULT_NUXT_TRANSPILING, templateTranspiling: Boolean = Config.DEFAULT_TEMPLATE_TRANSPILING, packageJsonLocation: String = FileDefaults.PACKAGE_JSON_FILENAME, - outputFile: String = Config.DEFAULT_CPG_OUT_FILE, withTsTypes: Boolean = Config.DEFAULT_TS_TYPES, - ignoredFilesRegex: Regex = Config.DEFAULT_IGNORED_FILES_REGEX, - ignoredFiles: Seq[Path] = Config.DEFAULT_IGNORED_FILES, ignoreMinified: Boolean = Config.DEFAULT_IGNORE_MINIFIED, ignoreTests: Boolean = Config.DEFAULT_IGNORE_TESTS, ignorePrivateDeps: Boolean = Config.DEFAULT_IGNORE_PRIVATE_DEPS, privateDeps: Seq[String] = Config.DEFAULT_PRIVATE_DEPS, - includeConfigs: Boolean = Config.DEFAULT_INCLUDE_CONFIGS, includeHtml: Boolean = Config.DEFAULT_INCLUDE_HTML, jvmMetrics: Option[Int] = Config.DEFAULT_JVM_METRICS, moduleMode: Option[String] = Config.DEFAULT_MODULE_MODE, withNodeModuleFolder: Boolean = Config.DEFAULT_WITH_NODE_MODULES_FOLDER, optimizeDependencies: Boolean = Config.DEFAULT_OPTIMIZE_DEPENDENCIES, fixedTranspilationDependencies: Boolean = Config.DEFAULT_FIXED_TRANSPILATION_DEPENDENCIES -) { +) extends X2CpgConfig[Config] { def createPathForPackageJson(): Path = Paths.get(packageJsonLocation) match { case path if path.isAbsolute => path - case _ if srcDir.endsWith(VSIX_SUFFIX) => - Paths.get(srcDir, "extension", packageJsonLocation).toAbsolutePath.normalize() - case _ => Paths.get(srcDir, packageJsonLocation).toAbsolutePath.normalize() + case _ if inputPath.endsWith(VSIX_SUFFIX) => + Paths.get(inputPath, "extension", packageJsonLocation).toAbsolutePath.normalize() + case _ => Paths.get(inputPath, packageJsonLocation).toAbsolutePath.normalize() } - def createPathForIgnore(ignore: String): Path = { - val path = Paths.get(ignore) - if (path.isAbsolute) { - path - } else { - Paths.get(srcDir, ignore).toAbsolutePath.normalize() - } + def withTsTranspiling(value: Boolean): Config = { + copy(tsTranspiling = value).withInheritedFields(this) + } + + def withBabelTranspiling(value: Boolean): Config = { + copy(babelTranspiling = value).withInheritedFields(this) + } + + def withVueTranspiling(value: Boolean): Config = { + copy(vueTranspiling = value).withInheritedFields(this) + } + + def withNuxtTranspiling(value: Boolean): Config = { + copy(nuxtTranspiling = value).withInheritedFields(this) + } + + def withTemplateTranspiling(value: Boolean): Config = { + copy(templateTranspiling = value).withInheritedFields(this) + } + + def withPackageJsonLocation(value: String): Config = { + copy(packageJsonLocation = value).withInheritedFields(this) + } + + def withTsTypes(value: Boolean): Config = { + copy(withTsTypes = value).withInheritedFields(this) + } + + def withIgnoreMinified(value: Boolean): Config = { + copy(ignoreMinified = value).withInheritedFields(this) + } + + def withIgnoreTests(value: Boolean): Config = { + copy(ignoreTests = value).withInheritedFields(this) + } + + def withIgnorePrivateDeps(value: Boolean): Config = { + copy(ignorePrivateDeps = value).withInheritedFields(this) } - def withLoadedIgnores(): Config = { - val slIngoreFilePath = Paths.get(srcDir, Config.SL_IGNORE_FILE) + def withPrivateDeps(value: Seq[String]): Config = { + copy(privateDeps = value).withInheritedFields(this) + } + + def withIncludeHtml(value: Boolean): Config = { + copy(includeHtml = value).withInheritedFields(this) + } + + def withJvmMetrics(value: Option[Int]): Config = { + copy(jvmMetrics = value).withInheritedFields(this) + } + + def withModuleMode(value: Option[String]): Config = { + copy(moduleMode = value).withInheritedFields(this) + } + + def withNodeModuleFolder(value: Boolean): Config = { + copy(withNodeModuleFolder = value).withInheritedFields(this) + } + + def withOptimizeDependencies(value: Boolean): Config = { + copy(optimizeDependencies = value).withInheritedFields(this) + } + + def withFixedTranspilationDependencies(value: Boolean): Config = { + copy(fixedTranspilationDependencies = value).withInheritedFields(this) + } + + override def withInputPath(inputPath: String): Config = { + super.withInputPath(inputPath).withLoadedIgnores().withInheritedFields(this) + } + + private def withLoadedIgnores(): Config = { + val slIngoreFilePath = Paths.get(inputPath, Config.SL_IGNORE_FILE) Try(IOUtils.readLinesInFile(slIngoreFilePath)) match { - case Failure(_) => this - case Success(lines) => - this.copy(ignoredFiles = ignoredFiles ++ lines.map(createPathForIgnore)) + case Failure(_) => this + case Success(lines) => this.withIgnoredFiles(this.ignoredFiles ++ lines).withInheritedFields(this) } } override def toString: String = s""" - |\t- Source project: '$srcDir' + |\t- Source project: '$inputPath' |\t- package.json location: '${createPathForPackageJson()}' |\t- Module mode: '${moduleMode.getOrElse(TypescriptTranspiler.DefaultModule)}' |\t- Optimize dependencies: $optimizeDependencies @@ -99,10 +151,14 @@ case class Config( |\t- Vue.js transpiling: $vueTranspiling |\t- Nuxt.js transpiling: $nuxtTranspiling |\t- Template transpiling: $templateTranspiling + |\t- Ignored files: ${ignoredFiles + .filter(f => new File(f).isFile) + .map(f => s"${System.lineSeparator()}\t\t'$f'") + .mkString} |\t- Ignored files regex: '$ignoredFilesRegex' |\t- Ignored folders: ${ignoredFiles - .filter(f => new File(f.toString).isDirectory) - .map(f => s"${System.lineSeparator()}\t\t'${f.toString}'") + .filter(f => new File(f).isDirectory) + .map(f => s"${System.lineSeparator()}\t\t'$f'") .mkString} |\t- Ignore minified files: $ignoreMinified |\t- Ignore test files: $ignoreTests @@ -110,8 +166,7 @@ case class Config( |\t- Additional private dependencies: ${privateDeps .map(f => s"${System.lineSeparator()}\t\t'$f'") .mkString} - |\t- Include configuration files: $includeConfigs |\t- Include HTML files: $includeHtml - |\t- Output file: '$outputFile' + |\t- Output file: '$outputPath' |""".stripMargin } diff --git a/src/main/scala/io/shiftleft/js2cpg/core/Js2Cpg.scala b/src/main/scala/io/shiftleft/js2cpg/core/Js2Cpg.scala index d867d2b3c..a5e1b71b8 100644 --- a/src/main/scala/io/shiftleft/js2cpg/core/Js2Cpg.scala +++ b/src/main/scala/io/shiftleft/js2cpg/core/Js2Cpg.scala @@ -3,20 +3,23 @@ package io.shiftleft.js2cpg.core import java.nio.file.{Path, StandardCopyOption} import better.files.File import better.files.File.LinkOptions -import io.shiftleft.js2cpg.passes._ -import io.shiftleft.js2cpg.io.FileDefaults._ +import io.shiftleft.js2cpg.passes.* +import io.shiftleft.js2cpg.io.FileDefaults.* import io.shiftleft.js2cpg.io.FileUtils import io.shiftleft.js2cpg.parser.PackageJsonParser import io.shiftleft.js2cpg.preprocessing.NuxtTranspiler import io.shiftleft.js2cpg.preprocessing.TranspilationRunner import io.shiftleft.js2cpg.utils.MemoryMetrics -import io.joern.x2cpg.X2Cpg.newEmptyCpg +import io.joern.x2cpg.X2Cpg.withNewEmptyCpg import io.joern.x2cpg.utils.HashUtil +import io.joern.x2cpg.utils.Report +import io.joern.x2cpg.X2CpgFrontend +import io.shiftleft.codepropertygraph.Cpg import org.slf4j.LoggerFactory import scala.util.{Failure, Success, Try} -class Js2Cpg { +class Js2Cpg extends X2CpgFrontend[Config] { private val logger = LoggerFactory.getLogger(getClass) @@ -24,7 +27,7 @@ class Js2Cpg { private def checkCpgGenInputFiles(jsFiles: List[(Path, Path)], config: Config): Unit = { if (jsFiles.isEmpty) { - val project = File(config.srcDir) + val project = File(config.inputPath) logger.warn(s"'$project' contains no *.js files. No CPG was generated.") if (config.babelTranspiling) { logger.warn("\t- Babel transpilation did not yield any *.js files") @@ -101,7 +104,7 @@ class Js2Cpg { } ++ transpiledJsFiles } - private def prepareAndGenerateCpg(project: File, tmpProjectDir: File, config: Config): Unit = { + private def prepareAndGenerateCpg(project: File, tmpProjectDir: File, config: Config): Try[Cpg] = { val newTmpProjectDir = if (project.extension.contains(VSIX_SUFFIX)) { handleVsixProject(project, tmpProjectDir) } else { @@ -114,7 +117,9 @@ class Js2Cpg { .getFileTree(newTmpProjectDir.path, config, List(JS_SUFFIX, MJS_SUFFIX)) .map(f => (f, newTmpProjectDir.path)) - File.usingTemporaryDirectory("js2cpgTranspileOut") { tmpTranspileDir => + val result = for { + tmpTranspileDir <- File.temporaryDirectory("js2cpgTranspileOut") + } yield { findProjects(newTmpProjectDir, config) .foreach { p => val subDir = @@ -144,57 +149,55 @@ class Js2Cpg { // as we do not have any control of the transpilers themselves // (also they very much depend on the speed of npm). MemoryMetrics.withMemoryMetrics(config) { - generateCPG(config.copy(srcDir = newTmpProjectDir.toString), jsFiles) + generateCPG(config.withInputPath(newTmpProjectDir.toString), jsFiles) } } + result.get() } - def run(config: Config): Unit = { - val project = File(config.srcDir) + def createCpg(config: Config): Try[Cpg] = { + val project = File(config.inputPath) // We need to get the absolut project path here otherwise user configured // excludes based on either absolut or relative paths can not be matched. // We intentionally use .canonicalFile as this is using java.io.File#getCanonicalPath // under the hood that will resolve . or ../ and symbolic links in any combination. val absoluteProjectPath = project.canonicalFile.pathAsString - val configWithAbsolutProjectPath = config.copy(srcDir = absoluteProjectPath) + val configWithAbsolutProjectPath = config.withInputPath(absoluteProjectPath) logger.info(s"Generating CPG from Javascript sources in: '$absoluteProjectPath'") logger.debug(s"Configuration:$configWithAbsolutProjectPath") - File.usingTemporaryDirectory(project.name) { tmpProjectDir => - prepareAndGenerateCpg(project, tmpProjectDir, configWithAbsolutProjectPath) - } + val result = for { + tmpProjectDir <- File.temporaryDirectory(project.name) + } yield prepareAndGenerateCpg(project, tmpProjectDir, configWithAbsolutProjectPath) logger.info("Generation of CPG is complete.") report.print() + + result.get() } private def configFiles(config: Config, extensions: List[String]): List[(Path, Path)] = FileUtils - .getFileTree(File(config.srcDir).path, config, extensions, filterIgnoredFiles = false) - .map(f => (f, File(config.srcDir).path)) - - private def generateCPG(config: Config, jsFilesWithRoot: List[(Path, Path)]): Unit = { - val cpg = newEmptyCpg(Some(config.outputFile)) - val hash = HashUtil.sha256(jsFilesWithRoot.map(_._1)) - - new AstCreationPass(File(config.srcDir), jsFilesWithRoot, cpg, report).createAndApply() - new JsMetaDataPass(cpg, hash, config.srcDir).createAndApply() - new BuiltinTypesPass(cpg).createAndApply() - new DependenciesPass(cpg, config).createAndApply() - new ConfigPass(configFiles(config, List(VUE_SUFFIX)), cpg, report).createAndApply() - new PrivateKeyFilePass(configFiles(config, List(KEY_SUFFIX)), cpg, report).createAndApply() - - if (config.includeHtml) { - new ConfigPass(configFiles(config, List(HTML_SUFFIX)), cpg, report).createAndApply() - } - - if (config.includeConfigs) { + .getFileTree(File(config.inputPath).path, config, extensions, filterIgnoredFiles = false) + .map(f => (f, File(config.inputPath).path)) + + private def generateCPG(config: Config, jsFilesWithRoot: List[(Path, Path)]): Try[Cpg] = { + withNewEmptyCpg(config.outputPath, config) { (cpg, config) => + val hash = HashUtil.sha256(jsFilesWithRoot.map(_._1)) + + new AstCreationPass(cpg, jsFilesWithRoot, config, report).createAndApply() + new JsMetaDataPass(cpg, hash, config.inputPath).createAndApply() + new BuiltinTypesPass(cpg).createAndApply() + new DependenciesPass(cpg, config).createAndApply() + new ConfigPass(configFiles(config, List(VUE_SUFFIX)), cpg, report).createAndApply() + new PrivateKeyFilePass(configFiles(config, List(KEY_SUFFIX)), cpg, report).createAndApply() + if (config.includeHtml) { + new ConfigPass(configFiles(config, List(HTML_SUFFIX)), cpg, report).createAndApply() + } new ConfigPass(configFiles(config, CONFIG_FILES), cpg, report).createAndApply() } - - cpg.close() } } diff --git a/src/main/scala/io/shiftleft/js2cpg/core/Js2CpgMain.scala b/src/main/scala/io/shiftleft/js2cpg/core/Js2CpgMain.scala index 24f957d14..d95105169 100644 --- a/src/main/scala/io/shiftleft/js2cpg/core/Js2CpgMain.scala +++ b/src/main/scala/io/shiftleft/js2cpg/core/Js2CpgMain.scala @@ -1,16 +1,18 @@ package io.shiftleft.js2cpg.core -object Js2CpgMain { - def main(args: Array[String]): Unit = { - val argumentsParser: Js2cpgArgumentsParser = new Js2cpgArgumentsParser() +import Js2cpgArgumentsParser.* +import io.joern.x2cpg.X2CpgMain +import io.joern.x2cpg.utils.Environment - argumentsParser.parse(args) match { - case Some(config) => - new Js2Cpg().run(config) - case None => - argumentsParser.showUsage() - System.exit(1) - } +import java.nio.file.Paths +object Js2CpgMain extends X2CpgMain(parser, new Js2Cpg()) { + def run(config: Config, js2cpg: Js2Cpg): Unit = { + val absPath = Paths.get(config.inputPath).toAbsolutePath.toString + if (Environment.pathExists(absPath)) { + js2cpg.run(config.withInputPath(absPath)) + } else { + System.exit(1) + } } } diff --git a/src/main/scala/io/shiftleft/js2cpg/core/Js2cpgArgumentsParser.scala b/src/main/scala/io/shiftleft/js2cpg/core/Js2cpgArgumentsParser.scala index a685bec66..297679f3b 100644 --- a/src/main/scala/io/shiftleft/js2cpg/core/Js2cpgArgumentsParser.scala +++ b/src/main/scala/io/shiftleft/js2cpg/core/Js2cpgArgumentsParser.scala @@ -1,21 +1,14 @@ package io.shiftleft.js2cpg.core import io.shiftleft.js2cpg.io.FileDefaults -import io.shiftleft.js2cpg.io.FileDefaults.VSIX_SUFFIX - import java.io.File -import io.shiftleft.js2cpg.parser.PackageJsonParser import io.shiftleft.js2cpg.preprocessing.TypescriptTranspiler -import scopt.OptionParser +import scopt.OParser object Js2cpgArgumentsParser { - val HELP: String = "help" + implicit val defaultConfig: Config = Config() val VERSION: String = "version" - val SRCDIR: String = "" - val OUTPUT: String = "output" val WITH_TS_TYPES: String = "with-typescript-types" - val EXCLUDE: String = "exclude" - val EXCLUDE_REGEX: String = "exclude-regex" val PACKAGE_JSON: String = "package-json" val NO_TS: String = "no-ts" val TS: String = "ts" @@ -42,11 +35,6 @@ object Js2cpgArgumentsParser { val OPTIMIZE_DEPENDENCIES: String = "optimize-dependencies" val ALL_DEPENDENCIES: String = "all-dependencies" val FIXED_TRANSPILATION_DEPENDENCIES: String = "fixed-transpilation-dependencies" -} - -class Js2cpgArgumentsParser { - - import Js2cpgArgumentsParser._ private lazy val banner: String = """ @@ -58,156 +46,130 @@ class Js2cpgArgumentsParser { | ╚════╝ ╚══════╝╚══════╝ ╚═════╝╚═╝ ╚═════╝ """.stripMargin - private val parser: OptionParser[Config] = new OptionParser[Config]("js2cpg.sh") { - help(HELP).text("prints this usage text") - head(s""" + val parser: OParser[Unit, Config] = { + val builder = OParser.builder[Config] + import builder.* + OParser.sequence( + programName("js2cpg"), + head(s""" |$banner |js2cpg version "${io.shiftleft.js2cpg.core.BuildInfo.version}" - |""".stripMargin) - version(VERSION) - .text("print js2cpg version and exit") - arg[String](SRCDIR) - .required() - .text("directory containing Javascript code or the path to a *.vsix file") - .action((x, c) => c.copy(srcDir = x).withLoadedIgnores()) - .validate(path => { - val f = new File(path) - if (f.exists() && (f.isDirectory || f.toString.endsWith(VSIX_SUFFIX))) success - else failure(s"Invalid $SRCDIR path: '$path'") - }) - opt[String](PACKAGE_JSON) - .text( - s"path to the projects package.json (path relative to $SRCDIR or absolute path; defaults to '$SRCDIR${java.io.File.separator}${FileDefaults.PACKAGE_JSON_FILENAME}')" - ) - .action((x, c) => c.copy(packageJsonLocation = x)) - .validate(path => { - val f = new File(path) - if (f.exists() && !f.isDirectory) success - else failure(s"File '$path' does not exist or is a directory") - }) - opt[String](OUTPUT) - .text(s"CPG output file name (defaults to '${Config.DEFAULT_CPG_OUT_FILE}')") - .action((x, c) => c.copy(outputFile = x)) - .validate(x => - if (x.isEmpty) { - failure("Output file cannot be empty") - } else if (!new File(x).getAbsoluteFile.getParentFile.exists()) { - failure("Directory of the output file does not exist") - } else success - ) - opt[Unit](NO_TS) - .text("disables transpiling Typescript files to Javascript") - .action((_, c) => c.copy(tsTranspiling = false)) - opt[Unit](NO_BABEL) - .text("disables transpiling Javascript files with Babel") - .action((_, c) => c.copy(babelTranspiling = false)) - opt[Unit](NO_VUE) - .text("disables transpiling Vue.js files") - .action((_, c) => c.copy(vueTranspiling = false)) - opt[Unit](NO_NUXT) - .text("disables Nuxt.js transpiling") - .action((_, c) => c.copy(nuxtTranspiling = false)) - opt[Unit](NO_TEMPLATES) - .text("disables transpiling EJS or Pug template files") - .action((_, c) => c.copy(templateTranspiling = false)) - // for backwards compatibility - has no effect: - opt[Unit](TRANSPILING) - .text("enables transpiling Typescript files to Javascript") - .hidden() // deprecated - // for backwards compatibility - has no effect: - opt[Unit](BABEL) - .text("enables transpiling Javascript files with Babel") - .hidden() - // for backwards compatibility - has no effect: - opt[Unit](TS) - .text("enables transpiling Typescript files to Javascript") - .hidden() - opt[Unit](WITH_NODE_MODULES_FOLDER) - .text(s"include the node_module folder (defaults to '${Config.DEFAULT_WITH_NODE_MODULES_FOLDER}')") - .action((_, c) => c.copy(withNodeModuleFolder = true)) - .hidden() - opt[Unit](WITH_TS_TYPES) - .text(s"query types via Typescript; needs a `package.json` (defaults to '${Config.DEFAULT_TS_TYPES}')") - .action((_, c) => c.copy(withTsTypes = true)) - .hidden() // deprecated - opt[Seq[String]](EXCLUDE) - .valueName(",,...") - .action((x, c) => c.copy(ignoredFiles = c.ignoredFiles ++ x.map(c.createPathForIgnore))) - .text("files to exclude during CPG generation (paths relative to or absolute paths)") - opt[String](EXCLUDE_REGEX) - .action((x, c) => c.copy(ignoredFilesRegex = x.r)) - .text("a regex specifying files to exclude during CPG generation (the absolute file path is matched)") - // for backwards compatibility - has no effect: - opt[Unit](IGNORE_MINIFIED) - .text("ignore minified Javascript files (filename ending with '-min.js', '.min.js', or 'bundle.js')") - .hidden() // deprecated - opt[Unit](WITH_MINIFIED) - .action((_, c) => c.copy(ignoreMinified = false)) - .hidden() // deprecated - .text("include minified Javascript files (filename ending with '-min.js', '.min.js', or 'bundle.js')") - opt[Unit](INCLUDE_MINIFIED) - .action((_, c) => c.copy(ignoreMinified = false)) - .text("include minified Javascript files (filename ending with '-min.js', '.min.js', or 'bundle.js')") - opt[Unit](WITH_TESTS) - .action((_, c) => c.copy(ignoreTests = false)) - .hidden() // deprecated - .text("include test files") - opt[Unit](INCLUDE_TESTS) - .action((_, c) => c.copy(ignoreTests = false)) - .text("include test files") - opt[Unit](IGNORE_PRIVATE_DEPS) - .text( - s"ignores private modules/dependencies in 'node_modules/' (defaults to '${Config.DEFAULT_IGNORE_PRIVATE_DEPS}')" - ) - .action((_, c) => c.copy(ignorePrivateDeps = true)) - .hidden() - opt[Unit](EXCLUDE_PRIVATE_DEPS) - .text( - s"excludes private modules/dependencies in 'node_modules/' (defaults to '${Config.DEFAULT_IGNORE_PRIVATE_DEPS}')" - ) - .action((_, c) => c.copy(ignorePrivateDeps = true)) - opt[Seq[String]](PRIVATE_DEPS) - .valueName(",,...") - .action((x, c) => c.copy(privateDeps = c.privateDeps ++ x.flatMap(d => Seq(d, s"@$d")))) - .text(s"additional private dependencies to be analyzed from '${FileDefaults.NODE_MODULES_DIR_NAME}/'") - opt[Unit](INCLUDE_CONFIGS) - .text("include configuration files (*.conf.js, *.config.js, *.json)") - .hidden() // deprecated, it is the default - opt[Unit](INCLUDE_HTML) - .text("include HTML files (*.html)") - .hidden() // deprecated, it is the default - opt[Unit](EXCLUDE_HTML) - .text("excludes HTML files (*.html)") - .action((_, c) => c.copy(includeHtml = false)) - opt[Unit](OPTIMIZE_DEPENDENCIES) - .text( - s"optimize project dependencies during transpilation (defaults to '${Config.DEFAULT_OPTIMIZE_DEPENDENCIES}')" - ) - .hidden() // deprecated, it is the default - opt[Unit](ALL_DEPENDENCIES) - .text( - s"install all project dependencies during transpilation (defaults to '${!Config.DEFAULT_OPTIMIZE_DEPENDENCIES}')" - ) - .action((_, c) => c.copy(optimizeDependencies = false)) - opt[Unit](FIXED_TRANSPILATION_DEPENDENCIES) - .text( - s"install fixed versions of transpilation dependencies during transpilation (defaults to '${!Config.DEFAULT_FIXED_TRANSPILATION_DEPENDENCIES}')" - ) - .action((_, c) => c.copy(fixedTranspilationDependencies = true)) - opt[Int](JVM_MONITOR) - .text("enable JVM metrics logging (requires JMX port number)") - .action((jmxPortNumber, c) => c.copy(jvmMetrics = Some(jmxPortNumber))) - .hidden() - opt[String](MODULE_MODE) - .text( - s"set the module mode for transpiling (default is '${TypescriptTranspiler.DefaultModule}', alternatives are e.g., esnext or es2015)" - ) - .action((module, c) => c.copy(moduleMode = Some(module))) - .hidden() + |""".stripMargin), + version(VERSION) + .text("print js2cpg version and exit"), + opt[String](PACKAGE_JSON) + .text( + s"path to the projects package.json (path relative to or absolute path; defaults to '${java.io.File.separator}${FileDefaults.PACKAGE_JSON_FILENAME}')" + ) + .action((x, c) => c.withPackageJsonLocation(x)) + .validate(path => { + val f = new File(path) + if (f.exists() && !f.isDirectory) success + else failure(s"File '$path' does not exist or is a directory") + }), + opt[Unit](NO_TS) + .text("disables transpiling Typescript files to Javascript") + .action((_, c) => c.withTsTranspiling(false)), + opt[Unit](NO_BABEL) + .text("disables transpiling Javascript files with Babel") + .action((_, c) => c.withBabelTranspiling(false)), + opt[Unit](NO_VUE) + .text("disables transpiling Vue.js files") + .action((_, c) => c.withVueTranspiling(false)), + opt[Unit](NO_NUXT) + .text("disables Nuxt.js transpiling") + .action((_, c) => c.withNuxtTranspiling(false)), + opt[Unit](NO_TEMPLATES) + .text("disables transpiling EJS or Pug template files") + .action((_, c) => c.withTemplateTranspiling(false)), + // for backwards compatibility - has no effect: + opt[Unit](TRANSPILING) + .text("enables transpiling Typescript files to Javascript") + .hidden(), // deprecated + // for backwards compatibility - has no effect: + opt[Unit](BABEL) + .text("enables transpiling Javascript files with Babel") + .hidden(), + // for backwards compatibility - has no effect: + opt[Unit](TS) + .text("enables transpiling Typescript files to Javascript") + .hidden(), + opt[Unit](WITH_NODE_MODULES_FOLDER) + .text(s"include the node_module folder (defaults to '${Config.DEFAULT_WITH_NODE_MODULES_FOLDER}')") + .action((_, c) => c.withNodeModuleFolder(true)) + .hidden(), + opt[Unit](WITH_TS_TYPES) + .text(s"query types via Typescript; needs a `package.json` (defaults to '${Config.DEFAULT_TS_TYPES}')") + .action((_, c) => c.withTsTypes(true)) + .hidden(), // deprecated + // for backwards compatibility - has no effect: + opt[Unit](IGNORE_MINIFIED) + .text("ignore minified Javascript files (filename ending with '-min.js', '.min.js', or 'bundle.js')") + .hidden(), // deprecated + opt[Unit](WITH_MINIFIED) + .action((_, c) => c.withIgnoreMinified(false)) + .text("include minified Javascript files (filename ending with '-min.js', '.min.js', or 'bundle.js')") + .hidden(), // deprecated + opt[Unit](INCLUDE_MINIFIED) + .action((_, c) => c.withIgnoreMinified(false)) + .text("include minified Javascript files (filename ending with '-min.js', '.min.js', or 'bundle.js')"), + opt[Unit](WITH_TESTS) + .action((_, c) => c.withIgnoreTests(false)) + .text("include test files") + .hidden(), // deprecated + opt[Unit](INCLUDE_TESTS) + .action((_, c) => c.withIgnoreTests(false)) + .text("include test files"), + opt[Unit](IGNORE_PRIVATE_DEPS) + .text( + s"ignores private modules/dependencies in 'node_modules/' (defaults to '${Config.DEFAULT_IGNORE_PRIVATE_DEPS}')" + ) + .action((_, c) => c.withIgnorePrivateDeps(true)) + .hidden(), + opt[Unit](EXCLUDE_PRIVATE_DEPS) + .text( + s"excludes private modules/dependencies in 'node_modules/' (defaults to '${Config.DEFAULT_IGNORE_PRIVATE_DEPS}')" + ) + .action((_, c) => c.withIgnorePrivateDeps(true)), + opt[Seq[String]](PRIVATE_DEPS) + .valueName(",,...") + .action((x, c) => c.withPrivateDeps(c.privateDeps ++ x.flatMap(d => Seq(d, s"@$d")))) + .text(s"additional private dependencies to be analyzed from '${FileDefaults.NODE_MODULES_DIR_NAME}/'"), + opt[Unit](INCLUDE_CONFIGS) + .text("include configuration files (*.conf.js, *.config.js, *.json)") + .hidden(), // deprecated, it is the default + opt[Unit](INCLUDE_HTML) + .text("include HTML files (*.html)") + .hidden(), // deprecated, it is the default + opt[Unit](EXCLUDE_HTML) + .text("excludes HTML files (*.html)") + .action((_, c) => c.withIncludeHtml(false)), + opt[Unit](OPTIMIZE_DEPENDENCIES) + .text( + s"optimize project dependencies during transpilation (defaults to '${Config.DEFAULT_OPTIMIZE_DEPENDENCIES}')" + ) + .hidden(), // deprecated, it is the default + opt[Unit](ALL_DEPENDENCIES) + .text( + s"install all project dependencies during transpilation (defaults to '${!Config.DEFAULT_OPTIMIZE_DEPENDENCIES}')" + ) + .action((_, c) => c.withOptimizeDependencies(false)), + opt[Unit](FIXED_TRANSPILATION_DEPENDENCIES) + .text( + s"install fixed versions of transpilation dependencies during transpilation (defaults to '${!Config.DEFAULT_FIXED_TRANSPILATION_DEPENDENCIES}')" + ) + .action((_, c) => c.withFixedTranspilationDependencies(true)), + opt[Int](JVM_MONITOR) + .text("enable JVM metrics logging (requires JMX port number)") + .action((jmxPortNumber, c) => c.withJvmMetrics(Some(jmxPortNumber))) + .hidden(), + opt[String](MODULE_MODE) + .text( + s"set the module mode for transpiling (default is '${TypescriptTranspiler.DefaultModule}', alternatives are e.g., esnext or es2015)" + ) + .action((module, c) => c.withModuleMode(Some(module))) + .hidden() + ) } - def parse(args: Array[String]): Option[Config] = parser.parse(args, Config()) - - def showUsage(): Unit = println(parser.usage) - } diff --git a/src/main/scala/io/shiftleft/js2cpg/core/Report.scala b/src/main/scala/io/shiftleft/js2cpg/core/Report.scala deleted file mode 100644 index ab967ec20..000000000 --- a/src/main/scala/io/shiftleft/js2cpg/core/Report.scala +++ /dev/null @@ -1,105 +0,0 @@ -package io.shiftleft.js2cpg.core - -import io.shiftleft.js2cpg.utils.TimeUtils -import org.slf4j.LoggerFactory - -import scala.collection.concurrent.TrieMap -import scala.concurrent.duration.Duration - -object Report { - - private val logger = LoggerFactory.getLogger(Report.getClass) - - private type FileName = String - - private type Reports = TrieMap[FileName, ReportEntry] - - private case class ReportEntry( - loc: Long, - parsed: Boolean, - cpgGen: Boolean, - duration: Long, - isConfig: Boolean = false - ) { - def toSeq: Seq[String] = { - val lines = loc.toString - val dur = if (duration == 0) "-" else TimeUtils.pretty(Duration.fromNanos(duration)) - val es6 = if (parsed) "yes" else "no" - val cpg = if (cpgGen) "yes" else "no" - Seq(lines, es6, cpg, dur) - } - } - -} - -class Report { - - import Report._ - - private val reports: Reports = TrieMap.empty - - def print(): Unit = { - - def formatTable(table: Seq[Seq[String]]): String = { - if (table.isEmpty) "" - else { - // Get column widths based on the maximum cell width in each column (+2 for a one character padding on each side) - val colWidths = - table.transpose.map(_.map(cell => if (cell == null) 0 else cell.length).max + 2) - // Format each row - val rows = table.map( - _.zip(colWidths) - .map { case (item, size) => (" %-" + (size - 1) + "s").format(item) } - .mkString("|", "|", "|") - ) - // Formatted separator row, used to separate the header and draw table borders - val separator = colWidths.map("-" * _).mkString("+", "+", "+") - // Put the table together and return - val header = rows.head - val content = rows.tail.take(rows.tail.size - 1) - val footer = rows.tail.last - (separator +: header +: separator +: content :+ separator :+ footer :+ separator) - .mkString("\n") - } - } - - val rows = reports.toSeq - .sortBy(_._1) - .zipWithIndex - .view - .map { - case ((file, sum), index) if sum.isConfig => - s"${index + 1}" +: s"$file (config file)" +: sum.toSeq - case ((file, sum), index) => s"${index + 1}" +: file +: sum.toSeq - } - .toSeq - val numOfReports = reports.size - val header = Seq(Seq("#", "File", "LOC", "Parsed", "Got a CPG", "CPG Gen. Duration")) - val footer = Seq( - Seq( - "Total", - "", - s"${reports.map(_._2.loc).sum}", - s"${reports.count(_._2.parsed)}/$numOfReports", - s"${reports.count(_._2.cpgGen)}/$numOfReports", - "" - ) - ) - val table = header ++ rows ++ footer - logger.info(s"Report:${System.lineSeparator()}" + formatTable(table)) - } - - def addReportInfo( - fileName: FileName, - loc: Long, - parsed: Boolean = false, - cpgGen: Boolean = false, - duration: Long = 0, - isConfig: Boolean = false - ): Unit = - reports(fileName) = ReportEntry(loc, parsed, cpgGen, duration, isConfig) - - def updateReportDuration(fileName: FileName, duration: Long): Unit = - reports.updateWith(fileName)(_.map(_.copy(cpgGen = true, duration = duration))) - -} diff --git a/src/main/scala/io/shiftleft/js2cpg/datastructures/LineAndColumn.scala b/src/main/scala/io/shiftleft/js2cpg/datastructures/LineAndColumn.scala deleted file mode 100644 index ef66b86d0..000000000 --- a/src/main/scala/io/shiftleft/js2cpg/datastructures/LineAndColumn.scala +++ /dev/null @@ -1,3 +0,0 @@ -package io.shiftleft.js2cpg.datastructures - -case class LineAndColumn(line: Option[Int], column: Option[Int]) diff --git a/src/main/scala/io/shiftleft/js2cpg/io/FileUtils.scala b/src/main/scala/io/shiftleft/js2cpg/io/FileUtils.scala index bc747d8c4..dd7ab1d72 100644 --- a/src/main/scala/io/shiftleft/js2cpg/io/FileUtils.scala +++ b/src/main/scala/io/shiftleft/js2cpg/io/FileUtils.scala @@ -165,7 +165,7 @@ object FileUtils { (positionToLineNumber, positionToFirstPositionInLine) } - final case class FileStatistics(linesOfCode: Long, longestLineLength: Int, containsMarker: Boolean) + final case class FileStatistics(linesOfCode: Int, longestLineLength: Int, containsMarker: Boolean) private def createDecoder(): CharsetDecoder = Codec.UTF8.decoder @@ -173,7 +173,7 @@ object FileUtils { .onUnmappableCharacter(CodingErrorAction.REPLACE) def fileStatistics(lines: Iterator[String]): FileStatistics = { - var linesOfCode = 0L + var linesOfCode = 0 var longestLineLength = 0 var containsMarker = false diff --git a/src/main/scala/io/shiftleft/js2cpg/io/JsFileChecks.scala b/src/main/scala/io/shiftleft/js2cpg/io/JsFileChecks.scala index 1130ee02f..d670197a6 100644 --- a/src/main/scala/io/shiftleft/js2cpg/io/JsFileChecks.scala +++ b/src/main/scala/io/shiftleft/js2cpg/io/JsFileChecks.scala @@ -1,9 +1,7 @@ package io.shiftleft.js2cpg.io -import io.shiftleft.js2cpg.core.Js2cpgArgumentsParser import io.shiftleft.js2cpg.io.FileDefaults._ import io.shiftleft.js2cpg.io.FileUtils.FileStatistics -import io.shiftleft.utils.IOUtils import org.slf4j.LoggerFactory import java.nio.file.Path @@ -14,15 +12,13 @@ object JsFileChecks { private val logger = LoggerFactory.getLogger(JsFileChecks.getClass) private def printPerformanceHints(relPath: String, reasons: Seq[String]): Unit = { - logger.debug( - s"""The file '$relPath' may have negative impact on the analyzing performance! + logger.debug(s"""The file '$relPath' may have negative impact on the analyzing performance! | ${if (reasons.length > 1) "Reasons:" else "Reason:"} | ${reasons.mkString(System.lineSeparator())} | Please check if: | \t- this file is the result of your build process | \t- this file is the result of applying transpilation tools (e.g., Typescript, Emscripten) - | You might want to exclude this file when running js2cpg by adding it to '--${Js2cpgArgumentsParser.EXCLUDE}'.""".stripMargin - ) + | You might want to exclude this file when running js2cpg by adding it to '--exclude'.""".stripMargin) } def isMinifiedFile(path: String, fileStatistics: FileStatistics): Boolean = { diff --git a/src/main/scala/io/shiftleft/js2cpg/io/PathFilter.scala b/src/main/scala/io/shiftleft/js2cpg/io/PathFilter.scala index 3c45694d3..944e7d5f5 100644 --- a/src/main/scala/io/shiftleft/js2cpg/io/PathFilter.scala +++ b/src/main/scala/io/shiftleft/js2cpg/io/PathFilter.scala @@ -18,10 +18,10 @@ case class PathFilter( private val logger = LoggerFactory.getLogger(PathFilter.getClass) - private val projectDir: String = Paths.get(config.srcDir).toAbsolutePath.toString + private val projectDir: String = Paths.get(config.inputPath).toAbsolutePath.toString private def shouldBeIgnoredByUserConfig(filePath: Path, config: Config): Boolean = - config.ignoredFiles.contains(filePath) || config.ignoredFilesRegex.matches(filePath.toString) + config.ignoredFiles.contains(filePath.toString) || config.ignoredFilesRegex.matches(filePath.toString) private def acceptFromNodeModulesFolder(path: Path): Boolean = withNodeModuleFolder && (".*" + NODE_MODULES_DIR_NAME + ".*").r.matches(path.toString) @@ -34,7 +34,7 @@ case class PathFilter( if IGNORED_FOLDERS_REGEX.exists(_.matches(File(dirPath).name)) && !acceptFromNodeModulesFolder(dirPath) => Rejected(relDir, "folder ignored by default") - case dirPath if config.ignoredFiles.exists(i => dirPath.toString.startsWith(i.toString)) => + case dirPath if config.ignoredFiles.exists(i => dirPath.toString.startsWith(i)) => Rejected(relDir, "folder ignored by user configuration") case _ => Accepted() diff --git a/src/main/scala/io/shiftleft/js2cpg/parser/FreshJsonParser.scala b/src/main/scala/io/shiftleft/js2cpg/parser/FreshJsonParser.scala index dfb8d0f6c..f5705b795 100644 --- a/src/main/scala/io/shiftleft/js2cpg/parser/FreshJsonParser.scala +++ b/src/main/scala/io/shiftleft/js2cpg/parser/FreshJsonParser.scala @@ -33,7 +33,7 @@ object FreshJsonParser { def findImportMapPaths(config: Config): Set[Path] = { val objectMapper = new ObjectMapper FileUtils - .getFileTree(Paths.get(config.srcDir), config, List(".json")) + .getFileTree(Paths.get(config.inputPath), config, List(".json")) .filter(_.endsWith(TypescriptTranspiler.DenoConfig)) .flatMap { file => val packageJson = objectMapper.readTree(IOUtils.readLinesInFile(file).mkString) diff --git a/src/main/scala/io/shiftleft/js2cpg/parser/JavaScriptParser.scala b/src/main/scala/io/shiftleft/js2cpg/parser/JavaScriptParser.scala index e6e1a65ba..534d6b286 100644 --- a/src/main/scala/io/shiftleft/js2cpg/parser/JavaScriptParser.scala +++ b/src/main/scala/io/shiftleft/js2cpg/parser/JavaScriptParser.scala @@ -74,7 +74,7 @@ object JavaScriptParser { } private def nodeJsFix(jsSource: JsSource): JsSource = { - val lines = jsSource.source.getContent.toString.linesIterator.toSeq + val lines = jsSource.source.getContent.linesIterator.toSeq val replaceIndex = lines.lastIndexWhere(l => importRegex.matches(l.trim())) + 1 val (head, rest) = lines.splitAt(replaceIndex) val fixedCode = (head ++ nodeJsFixWrapper(rest)).mkString("\n") diff --git a/src/main/scala/io/shiftleft/js2cpg/parser/JsSource.scala b/src/main/scala/io/shiftleft/js2cpg/parser/JsSource.scala index 7c6afada9..1a30329f5 100644 --- a/src/main/scala/io/shiftleft/js2cpg/parser/JsSource.scala +++ b/src/main/scala/io/shiftleft/js2cpg/parser/JsSource.scala @@ -5,30 +5,29 @@ import better.files.File import com.atlassian.sourcemap.{ReadableSourceMap, ReadableSourceMapImpl} import com.oracle.js.parser.Source import com.oracle.js.parser.ir.Node -import io.shiftleft.js2cpg.io.FileDefaults._ +import io.shiftleft.js2cpg.io.FileDefaults.* import io.shiftleft.js2cpg.io.FileUtils import io.shiftleft.js2cpg.preprocessing.NuxtTranspiler import io.shiftleft.utils.IOUtils -import org.apache.commons.lang3.StringUtils + import org.slf4j.LoggerFactory -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.util.{Failure, Success, Try} object JsSource { private val logger = LoggerFactory.getLogger(getClass) - // maximum length of re-mapped code fields after transpilation in number of characters - private val MAX_CODE_LENGTH: Int = 1000 - private val MIN_CODE_LENGTH: Int = 50 - - def shortenCode(code: String, length: Int = MAX_CODE_LENGTH): String = - StringUtils.abbreviate(code, math.max(MIN_CODE_LENGTH, length)) + case class SourceMapOrigin( + sourceFilePath: Path, + sourceMap: Option[ReadableSourceMap], + sourceWithLineNumbers: Map[Int, String] + ) } -class JsSource(val srcDir: File, val projectDir: Path, val source: Source) { +case class JsSource(srcDir: File, projectDir: Path, source: Source) { import JsSource._ @@ -36,40 +35,17 @@ class JsSource(val srcDir: File, val projectDir: Path, val source: Source) { private val mapFilePath = absoluteFilePath + ".map" private val sourceMap = sourceMapOrigin() + def getSourceMap: Option[SourceMapOrigin] = sourceMap + private val (positionToLineNumberMapping, positionToFirstPositionInLineMapping) = FileUtils.positionLookupTables(source.getContent) - private case class SourceMapOrigin( - sourceFilePath: Path, - sourceMap: Option[ReadableSourceMap], - sourceWithLineNumbers: Map[Int, String] - ) - /** @return * the file path of the parsed file. If this file is the result of transpilation the original source file path is * calculated from the corresponding sourcemap. */ def filePath: String = filePathFromSourceMap - /** @return - * the line number of a node in the parsed file. If this file is the result of transpilation the original line - * number is calculated from the corresponding sourcemap. - */ - def getLine(node: Node): Option[Int] = lineFromSourceMap(node) - - /** @return - * the column number of a node in the parsed file. If this file is the result of transpilation the original column - * number is calculated from the corresponding sourcemap. - */ - def getColumn(node: Node): Option[Int] = columnFromSourceMap(node) - - /** @return - * the code of a node in the parsed file. If this file is the result of transpilation the original code is - * calculated from the corresponding sourcemap. Note: in this case, only the re-mapped starting line/column number - * are available. Hence, we extract only a fixed number of characters (max. until the end of the file). - */ - def getCode(node: Node): String = codeFromSourceMap(node) - def getString(node: Node): String = source.getString(node.getToken) /** @return @@ -165,72 +141,7 @@ class JsSource(val srcDir: File, val projectDir: Path, val source: Source) { } } - private def codeFromSourceMap(node: Node): String = { - sourceMap match { - case Some(SourceMapOrigin(_, Some(sourceMap), sourceWithLineNumbers)) => - val line = getLineOfSource(node.getStart) - 1 - val column = getColumnOfSource(node.getStart) - sourceMap.getMapping(line, column) match { - case null => - source.getString(node.getStart, node.getFinish - node.getStart) - case mapping => - val originLine = mapping.getSourceLine - val originColumn = mapping.getSourceColumn - val transpiledCodeLength = node.getFinish - node.getStart match { - // for some transpiled nodes the start and finish indices are wrong: - case 0 => node.toString.length - case other => other - } - sourceWithLineNumbers.get(originLine) match { - case Some(startingCodeLine) => - // Code from the origin source file was found. - val maxCodeLength = math.min(transpiledCodeLength, MAX_CODE_LENGTH) - // We are extra careful: we do not want to generate empty lines. - // That can happen e.g., for synthetic return statements. - // Hence, we back up 1 char. - val startingCode = - startingCodeLine.substring(math.min(math.max(startingCodeLine.length - 1, 0), originColumn)) - calculateCode(sourceWithLineNumbers, startingCode, originLine, maxCodeLength) - case None => - // It has an actual mapping, but it is synthetic code not found in the source file. - // We return the synthetic code. - source.getString(node.getStart, node.getFinish - node.getStart) - } - } - case _ => - // No mapping at all. We return the node code. - source.getString(node.getStart, node.getFinish - node.getStart) - } - } - - /** Code field calculation: - * - We start with the re-mapped line/column number. - * - We always read at the length of the transpiled node (except if the original file ends earlier) capped at - * MAX_CODE_LENGTH. - * - If there would be more content we append ' [...]'. - */ - @scala.annotation.tailrec - private def calculateCode( - sourceWithLineNumbers: Map[Int, String], - currentLine: String, - currentLineNumber: Int, - transpiledCodeLength: Int - ): String = - currentLine match { - case line if line.length >= transpiledCodeLength => - shortenCode(line, transpiledCodeLength - 1) - case line if line.length < transpiledCodeLength && sourceWithLineNumbers.contains(currentLineNumber + 1) => - calculateCode( - sourceWithLineNumbers, - line + "\n" + sourceWithLineNumbers(currentLineNumber + 1), - currentLineNumber + 1, - transpiledCodeLength - ) - case line => - line.stripLineEnd - } - - private def lineFromSourceMap(node: Node): Option[Int] = { + def lineFromSourceMap(node: Node): Option[Int] = { sourceMap match { case Some(SourceMapOrigin(_, Some(sourceMap), _)) => val line = getLineOfSource(node.getStart) - 1 @@ -241,7 +152,7 @@ class JsSource(val srcDir: File, val projectDir: Path, val source: Source) { } } - private def columnFromSourceMap(node: Node): Option[Int] = { + def columnFromSourceMap(node: Node): Option[Int] = { sourceMap match { case Some(SourceMapOrigin(_, Some(sourceMap), _)) => val line = getLineOfSource(node.getStart) - 1 @@ -267,14 +178,14 @@ class JsSource(val srcDir: File, val projectDir: Path, val source: Source) { // Returns the line number for a given position in the source. // We use this method instead of source.getLine for performance reasons. - private def getLineOfSource(position: Int): Int = { + def getLineOfSource(position: Int): Int = { val (_, lineNumber) = positionToLineNumberMapping.minAfter(position).get lineNumber } // Returns the column number for a given position in the source. // We use this method instead of source.getColumn for performance reasons. - private def getColumnOfSource(position: Int): Int = { + def getColumnOfSource(position: Int): Int = { val (_, firstPositionInLine) = positionToFirstPositionInLineMapping.minAfter(position).get position - firstPositionInLine } diff --git a/src/main/scala/io/shiftleft/js2cpg/passes/AstCreationPass.scala b/src/main/scala/io/shiftleft/js2cpg/passes/AstCreationPass.scala index 0b77da5fa..9a8432280 100644 --- a/src/main/scala/io/shiftleft/js2cpg/passes/AstCreationPass.scala +++ b/src/main/scala/io/shiftleft/js2cpg/passes/AstCreationPass.scala @@ -4,22 +4,23 @@ import java.nio.file.Path import better.files.File import com.oracle.js.parser.Source import com.oracle.js.parser.ir.FunctionNode +import io.joern.x2cpg.utils.Report +import io.joern.x2cpg.utils.TimeUtils import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.js2cpg.core.Report import io.shiftleft.js2cpg.astcreation.AstCreator +import io.shiftleft.js2cpg.core.Config import io.shiftleft.js2cpg.io.{FileUtils, JsFileChecks} import io.shiftleft.js2cpg.parser.{JavaScriptParser, JsSource} import io.shiftleft.passes.ConcurrentWriterCpgPass import org.slf4j.LoggerFactory -import io.shiftleft.js2cpg.utils.SourceWrapper._ -import io.shiftleft.js2cpg.utils.TimeUtils +import io.shiftleft.js2cpg.utils.SourceWrapper.* import scala.util.{Failure, Success, Try} /** Given a list of filenames, this pass creates the abstract syntax tree and CPG AST for each file. Files are processed * in parallel. */ -class AstCreationPass(srcDir: File, filenames: List[(Path, Path)], cpg: Cpg, report: Report) +class AstCreationPass(cpg: Cpg, filenames: List[(Path, Path)], config: Config, report: Report) extends ConcurrentWriterCpgPass[(Path, Path)](cpg) { private val logger = LoggerFactory.getLogger(getClass) @@ -49,7 +50,7 @@ class AstCreationPass(srcDir: File, filenames: List[(Path, Path)], cpg: Cpg, rep logger.warn(s"Failed to generate CPG for '$path'!", exception) case Success(localDiff) => logger.info(s"Processed file '$path'") - report.updateReportDuration(path, duration) + report.updateReport(path, true, duration) diffGraph.absorb(localDiff) } } @@ -84,7 +85,7 @@ class AstCreationPass(srcDir: File, filenames: List[(Path, Path)], cpg: Cpg, rep val fileStatistics = JsFileChecks.check(relPath, lines) val source = Source.sourceFor(relPath, lines.mkString("\n")) - val jsSource = source.toJsSource(srcDir, rootDir) + val jsSource = source.toJsSource(File(config.inputPath), rootDir) logger.debug(s"Parsing file '$relPath'.") Try(JavaScriptParser.parseFromSource(jsSource)) match { diff --git a/src/main/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPass.scala b/src/main/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPass.scala index b68ec4751..f4511a70f 100644 --- a/src/main/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPass.scala +++ b/src/main/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPass.scala @@ -21,7 +21,7 @@ class BuiltinTypesPass(cpg: Cpg) extends CpgPass(cpg) { diffGraph.addNode(namespaceBlock) val orderTracker = new OrderTracker() - Defines.JsTypes.foreach { case typeName: String => + Defines.JsTypes.foreach { typeName => val tpe = NewType() .name(typeName) .fullName(typeName) diff --git a/src/main/scala/io/shiftleft/js2cpg/passes/ConfigPass.scala b/src/main/scala/io/shiftleft/js2cpg/passes/ConfigPass.scala index 31d07a23c..2e369e973 100644 --- a/src/main/scala/io/shiftleft/js2cpg/passes/ConfigPass.scala +++ b/src/main/scala/io/shiftleft/js2cpg/passes/ConfigPass.scala @@ -1,11 +1,11 @@ package io.shiftleft.js2cpg.passes +import io.joern.x2cpg.utils.Report +import io.joern.x2cpg.utils.TimeUtils import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.codepropertygraph.generated.nodes.NewConfigFile -import io.shiftleft.js2cpg.core.Report import io.shiftleft.js2cpg.io.FileDefaults import io.shiftleft.js2cpg.io.FileUtils -import io.shiftleft.js2cpg.utils.TimeUtils import io.shiftleft.passes.ConcurrentWriterCpgPass import io.shiftleft.utils.IOUtils import org.slf4j.{Logger, LoggerFactory} @@ -40,12 +40,12 @@ class ConfigPass(filenames: List[(Path, Path)], cpg: Cpg, report: Report) val localDiff = new DiffGraphBuilder logger.debug(s"Adding file '$relativeFile' as config file.") val configNode = NewConfigFile().name(fileName).content(content.mkString("\n")) - report.addReportInfo(fileName, fileStatistics.linesOfCode, parsed = true, cpgGen = true, isConfig = true) + report.addReportInfo(fileName, fileStatistics.linesOfCode, parsed = true, cpgGen = true) localDiff.addNode(configNode) localDiff } diffGraph.absorb(result) - report.updateReportDuration(fileName, time) + report.updateReport(fileName, true, time) } } diff --git a/src/main/scala/io/shiftleft/js2cpg/passes/DependenciesPass.scala b/src/main/scala/io/shiftleft/js2cpg/passes/DependenciesPass.scala index 8931dda7d..6ae8d6c78 100644 --- a/src/main/scala/io/shiftleft/js2cpg/passes/DependenciesPass.scala +++ b/src/main/scala/io/shiftleft/js2cpg/passes/DependenciesPass.scala @@ -15,7 +15,7 @@ class DependenciesPass(cpg: Cpg, config: Config) extends CpgPass(cpg) { private def dependenciesForPackageJsons(): Map[String, String] = { val packagesJsons = (FileUtils - .getFileTree(Paths.get(config.srcDir), config, List(".json")) + .getFileTree(Paths.get(config.inputPath), config, List(".json")) .filter(_.toString.endsWith(FileDefaults.PACKAGE_JSON_FILENAME)) :+ config.createPathForPackageJson()).toSet packagesJsons.flatMap(p => PackageJsonParser.dependencies(p)).toMap diff --git a/src/main/scala/io/shiftleft/js2cpg/passes/PrivateKeyFilePass.scala b/src/main/scala/io/shiftleft/js2cpg/passes/PrivateKeyFilePass.scala index 96fe83288..00372bb21 100644 --- a/src/main/scala/io/shiftleft/js2cpg/passes/PrivateKeyFilePass.scala +++ b/src/main/scala/io/shiftleft/js2cpg/passes/PrivateKeyFilePass.scala @@ -1,7 +1,7 @@ package io.shiftleft.js2cpg.passes +import io.joern.x2cpg.utils.Report import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.js2cpg.core.Report import io.shiftleft.utils.IOUtils import java.nio.file.Path diff --git a/src/main/scala/io/shiftleft/js2cpg/preprocessing/NuxtTranspiler.scala b/src/main/scala/io/shiftleft/js2cpg/preprocessing/NuxtTranspiler.scala index a0755a1eb..bd8e946ff 100644 --- a/src/main/scala/io/shiftleft/js2cpg/preprocessing/NuxtTranspiler.scala +++ b/src/main/scala/io/shiftleft/js2cpg/preprocessing/NuxtTranspiler.scala @@ -46,7 +46,7 @@ class NuxtTranspiler(override val config: Config, override val projectPath: Path private def isNuxtProject: Boolean = PackageJsonParser - .dependencies((File(config.srcDir) / FileDefaults.PACKAGE_JSON_FILENAME).path) + .dependencies((File(config.inputPath) / FileDefaults.PACKAGE_JSON_FILENAME).path) .contains("nuxt") override def shouldRun(): Boolean = config.nuxtTranspiling && isNuxtProject diff --git a/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunner.scala b/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunner.scala index e4f655630..c0a91ab27 100644 --- a/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunner.scala +++ b/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunner.scala @@ -67,7 +67,7 @@ class TranspilationRunner(projectPath: Path, tmpTranspileDir: Path, config: Conf } def handlePrivateModules(): List[(Path, Path)] = { - val project = File(config.srcDir) + val project = File(config.inputPath) val nodeModulesFolder = project / NODE_MODULES_DIR_NAME if (!nodeModulesFolder.exists) { List.empty diff --git a/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilingEnvironment.scala b/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilingEnvironment.scala index 92f670bd9..f4f51b334 100644 --- a/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilingEnvironment.scala +++ b/src/main/scala/io/shiftleft/js2cpg/preprocessing/TranspilingEnvironment.scala @@ -160,7 +160,7 @@ trait TranspilingEnvironment { isYarnAvailable.get } - protected def npmAvailable(): Boolean = isNpmAvailable match { + private def npmAvailable(): Boolean = isNpmAvailable match { case Some(value) => value case None => diff --git a/src/main/scala/io/shiftleft/js2cpg/preprocessing/VueTranspiler.scala b/src/main/scala/io/shiftleft/js2cpg/preprocessing/VueTranspiler.scala index dad399398..30d6adee4 100644 --- a/src/main/scala/io/shiftleft/js2cpg/preprocessing/VueTranspiler.scala +++ b/src/main/scala/io/shiftleft/js2cpg/preprocessing/VueTranspiler.scala @@ -41,14 +41,14 @@ object VueTranspiler { def isVueProject(config: Config, projectPath: Path): Boolean = { val hasVueDep = PackageJsonParser - .dependencies((File(config.srcDir) / FileDefaults.PACKAGE_JSON_FILENAME).path) + .dependencies((File(config.inputPath) / FileDefaults.PACKAGE_JSON_FILENAME).path) .exists(_._1.startsWith("vue")) hasVueDep && hasVueFiles(config, projectPath) } private def vueVersion(config: Config): Int = { val versionString = - PackageJsonParser.dependencies((File(config.srcDir) / FileDefaults.PACKAGE_JSON_FILENAME).path)("vue") + PackageJsonParser.dependencies((File(config.inputPath) / FileDefaults.PACKAGE_JSON_FILENAME).path)("vue") // ignore ~, ^, and more from semver; see: https://stackoverflow.com/a/25861938 val c = versionString.collectFirst { case c if Try(c.toString.toInt).isSuccess => c.toString.toInt } c.getOrElse(3) // 3 is the latest version; we default to that diff --git a/src/main/scala/io/shiftleft/js2cpg/utils/MemoryMetrics.scala b/src/main/scala/io/shiftleft/js2cpg/utils/MemoryMetrics.scala index aae334b7e..64d1f0c38 100644 --- a/src/main/scala/io/shiftleft/js2cpg/utils/MemoryMetrics.scala +++ b/src/main/scala/io/shiftleft/js2cpg/utils/MemoryMetrics.scala @@ -3,6 +3,7 @@ package io.shiftleft.js2cpg.utils import io.shiftleft.js2cpg.core.Config import org.slf4j.LoggerFactory +import scala.util.Try import scala.util.Using object MemoryMetrics { @@ -54,14 +55,14 @@ object MemoryMetrics { } } - def withMemoryMetrics(config: Config)(work: => Unit): Unit = config.jvmMetrics match { + def withMemoryMetrics[T](config: Config)(work: => T): T = config.jvmMetrics match { case Some(port) => Using(new JmxRunnable(port, 5000)) { monitor => val t = new Thread(monitor) t.setName("js2cpg-jvm-monitor") t.start() work - } + }.get case None => work } diff --git a/src/main/scala/io/shiftleft/js2cpg/utils/SourceWrapper.scala b/src/main/scala/io/shiftleft/js2cpg/utils/SourceWrapper.scala index 80c63d03b..304194f16 100644 --- a/src/main/scala/io/shiftleft/js2cpg/utils/SourceWrapper.scala +++ b/src/main/scala/io/shiftleft/js2cpg/utils/SourceWrapper.scala @@ -1,13 +1,13 @@ package io.shiftleft.js2cpg.utils import better.files.File - +import com.oracle.js.parser.Source import java.nio.file.{Path, Paths} import io.shiftleft.js2cpg.parser.JsSource object SourceWrapper { - implicit class SourceWrapper(val source: com.oracle.js.parser.Source) extends AnyVal { - def toJsSource(srcDir: File = File(""), projectDir: Path = Paths.get("")) = - new JsSource(srcDir, projectDir, source) + implicit class SourceWrapper(val source: Source) extends AnyVal { + def toJsSource(srcDir: File = File(""), projectDir: Path = Paths.get("")): JsSource = + JsSource(srcDir, projectDir, source) } } diff --git a/src/main/scala/io/shiftleft/js2cpg/utils/TimeUtils.scala b/src/main/scala/io/shiftleft/js2cpg/utils/TimeUtils.scala deleted file mode 100644 index 647acf6c6..000000000 --- a/src/main/scala/io/shiftleft/js2cpg/utils/TimeUtils.scala +++ /dev/null @@ -1,55 +0,0 @@ -package io.shiftleft.js2cpg.utils - -import java.util.Locale -import scala.concurrent.duration._ - -object TimeUtils { - - /** Measures elapsed time for executing a block in nanoseconds */ - def time[R](block: => R): (R, Long) = { - val t0 = System.nanoTime() - val result = block - val t1 = System.nanoTime() - val elapsed = t1 - t0 - (result, elapsed) - } - - /** Selects most appropriate TimeUnit for given duration and formats it accordingly */ - def pretty(duration: Duration): String = { - duration match { - case d: FiniteDuration => - val nanos = d.toNanos - val unit = chooseUnit(nanos) - val value = nanos.toDouble / NANOSECONDS.convert(1, unit) - - s"%.4g %s".formatLocal(Locale.ROOT, value, abbreviate(unit)) - - case Duration.MinusInf => s"-∞ (minus infinity)" - case Duration.Inf => s"∞ (infinity)" - case _ => "undefined" - } - } - - private def chooseUnit(nanos: Long): TimeUnit = { - val d = nanos.nanos - - if (d.toDays > 0) DAYS - else if (d.toHours > 0) HOURS - else if (d.toMinutes > 0) MINUTES - else if (d.toSeconds > 0) SECONDS - else if (d.toMillis > 0) MILLISECONDS - else if (d.toMicros > 0) MICROSECONDS - else NANOSECONDS - } - - private def abbreviate(unit: TimeUnit): String = unit match { - case NANOSECONDS => "ns" - case MICROSECONDS => "μs" - case MILLISECONDS => "ms" - case SECONDS => "s" - case MINUTES => "min" - case HOURS => "h" - case DAYS => "d" - } - -} diff --git a/src/test/resources/excludes/a.js b/src/test/resources/excludes/a.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/folder/b.js b/src/test/resources/excludes/folder/b.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/folder/c.js b/src/test/resources/excludes/folder/c.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/foo.bar/d.js b/src/test/resources/excludes/foo.bar/d.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/index.js b/src/test/resources/excludes/index.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/tests/a.spec.js b/src/test/resources/excludes/tests/a.spec.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/tests/b.mock.js b/src/test/resources/excludes/tests/b.mock.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/tests/c.e2e.js b/src/test/resources/excludes/tests/c.e2e.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/resources/excludes/tests/d.test.js b/src/test/resources/excludes/tests/d.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/scala/io/shiftleft/js2cpg/io/ExcludeTest.scala b/src/test/scala/io/shiftleft/js2cpg/io/ExcludeTest.scala index f7b7aa889..33e42e38f 100644 --- a/src/test/scala/io/shiftleft/js2cpg/io/ExcludeTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/io/ExcludeTest.scala @@ -1,50 +1,67 @@ package io.shiftleft.js2cpg.io import better.files.File -import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.js2cpg.core.{Js2cpgArgumentsParser, Js2CpgMain} -import io.shiftleft.semanticcpg.language._ - +import io.joern.x2cpg.X2Cpg.newEmptyCpg +import io.joern.x2cpg.utils.Report +import io.shiftleft.js2cpg.core.Config +import io.shiftleft.js2cpg.io.FileDefaults.JS_SUFFIX +import io.shiftleft.js2cpg.passes.AstCreationPass +import io.shiftleft.semanticcpg.language.* import org.scalatest.matchers.should.Matchers import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.BeforeAndAfterAll import java.util.regex.Pattern -object ExcludeTest { - private implicit class ToArg(val arg: String) extends AnyVal { - def toArg: String = s"--$arg" +class ExcludeTest extends AnyWordSpec with Matchers with TableDrivenPropertyChecks with BeforeAndAfterAll { + + private val testFiles: List[String] = List( + ".sub/e.js", + "folder/b.js", + "folder/c.js", + "foo.bar/d.js", + "tests/a.spec.js", + "tests/b.mock.js", + "tests/c.e2e.js", + "tests/d.test.js", + "a.js", + "b-min.js", + "c.spec.js", + "d.chunk.js", + "index.js" + ) + + private val projectUnderTest: File = { + val dir = File.newTemporaryDirectory("jssrc2cpgTestsExcludeTest") + testFiles.foreach { testFile => + val file = dir / testFile + file.createIfNotExists(createParents = true) + } + dir } -} - -class ExcludeTest extends AnyWordSpec with Matchers with TableDrivenPropertyChecks { - - import ExcludeTest._ - import Js2cpgArgumentsParser._ - private val projectUnderTestPath = File(getClass.getResource("/excludes").toURI).pathAsString - - private def fileNames(cpg: Cpg): List[String] = cpg.file.name.l + override def afterAll(): Unit = projectUnderTest.delete(swallowIOExceptions = true) private def testWithArguments( - args: Seq[String], - expectedFiles: Set[String], - defaultArgs: Set[String] = Set(NO_TS, NO_BABEL) + exclude: Seq[String], + excludeRegex: String, + ignoreTests: Boolean, + expectedFiles: Set[String] ): Unit = { - File.usingTemporaryDirectory("js2cpgTest") { tmpDir => - val cpgPath = tmpDir / "cpg.bin.zip" - - Js2CpgMain.main( - Array(projectUnderTestPath, "--output", cpgPath.pathAsString) ++ - args.toArray ++ - defaultArgs.map(_.toArg).toArray - ) - - val cpg = Cpg.withConfig(overflowdb.Config.withoutOverflow.withStorageLocation(cpgPath.pathAsString)) - - fileNames(cpg) should contain theSameElementsAs expectedFiles.map(_.replace("/", java.io.File.separator)) - cpg.close() - cpgPath.deleteOnExit() + File.usingTemporaryDirectory("js2cpgTests") { tmpDir => + val cpg = newEmptyCpg() + val config = Config() + .withInputPath(projectUnderTest.toString) + .withOutputPath(tmpDir.toString) + .withIgnoredFiles(exclude) + .withIgnoredFilesRegex(excludeRegex) + .withIgnoreTests(ignoreTests) + val files = FileUtils + .getFileTree(projectUnderTest.path, config, List(JS_SUFFIX)) + .map(f => (f, projectUnderTest.path)) + new AstCreationPass(cpg, files, config, new Report()).createAndApply() + cpg.file.name.l should contain theSameElementsAs expectedFiles.map(_.replace("/", java.io.File.separator)) } } @@ -52,83 +69,106 @@ class ExcludeTest extends AnyWordSpec with Matchers with TableDrivenPropertyChec val testInput = Table( // -- Header for naming all test parameters - ("statement", "arguments", "expectedResult"), + ("statement", "--exclude", "--exclude-regex", "with-tests", "expectedResult"), // -- // Test for default: ( "exclude nothing if no excludes are given", Seq.empty[String], + "", + true, Set("index.js", "a.js", "folder/b.js", "folder/c.js", "foo.bar/d.js") ), // -- // Tests for --exclude only: ( - s"exclude a file with ${EXCLUDE.toArg} with relative path", - Seq(EXCLUDE.toArg, "index.js"), + "exclude a file with --exclude with relative path", + Seq("index.js"), + "", + true, Set("a.js", "folder/b.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude files with ${EXCLUDE.toArg} with relative paths", - Seq(EXCLUDE.toArg, "index.js,folder/b.js"), + "exclude files with --exclude with relative paths", + Seq("index.js", "folder/b.js"), + "", + true, Set("a.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude a file with ${EXCLUDE.toArg} with absolute path", - Seq(EXCLUDE.toArg, s"$projectUnderTestPath/index.js"), + "exclude a file with --exclude with absolute path", + Seq(s"$projectUnderTest/index.js"), + "", + true, Set("a.js", "folder/b.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude files with ${EXCLUDE.toArg} with absolute paths", - Seq(EXCLUDE.toArg, s"$projectUnderTestPath/index.js,$projectUnderTestPath/folder/b.js"), + "exclude files with --exclude with absolute paths", + Seq(s"$projectUnderTest/index.js", s"$projectUnderTest/folder/b.js"), + "", + true, Set("a.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude files with ${EXCLUDE.toArg} with mixed paths", - Seq(EXCLUDE.toArg, s"index.js,$projectUnderTestPath/folder/b.js"), + "exclude files with --exclude with mixed paths", + Seq("index.js", s"$projectUnderTest/folder/b.js"), + "", + true, Set("a.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude a folder with ${EXCLUDE.toArg} with absolute path", - Seq(EXCLUDE.toArg, s"$projectUnderTestPath/folder/"), + "exclude a folder with --exclude with absolute path", + Seq(s"$projectUnderTest/folder/"), + "", + true, Set("a.js", "index.js", "foo.bar/d.js") ), ( - s"exclude a folder with ${EXCLUDE.toArg} with relative path", - Seq(EXCLUDE.toArg, s"folder/"), + "exclude a folder with --exclude with relative path", + Seq("folder/"), + "", + true, Set("a.js", "index.js", "foo.bar/d.js") ), // -- // Tests for --exclude-regex only: ( - s"exclude a file with ${EXCLUDE_REGEX.toArg}", - Seq(EXCLUDE_REGEX.toArg, ".*index\\..*"), + "exclude a file with --exclude-regex", + Seq.empty, + ".*index\\..*", + true, Set("a.js", "folder/b.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude files with ${EXCLUDE_REGEX.toArg}", - Seq(EXCLUDE_REGEX.toArg, ".*(index|b)\\..*"), + "exclude files with --exclude-regex", + Seq.empty, + ".*(index|b)\\..*", + true, Set("a.js", "folder/c.js", "foo.bar/d.js") ), ( - s"exclude a complete folder with ${EXCLUDE_REGEX.toArg}", - Seq( - EXCLUDE_REGEX.toArg, - s".*${Pattern.quote(java.io.File.separator)}folder${Pattern.quote(java.io.File.separator)}.*" - ), + "exclude a complete folder with --exclude-regex", + Seq.empty, + s".*${Pattern.quote(java.io.File.separator)}?folder${Pattern.quote(java.io.File.separator)}.*", + true, Set("index.js", "a.js", "foo.bar/d.js") ), // -- // Tests for mixed arguments ( - s"exclude files with ${EXCLUDE.toArg} and ${EXCLUDE_REGEX.toArg}", - Seq(EXCLUDE.toArg, "a.js", EXCLUDE_REGEX.toArg, ".*(index|b)\\..*"), + "exclude files with --exclude and --exclude-regex", + Seq("a.js"), + ".*(index|b)\\..*", + true, Set("folder/c.js", "foo.bar/d.js") ), // -- // Tests for including test files ( - s"include test files with ${WITH_TESTS.toArg}", - Seq(WITH_TESTS.toArg), + "include test files with --with-tests", + Seq.empty, + "", + false, Set( "index.js", "a.js", @@ -138,13 +178,14 @@ class ExcludeTest extends AnyWordSpec with Matchers with TableDrivenPropertyChec "tests/a.spec.js", "tests/b.mock.js", "tests/c.e2e.js", - "tests/d.test.js" + "tests/d.test.js", + "c.spec.js" ) ) ) - forAll(testInput) { (statement, arguments, result) => - s"$statement" in testWithArguments(arguments, result) + forAll(testInput) { (statement, exclude, excludeRegex, withTests, result) => + s"$statement" in testWithArguments(exclude, excludeRegex, withTests, result) } } diff --git a/src/test/scala/io/shiftleft/js2cpg/io/ExternalCommandTest.scala b/src/test/scala/io/shiftleft/js2cpg/io/ExternalCommandTest.scala index 152c545b0..683625a5a 100644 --- a/src/test/scala/io/shiftleft/js2cpg/io/ExternalCommandTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/io/ExternalCommandTest.scala @@ -5,17 +5,22 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec class ExternalCommandTest extends AnyWordSpec with Matchers { + + private val command = if (scala.util.Properties.isWin) "cmd /C dir" else "ls" + "ExternalCommand" should { "run an external command with ProcessBuilder and no spaces in the directory name" in { File.usingTemporaryDirectory("js2cpgTest") { sourceDir => - val cmd = "ls " + sourceDir.pathAsString + val cmd = s"$command ${sourceDir.pathAsString}" + (sourceDir / "Main.js").createFileIfNotExists().write("console.log('Foo');") ExternalCommand.run(cmd, sourceDir.pathAsString) should be a Symbol("success") } } "run an external command with ProcessBuilder and spaces in the directory name" in { File.usingTemporaryDirectory("js2cpg Test") { sourceDir => - val cmd = "ls " + sourceDir.pathAsString + val cmd = s"$command ${sourceDir.pathAsString}" + (sourceDir / "Main.js").createFileIfNotExists().write("console.log('Foo');") ExternalCommand.run(cmd, sourceDir.pathAsString) should be a Symbol("success") } } diff --git a/src/test/scala/io/shiftleft/js2cpg/io/FileUtilsTest.scala b/src/test/scala/io/shiftleft/js2cpg/io/FileUtilsTest.scala index eba6f2ec1..01f1ee4f2 100644 --- a/src/test/scala/io/shiftleft/js2cpg/io/FileUtilsTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/io/FileUtilsTest.scala @@ -19,7 +19,7 @@ class FileUtilsTest extends AnyWordSpec with Matchers { (sourceDir / ".folder" / "e.js").createIfNotExists(createParents = true) File.usingTemporaryDirectory("js2cpgTest") { targetDir => - val config = Config(srcDir = sourceDir.pathAsString) + val config = Config().withInputPath(sourceDir.pathAsString) val copiedDir = FileUtils.copyToDirectory(sourceDir, targetDir, config) val dirContent = FileUtils.getFileTree(copiedDir.path, config, List(JS_SUFFIX)) dirContent shouldBe empty @@ -34,7 +34,7 @@ class FileUtilsTest extends AnyWordSpec with Matchers { (sourceDir / "b-min.js").createFile() (sourceDir / "b-min.23472420.js").createFile() - val config = Config(srcDir = sourceDir.pathAsString) + val config = Config().withInputPath(sourceDir.pathAsString) val minFile = (sourceDir / "something.js").createFile() minFile.write(s"console.log('${"x" * FileDefaults.LINE_LENGTH_THRESHOLD}');") val dirContent = FileUtils.getFileTree(sourceDir.path, config, List(JS_SUFFIX)) diff --git a/src/test/scala/io/shiftleft/js2cpg/parser/ParserTest.scala b/src/test/scala/io/shiftleft/js2cpg/parser/ParserTest.scala index 3c9a7728c..ccd297ec6 100644 --- a/src/test/scala/io/shiftleft/js2cpg/parser/ParserTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/parser/ParserTest.scala @@ -109,7 +109,7 @@ class ParserTest extends AnyWordSpec with Matchers { val expected = "(function (exports, require, module, __filename, __dirname) { if(true) { return 1; } });" val jsSource = JavaScriptParser.parse(jsfunction)._2 - jsSource.source.getContent.toString shouldBe expected + jsSource.source.getContent shouldBe expected } "fix NodeJS invalid return with import like code line" in { @@ -124,7 +124,7 @@ class ParserTest extends AnyWordSpec with Matchers { |// fooimportbar |console.log('fizzbuzz'); });""".stripMargin val jsSource = JavaScriptParser.parse(jsfunction)._2 - jsSource.source.getContent.toString shouldBe expected + jsSource.source.getContent shouldBe expected } "fix NodeJS invalid return with simple import" in { @@ -137,7 +137,7 @@ class ParserTest extends AnyWordSpec with Matchers { |import foo from 'dep.js'; |(function (exports, require, module, __filename, __dirname) { if(true) { return 1; } });""".stripMargin val jsSource = JavaScriptParser.parse(jsfunction)._2 - jsSource.source.getContent.toString shouldBe expected + jsSource.source.getContent shouldBe expected } "fix NodeJS invalid return with multiple imports" in { @@ -168,7 +168,7 @@ class ParserTest extends AnyWordSpec with Matchers { |import "module-name"; |(function (exports, require, module, __filename, __dirname) { if(true) { return 1; } });""".stripMargin val jsSource = JavaScriptParser.parse(jsfunction)._2 - jsSource.source.getContent.toString shouldBe expected + jsSource.source.getContent shouldBe expected } } diff --git a/src/test/scala/io/shiftleft/js2cpg/passes/AbstractPassTest.scala b/src/test/scala/io/shiftleft/js2cpg/passes/AbstractPassTest.scala index 054762224..55872e9bd 100644 --- a/src/test/scala/io/shiftleft/js2cpg/passes/AbstractPassTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/passes/AbstractPassTest.scala @@ -2,18 +2,14 @@ package io.shiftleft.js2cpg.passes import better.files.File import io.joern.x2cpg.X2Cpg.newEmptyCpg -import io.shiftleft.codepropertygraph.generated.EdgeTypes +import io.joern.x2cpg.utils.Report import io.shiftleft.codepropertygraph.generated.nodes.Dependency import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.js2cpg.core.Report +import io.shiftleft.js2cpg.core.Config import io.shiftleft.semanticcpg.language.* -import overflowdb.Node -import overflowdb.traversal.* import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import scala.jdk.CollectionConverters.* - abstract class AbstractPassTest extends AnyWordSpec with Matchers { protected abstract class Fixture @@ -27,7 +23,7 @@ abstract class AbstractPassTest extends AnyWordSpec with Matchers { file.write(code) val cpg = newEmptyCpg() val filenames = List((file.path, file.parent.path)) - new AstCreationPass(dir, filenames, cpg, new Report()).createAndApply() + new AstCreationPass(cpg, filenames, Config().withInputPath(dir.toString), new Report()).createAndApply() f(cpg) file.delete() } @@ -37,7 +33,7 @@ abstract class AbstractPassTest extends AnyWordSpec with Matchers { val file = testFile val cpg = newEmptyCpg() val filenames = List((file.path, file.parent.path)) - new AstCreationPass(file.parent, filenames, cpg, new Report()).createAndApply() + new AstCreationPass(cpg, filenames, Config().withInputPath(file.parent.toString), new Report()).createAndApply() f(cpg) } diff --git a/src/test/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPassTest.scala b/src/test/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPassTest.scala index 353047375..cd01d122a 100644 --- a/src/test/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPassTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/passes/BuiltinTypesPassTest.scala @@ -14,7 +14,7 @@ class BuiltinTypesPassTest extends AbstractPassTest { } "create types and type decls correctly" in { - Defines.JsTypes.foreach { case typeName: String => + Defines.JsTypes.foreach { typeName => val typeDeclNodes = cpg.typeDecl(typeName).l typeDeclNodes should have length 1 val typeDeclNode = typeDeclNodes.head diff --git a/src/test/scala/io/shiftleft/js2cpg/passes/CfgCreationPassTest.scala b/src/test/scala/io/shiftleft/js2cpg/passes/CfgCreationPassTest.scala index e7b61f299..568dee7ef 100644 --- a/src/test/scala/io/shiftleft/js2cpg/passes/CfgCreationPassTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/passes/CfgCreationPassTest.scala @@ -2,13 +2,14 @@ package io.shiftleft.js2cpg.passes import better.files.File import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.codepropertygraph.generated._ -import io.shiftleft.js2cpg.core.Report -import io.shiftleft.semanticcpg.language._ +import io.shiftleft.codepropertygraph.generated.* +import io.shiftleft.semanticcpg.language.* import io.joern.x2cpg.passes.controlflow.CfgCreationPass -import io.joern.x2cpg.passes.controlflow.cfgcreation.Cfg._ +import io.joern.x2cpg.passes.controlflow.cfgcreation.Cfg.* +import io.joern.x2cpg.utils.Report import io.shiftleft.codepropertygraph.generated.nodes.AstNodeBase import io.shiftleft.codepropertygraph.generated.nodes.Method +import io.shiftleft.js2cpg.core.Config import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import overflowdb.Node @@ -1296,7 +1297,7 @@ class CfgCreationPassTest extends AnyWordSpec with Matchers { val file = workspace / "test.js" file.write(code) val filenames = List((file.path, file.parent.path)) - new AstCreationPass(workspace, filenames, cpg, new Report()).createAndApply() + new AstCreationPass(cpg, filenames, Config().withInputPath(workspace.toString), new Report()).createAndApply() new CfgCreationPass(cpg).createAndApply() } diff --git a/src/test/scala/io/shiftleft/js2cpg/passes/ConfigPassTest.scala b/src/test/scala/io/shiftleft/js2cpg/passes/ConfigPassTest.scala index 1cc004acb..5cbc3b7e8 100644 --- a/src/test/scala/io/shiftleft/js2cpg/passes/ConfigPassTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/passes/ConfigPassTest.scala @@ -1,9 +1,9 @@ package io.shiftleft.js2cpg.passes -import io.shiftleft.js2cpg.core.Report import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.semanticcpg.language._ +import io.shiftleft.semanticcpg.language.* import better.files.File +import io.joern.x2cpg.utils.Report import io.shiftleft.js2cpg.io.FileDefaults import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec diff --git a/src/test/scala/io/shiftleft/js2cpg/passes/DependenciesPassTest.scala b/src/test/scala/io/shiftleft/js2cpg/passes/DependenciesPassTest.scala index ec08cd93e..0347baf50 100644 --- a/src/test/scala/io/shiftleft/js2cpg/passes/DependenciesPassTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/passes/DependenciesPassTest.scala @@ -2,12 +2,13 @@ package io.shiftleft.js2cpg.passes import better.files.File import io.joern.x2cpg.X2Cpg.newEmptyCpg +import io.joern.x2cpg.utils.Report import io.shiftleft.codepropertygraph.Cpg -import io.shiftleft.js2cpg.core.{Config, Report} +import io.shiftleft.js2cpg.core.Config import io.shiftleft.js2cpg.io.FileDefaults import io.shiftleft.js2cpg.parser.PackageJsonParser import io.shiftleft.js2cpg.preprocessing.TypescriptTranspiler -import io.shiftleft.semanticcpg.language._ +import io.shiftleft.semanticcpg.language.* class DependenciesPassTest extends AbstractPassTest { @@ -180,8 +181,9 @@ class DependenciesPassTest extends AbstractPassTest { val filenames = List((file.path, file.parent.path)) val cpg = newEmptyCpg() - new AstCreationPass(dir, filenames, cpg, new Report()).createAndApply() - new DependenciesPass(cpg, Config(srcDir = dir.toString, packageJsonLocation = packageJson)).createAndApply() + val config = Config().withInputPath(dir.toString).withPackageJsonLocation(packageJson) + new AstCreationPass(cpg, filenames, config, new Report()).createAndApply() + new DependenciesPass(cpg, config).createAndApply() f(cpg) jsonTmpFiles.foreach(_.delete()) diff --git a/src/test/scala/io/shiftleft/js2cpg/passes/SimpleAstCreationPassTest.scala b/src/test/scala/io/shiftleft/js2cpg/passes/SimpleAstCreationPassTest.scala index beab49894..155e841a7 100644 --- a/src/test/scala/io/shiftleft/js2cpg/passes/SimpleAstCreationPassTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/passes/SimpleAstCreationPassTest.scala @@ -1,11 +1,7 @@ package io.shiftleft.js2cpg.passes -import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.codepropertygraph.generated.* import io.shiftleft.codepropertygraph.generated.nodes.* -import io.shiftleft.js2cpg.core.Config -import io.shiftleft.js2cpg.io.FileDefaults.JS_SUFFIX -import io.shiftleft.js2cpg.io.FileUtils import io.shiftleft.semanticcpg.language.* class SimpleAstCreationPassTest extends AbstractPassTest { diff --git a/src/test/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunnerTest.scala b/src/test/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunnerTest.scala index 80f686843..48d151392 100644 --- a/src/test/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunnerTest.scala +++ b/src/test/scala/io/shiftleft/js2cpg/preprocessing/TranspilationRunnerTest.scala @@ -32,7 +32,7 @@ class TranspilationRunnerTest extends AnyWordSpec with Matchers { "generate js files correctly for a simple Babel project" in TranspilationFixture("babel") { tmpDir => File.usingTemporaryDirectory("js2cpgTest") { transpileOutDir => - val config = Config(srcDir = transpileOutDir.pathAsString, tsTranspiling = false) + val config = Config().withInputPath(transpileOutDir.pathAsString).withTsTranspiling(false) new TranspilationRunner(tmpDir.path, transpileOutDir.path, config).execute() @@ -52,7 +52,7 @@ class TranspilationRunnerTest extends AnyWordSpec with Matchers { "generate js files correctly for a simple Babel project in folder with whitespace" in TranspilationFixture("babel") { tmpDir => File.usingTemporaryDirectory("js2cpgTest folder") { transpileOutDir => - val config = Config(srcDir = transpileOutDir.pathAsString, tsTranspiling = false) + val config = Config().withInputPath(transpileOutDir.pathAsString).withTsTranspiling(false) new TranspilationRunner(tmpDir.path, transpileOutDir.path, config).execute() @@ -113,10 +113,10 @@ class TranspilationRunnerTest extends AnyWordSpec with Matchers { "generate js files correctly for a simple Typescript project" in TranspilationFixture("typescript") { tmpDir => File.usingTemporaryDirectory("js2cpgTest") { transpileOutDir => - val config = Config(srcDir = transpileOutDir.pathAsString, tsTranspiling = false) + val config = Config().withInputPath(transpileOutDir.pathAsString).withTsTranspiling(false) val jsFiles = FileUtils - .getFileTree(tmpDir.path, Config(srcDir = tmpDir.pathAsString), List(JS_SUFFIX)) + .getFileTree(tmpDir.path, Config().withInputPath(tmpDir.pathAsString), List(JS_SUFFIX)) .map(f => (f, tmpDir.path)) val expectedJsFiles = @@ -294,10 +294,14 @@ class TranspilationRunnerTest extends AnyWordSpec with Matchers { new TranspilationRunner( tmpDir.path, transpileOutDir.path, - Config(srcDir = tmpDir.pathAsString, babelTranspiling = false, optimizeDependencies = false) + Config().withInputPath(tmpDir.pathAsString).withBabelTranspiling(false).withOptimizeDependencies(false) ).execute() val transpiledJsFiles = - FileUtils.getFileTree(transpileOutDir.path, Config(srcDir = transpileOutDir.pathAsString), List(JS_SUFFIX)) + FileUtils.getFileTree( + transpileOutDir.path, + Config().withInputPath(transpileOutDir.pathAsString), + List(JS_SUFFIX) + ) transpiledJsFiles shouldBe empty } } @@ -309,10 +313,10 @@ class TranspilationRunnerTest extends AnyWordSpec with Matchers { new TranspilationRunner( tmpDir.path, transpileOutDir.path, - Config(srcDir = tmpDir.pathAsString, babelTranspiling = false, optimizeDependencies = true) + Config().withInputPath(tmpDir.pathAsString).withBabelTranspiling(false).withOptimizeDependencies(true) ).execute() val transpiledJsFiles = FileUtils - .getFileTree(transpileOutDir.path, Config(srcDir = transpileOutDir.pathAsString), List(JS_SUFFIX)) + .getFileTree(transpileOutDir.path, Config().withInputPath(transpileOutDir.pathAsString), List(JS_SUFFIX)) .map(_.getFileName.toString) transpiledJsFiles shouldBe List("index.js") }