blob: c4f875866f781942071ea9bc07c5090ec617c856 [file] [log] [blame] [edit]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "native_client/src/untrusted/valgrind/dynamic_annotations.h"
/*
This small test is mainly for checking sanity of ThreadSanitizer on NaCl
and is not a substitute for the ThreadSanitizer's full test suite.
TODO(kcc): once ThreadSanitizer starts working reliably on NaCl,
create a golden file to compare with the ThreadSanitizer's output.
Tests named positive_* have races that should be detectable by ThreadSanitizer.
negative_* tests don't have races and ThreadSanitizer should be silent.
*/
#define NOINLINE __attribute__((noinline))
#define SHOW_ME printf("========== %s: =========\n", __FUNCTION__)
enum {
kMaxNumThreads = 5
};
typedef void *(*ThreadCallback)(void*);
void create_and_join_threads(ThreadCallback *callbacks, int n) {
int i;
pthread_t t[kMaxNumThreads];
int ids[kMaxNumThreads];
assert(n <= kMaxNumThreads);
for (i = 0; i < n; i++) {
ids[i] = i;
pthread_create(&t[i], NULL, callbacks[i], &ids[i]);
}
for (i = 0; i < n; i++) {
pthread_join(t[i], NULL);
}
}
/* Don't let various optimizations (e.g. tail call elimination) to happen. */
NOINLINE void break_optimization(void) {
#if defined(__pnacl__)
volatile int foo;
foo = 0;
#else
__asm__ volatile ("");
#endif
}
/* -------------------------------------------------------------------------- */
/* positive_race_on_global_test
Simle race on a global variable between three threads.
We have few stack frames here to test ThreadSanitizer's output. */
int simple_race_obj;
NOINLINE void simple_race_write_frame_2(void) {
break_optimization();
simple_race_obj++; /* Race here. */
}
NOINLINE void simple_race_write_frame_1(void) {
break_optimization();
simple_race_write_frame_2();
break_optimization();
}
void *simple_race_write(void *arg) {
break_optimization();
simple_race_write_frame_1();
break_optimization();
return NULL;
}
void positive_race_on_global_test(void) {
ThreadCallback t[] = {simple_race_write, simple_race_write,
simple_race_write};
SHOW_ME;
ANNOTATE_EXPECT_RACE(&simple_race_obj,
"positive_race_on_global_test. NACL_UNTRUSTED.");
create_and_join_threads(t, 3);
}
/* -------------------------------------------------------------------------- */
/* positive_race_on_heap_test
Simple race on a heap object. */
int *race_on_heap_obj;
NOINLINE void race_on_heap_frame_2(void) {
break_optimization();
*race_on_heap_obj = 1; /* Race here. */
}
NOINLINE void race_on_heap_frame_1(void) {
break_optimization();
race_on_heap_frame_2();
break_optimization();
}
void *race_on_heap_write(void *unused) {
break_optimization();
race_on_heap_frame_1();
break_optimization();
return NULL;
}
void positive_race_on_heap_test(void) {
ThreadCallback t[2] = {race_on_heap_write, race_on_heap_write};
SHOW_ME;
race_on_heap_obj = (int*)malloc(sizeof(int));
ANNOTATE_EXPECT_RACE(race_on_heap_obj,
"positive_race_on_heap_test. NACL_UNTRUSTED.");
create_and_join_threads(t, 2);
free(race_on_heap_obj);
}
/* -------------------------------------------------------------------------- */
/* positive_wrong_lock_test
Race on a global object between two threads, each holding its own lock. */
pthread_mutex_t wrong_lock_test_mu_1;
pthread_mutex_t wrong_lock_test_mu_2;
int wrong_lock_test_obj;
void *wrong_lock_test_access1(void *unused) {
pthread_mutex_lock(&wrong_lock_test_mu_1);
wrong_lock_test_obj++; /* Race here. */
pthread_mutex_unlock(&wrong_lock_test_mu_1);
return NULL;
}
void *wrong_lock_test_access2(void *unused) {
pthread_mutex_lock(&wrong_lock_test_mu_2);
wrong_lock_test_obj++; /* Race here. */
pthread_mutex_unlock(&wrong_lock_test_mu_2);
return NULL;
}
void positive_wrong_lock_test(void) {
ThreadCallback t[2] = {wrong_lock_test_access1, wrong_lock_test_access2};
pthread_mutex_init(&wrong_lock_test_mu_1, NULL);
pthread_mutex_init(&wrong_lock_test_mu_2, NULL);
SHOW_ME;
ANNOTATE_EXPECT_RACE(&wrong_lock_test_obj,
"positive_wrong_lock_test. NACL_UNTRUSTED.");
create_and_join_threads(t, 2);
}
/* -------------------------------------------------------------------------- */
/* negative_locked_access_test
Correctly synchronized code: an object is accessed
by different threads under the same lock. */
pthread_mutex_t locked_access_test_mu;
int locked_access_test_obj;
void locked_access_test_frame_2(void) {
pthread_mutex_lock(&locked_access_test_mu);
locked_access_test_obj++; /* No race here. */
pthread_mutex_unlock(&locked_access_test_mu);
}
void locked_access_test_frame_1(void) {
locked_access_test_frame_2();
}
void *locked_access_test_thread(void *unused) {
locked_access_test_frame_1();
return NULL;
}
void negative_locked_access_test(void) {
ThreadCallback t[2] = {locked_access_test_thread, locked_access_test_thread};
pthread_mutex_init(&locked_access_test_mu, NULL);
SHOW_ME;
create_and_join_threads(t, 2);
}
/* -------------------------------------------------------------------------- */
/* negative_posix_sem_test */
int posix_sem_test_glob = 0;
sem_t posix_sem_test_sem[2];
void* posix_sem_test_poster(void* unused) {
posix_sem_test_glob = 1;
sem_post(&posix_sem_test_sem[0]);
sem_post(&posix_sem_test_sem[1]);
return NULL;
}
void* posix_sem_test_waiter(void* unused) {
sem_wait(&posix_sem_test_sem[0]);
assert(posix_sem_test_glob == 1);
return NULL;
}
void negative_posix_sem_test(void) {
ThreadCallback t[2] = {posix_sem_test_poster, posix_sem_test_waiter};
SHOW_ME;
sem_init(&posix_sem_test_sem[0], 0, 0);
sem_init(&posix_sem_test_sem[1], 0, 0);
create_and_join_threads(t, 2);
sem_destroy(&posix_sem_test_sem[0]);
sem_destroy(&posix_sem_test_sem[1]);
}
/* -------------------------------------------------------------------------- */
/* pthread_create_and_join_test */
/* Test that thread creation and joining adds happens-before arcs. */
int thread_create_and_join_test_glob;
void* thread_create_and_join_test_thread1(void* unused) {
thread_create_and_join_test_glob = 2;
return NULL;
}
void thread_create_and_join_test(void) {
ThreadCallback t[1] = {thread_create_and_join_test_thread1};
SHOW_ME;
ANNOTATE_TRACE_MEMORY(&thread_create_and_join_test_glob);
thread_create_and_join_test_glob = 1;
create_and_join_threads(t, 1);
assert(thread_create_and_join_test_glob == 2);
}
/* -------------------------------------------------------------------------- */
int main(void) {
if (1) positive_race_on_global_test();
if (1) positive_race_on_heap_test();
if (1) positive_wrong_lock_test();
if (1) negative_locked_access_test();
if (1) negative_posix_sem_test();
if (1) thread_create_and_join_test();
return 0;
}