Skip to content

Commit

Permalink
Increased granularity of McCabe metrics. Configurable extraction name…
Browse files Browse the repository at this point in the history
… via cmd args.
  • Loading branch information
dbukki committed Mar 28, 2024
1 parent 3d4a484 commit 66f6e3a
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 35 deletions.
14 changes: 11 additions & 3 deletions plugins/cpp/model/include/model/cppfunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ struct CppFunction : CppTypedEntity
#pragma db on_delete(cascade)
std::vector<odb::lazy_shared_ptr<CppVariable>> locals;

unsigned int mccabe;
bool isDefinition;
unsigned int branchCount;
unsigned int loopCount;
unsigned int flowCount;

unsigned int bumpiness;
unsigned int statementCount;

Expand Down Expand Up @@ -78,8 +82,12 @@ struct CppFunctionMcCabe
#pragma db column(CppEntity::astNodeId)
CppAstNodeId astNodeId;

#pragma db column(CppFunction::mccabe)
unsigned int mccabe;
#pragma db column(CppFunction::branchCount)
unsigned int branchCount;
#pragma db column(CppFunction::loopCount)
unsigned int loopCount;
#pragma db column(CppFunction::flowCount)
unsigned int flowCount;

#pragma db column(File::path)
std::string filePath;
Expand Down
71 changes: 51 additions & 20 deletions plugins/cpp/parser/src/clangastvisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,6 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>

// Metrics helpers

void CountMcCabe()
{
if (!_functionStack.empty())
++_functionStack.top()->mccabe;
}

