Skip to content

Commit d922b0c

Browse files
authored
Lack of cohesion metrics (#688)
1 parent cc0615d commit d922b0c

13 files changed

Lines changed: 798 additions & 41 deletions

File tree

plugins/cpp/parser/src/clangastvisitor.h

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,34 +1633,27 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
16331633
model::FileLoc fileLoc;
16341634

16351635
if (start_.isInvalid() || end_.isInvalid())
1636-
{
16371636
fileLoc.file = getFile(start_);
1638-
const std::string& type = fileLoc.file.load()->type;
1639-
if (type != model::File::DIRECTORY_TYPE && type != _cppSourceType)
1640-
{
1641-
fileLoc.file->type = _cppSourceType;
1642-
_ctx.srcMgr.updateFile(*fileLoc.file);
1643-
}
1644-
return fileLoc;
1645-
}
1646-
1647-
clang::SourceLocation realStart = start_;
1648-
clang::SourceLocation realEnd = end_;
1637+
else
1638+
{
1639+
clang::SourceLocation realStart = start_;
1640+
clang::SourceLocation realEnd = end_;
16491641

1650-
if (_clangSrcMgr.isMacroBodyExpansion(start_))
1651-
realStart = _clangSrcMgr.getExpansionLoc(start_);
1652-
if (_clangSrcMgr.isMacroArgExpansion(start_))
1653-
realStart = _clangSrcMgr.getSpellingLoc(start_);
1642+
if (_clangSrcMgr.isMacroBodyExpansion(start_))
1643+
realStart = _clangSrcMgr.getExpansionLoc(start_);
1644+
if (_clangSrcMgr.isMacroArgExpansion(start_))
1645+
realStart = _clangSrcMgr.getSpellingLoc(start_);
16541646

1655-
if (_clangSrcMgr.isMacroBodyExpansion(end_))
1656-
realEnd = _clangSrcMgr.getExpansionLoc(end_);
1657-
if (_clangSrcMgr.isMacroArgExpansion(end_))
1658-
realEnd = _clangSrcMgr.getSpellingLoc(end_);
1647+
if (_clangSrcMgr.isMacroBodyExpansion(end_))
1648+
realEnd = _clangSrcMgr.getExpansionLoc(end_);
1649+
if (_clangSrcMgr.isMacroArgExpansion(end_))
1650+
realEnd = _clangSrcMgr.getSpellingLoc(end_);
16591651

1660-
if (!_isImplicit)
1661-
_fileLocUtil.setRange(realStart, realEnd, fileLoc.range);
1652+
if (!_isImplicit)
1653+
_fileLocUtil.setRange(realStart, realEnd, fileLoc.range);
16621654

1663-
fileLoc.file = getFile(realStart);
1655+
fileLoc.file = getFile(realStart);
1656+
}
16641657

16651658
const std::string& type = fileLoc.file.load()->type;
16661659
if (type != model::File::DIRECTORY_TYPE && type != _cppSourceType)

plugins/cpp/test/src/cpparsertest.cpp

Whitespace-only changes.

plugins/cpp_metrics/model/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ include_directories(
44

55
set(ODB_SOURCES
66
include/model/cppastnodemetrics.h
7+
include/model/cppcohesionmetrics.h
78
include/model/cppfilemetrics.h)
89

910
generate_odb_files("${ODB_SOURCES}" "cpp")

plugins/cpp_metrics/model/include/model/cppastnodemetrics.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#define CC_MODEL_CPPASTNODEMETRICS_H
33

44
#include <model/cppastnode.h>
5+
#include <model/cppentity.h>
6+
#include <model/cpprecord.h>
57

68
namespace cc
79
{
@@ -14,7 +16,9 @@ struct CppAstNodeMetrics
1416
enum Type
1517
{
1618
PARAMETER_COUNT,
17-
MCCABE
19+
MCCABE,
20+
LACK_OF_COHESION,
21+
LACK_OF_COHESION_HS,
1822
};
1923

2024
#pragma db id auto
@@ -26,8 +30,18 @@ struct CppAstNodeMetrics
2630
#pragma db not_null
2731
Type type;
2832

29-
#pragma db not_null
30-
unsigned value;
33+
#pragma db null
34+
double value;
35+
};
36+
37+
#pragma db view \
38+
object(CppRecord) \
39+
object(CppAstNodeMetrics : \
40+
CppRecord::astNodeId == CppAstNodeMetrics::astNodeId)
41+
struct CppRecordMetricsView
42+
{
43+
#pragma db column(CppAstNodeMetrics::value)
44+
double value;
3145
};
3246

3347
} //model
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#ifndef CC_MODEL_CPPCOHESIONMETRICS_H
2+
#define CC_MODEL_CPPCOHESIONMETRICS_H
3+
4+
#include <model/cppentity.h>
5+
#include <model/cpprecord.h>
6+
#include <model/cppastnode.h>
7+
8+
namespace cc
9+
{
10+
namespace model
11+
{
12+
#pragma db view \
13+
object(CppRecord) \
14+
object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \
15+
object(File : CppAstNode::location.file)
16+
struct CohesionCppRecordView
17+
{
18+
#pragma db column(CppEntity::entityHash)
19+
std::size_t entityHash;
20+
21+
#pragma db column(CppEntity::qualifiedName)
22+
std::string qualifiedName;
23+
24+
#pragma db column(CppEntity::astNodeId)
25+
CppAstNodeId astNodeId;
26+
27+
#pragma db column(File::path)
28+
std::string filePath;
29+
};
30+
31+
#pragma db view \
32+
object(CppMemberType) \
33+
object(CppAstNode : CppMemberType::memberAstNode) \
34+
query(CppMemberType::kind == cc::model::CppMemberType::Kind::Field && (?))
35+
struct CohesionCppFieldView
36+
{
37+
#pragma db column(CppAstNode::entityHash)
38+
std::size_t entityHash;
39+
};
40+
41+
#pragma db view \
42+
object(CppMemberType) \
43+
object(CppAstNode : CppMemberType::memberAstNode) \
44+
object(File : CppAstNode::location.file) \
45+
query(CppMemberType::kind == cc::model::CppMemberType::Kind::Method && (?))
46+
struct CohesionCppMethodView
47+
{
48+
typedef cc::model::Position::PosType PosType;
49+
50+
#pragma db column(CppAstNode::location.range.start.line)
51+
PosType startLine;
52+
#pragma db column(CppAstNode::location.range.start.column)
53+
PosType startColumn;
54+
#pragma db column(CppAstNode::location.range.end.line)
55+
PosType endLine;
56+
#pragma db column(CppAstNode::location.range.end.column)
57+
PosType endColumn;
58+
59+
#pragma db column(File::path)
60+
std::string filePath;
61+
};
62+
63+
#pragma db view \
64+
object(CppAstNode) \
65+
object(File : CppAstNode::location.file) \
66+
query((CppAstNode::astType == cc::model::CppAstNode::AstType::Read \
67+
|| CppAstNode::astType == cc::model::CppAstNode::AstType::Write) && (?))
68+
struct CohesionCppAstNodeView
69+
{
70+
#pragma db column(CppAstNode::entityHash)
71+
std::uint64_t entityHash;
72+
};
73+
74+
} //model
75+
} //cc
76+
77+
#endif //CC_MODEL_CPPCOHESIONMETRICS_H

plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#include <model/cppfunction.h>
1111
#include <model/cppfunction-odb.hxx>
1212

13+
#include <model/cpprecord.h>
14+
#include <model/cpprecord-odb.hxx>
15+
1316
#include <util/parserutil.h>
1417
#include <util/threadpool.h>
1518

@@ -23,12 +26,18 @@ class CppMetricsParser : public AbstractParser
2326
public:
2427
CppMetricsParser(ParserContext& ctx_);
2528
virtual ~CppMetricsParser();
29+
2630
virtual bool cleanupDatabase() override;
2731
virtual bool parse() override;
2832

2933
private:
34+
// Calculate the count of parameters for every function.
3035
void functionParameters();
36+
// Calculate the McCabe complexity of functions.
3137
void functionMcCabe();
38+
// Calculate the lack of cohesion between member variables
39+
// and member functions for every type.
40+
void lackOfCohesion();
3241

3342
std::vector<std::string> _inputPaths;
3443
std::unordered_set<model::FileId> _fileIdCache;

plugins/cpp_metrics/parser/src/cppmetricsparser.cpp

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <model/cppastnodemetrics.h>
44
#include <model/cppastnodemetrics-odb.hxx>
5+
#include <model/cppcohesionmetrics.h>
6+
#include <model/cppcohesionmetrics-odb.hxx>
57
#include <model/cppfilemetrics.h>
68
#include <model/cppfilemetrics-odb.hxx>
79

@@ -12,6 +14,7 @@
1214

1315
#include <util/filesystem.h>
1416
#include <util/logutil.h>
17+
#include <util/filesystem.h>
1518
#include <util/odbtransaction.h>
1619

1720
#include <memory>
@@ -138,13 +141,116 @@ void CppMetricsParser::functionMcCabe()
138141
});
139142
}
140143

