//===--- NonConstParameterCheck.cpp - clang-tidy---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "NonConstParameterCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace readability { void NonConstParameterCheck::registerMatchers(MatchFinder *Finder) { // Add parameters to Parameters. Finder->addMatcher(parmVarDecl(unless(isInstantiated())).bind("Parm"), this); // C++ constructor. Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this); // Track unused parameters, there is Wunused-parameter about unused // parameters. Finder->addMatcher(declRefExpr().bind("Ref"), this); // Analyse parameter usage in function. Finder->addMatcher(stmt(anyOf(unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--"))), binaryOperator(), callExpr(), returnStmt(), cxxConstructExpr())) .bind("Mark"), this); Finder->addMatcher(varDecl(hasInitializer(anything())).bind("Mark"), this); } void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *Parm = Result.Nodes.getNodeAs("Parm")) { if (const DeclContext *D = Parm->getParentFunctionOrMethod()) { if (const auto *M = dyn_cast(D)) { if (M->isVirtual() || M->size_overridden_methods() != 0) return; } } addParm(Parm); } else if (const auto *Ctor = Result.Nodes.getNodeAs("Ctor")) { for (const auto *Parm : Ctor->parameters()) addParm(Parm); for (const auto *Init : Ctor->inits()) markCanNotBeConst(Init->getInit(), true); } else if (const auto *Ref = Result.Nodes.getNodeAs("Ref")) { setReferenced(Ref); } else if (const auto *S = Result.Nodes.getNodeAs("Mark")) { if (const auto *B = dyn_cast(S)) { if (B->isAssignmentOp()) markCanNotBeConst(B, false); } else if (const auto *CE = dyn_cast(S)) { // Typically, if a parameter is const then it is fine to make the data // const. But sometimes the data is written even though the parameter // is const. Mark all data passed by address to the function. for (const auto *Arg : CE->arguments()) { markCanNotBeConst(Arg->IgnoreParenCasts(), true); } // Data passed by nonconst reference should not be made const. if (const FunctionDecl *FD = CE->getDirectCallee()) { unsigned ArgNr = 0U; for (const auto *Par : FD->parameters()) { if (ArgNr >= CE->getNumArgs()) break; const Expr *Arg = CE->getArg(ArgNr++); // Is this a non constant reference parameter? const Type *ParType = Par->getType().getTypePtr(); if (!ParType->isReferenceType() || Par->getType().isConstQualified()) continue; markCanNotBeConst(Arg->IgnoreParenCasts(), false); } } } else if (const auto *CE = dyn_cast(S)) { for (const auto *Arg : CE->arguments()) { markCanNotBeConst(Arg->IgnoreParenCasts(), true); } } else if (const auto *R = dyn_cast(S)) { markCanNotBeConst(R->getRetValue(), true); } else if (const auto *U = dyn_cast(S)) { markCanNotBeConst(U, true); } } else if (const auto *VD = Result.Nodes.getNodeAs("Mark")) { const QualType T = VD->getType(); if ((T->isPointerType() && !T->getPointeeType().isConstQualified()) || T->isArrayType()) markCanNotBeConst(VD->getInit(), true); } } void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) { // Only add nonconst integer/float pointer parameters. const QualType T = Parm->getType(); if (!T->isPointerType() || T->getPointeeType().isConstQualified() || !(T->getPointeeType()->isIntegerType() || T->getPointeeType()->isFloatingType())) return; if (Parameters.find(Parm) != Parameters.end()) return; ParmInfo PI; PI.IsReferenced = false; PI.CanBeConst = true; Parameters[Parm] = PI; } void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) { auto It = Parameters.find(dyn_cast(Ref->getDecl())); if (It != Parameters.end()) It->second.IsReferenced = true; } void NonConstParameterCheck::onEndOfTranslationUnit() { diagnoseNonConstParameters(); } void NonConstParameterCheck::diagnoseNonConstParameters() { for (const auto &It : Parameters) { const ParmVarDecl *Par = It.first; const ParmInfo &ParamInfo = It.second; // Unused parameter => there are other warnings about this. if (!ParamInfo.IsReferenced) continue; // Parameter can't be const. if (!ParamInfo.CanBeConst) continue; SmallVector Fixes; auto *Function = dyn_cast_or_null(Par->getParentFunctionOrMethod()); if (!Function) continue; unsigned Index = Par->getFunctionScopeIndex(); for (FunctionDecl *FnDecl : Function->redecls()) Fixes.push_back(FixItHint::CreateInsertion( FnDecl->getParamDecl(Index)->getBeginLoc(), "const ")); diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const") << Par->getName() << Fixes; } } void NonConstParameterCheck::markCanNotBeConst(const Expr *E, bool CanNotBeConst) { if (!E) return; if (const auto *Cast = dyn_cast(E)) { // If expression is const then ignore usage. const QualType T = Cast->getType(); if (T->isPointerType() && T->getPointeeType().isConstQualified()) return; } E = E->IgnoreParenCasts(); if (const auto *B = dyn_cast(E)) { if (B->isAdditiveOp()) { // p + 2 markCanNotBeConst(B->getLHS(), CanNotBeConst); markCanNotBeConst(B->getRHS(), CanNotBeConst); } else if (B->isAssignmentOp()) { markCanNotBeConst(B->getLHS(), false); // If LHS is not const then RHS can't be const. const QualType T = B->getLHS()->getType(); if (T->isPointerType() && !T->getPointeeType().isConstQualified()) markCanNotBeConst(B->getRHS(), true); } } else if (const auto *C = dyn_cast(E)) { markCanNotBeConst(C->getTrueExpr(), CanNotBeConst); markCanNotBeConst(C->getFalseExpr(), CanNotBeConst); } else if (const auto *U = dyn_cast(E)) { if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec || U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) { if (const auto *SubU = dyn_cast(U->getSubExpr()->IgnoreParenCasts())) markCanNotBeConst(SubU->getSubExpr(), true); markCanNotBeConst(U->getSubExpr(), CanNotBeConst); } else if (U->getOpcode() == UO_Deref) { if (!CanNotBeConst) markCanNotBeConst(U->getSubExpr(), true); } else { markCanNotBeConst(U->getSubExpr(), CanNotBeConst); } } else if (const auto *A = dyn_cast(E)) { markCanNotBeConst(A->getBase(), true); } else if (const auto *CLE = dyn_cast(E)) { markCanNotBeConst(CLE->getInitializer(), true); } else if (const auto *Constr = dyn_cast(E)) { for (const auto *Arg : Constr->arguments()) { if (const auto *M = dyn_cast(Arg)) markCanNotBeConst(cast(M->getTemporary()), CanNotBeConst); } } else if (const auto *ILE = dyn_cast(E)) { for (unsigned I = 0U; I < ILE->getNumInits(); ++I) markCanNotBeConst(ILE->getInit(I), true); } else if (CanNotBeConst) { // Referencing parameter. if (const auto *D = dyn_cast(E)) { auto It = Parameters.find(dyn_cast(D->getDecl())); if (It != Parameters.end()) It->second.CanBeConst = false; } } } } // namespace readability } // namespace tidy } // namespace clang