Skip to content

Commit 0b7517d

Browse files
Seeker04Seeker04mcserep
authored
Initial implementation of type McCabe (#683) (#727)
Co-authored-by: Seeker04 <zahoranb@proton.me> Co-authored-by: Máté Cserép <mcserep@gmail.com>
1 parent e86e58b commit 0b7517d

File tree

16 files changed

+678
-14
lines changed

16 files changed

+678
-14
lines changed

plugins/cpp/model/include/model/common.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ enum Tag
2323
Static = 3,
2424
Implicit = 4,
2525
Global = 5,
26-
Constant = 6
26+
Constant = 6,
27+
TemplateSpecialization = 7,
28+
TemplateInstantiation = 8 // all cases other than explicit specialization
2729
};
2830

2931
inline std::string visibilityToString(Visibility v_)

plugins/cpp/parser/src/clangastvisitor.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,15 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
621621
cppRecord->isAbstract = crd->isAbstract();
622622
cppRecord->isPOD = crd->isPOD();
623623

624+
if (crd->getTemplateInstantiationPattern())
625+
{
626+
cppRecord->tags.insert(
627+
crd->getTemplateSpecializationKind() == clang::TSK_ExplicitSpecialization
628+
? model::Tag::TemplateSpecialization
629+
: model::Tag::TemplateInstantiation
630+
);
631+
}
632+
624633
//--- CppInheritance ---//
625634

626635
for (auto it = crd->bases_begin(); it != crd->bases_end(); ++it)

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ struct CppAstNodeMetrics
1616
enum Type
1717
{
1818
PARAMETER_COUNT = 1,
19-
MCCABE = 2,
20-
BUMPY_ROAD = 3,
21-
LACK_OF_COHESION = 4,
22-
LACK_OF_COHESION_HS = 5,
19+
MCCABE_FUNCTION = 2,
20+
MCCABE_TYPE = 3,
21+
BUMPY_ROAD = 4,
22+
LACK_OF_COHESION = 5,
23+
LACK_OF_COHESION_HS = 6,
2324
};
2425

2526
#pragma db id auto

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class CppMetricsParser : public AbstractParser
6666
void functionMcCabe();
6767
// Calculate the bumpy road metric for every function.
6868
void functionBumpyRoad();
69+
// Calculate the McCabe complexity of types.
70+
void typeMcCabe();
6971
// Calculate the lack of cohesion between member variables
7072
// and member functions for every type.
7173
void lackOfCohesion();
@@ -112,7 +114,7 @@ class CppMetricsParser : public AbstractParser
112114
util::make_thread_pool<TJobParam>(_threadCount,
113115
[&](const TJobParam& job)
114116
{
115-
LOG(info) << '(' << job.first << '/' << partitions_
117+
LOG(debug) << '(' << job.first << '/' << partitions_
116118
<< ") " << name_;
117119
worker_(job.second);
118120
});

plugins/cpp_metrics/parser/src/cppmetricsparser.cpp

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ void CppMetricsParser::functionMcCabe()
134134
{
135135
model::CppAstNodeMetrics funcMcCabe;
136136
funcMcCabe.astNodeId = param.astNodeId;
137-
funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE;
137+
funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE_FUNCTION;
138138
funcMcCabe.value = param.mccabe;
139139
_ctx.db->persist(funcMcCabe);
140140
}
@@ -169,6 +169,94 @@ void CppMetricsParser::functionBumpyRoad()
169169
});
170170
}
171171

