Skip to content

Commit

Permalink
Attempt to implement dynamic dispatch and dynamic call linking for c/…
Browse files Browse the repository at this point in the history
…c++ (#46)

Signed-off-by: Prabhu Subramanian <[email protected]>
  • Loading branch information
prabhu authored Dec 21, 2023
1 parent 370aa44 commit 5d32fe8
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 31 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name := "chen"
ThisBuild / organization := "io.appthreat"
ThisBuild / version := "1.1.1"
ThisBuild / version := "1.1.2"
ThisBuild / scalaVersion := "3.3.1"

val cpgVersion = "1.4.22"
Expand Down
2 changes: 1 addition & 1 deletion codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"downloadUrl": "https://github.com/AppThreat/chen",
"issueTracker": "https://github.com/AppThreat/chen/issues",
"name": "chen",
"version": "1.1.1",
"version": "1.1.2",
"description": "Code Hierarchy Exploration Net (chen) is an advanced exploration toolkit for your application source code and its dependency hierarchy.",
"applicationCategory": "code-analysis",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion meta.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% set version = "1.1.1" %}
{% set version = "1.1.2" %}

package:
name: chen
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package io.appthreat.c2cpg.astcreation

import io.appthreat.c2cpg.datastructures.CGlobal
import io.appthreat.c2cpg.parser.FileDefaults
import io.shiftleft.codepropertygraph.generated.nodes.{ExpressionNew, NewNode}
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}
import io.appthreat.x2cpg.{Ast, SourceFiles, ValidationMode}
import io.appthreat.x2cpg.utils.NodeBuilders.newDependencyNode
import io.shiftleft.codepropertygraph.generated.EdgeTypes
import io.appthreat.x2cpg.{Ast, SourceFiles, ValidationMode}
import io.shiftleft.codepropertygraph.generated.nodes.{ExpressionNew, NewNode}
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EdgeTypes, Operators}
import io.shiftleft.utils.IOUtils
import org.apache.commons.lang.StringUtils
import org.eclipse.cdt.core.dom.ast.*
Expand All @@ -18,12 +16,8 @@ import org.eclipse.cdt.core.dom.ast.c.{
import org.eclipse.cdt.core.dom.ast.cpp.*
import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator
import org.eclipse.cdt.internal.core.dom.parser.c.CASTArrayRangeDesignator
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalBinding
import org.eclipse.cdt.internal.core.dom.parser.cpp.{CPPASTIdExpression, CPPFunction}
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTArrayRangeDesignator
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalMemberAccess
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFieldReference
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPMethod
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.{EvalBinding, EvalMemberAccess}
import org.eclipse.cdt.internal.core.dom.parser.cpp.*
import org.eclipse.cdt.internal.core.model.ASTStringUtil

import java.nio.file.{Path, Paths}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,16 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode):
case IASTBinaryExpression.op_ellipses => "<operator>.op_ellipses"
case _ => "<operator>.unknown"

val callNode_ = callNode(bin, nodeSignature(bin), op, op, DispatchTypes.STATIC_DISPATCH)
val left = nullSafeAst(bin.getOperand1)
val right = nullSafeAst(bin.getOperand2)
val callNode_ = callNode(
bin,
nodeSignature(bin),
op,
op,
if op == Operators.indirectFieldAccess then DispatchTypes.DYNAMIC_DISPATCH
else DispatchTypes.STATIC_DISPATCH
)
val left = nullSafeAst(bin.getOperand1)
val right = nullSafeAst(bin.getOperand2)
callAst(callNode_, List(left, right))
end astForBinaryExpression

