diff --git a/build.sbt b/build.sbt index 0e634637..654e7914 100644 --- a/build.sbt +++ b/build.sbt @@ -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" diff --git a/codemeta.json b/codemeta.json index f59c6ab0..2b883b13 100644 --- a/codemeta.json +++ b/codemeta.json @@ -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": [ diff --git a/meta.yaml b/meta.yaml index e1dbb3c0..0b78cc0e 100644 --- a/meta.yaml +++ b/meta.yaml @@ -1,4 +1,4 @@ -{% set version = "1.1.1" %} +{% set version = "1.1.2" %} package: name: chen diff --git a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala index 8cf48334..747ae14a 100644 --- a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala +++ b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala @@ -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.* @@ -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} diff --git a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForExpressionsCreator.scala b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForExpressionsCreator.scala index 18059ac0..9ae0790d 100644 --- a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForExpressionsCreator.scala +++ b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForExpressionsCreator.scala @@ -50,9 +50,16 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode): case IASTBinaryExpression.op_ellipses => ".op_ellipses" case _ => ".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 @@ -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]) => @@ -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)) diff --git a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForPrimitivesCreator.scala b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForPrimitivesCreator.scala index c2ee5ca5..1837c5db 100644 --- a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForPrimitivesCreator.scala +++ b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForPrimitivesCreator.scala @@ -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, diff --git a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForTypesCreator.scala b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForTypesCreator.scala index 5365f4f8..f53b24f4 100644 --- a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForTypesCreator.scala +++ b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForTypesCreator.scala @@ -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) diff --git a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/ast/AstCreationPassTests.scala b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/ast/AstCreationPassTests.scala index da96b0e1..4470c76f 100644 --- a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/ast/AstCreationPassTests.scala +++ b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/ast/AstCreationPassTests.scala @@ -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") diff --git a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/querying/CallGraphQueryTests.scala b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/querying/CallGraphQueryTests.scala index 1478e053..297ddc4c 100644 --- a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/querying/CallGraphQueryTests.scala +++ b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/querying/CallGraphQueryTests.scala @@ -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") + } + } } diff --git a/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/callgraph/DynamicCallLinker.scala b/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/callgraph/DynamicCallLinker.scala index 9565d0ea..9fb39ffe 100644 --- a/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/callgraph/DynamicCallLinker.scala +++ b/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/callgraph/DynamicCallLinker.scala @@ -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*) @@ -159,8 +162,30 @@ class DynamicCallLinker(cpg: Cpg) extends CpgPass(cpg): ) ) true + else if call.methodFullName == ".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 = @@ -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("") && resolved then + methodNameToUse = call.argument.last.code + validM.get(methodNameToUse) match case Some(tgts) => val callsOut = call.callOut.fullName.toSetImmutable val tgtMs = tgts diff --git a/pyproject.toml b/pyproject.toml index a754d41f..2d722c45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 "] license = "Apache-2.0"