172+
void CppMetricsParser::typeMcCabe()
173+
{
174+
util::OdbTransaction {_ctx.db} ([&, this]
175+
{
176+
using MemberT = model::CppMemberType;
177+
using AstNode = model::CppAstNode;
178+
using Entity = model::CppEntity;
179+
using AstNodeMet = model::CppAstNodeMetrics;
180+
181+
std::map<model::CppAstNodeId, unsigned int> mcValues;
182+
183+
// Process all class definitions
184+
for (const auto& type : _ctx.db->query<AstNode>(
185+
odb::query<AstNode>::symbolType == AstNode::SymbolType::Type &&
186+
odb::query<AstNode>::astType == AstNode::AstType::Definition))
187+
{
188+
// Skip if class is included from external library
189+
type.location.file.load();
190+
const auto typeFile = _ctx.db->query_one<model::File>(
191+
odb::query<model::File>::id == type.location.file->id);
192+
if (!typeFile || !cc::util::isRootedUnderAnyOf(_inputPaths, typeFile->path))
193+
continue;
194+
195+
// Skip if its a template instantiation
196+
const auto typeEntity = _ctx.db->query_one<Entity>(
197+
odb::query<Entity>::astNodeId == type.id);
198+
if (typeEntity && typeEntity->tags.find(model::Tag::TemplateInstantiation)
199+
!= typeEntity->tags.cend())
200+
continue;
201+
202+
mcValues[type.id] = 0;
203+
204+
// Process its methods
205+
for (const auto& method : _ctx.db->query<MemberT>(
206+
odb::query<MemberT>::typeHash == type.entityHash &&
207+
odb::query<MemberT>::kind == MemberT::Kind::Method))
208+
{
209+
// Lookup AST node of method
210+
method.memberAstNode.load();
211+
const auto methodAstNode = _ctx.db->query_one<AstNode>(
212+
odb::query<AstNode>::id == method.memberAstNode->id);
213+
if (!methodAstNode)
214+
continue;
215+
216+
// Lookup its definition (different AST node if not defined in class body)
217+
auto methodDefs = _ctx.db->query<AstNode>(
218+
odb::query<AstNode>::entityHash == methodAstNode->entityHash &&
219+
odb::query<AstNode>::symbolType == AstNode::SymbolType::Function &&
220+
odb::query<AstNode>::astType == AstNode::AstType::Definition);
221+
if (methodDefs.empty())
222+
continue;
223+
const auto methodDef = *methodDefs.begin();
224+
// Note: we cannot use query_one, because a project might have multiple
225+
// functions with the same entityHash compiled to different binaries
226+
// So we take the first result, which introduces a small level of
227+
// potential inaccuracy
228+
// This could be optimized in the future if linkage information about
229+
// translation units got added to the database
230+
231+
// Skip implicitly defined methods (constructors, operator=, etc.)
232+
const auto entity = _ctx.db->query_one<Entity>(
233+
odb::query<Entity>::astNodeId == methodDef.id);
234+
if (entity && entity->tags.find(model::Tag::Implicit) != entity->tags.cend())
235+
continue;
236+
237+
// Lookup metrics of this definition
238+
const auto funcMetrics = _ctx.db->query_one<AstNodeMet>(
239+
odb::query<AstNodeMet>::astNodeId == methodDef.id &&
240+
odb::query<AstNodeMet>::type == model::CppAstNodeMetrics::Type::MCCABE_FUNCTION);
241+
if (funcMetrics)
242+
{
243+
// Increase class mccabe by the method's
244+
mcValues[type.id] += funcMetrics->value;
245+
}
246+
}
247+
}
248+
249+
for (const auto& mcValue : mcValues)
250+
{
251+
model::CppAstNodeMetrics typeMcMetric;
252+
typeMcMetric.astNodeId = mcValue.first;
253+
typeMcMetric.type = model::CppAstNodeMetrics::Type::MCCABE_TYPE;
254+
typeMcMetric.value = mcValue.second;
255+
_ctx.db->persist(typeMcMetric);
256+
}
257+
});
258+
}
259+
172260
void CppMetricsParser::lackOfCohesion()
173261
{
174262
// Calculate the cohesion metric for all types on parallel threads.
@@ -284,6 +372,8 @@ bool CppMetricsParser::parse()
284372
functionMcCabe();
285373
LOG(info) << "[cppmetricsparser] Computing Bumpy Road metric for functions.";
286374
functionBumpyRoad();
375+
LOG(info) << "[cppmetricsparser] Computing McCabe metric for types.";
376+
typeMcCabe();
287377
LOG(info) << "[cppmetricsparser] Computing Lack of Cohesion metric for types.";
288378
lackOfCohesion();
289379
return true;

plugins/cpp_metrics/service/cxxmetrics.thrift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ namespace java cc.service.cppmetrics
77
enum CppAstNodeMetricsType
88
{
99
ParameterCount = 1,
10-
McCabe = 2,
11-
BumpyRoad = 3,
12-
LackOfCohesion = 4,
13-
LackOfCohesionHS = 5,
10+
McCabeFunction = 2,
11+
McCabeType = 3,
12+
BumpyRoad = 4,
13+
LackOfCohesion = 5,
14+
LackOfCohesionHS = 6,
1415
}
1516

1617
enum CppModuleMetricsType

plugins/cpp_metrics/service/src/cppmetricsservice.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,14 @@ void CppMetricsServiceHandler::getCppAstNodeMetricsTypeNames(
3232
typeName.name = "Number of function parameters";
3333
_return.push_back(typeName);
3434

35-
typeName.type = CppAstNodeMetricsType::McCabe;
35+
typeName.type = CppAstNodeMetricsType::McCabeFunction;
3636
typeName.name = "Cyclomatic (McCabe) complexity of function";
3737
_return.push_back(typeName);
3838

39+
typeName.type = CppAstNodeMetricsType::McCabeType;
40+
typeName.name = "Cyclomatic (McCabe) complexity of type";
41+
_return.push_back(typeName);
42+
3943
typeName.type = CppAstNodeMetricsType::BumpyRoad;
4044
typeName.name = "Bumpy road complexity of function";
4145
_return.push_back(typeName);

plugins/cpp_metrics/test/sources/parser/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.6)
22
project(CppMetricsTestProject)
33

44
add_library(CppMetricsTestProject STATIC
5-
mccabe.cpp
5+
functionmccabe.cpp
6+
typemccabe.cpp
67
lackofcohesion.cpp
78
bumpyroad.cpp)

plugins/cpp_metrics/test/sources/parser/mccabe.cpp renamed to plugins/cpp_metrics/test/sources/parser/functionmccabe.cpp

File renamed without changes.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include "typemccabe.h"
2+
3+
void ClassMethodsOutside::conditionals(int arg1, bool arg2) { // +1
4+
if (arg2) { } // +1
5+
else { }
6+
7+
if ((arg1 > 5 && arg2) || (arg1 < 13 && !arg2)) { } // +4
8+
9+
int local1 = arg2 ? arg1 / 2 : arg1 * 2; // + 1
10+
int local2 = local1 ?: arg1 + 5; // +1
11+
} // 8
12+
13+
void ClassMethodsOutside::loops1() { // +1
14+
for (int i = 0; i < 5; ++i) {} // +1
15+
16+
int j = 0;
17+
while(j < 5) { ++j; } // +1
18+
19+
do
20+
{
21+
++j;
22+
} while (j < 10); // +1
23+
24+
char str[] = "hello";
25+
26+
for(char c : str) // +1
27+
{
28+
if (c == 'h') {} // +1
29+
}
30+
} // 6
31+
32+
void ClassMethodsOutside::loops2(int arg1, bool arg2) // +1
33+
{
34+
while(arg2) // +1
35+
{
36+
if (arg1 < 5) // +1
37+
{
38+
arg1 *= 2;
39+
continue; // +1
40+
}
41+
else if (arg1 > 30) // +1
42+
{
43+
arg1 /= 2;
44+
continue; // +1
45+
}
46+
break;
47+
}
48+
} // 6
49+
50+
int ClassMethodsOutside::loops3(int arg1) // +1
51+
{
52+
const int LIMIT = 42;
53+
int result = 0;
54+
for (int i = 0; i < arg1 * 2; ++i) // +1
55+
{
56+
++result;
57+
for (int j = i; j < arg1 * 3; ++j) // +1
58+
{
59+
result -= j/5;
60+
if (result >= LIMIT) // +1
61+
goto endfor; // +1
62+
}
63+
}
64+
endfor:
65+
return result;
66+
} // 5
67+
68+
void ClassMethodsOutside::switchcase(int arg1) // +1
69+
{
70+
switch(arg1)
71+
{
72+
case 1: // +1
73+
break;
74+
case 2: // +1
75+
break;
76+
case 3: // +1
77+
break;
78+
default: // +1
79+
switch(arg1 * 5) {
80+
case 85: // +1
81+
break;
82+
case 90: // +1
83+
break;
84+
}
85+
break;
86+
}
87+
} // 7
88+
89+
void ClassMethodsOutside::fragile(int arg1) // +1
90+
{} // 1
91+
92+
void ClassMethodsOutside::trycatch(int arg1) // +1
93+
{
94+
try
95+
{
96+
fragile(arg1);
97+
}
98+
catch (int err) // +1
99+
{
100+
101+
}
102+
catch (float err) // +1
103+
{
104+
105+
}
106+
} // 3
107+
108+
void ClassMethodsOutside::method1(int arg1) // +1
109+
{
110+
for (unsigned int i = arg1; i < 10; ++i) // +1
111+
{
112+
switch(arg1)
113+
{
114+
case -1: // +1
115+
goto endfor; // +1
116+
case 0: // +1
117+
break;
118+
case 1: // +1
119+
break;
120+
default: // +1
121+
continue; // +1
122+
}
123+
arg1 *= 2;
124+
}
125+
endfor:;
126+
} // 8
127+
128+
int ClassMethodsInsideAndOutside::baz() // +1
129+
{
130+
if (true) // +1
131+
{
132+
return 42;
133+
}
134+
} // 2
135+
136+
bool ClassWithInnerClass::bar(bool b1, bool b2) // +1
137+
{
138+
return b1 || b2; // +1
139+
} // 2
140+
141+
bool ClassWithInnerClass::InnerClass::bar(bool b1, bool b2) // +1
142+
{
143+
return b1 || b2; // +1
144+
} // 2
145+

0 commit comments

Comments
 (0)