Expand Down Expand Up @@ -91,7 +98,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode):
val (dd, name) = call.getFunctionNameExpression match
case _: ICPPASTLambdaExpression =>
(
DispatchTypes.STATIC_DISPATCH,
DispatchTypes.DYNAMIC_DISPATCH,
rec.root.get.asInstanceOf[NewMethodRef].methodFullName
)
case _ if rec.root.exists(_.isInstanceOf[NewIdentifier]) =>
Expand Down Expand Up @@ -164,7 +171,9 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode):
nodeSignature(unary),
operatorMethod,
operatorMethod,
DispatchTypes.STATIC_DISPATCH
if operatorMethod == Operators.addressOf || operatorMethod == Operators.indirectFieldAccess
then DispatchTypes.DYNAMIC_DISPATCH
else DispatchTypes.STATIC_DISPATCH
)
val operand = nullSafeAst(unary.getOperand)
callAst(cpgUnary, List(operand))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ trait AstForPrimitivesCreator(implicit withSchemaValidation: ValidationMode):
protected def astForFieldReference(fieldRef: IASTFieldReference): Ast =
val op = if fieldRef.isPointerDereference then Operators.indirectFieldAccess
else Operators.fieldAccess
val ma = callNode(fieldRef, nodeSignature(fieldRef), op, op, DispatchTypes.STATIC_DISPATCH)
val ma = callNode(
fieldRef,
nodeSignature(fieldRef),
op,
op,
if fieldRef.isPointerDereference then DispatchTypes.DYNAMIC_DISPATCH
else DispatchTypes.STATIC_DISPATCH
)
val owner = astForExpression(fieldRef.getFieldOwner)
val member = fieldIdentifierNode(
fieldRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,21 @@ trait AstForTypesCreator(implicit withSchemaValidation: ValidationMode):
init match
case i: IASTEqualsInitializer =>
val operatorName = Operators.assignment
val left = astForNode(declarator.getName)
val right = astForNode(i.getInitializerClause)
val code = i.getInitializerClause.getRawSignature;
val dispatchType =
if code.nonEmpty && (code.startsWith("&") || code.contains("->")) then
DispatchTypes.DYNAMIC_DISPATCH
else DispatchTypes.STATIC_DISPATCH
val callNode_ =
callNode(
declarator,
nodeSignature(declarator),
operatorName,
operatorName,
DispatchTypes.STATIC_DISPATCH
dispatchType
)
val left = astForNode(declarator.getName)
val right = astForNode(i.getInitializerClause)
callAst(callNode_, List(left, right))
case i: ICPPASTConstructorInitializer =>
val name = ASTStringUtil.getSimpleName(declarator.getName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class AstCreationPassTests extends AbstractPassTest {
inside(cpg.call(lambda2Name).l) { case List(lambda2call) =>
lambda2call.name shouldBe lambda2Name
lambda2call.methodFullName shouldBe lambda2Name
// TODO: lambda2call.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH
lambda2call.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH
inside(lambda2call.astChildren.l) { case List(ref: MethodRef, lit: Literal) =>
ref.methodFullName shouldBe lambda2Name
ref.code should startWith("[](int n) -> int")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,46 @@ class CallGraphQueryTests extends CCodeToCpgSuite {
cpg.method.name("printf").callIn.argument.inCall.name.toSetMutable shouldBe Set("printf")
}

"CallTest 6" should {
val cpg = code(
"""
|class Animal {
| public:
| virtual void eat() {
| std::cout<<"eat nothing";
| }
|};
|class Cat:public Animal {
| public:
| void eat() {
| std::cout<<"eat fish";
| }
|};
|class People {
| Cat d;
| Animal *pet = &d;
| void eat() {
| std::cout<<"people eat";
| }
| void feedPets() {
| pet->eat();
| }
|}
|int main( )
|{
| Cat c;
| Animal *a = &c;
| a->eat();
| return 0;
|}
|""".stripMargin,
"test.cpp"
)
"have correct type full names for calls" in {
cpg.method.name("eat").fullName.toSet shouldBe Set("Animal.eat", "Cat.eat", "People.eat")
cpg.method.fullName("main").callee.fullName.toSet shouldBe Set("Animal.eat", "Cat.eat")
cpg.method.fullName("Cat.eat").caller.fullName.toSet shouldBe Set("main")
cpg.method.fullName("Animal.eat").caller.fullName.toSet shouldBe Set("People.feedPets", "main")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,13 @@ class DynamicCallLinker(cpg: Cpg) extends CpgPass(cpg):
case None => None

private def resolveCallInSuperClasses(call: Call): Boolean =
if !call.methodFullName.contains(":") then return false
if !call.methodFullName.contains(":") && !call.methodFullName.contains(".") then
return false
def split(str: String, n: Int) = (str.take(n), str.drop(n + 1))
val (fullName, signature) = split(call.methodFullName, call.methodFullName.lastIndexOf(":"))
val typeDeclFullName = fullName.replace(s".${call.name}", "")
val (fullName, signature) = if call.methodFullName.contains(":") then
split(call.methodFullName, call.methodFullName.lastIndexOf(":"))
else split(call.methodFullName, call.methodFullName.lastIndexOf("."))
val typeDeclFullName = fullName.replace(s".${call.name}", "")
val candidateInheritedMethods =
cpg.typeDecl
.fullNameExact(allSuperClasses(typeDeclFullName).toIndexedSeq*)
Expand All @@ -159,8 +162,30 @@ class DynamicCallLinker(cpg: Cpg) extends CpgPass(cpg):
)
)
true
else if call.methodFullName == "<operator>.indirectFieldAccess" then
val calledMethodName = call.argument.last.code
val fieldTypes = call.argument.head.typ.l
fieldTypes.foreach { ft =>
val ftSubClasses = allSubclasses(ft.fullName).filterNot(cn => cn == ft.fullName)
if ftSubClasses.nonEmpty then
val candidateSubTypeMethods = cpg.typeDecl.fullNameExact(
ftSubClasses.toIndexedSeq*
).astChildren.isMethod.name(calledMethodName).fullName.l
if candidateSubTypeMethods.nonEmpty then
validM.put(
calledMethodName,
validM.getOrElse(
calledMethodName,
mutable.LinkedHashSet.empty
) ++ mutable.LinkedHashSet.from(
candidateSubTypeMethods
)
)
}
true
else
false
end if
end resolveCallInSuperClasses

private def linkDynamicCall(call: Call, dstGraph: DiffGraphBuilder): Unit =
Expand All @@ -170,9 +195,11 @@ class DynamicCallLinker(cpg: Cpg) extends CpgPass(cpg):
)
then return
// Support for overriding
resolveCallInSuperClasses(call)

validM.get(call.methodFullName) match
val resolved = resolveCallInSuperClasses(call)
var methodNameToUse = call.methodFullName
if call.methodFullName.startsWith("<operator>") && resolved then
methodNameToUse = call.argument.last.code
validM.get(methodNameToUse) match
case Some(tgts) =>
val callsOut = call.callOut.fullName.toSetImmutable
val tgtMs = tgts
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "appthreat-chen"
version = "1.1.1"
version = "1.1.2"
description = "Code Hierarchy Exploration Net (chen)"
authors = ["Team AppThreat <[email protected]>"]
license = "Apache-2.0"
Expand Down

0 comments on commit 5d32fe8

Please sign in to comment.