Skip to content

Commit

Permalink
Merge pull request #780 from wbqpk3/afferent-coupling
Browse files Browse the repository at this point in the history
Added afferent coupling for types.
  • Loading branch information
mcserep authored Feb 11, 2025
2 parents 2f0cd48 + fa16bab commit 8dec0d0
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 4 deletions.
9 changes: 9 additions & 0 deletions plugins/cpp/model/include/model/cpprecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ struct CppRecordCount
std::size_t count;
};

#pragma db view \
object(CppMemberType) \
object(CppAstNode : CppMemberType::memberAstNode == CppAstNode::id)
struct CppMemberTypeAstView
{
#pragma db column(CppMemberType::typeHash)
std::uint64_t typeHash;
};

}
}

Expand Down
3 changes: 2 additions & 1 deletion plugins/cpp_metrics/model/include/model/cppastnodemetrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct CppAstNodeMetrics
BUMPY_ROAD = 4,
LACK_OF_COHESION = 5,
LACK_OF_COHESION_HS = 6,
EFFERENT_TYPE = 7
EFFERENT_TYPE = 7,
AFFERENT_TYPE = 8
};

#pragma db id auto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class CppMetricsParser : public AbstractParser
void lackOfCohesion();
// Calculate the efferent coupling of types.
void efferentTypeLevel();
// Calculate the afferent coupling of types.
void afferentTypeLevel();


/// @brief Constructs an ODB query that you can use to filter only
Expand Down Expand Up @@ -200,6 +202,7 @@ class CppMetricsParser : public AbstractParser
static const int functionBumpyRoadPartitionMultiplier = 5;
static const int lackOfCohesionPartitionMultiplier = 25;
static const int efferentCouplingTypesPartitionMultiplier = 5;
static const int afferentCouplingTypesPartitionMultiplier = 5;
};

} // parser
Expand Down
87 changes: 87 additions & 0 deletions plugins/cpp_metrics/parser/src/cppmetricsparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <model/cppfilemetrics-odb.hxx>
#include <model/cppinheritance.h>
#include <model/cppinheritance-odb.hxx>
#include <model/cpprecord.h>
#include <model/cpprecord-odb.hxx>

#include <model/cppastnode.h>
#include <model/cppastnode-odb.hxx>
Expand Down Expand Up @@ -428,6 +430,89 @@ void CppMetricsParser::efferentTypeLevel()
});
}

void CppMetricsParser::afferentTypeLevel()
{
parallelCalcMetric<model::CohesionCppRecordView>(
"Afferent coupling of types",
_threadCount * afferentCouplingTypesPartitionMultiplier,// number of jobs; adjust for granularity
getFilterPathsQuery<model::CohesionCppRecordView>(),
[&, this](const MetricsTasks<model::CohesionCppRecordView>& tasks)
{
util::OdbTransaction{_ctx.db}([&, this]
{
typedef odb::query<cc::model::CppAstNode> AstQuery;
typedef odb::query<cc::model::CppInheritance> InheritanceQuery;
typedef odb::query<cc::model::CppMemberType> MemTypeQuery;
typedef odb::result<cc::model::CppAstNode> AstResult;
typedef odb::result<cc::model::CppMemberTypeAstView> MemTypeAstResult;

std::set<std::uint64_t> dependentTypes;
for (const model::CohesionCppRecordView& type : tasks)
{
dependentTypes.clear();

// Find derived types
for (const model::CppInheritance& inheritance : _ctx.db->query<model::CppInheritance>(
InheritanceQuery::base == type.entityHash))
{
dependentTypes.insert(inheritance.derived);
}

// Find usages of the type
for (const model::CppAstNode& usage : _ctx.db->query<model::CppAstNode>(
AstQuery::entityHash == type.entityHash &&
AstQuery::location.range.end.line != model::Position::npos))
{
// Check if usage is in class member function or attribute
MemTypeAstResult memberNode = _ctx.db->query<model::CppMemberTypeAstView>(
AstQuery::symbolType.in(model::CppAstNode::SymbolType::Function, model::CppAstNode::SymbolType::Variable) &&
AstQuery::astType.in(model::CppAstNode::AstType::Definition, model::CppAstNode::AstType::Declaration) &&
AstQuery::location.file == usage.location.file.object_id() &&
AstQuery::location.range.start.line <= usage.location.range.start.line &&
AstQuery::location.range.end.line >= usage.location.range.end.line &&
MemTypeQuery::typeHash != usage.entityHash);

if (!memberNode.empty())
{
dependentTypes.insert(memberNode.begin()->typeHash);
} else {
// The usage can be in a member function defined outside of the class definition
// E.g. void ClassName::foo() { A a; }
// ^ usage here

// Find parent function
AstResult parentFunction = _ctx.db->query<model::CppAstNode>(
AstQuery::symbolType == model::CppAstNode::SymbolType::Function &&
AstQuery::astType == model::CppAstNode::AstType::Definition &&
AstQuery::location.file == usage.location.file.object_id() &&
AstQuery::location.range.start.line <= usage.location.range.start.line &&
AstQuery::location.range.end.line >= usage.location.range.end.line);

if (!parentFunction.empty())
{
// Find if the function is a member function of a class
MemTypeAstResult memberFunction = _ctx.db->query<model::CppMemberTypeAstView>(
AstQuery::entityHash == parentFunction.begin()->entityHash &&
MemTypeQuery::typeHash != usage.entityHash);

if (!memberFunction.empty())
{
dependentTypes.insert(memberFunction.begin()->typeHash);
}
}
}
}

model::CppAstNodeMetrics metric;
metric.astNodeId = type.astNodeId;
metric.type = model::CppAstNodeMetrics::Type::AFFERENT_TYPE;
metric.value = dependentTypes.size();
_ctx.db->persist(metric);
}
});
});
}

