Revision a3877247dd56b0a7f3105faeb4f767d083e22e35 authored by Shuhei Kadowaki on 30 October 2021, 08:41:05 UTC, committed by GitHub on 30 October 2021, 08:41:05 UTC
This is more consistent with the arrangement of `InvokeCallInfo`, and
will make succeeding processing a bit cleaner.

This change also avoids unnecessary specializations in callinfo constructions.
1 parent 60e3e55
Raw File
ImplicitAtomics.cpp
// This file is a part of Julia. License is MIT: https://julialang.org/license

#include "clang/AST/ExprObjC.h"
#include "clang/AST/ExprOpenMP.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"


using namespace clang;
using namespace ento;

namespace {
class ImplicitAtomicsChecker
    : public Checker< check::PreStmt<CastExpr>,
                      check::PreStmt<BinaryOperator>,
                      check::PreStmt<UnaryOperator>,
                      check::PreCall> {
                      //check::Bind
                      //check::Location
  BugType ImplicitAtomicsBugType{this, "Implicit Atomic seq_cst synchronization", "Atomics"};

  void reportBug(const Stmt *S, CheckerContext &C) const;
  void reportBug(const Stmt *S, CheckerContext &C, StringRef desc) const;
  void reportBug(const CallEvent &S, CheckerContext &C, StringRef desc="") const;

public:
  //void checkLocation(SVal location, bool isLoad, const Stmt* S,
  //                   CheckerContext &C) const;
  //void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
  void checkPreStmt(const CastExpr *CE, CheckerContext &C) const;
  void checkPreStmt(const UnaryOperator *UOp, CheckerContext &C) const;
  void checkPreStmt(const BinaryOperator *BOp, CheckerContext &C) const;
  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
};
} // end anonymous namespace

// Checks if RD has name in Names and is in std namespace
static bool hasStdClassWithName(const CXXRecordDecl *RD,
                                ArrayRef<llvm::StringLiteral> Names) {
  // or could check ASTContext::getQualifiedTemplateName()->isDerivedFrom() ?
  if (!RD || !RD->getDeclContext()->isStdNamespace())
    return false;
  if (RD->getDeclName().isIdentifier()) {
    StringRef Name = RD->getName();
    return llvm::any_of(Names, [&Name](StringRef GivenName) -> bool {
      return Name == GivenName;
    });
  }
  return false;
}

constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"atomic", "atomic_ref"};

static bool isStdAtomic(const CXXRecordDecl *RD) {
  return hasStdClassWithName(RD, STD_PTR_NAMES);
}

static bool isStdAtomicCall(const Expr *E) {
  return E && isStdAtomic(E->IgnoreImplicit()->getType()->getAsCXXRecordDecl());
}

static bool isStdAtomic(const Expr *E) {
  return E->getType()->isAtomicType();
}

void ImplicitAtomicsChecker::reportBug(const CallEvent &S, CheckerContext &C, StringRef desc) const {
    reportBug(S.getOriginExpr(), C, desc);
}

// try to find the "best" node to attach this to, so we generate fewer duplicate reports
void ImplicitAtomicsChecker::reportBug(const Stmt *S, CheckerContext &C) const {
  while (1) {
    const auto *expr = dyn_cast<Expr>(S);
    if (!expr)
      break;
    expr = expr->IgnoreParenCasts();
    if (const auto *UO = dyn_cast<UnaryOperator>(expr))
      S = UO->getSubExpr();
    else if (const auto *BO = dyn_cast<BinaryOperator>(expr))
      S = isStdAtomic(BO->getLHS()) ? BO->getLHS() :
             isStdAtomic(BO->getRHS()) ? BO->getRHS() :
             BO->getLHS();
    else
      break;
  }
  reportBug(S, C, "");
}

void ImplicitAtomicsChecker::reportBug(const Stmt *S, CheckerContext &C, StringRef desc) const {
  SmallString<100> buf;
  llvm::raw_svector_ostream os(buf);
  os << ImplicitAtomicsBugType.getDescription() << desc;
  PathDiagnosticLocation N = PathDiagnosticLocation::createBegin(
    S, C.getSourceManager(), C.getLocationContext());
  auto report = std::make_unique<BasicBugReport>(ImplicitAtomicsBugType, buf.str(), N);
  C.emitReport(std::move(report));
}

void ImplicitAtomicsChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const {
  //if (isStdAtomic(CE) != isStdAtomic(CE->getSubExpr())) { // AtomicToNonAtomic or NonAtomicToAtomic CastExpr
  if (CE->getCastKind() == CK_AtomicToNonAtomic) {
    reportBug(CE, C);
  }
}

void ImplicitAtomicsChecker::checkPreStmt(const UnaryOperator *UOp,
                                          CheckerContext &C) const {
  if (UOp->getOpcode() == UO_AddrOf)
    return;
  const Expr *Sub = UOp->getSubExpr();
  if (isStdAtomic(UOp) || isStdAtomic(Sub))
    reportBug(UOp, C);
}

void ImplicitAtomicsChecker::checkPreStmt(const BinaryOperator *BOp,
                                          CheckerContext &C) const {
  const Expr *Lhs = BOp->getLHS();
  const Expr *Rhs = BOp->getRHS();
  if (isStdAtomic(Lhs) || isStdAtomic(Rhs) || isStdAtomic(BOp))
    reportBug(BOp, C);
}

void ImplicitAtomicsChecker::checkPreCall(const CallEvent &Call,
                                          CheckerContext &C) const {
  const auto *MC = dyn_cast<CXXInstanceCall>(&Call);
  if (!MC || !isStdAtomicCall(MC->getCXXThisExpr()))
    return;
  if (const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call)) {
    OverloadedOperatorKind OOK = OC->getOverloadedOperator();
    if (CXXOperatorCallExpr::isAssignmentOp(OOK) || OOK == OO_PlusPlus || OOK == OO_MinusMinus) {
      reportBug(Call, C, " (std::atomic)");
    }
  }
  else if (const auto *Convert = dyn_cast<CXXConversionDecl>(MC->getDecl())) {
    reportBug(Call, C, " (std::atomic)");
  }
}


//// These seem probably unnecessary:
//
//static const Expr *getDereferenceExpr(const Stmt *S, bool IsBind=false) {
//  const Expr *E = nullptr;
//
//  // Walk through lvalue casts to get the original expression
//  // that syntactically caused the load.
//  if (const Expr *expr = dyn_cast<Expr>(S))
//    E = expr->IgnoreParenLValueCasts();
//
//  if (IsBind) {
//    const VarDecl *VD;
//    const Expr *Init;
//    std::tie(VD, Init) = parseAssignment(S);
//    if (VD && Init)
//      E = Init;
//  }
//  return E;
//}
//
//// load or bare symbol
//void ImplicitAtomicsChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
//                                           CheckerContext &C) const {
//  const Expr *expr = getDereferenceExpr(S);
//  assert(expr);
//  if (isStdAtomic(expr))
//    reportBug(S, C);
//}
//
//// auto &r = *l, or store
//void ImplicitAtomicsChecker::checkBind(SVal L, SVal V, const Stmt *S,
//                                       CheckerContext &C) const {
//  const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true);
//  assert(expr);
//  if (isStdAtomic(expr))
//    reportBug(S, C, " (bind)");
//}

namespace clang {
namespace ento {
void registerImplicitAtomicsChecker(CheckerManager &mgr) {
  mgr.registerChecker<ImplicitAtomicsChecker>();
}
bool shouldRegisterImplicitAtomicsChecker(const CheckerManager &mgr) {
  return true;
}
} // namespace ento
} // namespace clang

#ifdef CLANG_PLUGIN
extern "C" const char clang_analyzerAPIVersionString[] =
    CLANG_ANALYZER_API_VERSION_STRING;
extern "C" void clang_registerCheckers(CheckerRegistry &registry) {
  registry.addChecker<ImplicitAtomicsChecker>(
      "julia.ImplicitAtomics", "Flags implicit atomic operations", ""
  );
}
#endif
back to top