diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index ae9f15834..a6765894a 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -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; +}; + } } diff --git a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h index 69e996451..6e0d56e35 100644 --- a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h @@ -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 diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 69f1f2b40..357904549 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -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 @@ -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 diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 8fe839c64..4b71bbee2 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -428,6 +430,89 @@ void CppMetricsParser::efferentTypeLevel() }); } +void CppMetricsParser::afferentTypeLevel() +{ + parallelCalcMetric( + "Afferent coupling of types", + _threadCount * afferentCouplingTypesPartitionMultiplier,// number of jobs; adjust for granularity + getFilterPathsQuery(), + [&, this](const MetricsTasks& tasks) + { + util::OdbTransaction{_ctx.db}([&, this] + { + typedef odb::query AstQuery; + typedef odb::query InheritanceQuery; + typedef odb::query MemTypeQuery; + typedef odb::result AstResult; + typedef odb::result MemTypeAstResult; + + std::set dependentTypes; + for (const model::CohesionCppRecordView& type : tasks) + { + dependentTypes.clear(); + + // Find derived types + for (const model::CppInheritance& inheritance : _ctx.db->query( + InheritanceQuery::base == type.entityHash)) + { + dependentTypes.insert(inheritance.derived); + } + + // Find usages of the type + for (const model::CppAstNode& usage : _ctx.db->query( + 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( + 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( + 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( + 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."; @@ -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; } diff --git a/plugins/cpp_metrics/service/cxxmetrics.thrift b/plugins/cpp_metrics/service/cxxmetrics.thrift index 152063a48..68defdc0b 100644 --- a/plugins/cpp_metrics/service/cxxmetrics.thrift +++ b/plugins/cpp_metrics/service/cxxmetrics.thrift @@ -12,7 +12,8 @@ enum CppAstNodeMetricsType BumpyRoad = 4, LackOfCohesion = 5, LackOfCohesionHS = 6, - EfferentType = 7 + EfferentType = 7, + AfferentType = 8 } enum CppModuleMetricsType @@ -124,4 +125,4 @@ service CppMetricsService * This function returns the names of module-level C++ metrics. */ list getCppModuleMetricsTypeNames() -} \ No newline at end of file +} diff --git a/plugins/cpp_metrics/service/src/cppmetricsservice.cpp b/plugins/cpp_metrics/service/src/cppmetricsservice.cpp index 9e3b999b5..14b6ddc35 100644 --- a/plugins/cpp_metrics/service/src/cppmetricsservice.cpp +++ b/plugins/cpp_metrics/service/src/cppmetricsservice.cpp @@ -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( diff --git a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt index 6b790120b..650aa24c0 100644 --- a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt +++ b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt @@ -5,4 +5,5 @@ add_library(CppMetricsTestProject STATIC functionmccabe.cpp typemccabe.cpp lackofcohesion.cpp - bumpyroad.cpp) + bumpyroad.cpp + afferentcoupling.cpp) diff --git a/plugins/cpp_metrics/test/sources/parser/afferentcoupling.cpp b/plugins/cpp_metrics/test/sources/parser/afferentcoupling.cpp new file mode 100644 index 000000000..61601122e --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/afferentcoupling.cpp @@ -0,0 +1,176 @@ +#include +#include + +// 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_vec; +}; + +class A5 {}; +class A5_D { + std::unique_ptr 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_vec); +}; + +class B5 {}; +class B5_D { + void foo(std::unique_ptr& 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_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_ptr; +} +// ========== + +// Multiple usage of the same type +class E {}; +class E_D { + E* e; + + void foo() + { + E e; + } + + void bar(std::vector 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_vec; + } +}; + +// ---------- + +class H2 {}; +class H2_D1 { + H2 h2; + void bar(std::unique_ptr

h2_ptr); +}; + +class H2_D2 { + H2* h2_ptr; + + void foo() + { + std::vector

h2_vec; + } +}; +// ========== diff --git a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp index 37da8caa4..677cb4486 100644 --- a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp +++ b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp @@ -268,3 +268,55 @@ INSTANTIATE_TEST_SUITE_P( ParameterizedLackOfCohesionTest, ::testing::ValuesIn(paramLackOfCohesion) ); + +// Afferent coupling + +typedef std::pair AfferentParam; +class ParameterizedAfferentCouplingTest + : public CppMetricsParserTest, + public ::testing::WithParamInterface +{}; + +std::vector paramAfferent = { + {"A", 1}, + {"A2", 1}, + {"A3", 1}, + {"A4", 1}, + {"A5", 1}, + {"B", 1}, + {"B2", 1}, + {"B3", 1}, + {"B4", 1}, + {"B5", 1}, + {"B6", 1}, + {"B7", 1}, + {"B8", 1}, + {"C", 1}, + {"C2", 1}, + {"C3", 1}, + {"E", 1}, + {"F", 1}, + {"G", 1}, + {"H", 2}, + {"H2", 2}, +}; + +TEST_P(ParameterizedAfferentCouplingTest, TypeAfferentTest) { + _transaction([&, this]() { + + const auto record = _db->query_value( + odb::query::qualifiedName == GetParam().first); + + const auto metric = _db->query_value( + odb::query::astNodeId == record.astNodeId && + odb::query::type == model::CppAstNodeMetrics::AFFERENT_TYPE); + + EXPECT_EQ(GetParam().second, metric.value); + }); +} + +INSTANTIATE_TEST_SUITE_P( + ParameterizedAfferentCouplingTestSuite, + ParameterizedAfferentCouplingTest, + ::testing::ValuesIn(paramAfferent) +);