Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to implement dynamic dispatch and dynamic call linking for c/c++ #46

Merged
merged 1 commit into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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