A system for detecting data races in a multi-threaded program (106, 216) comprising one or more critical sections and a plurality of shared memory objects, the system comprising: a computer processor (202, 1102) supporting per-thread memory protection; and computer memory (1104, 1106, 1122) storing a plurality of software components each comprising instructions for execution by the computer processor (202, 1102), the software components comprising: a memory allocator configured to assign the plurality of shared memory objects to respective virtual memory pages (700, 702, 704) each storing only one of the plurality of shared memory objects, each shared memory object being protectable by a memory protection key assigned to the respective virtual memory page (700, 702, 704); synchronization wrapper functions associated with the one or more critical sections to provide to a thread, upon entry of the thread into one of the one or more critical sections, for each of the plurality of shared memory objects that is accessed within the critical section, the respective memory protection key, provided that no other thread holds the memory protection key, wherein an attempt by the thread to access any of the shared memory objects without holding the respective memory protection key causes a protection fault; and a fault handler to receive and process any protection fault raised by access to one or the memory objects without the respective memory protection key. a computer processor (202, 1102) supporting per-thread memory protection; and computer memory (1104, 1106, 1122) storing a plurality of software components each comprising instructions for execution by the computer processor (202, 1102), the software components comprising: a memory allocator configured to assign the plurality of shared memory objects to respective virtual memory pages (700, 702, 704) each storing only one of the plurality of shared memory objects, each shared memory object being protectable by a memory protection key assigned to the respective virtual memory page (700, 702, 704); synchronization wrapper functions associated with the one or more critical sections to provide to a thread, upon entry of the thread into one of the one or more critical sections, for each of the plurality of shared memory objects that is accessed within the critical section, the respective memory protection key, provided that no other thread holds the memory protection key, wherein an attempt by the thread to access any of the shared memory objects without holding the respective memory protection key causes a protection fault; and a fault handler to receive and process any protection fault raised by access to one or the memory objects without the respective memory protection key. a memory allocator configured to assign the plurality of shared memory objects to respective virtual memory pages (700, 702, 704) each storing only one of the plurality of shared memory objects, each shared memory object being protectable by a memory protection key assigned to the respective virtual memory page (700, 702, 704); synchronization wrapper functions associated with the one or more critical sections to provide to a thread, upon entry of the thread into one of the one or more critical sections, for each of the plurality of shared memory objects that is accessed within the critical section, the respective memory protection key, provided that no other thread holds the memory protection key, wherein an attempt by the thread to access any of the shared memory objects without holding the respective memory protection key causes a protection fault; and a fault handler to receive and process any protection fault raised by access to one or the memory objects without the respective memory protection key.