void CountBumpiness(const StatementScope& scope_)
{
if (!_functionStack.empty() && scope_.IsReal())
Expand Down Expand Up @@ -373,21 +367,24 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>

bool TraverseBinaryOperator(clang::BinaryOperator* bo_)
{
if (bo_->isLogicalOp()) CountMcCabe();
if (bo_->isLogicalOp() && !_functionStack.empty())
++_functionStack.top()->branchCount;
CtxStmtScope css(this, bo_);
return Base::TraverseBinaryOperator(bo_);
}

bool TraverseConditionalOperator(clang::ConditionalOperator* co_)
{
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->branchCount;
return Base::TraverseConditionalOperator(co_);
}

bool TraverseBinaryConditionalOperator(
clang::BinaryConditionalOperator* bco_)
{
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->branchCount;
return Base::TraverseBinaryConditionalOperator(bco_);
}

Expand Down Expand Up @@ -415,35 +412,40 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
bool TraverseIfStmt(clang::IfStmt* is_)
{
_stmtStack.Top()->MakeTwoWay(is_->getThen(), is_->getElse());
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->branchCount;
return Base::TraverseIfStmt(is_);
}

bool TraverseWhileStmt(clang::WhileStmt* ws_)
{
_stmtStack.Top()->MakeOneWay(ws_->getBody());
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->loopCount;
return Base::TraverseWhileStmt(ws_);
}

bool TraverseDoStmt(clang::DoStmt* ds_)
{
_stmtStack.Top()->MakeOneWay(ds_->getBody());
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->loopCount;
return Base::TraverseDoStmt(ds_);
}

bool TraverseForStmt(clang::ForStmt* fs_)
{
_stmtStack.Top()->MakeOneWay(fs_->getBody());
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->loopCount;
return Base::TraverseForStmt(fs_);
}

bool TraverseCXXForRangeStmt(clang::CXXForRangeStmt* frs_)
{
_stmtStack.Top()->MakeOneWay(frs_->getBody());
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->loopCount;
return Base::TraverseCXXForRangeStmt(frs_);
}

Expand All @@ -456,26 +458,51 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
bool TraverseCaseStmt(clang::CaseStmt* cs_)
{
_stmtStack.Top()->MakeTransparent();
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->branchCount;
return Base::TraverseCaseStmt(cs_);
}

bool TraverseDefaultStmt(clang::DefaultStmt* ds_)
{
_stmtStack.Top()->MakeTransparent();
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->branchCount;
return Base::TraverseDefaultStmt(ds_);
}

StatementScope* GetBreakableParentScope(StatementScope* ss)
{
while (ss != nullptr && !(
llvm::isa<clang::ForStmt>(ss->Statement()) ||
llvm::isa<clang::CXXForRangeStmt>(ss->Statement()) ||
llvm::isa<clang::WhileStmt>(ss->Statement()) ||
llvm::isa<clang::DoStmt>(ss->Statement()) ||
llvm::isa<clang::SwitchStmt>(ss->Statement()) ))
ss = ss->Previous();
return ss;
}

bool TraverseBreakStmt(clang::BreakStmt* bs_)
{
StatementScope* ss = GetBreakableParentScope(_stmtStack.TopValid());
assert(ss != nullptr);
if (!_functionStack.empty() && !llvm::isa<clang::SwitchStmt>(ss->Statement()))
++_functionStack.top()->flowCount;
return Base::TraverseBreakStmt(bs_);
}

bool TraverseContinueStmt(clang::ContinueStmt* cs_)
{
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->flowCount;
return Base::TraverseContinueStmt(cs_);
}

bool TraverseGotoStmt(clang::GotoStmt* gs_)
{
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->flowCount;
return Base::TraverseGotoStmt(gs_);
}

Expand All @@ -488,7 +515,8 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
bool TraverseCXXCatchStmt(clang::CXXCatchStmt* cs_)
{
_stmtStack.Top()->MakeOneWay(cs_->getHandlerBlock());
CountMcCabe();
if (!_functionStack.empty())
++_functionStack.top()->branchCount;
return Base::TraverseCXXCatchStmt(cs_);
}

Expand Down Expand Up @@ -883,7 +911,10 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
cppFunction->qualifiedName = fn_->getQualifiedNameAsString();
cppFunction->typeHash = util::fnvHash(getUSR(qualType, _astContext));
cppFunction->qualifiedType = qualType.getAsString();
cppFunction->mccabe = fn_->isThisDeclarationADefinition() ? 1 : 0;
cppFunction->isDefinition = fn_->isThisDeclarationADefinition();
cppFunction->branchCount = 0;
cppFunction->loopCount = 0;
cppFunction->flowCount = 0;
cppFunction->bumpiness = 0;
cppFunction->statementCount = 0;

Expand Down
3 changes: 3 additions & 0 deletions plugins/cpp_metrics/model/include/model/cppastnodemetrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ struct CppAstNodeMetrics
BUMPY_ROAD = 3,
LACK_OF_COHESION = 4,
LACK_OF_COHESION_HS = 5,
BRANCH_COUNT = 6,
LOOP_COUNT = 7,
FLOW_COUNT = 8,
};

