Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-12-19 04:04:57

0001 //== MutableMemberModificationChecker.cpp - Checks for accessing mutable members via const pointer --------------*- C++ -*--==//
0002 //
0003 // By Thomas Hauth [ Thomas.Hauth@cern.ch ], updated by Ivan Razumov <ivan.razumov@cern.ch>
0004 //
0005 //===----------------------------------------------------------------------===//
0006 
0007 #include "MutableMemberModificationChecker.h"
0008 #include <clang/AST/Decl.h>
0009 #include <clang/AST/Type.h>
0010 #include <clang/AST/DeclCXX.h>
0011 #include <clang/AST/ASTContext.h>
0012 #include <clang/AST/Stmt.h>
0013 #include <clang/AST/Expr.h>
0014 #include <clang/AST/ExprCXX.h>
0015 #include <clang/AST/ParentMap.h>
0016 #include <clang/Analysis/AnalysisDeclContext.h>
0017 #include <clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h>
0018 
0019 namespace clangcms {
0020   void MutableMemberModificationChecker::checkPreStmt(const clang::MemberExpr *ME,
0021                                                       clang::ento::CheckerContext &C) const {
0022     // Common checks
0023 
0024     // == Filter out classes with "safe" names ==
0025     const auto *RD = llvm::dyn_cast<clang::CXXRecordDecl>(ME->getMemberDecl()->getDeclContext());
0026     if (RD) {
0027       std::string ClassName = RD->getNameAsString();
0028       if (support::isSafeClassName(ClassName)) {
0029         return;  // Skip checking for this class
0030       }
0031     }
0032 
0033     // == Check attributes ==
0034     const clang::FunctionDecl *FuncD = C.getLocationContext()->getStackFrame()->getDecl()->getAsFunction();
0035     const clang::AttrVec &Attrs = FuncD->getAttrs();
0036     for (const auto *A : Attrs) {
0037       if (clang::isa<clang::CMSThreadGuardAttr>(A) || clang::isa<clang::CMSThreadSafeAttr>(A) ||
0038           clang::isa<clang::CMSSaAllowAttr>(A)) {
0039         return;
0040       }
0041     }
0042 
0043     // == Check if this is a cmssw local file ==
0044     // Create a PathDiagnosticLocation for reporting
0045     clang::ento::PathDiagnosticLocation PathLoc =
0046         clang::ento::PathDiagnosticLocation::createBegin(ME, C.getSourceManager(), C.getLocationContext());
0047 
0048     // Get the BugReporter instance from the CheckerContext
0049     clang::ento::BugReporter &BR = C.getBugReporter();
0050 
0051     if (!m_exception.reportMutableMember(PathLoc, BR)) {
0052       return;
0053     }
0054 
0055     // == Only proceed if the member is mutable ==
0056     const auto *FD = llvm::dyn_cast<clang::FieldDecl>(ME->getMemberDecl());
0057     if (!FD || !FD->isMutable()) {
0058       return;  // Skip if it's not a mutable field
0059     }
0060 
0061     // == Skip non-private mutables, we deal with them elsewhere
0062     if (FD->getAccess() != clang::AS_private) {
0063       return;
0064     }
0065 
0066     // == Skip atomic mutables, these are thread-safe by design ==
0067     if (support::isStdAtomic(FD)) {
0068       return;  // Skip if it's a mutable std::atomic
0069     }
0070 
0071     // == Check if a field is marked with special attributes ==
0072     const clang::AttrVec &FAttrs = FD->getAttrs();
0073     for (const auto *A : FAttrs) {
0074       if (clang::isa<clang::CMSThreadGuardAttr>(A) || clang::isa<clang::CMSThreadSafeAttr>(A) ||
0075           clang::isa<clang::CMSSaAllowAttr>(A)) {
0076         return;
0077       }
0078     }
0079 
0080     // == Check if we are inside a const-qualified member function ==
0081     const auto *MethodDecl = llvm::dyn_cast<clang::CXXMethodDecl>(FuncD);
0082     if (!MethodDecl || !MethodDecl->isConst()) {
0083       return;
0084     }
0085 
0086     // == Check if we are modifying the mutable ==
0087     bool ret;
0088     if (checkAssignToMutable(ME, C)) {
0089       // == Emit report ==
0090       std::string MutableMemberName = ME->getMemberDecl()->getQualifiedNameAsString();
0091       if (!BT) {
0092         BT = std::make_unique<clang::ento::BugType>(
0093             this, "Mutable member modification in const member function", "ConstThreadSafety");
0094       }
0095       std::string Description =
0096           "Modifying mutable member '" + MutableMemberName + "' in const member function is potentially thread-unsafe ";
0097       auto Report = std::make_unique<clang::ento::PathSensitiveBugReport>(*BT, Description, C.generateErrorNode());
0098       Report->addRange(ME->getSourceRange());
0099       C.emitReport(std::move(Report));
0100       return;
0101     }
0102 
0103     if (checkCallNonConstOfMutable(ME, C)) {
0104       if (RD) {
0105         std::string ClassName = RD->getNameAsString();
0106         std::string MemberName = ME->getMemberDecl()->getNameAsString();
0107         std::string FunctionName = MethodDecl->getNameAsString();
0108         std::string tname = "mutablemember-checker.txt.unsorted";
0109         std::string ostring = "flagged class '" + ClassName + "' modifying mutable member '" + MemberName +
0110                               "' in function '" + FunctionName + "'";
0111         support::writeLog(ostring, tname);
0112       }
0113     }
0114   }  // checkPreStmt
0115 
0116   // Check direct modifications of mutable (assign, compound stmt, increment/decrement)
0117   bool MutableMemberModificationChecker::checkAssignToMutable(const clang::MemberExpr *ME,
0118                                                               clang::ento::CheckerContext &C) const {
0119     // == Check if this is a modifying statement ==
0120 
0121     // Retrieve the parent statement of the MemberExpr
0122     const clang::LocationContext *LC = C.getLocationContext();
0123     const clang::ParentMap &PM = LC->getParentMap();
0124     const clang::Stmt *ParentStmt = PM.getParent(ME);
0125 
0126     if (!ParentStmt) {
0127       return false;
0128     }
0129 
0130     // Check if it is an assignment operator (binary operator)
0131     const auto *BO = llvm::dyn_cast<clang::BinaryOperator>(ParentStmt);
0132     if (BO) {
0133       const auto *LHSAsME = llvm::dyn_cast<clang::MemberExpr>(BO->getLHS());
0134       if (LHSAsME) {
0135         if (BO->isAssignmentOp() && LHSAsME == ME) {
0136           // The MemberExpr is on the left-hand side of an assignment
0137           return true;
0138         }
0139       }
0140     }
0141 
0142     // Check if it is an overloaded assignment operator
0143     const auto *CO = llvm::dyn_cast<clang::CXXOperatorCallExpr>(ParentStmt);
0144     if (CO) {
0145       const auto *LHSAsME = llvm::dyn_cast<clang::MemberExpr>(CO->getArg(0));
0146       if (LHSAsME) {
0147         if (CO->isAssignmentOp() && LHSAsME == ME) {
0148           // The MemberExpr is on the left-hand side of an assignment
0149           return true;
0150         }
0151       }
0152     }
0153 
0154     // Check for increment/decrement
0155     if (const auto *UO = llvm::dyn_cast<clang::UnaryOperator>(ParentStmt)) {
0156       if (UO->isIncrementDecrementOp() && UO->getSubExpr() == ME) {
0157         return true;
0158       }
0159     }
0160 
0161     return false;
0162   }  // checkAssignToMutable
0163 
0164   // Check for indirect modifications of mutable (calling non-const method)
0165   bool MutableMemberModificationChecker::checkCallNonConstOfMutable(const clang::MemberExpr *ME,
0166                                                                     clang::ento::CheckerContext &C) const {
0167     // Traverse upwards to check if the MemberExpr is part of a CXXMemberCallExpr
0168     const clang::Expr *E = ME;
0169     while (E) {
0170       if (const clang::CXXMemberCallExpr *Call = llvm::dyn_cast<clang::CXXMemberCallExpr>(E->IgnoreParenCasts())) {
0171         const clang::CXXMethodDecl *CalledMethod = Call->getMethodDecl();
0172         if (CalledMethod && !CalledMethod->isConst()) {
0173           // Get the name of the mutable member
0174           std::string MutableMemberName = ME->getMemberDecl()->getQualifiedNameAsString();
0175 
0176           // Get the name of the called method
0177           std::string CalledMethodName = CalledMethod->getQualifiedNameAsString();
0178           // Report an issue
0179           if (!BT) {
0180             BT = std::make_unique<clang::ento::BugType>(
0181                 this, "Mutable member modification in const member function", "ConstThreadSafety");
0182           }
0183           std::string Description = "Calling non-const method '" + CalledMethodName + "' of mutable member '" +
0184                                     MutableMemberName + "' in a const member function is potentially thread-unsafe.";
0185           auto Report = std::make_unique<clang::ento::PathSensitiveBugReport>(*BT, Description, C.generateErrorNode());
0186           Report->addRange(ME->getSourceRange());
0187           C.emitReport(std::move(Report));
0188           return true;
0189         }
0190       }
0191       // Move up to the parent expression
0192       const clang::Stmt *ParentStmt = C.getLocationContext()->getParentMap().getParent(E);
0193       E = llvm::dyn_cast_or_null<clang::Expr>(ParentStmt);
0194     }
0195     return false;
0196   }  // checkCallNonConstOfMutable
0197 
0198 }  // namespace clangcms