From 1bc90607444f37cd49baae54b7b73b4fca03a8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 25 Nov 2023 17:27:04 +0100 Subject: [PATCH 01/28] Initial implementation of the lack of cohesion metric. --- .../model/include/model/cppastnodemetrics.h | 4 +- .../cppmetricsparser/cppmetricsparser.h | 6 +- .../parser/src/cppmetricsparser.cpp | 106 ++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h index 32f5aeb06..2afc933af 100644 --- a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h @@ -13,7 +13,9 @@ struct CppAstNodeMetrics { enum Type { - PARAMETER_COUNT + PARAMETER_COUNT, + LACK_OF_COHESION, + LACK_OF_COHESION_HS, }; #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 f4d508ef3..8453ddf01 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -10,6 +10,9 @@ #include #include +#include +#include + #include #include @@ -28,7 +31,8 @@ class CppMetricsParser : public AbstractParser private: void functionParameters(); - + void lackOfCohesion(); + std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; std::unique_ptr> _pool; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 2730cdb96..de0dc3841 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -107,11 +107,117 @@ void CppMetricsParser::functionParameters() }); } +void CppMetricsParser::lackOfCohesion() +{ + util::OdbTransaction {_ctx.db} ([&, this] + { + // Simplify some type names for readability. + typedef model::CppMemberType::Kind MemberKind; + typedef model::CppAstNode::AstType AstType; + typedef std::uint64_t HashType; + + typedef odb::query QMem; + typedef odb::query QNode; + + // Calculate the cohesion metric for all types. + for (const model::CppRecord& type + : _ctx.db->query()) + { + std::unordered_set fieldHashes; + // Query all members... + for (const model::CppMemberType& field + : _ctx.db->query( + // ... that are fields + QMem::kind == MemberKind::Field && + // ... of the current type. + QMem::typeHash == type.entityHash + )) + { + // Record these fields for later use. + fieldHashes.insert(field.memberAstNode->entityHash); + } + size_t fieldCount = fieldHashes.size(); + + size_t methodCount = 0; + size_t totalCohesion = 0; + // Query all members... + for (const model::CppMemberType& method + : _ctx.db->query( + // ... that are methods + QMem::kind == MemberKind::Method && + // ... of the current type. + QMem::typeHash == type.entityHash + )) + { + // Do not consider methods with no explicit bodies. + model::FileLoc methodLoc = method.memberAstNode->location; + if (methodLoc.range.start < methodLoc.range.end) + { + std::unordered_set usedFields; + + // Query all AST nodes... + for (const model::CppAstNode& node + : _ctx.db->query( + // ... that use a variable for reading or writing + (QNode::astType == AstType::Read || + QNode::astType == AstType::Write) && + // ... in the same file as the current method + (QNode::location.file->path == methodLoc.file->path && + // ... within the textual scope of the current method's body. + (QNode::location.range.start.line >= methodLoc.range.start.line + || (QNode::location.range.start.line == methodLoc.range.start.line + && QNode::location.range.start.column >= methodLoc.range.start.column)) && + (QNode::location.range.end.line <= methodLoc.range.end.line + || (QNode::location.range.end.line == methodLoc.range.end.line + && QNode::location.range.end.column <= methodLoc.range.end.column))) + )) + { + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + { + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); + } + } + + ++methodCount; + totalCohesion += usedFields.size(); + } + } + + // Calculate and record metrics. + const double dF = fieldCount; + const double dM = methodCount; + const double dC = totalCohesion; + constexpr double scaling = 1e+4; + + // Standard lack of cohesion: + model::CppAstNodeMetrics lcm; + lcm.astNodeId = type.astNodeId; + lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; + lcm.value = static_cast(scaling * + (1.0 - dC / (dM * dF)));// range: [0,1] + _ctx.db->persist(lcm); + + // Henderson-Sellers variant: + model::CppAstNodeMetrics lcm_hs; + lcm_hs.astNodeId = type.astNodeId; + lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; + lcm_hs.value = static_cast(scaling * + ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] + _ctx.db->persist(lcm_hs); + } + }); +} + bool CppMetricsParser::parse() { // Function parameter number metric. functionParameters(); + // Lack of cohesion within types. + lackOfCohesion(); + return true; } From 7700e165eb84cafb7418ec48231968651f12ab5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 11:30:52 +0100 Subject: [PATCH 02/28] Added manual ODB lazy pointer loading to avoid segfaults. --- .../parser/src/cppmetricsparser.cpp | 75 +++++++++++-------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index de0dc3841..46e7d9a95 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -118,7 +118,7 @@ void CppMetricsParser::lackOfCohesion() typedef odb::query QMem; typedef odb::query QNode; - + // Calculate the cohesion metric for all types. for (const model::CppRecord& type : _ctx.db->query()) @@ -133,8 +133,11 @@ void CppMetricsParser::lackOfCohesion() QMem::typeHash == type.entityHash )) { - // Record these fields for later use. - fieldHashes.insert(field.memberAstNode->entityHash); + if (model::CppAstNodePtr fieldAst = field.memberAstNode.load()) + { + // Record these fields for later use. + fieldHashes.insert(fieldAst->entityHash); + } } size_t fieldCount = fieldHashes.size(); @@ -149,39 +152,49 @@ void CppMetricsParser::lackOfCohesion() QMem::typeHash == type.entityHash )) { - // Do not consider methods with no explicit bodies. - model::FileLoc methodLoc = method.memberAstNode->location; - if (methodLoc.range.start < methodLoc.range.end) + if (model::CppAstNodePtr methodAst = method.memberAstNode.load()) { - std::unordered_set usedFields; - - // Query all AST nodes... - for (const model::CppAstNode& node - : _ctx.db->query( - // ... that use a variable for reading or writing - (QNode::astType == AstType::Read || - QNode::astType == AstType::Write) && - // ... in the same file as the current method - (QNode::location.file->path == methodLoc.file->path && - // ... within the textual scope of the current method's body. - (QNode::location.range.start.line >= methodLoc.range.start.line - || (QNode::location.range.start.line == methodLoc.range.start.line - && QNode::location.range.start.column >= methodLoc.range.start.column)) && - (QNode::location.range.end.line <= methodLoc.range.end.line - || (QNode::location.range.end.line == methodLoc.range.end.line - && QNode::location.range.end.column <= methodLoc.range.end.column))) - )) + // Do not consider methods with no explicit bodies. + model::FileLoc methodLoc = methodAst->location; + model::FilePtr filePtr = methodLoc.file.load(); + if (filePtr && methodLoc.range.start < methodLoc.range.end) { - // If this AST node is a reference to a field of the type... - if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + std::unordered_set usedFields; + + // Query all AST nodes... + for (const model::CppAstNode& node + : _ctx.db->query( + // ... that use a variable for reading or writing + (QNode::astType == AstType::Read || + QNode::astType == AstType::Write) && + // ... in the same file as the current method + (QNode::location.file->path == filePtr->path && + // ... within the textual scope of the current method's body. + (QNode::location.range.start.line + >= methodLoc.range.start.line + || (QNode::location.range.start.line + == methodLoc.range.start.line + && QNode::location.range.start.column + >= methodLoc.range.start.column)) && + (QNode::location.range.end.line + <= methodLoc.range.end.line + || (QNode::location.range.end.line + == methodLoc.range.end.line + && QNode::location.range.end.column + <= methodLoc.range.end.column))) + )) { - // ... then mark it as used by this method. - usedFields.insert(node.entityHash); + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + { + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); + } } + + ++methodCount; + totalCohesion += usedFields.size(); } - - ++methodCount; - totalCohesion += usedFields.size(); } } From 3eb47f6f1edca091dfc4b69c4400181871b0ffe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 14:42:46 +0100 Subject: [PATCH 03/28] Replaced the lazy pointer loading with db views. --- plugins/cpp/model/include/model/cpprecord.h | 37 +++++++ .../parser/src/cppmetricsparser.cpp | 99 ++++++++----------- 2 files changed, 77 insertions(+), 59 deletions(-) diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index ae9f15834..33897fbe2 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -90,6 +90,43 @@ struct CppRecordCount std::size_t count; }; +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) +struct CppFieldWithEntityHash +{ + #pragma db column(CppMemberType::typeHash) + std::uint64_t typeHash; + + #pragma db column(CppAstNode::entityHash) + std::size_t entityHash; +}; + +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + object(File : CppAstNode::location.file) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) +struct CppMethodWithLocation +{ + typedef cc::model::Position::PosType PosType; + + #pragma db column(CppMemberType::typeHash) + std::uint64_t typeHash; + + #pragma db column(CppAstNode::location.range.start.line) + PosType startLine; + #pragma db column(CppAstNode::location.range.start.column) + PosType startColumn; + #pragma db column(CppAstNode::location.range.end.line) + PosType endLine; + #pragma db column(CppAstNode::location.range.end.column) + PosType endColumn; + + #pragma db column(File::path) + std::string filePath; +}; } } diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 46e7d9a95..c4e65ce8a 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -112,11 +112,10 @@ void CppMetricsParser::lackOfCohesion() util::OdbTransaction {_ctx.db} ([&, this] { // Simplify some type names for readability. - typedef model::CppMemberType::Kind MemberKind; typedef model::CppAstNode::AstType AstType; typedef std::uint64_t HashType; - typedef odb::query QMem; + typedef odb::query QMember; typedef odb::query QNode; // Calculate the cohesion metric for all types. @@ -124,77 +123,59 @@ void CppMetricsParser::lackOfCohesion() : _ctx.db->query()) { std::unordered_set fieldHashes; - // Query all members... - for (const model::CppMemberType& field - : _ctx.db->query( - // ... that are fields - QMem::kind == MemberKind::Field && - // ... of the current type. - QMem::typeHash == type.entityHash + // Query all fields of the current type. + for (const model::CppFieldWithEntityHash& field + : _ctx.db->query( + QMember::typeHash == type.entityHash )) { - if (model::CppAstNodePtr fieldAst = field.memberAstNode.load()) - { - // Record these fields for later use. - fieldHashes.insert(fieldAst->entityHash); - } + // Record these fields for later use. + fieldHashes.insert(field.entityHash); } size_t fieldCount = fieldHashes.size(); size_t methodCount = 0; size_t totalCohesion = 0; - // Query all members... - for (const model::CppMemberType& method - : _ctx.db->query( - // ... that are methods - QMem::kind == MemberKind::Method && - // ... of the current type. - QMem::typeHash == type.entityHash + // Query all methods of the current type. + for (const model::CppMethodWithLocation& method + : _ctx.db->query( + QMember::typeHash == type.entityHash )) { - if (model::CppAstNodePtr methodAst = method.memberAstNode.load()) + // Do not consider methods with no explicit bodies. + const model::Position start(method.startLine, method.startColumn); + const model::Position end(method.endLine, method.endColumn); + if (start < end) { - // Do not consider methods with no explicit bodies. - model::FileLoc methodLoc = methodAst->location; - model::FilePtr filePtr = methodLoc.file.load(); - if (filePtr && methodLoc.range.start < methodLoc.range.end) + std::unordered_set usedFields; + + // Query all AST nodes... + for (const model::CppAstNode& node + : _ctx.db->query( + // ... that use a variable for reading or writing + (QNode::astType == AstType::Read || + QNode::astType == AstType::Write) && + // ... in the same file as the current method + (QNode::location.file->path == method.filePath && + // ... within the textual scope of the current method's body. + (QNode::location.range.start.line >= start.line + || (QNode::location.range.start.line == start.line + && QNode::location.range.start.column >= start.column)) && + (QNode::location.range.end.line <= end.line + || (QNode::location.range.end.line == end.line + && QNode::location.range.end.column <= end.column))) + )) { - std::unordered_set usedFields; - - // Query all AST nodes... - for (const model::CppAstNode& node - : _ctx.db->query( - // ... that use a variable for reading or writing - (QNode::astType == AstType::Read || - QNode::astType == AstType::Write) && - // ... in the same file as the current method - (QNode::location.file->path == filePtr->path && - // ... within the textual scope of the current method's body. - (QNode::location.range.start.line - >= methodLoc.range.start.line - || (QNode::location.range.start.line - == methodLoc.range.start.line - && QNode::location.range.start.column - >= methodLoc.range.start.column)) && - (QNode::location.range.end.line - <= methodLoc.range.end.line - || (QNode::location.range.end.line - == methodLoc.range.end.line - && QNode::location.range.end.column - <= methodLoc.range.end.column))) - )) + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) { - // If this AST node is a reference to a field of the type... - if (fieldHashes.find(node.entityHash) != fieldHashes.end()) - { - // ... then mark it as used by this method. - usedFields.insert(node.entityHash); - } + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); } - - ++methodCount; - totalCohesion += usedFields.size(); } + + ++methodCount; + totalCohesion += usedFields.size(); } } From d015b221415a601bd71d4dadc0edaf8ca7685da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 15:43:36 +0100 Subject: [PATCH 04/28] Further refactor of queries via db views. --- plugins/cpp/model/include/model/cppastnode.h | 24 ++++++++++++ .../parser/src/cppmetricsparser.cpp | 39 ++++++++++--------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 67f93507b..062a66b1f 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -237,6 +237,30 @@ struct CppAstCount std::size_t count; }; +#pragma db view \ + object(CppAstNode) \ + object(File : CppAstNode::location.file) \ + query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ + || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) +struct CppRWAstNodeWithHashAndLoc +{ + typedef cc::model::Position::PosType PosType; + + #pragma db column(CppAstNode::entityHash) + std::uint64_t entityHash; + + #pragma db column(CppAstNode::location.range.start.line) + PosType startLine; + #pragma db column(CppAstNode::location.range.start.column) + PosType startColumn; + #pragma db column(CppAstNode::location.range.end.line) + PosType endLine; + #pragma db column(CppAstNode::location.range.end.column) + PosType endColumn; + + #pragma db column(File::path) + std::string filePath; +}; } } diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index c4e65ce8a..2fb4b7f05 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -112,12 +112,18 @@ void CppMetricsParser::lackOfCohesion() util::OdbTransaction {_ctx.db} ([&, this] { // Simplify some type names for readability. - typedef model::CppAstNode::AstType AstType; typedef std::uint64_t HashType; - typedef odb::query QMember; - typedef odb::query QNode; + typedef odb::query::query_columns QField; + const auto& QFieldTypeHash = QField::CppMemberType::typeHash; + typedef odb::query::query_columns QMethod; + const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; + + typedef odb::query::query_columns QNode; + const auto& QNodeFilePath = QNode::File::path; + const auto& QNodeRange = QNode::CppAstNode::location.range; + // Calculate the cohesion metric for all types. for (const model::CppRecord& type : _ctx.db->query()) @@ -126,7 +132,7 @@ void CppMetricsParser::lackOfCohesion() // Query all fields of the current type. for (const model::CppFieldWithEntityHash& field : _ctx.db->query( - QMember::typeHash == type.entityHash + QFieldTypeHash == type.entityHash )) { // Record these fields for later use. @@ -139,7 +145,7 @@ void CppMetricsParser::lackOfCohesion() // Query all methods of the current type. for (const model::CppMethodWithLocation& method : _ctx.db->query( - QMember::typeHash == type.entityHash + QMethodTypeHash == type.entityHash )) { // Do not consider methods with no explicit bodies. @@ -149,21 +155,18 @@ void CppMetricsParser::lackOfCohesion() { std::unordered_set usedFields; - // Query all AST nodes... - for (const model::CppAstNode& node - : _ctx.db->query( - // ... that use a variable for reading or writing - (QNode::astType == AstType::Read || - QNode::astType == AstType::Write) && + // Query all AST nodes that use a variable for reading or writing... + for (const model::CppRWAstNodeWithHashAndLoc& node + : _ctx.db->query( // ... in the same file as the current method - (QNode::location.file->path == method.filePath && + (QNodeFilePath == method.filePath && // ... within the textual scope of the current method's body. - (QNode::location.range.start.line >= start.line - || (QNode::location.range.start.line == start.line - && QNode::location.range.start.column >= start.column)) && - (QNode::location.range.end.line <= end.line - || (QNode::location.range.end.line == end.line - && QNode::location.range.end.column <= end.column))) + (QNodeRange.start.line >= start.line + || (QNodeRange.start.line == start.line + && QNodeRange.start.column >= start.column)) && + (QNodeRange.end.line <= end.line + || (QNodeRange.end.line == end.line + && QNodeRange.end.column <= end.column))) )) { // If this AST node is a reference to a field of the type... From e08e522a4eb64ccace4fec577a9bf3d69a5f34d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 16:21:57 +0100 Subject: [PATCH 05/28] Removed unneeded fields from the db views. Renamed view types. --- plugins/cpp/model/include/model/cppastnode.h | 16 +--------------- plugins/cpp/model/include/model/cpprecord.h | 10 ++-------- .../parser/src/cppmetricsparser.cpp | 18 +++++++++--------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 062a66b1f..427cda5be 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -242,24 +242,10 @@ struct CppAstCount object(File : CppAstNode::location.file) \ query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) -struct CppRWAstNodeWithHashAndLoc +struct CohesionCppAstNodeView { - typedef cc::model::Position::PosType PosType; - #pragma db column(CppAstNode::entityHash) std::uint64_t entityHash; - - #pragma db column(CppAstNode::location.range.start.line) - PosType startLine; - #pragma db column(CppAstNode::location.range.start.column) - PosType startColumn; - #pragma db column(CppAstNode::location.range.end.line) - PosType endLine; - #pragma db column(CppAstNode::location.range.end.column) - PosType endColumn; - - #pragma db column(File::path) - std::string filePath; }; } } diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index 33897fbe2..583d8a426 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -94,11 +94,8 @@ struct CppRecordCount object(CppMemberType) \ object(CppAstNode : CppMemberType::memberAstNode) \ query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) -struct CppFieldWithEntityHash +struct CohesionCppFieldView { - #pragma db column(CppMemberType::typeHash) - std::uint64_t typeHash; - #pragma db column(CppAstNode::entityHash) std::size_t entityHash; }; @@ -108,13 +105,10 @@ struct CppFieldWithEntityHash object(CppAstNode : CppMemberType::memberAstNode) \ object(File : CppAstNode::location.file) \ query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) -struct CppMethodWithLocation +struct CohesionCppMethodView { typedef cc::model::Position::PosType PosType; - #pragma db column(CppMemberType::typeHash) - std::uint64_t typeHash; - #pragma db column(CppAstNode::location.range.start.line) PosType startLine; #pragma db column(CppAstNode::location.range.start.column) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 2fb4b7f05..32488a5c3 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -114,13 +114,13 @@ void CppMetricsParser::lackOfCohesion() // Simplify some type names for readability. typedef std::uint64_t HashType; - typedef odb::query::query_columns QField; + typedef odb::query::query_columns QField; const auto& QFieldTypeHash = QField::CppMemberType::typeHash; - typedef odb::query::query_columns QMethod; + typedef odb::query::query_columns QMethod; const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; - typedef odb::query::query_columns QNode; + typedef odb::query::query_columns QNode; const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; @@ -130,8 +130,8 @@ void CppMetricsParser::lackOfCohesion() { std::unordered_set fieldHashes; // Query all fields of the current type. - for (const model::CppFieldWithEntityHash& field - : _ctx.db->query( + for (const model::CohesionCppFieldView& field + : _ctx.db->query( QFieldTypeHash == type.entityHash )) { @@ -143,8 +143,8 @@ void CppMetricsParser::lackOfCohesion() size_t methodCount = 0; size_t totalCohesion = 0; // Query all methods of the current type. - for (const model::CppMethodWithLocation& method - : _ctx.db->query( + for (const model::CohesionCppMethodView& method + : _ctx.db->query( QMethodTypeHash == type.entityHash )) { @@ -156,8 +156,8 @@ void CppMetricsParser::lackOfCohesion() std::unordered_set usedFields; // Query all AST nodes that use a variable for reading or writing... - for (const model::CppRWAstNodeWithHashAndLoc& node - : _ctx.db->query( + for (const model::CohesionCppAstNodeView& node + : _ctx.db->query( // ... in the same file as the current method (QNodeFilePath == method.filePath && // ... within the textual scope of the current method's body. From 7121d53ca93cf0c1f79d5c2a30d126c9f874e06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:07:01 +0100 Subject: [PATCH 06/28] Added checks to ensure that the cohesion metrics are only calculated for internal types. Record access is now done via a specialized view. Minor refactor (code duplication fix) in the file location recording in the AST visitor. --- plugins/cpp/model/include/model/cpprecord.h | 20 ++++++++++ plugins/cpp/parser/src/clangastvisitor.h | 39 ++++++++----------- .../cppmetricsparser/cppmetricsparser.h | 5 +++ .../parser/src/cppmetricsparser.cpp | 26 ++++++++++--- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index 583d8a426..f96933317 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -90,6 +90,26 @@ struct CppRecordCount std::size_t count; }; + +#pragma db view \ + object(CppRecord) \ + object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ + object(File : CppAstNode::location.file) +struct CohesionCppRecordView +{ + #pragma db column(CppEntity::entityHash) + std::size_t entityHash; + + #pragma db column(CppEntity::qualifiedName) + std::string qualifiedName; + + #pragma db column(CppEntity::astNodeId) + CppAstNodeId astNodeId; + + #pragma db column(File::path) + std::string filePath; +}; + #pragma db view \ object(CppMemberType) \ object(CppAstNode : CppMemberType::memberAstNode) \ diff --git a/plugins/cpp/parser/src/clangastvisitor.h b/plugins/cpp/parser/src/clangastvisitor.h index f50af517c..903f6db56 100644 --- a/plugins/cpp/parser/src/clangastvisitor.h +++ b/plugins/cpp/parser/src/clangastvisitor.h @@ -1339,34 +1339,27 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor model::FileLoc fileLoc; if (start_.isInvalid() || end_.isInvalid()) - { fileLoc.file = getFile(start_); - const std::string& type = fileLoc.file.load()->type; - if (type != model::File::DIRECTORY_TYPE && type != _cppSourceType) - { - fileLoc.file->type = _cppSourceType; - _ctx.srcMgr.updateFile(*fileLoc.file); - } - return fileLoc; - } - - clang::SourceLocation realStart = start_; - clang::SourceLocation realEnd = end_; + else + { + clang::SourceLocation realStart = start_; + clang::SourceLocation realEnd = end_; - if (_clangSrcMgr.isMacroBodyExpansion(start_)) - realStart = _clangSrcMgr.getExpansionLoc(start_); - if (_clangSrcMgr.isMacroArgExpansion(start_)) - realStart = _clangSrcMgr.getSpellingLoc(start_); + if (_clangSrcMgr.isMacroBodyExpansion(start_)) + realStart = _clangSrcMgr.getExpansionLoc(start_); + if (_clangSrcMgr.isMacroArgExpansion(start_)) + realStart = _clangSrcMgr.getSpellingLoc(start_); - if (_clangSrcMgr.isMacroBodyExpansion(end_)) - realEnd = _clangSrcMgr.getExpansionLoc(end_); - if (_clangSrcMgr.isMacroArgExpansion(end_)) - realEnd = _clangSrcMgr.getSpellingLoc(end_); + if (_clangSrcMgr.isMacroBodyExpansion(end_)) + realEnd = _clangSrcMgr.getExpansionLoc(end_); + if (_clangSrcMgr.isMacroArgExpansion(end_)) + realEnd = _clangSrcMgr.getSpellingLoc(end_); - if (!_isImplicit) - _fileLocUtil.setRange(realStart, realEnd, fileLoc.range); + if (!_isImplicit) + _fileLocUtil.setRange(realStart, realEnd, fileLoc.range); - fileLoc.file = getFile(realStart); + fileLoc.file = getFile(realStart); + } const std::string& type = fileLoc.file.load()->type; if (type != model::File::DIRECTORY_TYPE && type != _cppSourceType) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 8453ddf01..cc4dd1616 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -33,6 +33,11 @@ class CppMetricsParser : public AbstractParser void functionParameters(); void lackOfCohesion(); + // Checks if the given (canonical) path begins with any of the input paths + // specified for the metrics parser via command line arguments on startup. + bool isInInputPath(const std::string& path_) const; + std::vector _inputPaths; + std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; std::unique_ptr> _pool; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 32488a5c3..a81fe5623 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -22,6 +22,10 @@ namespace parser CppMetricsParser::CppMetricsParser(ParserContext& ctx_): AbstractParser(ctx_) { + for (const std::string& path : + _ctx.options["input"].as>()) + _inputPaths.push_back(boost::filesystem::canonical(path).string()); + util::OdbTransaction {_ctx.db} ([&, this] { for (const model::CppFileMetrics& fm : _ctx.db->query()) @@ -125,9 +129,13 @@ void CppMetricsParser::lackOfCohesion() const auto& QNodeRange = QNode::CppAstNode::location.range; // Calculate the cohesion metric for all types. - for (const model::CppRecord& type - : _ctx.db->query()) + for (const model::CohesionCppRecordView& type + : _ctx.db->query()) { + // Skip types that were included from external libraries. + if (!isInInputPath(type.filePath)) + continue; + std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -138,10 +146,10 @@ void CppMetricsParser::lackOfCohesion() // Record these fields for later use. fieldHashes.insert(field.entityHash); } - size_t fieldCount = fieldHashes.size(); + std::size_t fieldCount = fieldHashes.size(); - size_t methodCount = 0; - size_t totalCohesion = 0; + std::size_t methodCount = 0; + std::size_t totalCohesion = 0; // Query all methods of the current type. for (const model::CohesionCppMethodView& method : _ctx.db->query( @@ -207,6 +215,14 @@ void CppMetricsParser::lackOfCohesion() }); } +bool CppMetricsParser::isInInputPath(const std::string& path_) const +{ + std::size_t i = 0; + while (i < _inputPaths.size() && path_.rfind(_inputPaths[i], 0) != 0) + ++i; + return i < _inputPaths.size(); +} + bool CppMetricsParser::parse() { // Function parameter number metric. From 25ea6877bc592971c85ca02ce2f5cab5ac8d2693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:07:57 +0100 Subject: [PATCH 07/28] Added debug utilities to the cohesion metrics. --- .../parser/src/cppmetricsparser.cpp | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index a81fe5623..e08223c2d 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -15,6 +15,16 @@ #include +// Controls whether cohesion metrics are printed to the output +// immediately as they are being calculated. +#define DEBUG_COHESION_VERBOSE + +#ifdef DEBUG_COHESION_VERBOSE +#include +#include +#include +#endif + namespace cc { namespace parser @@ -128,14 +138,37 @@ void CppMetricsParser::lackOfCohesion() const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; + #ifdef DEBUG_COHESION_VERBOSE + std::size_t typecount = + _ctx.db->query_value().count; + std::size_t typeindex = 0; + std::size_t checkedcount = 0; + + std::cout << "=== Lack of Cohesion (LoC) metrics parser ===" << std::endl; + int colwidth = static_cast(ceil(log10(typecount))); + auto start = std::chrono::steady_clock::now(); + #endif + // Calculate the cohesion metric for all types. for (const model::CohesionCppRecordView& type : _ctx.db->query()) { + #ifdef DEBUG_COHESION_VERBOSE + ++typeindex; + #endif + // Skip types that were included from external libraries. if (!isInInputPath(type.filePath)) continue; + #ifdef DEBUG_COHESION_VERBOSE + ++checkedcount; + std::cout << std::right << std::setw(colwidth) << typeindex << '/'; + std::cout << std::left << std::setw(colwidth) << typecount << '\t'; + std::cout << std::left << std::setw(32) << type.qualifiedName; + std::cout.flush(); + #endif + std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -211,7 +244,22 @@ void CppMetricsParser::lackOfCohesion() lcm_hs.value = static_cast(scaling * ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] _ctx.db->persist(lcm_hs); + + #ifdef DEBUG_COHESION_VERBOSE + std::cout << std::right << std::setw(8) << (lcm.value / scaling); + std::cout << std::right << std::setw(8) << (lcm_hs.value / scaling); + std::cout << std::endl; + #endif } + + #ifdef DEBUG_COHESION_VERBOSE + auto finish = std::chrono::steady_clock::now(); + auto duration = finish - start; + auto durs = std::chrono::duration_cast(duration); + std::cout << "=== Checked types: " << checkedcount + << ", Total runtime: " << durs.count() + << "s ===" << std::endl; + #endif }); } From 66390e3b6046d0fd7ad59e95097f0c4651660442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:23:01 +0100 Subject: [PATCH 08/28] Cohesion database views have been moved to their own new header (in the metrics parser). --- plugins/cpp/model/include/model/cppastnode.h | 11 --- plugins/cpp/model/include/model/cpprecord.h | 52 ------------- plugins/cpp_metrics/model/CMakeLists.txt | 1 + .../model/include/model/cppcohesionmetrics.h | 77 +++++++++++++++++++ .../parser/src/cppmetricsparser.cpp | 2 + 5 files changed, 80 insertions(+), 63 deletions(-) create mode 100644 plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 427cda5be..7b7b51ef8 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -236,17 +236,6 @@ struct CppAstCount #pragma db column("count(" + CppAstNode::id + ")") std::size_t count; }; - -#pragma db view \ - object(CppAstNode) \ - object(File : CppAstNode::location.file) \ - query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ - || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) -struct CohesionCppAstNodeView -{ - #pragma db column(CppAstNode::entityHash) - std::uint64_t entityHash; -}; } } diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index f96933317..cfd44f54a 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -89,58 +89,6 @@ struct CppRecordCount #pragma db column("count(" + CppRecord::id + ")") std::size_t count; }; - - -#pragma db view \ - object(CppRecord) \ - object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ - object(File : CppAstNode::location.file) -struct CohesionCppRecordView -{ - #pragma db column(CppEntity::entityHash) - std::size_t entityHash; - - #pragma db column(CppEntity::qualifiedName) - std::string qualifiedName; - - #pragma db column(CppEntity::astNodeId) - CppAstNodeId astNodeId; - - #pragma db column(File::path) - std::string filePath; -}; - -#pragma db view \ - object(CppMemberType) \ - object(CppAstNode : CppMemberType::memberAstNode) \ - query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) -struct CohesionCppFieldView -{ - #pragma db column(CppAstNode::entityHash) - std::size_t entityHash; -}; - -#pragma db view \ - object(CppMemberType) \ - object(CppAstNode : CppMemberType::memberAstNode) \ - object(File : CppAstNode::location.file) \ - query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) -struct CohesionCppMethodView -{ - typedef cc::model::Position::PosType PosType; - - #pragma db column(CppAstNode::location.range.start.line) - PosType startLine; - #pragma db column(CppAstNode::location.range.start.column) - PosType startColumn; - #pragma db column(CppAstNode::location.range.end.line) - PosType endLine; - #pragma db column(CppAstNode::location.range.end.column) - PosType endColumn; - - #pragma db column(File::path) - std::string filePath; -}; } } diff --git a/plugins/cpp_metrics/model/CMakeLists.txt b/plugins/cpp_metrics/model/CMakeLists.txt index 2f5c29eb6..595a3ddf3 100644 --- a/plugins/cpp_metrics/model/CMakeLists.txt +++ b/plugins/cpp_metrics/model/CMakeLists.txt @@ -6,6 +6,7 @@ message(WARNING "${cpp_PLUGIN_DIR}/model/include") set(ODB_SOURCES include/model/cppastnodemetrics.h + include/model/cppcohesionmetrics.h include/model/cppfilemetrics.h) generate_odb_files("${ODB_SOURCES}" "cpp") diff --git a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h new file mode 100644 index 000000000..bc8a20629 --- /dev/null +++ b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h @@ -0,0 +1,77 @@ +#ifndef CC_MODEL_CPPCOHESIONMETRICS_H +#define CC_MODEL_CPPCOHESIONMETRICS_H + +#include +#include +#include + +namespace cc +{ +namespace model +{ +#pragma db view \ + object(CppRecord) \ + object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ + object(File : CppAstNode::location.file) +struct CohesionCppRecordView +{ + #pragma db column(CppEntity::entityHash) + std::size_t entityHash; + + #pragma db column(CppEntity::qualifiedName) + std::string qualifiedName; + + #pragma db column(CppEntity::astNodeId) + CppAstNodeId astNodeId; + + #pragma db column(File::path) + std::string filePath; +}; + +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) +struct CohesionCppFieldView +{ + #pragma db column(CppAstNode::entityHash) + std::size_t entityHash; +}; + +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + object(File : CppAstNode::location.file) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) +struct CohesionCppMethodView +{ + typedef cc::model::Position::PosType PosType; + + #pragma db column(CppAstNode::location.range.start.line) + PosType startLine; + #pragma db column(CppAstNode::location.range.start.column) + PosType startColumn; + #pragma db column(CppAstNode::location.range.end.line) + PosType endLine; + #pragma db column(CppAstNode::location.range.end.column) + PosType endColumn; + + #pragma db column(File::path) + std::string filePath; +}; + +#pragma db view \ + object(CppAstNode) \ + object(File : CppAstNode::location.file) \ + query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ + || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) +struct CohesionCppAstNodeView +{ + #pragma db column(CppAstNode::entityHash) + std::uint64_t entityHash; +}; + +} //model +} //cc + +#endif //CC_MODEL_CPPCOHESIONMETRICS_H diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index e08223c2d..13c935c94 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include From 4433df35b005a47461fa9021bfe666211f756d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:43:22 +0100 Subject: [PATCH 09/28] Minor interface refactors + code comments. --- plugins/cpp/model/include/model/cppastnode.h | 1 + plugins/cpp/model/include/model/cpprecord.h | 1 + .../include/cppmetricsparser/cppmetricsparser.h | 6 ++++++ plugins/cpp_metrics/parser/src/cppmetricsparser.cpp | 13 ++++--------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 7b7b51ef8..67f93507b 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -236,6 +236,7 @@ struct CppAstCount #pragma db column("count(" + CppAstNode::id + ")") std::size_t count; }; + } } diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index cfd44f54a..ae9f15834 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -89,6 +89,7 @@ struct CppRecordCount #pragma db column("count(" + CppRecord::id + ")") std::size_t count; }; + } } diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index cc4dd1616..dd7d1311c 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -24,13 +24,19 @@ namespace parser class CppMetricsParser : public AbstractParser { public: + static constexpr double LOC_SCALING = 1e+4; + CppMetricsParser(ParserContext& ctx_); virtual ~CppMetricsParser(); + virtual bool cleanupDatabase() override; virtual bool parse() override; private: + // Calculate the count of parameters for every function. void functionParameters(); + // Calculate the lack of cohesion between member variables + // and member functions for every type. void lackOfCohesion(); // Checks if the given (canonical) path begins with any of the input paths diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 13c935c94..4f0caa8f8 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -229,13 +229,12 @@ void CppMetricsParser::lackOfCohesion() const double dF = fieldCount; const double dM = methodCount; const double dC = totalCohesion; - constexpr double scaling = 1e+4; // Standard lack of cohesion: model::CppAstNodeMetrics lcm; lcm.astNodeId = type.astNodeId; lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; - lcm.value = static_cast(scaling * + lcm.value = static_cast(LOC_SCALING * (1.0 - dC / (dM * dF)));// range: [0,1] _ctx.db->persist(lcm); @@ -243,13 +242,13 @@ void CppMetricsParser::lackOfCohesion() model::CppAstNodeMetrics lcm_hs; lcm_hs.astNodeId = type.astNodeId; lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; - lcm_hs.value = static_cast(scaling * + lcm_hs.value = static_cast(LOC_SCALING * ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] _ctx.db->persist(lcm_hs); #ifdef DEBUG_COHESION_VERBOSE - std::cout << std::right << std::setw(8) << (lcm.value / scaling); - std::cout << std::right << std::setw(8) << (lcm_hs.value / scaling); + std::cout << std::right << std::setw(8) << (lcm.value / LOC_SCALING); + std::cout << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); std::cout << std::endl; #endif } @@ -275,12 +274,8 @@ bool CppMetricsParser::isInInputPath(const std::string& path_) const bool CppMetricsParser::parse() { - // Function parameter number metric. functionParameters(); - - // Lack of cohesion within types. lackOfCohesion(); - return true; } From b0dcc41ecdffabbb01f85b4f997dae015244ce92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 9 Dec 2023 13:09:30 +0100 Subject: [PATCH 10/28] Replaced std::cout with LOG(debug). --- .../parser/src/cppmetricsparser.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 4f0caa8f8..74bc04949 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -22,7 +22,7 @@ #define DEBUG_COHESION_VERBOSE #ifdef DEBUG_COHESION_VERBOSE -#include +#include #include #include #endif @@ -146,7 +146,7 @@ void CppMetricsParser::lackOfCohesion() std::size_t typeindex = 0; std::size_t checkedcount = 0; - std::cout << "=== Lack of Cohesion (LoC) metrics parser ===" << std::endl; + LOG(debug) << "=== Lack of Cohesion (LoC) metrics parser ==="; int colwidth = static_cast(ceil(log10(typecount))); auto start = std::chrono::steady_clock::now(); #endif @@ -165,10 +165,10 @@ void CppMetricsParser::lackOfCohesion() #ifdef DEBUG_COHESION_VERBOSE ++checkedcount; - std::cout << std::right << std::setw(colwidth) << typeindex << '/'; - std::cout << std::left << std::setw(colwidth) << typecount << '\t'; - std::cout << std::left << std::setw(32) << type.qualifiedName; - std::cout.flush(); + std::ostringstream logLine; + logLine << std::right << std::setw(colwidth) << typeindex << '/'; + logLine << std::left << std::setw(colwidth) << typecount << '\t'; + logLine << std::left << std::setw(32) << type.qualifiedName; #endif std::unordered_set fieldHashes; @@ -247,9 +247,9 @@ void CppMetricsParser::lackOfCohesion() _ctx.db->persist(lcm_hs); #ifdef DEBUG_COHESION_VERBOSE - std::cout << std::right << std::setw(8) << (lcm.value / LOC_SCALING); - std::cout << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); - std::cout << std::endl; + logLine << std::right << std::setw(8) << (lcm.value / LOC_SCALING); + logLine << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); + LOG(debug) << logLine.str(); #endif } @@ -257,9 +257,8 @@ void CppMetricsParser::lackOfCohesion() auto finish = std::chrono::steady_clock::now(); auto duration = finish - start; auto durs = std::chrono::duration_cast(duration); - std::cout << "=== Checked types: " << checkedcount - << ", Total runtime: " << durs.count() - << "s ===" << std::endl; + LOG(debug) << "=== Checked types: " << checkedcount + << ", Total runtime: " << durs.count() << "s ==="; #endif }); } From adf2c44c0a05fc77c3d60b29c3f88abab3cf4984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Mon, 11 Dec 2023 21:41:18 +0100 Subject: [PATCH 11/28] isInInputPath is generalized and moved to cc::util::isRootedUnderAnyOf. Fixed the casing and naming of debug utility variables in the LoC parser. --- .../cppmetricsparser/cppmetricsparser.h | 4 -- .../parser/src/cppmetricsparser.cpp | 39 ++++++++----------- util/include/util/filesystem.h | 13 +++++++ util/src/filesystem.cpp | 11 ++++++ 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index dd7d1311c..9326719db 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -39,11 +39,7 @@ class CppMetricsParser : public AbstractParser // and member functions for every type. void lackOfCohesion(); - // Checks if the given (canonical) path begins with any of the input paths - // specified for the metrics parser via command line arguments on startup. - bool isInInputPath(const std::string& path_) const; std::vector _inputPaths; - std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; std::unique_ptr> _pool; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 74bc04949..41de07e82 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -141,14 +142,14 @@ void CppMetricsParser::lackOfCohesion() const auto& QNodeRange = QNode::CppAstNode::location.range; #ifdef DEBUG_COHESION_VERBOSE - std::size_t typecount = + std::size_t typeCount = _ctx.db->query_value().count; - std::size_t typeindex = 0; - std::size_t checkedcount = 0; + std::size_t typeIndex = 0; + std::size_t checkedCount = 0; LOG(debug) << "=== Lack of Cohesion (LoC) metrics parser ==="; - int colwidth = static_cast(ceil(log10(typecount))); - auto start = std::chrono::steady_clock::now(); + int colWidth = static_cast(ceil(log10(typeCount))); + auto startTime = std::chrono::steady_clock::now(); #endif // Calculate the cohesion metric for all types. @@ -156,18 +157,18 @@ void CppMetricsParser::lackOfCohesion() : _ctx.db->query()) { #ifdef DEBUG_COHESION_VERBOSE - ++typeindex; + ++typeIndex; #endif // Skip types that were included from external libraries. - if (!isInInputPath(type.filePath)) + if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath)) continue; #ifdef DEBUG_COHESION_VERBOSE - ++checkedcount; + ++checkedCount; std::ostringstream logLine; - logLine << std::right << std::setw(colwidth) << typeindex << '/'; - logLine << std::left << std::setw(colwidth) << typecount << '\t'; + logLine << std::right << std::setw(colWidth) << typeIndex << '/'; + logLine << std::left << std::setw(colWidth) << typeCount << '\t'; logLine << std::left << std::setw(32) << type.qualifiedName; #endif @@ -254,23 +255,15 @@ void CppMetricsParser::lackOfCohesion() } #ifdef DEBUG_COHESION_VERBOSE - auto finish = std::chrono::steady_clock::now(); - auto duration = finish - start; - auto durs = std::chrono::duration_cast(duration); - LOG(debug) << "=== Checked types: " << checkedcount - << ", Total runtime: " << durs.count() << "s ==="; + auto finishTime = std::chrono::steady_clock::now(); + auto durTime = finishTime - startTime; + auto durSecs = std::chrono::duration_cast(durTime); + LOG(debug) << "=== Checked types: " << checkedCount + << ", Total runtime: " << durSecs.count() << "s ==="; #endif }); } -bool CppMetricsParser::isInInputPath(const std::string& path_) const -{ - std::size_t i = 0; - while (i < _inputPaths.size() && path_.rfind(_inputPaths[i], 0) != 0) - ++i; - return i < _inputPaths.size(); -} - bool CppMetricsParser::parse() { functionParameters(); diff --git a/util/include/util/filesystem.h b/util/include/util/filesystem.h index 7454b16c5..5f685ff83 100644 --- a/util/include/util/filesystem.h +++ b/util/include/util/filesystem.h @@ -22,6 +22,19 @@ std::string binaryPathToInstallDir(const char* path); */ std::string findCurrentExecutableDir(); +/** + * @brief Determines if the given path is rooted under + * any of the given other paths. + * + * @param paths_ A list of canonical paths. + * @param path_ A canonical path to match against the given paths. + * @return True if any of the paths in paths_ is a prefix of path_, + * otherwise false. +*/ +bool isRootedUnderAnyOf( + const std::vector& paths_, + const std::string& path_); + } // namespace util } // namespace cc diff --git a/util/src/filesystem.cpp b/util/src/filesystem.cpp index d18c16b8d..e459102af 100644 --- a/util/src/filesystem.cpp +++ b/util/src/filesystem.cpp @@ -64,5 +64,16 @@ std::string findCurrentExecutableDir() return fs::path(exePath).parent_path().string(); } +bool isRootedUnderAnyOf( + const std::vector& paths_, + const std::string& path_) +{ + auto it = paths_.begin(); + const auto end = paths_.end(); + while (it != end && path_.rfind(*it, 0) != 0) + ++it; + return it != end; +} + } // namespace util } // namespace cc From 24ead2db1f1b3b5f87042370151929a2487daab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 25 Nov 2023 17:27:04 +0100 Subject: [PATCH 12/28] Initial implementation of the lack of cohesion metric. --- .../model/include/model/cppastnodemetrics.h | 4 +- .../cppmetricsparser/cppmetricsparser.h | 6 +- .../parser/src/cppmetricsparser.cpp | 106 ++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h index 3334a9bd7..10b7f2f2a 100644 --- a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h @@ -14,7 +14,9 @@ struct CppAstNodeMetrics enum Type { PARAMETER_COUNT, - MCCABE + MCCABE, + LACK_OF_COHESION, + LACK_OF_COHESION_HS, }; #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 518361a25..8f3e88e8c 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -10,6 +10,9 @@ #include #include +#include +#include + #include #include @@ -29,7 +32,8 @@ class CppMetricsParser : public AbstractParser private: void functionParameters(); void functionMcCabe(); - + void lackOfCohesion(); + std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; std::unique_ptr> _pool; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index bbe3cfdf4..7fcee5c6f 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -123,6 +123,109 @@ void CppMetricsParser::functionMcCabe() }); } +void CppMetricsParser::lackOfCohesion() +{ + util::OdbTransaction {_ctx.db} ([&, this] + { + // Simplify some type names for readability. + typedef model::CppMemberType::Kind MemberKind; + typedef model::CppAstNode::AstType AstType; + typedef std::uint64_t HashType; + + typedef odb::query QMem; + typedef odb::query QNode; + + // Calculate the cohesion metric for all types. + for (const model::CppRecord& type + : _ctx.db->query()) + { + std::unordered_set fieldHashes; + // Query all members... + for (const model::CppMemberType& field + : _ctx.db->query( + // ... that are fields + QMem::kind == MemberKind::Field && + // ... of the current type. + QMem::typeHash == type.entityHash + )) + { + // Record these fields for later use. + fieldHashes.insert(field.memberAstNode->entityHash); + } + size_t fieldCount = fieldHashes.size(); + + size_t methodCount = 0; + size_t totalCohesion = 0; + // Query all members... + for (const model::CppMemberType& method + : _ctx.db->query( + // ... that are methods + QMem::kind == MemberKind::Method && + // ... of the current type. + QMem::typeHash == type.entityHash + )) + { + // Do not consider methods with no explicit bodies. + model::FileLoc methodLoc = method.memberAstNode->location; + if (methodLoc.range.start < methodLoc.range.end) + { + std::unordered_set usedFields; + + // Query all AST nodes... + for (const model::CppAstNode& node + : _ctx.db->query( + // ... that use a variable for reading or writing + (QNode::astType == AstType::Read || + QNode::astType == AstType::Write) && + // ... in the same file as the current method + (QNode::location.file->path == methodLoc.file->path && + // ... within the textual scope of the current method's body. + (QNode::location.range.start.line >= methodLoc.range.start.line + || (QNode::location.range.start.line == methodLoc.range.start.line + && QNode::location.range.start.column >= methodLoc.range.start.column)) && + (QNode::location.range.end.line <= methodLoc.range.end.line + || (QNode::location.range.end.line == methodLoc.range.end.line + && QNode::location.range.end.column <= methodLoc.range.end.column))) + )) + { + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + { + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); + } + } + + ++methodCount; + totalCohesion += usedFields.size(); + } + } + + // Calculate and record metrics. + const double dF = fieldCount; + const double dM = methodCount; + const double dC = totalCohesion; + constexpr double scaling = 1e+4; + + // Standard lack of cohesion: + model::CppAstNodeMetrics lcm; + lcm.astNodeId = type.astNodeId; + lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; + lcm.value = static_cast(scaling * + (1.0 - dC / (dM * dF)));// range: [0,1] + _ctx.db->persist(lcm); + + // Henderson-Sellers variant: + model::CppAstNodeMetrics lcm_hs; + lcm_hs.astNodeId = type.astNodeId; + lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; + lcm_hs.value = static_cast(scaling * + ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] + _ctx.db->persist(lcm_hs); + } + }); +} + bool CppMetricsParser::parse() { // Function parameter number metric. @@ -130,6 +233,9 @@ bool CppMetricsParser::parse() // Function McCabe metric functionMcCabe(); + // Lack of cohesion within types. + lackOfCohesion(); + return true; } From bb2b46c10caf3d63a1a5add11775e6cec6b4ef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 11:30:52 +0100 Subject: [PATCH 13/28] Added manual ODB lazy pointer loading to avoid segfaults. --- .../parser/src/cppmetricsparser.cpp | 75 +++++++++++-------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 7fcee5c6f..c35052d0d 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -134,7 +134,7 @@ void CppMetricsParser::lackOfCohesion() typedef odb::query QMem; typedef odb::query QNode; - + // Calculate the cohesion metric for all types. for (const model::CppRecord& type : _ctx.db->query()) @@ -149,8 +149,11 @@ void CppMetricsParser::lackOfCohesion() QMem::typeHash == type.entityHash )) { - // Record these fields for later use. - fieldHashes.insert(field.memberAstNode->entityHash); + if (model::CppAstNodePtr fieldAst = field.memberAstNode.load()) + { + // Record these fields for later use. + fieldHashes.insert(fieldAst->entityHash); + } } size_t fieldCount = fieldHashes.size(); @@ -165,39 +168,49 @@ void CppMetricsParser::lackOfCohesion() QMem::typeHash == type.entityHash )) { - // Do not consider methods with no explicit bodies. - model::FileLoc methodLoc = method.memberAstNode->location; - if (methodLoc.range.start < methodLoc.range.end) + if (model::CppAstNodePtr methodAst = method.memberAstNode.load()) { - std::unordered_set usedFields; - - // Query all AST nodes... - for (const model::CppAstNode& node - : _ctx.db->query( - // ... that use a variable for reading or writing - (QNode::astType == AstType::Read || - QNode::astType == AstType::Write) && - // ... in the same file as the current method - (QNode::location.file->path == methodLoc.file->path && - // ... within the textual scope of the current method's body. - (QNode::location.range.start.line >= methodLoc.range.start.line - || (QNode::location.range.start.line == methodLoc.range.start.line - && QNode::location.range.start.column >= methodLoc.range.start.column)) && - (QNode::location.range.end.line <= methodLoc.range.end.line - || (QNode::location.range.end.line == methodLoc.range.end.line - && QNode::location.range.end.column <= methodLoc.range.end.column))) - )) + // Do not consider methods with no explicit bodies. + model::FileLoc methodLoc = methodAst->location; + model::FilePtr filePtr = methodLoc.file.load(); + if (filePtr && methodLoc.range.start < methodLoc.range.end) { - // If this AST node is a reference to a field of the type... - if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + std::unordered_set usedFields; + + // Query all AST nodes... + for (const model::CppAstNode& node + : _ctx.db->query( + // ... that use a variable for reading or writing + (QNode::astType == AstType::Read || + QNode::astType == AstType::Write) && + // ... in the same file as the current method + (QNode::location.file->path == filePtr->path && + // ... within the textual scope of the current method's body. + (QNode::location.range.start.line + >= methodLoc.range.start.line + || (QNode::location.range.start.line + == methodLoc.range.start.line + && QNode::location.range.start.column + >= methodLoc.range.start.column)) && + (QNode::location.range.end.line + <= methodLoc.range.end.line + || (QNode::location.range.end.line + == methodLoc.range.end.line + && QNode::location.range.end.column + <= methodLoc.range.end.column))) + )) { - // ... then mark it as used by this method. - usedFields.insert(node.entityHash); + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + { + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); + } } + + ++methodCount; + totalCohesion += usedFields.size(); } - - ++methodCount; - totalCohesion += usedFields.size(); } } From 87d8f5894767f95ee6f23a42a1342cdcb27617ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 14:42:46 +0100 Subject: [PATCH 14/28] Replaced the lazy pointer loading with db views. --- plugins/cpp/model/include/model/cpprecord.h | 37 +++++++ .../parser/src/cppmetricsparser.cpp | 99 ++++++++----------- 2 files changed, 77 insertions(+), 59 deletions(-) diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index ae9f15834..33897fbe2 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -90,6 +90,43 @@ struct CppRecordCount std::size_t count; }; +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) +struct CppFieldWithEntityHash +{ + #pragma db column(CppMemberType::typeHash) + std::uint64_t typeHash; + + #pragma db column(CppAstNode::entityHash) + std::size_t entityHash; +}; + +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + object(File : CppAstNode::location.file) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) +struct CppMethodWithLocation +{ + typedef cc::model::Position::PosType PosType; + + #pragma db column(CppMemberType::typeHash) + std::uint64_t typeHash; + + #pragma db column(CppAstNode::location.range.start.line) + PosType startLine; + #pragma db column(CppAstNode::location.range.start.column) + PosType startColumn; + #pragma db column(CppAstNode::location.range.end.line) + PosType endLine; + #pragma db column(CppAstNode::location.range.end.column) + PosType endColumn; + + #pragma db column(File::path) + std::string filePath; +}; } } diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index c35052d0d..aa20ae816 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -128,11 +128,10 @@ void CppMetricsParser::lackOfCohesion() util::OdbTransaction {_ctx.db} ([&, this] { // Simplify some type names for readability. - typedef model::CppMemberType::Kind MemberKind; typedef model::CppAstNode::AstType AstType; typedef std::uint64_t HashType; - typedef odb::query QMem; + typedef odb::query QMember; typedef odb::query QNode; // Calculate the cohesion metric for all types. @@ -140,77 +139,59 @@ void CppMetricsParser::lackOfCohesion() : _ctx.db->query()) { std::unordered_set fieldHashes; - // Query all members... - for (const model::CppMemberType& field - : _ctx.db->query( - // ... that are fields - QMem::kind == MemberKind::Field && - // ... of the current type. - QMem::typeHash == type.entityHash + // Query all fields of the current type. + for (const model::CppFieldWithEntityHash& field + : _ctx.db->query( + QMember::typeHash == type.entityHash )) { - if (model::CppAstNodePtr fieldAst = field.memberAstNode.load()) - { - // Record these fields for later use. - fieldHashes.insert(fieldAst->entityHash); - } + // Record these fields for later use. + fieldHashes.insert(field.entityHash); } size_t fieldCount = fieldHashes.size(); size_t methodCount = 0; size_t totalCohesion = 0; - // Query all members... - for (const model::CppMemberType& method - : _ctx.db->query( - // ... that are methods - QMem::kind == MemberKind::Method && - // ... of the current type. - QMem::typeHash == type.entityHash + // Query all methods of the current type. + for (const model::CppMethodWithLocation& method + : _ctx.db->query( + QMember::typeHash == type.entityHash )) { - if (model::CppAstNodePtr methodAst = method.memberAstNode.load()) + // Do not consider methods with no explicit bodies. + const model::Position start(method.startLine, method.startColumn); + const model::Position end(method.endLine, method.endColumn); + if (start < end) { - // Do not consider methods with no explicit bodies. - model::FileLoc methodLoc = methodAst->location; - model::FilePtr filePtr = methodLoc.file.load(); - if (filePtr && methodLoc.range.start < methodLoc.range.end) + std::unordered_set usedFields; + + // Query all AST nodes... + for (const model::CppAstNode& node + : _ctx.db->query( + // ... that use a variable for reading or writing + (QNode::astType == AstType::Read || + QNode::astType == AstType::Write) && + // ... in the same file as the current method + (QNode::location.file->path == method.filePath && + // ... within the textual scope of the current method's body. + (QNode::location.range.start.line >= start.line + || (QNode::location.range.start.line == start.line + && QNode::location.range.start.column >= start.column)) && + (QNode::location.range.end.line <= end.line + || (QNode::location.range.end.line == end.line + && QNode::location.range.end.column <= end.column))) + )) { - std::unordered_set usedFields; - - // Query all AST nodes... - for (const model::CppAstNode& node - : _ctx.db->query( - // ... that use a variable for reading or writing - (QNode::astType == AstType::Read || - QNode::astType == AstType::Write) && - // ... in the same file as the current method - (QNode::location.file->path == filePtr->path && - // ... within the textual scope of the current method's body. - (QNode::location.range.start.line - >= methodLoc.range.start.line - || (QNode::location.range.start.line - == methodLoc.range.start.line - && QNode::location.range.start.column - >= methodLoc.range.start.column)) && - (QNode::location.range.end.line - <= methodLoc.range.end.line - || (QNode::location.range.end.line - == methodLoc.range.end.line - && QNode::location.range.end.column - <= methodLoc.range.end.column))) - )) + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) { - // If this AST node is a reference to a field of the type... - if (fieldHashes.find(node.entityHash) != fieldHashes.end()) - { - // ... then mark it as used by this method. - usedFields.insert(node.entityHash); - } + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); } - - ++methodCount; - totalCohesion += usedFields.size(); } + + ++methodCount; + totalCohesion += usedFields.size(); } } From 9ee55af7685895a0656c69a54a67548c7bebb12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 15:43:36 +0100 Subject: [PATCH 15/28] Further refactor of queries via db views. --- plugins/cpp/model/include/model/cppastnode.h | 24 ++++++++++++ .../parser/src/cppmetricsparser.cpp | 39 ++++++++++--------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 67f93507b..062a66b1f 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -237,6 +237,30 @@ struct CppAstCount std::size_t count; }; +#pragma db view \ + object(CppAstNode) \ + object(File : CppAstNode::location.file) \ + query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ + || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) +struct CppRWAstNodeWithHashAndLoc +{ + typedef cc::model::Position::PosType PosType; + + #pragma db column(CppAstNode::entityHash) + std::uint64_t entityHash; + + #pragma db column(CppAstNode::location.range.start.line) + PosType startLine; + #pragma db column(CppAstNode::location.range.start.column) + PosType startColumn; + #pragma db column(CppAstNode::location.range.end.line) + PosType endLine; + #pragma db column(CppAstNode::location.range.end.column) + PosType endColumn; + + #pragma db column(File::path) + std::string filePath; +}; } } diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index aa20ae816..7c19059e8 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -128,12 +128,18 @@ void CppMetricsParser::lackOfCohesion() util::OdbTransaction {_ctx.db} ([&, this] { // Simplify some type names for readability. - typedef model::CppAstNode::AstType AstType; typedef std::uint64_t HashType; - typedef odb::query QMember; - typedef odb::query QNode; + typedef odb::query::query_columns QField; + const auto& QFieldTypeHash = QField::CppMemberType::typeHash; + typedef odb::query::query_columns QMethod; + const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; + + typedef odb::query::query_columns QNode; + const auto& QNodeFilePath = QNode::File::path; + const auto& QNodeRange = QNode::CppAstNode::location.range; + // Calculate the cohesion metric for all types. for (const model::CppRecord& type : _ctx.db->query()) @@ -142,7 +148,7 @@ void CppMetricsParser::lackOfCohesion() // Query all fields of the current type. for (const model::CppFieldWithEntityHash& field : _ctx.db->query( - QMember::typeHash == type.entityHash + QFieldTypeHash == type.entityHash )) { // Record these fields for later use. @@ -155,7 +161,7 @@ void CppMetricsParser::lackOfCohesion() // Query all methods of the current type. for (const model::CppMethodWithLocation& method : _ctx.db->query( - QMember::typeHash == type.entityHash + QMethodTypeHash == type.entityHash )) { // Do not consider methods with no explicit bodies. @@ -165,21 +171,18 @@ void CppMetricsParser::lackOfCohesion() { std::unordered_set usedFields; - // Query all AST nodes... - for (const model::CppAstNode& node - : _ctx.db->query( - // ... that use a variable for reading or writing - (QNode::astType == AstType::Read || - QNode::astType == AstType::Write) && + // Query all AST nodes that use a variable for reading or writing... + for (const model::CppRWAstNodeWithHashAndLoc& node + : _ctx.db->query( // ... in the same file as the current method - (QNode::location.file->path == method.filePath && + (QNodeFilePath == method.filePath && // ... within the textual scope of the current method's body. - (QNode::location.range.start.line >= start.line - || (QNode::location.range.start.line == start.line - && QNode::location.range.start.column >= start.column)) && - (QNode::location.range.end.line <= end.line - || (QNode::location.range.end.line == end.line - && QNode::location.range.end.column <= end.column))) + (QNodeRange.start.line >= start.line + || (QNodeRange.start.line == start.line + && QNodeRange.start.column >= start.column)) && + (QNodeRange.end.line <= end.line + || (QNodeRange.end.line == end.line + && QNodeRange.end.column <= end.column))) )) { // If this AST node is a reference to a field of the type... From bab58a960d9b76c79da9f42f768e5dfa3c4d993c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 26 Nov 2023 16:21:57 +0100 Subject: [PATCH 16/28] Removed unneeded fields from the db views. Renamed view types. --- plugins/cpp/model/include/model/cppastnode.h | 16 +--------------- plugins/cpp/model/include/model/cpprecord.h | 10 ++-------- .../parser/src/cppmetricsparser.cpp | 18 +++++++++--------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 062a66b1f..427cda5be 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -242,24 +242,10 @@ struct CppAstCount object(File : CppAstNode::location.file) \ query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) -struct CppRWAstNodeWithHashAndLoc +struct CohesionCppAstNodeView { - typedef cc::model::Position::PosType PosType; - #pragma db column(CppAstNode::entityHash) std::uint64_t entityHash; - - #pragma db column(CppAstNode::location.range.start.line) - PosType startLine; - #pragma db column(CppAstNode::location.range.start.column) - PosType startColumn; - #pragma db column(CppAstNode::location.range.end.line) - PosType endLine; - #pragma db column(CppAstNode::location.range.end.column) - PosType endColumn; - - #pragma db column(File::path) - std::string filePath; }; } } diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index 33897fbe2..583d8a426 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -94,11 +94,8 @@ struct CppRecordCount object(CppMemberType) \ object(CppAstNode : CppMemberType::memberAstNode) \ query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) -struct CppFieldWithEntityHash +struct CohesionCppFieldView { - #pragma db column(CppMemberType::typeHash) - std::uint64_t typeHash; - #pragma db column(CppAstNode::entityHash) std::size_t entityHash; }; @@ -108,13 +105,10 @@ struct CppFieldWithEntityHash object(CppAstNode : CppMemberType::memberAstNode) \ object(File : CppAstNode::location.file) \ query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) -struct CppMethodWithLocation +struct CohesionCppMethodView { typedef cc::model::Position::PosType PosType; - #pragma db column(CppMemberType::typeHash) - std::uint64_t typeHash; - #pragma db column(CppAstNode::location.range.start.line) PosType startLine; #pragma db column(CppAstNode::location.range.start.column) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 7c19059e8..fc01eb459 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -130,13 +130,13 @@ void CppMetricsParser::lackOfCohesion() // Simplify some type names for readability. typedef std::uint64_t HashType; - typedef odb::query::query_columns QField; + typedef odb::query::query_columns QField; const auto& QFieldTypeHash = QField::CppMemberType::typeHash; - typedef odb::query::query_columns QMethod; + typedef odb::query::query_columns QMethod; const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; - typedef odb::query::query_columns QNode; + typedef odb::query::query_columns QNode; const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; @@ -146,8 +146,8 @@ void CppMetricsParser::lackOfCohesion() { std::unordered_set fieldHashes; // Query all fields of the current type. - for (const model::CppFieldWithEntityHash& field - : _ctx.db->query( + for (const model::CohesionCppFieldView& field + : _ctx.db->query( QFieldTypeHash == type.entityHash )) { @@ -159,8 +159,8 @@ void CppMetricsParser::lackOfCohesion() size_t methodCount = 0; size_t totalCohesion = 0; // Query all methods of the current type. - for (const model::CppMethodWithLocation& method - : _ctx.db->query( + for (const model::CohesionCppMethodView& method + : _ctx.db->query( QMethodTypeHash == type.entityHash )) { @@ -172,8 +172,8 @@ void CppMetricsParser::lackOfCohesion() std::unordered_set usedFields; // Query all AST nodes that use a variable for reading or writing... - for (const model::CppRWAstNodeWithHashAndLoc& node - : _ctx.db->query( + for (const model::CohesionCppAstNodeView& node + : _ctx.db->query( // ... in the same file as the current method (QNodeFilePath == method.filePath && // ... within the textual scope of the current method's body. From a6747a07b7781850e70859957aafdc6062dc79ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:07:01 +0100 Subject: [PATCH 17/28] Added checks to ensure that the cohesion metrics are only calculated for internal types. Record access is now done via a specialized view. Minor refactor (code duplication fix) in the file location recording in the AST visitor. --- plugins/cpp/model/include/model/cpprecord.h | 20 ++++++++++ plugins/cpp/parser/src/clangastvisitor.h | 39 ++++++++----------- .../cppmetricsparser/cppmetricsparser.h | 5 +++ .../parser/src/cppmetricsparser.cpp | 26 ++++++++++--- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index 583d8a426..f96933317 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -90,6 +90,26 @@ struct CppRecordCount std::size_t count; }; + +#pragma db view \ + object(CppRecord) \ + object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ + object(File : CppAstNode::location.file) +struct CohesionCppRecordView +{ + #pragma db column(CppEntity::entityHash) + std::size_t entityHash; + + #pragma db column(CppEntity::qualifiedName) + std::string qualifiedName; + + #pragma db column(CppEntity::astNodeId) + CppAstNodeId astNodeId; + + #pragma db column(File::path) + std::string filePath; +}; + #pragma db view \ object(CppMemberType) \ object(CppAstNode : CppMemberType::memberAstNode) \ diff --git a/plugins/cpp/parser/src/clangastvisitor.h b/plugins/cpp/parser/src/clangastvisitor.h index 47bd608f0..100dce4c3 100644 --- a/plugins/cpp/parser/src/clangastvisitor.h +++ b/plugins/cpp/parser/src/clangastvisitor.h @@ -1620,34 +1620,27 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor model::FileLoc fileLoc; if (start_.isInvalid() || end_.isInvalid()) - { fileLoc.file = getFile(start_); - const std::string& type = fileLoc.file.load()->type; - if (type != model::File::DIRECTORY_TYPE && type != _cppSourceType) - { - fileLoc.file->type = _cppSourceType; - _ctx.srcMgr.updateFile(*fileLoc.file); - } - return fileLoc; - } - - clang::SourceLocation realStart = start_; - clang::SourceLocation realEnd = end_; + else + { + clang::SourceLocation realStart = start_; + clang::SourceLocation realEnd = end_; - if (_clangSrcMgr.isMacroBodyExpansion(start_)) - realStart = _clangSrcMgr.getExpansionLoc(start_); - if (_clangSrcMgr.isMacroArgExpansion(start_)) - realStart = _clangSrcMgr.getSpellingLoc(start_); + if (_clangSrcMgr.isMacroBodyExpansion(start_)) + realStart = _clangSrcMgr.getExpansionLoc(start_); + if (_clangSrcMgr.isMacroArgExpansion(start_)) + realStart = _clangSrcMgr.getSpellingLoc(start_); - if (_clangSrcMgr.isMacroBodyExpansion(end_)) - realEnd = _clangSrcMgr.getExpansionLoc(end_); - if (_clangSrcMgr.isMacroArgExpansion(end_)) - realEnd = _clangSrcMgr.getSpellingLoc(end_); + if (_clangSrcMgr.isMacroBodyExpansion(end_)) + realEnd = _clangSrcMgr.getExpansionLoc(end_); + if (_clangSrcMgr.isMacroArgExpansion(end_)) + realEnd = _clangSrcMgr.getSpellingLoc(end_); - if (!_isImplicit) - _fileLocUtil.setRange(realStart, realEnd, fileLoc.range); + if (!_isImplicit) + _fileLocUtil.setRange(realStart, realEnd, fileLoc.range); - fileLoc.file = getFile(realStart); + fileLoc.file = getFile(realStart); + } const std::string& type = fileLoc.file.load()->type; if (type != model::File::DIRECTORY_TYPE && type != _cppSourceType) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 8f3e88e8c..3ac6c1be0 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -34,6 +34,11 @@ class CppMetricsParser : public AbstractParser void functionMcCabe(); void lackOfCohesion(); + // Checks if the given (canonical) path begins with any of the input paths + // specified for the metrics parser via command line arguments on startup. + bool isInInputPath(const std::string& path_) const; + std::vector _inputPaths; + std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; std::unique_ptr> _pool; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index fc01eb459..d4393bba8 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -22,6 +22,10 @@ namespace parser CppMetricsParser::CppMetricsParser(ParserContext& ctx_): AbstractParser(ctx_) { + for (const std::string& path : + _ctx.options["input"].as>()) + _inputPaths.push_back(boost::filesystem::canonical(path).string()); + util::OdbTransaction {_ctx.db} ([&, this] { for (const model::CppFileMetrics& fm : _ctx.db->query()) @@ -141,9 +145,13 @@ void CppMetricsParser::lackOfCohesion() const auto& QNodeRange = QNode::CppAstNode::location.range; // Calculate the cohesion metric for all types. - for (const model::CppRecord& type - : _ctx.db->query()) + for (const model::CohesionCppRecordView& type + : _ctx.db->query()) { + // Skip types that were included from external libraries. + if (!isInInputPath(type.filePath)) + continue; + std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -154,10 +162,10 @@ void CppMetricsParser::lackOfCohesion() // Record these fields for later use. fieldHashes.insert(field.entityHash); } - size_t fieldCount = fieldHashes.size(); + std::size_t fieldCount = fieldHashes.size(); - size_t methodCount = 0; - size_t totalCohesion = 0; + std::size_t methodCount = 0; + std::size_t totalCohesion = 0; // Query all methods of the current type. for (const model::CohesionCppMethodView& method : _ctx.db->query( @@ -223,6 +231,14 @@ void CppMetricsParser::lackOfCohesion() }); } +bool CppMetricsParser::isInInputPath(const std::string& path_) const +{ + std::size_t i = 0; + while (i < _inputPaths.size() && path_.rfind(_inputPaths[i], 0) != 0) + ++i; + return i < _inputPaths.size(); +} + bool CppMetricsParser::parse() { // Function parameter number metric. From 31aaa8e400946e043c58afbe3cb551b3ac00a96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:07:57 +0100 Subject: [PATCH 18/28] Added debug utilities to the cohesion metrics. --- .../parser/src/cppmetricsparser.cpp | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index d4393bba8..390f9bd06 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -15,6 +15,16 @@ #include +// Controls whether cohesion metrics are printed to the output +// immediately as they are being calculated. +#define DEBUG_COHESION_VERBOSE + +#ifdef DEBUG_COHESION_VERBOSE +#include +#include +#include +#endif + namespace cc { namespace parser @@ -144,14 +154,37 @@ void CppMetricsParser::lackOfCohesion() const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; + #ifdef DEBUG_COHESION_VERBOSE + std::size_t typecount = + _ctx.db->query_value().count; + std::size_t typeindex = 0; + std::size_t checkedcount = 0; + + std::cout << "=== Lack of Cohesion (LoC) metrics parser ===" << std::endl; + int colwidth = static_cast(ceil(log10(typecount))); + auto start = std::chrono::steady_clock::now(); + #endif + // Calculate the cohesion metric for all types. for (const model::CohesionCppRecordView& type : _ctx.db->query()) { + #ifdef DEBUG_COHESION_VERBOSE + ++typeindex; + #endif + // Skip types that were included from external libraries. if (!isInInputPath(type.filePath)) continue; + #ifdef DEBUG_COHESION_VERBOSE + ++checkedcount; + std::cout << std::right << std::setw(colwidth) << typeindex << '/'; + std::cout << std::left << std::setw(colwidth) << typecount << '\t'; + std::cout << std::left << std::setw(32) << type.qualifiedName; + std::cout.flush(); + #endif + std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -227,7 +260,22 @@ void CppMetricsParser::lackOfCohesion() lcm_hs.value = static_cast(scaling * ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] _ctx.db->persist(lcm_hs); + + #ifdef DEBUG_COHESION_VERBOSE + std::cout << std::right << std::setw(8) << (lcm.value / scaling); + std::cout << std::right << std::setw(8) << (lcm_hs.value / scaling); + std::cout << std::endl; + #endif } + + #ifdef DEBUG_COHESION_VERBOSE + auto finish = std::chrono::steady_clock::now(); + auto duration = finish - start; + auto durs = std::chrono::duration_cast(duration); + std::cout << "=== Checked types: " << checkedcount + << ", Total runtime: " << durs.count() + << "s ===" << std::endl; + #endif }); } From 9ee52a9fe461dc08c6023799a5db143c1a95bc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:23:01 +0100 Subject: [PATCH 19/28] Cohesion database views have been moved to their own new header (in the metrics parser). --- plugins/cpp/model/include/model/cppastnode.h | 11 --- plugins/cpp/model/include/model/cpprecord.h | 52 ------------- plugins/cpp_metrics/model/CMakeLists.txt | 1 + .../model/include/model/cppcohesionmetrics.h | 77 +++++++++++++++++++ .../parser/src/cppmetricsparser.cpp | 2 + 5 files changed, 80 insertions(+), 63 deletions(-) create mode 100644 plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 427cda5be..7b7b51ef8 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -236,17 +236,6 @@ struct CppAstCount #pragma db column("count(" + CppAstNode::id + ")") std::size_t count; }; - -#pragma db view \ - object(CppAstNode) \ - object(File : CppAstNode::location.file) \ - query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ - || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) -struct CohesionCppAstNodeView -{ - #pragma db column(CppAstNode::entityHash) - std::uint64_t entityHash; -}; } } diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index f96933317..cfd44f54a 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -89,58 +89,6 @@ struct CppRecordCount #pragma db column("count(" + CppRecord::id + ")") std::size_t count; }; - - -#pragma db view \ - object(CppRecord) \ - object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ - object(File : CppAstNode::location.file) -struct CohesionCppRecordView -{ - #pragma db column(CppEntity::entityHash) - std::size_t entityHash; - - #pragma db column(CppEntity::qualifiedName) - std::string qualifiedName; - - #pragma db column(CppEntity::astNodeId) - CppAstNodeId astNodeId; - - #pragma db column(File::path) - std::string filePath; -}; - -#pragma db view \ - object(CppMemberType) \ - object(CppAstNode : CppMemberType::memberAstNode) \ - query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) -struct CohesionCppFieldView -{ - #pragma db column(CppAstNode::entityHash) - std::size_t entityHash; -}; - -#pragma db view \ - object(CppMemberType) \ - object(CppAstNode : CppMemberType::memberAstNode) \ - object(File : CppAstNode::location.file) \ - query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) -struct CohesionCppMethodView -{ - typedef cc::model::Position::PosType PosType; - - #pragma db column(CppAstNode::location.range.start.line) - PosType startLine; - #pragma db column(CppAstNode::location.range.start.column) - PosType startColumn; - #pragma db column(CppAstNode::location.range.end.line) - PosType endLine; - #pragma db column(CppAstNode::location.range.end.column) - PosType endColumn; - - #pragma db column(File::path) - std::string filePath; -}; } } diff --git a/plugins/cpp_metrics/model/CMakeLists.txt b/plugins/cpp_metrics/model/CMakeLists.txt index 2f5c29eb6..595a3ddf3 100644 --- a/plugins/cpp_metrics/model/CMakeLists.txt +++ b/plugins/cpp_metrics/model/CMakeLists.txt @@ -6,6 +6,7 @@ message(WARNING "${cpp_PLUGIN_DIR}/model/include") set(ODB_SOURCES include/model/cppastnodemetrics.h + include/model/cppcohesionmetrics.h include/model/cppfilemetrics.h) generate_odb_files("${ODB_SOURCES}" "cpp") diff --git a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h new file mode 100644 index 000000000..bc8a20629 --- /dev/null +++ b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h @@ -0,0 +1,77 @@ +#ifndef CC_MODEL_CPPCOHESIONMETRICS_H +#define CC_MODEL_CPPCOHESIONMETRICS_H + +#include +#include +#include + +namespace cc +{ +namespace model +{ +#pragma db view \ + object(CppRecord) \ + object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ + object(File : CppAstNode::location.file) +struct CohesionCppRecordView +{ + #pragma db column(CppEntity::entityHash) + std::size_t entityHash; + + #pragma db column(CppEntity::qualifiedName) + std::string qualifiedName; + + #pragma db column(CppEntity::astNodeId) + CppAstNodeId astNodeId; + + #pragma db column(File::path) + std::string filePath; +}; + +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?)) +struct CohesionCppFieldView +{ + #pragma db column(CppAstNode::entityHash) + std::size_t entityHash; +}; + +#pragma db view \ + object(CppMemberType) \ + object(CppAstNode : CppMemberType::memberAstNode) \ + object(File : CppAstNode::location.file) \ + query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?)) +struct CohesionCppMethodView +{ + typedef cc::model::Position::PosType PosType; + + #pragma db column(CppAstNode::location.range.start.line) + PosType startLine; + #pragma db column(CppAstNode::location.range.start.column) + PosType startColumn; + #pragma db column(CppAstNode::location.range.end.line) + PosType endLine; + #pragma db column(CppAstNode::location.range.end.column) + PosType endColumn; + + #pragma db column(File::path) + std::string filePath; +}; + +#pragma db view \ + object(CppAstNode) \ + object(File : CppAstNode::location.file) \ + query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \ + || CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?)) +struct CohesionCppAstNodeView +{ + #pragma db column(CppAstNode::entityHash) + std::uint64_t entityHash; +}; + +} //model +} //cc + +#endif //CC_MODEL_CPPCOHESIONMETRICS_H diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 390f9bd06..b2164803b 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include From e902fb284793f03375c846fb13bf8140dc33db1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 3 Dec 2023 13:43:22 +0100 Subject: [PATCH 20/28] Minor interface refactors + code comments. --- plugins/cpp/model/include/model/cppastnode.h | 1 + plugins/cpp/model/include/model/cpprecord.h | 1 + .../include/cppmetricsparser/cppmetricsparser.h | 6 ++++++ plugins/cpp_metrics/parser/src/cppmetricsparser.cpp | 13 ++++--------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/plugins/cpp/model/include/model/cppastnode.h b/plugins/cpp/model/include/model/cppastnode.h index 7b7b51ef8..67f93507b 100644 --- a/plugins/cpp/model/include/model/cppastnode.h +++ b/plugins/cpp/model/include/model/cppastnode.h @@ -236,6 +236,7 @@ struct CppAstCount #pragma db column("count(" + CppAstNode::id + ")") std::size_t count; }; + } } diff --git a/plugins/cpp/model/include/model/cpprecord.h b/plugins/cpp/model/include/model/cpprecord.h index cfd44f54a..ae9f15834 100644 --- a/plugins/cpp/model/include/model/cpprecord.h +++ b/plugins/cpp/model/include/model/cpprecord.h @@ -89,6 +89,7 @@ struct CppRecordCount #pragma db column("count(" + CppRecord::id + ")") std::size_t count; }; + } } diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 3ac6c1be0..061e2bee6 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -24,14 +24,20 @@ namespace parser class CppMetricsParser : public AbstractParser { public: + static constexpr double LOC_SCALING = 1e+4; + CppMetricsParser(ParserContext& ctx_); virtual ~CppMetricsParser(); + virtual bool cleanupDatabase() override; virtual bool parse() override; private: + // Calculate the count of parameters for every function. void functionParameters(); void functionMcCabe(); + // Calculate the lack of cohesion between member variables + // and member functions for every type. void lackOfCohesion(); // Checks if the given (canonical) path begins with any of the input paths diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index b2164803b..0ef9385b5 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -245,13 +245,12 @@ void CppMetricsParser::lackOfCohesion() const double dF = fieldCount; const double dM = methodCount; const double dC = totalCohesion; - constexpr double scaling = 1e+4; // Standard lack of cohesion: model::CppAstNodeMetrics lcm; lcm.astNodeId = type.astNodeId; lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; - lcm.value = static_cast(scaling * + lcm.value = static_cast(LOC_SCALING * (1.0 - dC / (dM * dF)));// range: [0,1] _ctx.db->persist(lcm); @@ -259,13 +258,13 @@ void CppMetricsParser::lackOfCohesion() model::CppAstNodeMetrics lcm_hs; lcm_hs.astNodeId = type.astNodeId; lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; - lcm_hs.value = static_cast(scaling * + lcm_hs.value = static_cast(LOC_SCALING * ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] _ctx.db->persist(lcm_hs); #ifdef DEBUG_COHESION_VERBOSE - std::cout << std::right << std::setw(8) << (lcm.value / scaling); - std::cout << std::right << std::setw(8) << (lcm_hs.value / scaling); + std::cout << std::right << std::setw(8) << (lcm.value / LOC_SCALING); + std::cout << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); std::cout << std::endl; #endif } @@ -291,14 +290,10 @@ bool CppMetricsParser::isInInputPath(const std::string& path_) const bool CppMetricsParser::parse() { - // Function parameter number metric. functionParameters(); // Function McCabe metric functionMcCabe(); - - // Lack of cohesion within types. lackOfCohesion(); - return true; } From 14971b7e80bff7e2989c8fb1067e0c87242e3169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 9 Dec 2023 13:09:30 +0100 Subject: [PATCH 21/28] Replaced std::cout with LOG(debug). --- .../parser/src/cppmetricsparser.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 0ef9385b5..2c35dcad6 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -22,7 +22,7 @@ #define DEBUG_COHESION_VERBOSE #ifdef DEBUG_COHESION_VERBOSE -#include +#include #include #include #endif @@ -162,7 +162,7 @@ void CppMetricsParser::lackOfCohesion() std::size_t typeindex = 0; std::size_t checkedcount = 0; - std::cout << "=== Lack of Cohesion (LoC) metrics parser ===" << std::endl; + LOG(debug) << "=== Lack of Cohesion (LoC) metrics parser ==="; int colwidth = static_cast(ceil(log10(typecount))); auto start = std::chrono::steady_clock::now(); #endif @@ -181,10 +181,10 @@ void CppMetricsParser::lackOfCohesion() #ifdef DEBUG_COHESION_VERBOSE ++checkedcount; - std::cout << std::right << std::setw(colwidth) << typeindex << '/'; - std::cout << std::left << std::setw(colwidth) << typecount << '\t'; - std::cout << std::left << std::setw(32) << type.qualifiedName; - std::cout.flush(); + std::ostringstream logLine; + logLine << std::right << std::setw(colwidth) << typeindex << '/'; + logLine << std::left << std::setw(colwidth) << typecount << '\t'; + logLine << std::left << std::setw(32) << type.qualifiedName; #endif std::unordered_set fieldHashes; @@ -263,9 +263,9 @@ void CppMetricsParser::lackOfCohesion() _ctx.db->persist(lcm_hs); #ifdef DEBUG_COHESION_VERBOSE - std::cout << std::right << std::setw(8) << (lcm.value / LOC_SCALING); - std::cout << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); - std::cout << std::endl; + logLine << std::right << std::setw(8) << (lcm.value / LOC_SCALING); + logLine << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); + LOG(debug) << logLine.str(); #endif } @@ -273,9 +273,8 @@ void CppMetricsParser::lackOfCohesion() auto finish = std::chrono::steady_clock::now(); auto duration = finish - start; auto durs = std::chrono::duration_cast(duration); - std::cout << "=== Checked types: " << checkedcount - << ", Total runtime: " << durs.count() - << "s ===" << std::endl; + LOG(debug) << "=== Checked types: " << checkedcount + << ", Total runtime: " << durs.count() << "s ==="; #endif }); } From 2fc27822256f25a5714bc3453abeec1d5f264934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Mon, 11 Dec 2023 21:41:18 +0100 Subject: [PATCH 22/28] isInInputPath is generalized and moved to cc::util::isRootedUnderAnyOf. Fixed the casing and naming of debug utility variables in the LoC parser. --- .../cppmetricsparser/cppmetricsparser.h | 4 -- .../parser/src/cppmetricsparser.cpp | 39 ++++++++----------- util/include/util/filesystem.h | 13 +++++++ util/src/filesystem.cpp | 11 ++++++ 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 061e2bee6..5524688f7 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -40,11 +40,7 @@ class CppMetricsParser : public AbstractParser // and member functions for every type. void lackOfCohesion(); - // Checks if the given (canonical) path begins with any of the input paths - // specified for the metrics parser via command line arguments on startup. - bool isInInputPath(const std::string& path_) const; std::vector _inputPaths; - std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; std::unique_ptr> _pool; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 2c35dcad6..d7f8e6941 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -157,14 +158,14 @@ void CppMetricsParser::lackOfCohesion() const auto& QNodeRange = QNode::CppAstNode::location.range; #ifdef DEBUG_COHESION_VERBOSE - std::size_t typecount = + std::size_t typeCount = _ctx.db->query_value().count; - std::size_t typeindex = 0; - std::size_t checkedcount = 0; + std::size_t typeIndex = 0; + std::size_t checkedCount = 0; LOG(debug) << "=== Lack of Cohesion (LoC) metrics parser ==="; - int colwidth = static_cast(ceil(log10(typecount))); - auto start = std::chrono::steady_clock::now(); + int colWidth = static_cast(ceil(log10(typeCount))); + auto startTime = std::chrono::steady_clock::now(); #endif // Calculate the cohesion metric for all types. @@ -172,18 +173,18 @@ void CppMetricsParser::lackOfCohesion() : _ctx.db->query()) { #ifdef DEBUG_COHESION_VERBOSE - ++typeindex; + ++typeIndex; #endif // Skip types that were included from external libraries. - if (!isInInputPath(type.filePath)) + if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath)) continue; #ifdef DEBUG_COHESION_VERBOSE - ++checkedcount; + ++checkedCount; std::ostringstream logLine; - logLine << std::right << std::setw(colwidth) << typeindex << '/'; - logLine << std::left << std::setw(colwidth) << typecount << '\t'; + logLine << std::right << std::setw(colWidth) << typeIndex << '/'; + logLine << std::left << std::setw(colWidth) << typeCount << '\t'; logLine << std::left << std::setw(32) << type.qualifiedName; #endif @@ -270,23 +271,15 @@ void CppMetricsParser::lackOfCohesion() } #ifdef DEBUG_COHESION_VERBOSE - auto finish = std::chrono::steady_clock::now(); - auto duration = finish - start; - auto durs = std::chrono::duration_cast(duration); - LOG(debug) << "=== Checked types: " << checkedcount - << ", Total runtime: " << durs.count() << "s ==="; + auto finishTime = std::chrono::steady_clock::now(); + auto durTime = finishTime - startTime; + auto durSecs = std::chrono::duration_cast(durTime); + LOG(debug) << "=== Checked types: " << checkedCount + << ", Total runtime: " << durSecs.count() << "s ==="; #endif }); } -bool CppMetricsParser::isInInputPath(const std::string& path_) const -{ - std::size_t i = 0; - while (i < _inputPaths.size() && path_.rfind(_inputPaths[i], 0) != 0) - ++i; - return i < _inputPaths.size(); -} - bool CppMetricsParser::parse() { functionParameters(); diff --git a/util/include/util/filesystem.h b/util/include/util/filesystem.h index 7454b16c5..5f685ff83 100644 --- a/util/include/util/filesystem.h +++ b/util/include/util/filesystem.h @@ -22,6 +22,19 @@ std::string binaryPathToInstallDir(const char* path); */ std::string findCurrentExecutableDir(); +/** + * @brief Determines if the given path is rooted under + * any of the given other paths. + * + * @param paths_ A list of canonical paths. + * @param path_ A canonical path to match against the given paths. + * @return True if any of the paths in paths_ is a prefix of path_, + * otherwise false. +*/ +bool isRootedUnderAnyOf( + const std::vector& paths_, + const std::string& path_); + } // namespace util } // namespace cc diff --git a/util/src/filesystem.cpp b/util/src/filesystem.cpp index d18c16b8d..e459102af 100644 --- a/util/src/filesystem.cpp +++ b/util/src/filesystem.cpp @@ -64,5 +64,16 @@ std::string findCurrentExecutableDir() return fs::path(exePath).parent_path().string(); } +bool isRootedUnderAnyOf( + const std::vector& paths_, + const std::string& path_) +{ + auto it = paths_.begin(); + const auto end = paths_.end(); + while (it != end && path_.rfind(*it, 0) != 0) + ++it; + return it != end; +} + } // namespace util } // namespace cc From 656dca0e01330c4129d9fea85b39390dc93b265a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 27 Jan 2024 10:38:18 +0100 Subject: [PATCH 23/28] Rebased branch onto master. --- .../parser/include/cppmetricsparser/cppmetricsparser.h | 1 + plugins/cpp_metrics/parser/src/cppmetricsparser.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 5524688f7..94f96523a 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -35,6 +35,7 @@ class CppMetricsParser : public AbstractParser private: // Calculate the count of parameters for every function. void functionParameters(); + // Calculate the McCabe complexity of functions. void functionMcCabe(); // Calculate the lack of cohesion between member variables // and member functions for every type. diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index d7f8e6941..e4fba78cf 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -283,7 +283,6 @@ void CppMetricsParser::lackOfCohesion() bool CppMetricsParser::parse() { functionParameters(); - // Function McCabe metric functionMcCabe(); lackOfCohesion(); return true; From 8d757aec85a6e8a20ca17b07436127f448a9eac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 27 Jan 2024 11:57:19 +0100 Subject: [PATCH 24/28] Changed the metrics value type to double in the database. Explicit edge cases in LoC formulas. --- .../model/include/model/cppastnodemetrics.h | 4 +-- .../cppmetricsparser/cppmetricsparser.h | 2 -- .../parser/src/cppmetricsparser.cpp | 26 ++++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h index 10b7f2f2a..5587b46ea 100644 --- a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h @@ -28,8 +28,8 @@ struct CppAstNodeMetrics #pragma db not_null Type type; - #pragma db not_null - unsigned value; + #pragma db null + double value; }; } //model diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 94f96523a..144c34df2 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -24,8 +24,6 @@ namespace parser class CppMetricsParser : public AbstractParser { public: - static constexpr double LOC_SCALING = 1e+4; - CppMetricsParser(ParserContext& ctx_); virtual ~CppMetricsParser(); diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index f2dfc5f84..5f3950387 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -184,7 +184,7 @@ void CppMetricsParser::lackOfCohesion() ++checkedCount; std::ostringstream logLine; logLine << std::right << std::setw(colWidth) << typeIndex << '/'; - logLine << std::left << std::setw(colWidth) << typeCount << '\t'; + logLine << std::left << std::setw(colWidth) << typeCount << ' '; logLine << std::left << std::setw(32) << type.qualifiedName; #endif @@ -246,26 +246,31 @@ void CppMetricsParser::lackOfCohesion() const double dF = fieldCount; const double dM = methodCount; const double dC = totalCohesion; + const bool trivial = fieldCount == 0 || methodCount == 0; + const bool singular = methodCount == 1; - // Standard lack of cohesion: + // Standard lack of cohesion (range: [0,1]) model::CppAstNodeMetrics lcm; lcm.astNodeId = type.astNodeId; lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; - lcm.value = static_cast(LOC_SCALING * - (1.0 - dC / (dM * dF)));// range: [0,1] + lcm.value = trivial ? 0.0 : + (1.0 - dC / (dM * dF)); _ctx.db->persist(lcm); - // Henderson-Sellers variant: + // Henderson-Sellers variant (range: [0,2]) model::CppAstNodeMetrics lcm_hs; lcm_hs.astNodeId = type.astNodeId; lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; - lcm_hs.value = static_cast(LOC_SCALING * - ((dM - dC / dF) / (dM - 1.0)));// range: [0,2] + lcm_hs.value = trivial ? 0.0 : singular ? NAN : + ((dM - dC / dF) / (dM - 1.0)); _ctx.db->persist(lcm_hs); #ifdef DEBUG_COHESION_VERBOSE - logLine << std::right << std::setw(8) << (lcm.value / LOC_SCALING); - logLine << std::right << std::setw(8) << (lcm_hs.value / LOC_SCALING); + constexpr int valuePrec = 4; + constexpr int valueWidth = valuePrec + 3; + logLine << std::setprecision(valuePrec); + logLine << std::right << std::setw(valueWidth) << lcm.value; + logLine << std::right << std::setw(valueWidth) << lcm_hs.value; LOG(debug) << logLine.str(); #endif } @@ -283,11 +288,8 @@ void CppMetricsParser::lackOfCohesion() bool CppMetricsParser::parse() { functionParameters(); - functionMcCabe(); - lackOfCohesion(); - return true; } From 9125ad07d327b13b4f49f8e00e2e7b2566df44f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 27 Jan 2024 23:37:17 +0100 Subject: [PATCH 25/28] Unit tests for LoC metrics. --- .../model/include/model/cppastnodemetrics.h | 12 + plugins/cpp_metrics/test/CMakeLists.txt | 7 +- .../test/sources/parser/CMakeLists.txt | 3 +- .../test/sources/parser/lackofcohesion.cpp | 223 ++++++++++++++++++ .../test/sources/service/CMakeLists.txt | 3 +- .../test/sources/service/lackofcohesion.cpp | 223 ++++++++++++++++++ .../test/src/cppmetricsparsertest.cpp | 100 +++++++- 7 files changed, 563 insertions(+), 8 deletions(-) create mode 100644 plugins/cpp_metrics/test/sources/parser/lackofcohesion.cpp create mode 100644 plugins/cpp_metrics/test/sources/service/lackofcohesion.cpp diff --git a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h index 5587b46ea..a5d84a91c 100644 --- a/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppastnodemetrics.h @@ -2,6 +2,8 @@ #define CC_MODEL_CPPASTNODEMETRICS_H #include +#include +#include namespace cc { @@ -32,6 +34,16 @@ struct CppAstNodeMetrics double value; }; +#pragma db view \ + object(CppRecord) \ + object(CppAstNodeMetrics : \ + CppRecord::astNodeId == CppAstNodeMetrics::astNodeId) +struct CppRecordMetricsView +{ + #pragma db column(CppAstNodeMetrics::value) + double value; +}; + } //model } //cc diff --git a/plugins/cpp_metrics/test/CMakeLists.txt b/plugins/cpp_metrics/test/CMakeLists.txt index 34b7cf60e..9c2fcb2d2 100644 --- a/plugins/cpp_metrics/test/CMakeLists.txt +++ b/plugins/cpp_metrics/test/CMakeLists.txt @@ -2,7 +2,9 @@ include_directories( ${PLUGIN_DIR}/model/include ${PLUGIN_DIR}/service/include ${CMAKE_CURRENT_BINARY_DIR}/../service/gen-cpp - ${PROJECT_SOURCE_DIR}/model/include) + ${PROJECT_SOURCE_DIR}/model/include + ${PROJECT_SOURCE_DIR}/plugins/cpp_metrics/model/include + ${PROJECT_BINARY_DIR}/plugins/cpp_metrics/model/include) include_directories(SYSTEM ${THRIFT_LIBTHRIFT_INCLUDE_DIRS}) @@ -27,8 +29,9 @@ target_link_libraries(cppmetricsservicetest pthread) target_link_libraries(cppmetricsparsertest - util + cppmetricsmodel model + util cppmodel ${Boost_LIBRARIES} ${GTEST_BOTH_LIBRARIES} diff --git a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt index 305294e31..7de446fab 100644 --- a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt +++ b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt @@ -2,4 +2,5 @@ cmake_minimum_required(VERSION 2.6) project(CppTestProject) add_library(CppTestProject STATIC - mccabe.cpp) + mccabe.cpp + lackofcohesion.cpp) diff --git a/plugins/cpp_metrics/test/sources/parser/lackofcohesion.cpp b/plugins/cpp_metrics/test/sources/parser/lackofcohesion.cpp new file mode 100644 index 000000000..a2c9b1f71 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/lackofcohesion.cpp @@ -0,0 +1,223 @@ +//////////////// + +struct trivial_0_0 +{ + +}; + +//////////////// + +struct trivial_1_0 +{ + int field1; +}; + +struct trivial_2_0 +{ + int field1; + int field2; +}; + +struct trivial_3_0 +{ + int field1; + int field2; + int field3; +}; + +//////////////// + +struct trivial_0_1 +{ + void method1() {} +}; + +struct trivial_0_2 +{ + void method1() {} + void method2() {} +}; + +struct trivial_0_3 +{ + void method1() {} + void method2() {} + void method3() {} +}; + +//////////////// + +struct fully_cohesive_read +{ + int single_field; + + int single_method() const { return single_field; } +}; + +struct fully_cohesive_write +{ + int single_field; + + void single_method(int value) { single_field = value; } +}; + +struct fully_cohesive_read_write +{ + int single_field; + + bool single_method(int value) + { + if (single_field != value) + { + single_field = value; + return true; + } + else return false; + } +}; + +//////////////// + +struct fully_cohesive_2_1 +{ + int field1; + int field2; + + int method1() const { return field1 + field2; } +}; + +struct fully_cohesive_1_2 +{ + int field1; + + int method1() const { return +field1; } + int method2() const { return -field1; } +}; + +struct fully_cohesive_2_2 +{ + int field1; + int field2; + + int method1() const { return field1 + field2; } + int method2() const { return field1 - field2; } +}; + +struct fully_cohesive_3_3 +{ + int field1; + int field2; + int field3; + + int method1() const { return field1 + field2 + field3; } + int method2() const { return field1 * field2 * field3; } + void method3(int value) { field1 = field2 = field3 = value; } +}; + +//////////////// + +struct fully_incohesive_1_1 +{ + int field1; + + void method1() {} +}; + +struct fully_incohesive_1_2 +{ + int field1; + + void method1() {} + void method2() {} +}; + +struct fully_incohesive_2_1 +{ + int field1; + int field2; + + void method1() {} +}; + +struct fully_incohesive_2_2 +{ + int field1; + int field2; + + void method1() {} + void method2() {} +}; + +//////////////// + +struct partially_cohesive_1_2 +{ + int field1; + + int method1() const { return field1; } + int method2() const { return 42; } +}; + +struct partially_cohesive_2_1 +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } +}; + +struct partially_cohesive_2_2_A +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } + int method2() const { return 42; } +}; + +struct partially_cohesive_2_2_B +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } + int method2() const { return field2 - 42; } +}; + +struct partially_cohesive_2_2_C +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } + int method2() const { return field2 - field1; } +}; + +//////////////// + +struct same_partial_coh_A +{ + int field1, field2, field3; + + int method1() const { return field1; } + void method2() { field2 = field1; } + int method3() { return field3 = field2 * field1; } +}; + +struct same_partial_coh_B +{ + int field1, field2, field3; + + int method1() const { return field1 + field1; } + void method2() { field2 += field1; } + int method3() { return field3 = field2 * field2 + field1 * field1; } +}; + +struct same_partial_coh_C +{ + int field1, field2, field3; + + int method1() const { return 2 * field3; } + void method2() { field2 = 42 * field3 / field2; } + int method3() { return field1 = field3 + field2; } +}; diff --git a/plugins/cpp_metrics/test/sources/service/CMakeLists.txt b/plugins/cpp_metrics/test/sources/service/CMakeLists.txt index 305294e31..7de446fab 100644 --- a/plugins/cpp_metrics/test/sources/service/CMakeLists.txt +++ b/plugins/cpp_metrics/test/sources/service/CMakeLists.txt @@ -2,4 +2,5 @@ cmake_minimum_required(VERSION 2.6) project(CppTestProject) add_library(CppTestProject STATIC - mccabe.cpp) + mccabe.cpp + lackofcohesion.cpp) diff --git a/plugins/cpp_metrics/test/sources/service/lackofcohesion.cpp b/plugins/cpp_metrics/test/sources/service/lackofcohesion.cpp new file mode 100644 index 000000000..a2c9b1f71 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/service/lackofcohesion.cpp @@ -0,0 +1,223 @@ +//////////////// + +struct trivial_0_0 +{ + +}; + +//////////////// + +struct trivial_1_0 +{ + int field1; +}; + +struct trivial_2_0 +{ + int field1; + int field2; +}; + +struct trivial_3_0 +{ + int field1; + int field2; + int field3; +}; + +//////////////// + +struct trivial_0_1 +{ + void method1() {} +}; + +struct trivial_0_2 +{ + void method1() {} + void method2() {} +}; + +struct trivial_0_3 +{ + void method1() {} + void method2() {} + void method3() {} +}; + +//////////////// + +struct fully_cohesive_read +{ + int single_field; + + int single_method() const { return single_field; } +}; + +struct fully_cohesive_write +{ + int single_field; + + void single_method(int value) { single_field = value; } +}; + +struct fully_cohesive_read_write +{ + int single_field; + + bool single_method(int value) + { + if (single_field != value) + { + single_field = value; + return true; + } + else return false; + } +}; + +//////////////// + +struct fully_cohesive_2_1 +{ + int field1; + int field2; + + int method1() const { return field1 + field2; } +}; + +struct fully_cohesive_1_2 +{ + int field1; + + int method1() const { return +field1; } + int method2() const { return -field1; } +}; + +struct fully_cohesive_2_2 +{ + int field1; + int field2; + + int method1() const { return field1 + field2; } + int method2() const { return field1 - field2; } +}; + +struct fully_cohesive_3_3 +{ + int field1; + int field2; + int field3; + + int method1() const { return field1 + field2 + field3; } + int method2() const { return field1 * field2 * field3; } + void method3(int value) { field1 = field2 = field3 = value; } +}; + +//////////////// + +struct fully_incohesive_1_1 +{ + int field1; + + void method1() {} +}; + +struct fully_incohesive_1_2 +{ + int field1; + + void method1() {} + void method2() {} +}; + +struct fully_incohesive_2_1 +{ + int field1; + int field2; + + void method1() {} +}; + +struct fully_incohesive_2_2 +{ + int field1; + int field2; + + void method1() {} + void method2() {} +}; + +//////////////// + +struct partially_cohesive_1_2 +{ + int field1; + + int method1() const { return field1; } + int method2() const { return 42; } +}; + +struct partially_cohesive_2_1 +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } +}; + +struct partially_cohesive_2_2_A +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } + int method2() const { return 42; } +}; + +struct partially_cohesive_2_2_B +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } + int method2() const { return field2 - 42; } +}; + +struct partially_cohesive_2_2_C +{ + int field1; + int field2; + + int method1() const { return field1 + 42; } + int method2() const { return field2 - field1; } +}; + +//////////////// + +struct same_partial_coh_A +{ + int field1, field2, field3; + + int method1() const { return field1; } + void method2() { field2 = field1; } + int method3() { return field3 = field2 * field1; } +}; + +struct same_partial_coh_B +{ + int field1, field2, field3; + + int method1() const { return field1 + field1; } + void method2() { field2 += field1; } + int method3() { return field3 = field2 * field2 + field1 * field1; } +}; + +struct same_partial_coh_C +{ + int field1, field2, field3; + + int method1() const { return 2 * field3; } + void method2() { field2 = 42 * field3 / field2; } + int method3() { return field1 = field3 + field2; } +}; diff --git a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp index c958ff44a..4b6df87da 100644 --- a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp +++ b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp @@ -1,7 +1,17 @@ #include +// Same as EXPECT_NEAR, but NANs are also considered equal. +#define EXPECT_EQF(val1, val2, abs_error) { \ + const auto v1 = val1; const auto v2 = val2; \ + const bool n1 = std::isnan(v1); const bool n2 = std::isnan(v2); \ + EXPECT_EQ(n1, n2); \ + if (!n1 && !n2) EXPECT_NEAR(v1, v2, abs_error); \ +} + #include #include +#include +#include #include #include @@ -19,16 +29,36 @@ class CppMetricsParserTest : public ::testing::Test {} protected: + typedef model::CppAstNodeMetrics::Type Type; + + double queryRecordMetric(const std::string& qualType_, Type metricType_); + std::shared_ptr _db; util::OdbTransaction _transaction; }; +double CppMetricsParserTest::queryRecordMetric( + const std::string& qualType_, + Type metricType_) +{ + typedef odb::query::query_columns QEntry; + const auto& QEntryQualName = QEntry::CppRecord::qualifiedName; + const auto& QEntryType = QEntry::CppAstNodeMetrics::type; + + return _db->query_value( + QEntryQualName == qualType_ && QEntryType == metricType_).value; +} + +// McCabe + +typedef std::pair McCabeParam; + class ParameterizedMcCabeTest : public CppMetricsParserTest, - public ::testing::WithParamInterface> + public ::testing::WithParamInterface {}; -std::vector> parameters = { +std::vector paramMcCabe = { {"conditionals", 8}, {"loops1", 6}, {"loops2", 6}, @@ -53,5 +83,67 @@ TEST_P(ParameterizedMcCabeTest, McCabeTest) { INSTANTIATE_TEST_SUITE_P( ParameterizedMcCabeTestSuite, ParameterizedMcCabeTest, - ::testing::ValuesIn(parameters) - ); \ No newline at end of file + ::testing::ValuesIn(paramMcCabe) +); + +// Lack of Cohesion + +struct LackOfCohesionParam +{ + std::string typeName; + double loc; + double locHS; +}; + +class ParameterizedLackOfCohesionTest + : public CppMetricsParserTest, + public ::testing::WithParamInterface +{}; + +constexpr double C1_3 = 1.0 / 3.0; +std::vector paramLackOfCohesion = { + {"trivial_0_0", 0, 0}, + {"trivial_1_0", 0, 0}, + {"trivial_2_0", 0, 0}, + {"trivial_3_0", 0, 0}, + {"trivial_0_1", 0, 0}, + {"trivial_0_2", 0, 0}, + {"trivial_0_3", 0, 0}, + {"fully_cohesive_read", 0, NAN}, + {"fully_cohesive_write", 0, NAN}, + {"fully_cohesive_read_write", 0, NAN}, + {"fully_cohesive_2_1", 0, NAN}, + {"fully_cohesive_1_2", 0, 0}, + {"fully_cohesive_2_2", 0, 0}, + {"fully_cohesive_3_3", 0, 0}, + {"fully_incohesive_1_1", 1, NAN}, + {"fully_incohesive_1_2", 1, 2}, + {"fully_incohesive_2_1", 1, NAN}, + {"fully_incohesive_2_2", 1, 2}, + {"partially_cohesive_1_2", 0.5, 1}, + {"partially_cohesive_2_1", 0.5, NAN}, + {"partially_cohesive_2_2_A", 0.75, 1.5}, + {"partially_cohesive_2_2_B", 0.5, 1}, + {"partially_cohesive_2_2_C", 0.25, 0.5}, + {"same_partial_coh_A", C1_3, 0.5}, + {"same_partial_coh_B", C1_3, 0.5}, + {"same_partial_coh_C", C1_3, 0.5}, +}; + +TEST_P(ParameterizedLackOfCohesionTest, LackOfCohesionTest) { + _transaction([&, this]() { + const std::string& qualType = GetParam().typeName; + double loc = queryRecordMetric(qualType, Type::LACK_OF_COHESION); + double locHS = queryRecordMetric(qualType, Type::LACK_OF_COHESION_HS); + + constexpr double tolerance = 1e-8; + EXPECT_EQF(GetParam().loc, loc, tolerance); + EXPECT_EQF(GetParam().locHS, locHS, tolerance); + }); +} + +INSTANTIATE_TEST_SUITE_P( + ParameterizedLackOfCohesionTestSuite, + ParameterizedLackOfCohesionTest, + ::testing::ValuesIn(paramLackOfCohesion) +); From 7caf4d7825e4e3bf2ba5d62334f26b62e7beee98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sun, 28 Jan 2024 10:22:50 +0100 Subject: [PATCH 26/28] Fixed missing math.h include. Query success is now also checked in test case. --- .../test/src/cppmetricsparsertest.cpp | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp index 4b6df87da..b299bea0f 100644 --- a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp +++ b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp @@ -1,9 +1,10 @@ +#include #include // Same as EXPECT_NEAR, but NANs are also considered equal. #define EXPECT_EQF(val1, val2, abs_error) { \ const auto v1 = val1; const auto v2 = val2; \ - const bool n1 = std::isnan(v1); const bool n2 = std::isnan(v2); \ + const bool n1 = isnan(v1); const bool n2 = isnan(v2); \ EXPECT_EQ(n1, n2); \ if (!n1 && !n2) EXPECT_NEAR(v1, v2, abs_error); \ } @@ -31,22 +32,32 @@ class CppMetricsParserTest : public ::testing::Test protected: typedef model::CppAstNodeMetrics::Type Type; - double queryRecordMetric(const std::string& qualType_, Type metricType_); + bool queryRecordMetric( + const std::string& qualType_, + Type metricType_, + double& result_); std::shared_ptr _db; util::OdbTransaction _transaction; }; -double CppMetricsParserTest::queryRecordMetric( +bool CppMetricsParserTest::queryRecordMetric( const std::string& qualType_, - Type metricType_) + Type metricType_, + double& result_) { typedef odb::query::query_columns QEntry; const auto& QEntryQualName = QEntry::CppRecord::qualifiedName; const auto& QEntryType = QEntry::CppAstNodeMetrics::type; - return _db->query_value( - QEntryQualName == qualType_ && QEntryType == metricType_).value; + model::CppRecordMetricsView entry; + if (_db->query_one( + QEntryQualName == qualType_ && QEntryType == metricType_, entry)) + { + result_ = entry.value; + return true; + } + else return false; } // McCabe @@ -133,8 +144,9 @@ std::vector paramLackOfCohesion = { TEST_P(ParameterizedLackOfCohesionTest, LackOfCohesionTest) { _transaction([&, this]() { const std::string& qualType = GetParam().typeName; - double loc = queryRecordMetric(qualType, Type::LACK_OF_COHESION); - double locHS = queryRecordMetric(qualType, Type::LACK_OF_COHESION_HS); + double loc, locHS; + ASSERT_TRUE(queryRecordMetric(qualType, Type::LACK_OF_COHESION, loc)); + ASSERT_TRUE(queryRecordMetric(qualType, Type::LACK_OF_COHESION_HS, locHS)); constexpr double tolerance = 1e-8; EXPECT_EQF(GetParam().loc, loc, tolerance); From 4f43dd7ee550b552e1e13bf910708082a5ae0f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Cser=C3=A9p?= Date: Sun, 4 Feb 2024 22:03:52 +0100 Subject: [PATCH 27/28] Add project path as input path during parsing for C++ metrics test. --- plugins/cpp/test/src/cpparsertest.cpp | 0 plugins/cpp_metrics/test/CMakeLists.txt | 2 ++ plugins/cpp_metrics/test/sources/parser/CMakeLists.txt | 4 ++-- plugins/cpp_metrics/test/sources/service/CMakeLists.txt | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 plugins/cpp/test/src/cpparsertest.cpp diff --git a/plugins/cpp/test/src/cpparsertest.cpp b/plugins/cpp/test/src/cpparsertest.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/cpp_metrics/test/CMakeLists.txt b/plugins/cpp_metrics/test/CMakeLists.txt index 9c2fcb2d2..5924e11ba 100644 --- a/plugins/cpp_metrics/test/CMakeLists.txt +++ b/plugins/cpp_metrics/test/CMakeLists.txt @@ -56,6 +56,7 @@ else() --database \"${TEST_DB}\" \ --name cppmetricsservicetest \ --input ${CMAKE_CURRENT_BINARY_DIR}/build/compile_commands.json \ + --input ${CMAKE_CURRENT_SOURCE_DIR}/sources/service \ --workspace ${CMAKE_CURRENT_BINARY_DIR}/workdir/ \ --force" "${TEST_DB}") @@ -71,6 +72,7 @@ else() --database \"${TEST_DB}\" \ --name cppparsertest \ --input ${CMAKE_CURRENT_BINARY_DIR}/build/compile_commands.json \ + --input ${CMAKE_CURRENT_SOURCE_DIR}/sources/parser \ --workspace ${CMAKE_CURRENT_BINARY_DIR}/workdir/ \ --force" "${TEST_DB}") diff --git a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt index 7de446fab..7a4633ff0 100644 --- a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt +++ b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 2.6) -project(CppTestProject) +project(CppMetricsTestProject) -add_library(CppTestProject STATIC +add_library(CppMetricsTestProject STATIC mccabe.cpp lackofcohesion.cpp) diff --git a/plugins/cpp_metrics/test/sources/service/CMakeLists.txt b/plugins/cpp_metrics/test/sources/service/CMakeLists.txt index 7de446fab..7a4633ff0 100644 --- a/plugins/cpp_metrics/test/sources/service/CMakeLists.txt +++ b/plugins/cpp_metrics/test/sources/service/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 2.6) -project(CppTestProject) +project(CppMetricsTestProject) -add_library(CppTestProject STATIC +add_library(CppMetricsTestProject STATIC mccabe.cpp lackofcohesion.cpp) From aa2d22bc3f99ec44726ac135d6c10a3f92530e89 Mon Sep 17 00:00:00 2001 From: Anett Fekete Date: Sun, 4 Feb 2024 23:48:53 +0100 Subject: [PATCH 28/28] Deleted trace macro. --- .../parser/src/cppmetricsparser.cpp | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 410e5c89d..7e8269a4d 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -19,16 +19,6 @@ #include -// Controls whether cohesion metrics are printed to the output -// immediately as they are being calculated. -#define DEBUG_COHESION_VERBOSE - -#ifdef DEBUG_COHESION_VERBOSE -#include -#include -#include -#endif - namespace cc { namespace parser @@ -167,38 +157,15 @@ void CppMetricsParser::lackOfCohesion() typedef odb::query::query_columns QNode; const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; - - #ifdef DEBUG_COHESION_VERBOSE - std::size_t typeCount = - _ctx.db->query_value().count; - std::size_t typeIndex = 0; - std::size_t checkedCount = 0; - - LOG(debug) << "=== Lack of Cohesion (LoC) metrics parser ==="; - int colWidth = static_cast(ceil(log10(typeCount))); - auto startTime = std::chrono::steady_clock::now(); - #endif // Calculate the cohesion metric for all types. for (const model::CohesionCppRecordView& type : _ctx.db->query()) { - #ifdef DEBUG_COHESION_VERBOSE - ++typeIndex; - #endif - // Skip types that were included from external libraries. if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath)) continue; - #ifdef DEBUG_COHESION_VERBOSE - ++checkedCount; - std::ostringstream logLine; - logLine << std::right << std::setw(colWidth) << typeIndex << '/'; - logLine << std::left << std::setw(colWidth) << typeCount << ' '; - logLine << std::left << std::setw(32) << type.qualifiedName; - #endif - std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -275,24 +242,7 @@ void CppMetricsParser::lackOfCohesion() lcm_hs.value = trivial ? 0.0 : singular ? NAN : ((dM - dC / dF) / (dM - 1.0)); _ctx.db->persist(lcm_hs); - - #ifdef DEBUG_COHESION_VERBOSE - constexpr int valuePrec = 4; - constexpr int valueWidth = valuePrec + 3; - logLine << std::setprecision(valuePrec); - logLine << std::right << std::setw(valueWidth) << lcm.value; - logLine << std::right << std::setw(valueWidth) << lcm_hs.value; - LOG(debug) << logLine.str(); - #endif } - - #ifdef DEBUG_COHESION_VERBOSE - auto finishTime = std::chrono::steady_clock::now(); - auto durTime = finishTime - startTime; - auto durSecs = std::chrono::duration_cast(durTime); - LOG(debug) << "=== Checked types: " << checkedCount - << ", Total runtime: " << durSecs.count() << "s ==="; - #endif }); }