#pragma db id auto
Expand Down
55 changes: 45 additions & 10 deletions plugins/cpp_metrics/parser/src/cppmetricsparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,38 @@ void CppMetricsParser::functionMcCabe()
{
util::OdbTransaction {_ctx.db} ([&, this]
{
typedef odb::query<model::CppFunctionMcCabe>::query_columns QFun;
const auto& QFunDef = QFun::CppFunction::isDefinition;

for (const model::CppFunctionMcCabe& function
: _ctx.db->query<model::CppFunctionMcCabe>())
: _ctx.db->query<model::CppFunctionMcCabe>(QFunDef == true))
{
// Skip functions that were included from external libraries.
if (!cc::util::isRootedUnderAnyOf(_inputPaths, function.filePath))
continue;

model::CppAstNodeMetrics funcBC;
funcBC.astNodeId = function.astNodeId;
funcBC.type = model::CppAstNodeMetrics::Type::BRANCH_COUNT;
funcBC.value = function.branchCount;
_ctx.db->persist(funcBC);

model::CppAstNodeMetrics funcLC;
funcLC.astNodeId = function.astNodeId;
funcLC.type = model::CppAstNodeMetrics::Type::LOOP_COUNT;
funcLC.value = function.loopCount;
_ctx.db->persist(funcLC);

model::CppAstNodeMetrics funcFC;
funcFC.astNodeId = function.astNodeId;
funcFC.type = model::CppAstNodeMetrics::Type::FLOW_COUNT;
funcFC.value = function.flowCount;
_ctx.db->persist(funcFC);

model::CppAstNodeMetrics funcMcCabe;
funcMcCabe.astNodeId = function.astNodeId;
funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE;
funcMcCabe.value = function.mccabe;
funcMcCabe.value = 1 + function.branchCount + function.loopCount + function.flowCount;
_ctx.db->persist(funcMcCabe);
}
});
Expand All @@ -148,8 +169,11 @@ void CppMetricsParser::functionBumpyRoad()
{
util::OdbTransaction {_ctx.db} ([&, this]
{
typedef odb::query<model::CppFunctionBumpyRoad>::query_columns QFun;
const auto& QFunDef = QFun::CppFunction::isDefinition;

for (const model::CppFunctionBumpyRoad& function
: _ctx.db->query<model::CppFunctionBumpyRoad>())
: _ctx.db->query<model::CppFunctionBumpyRoad>(QFunDef == true))
{
// Skip functions that were included from external libraries.
if (!cc::util::isRootedUnderAnyOf(_inputPaths, function.filePath))
Expand Down Expand Up @@ -414,10 +438,10 @@ class MetricsExtractor

bool CppMetricsParser::parse()
{
functionParameters();
//functionParameters();
functionMcCabe();
functionBumpyRoad();
lackOfCohesion();
//lackOfCohesion();

extract();
return true;
Expand All @@ -426,20 +450,29 @@ bool CppMetricsParser::parse()
bool CppMetricsParser::extract()
{
std::string sName;
std::cout << "\nExtraction name: ";
std::getline(std::cin, sName);
const auto& varName = _ctx.options["extract-to"];
if (varName.empty())
{
std::cout << "\nExtraction name: ";
std::getline(std::cin, sName);
}
else
{
sName = varName.as<std::string>();
}
boost::trim(sName);

if (!sName.empty())
{
MetricsExtractor me(_ctx, sName.c_str());
std::cout << "Extracting to: " << me.RootDir().c_str() << std::endl;

typedef model::CppAstNodeMetrics::Type Type;
me.Extract(Type::PARAMETER_COUNT, "ParamCount");
me.Extract(Type::BRANCH_COUNT, "Branch");
me.Extract(Type::LOOP_COUNT, "Loop");
me.Extract(Type::FLOW_COUNT, "Flow");
me.Extract(Type::MCCABE, "McCabe");
me.Extract(Type::BUMPY_ROAD, "BumpyRoad");
me.Extract(Type::LACK_OF_COHESION, "LackOfCoh");
me.Extract(Type::LACK_OF_COHESION_HS, "LackOfCohHS");

return true;
}
Expand Down Expand Up @@ -471,6 +504,8 @@ extern "C"
boost::program_options::options_description getOptions()
{
boost::program_options::options_description description("C++ Metrics Plugin");
description.add_options()
("extract-to", po::value<std::string>(), "Name of directory to extract metrics data to.");

return description;
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ TEST_P(ParameterizedMcCabeTest, McCabeTest) {
_transaction([&, this]() {
typedef odb::query<model::CppFunction> QFun;
model::CppFunction func = _db->query_value<model::CppFunction>(
QFun::qualifiedName == GetParam().first && QFun::mccabe != 0);
QFun::qualifiedName == GetParam().first && QFun::isDefinition == true);

EXPECT_EQ(GetParam().second, func.mccabe);
EXPECT_EQ(GetParam().second, 1 + func.branchCount + func.loopCount + func.flowCount);
});
}

Expand Down

0 comments on commit 66f6e3a

Please sign in to comment.