144+
void CppMetricsParser::lackOfCohesion()
145+
{
146+
util::OdbTransaction {_ctx.db} ([&, this]
147+
{
148+
// Simplify some type names for readability.
149+
typedef std::uint64_t HashType;
150+
151+
typedef odb::query<model::CohesionCppFieldView>::query_columns QField;
152+
const auto& QFieldTypeHash = QField::CppMemberType::typeHash;
153+
154+
typedef odb::query<model::CohesionCppMethodView>::query_columns QMethod;
155+
const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash;
156+
157+
typedef odb::query<model::CohesionCppAstNodeView>::query_columns QNode;
158+
const auto& QNodeFilePath = QNode::File::path;
159+
const auto& QNodeRange = QNode::CppAstNode::location.range;
160+
161+
// Calculate the cohesion metric for all types.
162+
for (const model::CohesionCppRecordView& type
163+
: _ctx.db->query<model::CohesionCppRecordView>())
164+
{
165+
// Skip types that were included from external libraries.
166+
if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath))
167+
continue;
168+
169+
std::unordered_set<HashType> fieldHashes;
170+
// Query all fields of the current type.
171+
for (const model::CohesionCppFieldView& field
172+
: _ctx.db->query<model::CohesionCppFieldView>(
173+
QFieldTypeHash == type.entityHash
174+
))
175+
{
176+
// Record these fields for later use.
177+
fieldHashes.insert(field.entityHash);
178+
}
179+
std::size_t fieldCount = fieldHashes.size();
180+
181+
std::size_t methodCount = 0;
182+
std::size_t totalCohesion = 0;
183+
// Query all methods of the current type.
184+
for (const model::CohesionCppMethodView& method
185+
: _ctx.db->query<model::CohesionCppMethodView>(
186+
QMethodTypeHash == type.entityHash
187+
))
188+
{
189+
// Do not consider methods with no explicit bodies.
190+
const model::Position start(method.startLine, method.startColumn);
191+
const model::Position end(method.endLine, method.endColumn);
192+
if (start < end)
193+
{
194+
std::unordered_set<HashType> usedFields;
195+
196+
// Query all AST nodes that use a variable for reading or writing...
197+
for (const model::CohesionCppAstNodeView& node
198+
: _ctx.db->query<model::CohesionCppAstNodeView>(
199+
// ... in the same file as the current method
200+
(QNodeFilePath == method.filePath &&
201+
// ... within the textual scope of the current method's body.
202+
(QNodeRange.start.line >= start.line
203+
|| (QNodeRange.start.line == start.line
204+
&& QNodeRange.start.column >= start.column)) &&
205+
(QNodeRange.end.line <= end.line
206+
|| (QNodeRange.end.line == end.line
207+
&& QNodeRange.end.column <= end.column)))
208+
))
209+
{
210+
// If this AST node is a reference to a field of the type...
211+
if (fieldHashes.find(node.entityHash) != fieldHashes.end())
212+
{
213+
// ... then mark it as used by this method.
214+
usedFields.insert(node.entityHash);
215+
}
216+
}
217+
218+
++methodCount;
219+
totalCohesion += usedFields.size();
220+
}
221+
}
222+
223+
// Calculate and record metrics.
224+
const double dF = fieldCount;
225+
const double dM = methodCount;
226+
const double dC = totalCohesion;
227+
const bool trivial = fieldCount == 0 || methodCount == 0;
228+
const bool singular = methodCount == 1;
229+
230+
// Standard lack of cohesion (range: [0,1])
231+
model::CppAstNodeMetrics lcm;
232+
lcm.astNodeId = type.astNodeId;
233+
lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION;
234+
lcm.value = trivial ? 0.0 :
235+
(1.0 - dC / (dM * dF));
236+
_ctx.db->persist(lcm);
237+
238+
// Henderson-Sellers variant (range: [0,2])
239+
model::CppAstNodeMetrics lcm_hs;
240+
lcm_hs.astNodeId = type.astNodeId;
241+
lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS;
242+
lcm_hs.value = trivial ? 0.0 : singular ? NAN :
243+
((dM - dC / dF) / (dM - 1.0));
244+
_ctx.db->persist(lcm_hs);
245+
}
246+
});
247+
}
248+
141249
bool CppMetricsParser::parse()
142250
{
143-
// Function parameter number metric.
144251
functionParameters();
145-
// Function McCabe metric
146252
functionMcCabe();
147-
253+
lackOfCohesion();
148254
return true;
149255
}
150256

