Vuo  2.4.1
VuoHeap.cc
Go to the documentation of this file.
1
10#include "VuoHeap.h"
11#include <dispatch/dispatch.h>
12#include <dlfcn.h>
13#include <sys/errno.h>
14#include <sys/mman.h>
15#include <map>
16#include <set>
17#include <sstream>
18#include <iomanip>
19using namespace std;
20#include "VuoLog.h"
21#include "VuoRuntime.h"
22
23static set<const void *> *VuoHeap_trace;
24static pthread_mutex_t VuoHeap_mutex = PTHREAD_MUTEX_INITIALIZER;
25
29void sendErrorWrapper(const char *message)
30{
31#ifdef VUOHEAP_TRACE
32 fprintf(stderr, "%s\n", message);
34#endif
35
36 VuoSendErrorType *vuoSendError = (VuoSendErrorType *) dlsym(RTLD_SELF, "vuoSendError"); // for running composition in separate process as executable or in current process
37 if (! vuoSendError)
38 vuoSendError = (VuoSendErrorType *) dlsym(RTLD_DEFAULT, "vuoSendError"); // for running composition in separate process as dynamic libraries
39
40 pthread_key_t *vuoCompositionStateKey = (pthread_key_t *) dlsym(RTLD_SELF, "vuoCompositionStateKey");
42 vuoCompositionStateKey = (pthread_key_t *) dlsym(RTLD_DEFAULT, "vuoCompositionStateKey");
43
44 void *compositionState = NULL;
46 compositionState = pthread_getspecific(*vuoCompositionStateKey);
47
48 if (vuoSendError && compositionState)
49 vuoSendError((VuoCompositionState *)compositionState, message);
50 else
51 VUserLog("%s", message);
52}
53
54#ifdef VUOHEAP_TRACEALL
58static bool VuoHeap_isComposition(void)
59{
60 static bool isComposition = false;
61 static dispatch_once_t once = 0;
62 dispatch_once(&once, ^{
63 isComposition = dlsym(RTLD_DEFAULT, "vuoCompositionStateKey");
64 });
65 return isComposition;
66}
67#endif
68
72typedef struct
73{
74 int referenceCount;
75 DeallocateFunctionType deallocateFunction;
76
77 const char *file;
78 unsigned int line;
79 const char *function;
80 const char *variable;
82
83static map<const void *, VuoHeapEntry> *referenceCounts;
84static set<const void *> *singletons;
85
91static inline bool VuoHeap_isPointerValid(const void *pointer)
92{
93 // On macOS, memory allocated by `malloc` is 16-byte aligned,
94 // so any non-16-byte-aligned pointers are suspicious.
95 // https://opensource.apple.com/source/Libc/Libc-825.26/gen/malloc.3.auto.html says
96 // "The allocated memory is aligned such that it can be used for any data type,
97 // including AltiVec- and SSE-related types."
98 // And https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-alignment-support says
99 // "When using the IntelĀ® Streaming SIMD Extensions (IntelĀ® SSE) intrinsics,
100 // you should align data to 16 bytes in memory operations."
101 if ((unsigned long)pointer & 0xf)
102 return false;
103
104 // On macOS, `malloc` shouldn't give us any pointers below 4 GB.
105 // https://opensource.apple.com/source/ld64/ld64-242/doc/man/man1/ld.1.auto.html says
106 // "By default the linker creates an unreadable segment starting at address zero named __PAGEZERO.
107 // On 64-bit architectures, the default size is 4GB."
108 if ((unsigned long)pointer < 0x100000000)
109 return false;
110
111 // x86_64 is currently limited to 48 bits (256 TB) of virtual address space.
112 // http://support.amd.com/TechDocs/24593.pdf, page 131, says
113 // "Bits 63:48 are a sign extension of bit 47".
114 // This will no longer be the case if/when chips with 5-level paging are produced.
115 if ((unsigned long)pointer > 0xffffffffffff)
116 return false;
117
118 return true;
119}
120
127bool VuoHeap_isPointerReadable(const void *pointer)
128{
129 // Round down to the beginning of `pointer`'s heap page.
130 // Assume getpagesize() returns a power-of-two; subtracting 1 turns it into a bitmask.
131 int pageSize = getpagesize();
132 long heapPageMask = ~((long)pageSize-1);
133 long heapPage = (long)pointer & heapPageMask;
134
135 // Try to load the page into core.
136 mlock((void *)heapPage, pageSize);
137 munlock((void *)heapPage, pageSize);
138
139 // Check whether the page was successfully loaded into core.
140 char pageStatus[1];
141 if (mincore((void *)heapPage, pageSize, pageStatus) == 0)
142 return pageStatus[0] & MINCORE_INCORE;
143
144 else
145 return false;
146}
147
151static void VuoHeap_makeSafePointerSummary(char *summary, const void *pointer)
152{
153 if (VuoHeap_isPointerReadable(pointer)
154 && VuoHeap_isPointerReadable((char *)pointer + 15))
155 {
156 // Page(s) are valid, so output the pointer's first 16 printable characters.
157 char *pointerAsChar = (char *)pointer;
158 for (int i = 0; i < 16; ++i)
159 if (isprint(pointerAsChar[i]))
160 summary[i] = pointerAsChar[i];
161 else
162 summary[i] = '_';
163 summary[16] = 0;
164 }
165 else
166 strlcpy(summary, "(not allocated)", 17);
167}
168
177{
178 const char *format = "%s:%d :: %s() :: %s";
179 int size = snprintf(NULL, 0, format, e.file, e.line, e.function, e.variable);
180 char *description = (char *)malloc(size+1);
181 snprintf(description, size+1, format, e.file, e.line, e.function, e.variable);
182 return description;
183}
184
190static void __attribute__((constructor(101))) VuoHeap_init()
191{
192 referenceCounts = new map<const void *, VuoHeapEntry>;
193 singletons = new set<const void *>;
194 VuoHeap_trace = new set<const void *>;
195
196#if 0
197 // Periodically dump the referenceCounts table, to help find leaks.
198 const double dumpInterval = 5.0; // seconds
199 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
200 dispatch_source_set_timer(timer, dispatch_walltime(NULL,0), NSEC_PER_SEC*dumpInterval, NSEC_PER_SEC*dumpInterval);
201 dispatch_source_set_event_handler(timer, ^{
202 fprintf(stderr, "\n\n\n\n\nreferenceCounts:\n");
203 pthread_mutex_lock(&VuoHeap_mutex);
204 for (map<const void *, VuoHeapEntry>::iterator i = referenceCounts->begin(); i != referenceCounts->end(); ++i)
205 {
206 const void *heapPointer = i->first;
207 char pointerSummary[17];
208 VuoHeap_makeSafePointerSummary(pointerSummary, heapPointer);
209 char *description = VuoHeap_makeDescription(i->second);
210 fprintf(stderr, "\t% 3d refs to %p \"%s\", registered at %s\n", i->second.referenceCount, heapPointer, pointerSummary, description);
211 free(description);
212 }
213 pthread_mutex_unlock(&VuoHeap_mutex);
214 });
215 dispatch_resume(timer);
216#endif
217}
218
223{
224 pthread_mutex_lock(&VuoHeap_mutex);
225
226 if (! referenceCounts->empty())
227 {
228 ostringstream errorMessage;
229 errorMessage << "On reference table " << referenceCounts
230 << ", VuoRelease was not called enough times for:" << endl;
231 for (map<const void *, VuoHeapEntry>::iterator i = referenceCounts->begin(); i != referenceCounts->end(); ++i)
232 {
233 const void *heapPointer = i->first;
234 char pointerSummary[17];
235 VuoHeap_makeSafePointerSummary(pointerSummary, heapPointer);
236 char *description = VuoHeap_makeDescription(i->second);
237 errorMessage << "\t" << setw(3) << i->second.referenceCount << " refs to " << heapPointer << " \"" << pointerSummary << "\", registered at " << description << endl;
238 free(description);
239 }
240 sendErrorWrapper(errorMessage.str().c_str());
241 }
242
243 pthread_mutex_unlock(&VuoHeap_mutex);
244}
245
261int VuoRegisterF(const void *heapPointer, DeallocateFunctionType deallocate, const char *file, unsigned int linenumber, const char *func, const char *pointerName)
262{
263 if (! heapPointer)
264 return -1;
265
266 if (!VuoHeap_isPointerValid(heapPointer))
267 {
268 ostringstream errorMessage;
269 char *description = VuoHeap_makeDescription((VuoHeapEntry){0, NULL, file, linenumber, func, pointerName});
270 errorMessage << "On reference table " << referenceCounts
271 << ", VuoRegister was called for bogus pointer " << heapPointer
272 << " " << description;
273 free(description);
274 sendErrorWrapper(errorMessage.str().c_str());
275 }
276
277 bool isAlreadyReferenceCounted;
278 int updatedCount;
279
280 pthread_mutex_lock(&VuoHeap_mutex);
281 {
282#ifdef VUOHEAP_TRACE
283#ifdef VUOHEAP_TRACEALL
284 if (VuoHeap_isComposition())
285#else
286 if (VuoHeap_trace->find(heapPointer) != VuoHeap_trace->end())
287#endif
288 {
289 fprintf(stderr, "table=%p VuoRegister(%p) %s\n", referenceCounts, heapPointer, pointerName);
291 }
292#endif
293
294 isAlreadyReferenceCounted = referenceCounts->count(heapPointer);
295 if (! isAlreadyReferenceCounted)
296 {
297 updatedCount = 0;
298 (*referenceCounts)[heapPointer] = (VuoHeapEntry){updatedCount, deallocate, file, linenumber, func, pointerName};
299 }
300 else
301 updatedCount = (*referenceCounts)[heapPointer].referenceCount;
302 }
303 pthread_mutex_unlock(&VuoHeap_mutex);
304
305 if (isAlreadyReferenceCounted)
306 {
307 ostringstream errorMessage;
308 char *description = VuoHeap_makeDescription((VuoHeapEntry){0, NULL, file, linenumber, func, pointerName});
309 errorMessage << "On reference table " << referenceCounts
310 << ", VuoRegister was called more than once for " << heapPointer
311 << " " << description;
312 free(description);
313 sendErrorWrapper(errorMessage.str().c_str());
314 }
315
316 return updatedCount;
317}
318
323int VuoRegisterSingletonF(const void *heapPointer, const char *file, unsigned int linenumber, const char *func, const char *pointerName)
324{
325 if (! heapPointer)
326 return -1;
327
328 if (!VuoHeap_isPointerValid(heapPointer))
329 {
330 ostringstream errorMessage;
331 char *description = VuoHeap_makeDescription((VuoHeapEntry){0, NULL, file, linenumber, func, pointerName});
332 errorMessage << "On reference table " << referenceCounts
333 << ", VuoRegisterSingleton was called for bogus pointer " << heapPointer
334 << " " << description;
335 free(description);
336 sendErrorWrapper(errorMessage.str().c_str());
337 }
338
339 bool isAlreadyReferenceCounted;
340
341 pthread_mutex_lock(&VuoHeap_mutex);
342 {
343#ifdef VUOHEAP_TRACE
344#ifdef VUOHEAP_TRACEALL
345 if (VuoHeap_isComposition())
346#else
347 if (VuoHeap_trace->find(heapPointer) != VuoHeap_trace->end())
348#endif
349 {
350 fprintf(stderr, "table=%p VuoRegisterSingleton(%p) %s\n", referenceCounts, heapPointer, pointerName);
352 }
353#endif
354
355 // Remove the singleton from the main reference-counting table, if it exists there.
356 // Enables reclassifying a pointer that was already VuoRegister()ed.
357 referenceCounts->erase(heapPointer);
358
359 // Add the singleton to the singleton table.
360 isAlreadyReferenceCounted = (singletons->find(heapPointer) != singletons->end());
361 if (! isAlreadyReferenceCounted)
362 singletons->insert(heapPointer);
363 }
364 pthread_mutex_unlock(&VuoHeap_mutex);
365
366 if (isAlreadyReferenceCounted)
367 {
368 ostringstream errorMessage;
369 char *description = VuoHeap_makeDescription((VuoHeapEntry){0, NULL, file, linenumber, func, pointerName});
370 errorMessage << "On reference table " << referenceCounts
371 << ", VuoRegisterSingleton was called more than once for " << heapPointer
372 << " " << description;
373 free(description);
374 sendErrorWrapper(errorMessage.str().c_str());
375 }
376
377 return isAlreadyReferenceCounted ? 1 : 0;
378}
379
388extern "C" int VuoRetainF(const void *heapPointer, const char *file, unsigned int linenumber, const char *func)
389{
390 return VuoRetain(heapPointer);
391}
392
400int VuoRetain(const void *heapPointer)
401{
402 if (! heapPointer)
403 return -1;
404
405 if (!VuoHeap_isPointerValid(heapPointer))
406 {
407 char pointerSummary[17];
408 VuoHeap_makeSafePointerSummary(pointerSummary, heapPointer);
409 ostringstream errorMessage;
410 errorMessage << "On reference table " << referenceCounts
411 << ", VuoRetain was called for bogus pointer " << heapPointer
412 << " \"" << pointerSummary << "\"";
413 sendErrorWrapper(errorMessage.str().c_str());
414 }
415
416 int updatedCount = -1;
417 bool foundSingleton = false;
418
419 pthread_mutex_lock(&VuoHeap_mutex);
420 {
421 map<const void *, VuoHeapEntry>::iterator i = referenceCounts->find(heapPointer);
422
423#ifdef VUOHEAP_TRACE
424#ifdef VUOHEAP_TRACEALL
425 if (VuoHeap_isComposition())
426#else
427 if (VuoHeap_trace->find(heapPointer) != VuoHeap_trace->end())
428#endif
429 {
430 fprintf(stderr, "table=%p VuoRetain(%p) %s\n", referenceCounts, heapPointer, (i != referenceCounts->end()) ? i->second.variable : "");
432 }
433#endif
434
435 if (i != referenceCounts->end())
436 updatedCount = ++(i->second.referenceCount);
437 else
438 foundSingleton = singletons->find(heapPointer) != singletons->end();
439 }
440 pthread_mutex_unlock(&VuoHeap_mutex);
441
442 if (updatedCount == -1 && !foundSingleton)
443 {
444 char pointerSummary[17];
445 VuoHeap_makeSafePointerSummary(pointerSummary, heapPointer);
446 ostringstream errorMessage;
447 errorMessage << "On reference table " << referenceCounts
448 << ", VuoRetain was called for unregistered pointer " << heapPointer
449 << " \"" << pointerSummary << "\"";
450 sendErrorWrapper(errorMessage.str().c_str());
451 }
452
453 return updatedCount;
454}
455
464extern "C" int VuoReleaseF(const void *heapPointer, const char *file, unsigned int linenumber, const char *func)
465{
466 return VuoRelease(heapPointer);
467}
468
477int VuoRelease(const void *heapPointer)
478{
479 if (! heapPointer)
480 return -1;
481
482 if (!VuoHeap_isPointerValid(heapPointer))
483 {
484 char pointerSummary[17];
485 VuoHeap_makeSafePointerSummary(pointerSummary, heapPointer);
486 ostringstream errorMessage;
487 errorMessage << "On reference table " << referenceCounts
488 << ", VuoRelease was called for bogus pointer " << heapPointer
489 << " \"" << pointerSummary << "\"";
490 sendErrorWrapper(errorMessage.str().c_str());
491 }
492
493 int updatedCount = -1;
494 bool foundSingleton = false;
495 bool isRegisteredWithoutRetain = false;
496 DeallocateFunctionType deallocate = NULL;
497
498 pthread_mutex_lock(&VuoHeap_mutex);
499 {
500 map<const void *, VuoHeapEntry>::iterator i = referenceCounts->find(heapPointer);
501
502#ifdef VUOHEAP_TRACE
503#ifdef VUOHEAP_TRACEALL
504 if (VuoHeap_isComposition())
505#else
506 if (VuoHeap_trace->find(heapPointer) != VuoHeap_trace->end())
507#endif
508 {
509 fprintf(stderr, "table=%p VuoRelease(%p) %s\n", referenceCounts, heapPointer, (i != referenceCounts->end()) ? i->second.variable : "");
511 }
512#endif
513
514 if (i != referenceCounts->end())
515 {
516 if (i->second.referenceCount == 0)
517 {
518 isRegisteredWithoutRetain = true;
519 }
520 else
521 {
522 updatedCount = --(i->second.referenceCount);
523
524 if (updatedCount == 0)
525 {
526 deallocate = i->second.deallocateFunction;
527 referenceCounts->erase(heapPointer);
528 }
529 }
530 }
531 else
532 foundSingleton = singletons->find(heapPointer) != singletons->end();
533
534#ifdef VUOHEAP_TRACE
535 if (updatedCount == 0)
536 {
537#ifdef VUOHEAP_TRACEALL
538 if (VuoHeap_isComposition())
539 {
540#else
541 if (VuoHeap_trace->find(heapPointer) != VuoHeap_trace->end())
542 {
543 VuoHeap_trace->erase(heapPointer);
544#endif
545 fprintf(stderr, "table=%p VuoDeallocate(%p)\n", referenceCounts, heapPointer);
546// VuoLog_backtrace();
547 }
548 }
549#endif
550 }
551 pthread_mutex_unlock(&VuoHeap_mutex);
552
553 if (updatedCount == 0)
554 deallocate((void *)heapPointer);
555 else if (updatedCount == -1 && !foundSingleton)
556 {
557 char pointerSummary[17];
558 VuoHeap_makeSafePointerSummary(pointerSummary, heapPointer);
559 ostringstream errorMessage;
560 errorMessage << "On reference table " << referenceCounts
561 << ", VuoRelease was called for "
562 << (isRegisteredWithoutRetain ? "unretained" : "unregistered")
563 << " pointer " << heapPointer
564 << " \"" << pointerSummary << "\"";
565 sendErrorWrapper(errorMessage.str().c_str());
566 }
567
568 return updatedCount;
569}
570
578char *VuoHeap_getDescription(const void *heapPointer)
579{
580 char *description = nullptr;
581
582 pthread_mutex_lock(&VuoHeap_mutex);
583
584 map<const void *, VuoHeapEntry>::iterator i = referenceCounts->find(heapPointer);
585 if (i != referenceCounts->end())
586 description = VuoHeap_makeDescription(i->second);
587
588 pthread_mutex_unlock(&VuoHeap_mutex);
589
590 if (description)
591 return description;
592 else
593 return strdup("(pointer was not VuoRegister()ed)");
594}
595
601void VuoHeap_addTrace(const void *heapPointer)
602{
603 pthread_mutex_lock(&VuoHeap_mutex);
604
605 VuoHeap_trace->insert(heapPointer);
606
607 pthread_mutex_unlock(&VuoHeap_mutex);
608}