[libcxx] r208319 - Add Address Sanitizer support to std::vector

Stephan Tolksdorf st at quanttec.com
Mon May 12 08:04:48 PDT 2014


On 14-05-12 16:27, Kostya Serebryany wrote:
 >         And an unrelated issue: the documentation for
 >         __sanitizer_annotate_contiguous_container states that the
 >         complete buffer should be unpoisened before it is deallocated.
 >         This doesn't seem to be happening in the destructor or in the
 >         deallocate function.
 >
 >     Here we rely on the fact that vector is using the default allocator,
 >     which immediately calls delete, which itself unpoisons the memory
 >     chunk.

Do I understand you correctly, that the global ::operator delete doesn't 
actually require the complete buffer to be unpoisened? I assume this is 
also true for ::free then?

> Reproduced. (we have very little code with exceptions so this has
> avoided our testing efforts :)
> Any suggestion for the fix?

Using a scope guard object for annotating operations that increase the 
size would be one possibility, as e.g. in the following pseudo code:

struct AnnotatedAppendScope {
   Vector& v;
   const size_t newLength;
#if USE_ASAN
   bool committed;
#endif

   /// \pre vector.length() < newLength <= vector.capacity()
   AnnotatedAppendScope(Vector& vector, size_t newLength)
   : v(vector), newLength(newLength)
   #if USE_ASAN
     , committed(false)
   #endif
   {
   #if USE_ASAN
     __sanitizer_annotate_contiguous_container(
       v.begin(), v.begin() + v.capacity(),
       v.begin() + v.length(), v.begin() + newLength;
     );
   #endif
   }

#if USE_ASAN
   ~AnnotatedAppendScope() {
     if (!committed) {
       __sanitizer_annotate_contiguous_container(
         v.begin(), v.begin() + v.capacity(),
         v.begin() + newLength, v.begin() + v.length();
       );
     }
   }
#endif

   void commitLength() {
     v.setLength(newLength);
   #if USE_ASAN
     committed = true;
   #endif
   }
};

void Vector::push_back(const T& value) {
   reserve(length() + 1);
   AnnotatedAppendScope scope(*this, length() + 1);
   new (end()) T(value);
   scope.commitLength();
}

There's another comment below.

>
> #include <vector>
> #include <assert.h>
> #include <iostream>
> #include <sanitizer/asan_interface.h>
> using namespace std;
>
> class X {
>   public:
>    X(const X &x) {
>      if (x.a == 42) throw 0;
>      a = x.a;
>    }
>    X(char arg): a(arg){}
>    char get() const { return a; }
>   private:
>    char a;
> };
>
> int main() {
>    vector<X> v;
>    v.reserve(2);
>    v.push_back(X(2));
>    assert(v.size() == 1);
>    try {
>      v.push_back(X(42));
>      assert(0);
>    } catch (int e) {
>      assert(v.size() == 1);
>    }
>    assert(v.size() == 1);
>    assert(v[1].get() != 777);

What does the above line test?
It would always fail if the index is assert checked, which currently is 
the case when _LIBCPP_DEBUG is defined.

>    char *p = (char*)v.data();
>    assert(!__asan_address_is_poisoned(p));
>    assert(__asan_address_is_poisoned(p+1));
> }
>

- Stephan




More information about the cfe-commits mailing list