Skip to content

Commit 937cdcc

Browse files
committed
Merge branch 'master' into andrei/php-implement-use-trait
2 parents 303f9dd + b05324a commit 937cdcc

File tree

10 files changed

+529
-64
lines changed

10 files changed

+529
-64
lines changed

joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class AstCreator(
382382
val declarationsAsts = ktFile.getDeclarations.asScala.flatMap(astsForDeclaration)
383383
val fileNode = NewFile().name(fileWithMeta.relativizedPath)
384384
if (!disableFileContent) {
385-
fileNode.content(code(fileWithMeta.f))
385+
fileNode.content(fileWithMeta.f.getText)
386386
}
387387
val lambdaTypeDecls =
388388
lambdaBindingInfoQueue.flatMap(_.edgeMeta.collect { case (node: NewTypeDecl, _, _) => Ast(node) })

joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreatorHelper.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,19 @@ trait AstCreatorHelper(disableFileContent: Boolean)(implicit withSchemaValidatio
213213
}
214214

215215
protected def isBuiltinFunc(name: String): Boolean = PhpBuiltins.FuncNames.contains(name)
216+
217+
protected def createListExprCodeField(listExpr: PhpListExpr): String = {
218+
val name = PhpOperators.listFunc
219+
val args = listExpr.items.flatten
220+
.map {
221+
case PhpArrayItem(_, _ @PhpVariable(name: PhpNameExpr, _), _, _, _) => s"$$${name.name}"
222+
case PhpArrayItem(_, value: PhpListExpr, _, _, _) => createListExprCodeField(value)
223+
case x =>
224+
logger.warn(s"Invalid arg type for code field: ${x.getClass}")
225+
""
226+
}
227+
.mkString(",")
228+
229+
s"$name($args)"
230+
}
216231
}

joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstForControlStructuresCreator.scala

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,13 @@ trait AstForControlStructuresCreator(implicit withSchemaValidation: ValidationMo
179179
val localN = handleVariableOccurrence(stmt, iterIdentifier.name)
180180

181181
// keep this just used to construct the `code` field
182-
val assignItemTargetAst = stmt.keyVar match {
183-
case Some(key) => astForKeyValPair(stmt, key, stmt.valueVar)
184-
case None => astForExpr(stmt.valueVar)
182+
val assignItemTargetString = stmt.keyVar match {
183+
case Some(key) => astForKeyValPair(stmt, key, stmt.valueVar).rootCodeOrEmpty
184+
case None =>
185+
stmt.valueVar match {
186+
case x: PhpListExpr => createListExprCodeField(x)
187+
case x => astForExpr(x).rootCodeOrEmpty
188+
}
185189
}
186190

187191
// Initializer asts
@@ -193,15 +197,57 @@ trait AstForControlStructuresCreator(implicit withSchemaValidation: ValidationMo
193197
// - Assigned item assign
194198
val itemInitAst = getItemAssignAstForForeach(stmt, iterIdentifier.copy)
195199

196-
// Condition ast
197200
val isNullName = PhpOperators.isNull
198-
val valueAst = astForExpr(stmt.valueVar)
199-
val isNullCode = s"$isNullName(${valueAst.rootCodeOrEmpty})"
200-
val isNullCall = operatorCallNode(stmt, isNullCode, isNullName, Some(TypeConstants.Bool))
201-
.methodFullName(PhpOperators.isNull)
202-
val notIsNull = operatorCallNode(stmt, s"!$isNullCode", Operators.logicalNot, None)
203-
val isNullAst = callAst(isNullCall, valueAst :: Nil)
204-
val conditionAst = callAst(notIsNull, isNullAst :: Nil)
201+
202+
def createNotNullChecks(valueVar: PhpExpr): Ast = {
203+
valueVar match {
204+
case x: PhpListExpr =>
205+
x.items match {
206+
case List(None, _*) => Ast()
207+
case Some(head) :: Nil => createNotNullChecks(head) // only one item in the listExpr
208+
case Some(head) :: tail =>
209+
val headItem = createNotNullChecks(head)
210+
tail
211+
.filter(_.isDefined)
212+
.foldLeft(headItem)((previousVar, currentVar) => {
213+
currentVar.get match {
214+
case PhpArrayItem(_, value: (PhpVariable | PhpListExpr), _, _, _) =>
215+
val notCall = value match {
216+
case _: PhpVariable => createNotNullCall(value)
217+
case _: PhpListExpr => createNotNullChecks(value)
218+
}
219+
val callNode = operatorCallNode(
220+
currentVar.get,
221+
s"${previousVar.rootCodeOrEmpty} || ${notCall.rootCodeOrEmpty}",
222+
Operators.or,
223+
None
224+
)
225+
callAst(callNode, List(previousVar, notCall))
226+
case x =>
227+
logger.warn(s"Invalid PhpArrayItem.Value: ${x.value.getClass}")
228+
Ast()
229+
}
230+
})
231+
case Nil => Ast()
232+
}
233+
case PhpArrayItem(_, value: PhpVariable, _, _, _) => createNotNullCall(value)
234+
case PhpArrayItem(_, value: PhpListExpr, _, _, _) => createNotNullChecks(value)
235+
case x =>
236+
createNotNullCall(x)
237+
}
238+
}
239+
240+
def createNotNullCall(valueVar: PhpExpr): Ast = {
241+
val valueAst = astForExpr(valueVar)
242+
val isNullCode = s"$isNullName(${valueAst.rootCodeOrEmpty})"
243+
val isNullCall = operatorCallNode(stmt, isNullCode, isNullName, Some(TypeConstants.Bool))
244+
.methodFullName(PhpOperators.isNull)
245+
val notIsNull = operatorCallNode(stmt, s"!$isNullCode", Operators.logicalNot, None)
246+
val isNullAst = callAst(isNullCall, valueAst :: Nil)
247+
callAst(notIsNull, isNullAst :: Nil)
248+
}
249+
250+
val conditionAst = createNotNullChecks(stmt.valueVar)
205251

206252
// Update asts
207253
val nextIterIdent = astForIdentifierWithLocalRef(iterIdentifier.copy, localN)
@@ -219,7 +265,7 @@ trait AstForControlStructuresCreator(implicit withSchemaValidation: ValidationMo
219265
val bodyAst = stmtBodyBlockAst(stmt)
220266

221267
val ampPrefix = if (stmt.assignByRef) "&" else ""
222-
val foreachCode = s"foreach (${iterValue.rootCodeOrEmpty} as $ampPrefix${assignItemTargetAst.rootCodeOrEmpty})"
268+
val foreachCode = s"foreach (${iterValue.rootCodeOrEmpty} as $ampPrefix${assignItemTargetString})"
223269
val foreachNode = controlStructureNode(stmt, ControlStructureTypes.FOR, foreachCode)
224270
Ast(foreachNode)
225271
.withChild(wrapMultipleInBlock(iteratorAssignAst :: itemInitAst :: Nil, line(stmt)))
@@ -258,7 +304,13 @@ trait AstForControlStructuresCreator(implicit withSchemaValidation: ValidationMo
258304
} else {
259305
currentCallAst
260306
}
261-
simpleAssignAst(stmt, astForExpr(stmt.valueVar), valueAst)
307+
308+
stmt.valueVar match {
309+
case target: PhpListExpr =>
310+
astForArrayUnpack(stmt, target, valueAst)
311+
case target =>
312+
simpleAssignAst(stmt, astForExpr(target), valueAst)
313+
}
262314
}
263315

264316
// try to create assignment for key-part

joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstForExpressionsCreator.scala

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
3333
case evalExpr: PhpEvalExpr => astForEval(evalExpr)
3434
case exitExpr: PhpExitExpr => astForExit(exitExpr)
3535
case arrayExpr: PhpArrayExpr => astForArrayExpr(arrayExpr)
36-
case listExpr: PhpListExpr => astForListExpr(listExpr)
3736
case newExpr: PhpNewExpr => astForNewExpr(newExpr)
3837
case matchExpr: PhpMatchExpr => astForMatchExpr(matchExpr)
3938
case yieldExpr: PhpYieldExpr => astForYieldExpr(yieldExpr)
@@ -147,7 +146,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
147146
// Rewrite `$xs[] = <value_expr>` as `array_push($xs, <value_expr>)` to simplify finding dataflows.
148147
astForEmptyArrayDimAssign(assignment, arrayDimFetch)
149148
case arrayExpr: (PhpArrayExpr | PhpListExpr) =>
150-
astForArrayUnpack(assignment, arrayExpr)
149+
astForArrayUnpack(assignment, arrayExpr, astForExpr(assignment.source))
151150
case _ =>
152151
val operatorName = assignment.assignOp
153152

@@ -241,7 +240,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
241240

242241
/** Lower the array/list unpack. For example `[$a, $b] = $arr;` will be lowered to `$a = $arr[0]; $b = $arr[1];`
243242
*/
244-
private def astForArrayUnpack(assignment: PhpAssignment, target: PhpArrayExpr | PhpListExpr): Ast = {
243+
protected def astForArrayUnpack(assignment: PhpNode, target: PhpArrayExpr | PhpListExpr, sourceAst: Ast): Ast = {
245244
val loweredAssignNodes = mutable.ListBuffer.empty[Ast]
246245

247246
// create a Identifier ast for given name
@@ -316,7 +315,6 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
316315
}
317316
}
318317

319-
val sourceAst = astForExpr(assignment.source)
320318
val itemsOf = (exp: PhpArrayExpr | PhpListExpr) =>
321319
exp match {
322320
case x: PhpArrayExpr => x.items
@@ -513,22 +511,32 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
513511
case other => (other, None)
514512
}
515513

516-
val fieldAst = fieldNodeAndName(expr.name) match {
517-
case (other, None) =>
518-
logger.warn(s"Unable to determine field identifier node from ${other.getClass} (parent node $expr)")
519-
astForExpr(other)
520-
case (expr, Some(name)) => Ast(fieldIdentifierNode(expr, name.stripPrefix("$"), name))
514+
val (fieldNode, fieldName) = fieldNodeAndName(expr.name)
515+
516+
val fieldAst = fieldName match {
517+
case None =>
518+
astForExpr(fieldNode)
519+
case Some(name) => Ast(fieldIdentifierNode(expr, name.stripPrefix("$"), name))
520+
}
521+
522+
val operatorName = fieldName match {
523+
case Some(_) => Operators.fieldAccess
524+
case None => Operators.indexAccess
521525
}
522526

523527
val accessSymbol =
524528
if (expr.isStatic) StaticMethodDelimiter
525529
else if (expr.isNullsafe) s"?$InstanceMethodDelimiter"
526530
else InstanceMethodDelimiter
527531

528-
val targetAst = astForExpr(expr.expr)
529-
val targetCode = targetAst.rootCodeOrEmpty
530-
val code = s"$targetCode$accessSymbol${fieldAst.rootCodeOrEmpty}"
531-
val fieldAccessNode = operatorCallNode(expr, code, Operators.fieldAccess, None)
532+
val targetAst = astForExpr(expr.expr)
533+
val targetCode = targetAst.rootCodeOrEmpty
534+
val fieldAstCode = fieldName match {
535+
case Some(_) => fieldAst.rootCodeOrEmpty
536+
case None => s"{${fieldAst.rootCodeOrEmpty}}"
537+
}
538+
val code = s"$targetCode$accessSymbol$fieldAstCode"
539+
val fieldAccessNode = operatorCallNode(expr, code, operatorName, None)
532540
callAst(fieldAccessNode, Seq(targetAst, fieldAst))
533541
}
534542

@@ -695,39 +703,6 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
695703
}
696704
}
697705

698-
private def astForListExpr(expr: PhpListExpr): Ast = {
699-
/* TODO: Handling list in a way that will actually work with dataflow tracking is somewhat more complicated than
700-
* this and will likely need a fairly ugly lowering.
701-
*
702-
* In short, the case:
703-
* list($a, $b) = $arr;
704-
* can be lowered to:
705-
* $a = $arr[0];
706-
* $b = $arr[1];
707-
*
708-
* the case:
709-
* list("id" => $a, "name" => $b) = $arr;
710-
* can be lowered to:
711-
* $a = $arr["id"];
712-
* $b = $arr["name"];
713-
*
714-
* and the case:
715-
* foreach ($arr as list($a, $b)) { ... }
716-
* can be lowered as above for each $arr[i];
717-
*
718-
* The below is just a placeholder to prevent crashes while figuring out the cleanest way to
719-
* implement the above lowering or to think of a better way to do it.
720-
*/
721-
722-
val name = PhpOperators.listFunc
723-
val args = expr.items.flatten.map { item => astForExpr(item.value) }
724-
val listCode = s"$name(${args.map(_.rootCodeOrEmpty).mkString(",")})"
725-
val listNode = operatorCallNode(expr, listCode, name, None)
726-
.methodFullName(PhpOperators.listFunc)
727-
728-
callAst(listNode, args)
729-
}
730-
731706
private def astForNewExpr(expr: PhpNewExpr): Ast = {
732707
expr.className match {
733708
case classLikeStmt: PhpClassLikeStmt =>

joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/dataflow/IntraMethodDataflowTests.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,32 @@ class IntraMethodDataflowTests extends PhpCode2CpgFixture(runOssDataflow = true)
7575
val flows = sink.reachableByFlows(source)
7676
flows.size shouldBe 2
7777
}
78+
79+
"flow from list unpacking in foreach loop" in {
80+
val cpg = code("""<?php
81+
|foreach($arr as list($a, $b)) {
82+
| echo $a;
83+
| echo $b;
84+
|}
85+
|""".stripMargin)
86+
val source = cpg.identifier("arr")
87+
val sink = cpg.call("echo").argument(1)
88+
val flows = sink.reachableByFlows(source)
89+
flows.size shouldBe 2
90+
}
91+
92+
"flow from nested list unpacking in foreach loop" in {
93+
val cpg = code("""<?php
94+
|foreach($arr as list($a, list($b, $c))) {
95+
| echo $a;
96+
| echo $b;
97+
| echo $c;
98+
| }
99+
|""".stripMargin)
100+
101+
val source = cpg.identifier("arr")
102+
val sink = cpg.call("echo").argument(1)
103+
val flows = sink.reachableByFlows(source)
104+
flows.size shouldBe 3
105+
}
78106
}

0 commit comments

Comments
 (0)