Skip to content

Commit

Permalink
[jssrc2cpg][swiftsrc2cpg] Safer offset calculation (joernio#4208)
Browse files Browse the repository at this point in the history
  • Loading branch information
max-leuthaeuser authored Feb 20, 2024
1 parent ba2ebcf commit d9efef9
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class CodeDumperFromContentTest extends CCodeToCpgSuite {
"Foo.cpp"
).withConfig(Config().withDisableFileContent(false))

"allow one to dump a method node's source code from `TypeDecl.content`" in {
"allow one to dump a typedecl node's source code from `TypeDecl.content`" in {
val List(content) = cpg.typeDecl.nameExact("Foo").content.l
content shouldBe myClassContent
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ class AstCreator(val config: Config, val global: Global, val parserResult: Parse
for {
startOffset <- start(node)
endOffset <- end(node)
} yield (startOffset, endOffset)
} yield (math.max(startOffset, 0), math.min(endOffset, parserResult.fileContent.length))
}

override protected def offset(node: BabelNodeInfo): Option[(Int, Int)] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As

protected def code(node: Value): String = {
nodeOffsets(node) match {
case Some((startOffset, endOffset))
if startOffset < endOffset && startOffset >= 0 && endOffset <= parserResult.fileContent.length =>
case Some((startOffset, endOffset)) =>
shortenCode(parserResult.fileContent.substring(startOffset, endOffset).trim)
case _ => PropertyDefaults.Code
case _ =>
PropertyDefaults.Code
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,25 @@ class CodeDumperFromContentTest extends JsSrc2CpgSuite {
"index.js"
).withConfig(Config().withDisableFileContent(false))

"allow one to dump a method node's source code from `TypeDecl.content`" in {
"allow one to dump a typedecl node's source code from `TypeDecl.content`" in {
val List(content) = cpg.typeDecl.nameExact("Foo").content.l
content shouldBe myClassContent
}
}

"content with UTF8 characters" should {
val myClassContent =
"""class Foo {
| // ✅ This is a comment with UTF8.
| x = 'foo';
|}""".stripMargin

val cpg = code(s"""
|// A comment
|$myClassContent
|""".stripMargin).withConfig(Config().withDisableFileContent(false))

"allow one to dump source code" in {
val List(content) = cpg.typeDecl.nameExact("Foo").content.l
content shouldBe myClassContent
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class AstCreator(val config: Config, val global: SwiftGlobal, val parserResult:
for {
startOffset <- node.startOffset
endOffset <- node.endOffset
} yield (startOffset, endOffset)
} yield (math.max(startOffset, 0), math.min(endOffset, parserResult.fileContent.length))
}

override protected def offset(node: SwiftNode): Option[(Int, Int)] = {
Expand All @@ -148,10 +148,10 @@ class AstCreator(val config: Config, val global: SwiftGlobal, val parserResult:

override protected def code(node: SwiftNode): String = {
nodeOffsets(node) match {
case Some((startOffset, endOffset))
if startOffset < endOffset && startOffset >= 0 && endOffset <= parserResult.fileContent.length =>
case Some((startOffset, endOffset)) =>
shortenCode(parserResult.fileContent.substring(startOffset, endOffset).trim)
case _ => PropertyDefaults.Code
case _ =>
PropertyDefaults.Code
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import io.joern.swiftsrc2cpg.parser.SwiftNodeSyntax.*
import io.joern.swiftsrc2cpg.passes.Defines
import io.joern.x2cpg.Ast
import io.joern.x2cpg.ValidationMode
import io.shiftleft.codepropertygraph.generated.DispatchTypes
import io.shiftleft.codepropertygraph.generated.EdgeTypes
import io.shiftleft.codepropertygraph.generated.Operators

trait AstForPatternSyntaxCreator(implicit withSchemaValidation: ValidationMode) {
this: AstCreator =>
Expand All @@ -22,7 +24,15 @@ trait AstForPatternSyntaxCreator(implicit withSchemaValidation: ValidationMode)
Ast(identNode)
}

private def astForIsTypePatternSyntax(node: IsTypePatternSyntax): Ast = notHandledYet(node)
private def astForIsTypePatternSyntax(node: IsTypePatternSyntax): Ast = {
val op = Operators.instanceOf
val typ = code(node.`type`)
val lhsNode = node.`type`
val lhsAst = Ast(literalNode(lhsNode, code(lhsNode), None).dynamicTypeHintFullName(Seq(typ)))
val callNode_ = callNode(node, code(node), op, DispatchTypes.STATIC_DISPATCH).dynamicTypeHintFullName(Seq(typ))
callAst(callNode_, Seq(lhsAst))
}

private def astForMissingPatternSyntax(node: MissingPatternSyntax): Ast = notHandledYet(node)
private def astForTuplePatternSyntax(node: TuplePatternSyntax): Ast = notHandledYet(node)

Expand All @@ -34,29 +44,31 @@ trait AstForPatternSyntaxCreator(implicit withSchemaValidation: ValidationMode)
MethodScope
}

val name = node.pattern match {
val (name, typeFullName) = node.pattern match {
case expr: ExpressionPatternSyntax if expr.expression.isInstanceOf[AsExprSyntax] =>
val asExpr = expr.expression.asInstanceOf[AsExprSyntax]
(code(asExpr.expression), code(asExpr.`type`))
case expr: ExpressionPatternSyntax =>
notHandledYet(expr)
code(expr)
(code(expr), Defines.Any)
case ident: IdentifierPatternSyntax =>
code(ident.identifier)
(code(ident.identifier), Defines.Any)
case isType: IsTypePatternSyntax =>
notHandledYet(isType)
code(isType)
(code(isType), Defines.Any)
case missing: MissingPatternSyntax =>
code(missing.placeholder)
(code(missing.placeholder), Defines.Any)
case tuple: TuplePatternSyntax =>
notHandledYet(tuple)
code(tuple)
(code(tuple), Defines.Any)
case valueBinding: ValueBindingPatternSyntax =>
notHandledYet(valueBinding)
code(valueBinding)
(code(valueBinding), Defines.Any)
case wildcard: WildcardPatternSyntax =>
notHandledYet(wildcard)
generateUnusedVariableName(usedVariableNames, "wildcard")
(generateUnusedVariableName(usedVariableNames, "wildcard"), Defines.Any)
}
val typeFullName = Defines.Any
val nLocalNode = localNode(node, name, name, typeFullName).order(0)
val nLocalNode = localNode(node, name, name, typeFullName).order(0)
scope.addVariable(name, nLocalNode, scopeType)
diffGraph.addEdge(localAstParentStack.head, nLocalNode, EdgeTypes.AST)
Ast()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import io.shiftleft.codepropertygraph.generated.nodes.NewAnnotationParameter
import io.shiftleft.codepropertygraph.generated.nodes.NewAnnotationParameterAssign
import io.shiftleft.codepropertygraph.generated.EdgeTypes
import io.shiftleft.codepropertygraph.generated.nodes.NewIdentifier
import io.shiftleft.codepropertygraph.generated.DispatchTypes
import io.shiftleft.codepropertygraph.generated.Operators

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

Expand Down Expand Up @@ -258,10 +260,18 @@ trait AstForSyntaxCreator(implicit withSchemaValidation: ValidationMode) { this:

private def astForLabeledSpecializeArgumentSyntax(node: LabeledSpecializeArgumentSyntax): Ast = notHandledYet(node)
private def astForLayoutRequirementSyntax(node: LayoutRequirementSyntax): Ast = notHandledYet(node)
private def astForMatchingPatternConditionSyntax(node: MatchingPatternConditionSyntax): Ast = notHandledYet(node)
private def astForMemberBlockItemSyntax(node: MemberBlockItemSyntax): Ast = notHandledYet(node)
private def astForMemberBlockSyntax(node: MemberBlockSyntax): Ast = notHandledYet(node)
private def astForMissingSyntax(node: MissingSyntax): Ast = notHandledYet(node)

private def astForMatchingPatternConditionSyntax(node: MatchingPatternConditionSyntax): Ast = {
val lhsAst = astForNode(node.pattern)
val rhsAst = astForNode(node.initializer.value)
val callNode_ = callNode(node, code(node), Operators.assignment, DispatchTypes.STATIC_DISPATCH)
val argAsts = List(lhsAst, rhsAst)
callAst(callNode_, argAsts)
}

private def astForMemberBlockItemSyntax(node: MemberBlockItemSyntax): Ast = notHandledYet(node)
private def astForMemberBlockSyntax(node: MemberBlockSyntax): Ast = notHandledYet(node)
private def astForMissingSyntax(node: MissingSyntax): Ast = notHandledYet(node)
private def astForMultipleTrailingClosureElementSyntax(node: MultipleTrailingClosureElementSyntax): Ast =
notHandledYet(node)
private def astForObjCSelectorPieceSyntax(node: ObjCSelectorPieceSyntax): Ast = notHandledYet(node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@ import io.shiftleft.semanticcpg.language.*

class CodeDumperFromContentTest extends SwiftSrc2CpgSuite {

private val codeString = """
|// A comment
|func my_func(param1: Int) -> Int {
| let x: Int = foo(p: param1)
|}""".stripMargin

"dumping code from content" should {
implicit val finder: NodeExtensionFinder = DefaultNodeExtensionFinder

val cpg = code(codeString, "test.swift").withConfig(Config().withDisableFileContent(false))
val cpg = code("""
|// A comment
|func my_func(param1: Int) -> Int {
| let x: Int = foo(p: param1)
|}""".stripMargin).withConfig(Config().withDisableFileContent(false))

"allow one to dump a method node's source code from `File.contents`" in {
inside(cpg.method.nameExact("my_func").dumpRaw.l) {
case content :: Nil =>
content.linesIterator.map(_.strip).l shouldBe List(
"func my_func(param1: Int) -> Int { /* <=== test.swift:<global>:my_func */",
"func my_func(param1: Int) -> Int { /* <=== Test0.swift:<global>:my_func */",
"let x: Int = foo(p: param1)",
"}"
)
Expand All @@ -30,4 +28,38 @@ class CodeDumperFromContentTest extends SwiftSrc2CpgSuite {
}
}

"code from method content" should {
val myFuncContent =
"""func my_func(param1: Int) -> Int {
| let x: Int = foo(p: param1)
|}""".stripMargin

val cpg = code(s"""
|// A comment
|$myFuncContent
|""".stripMargin).withConfig(Config().withDisableFileContent(false))

"allow one to dump a method node's source code from `Method.content`" in {
val List(content) = cpg.method.nameExact("my_func").content.l
content shouldBe myFuncContent
}
}

"code from typedecl content" should {
val myClassContent =
"""class Foo {
| var x = 'foo';
|}""".stripMargin

val cpg = code(s"""
|// A comment
|$myClassContent
|""".stripMargin).withConfig(Config().withDisableFileContent(false))

"allow one to dump a typedecl node's source code from `TypeDecl.content`" in {
val List(content) = cpg.typeDecl.nameExact("Foo").content.l
content shouldBe myClassContent
}
}

}

0 comments on commit d9efef9

Please sign in to comment.