================ @@ -0,0 +1,526 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// This checker uses a 9-step algorithm to accomplish scope analysis of a +// variable and determine if it can be declared in a smaller scope. Note that +// the clang-tidy framework is aimed mainly at supporting text-manipulation, +// diagnostics, or common AST patterns. Scope reduction analysis is +// quite specialized, and there's not much support specifically for +// those steps. Perhaps someone else knows better and can help simplify +// this code in a more concrete way other than simply suggesting it can +// be simpler. +// +// The 9-step algorithm used by this checker for scope reduction analysis is: +// 1) AST Matcher Filtering +// - Only match variables within functions (hasAncestor(functionDecl()) +// - Exclude for-loop declared variables +// (unless(hasParent(declStmt(hasParent(forStmt)))))) +// - Exclude variables with function call initializers +// (unless(hasInitializer(...))) +// - Exclude parameters from analysis +// (unless(parmVarDecl()) +// - Exclude try-catch variables (unless(hasParent(cxxCatchStmt()))) +// 2) Collect variable uses +// - Find all DeclRefExpr nodes that reference the variable +// 3) Build scope chains +// - For each use, find all compound statements that contain it (from +// innermost to outermost) +// 4) Find the innermost compound statement that contains all uses +// - This is the smallest scope where the variable could be declared +// 5) Check for modification detection +// - Skip analysis for initialized variables modified with unary operators +// - Skip analysis for initialized variables moved into switch bodies +// - This prevents moving variables where initialization would be lost or +// conditional +// 6) Check for loop body placement +// - Skip analysis if suggested scope would place variable inside loop body +// - This prevents suggesting moving variables into loop bodies (inefficient) +// 7) Switch case analysis +// - Check if variable uses span multiple case labels in the same switch +// - Skip analysis if so, as variables cannot be declared in switch body +// 8) Verify scope nesting and report +// - Find the compound statement containing the variable declaration +// - Only report if the usage scope is nested within the declaration scope +// - This ensures we only suggest moving variables to smaller scopes +// 9) Alternative analysis - check for for-loop initialization opportunity +// - Only runs if compound statement analysis didn't find a smaller scope +// - Only check local variables, not parameters +// - Determine if all uses are within the same for-loop and suggest +// for-loop initialization, but only if for-loop is in smaller scope +// +// The algorithm works by finding the smallest scope that could contain the +// variable declaration while still encompassing all its uses, but only reports +// when that scope is smaller than the current declaration scope. + +#include "ScopeReductionCheck.h" +#include "../utils/DeclRefExprUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::misc { + +static void +collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var, + llvm::SmallVector<const clang::DeclRefExpr *, 8> &Uses) { + if (!S || !Var) + return; + + const llvm::SmallPtrSet<const clang::DeclRefExpr *, 16> DREs = + clang::tidy::utils::decl_ref_expr::allDeclRefExprs(*Var, *S, + Var->getASTContext()); + + // Copy the results into the provided SmallVector + Uses.clear(); + Uses.append(DREs.begin(), DREs.end()); +} + +void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + varDecl(hasLocalStorage(), unless(hasGlobalStorage()), + hasAncestor(functionDecl()), unless(parmVarDecl()), + unless(hasParent(declStmt(hasParent(forStmt())))), + unless(hasParent(declStmt(hasParent(cxxForRangeStmt())))), + unless(hasParent(cxxCatchStmt())), + unless(hasInitializer(anyOf( + hasDescendant(callExpr()), hasDescendant(cxxMemberCallExpr()), + hasDescendant(cxxOperatorCallExpr()), callExpr(), + cxxMemberCallExpr(), cxxOperatorCallExpr())))) + .bind("var"), + this); +} + +void ScopeReductionCheck::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var"); + + assert(Var); + + // Step 1: Filter out variables declared in for-loop initializations + // These variables are already in their optimal scope and shouldn't be + // analyzed + auto &Parents = Result.Context->getParentMapContext(); + + const auto *Function = dyn_cast<FunctionDecl>(Var->getDeclContext()); + assert(Function); ---------------- zwuis wrote:
Use `cast`. https://llvm.org/docs/ProgrammersManual.html#the-isa-cast-and-dyn-cast-templates https://github.com/llvm/llvm-project/pull/175429 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
