[FunctionAttrs] Fix an iterator wraparound bug
authorSanjoy Das <sanjoy@playingwithpointers.com>
Sat, 7 Nov 2015 01:55:53 +0000 (01:55 +0000)
committerSanjoy Das <sanjoy@playingwithpointers.com>
Sat, 7 Nov 2015 01:55:53 +0000 (01:55 +0000)
Summary:
This change fixes an iterator wraparound bug in
`determinePointerReadAttrs`.

Ideally, ++'ing off the `end()` of an iplist should result in a failed
assert, but currently iplist seems to silently wrap to the head of the
list on `end()++`.  This is why the bad behavior is difficult to
demonstrate.

Reviewers: chandlerc, reames

Subscribers: llvm-commits

Differential Revision: http://reviews.llvm.org/D14350

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@252386 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Transforms/IPO/FunctionAttrs.cpp
test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll [new file with mode: 0644]

index a9bc8e4c6decd2283dbd8a2d257c17543903a8c7..6024137bfe348a3ce1834854be71c4dfb7c3399f 100644 (file)
@@ -394,7 +394,6 @@ determinePointerReadAttrs(Argument *A,
   while (!Worklist.empty()) {
     Use *U = Worklist.pop_back_val();
     Instruction *I = cast<Instruction>(U->getUser());
   while (!Worklist.empty()) {
     Use *U = Worklist.pop_back_val();
     Instruction *I = cast<Instruction>(U->getUser());
-    Value *V = U->get();
 
     switch (I->getOpcode()) {
     case Instruction::BitCast:
 
     switch (I->getOpcode()) {
     case Instruction::BitCast:
@@ -438,24 +437,26 @@ determinePointerReadAttrs(Argument *A,
         return Attribute::None;
       }
 
         return Attribute::None;
       }
 
-      Function::arg_iterator AI = F->arg_begin(), AE = F->arg_end();
-      CallSite::arg_iterator B = CS.arg_begin(), E = CS.arg_end();
-      for (CallSite::arg_iterator A = B; A != E; ++A, ++AI) {
-        if (A->get() == V) {
-          if (AI == AE) {
-            assert(F->isVarArg() &&
-                   "More params than args in non-varargs call.");
-            return Attribute::None;
-          }
-          Captures &= !CS.doesNotCapture(A - B);
-          if (SCCNodes.count(&*AI))
-            continue;
-          if (!CS.onlyReadsMemory() && !CS.onlyReadsMemory(A - B))
-            return Attribute::None;
-          if (!CS.doesNotAccessMemory(A - B))
-            IsRead = true;
-        }
+      // Note: the callee and the two successor blocks *follow* the argument
+      // operands.  This means there is no need to adjust UseIndex to account
+      // for these.
+
+      unsigned UseIndex = std::distance(CS.arg_begin(), U);
+
+      assert(UseIndex < CS.arg_size() && "Non-argument use?");
+      if (UseIndex >= F->arg_size()) {
+        assert(F->isVarArg() && "More params than args in non-varargs call");
+        return Attribute::None;
       }
       }
+
+      Captures &= !CS.doesNotCapture(UseIndex);
+      if (!SCCNodes.count(std::next(F->arg_begin(), UseIndex))) {
+        if (!CS.onlyReadsMemory() && !CS.onlyReadsMemory(UseIndex))
+          return Attribute::None;
+        if (!CS.doesNotAccessMemory(UseIndex))
+          IsRead = true;
+      }
+
       AddUsersToWorklistIfCapturing();
       break;
     }
       AddUsersToWorklistIfCapturing();
       break;
     }
diff --git a/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll b/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll
new file mode 100644 (file)
index 0000000..db9a895
--- /dev/null
@@ -0,0 +1,30 @@
+; RUN: opt -functionattrs -S < %s | FileCheck %s
+
+; This checks for an iterator wraparound bug in FunctionAttrs.  The previous
+; "incorrect" behavior was inferring readonly for the %x argument in @caller.
+; Inferring readonly for %x *is* actually correct, since @va_func is marked
+; readonly, but FunctionAttrs was inferring readonly for the wrong reasons (and
+; we _need_ the readonly on @va_func to trigger the problematic code path).  It
+; is possible that in the future FunctionAttrs becomes smart enough to infer
+; readonly for %x for the right reasons, and at that point this test will have
+; to be marked invalid.
+
+declare void @llvm.va_start(i8*)
+declare void @llvm.va_end(i8*)
+
+define void @va_func(i32* readonly %b, ...) readonly nounwind {
+; CHECK-LABEL: define void @va_func(i32* nocapture readonly %b, ...)
+ entry:
+  %valist = alloca i8
+  call void @llvm.va_start(i8* %valist)
+  call void @llvm.va_end(i8* %valist)
+  %x = call i32 @caller(i32* %b)
+  ret void
+}
+
+define i32 @caller(i32* %x) {
+; CHECK-LABEL: define i32 @caller(i32* nocapture %x)
+ entry:
+  call void(i32*,...) @va_func(i32* null, i32 0, i32 0, i32 0, i32* %x)
+  ret i32 42
+}