Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2025-01-22 07:34:33

0001 #include "UnnecessaryMutableChecker.h"
0002 #include "clang/Basic/SourceManager.h"
0003 #include "llvm/Support/Path.h"
0004 #include "CmsSupport.h"
0005 
0006 namespace clangcms {
0007 
0008   // Find all classes/structs
0009   void UnnecessaryMutableChecker::checkASTDecl(const clang::CXXRecordDecl *RD,
0010                                                clang::ento::AnalysisManager &Mgr,
0011                                                clang::ento::BugReporter &BR) const {
0012     if (!RD->hasDefinition())
0013       return;
0014 
0015     // == Only process main file (one passed on the command line) ==
0016     const clang::SourceManager &SM = Mgr.getASTContext().getSourceManager();
0017     auto MainFileID = SM.getMainFileID();
0018     if (MainFileID.isInvalid()) {
0019       llvm::errs() << "Main file is invalid, skipping\n";
0020       return;
0021     }
0022     std::string MainFileStr = SM.getFileEntryRefForID(MainFileID)->getName().str();  // Main file name
0023     // Get the base name of the main file (without path or extension)
0024     std::string BaseNameStr =
0025         llvm::sys::path::parent_path(MainFileStr).str() + "/" + llvm::sys::path::stem(MainFileStr).str();
0026     clang::StringRef CurrentFile = SM.getFilename(RD->getLocation());
0027     if (!CurrentFile.ends_with(BaseNameStr + ".cpp") && !CurrentFile.ends_with(BaseNameStr + ".h")) {
0028       return;
0029     }
0030 
0031     // == Filter out classes with "safe" names ==
0032     std::string ClassName = RD->getNameAsString();
0033     if (support::isSafeClassName(ClassName)) {
0034       return;  // Skip checking for this class
0035     }
0036 
0037     // == Check if this is a cmssw local file ==
0038     clang::ento::PathDiagnosticLocation PathLoc = clang::ento::PathDiagnosticLocation::createBegin(RD, SM);
0039 
0040     if (!m_exception.reportMutableMember(PathLoc, BR)) {
0041       return;
0042     }
0043 
0044     // == Iterate over fields ==
0045     for (const auto *Field : RD->fields()) {
0046       if (!Field->isMutable()) {
0047         continue;
0048       }
0049 
0050       // == Skip non-private mutables
0051       // Public mutable members are forbidden and flagged by PublicMutableChecker
0052       // Protected ones can be modified by derived classes, so it is bit hard to find if
0053       // a `mutable` attribute is not necessary (i.e. not modified in any const method).
0054       if (Field->getAccess() != clang::AS_private) {
0055         return;
0056       }
0057 
0058       // == Skip atmoic mutables, these are thread-safe by design ==
0059       if (support::isStdAtomic(Field)) {
0060         return;  // Skip if it's a mutable std::atomic
0061       }
0062 
0063       // == Check if a field is marked with special attributes ==
0064       const clang::AttrVec &FAttrs = Field->getAttrs();
0065       for (const auto *A : FAttrs) {
0066         if (clang::isa<clang::CMSThreadGuardAttr>(A) || clang::isa<clang::CMSThreadSafeAttr>(A) ||
0067             clang::isa<clang::CMSSaAllowAttr>(A)) {
0068           return;
0069         }
0070       }
0071 
0072       // == Check for modifications ==
0073       if (!isMutableMemberModified(Field, RD)) {
0074         clang::SourceLocation Loc = Field->getLocation();
0075         if (Loc.isValid()) {
0076           clang::ento::PathDiagnosticLocation FieldLoc(Loc, SM);
0077           if (!BT) {
0078             BT = std::make_unique<clang::ento::BugType>(this, "Unnecessarily Mutable Member", "Coding Practices");
0079           }
0080           BR.EmitBasicReport(Field,
0081                              this,
0082                              "Useless mutable field",
0083                              "ConstThreadSafety",
0084                              "The mutable field '" + Field->getQualifiedNameAsString() +
0085                                  "' is not modified in any public const methods",
0086                              FieldLoc);
0087         }
0088       }
0089     }
0090   }
0091 
0092   bool UnnecessaryMutableChecker::isMutableMemberModified(const clang::FieldDecl *Field,
0093                                                           const clang::CXXRecordDecl *RD) const {
0094     for (const auto *Method : RD->methods()) {
0095       if (!Method->isConst())
0096         continue;
0097 
0098       if (const auto *Body = Method->getBody()) {
0099         for (const auto *Stmt : Body->children()) {
0100           if (Stmt) {
0101             if (analyzeStmt(Stmt, Field))
0102               return true;
0103           }
0104         }
0105       }
0106     }
0107 
0108     return false;
0109   }
0110 
0111   bool UnnecessaryMutableChecker::analyzeStmt(const clang::Stmt *S, const clang::FieldDecl *Field) const {
0112     if (const auto *UnaryOp = clang::dyn_cast<clang::UnaryOperator>(S)) {  // x++, x--, ++x, --x
0113       if (UnaryOp->isIncrementDecrementOp()) {
0114         if (const auto *ME = clang::dyn_cast<clang::MemberExpr>(UnaryOp->getSubExpr())) {
0115           if (const auto *FD = clang::dyn_cast<clang::FieldDecl>(ME->getMemberDecl())) {
0116             if (FD == Field) {
0117               return true;
0118             }
0119           }
0120         }
0121       }
0122     } else if (const auto *BinaryOp = clang::dyn_cast<clang::BinaryOperator>(S)) {  // x = y
0123       if (BinaryOp->isAssignmentOp() || BinaryOp->isCompoundAssignmentOp()) {
0124         if (const auto *LHS = clang::dyn_cast<clang::MemberExpr>(BinaryOp->getLHS())) {
0125           if (const auto *FD = clang::dyn_cast<clang::FieldDecl>(LHS->getMemberDecl())) {
0126             if (FD == Field) {
0127               return true;
0128             }
0129           }
0130         }
0131       }
0132     } else if (const auto *Call = clang::dyn_cast<clang::CXXMemberCallExpr>(S)) {  // x.const_method()
0133       if (const auto *Callee = Call->getMethodDecl()) {
0134         if (!Callee->isConst()) {
0135           if (const auto *ImplicitObj = Call->getImplicitObjectArgument()) {
0136             if (const auto *ME = clang::dyn_cast<clang::MemberExpr>(ImplicitObj)) {
0137               if (const auto *FD = clang::dyn_cast<clang::FieldDecl>(ME->getMemberDecl())) {
0138                 if (FD == Field) {
0139                   return true;
0140                 }
0141               }
0142             }
0143           }
0144         }
0145       }
0146     } else if (const auto *OpCall = clang::dyn_cast<clang::CXXOperatorCallExpr>(S)) {  // x.operator=()
0147       if (OpCall->isAssignmentOp()) {
0148         if (const auto *ME = llvm::dyn_cast<clang::MemberExpr>(OpCall->getArg(0))) {
0149           if (const auto *FD = clang::dyn_cast<clang::FieldDecl>(ME->getMemberDecl())) {
0150             if (FD == Field) {
0151               return true;
0152             }
0153           }
0154         }
0155       }
0156     }
0157 
0158     // Recursively analyze child statements
0159     for (const auto *Child : S->children()) {
0160       if (Child) {
0161         if (analyzeStmt(Child, Field)) {
0162           return true;
0163         }
0164       }
0165     }
0166 
0167     return false;
0168   }
0169 }  // namespace clangcms