alloc.h 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /// \file
  2. /// \ingroup cgraph_utils
  3. /// \brief Memory allocation wrappers that exit on failure
  4. ///
  5. /// Much Graphviz code is not in a position to gracefully handle failure of
  6. /// dynamic memory allocation. The following wrappers provide a safe compromise
  7. /// where allocation failure does not need to be handled, but simply causes
  8. /// process exit. This is not ideal for external callers, but it is better than
  9. /// memory corruption or confusing crashes.
  10. ///
  11. /// Note that the wrappers also take a more comprehensive strategy of zeroing
  12. /// newly allocated memory than `malloc`. This reduces the number of things
  13. /// callers need to think about and has only a modest overhead.
  14. #pragma once
  15. #include <assert.h>
  16. #include <limits.h>
  17. #include <stdint.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <util/exit.h>
  22. #include <util/prisize_t.h>
  23. static inline void *gv_calloc(size_t nmemb, size_t size) {
  24. if (nmemb > 0 && SIZE_MAX / nmemb < size) {
  25. fprintf(stderr,
  26. "integer overflow when trying to allocate "
  27. "%" PRISIZE_T " * %" PRISIZE_T " bytes\n",
  28. nmemb, size);
  29. graphviz_exit(EXIT_FAILURE);
  30. }
  31. void *p = calloc(nmemb, size);
  32. if (nmemb > 0 && size > 0 && p == NULL) {
  33. fprintf(stderr,
  34. "out of memory when trying to allocate %" PRISIZE_T " bytes\n",
  35. nmemb * size);
  36. graphviz_exit(EXIT_FAILURE);
  37. }
  38. return p;
  39. }
  40. static inline void *gv_alloc(size_t size) { return gv_calloc(1, size); }
  41. static inline void *gv_realloc(void *ptr, size_t old_size, size_t new_size) {
  42. // make realloc with 0 size equivalent to free, even under C23 rules
  43. if (new_size == 0) {
  44. free(ptr);
  45. return NULL;
  46. }
  47. void *p = realloc(ptr, new_size);
  48. if (p == NULL) {
  49. fprintf(stderr,
  50. "out of memory when trying to allocate %" PRISIZE_T " bytes\n",
  51. new_size);
  52. graphviz_exit(EXIT_FAILURE);
  53. }
  54. // if this was an expansion, zero the new memory
  55. if (new_size > old_size) {
  56. memset((char *)p + old_size, 0, new_size - old_size);
  57. }
  58. return p;
  59. }
  60. static inline void *gv_recalloc(void *ptr, size_t old_nmemb, size_t new_nmemb,
  61. size_t size) {
  62. assert(size > 0 && "attempt to allocate array of 0-sized elements");
  63. assert(old_nmemb < SIZE_MAX / size && "claimed previous extent is too large");
  64. // will multiplication overflow?
  65. if (new_nmemb > SIZE_MAX / size) {
  66. fprintf(stderr,
  67. "integer overflow when trying to allocate %" PRISIZE_T
  68. " * %" PRISIZE_T " bytes\n",
  69. new_nmemb, size);
  70. graphviz_exit(EXIT_FAILURE);
  71. }
  72. return gv_realloc(ptr, old_nmemb * size, new_nmemb * size);
  73. }
  74. // when including this header in a C++ source, G++ under Cygwin chooses to be
  75. // pedantic and hide the prototypes of `strdup` and `strndup` when not
  76. // compiling with a GNU extension standard, so re-prototype them
  77. #if defined(__cplusplus) && defined(__CYGWIN__)
  78. extern "C" {
  79. extern char *strdup(const char *s1);
  80. extern char *strndup(const char *s1, size_t n);
  81. }
  82. #endif
  83. static inline char *gv_strdup(const char *original) {
  84. char *copy = strdup(original);
  85. if (copy == NULL) {
  86. fprintf(stderr,
  87. "out of memory when trying to allocate %" PRISIZE_T " bytes\n",
  88. strlen(original) + 1);
  89. graphviz_exit(EXIT_FAILURE);
  90. }
  91. return copy;
  92. }
  93. static inline char *gv_strndup(const char *original, size_t length) {
  94. char *copy;
  95. // non-Cygwin Windows environments do not provide strndup
  96. #if defined(_MSC_VER) || defined(__MINGW32__)
  97. // does the string end before the given length?
  98. {
  99. const char *end = (const char *)memchr(original, '\0', length);
  100. if (end != NULL) {
  101. length = (size_t)(end - original);
  102. }
  103. }
  104. // will our calculation to include the NUL byte below overflow?
  105. if (SIZE_MAX - length < 1) {
  106. fprintf(stderr,
  107. "integer overflow when trying to allocate %" PRISIZE_T
  108. " + 1 bytes\n",
  109. length);
  110. graphviz_exit(EXIT_FAILURE);
  111. }
  112. copy = (char *)gv_alloc(length + 1);
  113. memcpy(copy, original, length);
  114. // `gv_alloc` has already zeroed the backing memory, so no need to manually
  115. // add a NUL terminator
  116. #else
  117. copy = strndup(original, length);
  118. #endif
  119. if (copy == NULL) {
  120. fprintf(stderr,
  121. "out of memory when trying to allocate %" PRISIZE_T " bytes\n",
  122. length + 1);
  123. graphviz_exit(EXIT_FAILURE);
  124. }
  125. return copy;
  126. }