Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cyclomatic complexity (McCabe) metric on a function level #689

Merged
merged 8 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions plugins/cpp/model/include/model/cppfunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct CppFunction : CppTypedEntity
std::vector<odb::lazy_shared_ptr<CppVariable>> parameters;
#pragma db on_delete(cascade)
std::vector<odb::lazy_shared_ptr<CppVariable>> locals;
unsigned int mccabe;

std::string toString() const
{
Expand Down Expand Up @@ -59,6 +60,13 @@ struct CppFunctionLocalCount
std::size_t count;
};

#pragma db view object(CppFunction)
struct CppFunctionMcCabeWithId
{
CppAstNodeId astNodeId;
unsigned int mccabe;
};

}
}

Expand Down
220 changes: 215 additions & 5 deletions plugins/cpp/parser/src/clangastvisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <mutex>
#include <type_traits>
#include <stack>
#include <functional>

#include <clang/Basic/SourceLocation.h>
#include <clang/Basic/SourceManager.h>
Expand Down Expand Up @@ -38,7 +39,6 @@
#include <parser/sourcemanager.h>
#include <util/hash.h>
#include <util/odbtransaction.h>
#include <util/logutil.h>

#include <cppparser/filelocutil.h>

Expand Down Expand Up @@ -147,6 +147,9 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>