bool CppMetricsParser::parse()
{
LOG(info) << "[cppmetricsparser] Computing function parameter count metric.";
Expand All @@ -442,6 +527,8 @@ bool CppMetricsParser::parse()
lackOfCohesion();
LOG(info) << "[cppmetricsparser] Computing efferent coupling metric for types.";
efferentTypeLevel();
LOG(info) << "[cppmetricsparser] Computing afferent coupling metric for types.";
afferentTypeLevel();
return true;
}

Expand Down
5 changes: 3 additions & 2 deletions plugins/cpp_metrics/service/cxxmetrics.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ enum CppAstNodeMetricsType
BumpyRoad = 4,
LackOfCohesion = 5,
LackOfCohesionHS = 6,
EfferentType = 7
EfferentType = 7,
AfferentType = 8
}

enum CppModuleMetricsType
Expand Down Expand Up @@ -124,4 +125,4 @@ service CppMetricsService
* This function returns the names of module-level C++ metrics.
*/
list<CppModuleMetricsTypeName> getCppModuleMetricsTypeNames()
}
}
4 changes: 4 additions & 0 deletions plugins/cpp_metrics/service/src/cppmetricsservice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ void CppMetricsServiceHandler::getCppAstNodeMetricsTypeNames(
typeName.type = CppAstNodeMetricsType::EfferentType;
typeName.name = "Efferent coupling of type";
_return.push_back(typeName);

typeName.type = CppAstNodeMetricsType::AfferentType;
typeName.name = "Afferent coupling of type";
_return.push_back(typeName);
}

void CppMetricsServiceHandler::getCppModuleMetricsTypeNames(
Expand Down
3 changes: 2 additions & 1 deletion plugins/cpp_metrics/test/sources/parser/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ add_library(CppMetricsTestProject STATIC
functionmccabe.cpp
typemccabe.cpp
lackofcohesion.cpp
bumpyroad.cpp)
bumpyroad.cpp
afferentcoupling.cpp)
176 changes: 176 additions & 0 deletions plugins/cpp_metrics/test/sources/parser/afferentcoupling.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#include <memory>
#include <vector>

// Member types
class A {};
class A_D {
A a;
};

class A2 {};
class A2_D {
A2* a2;
};

class A3 {};
class A3_D {
A3& a3;
};

class A4 {};
class A4_D {
std::vector<A4> a4_vec;
};

class A5 {};
class A5_D {
std::unique_ptr<A5> a5_ptr;
};
// ==========

// Function parameters
class B {};
class B_D {
void foo(B b);
};

class B2 {};
class B2_D {
void foo(B2* b);
};

class B3 {};
class B3_D {
void foo(B3& b);
};

class B4 {};
class B4_D {
void foo(std::vector<B4>& b4_vec);
};

class B5 {};
class B5_D {
void foo(std::unique_ptr<B5>& b5_ptr);
};

class B6 {};
class B6_D {
void foo(int, bool, B6* b);
};

class B7 {};
class B7_D {
void foo(int, bool, B7* b);
};

class B8 {};
class B8_D {
void foo(int, bool);
void foo(int, bool, B8* b);
};
// ==========

// Function local variables
class C {};
class C_D {
void foo()
{
C c;
}
};

class C2 {};
class C2_D {
void foo()
{
auto c2 = C2();
}
};

class C3 {};
class C3_D {
void foo()
{
std::vector<C3> c3_vec;
}
};
// ==========

// Out-of-class member functions
class D {};
class D_D {
void foo();
};

void D_D::foo()
{
D d;
}

class D2 {};
class D2_D {
void foo();
};

void D2_D::foo()
{
std::unique_ptr<D2> d2_ptr;
}
// ==========

// Multiple usage of the same type
class E {};
class E_D {
E* e;

void foo()
{
E e;
}

void bar(std::vector<E> e_vec);
};
// ==========

// Inheritance
class F {};
class F_D : public F {};
// ==========

// Multi inheritance
class G {};
class G_C {};
class G_D : public G_C, public G {};
// ==========

// Multiple dependant types
class H {};
class H_D1 {
H h;
};

class H_D2 {
void foo()
{
std::vector<H> h_vec;
}
};

// ----------

class H2 {};
class H2_D1 {
H2 h2;
void bar(std::unique_ptr<H2> h2_ptr);
};

class H2_D2 {
H2* h2_ptr;

void foo()
{
std::vector<H2> h2_vec;
}
};
// ==========
Loading

0 comments on commit 8dec0d0

Please sign in to comment.