khazem updated this revision to Diff 77277.
khazem added a comment.

If a double-lock or double-release is detected, path notes are now emitted on 
the _first_ lock or release event. Also updated the tests to check for these 
notes.


https://reviews.llvm.org/D26340

Files:
  include/clang/StaticAnalyzer/Checkers/Checkers.td
  lib/StaticAnalyzer/Checkers/CMakeLists.txt
  lib/StaticAnalyzer/Checkers/SpinLockChecker.cpp
  test/Analysis/spinlock_correct.c
  test/Analysis/spinlock_double_lock.c
  test/Analysis/spinlock_double_release.c

Index: test/Analysis/spinlock_double_release.c
===================================================================
--- /dev/null
+++ test/Analysis/spinlock_double_release.c
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -analyze -analyzer-output=text -analyzer-checker=magenta.SpinLock -verify %s
+
+typedef unsigned int lock_t;
+
+typedef struct S {
+  int a;
+  lock_t l;
+} S_t;
+
+static S_t st;
+
+void spin_lock(lock_t *lock);
+void spin_unlock(lock_t *lock);
+int bar();
+
+void bar1(lock_t *y) {
+  spin_unlock(y); // expected-note {{First unlocked here}}
+}
+
+void bar2(lock_t *x) {
+  spin_unlock(x); // expected-warning{{Execution path found where spinlock is unlocked twice in a row}}
+  // expected-note@-1 0+ {{Execution path found where spinlock is unlocked twice in a row}}
+}
+
+int foo() {
+  int a = bar();
+  if (a > 0) {  // expected-note {{Assuming 'a' is > 0}} \
+                // expected-note {{Taking true branch}}
+    spin_lock(&st.l);
+    bar1(&st.l); // expected-note {{Calling 'bar1'}} \
+                    expected-note {{Returning from 'bar1'}}
+  }
+
+  lock_t *c = &st.l;
+  bar2(c);  // expected-note {{Calling 'bar2'}}
+  return 0;
+}
+
Index: test/Analysis/spinlock_double_lock.c
===================================================================
--- /dev/null
+++ test/Analysis/spinlock_double_lock.c
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=magenta.SpinLock -analyzer-output=text -verify %s
+
+typedef unsigned int lock_t;
+
+static lock_t l;
+
+void spin_lock(lock_t *lock);
+void spin_unlock(lock_t *lock);
+int bar();
+
+int foo() {
+  int a = bar();
+  if (a > 0) // expected-note{{Assuming 'a' is > 0}} \
+                expected-note{{Taking true branch}}
+
+    spin_lock(&l); // expected-note{{First locked here}}
+  if (a > 10) // expected-note{{Assuming 'a' is > 10}} \
+                 expected-note{{Taking true branch}}
+
+    spin_lock(&l); // expected-warning{{Execution path found where spinlock is locked twice in a row}}
+// expected-note@-1 0+ {{Execution path found where spinlock is locked twice in a row}}
+
+
+  return 0;
+}
+
Index: test/Analysis/spinlock_correct.c
===================================================================
--- /dev/null
+++ test/Analysis/spinlock_correct.c
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=magenta.SpinLock -verify %s
+// expected-no-diagnostics
+
+typedef unsigned int lock_t;
+
+static lock_t l;
+
+void spin_lock(lock_t *lock);
+void spin_unlock(lock_t *lock);
+int bar();
+
+int foo() {
+  int a = bar();
+  if (a > 0) {
+    spin_lock(&l);
+  }
+
+  if (a < -10) {
+    spin_lock(&l);
+  }
+
+  if (a > 0)
+    spin_unlock(&l);
+
+  if (a < -10)
+    spin_unlock(&l);
+
+  return 0;
+}
+
Index: lib/StaticAnalyzer/Checkers/SpinLockChecker.cpp
===================================================================
--- /dev/null
+++ lib/StaticAnalyzer/Checkers/SpinLockChecker.cpp
@@ -0,0 +1,262 @@
+//== SpinLockChecker.cpp - SpinLock checker ---------------------*- C++ -*--==//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This defines SpinLockChecker, a check for the Magenta kernel. It
+// checks that there are no execution paths where spinlocks are locked
+// twice in a row, or unlocked twice in a row.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/ADT/SmallString.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+
+struct LockInfo {
+
+  enum Kind { Locked, Released } K;
+
+  LockInfo(Kind kind) : K(kind) {}
+
+  bool operator==(const LockInfo &LI) const { return K == LI.K; }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
+
+  bool isLocked() const { return K == Locked; }
+
+  bool isReleased() const { return K == Released; }
+
+  static LockInfo getLocked() { return LockInfo(Locked); }
+
+  static LockInfo getReleased() { return LockInfo(Released); }
+
+};
+
+// When we detect a double-lock or double-release, we want to place a
+// diagnostic path note on the _first_ lock as well as the second one.
+// This is implemented in SpinLockBRVisitor. Since the logic is almost
+// identical for locking and releasing, we use the same VisitNode
+// function for both. The only difference is checking if the state has
+// changed from locked to unlocked (or the other way round) between the
+// last state and the current one; this is implemented by a
+// StateChangedDecision lambda function that is passed into the
+// SpinLockBRVisitor on construction.
+
+typedef std::function<bool (const LockInfo*, const LockInfo*)>
+        StateChangedDecision;
+
+class SpinLockBRVisitor final
+    : public BugReporterVisitorImpl<SpinLockBRVisitor> {
+
+  const MemRegion *SpinLockLocation;
+  const StringRef PathNoteMessage;
+  const StateChangedDecision StateChanged;
+  bool AlreadyEmitted;
+
+public:
+  SpinLockBRVisitor(const MemRegion *SpinLockLocation,
+                    const StringRef PathNoteMessage,
+                    const StateChangedDecision StateChanged) :
+    SpinLockLocation(SpinLockLocation), PathNoteMessage(PathNoteMessage),
+    StateChanged(StateChanged), AlreadyEmitted(false) {}
+
+  PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
+                                 const ExplodedNode *Pred,
+                                 BugReporterContext &BRC,
+                                 BugReport &BR) override;
+
+  void Profile(llvm::FoldingSetNodeID &ID) const override {
+      ID.Add(SpinLockLocation);
+    }
+
+};
+
+}
+
+/// We keep track of the locks in a map. SpinLockMap maps the
+/// memory region of a spinlock to its status (locked, released).
+/// The reason that we keep track of spinlocks as memory region is
+/// that the lock/unlock functions take lock arguments as pointers.
+REGISTER_MAP_WITH_PROGRAMSTATE(SpinLockMap, const MemRegion *, LockInfo)
+
+namespace {
+
+class SpinLockChecker : public Checker<check::PreCall> {
+
+  /// When facing a spin_unlock function call, check the record of the
+  /// corresponding lock in SpinLockMap and make sure that there are no
+  /// problems such as double unlocks.
+  void processSpinUnlockCall(const CallEvent &Call, CheckerContext &C,
+                             const MemRegion *SpinLockLocation) const;
+
+  /// When facing a spin_lock function call, check the record of the
+  /// corresponding lock in SpinLockMap and make sure that there are no
+  /// problems such as double locks.
+  void processSpinLockCall(const CallEvent &Call, CheckerContext &C,
+                           const MemRegion *SpinLockLocation) const;
+  void reportDoubleLock(const CallEvent &Call, CheckerContext &C,
+                        const MemRegion *Mem) const;
+  void reportDoubleUnlock(ExplodedNode *Node, CheckerContext &C,
+                          const MemRegion *Mem) const;
+  std::unique_ptr<BugType> DoubleLockBugType;
+  std::unique_ptr<BugType> DoubleUnlockBugType;
+
+  const CallDescription SpinLockFuncName;
+  const CallDescription SpinUnlockFuncName;
+  const StringRef LockErrorCategory;
+
+public:
+  SpinLockChecker();
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+
+};
+}
+
+void ento::registerSpinLockChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<SpinLockChecker>();
+}
+
+SpinLockChecker::SpinLockChecker() :
+  SpinLockFuncName("spin_lock"), SpinUnlockFuncName("spin_unlock"),
+  LockErrorCategory("Lock Error") {
+  DoubleLockBugType.reset(
+      new BugType(this, "Double SpinLock", LockErrorCategory));
+
+  DoubleUnlockBugType.reset(
+      new BugType(this, "Double SpinUnlock", LockErrorCategory));
+}
+
+/// Run the necessary processing before a spin_lock/spin_unlock function call
+void SpinLockChecker::checkPreCall(const CallEvent &Call,
+                                   CheckerContext &C) const {
+  if (!Call.getCalleeIdentifier())
+    return;
+
+  if (!Call.isCalled(SpinLockFuncName) && !Call.isCalled(SpinUnlockFuncName))
+    return;
+
+  if (Call.getNumArgs() == 0)
+    return;
+
+  // Memory region of the spinlock
+  const MemRegion *SpinLockLocation = Call.getArgSVal(0).getAsRegion();
+  // If SpinLockLocation is NULL, this is likely due to changes in the
+  // signature of spin_lock/spin_unlock functions, and we have to update
+  // the checker accordingly. For now, we terminate with an error
+  // message.
+  if (!SpinLockLocation)
+    llvm_unreachable("No spinlock pointer passed to spin_lock/spin_unlock!");
+
+  if (Call.isCalled(SpinLockFuncName))
+    processSpinLockCall(Call, C, SpinLockLocation);
+  else
+    processSpinUnlockCall(Call, C, SpinLockLocation);
+}
+
+void SpinLockChecker::processSpinLockCall(
+    const CallEvent &Call, CheckerContext &C,
+    const MemRegion *SpinLockLocation) const {
+  ProgramStateRef St = C.getState();
+  const LockInfo *LI = St->get<SpinLockMap>(SpinLockLocation);
+  if (LI && LI->isLocked()) {
+    // This spinlock has been locked before!
+    reportDoubleLock(Call, C, SpinLockLocation);
+    return;
+  }
+
+  St = St->set<SpinLockMap>(SpinLockLocation, LockInfo::getLocked());
+  C.addTransition(St);
+}
+
+void SpinLockChecker::processSpinUnlockCall(
+    const CallEvent &Call, CheckerContext &C,
+    const MemRegion *SpinLockLocation) const {
+  ProgramStateRef St = C.getState();
+  const LockInfo *LI = St->get<SpinLockMap>(SpinLockLocation);
+  if (LI && LI->isReleased()) {
+    // Unlocking a key twice! Does not cause an error, but may be an
+    // indication of an incorrect logic (maybe the spinlock was supposed to
+    // be locked again before the second unlocking attempt).
+    ExplodedNode *Node = C.generateNonFatalErrorNode(St);
+    if (!Node)
+      return;
+    reportDoubleUnlock(Node, C, SpinLockLocation);
+  }
+
+  St = St->set<SpinLockMap>(SpinLockLocation, LockInfo::getReleased());
+  C.addTransition(St);
+}
+
+void SpinLockChecker::reportDoubleUnlock(
+    ExplodedNode *Node, CheckerContext &C,
+    const MemRegion *SpinLockLocation) const {
+  auto Report = llvm::make_unique<BugReport>(
+      *DoubleUnlockBugType,
+      "Execution path found where spinlock is unlocked twice in a row", Node);
+  Report->markInteresting(SpinLockLocation);
+  Report->addVisitor(llvm::make_unique<SpinLockBRVisitor>(SpinLockLocation,
+        "First unlocked here", [](const LockInfo *Succ, const LockInfo *Pred)
+        { return Succ->isReleased() && (!Pred || !Pred->isReleased()); }));
+  C.emitReport(std::move(Report));
+}
+
+void SpinLockChecker::reportDoubleLock(
+    const CallEvent &Call, CheckerContext &C,
+    const MemRegion *SpinLockLocation) const {
+  ExplodedNode *Node = C.generateErrorNode();
+  if (!Node)
+    return;
+  auto Report = llvm::make_unique<BugReport>(
+      *DoubleLockBugType,
+      "Execution path found where spinlock is locked twice in a row", Node);
+  Report->addRange(Call.getSourceRange());
+  Report->markInteresting(SpinLockLocation);
+  Report->addVisitor(llvm::make_unique<SpinLockBRVisitor>(SpinLockLocation,
+        "First locked here", [](const LockInfo *Succ, const LockInfo *Pred)
+        { return Succ->isLocked() && (!Pred || !Pred->isLocked()); }));
+  C.emitReport(std::move(Report));
+}
+
+PathDiagnosticPiece *SpinLockBRVisitor::VisitNode(const ExplodedNode *Succ,
+                                                  const ExplodedNode *Pred,
+                                                  BugReporterContext &BRC,
+                                                  BugReport &BR) {
+  if (AlreadyEmitted)
+    return nullptr;
+
+  const LockInfo *PredInfo = Pred->getState()->
+                                    get<SpinLockMap>(SpinLockLocation);
+  const LockInfo *SuccInfo = Succ->getState()->
+                                    get<SpinLockMap>(SpinLockLocation);
+
+  if (!SuccInfo)
+    return nullptr;
+
+  if (StateChanged(SuccInfo, PredInfo)){
+    AlreadyEmitted = true;
+
+    ProgramPoint P = Succ->getLocation();
+    PathDiagnosticLocation L =
+      PathDiagnosticLocation::create(P, BRC.getSourceManager());
+
+    if (!L.isValid() || !L.asLocation().isValid())
+      return nullptr;
+
+    return new PathDiagnosticEventPiece(L, PathNoteMessage);
+  }
+
+  return nullptr;
+}
Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -70,6 +70,7 @@
   ReturnPointerRangeChecker.cpp
   ReturnUndefChecker.cpp
   SimpleStreamChecker.cpp
+  SpinLockChecker.cpp
   StackAddrEscapeChecker.cpp
   StdLibraryFunctionsChecker.cpp
   StreamChecker.cpp
Index: include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -82,6 +82,8 @@
 
 def CloneDetectionAlpha : Package<"clone">, InPackage<Alpha>, Hidden;
 
+def Magenta : Package<"magenta">;
+
 //===----------------------------------------------------------------------===//
 // Core Checkers.
 //===----------------------------------------------------------------------===//
@@ -722,3 +724,14 @@
 
 } // end "clone"
 
+//===----------------------------------------------------------------------===//
+// Magenta checkers.
+//===----------------------------------------------------------------------===//
+
+let ParentPackage = Magenta in {
+
+def SpinLockChecker : Checker<"SpinLock">,
+  HelpText<"Check for correct handling of spinlocks">,
+  DescFile<"SpinLockChecker.cpp">;
+
+} // end "magenta"
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to