bool TraverseFunctionDecl(clang::FunctionDecl* fd_)
{
if (fd_->doesThisDeclarationHaveABody())
return TraverseFunctionDeclWithBody(fd_);

_functionStack.push(std::make_shared<model::CppFunction>());

bool b = Base::TraverseFunctionDecl(fd_);
Expand All @@ -158,8 +161,30 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseFunctionDeclWithBody(clang::FunctionDecl* fd_)
{
_functionStack.push(std::make_shared<model::CppFunction>());
_mcCabeStack.push(1);

bool b = Base::TraverseFunctionDecl(fd_);

model::CppFunctionPtr top = _functionStack.top();
if (top->astNodeId)
{
top->mccabe = _mcCabeStack.top();
_functions.push_back(top);
}
_mcCabeStack.pop();
_functionStack.pop();

return b;
}

bool TraverseCXXDeductionGuideDecl(clang::CXXDeductionGuideDecl* fd_)
{
if (fd_->doesThisDeclarationHaveABody())
return TraverseCXXDeductionGuideDeclWithBody(fd_);

_functionStack.push(std::make_shared<model::CppFunction>());

bool b = Base::TraverseCXXDeductionGuideDecl(fd_);
Expand All @@ -171,8 +196,30 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseCXXDeductionGuideDeclWithBody(clang::CXXDeductionGuideDecl* fd_)
{
_functionStack.push(std::make_shared<model::CppFunction>());
_mcCabeStack.push(1);

bool b = Base::TraverseCXXDeductionGuideDecl(fd_);

model::CppFunctionPtr top = _functionStack.top();
if (top->astNodeId)
{
top->mccabe = _mcCabeStack.top();
_functions.push_back(top);
}
_mcCabeStack.pop();
_functionStack.pop();

return b;
}

bool TraverseCXXMethodDecl(clang::CXXMethodDecl* fd_)
{
if (fd_->doesThisDeclarationHaveABody())
return TraverseCXXMethodDeclWithBody(fd_);

_functionStack.push(std::make_shared<model::CppFunction>());

bool b = Base::TraverseCXXMethodDecl(fd_);
Expand All @@ -184,8 +231,30 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseCXXMethodDeclWithBody(clang::CXXMethodDecl* fd_)
{
_functionStack.push(std::make_shared<model::CppFunction>());
_mcCabeStack.push(1);

bool b = Base::TraverseCXXMethodDecl(fd_);

model::CppFunctionPtr top = _functionStack.top();
if (top->astNodeId)
{
top->mccabe = _mcCabeStack.top();
_functions.push_back(top);
}
_mcCabeStack.pop();
_functionStack.pop();

return b;
}

bool TraverseCXXConstructorDecl(clang::CXXConstructorDecl* fd_)
{
if (fd_->doesThisDeclarationHaveABody())
return TraverseCXXConstructorDeclWithBody(fd_);

_functionStack.push(std::make_shared<model::CppFunction>());

bool b = Base::TraverseCXXConstructorDecl(fd_);
Expand All @@ -197,8 +266,30 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseCXXConstructorDeclWithBody(clang::CXXConstructorDecl* fd_)
{
_functionStack.push(std::make_shared<model::CppFunction>());
_mcCabeStack.push(1);

bool b = Base::TraverseCXXConstructorDecl(fd_);

model::CppFunctionPtr top = _functionStack.top();
if (top->astNodeId)
{
top->mccabe = _mcCabeStack.top();
_functions.push_back(top);
}
_mcCabeStack.pop();
_functionStack.pop();

return b;
}

bool TraverseCXXDestructorDecl(clang::CXXDestructorDecl* fd_)
{
if (fd_->doesThisDeclarationHaveABody())
return TraverseCXXDestructorDeclWithBody(fd_);

_functionStack.push(std::make_shared<model::CppFunction>());

bool b = Base::TraverseCXXDestructorDecl(fd_);
Expand All @@ -210,8 +301,30 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseCXXDestructorDeclWithBody(clang::CXXDestructorDecl* fd_)
{
_functionStack.push(std::make_shared<model::CppFunction>());
_mcCabeStack.push(1);

bool b = Base::TraverseCXXDestructorDecl(fd_);

model::CppFunctionPtr top = _functionStack.top();
if (top->astNodeId)
{
top->mccabe = _mcCabeStack.top();
_functions.push_back(top);
}
_mcCabeStack.pop();
_functionStack.pop();

return b;
}

bool TraverseCXXConversionDecl(clang::CXXConversionDecl* fd_)
{
if (fd_->doesThisDeclarationHaveABody())
return TraverseCXXConversionDeclWithBody(fd_);

_functionStack.push(std::make_shared<model::CppFunction>());

bool b = Base::TraverseCXXConversionDecl(fd_);
Expand All @@ -223,6 +336,25 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseCXXConversionDeclWithBody(clang::CXXConversionDecl* fd_)
{
_functionStack.push(std::make_shared<model::CppFunction>());
_mcCabeStack.push(1);

bool b = Base::TraverseCXXConversionDecl(fd_);

model::CppFunctionPtr top = _functionStack.top();
if (top->astNodeId)
{
top->mccabe = _mcCabeStack.top();
_functions.push_back(top);
}
_mcCabeStack.pop();
_functionStack.pop();

return b;
}

bool TraverseRecordDecl(clang::RecordDecl* rd_)
{
_typeStack.push(std::make_shared<model::CppRecord>());
Expand Down Expand Up @@ -316,6 +448,8 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>

bool TraverseBinaryOperator(clang::BinaryOperator* bo_)
{
if (bo_->isLogicalOp() && !_mcCabeStack.empty())
++_mcCabeStack.top();
_contextStatementStack.push(bo_);
bool b = Base::TraverseBinaryOperator(bo_);
_contextStatementStack.pop();
Expand All @@ -338,14 +472,88 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
return b;
}

bool TraverseStmt(clang::Stmt* s_)
template<typename T>
bool StatementStackDecorator(T* stmt_, const std::function<bool(T*)>& func_)
{
_statements.push(s_);
bool b = Base::TraverseStmt(s_);
_statements.push(stmt_);
bool b = func_(stmt_);
_statements.pop();
return b;
}

template<typename T>
bool McCabeDecorator(T* stmt_, const std::function<bool(T*)>& func_)
{
if(!_mcCabeStack.empty())
++_mcCabeStack.top();
return StatementStackDecorator(stmt_, func_);
}

bool TraverseIfStmt(clang::IfStmt* stmt_)
{
return McCabeDecorator<clang::IfStmt>(stmt_, [this] (clang::IfStmt* s) { return Base::TraverseIfStmt(s); });
}

bool TraverseWhileStmt(clang::WhileStmt* stmt_)
{
return McCabeDecorator<clang::WhileStmt>(stmt_, [this] (clang::WhileStmt* s) { return Base::TraverseWhileStmt(s); });
}

bool TraverseDoStmt(clang::DoStmt* stmt_)
{
return McCabeDecorator<clang::DoStmt>(stmt_, [this] (clang::DoStmt* s) { return Base::TraverseDoStmt(s); });
}

bool TraverseForStmt(clang::ForStmt* stmt_)
{
return McCabeDecorator<clang::ForStmt>(stmt_, [this] (clang::ForStmt* s) { return Base::TraverseForStmt(s); });
}

bool TraverseCXXForRangeStmt(clang::CXXForRangeStmt* stmt_)
{
return McCabeDecorator<clang::CXXForRangeStmt>(stmt_, [this] (clang::CXXForRangeStmt* s) { return Base::TraverseCXXForRangeStmt(s); });
}

bool TraverseCaseStmt(clang::CaseStmt* stmt_)
{
return McCabeDecorator<clang::CaseStmt>(stmt_, [this] (clang::CaseStmt* s) { return Base::TraverseCaseStmt(s); });
}

bool TraverseDefaultStmt(clang::DefaultStmt* stmt_)
{
return McCabeDecorator<clang::DefaultStmt>(stmt_, [this] (clang::DefaultStmt* s) { return Base::TraverseDefaultStmt(s); });
}

bool TraverseContinueStmt(clang::ContinueStmt* stmt_)
{
return McCabeDecorator<clang::ContinueStmt>(stmt_, [this] (clang::ContinueStmt* s) { return Base::TraverseContinueStmt(s); });
}

bool TraverseGotoStmt(clang::GotoStmt* stmt_)
{
return McCabeDecorator<clang::GotoStmt>(stmt_, [this] (clang::GotoStmt* s) { return Base::TraverseGotoStmt(s); });
}

bool TraverseCXXCatchStmt(clang::CXXCatchStmt* stmt_)
{
return McCabeDecorator<clang::CXXCatchStmt>(stmt_, [this] (clang::CXXCatchStmt* s) { return Base::TraverseCXXCatchStmt(s); });
}

bool TraverseConditionalOperator(clang::ConditionalOperator* op_)
{
return McCabeDecorator<clang::ConditionalOperator>(op_, [this] (clang::ConditionalOperator* s) { return Base::TraverseConditionalOperator(s); });
}

bool TraverseBinaryConditionalOperator(clang::BinaryConditionalOperator* op_)
{
return McCabeDecorator<clang::BinaryConditionalOperator>(op_, [this] (clang::BinaryConditionalOperator* s) { return Base::TraverseBinaryConditionalOperator(s); });
}

bool TraverseStmt(clang::Stmt* stmt_)
{
return StatementStackDecorator<clang::Stmt>(stmt_, [this] (clang::Stmt* s) { return Base::TraverseStmt(s); });
}

bool VisitTypedefTypeLoc(clang::TypedefTypeLoc tl_)
{
const clang::TypedefType* type = tl_.getTypePtr();
Expand Down Expand Up @@ -710,6 +918,7 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
cppFunction->qualifiedName = fn_->getQualifiedNameAsString();
cppFunction->typeHash = util::fnvHash(getUSR(qualType, _astContext));
cppFunction->qualifiedType = qualType.getAsString();
cppFunction->mccabe = 1;

clang::CXXMethodDecl* md = llvm::dyn_cast<clang::CXXMethodDecl>(fn_);

Expand Down Expand Up @@ -1653,8 +1862,9 @@ class ClangASTVisitor : public clang::RecursiveASTVisitor<ClangASTVisitor>
// Check lambda.
// TODO: _enumStack also doesn't have to be a stack.
std::stack<model::CppFunctionPtr> _functionStack;
std::stack<model::CppRecordPtr> _typeStack;
std::stack<model::CppRecordPtr> _typeStack;
std::stack<model::CppEnumPtr> _enumStack;
std::stack<unsigned int> _mcCabeStack;

bool _isImplicit;
ParserContext& _ctx;
Expand Down
1 change: 1 addition & 0 deletions plugins/cpp_metrics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ endif()
add_subdirectory(model)
add_subdirectory(parser)
add_subdirectory(service)
add_subdirectory(test)

#install_webplugin(webgui)
3 changes: 2 additions & 1 deletion plugins/cpp_metrics/model/include/model/cppastnodemetrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ struct CppAstNodeMetrics
{
enum Type
{
PARAMETER_COUNT
PARAMETER_COUNT,
MCCABE
};

#pragma db id auto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class CppMetricsParser : public AbstractParser

private:
void functionParameters();
void functionMcCabe();

std::unordered_set<model::FileId> _fileIdCache;
std::unordered_map<model::CppAstNodeId, model::FileId> _astNodeIdCache;
Expand Down
Loading
Loading