plugins/cpp_metrics/test/CMakeLists.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ include_directories(
22
${PLUGIN_DIR}/model/include
33
${PLUGIN_DIR}/service/include
44
${CMAKE_CURRENT_BINARY_DIR}/../service/gen-cpp
5-
${PROJECT_SOURCE_DIR}/model/include)
5+
${PROJECT_SOURCE_DIR}/model/include
6+
${PROJECT_SOURCE_DIR}/plugins/cpp_metrics/model/include
7+
${PROJECT_BINARY_DIR}/plugins/cpp_metrics/model/include)
68

79
include_directories(SYSTEM
810
${THRIFT_LIBTHRIFT_INCLUDE_DIRS})
@@ -27,8 +29,9 @@ target_link_libraries(cppmetricsservicetest
2729
pthread)
2830

2931
target_link_libraries(cppmetricsparsertest
30-
util
32+
cppmetricsmodel
3133
model
34+
util
3235
cppmodel
3336
${Boost_LIBRARIES}
3437
${GTEST_BOTH_LIBRARIES}
@@ -53,6 +56,7 @@ else()
5356
--database \"${TEST_DB}\" \
5457
--name cppmetricsservicetest \
5558
--input ${CMAKE_CURRENT_BINARY_DIR}/build/compile_commands.json \
59+
--input ${CMAKE_CURRENT_SOURCE_DIR}/sources/service \
5660
--workspace ${CMAKE_CURRENT_BINARY_DIR}/workdir/ \
5761
--force"
5862
"${TEST_DB}")
@@ -68,6 +72,7 @@ else()
6872
--database \"${TEST_DB}\" \
6973
--name cppparsertest \
7074
--input ${CMAKE_CURRENT_BINARY_DIR}/build/compile_commands.json \
75+
--input ${CMAKE_CURRENT_SOURCE_DIR}/sources/parser \
7176
--workspace ${CMAKE_CURRENT_BINARY_DIR}/workdir/ \
7277
--force"
7378
"${TEST_DB}")
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
cmake_minimum_required(VERSION 2.6)
2-
project(CppTestProject)
2+
project(CppMetricsTestProject)
33

4-
add_library(CppTestProject STATIC
5-
mccabe.cpp)
4+
add_library(CppMetricsTestProject STATIC
5+
mccabe.cpp
6+
lackofcohesion.cpp)

0 commit comments

Comments
 (0)