forked from joernio/joern
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FieldAccessLinkerPass: A Base Pass for Field Access -> Member (joerni…
…o#3979) Given there is a `CALL`.referencedMember step, it suggests that the schema requires a REF edge between all calls accessing fields to the referenced member node. This, however, is not the case in general and is neglected to be implemented consistently throughout frontends. The problem is fairly language agnostic, and one can rely on the `EVAL_TYPE` edge on the base of a field access along with the `FIELD_IDENTIFIER` to resolve the referenced member. This pass executes the above, to create a reference edge between a field access and its referencing member. Resolves joernio#3950
- Loading branch information
1 parent
e6bf9d0
commit 5010415
Showing
3 changed files
with
108 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 3 additions & 2 deletions
5
joern-cli/frontends/x2cpg/src/main/scala/io/joern/x2cpg/layers/TypeRelations.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
...ends/x2cpg/src/main/scala/io/joern/x2cpg/passes/typerelations/FieldAccessLinkerPass.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package io.joern.x2cpg.passes.typerelations | ||
|
||
import io.joern.x2cpg.passes.frontend.Dereference | ||
import io.joern.x2cpg.utils.LinkingUtil | ||
import io.shiftleft.codepropertygraph.generated.nodes.{Call, Member, StoredNode} | ||
import io.shiftleft.codepropertygraph.generated.{Cpg, EdgeTypes, NodeTypes, PropertyNames} | ||
import io.shiftleft.passes.CpgPass | ||
import io.shiftleft.semanticcpg.language.* | ||
import io.shiftleft.semanticcpg.language.operatorextension.OpNodes | ||
import io.shiftleft.semanticcpg.utils.MemberAccess | ||
import org.slf4j.LoggerFactory | ||
|
||
import scala.jdk.CollectionConverters.* | ||
|
||
/** Links field access calls to the field they are accessing to enable the `cpg.fieldAccess.referencedMember` step. | ||
*/ | ||
class FieldAccessLinkerPass(cpg: Cpg) extends CpgPass(cpg) with LinkingUtil { | ||
|
||
private val logger = LoggerFactory.getLogger(getClass) | ||
private val DOT = "." | ||
|
||
override def run(dstGraph: DiffGraphBuilder): Unit = { | ||
linkToMultiple( | ||
cpg, | ||
srcLabels = List(NodeTypes.CALL), | ||
dstNodeLabel = NodeTypes.MEMBER, | ||
edgeType = EdgeTypes.REF, | ||
dstNodeMap = typeDeclMemberToNode(cpg, _), | ||
getDstFullNames = (call: Call) => dstMemberFullNames(call), | ||
dstFullNameKey = PropertyNames.NAME, | ||
dstGraph | ||
) | ||
} | ||
|
||
private def dstMemberFullNames(call: Call): Seq[String] = { | ||
if (MemberAccess.isFieldAccess(call.name)) { | ||
val fieldAccess = call.asInstanceOf[OpNodes.FieldAccess] | ||
fieldAccess.argumentOption(1) match | ||
case Some(baseNode) => | ||
fieldAccess.fieldIdentifier.canonicalName.headOption match | ||
case Some(fieldName) => | ||
baseNode.evalType.map(x => s"$x$DOT$fieldName").toSeq | ||
case None => | ||
logger.warn(s"Field access ${fieldAccess.code} has no field identifier") | ||
Seq.empty | ||
case None => | ||
logger.warn(s"Field access ${fieldAccess.code} has no base node") | ||
Seq.empty | ||
} else { | ||
Seq.empty | ||
} | ||
} | ||
|
||
private def typeDeclMemberToNode(cpg: Cpg, fieldFullName: String): Option[Member] = { | ||
val (typeFullName, fieldName) = fieldFullName.splitAt(fieldFullName.lastIndexOf(DOT)) | ||
typeDeclFullNameToNode(cpg, typeFullName).member.nameExact(fieldName.stripPrefix(DOT)).headOption | ||
} | ||
|
||
// This is overridden to avoid the step that sets the `dstFullNameKey` property. | ||
override def linkToMultiple[SRC_NODE_TYPE <: StoredNode]( | ||
cpg: Cpg, | ||
srcLabels: List[String], | ||
dstNodeLabel: String, | ||
edgeType: String, | ||
dstNodeMap: String => Option[StoredNode], | ||
getDstFullNames: SRC_NODE_TYPE => Iterable[String], | ||
dstFullNameKey: String, | ||
dstGraph: DiffGraphBuilder | ||
): Unit = { | ||
val dereference = Dereference(cpg) | ||
cpg.graph.nodes(srcLabels: _*).asScala.cast[SRC_NODE_TYPE].filterNot(_.outE(edgeType).hasNext).foreach { srcNode => | ||
if (!srcNode.outE(edgeType).hasNext) { | ||
getDstFullNames(srcNode).foreach { dstFullName => | ||
val dereferenceDstFullName = dereference.dereferenceTypeFullName(dstFullName) | ||
dstNodeMap(dereferenceDstFullName) match { | ||
case Some(dstNode) => | ||
dstGraph.addEdge(srcNode, dstNode, edgeType) | ||
case None if dstNodeMap(dstFullName).isDefined => | ||
dstGraph.addEdge(srcNode, dstNodeMap(dstFullName).get, edgeType) | ||
case None => | ||
logFailedDstLookup(edgeType, srcNode.label, srcNode.id.toString, dstNodeLabel, dereferenceDstFullName) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
} |