brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.6 KiB · 4ae49d2 Raw
198 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "TimeSubtractionCheck.h"10#include "DurationRewriter.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Lex/Lexer.h"14#include <optional>15 16using namespace clang::ast_matchers;17 18namespace clang::tidy::abseil {19 20// Returns `true` if `Range` is inside a macro definition.21static bool insideMacroDefinition(const MatchFinder::MatchResult &Result,22                                  SourceRange Range) {23  return !clang::Lexer::makeFileCharRange(24              clang::CharSourceRange::getCharRange(Range),25              *Result.SourceManager, Result.Context->getLangOpts())26              .isValid();27}28 29static bool isConstructorAssignment(const MatchFinder::MatchResult &Result,30                                    const Expr *Node) {31  // For C++14 and earlier there are elidable constructors that must be matched32  // in hasParent. The elidable constructors do not exist in C++17 and later and33  // therefore an additional check that does not match against the elidable34  // constructors are needed for this case.35  return selectFirst<const Expr>(36             "e",37             match(expr(anyOf(38                       callExpr(hasParent(materializeTemporaryExpr(hasParent(39                                    cxxConstructExpr(hasParent(exprWithCleanups(40                                        hasParent(varDecl()))))))))41                           .bind("e"),42                       callExpr(hasParent(varDecl())).bind("e"))),43                   *Node, *Result.Context)) != nullptr;44}45 46static bool isArgument(const MatchFinder::MatchResult &Result,47                       const Expr *Node) {48  // For the same reason as in isConstructorAssignment two AST shapes need to be49  // matched here.50  return selectFirst<const Expr>(51             "e",52             match(53                 expr(anyOf(54                     expr(hasParent(materializeTemporaryExpr(55                              hasParent(cxxConstructExpr(56                                  hasParent(callExpr()),57                                  unless(hasParent(cxxOperatorCallExpr())))))))58                         .bind("e"),59                     expr(hasParent(callExpr()),60                          unless(hasParent(cxxOperatorCallExpr())))61                         .bind("e"))),62                 *Node, *Result.Context)) != nullptr;63}64 65static bool isReturn(const MatchFinder::MatchResult &Result, const Expr *Node) {66  // For the same reason as in isConstructorAssignment two AST shapes need to be67  // matched here.68  return selectFirst<const Expr>(69             "e",70             match(expr(anyOf(71                       expr(hasParent(materializeTemporaryExpr(hasParent(72                                cxxConstructExpr(hasParent(exprWithCleanups(73                                    hasParent(returnStmt()))))))))74                           .bind("e"),75                       expr(hasParent(returnStmt())).bind("e"))),76                   *Node, *Result.Context)) != nullptr;77}78 79static bool parensRequired(const MatchFinder::MatchResult &Result,80                           const Expr *Node) {81  // TODO: Figure out any more contexts in which we can omit the surrounding82  // parentheses.83  return !(isConstructorAssignment(Result, Node) || isArgument(Result, Node) ||84           isReturn(Result, Node));85}86 87void TimeSubtractionCheck::emitDiagnostic(const Expr *Node,88                                          llvm::StringRef Replacement) {89  diag(Node->getBeginLoc(), "perform subtraction in the time domain")90      << FixItHint::CreateReplacement(Node->getSourceRange(), Replacement);91}92 93void TimeSubtractionCheck::registerMatchers(MatchFinder *Finder) {94  for (const char *ScaleName :95       {"Hours", "Minutes", "Seconds", "Millis", "Micros", "Nanos"}) {96    const std::string TimeInverse = (llvm::Twine("ToUnix") + ScaleName).str();97    std::optional<DurationScale> Scale = getScaleForTimeInverse(TimeInverse);98    assert(Scale && "Unknown scale encountered");99 100    auto TimeInverseMatcher = callExpr(callee(101        functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse).str()))102            .bind("func_decl")));103 104    // Match the cases where we know that the result is a 'Duration' and the105    // first argument is a 'Time'. Just knowing the type of the first operand106    // is not sufficient, since the second operand could be either a 'Time' or107    // a 'Duration'. If we know the result is a 'Duration', we can then infer108    // that the second operand must be a 'Time'.109    auto CallMatcher =110        callExpr(111            callee(functionDecl(hasName(getDurationFactoryForScale(*Scale)))),112            hasArgument(0, binaryOperator(hasOperatorName("-"),113                                          hasLHS(TimeInverseMatcher))114                               .bind("binop")))115            .bind("outer_call");116    Finder->addMatcher(CallMatcher, this);117 118    // Match cases where we know the second operand is a 'Time'. Since119    // subtracting a 'Time' from a 'Duration' is not defined, in these cases,120    // we always know the first operand is a 'Time' if the second is a 'Time'.121    auto OperandMatcher =122        binaryOperator(hasOperatorName("-"), hasRHS(TimeInverseMatcher))123            .bind("binop");124    Finder->addMatcher(OperandMatcher, this);125  }126}127 128void TimeSubtractionCheck::check(const MatchFinder::MatchResult &Result) {129  const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop");130  const std::string InverseName =131      Result.Nodes.getNodeAs<FunctionDecl>("func_decl")->getNameAsString();132  if (insideMacroDefinition(Result, BinOp->getSourceRange()))133    return;134 135  std::optional<DurationScale> Scale = getScaleForTimeInverse(InverseName);136  if (!Scale)137    return;138 139  const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call");140  if (OuterCall) {141    if (insideMacroDefinition(Result, OuterCall->getSourceRange()))142      return;143 144    // We're working with the first case of matcher, and need to replace the145    // entire 'Duration' factory call. (Which also means being careful about146    // our order-of-operations and optionally putting in some parenthesis.147    const bool NeedParens = parensRequired(Result, OuterCall);148 149    emitDiagnostic(150        OuterCall,151        (llvm::Twine(NeedParens ? "(" : "") +152         rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + " - " +153         rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +154         (NeedParens ? ")" : ""))155            .str());156  } else {157    // We're working with the second case of matcher, and either just need to158    // change the arguments, or perhaps remove an outer function call. In the159    // latter case (addressed first), we also need to worry about parenthesis.160    const auto *MaybeCallArg = selectFirst<const CallExpr>(161        "arg", match(expr(hasAncestor(162                         callExpr(callee(functionDecl(hasName(163                                      getDurationFactoryForScale(*Scale)))))164                             .bind("arg"))),165                     *BinOp, *Result.Context));166    if (MaybeCallArg && MaybeCallArg->getArg(0)->IgnoreImpCasts() == BinOp &&167        !insideMacroDefinition(Result, MaybeCallArg->getSourceRange())) {168      // Handle the case where the matched expression is inside a call which169      // converts it from the inverse to a Duration.  In this case, we replace170      // the outer with just the subtraction expression, which gives the right171      // type and scale, taking care again about parenthesis.172      const bool NeedParens = parensRequired(Result, MaybeCallArg);173 174      emitDiagnostic(175          MaybeCallArg,176          (llvm::Twine(NeedParens ? "(" : "") +177           rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +178           " - " +179           rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +180           (NeedParens ? ")" : ""))181              .str());182    } else {183      // In the last case, just convert the arguments and wrap the result in184      // the correct inverse function.185      emitDiagnostic(186          BinOp,187          (llvm::Twine(188               getDurationInverseForScale(*Scale).second.str().substr(2)) +189           "(" + rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +190           " - " +191           rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + ")")192              .str());193    }194  }195}196 197} // namespace clang::tidy::abseil198