Marko Mäkelä
Marko Mäkelä

Reputation: 718

Is there any algorithm to check memory safety and data races at the same time?

I am aware of the question regarding combining AddressSanitizer and ThreadSanitizer. I am asking about this from the theoretical computer science point of view, prompted by an earlier discussion.

Consider the following buggy C++ program:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <thread>
#include <mutex>

static std::mutex m;
static char *buf;

static void a()
{
  for (;;) {
    char *b;
    {
      std::lock_guard<std::mutex> g{m};
      b = buf;
    }
    if (b)
      puts(b);
  }
}

static void b()
{
  for (;;) {
    {
      std::lock_guard<std::mutex> g{m};
      buf = strdup("foo");
    }

    {
      std::lock_guard<std::mutex> g{m};
      buf = static_cast<char*>(realloc(buf, 10000));
      strcpy(buf, "barbarbar");
    }

    {
      std::lock_guard<std::mutex> g{m};
      free(buf);
      buf = nullptr;
    }
  }
}

int main()
{
  auto thread_a = std::thread(a);
  auto thread_b = std::thread(b);
  thread_a.join();
  // unreachable
  thread_b.join();
  return 0;
}

When compiled with GCC 14.2.0 or Clang 20 using -fsanitize=address, the program will terminate instantly with a heap-use-after-free error, because a() is using a stale copy of a pointer.

When compiled without special options and run with valgrind ./a.out, on my system the program would run for nearly 4 minutes before reporting the first Invalid read of size 1. This I assume is because Valgrind is emulating multiple threads by preemptive scheduling in a single thread, and therefore you would need some luck to get a context switch between a() and b() at the right moment for the error to be reproduced.

Similarly, when the program is compiled with GCC or Clang using -fsanitize=thread, it seems to keep running without any problem.

My question: Has there been any recent academic research that would improve upon the ThreadSanitizer algorithm in this respect?

Upvotes: -5

Views: 133

Answers (1)

Paul Floyd
Paul Floyd

Reputation: 6936

Valgrind memcheck, DRD and Helgrind all detect errors in this code.

For DRD I recommend using --tool=drd --fair-sched=yes --check-stack-var=yes.

For Helgrind I recommend using --tool=helgrind --fair-sched=yes.

And for memcheck just --fair-sched=yes.

Without fair scheduling it takes a bit longer to trigger the errors in my tests, sometimes 10 seconds whilst fair scheduling is generally within a few seconds.

Fair scheduling is only available on Linux. Porting it to FreeBSD is on my todo list.

Only one build required, and I only used GCC.

Upvotes: 2

Related Questions