1 /**
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0.
4 */
5
6 #include <aws/common/assert.h>
7 #include <aws/common/common.h>
8 #include <aws/common/logging.h>
9 #include <aws/common/math.h>
10
11 #include <stdarg.h>
12 #include <stdlib.h>
13
14 #ifdef _WIN32
15 # include <Windows.h>
16 #endif
17
18 #ifdef __MACH__
19 # include <CoreFoundation/CoreFoundation.h>
20 #endif
21
22 /* turn off unused named parameter warning on msvc.*/
23 #ifdef _MSC_VER
24 # pragma warning(push)
25 # pragma warning(disable : 4100)
26 #endif
27
28 #ifndef PAGE_SIZE
29 # define PAGE_SIZE (4 * 1024)
30 #endif
31
aws_allocator_is_valid(const struct aws_allocator * alloc)32 bool aws_allocator_is_valid(const struct aws_allocator *alloc) {
33 /* An allocator must define mem_acquire and mem_release. All other fields are optional */
34 return alloc && AWS_OBJECT_PTR_IS_READABLE(alloc) && alloc->mem_acquire && alloc->mem_release;
35 }
36
s_default_malloc(struct aws_allocator * allocator,size_t size)37 static void *s_default_malloc(struct aws_allocator *allocator, size_t size) {
38 (void)allocator;
39 /* larger allocations should be aligned so that AVX and friends can avoid
40 * the extra preable during unaligned versions of memcpy/memset on big buffers
41 * This will also accelerate hardware CRC and SHA on ARM chips
42 *
43 * 64 byte alignment for > page allocations on 64 bit systems
44 * 32 byte alignment for > page allocations on 32 bit systems
45 * 16 byte alignment for <= page allocations on 64 bit systems
46 * 8 byte alignment for <= page allocations on 32 bit systems
47 *
48 * We use PAGE_SIZE as the boundary because we are not aware of any allocations of
49 * this size or greater that are not data buffers
50 */
51 const size_t alignment = sizeof(void *) * (size > PAGE_SIZE ? 8 : 2);
52 #if !defined(_WIN32)
53 void *result = NULL;
54 int err = posix_memalign(&result, alignment, size);
55 (void)err;
56 AWS_PANIC_OOM(result, "posix_memalign failed to allocate memory");
57 return result;
58 #else
59 void *mem = _aligned_malloc(size, alignment);
60 AWS_FATAL_POSTCONDITION(mem && "_aligned_malloc failed to allocate memory");
61 return mem;
62 #endif
63 }
64
s_default_free(struct aws_allocator * allocator,void * ptr)65 static void s_default_free(struct aws_allocator *allocator, void *ptr) {
66 (void)allocator;
67 #if !defined(_WIN32)
68 free(ptr);
69 #else
70 _aligned_free(ptr);
71 #endif
72 }
73
s_default_realloc(struct aws_allocator * allocator,void * ptr,size_t oldsize,size_t newsize)74 static void *s_default_realloc(struct aws_allocator *allocator, void *ptr, size_t oldsize, size_t newsize) {
75 (void)allocator;
76 (void)oldsize;
77 AWS_FATAL_PRECONDITION(newsize);
78
79 #if !defined(_WIN32)
80 if (newsize <= oldsize) {
81 return ptr;
82 }
83
84 /* newsize is > oldsize, need more memory */
85 void *new_mem = s_default_malloc(allocator, newsize);
86 AWS_PANIC_OOM(new_mem, "Unhandled OOM encountered in s_default_malloc");
87
88 if (ptr) {
89 memcpy(new_mem, ptr, oldsize);
90 s_default_free(allocator, ptr);
91 }
92
93 return new_mem;
94 #else
95 const size_t alignment = sizeof(void *) * (newsize > PAGE_SIZE ? 8 : 2);
96 void *new_mem = _aligned_realloc(ptr, newsize, alignment);
97 AWS_PANIC_OOM(new_mem, "Unhandled OOM encountered in _aligned_realloc");
98 return new_mem;
99 #endif
100 }
101
s_default_calloc(struct aws_allocator * allocator,size_t num,size_t size)102 static void *s_default_calloc(struct aws_allocator *allocator, size_t num, size_t size) {
103 void *mem = s_default_malloc(allocator, num * size);
104 AWS_PANIC_OOM(mem, "Unhandled OOM encountered in s_default_malloc");
105 memset(mem, 0, num * size);
106 return mem;
107 }
108
109 static struct aws_allocator default_allocator = {
110 .mem_acquire = s_default_malloc,
111 .mem_release = s_default_free,
112 .mem_realloc = s_default_realloc,
113 .mem_calloc = s_default_calloc,
114 };
115
aws_default_allocator(void)116 struct aws_allocator *aws_default_allocator(void) {
117 return &default_allocator;
118 }
119
aws_mem_acquire(struct aws_allocator * allocator,size_t size)120 void *aws_mem_acquire(struct aws_allocator *allocator, size_t size) {
121 AWS_FATAL_PRECONDITION(allocator != NULL);
122 AWS_FATAL_PRECONDITION(allocator->mem_acquire != NULL);
123 /* Protect against https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations */
124 AWS_FATAL_PRECONDITION(size != 0);
125
126 void *mem = allocator->mem_acquire(allocator, size);
127 AWS_PANIC_OOM(mem, "Unhandled OOM encountered in aws_mem_acquire with allocator");
128
129 return mem;
130 }
131
aws_mem_calloc(struct aws_allocator * allocator,size_t num,size_t size)132 void *aws_mem_calloc(struct aws_allocator *allocator, size_t num, size_t size) {
133 AWS_FATAL_PRECONDITION(allocator != NULL);
134 AWS_FATAL_PRECONDITION(allocator->mem_calloc || allocator->mem_acquire);
135 /* Protect against https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations */
136 AWS_FATAL_PRECONDITION(num != 0 && size != 0);
137
138 /* Defensive check: never use calloc with size * num that would overflow
139 * https://wiki.sei.cmu.edu/confluence/display/c/MEM07-C.+Ensure+that+the+arguments+to+calloc%28%29%2C+when+multiplied%2C+do+not+wrap
140 */
141 size_t required_bytes = 0;
142 AWS_FATAL_POSTCONDITION(!aws_mul_size_checked(num, size, &required_bytes), "calloc computed size > SIZE_MAX");
143
144 /* If there is a defined calloc, use it */
145 if (allocator->mem_calloc) {
146 void *mem = allocator->mem_calloc(allocator, num, size);
147 AWS_PANIC_OOM(mem, "Unhandled OOM encountered in aws_mem_acquire with allocator");
148 return mem;
149 }
150
151 /* Otherwise, emulate calloc */
152 void *mem = allocator->mem_acquire(allocator, required_bytes);
153 AWS_PANIC_OOM(mem, "Unhandled OOM encountered in aws_mem_acquire with allocator");
154
155 memset(mem, 0, required_bytes);
156 return mem;
157 }
158
159 #define AWS_ALIGN_ROUND_UP(value, alignment) (((value) + ((alignment)-1)) & ~((alignment)-1))
160
aws_mem_acquire_many(struct aws_allocator * allocator,size_t count,...)161 void *aws_mem_acquire_many(struct aws_allocator *allocator, size_t count, ...) {
162
163 enum { S_ALIGNMENT = sizeof(intmax_t) };
164
165 va_list args_size;
166 va_start(args_size, count);
167 va_list args_allocs;
168 va_copy(args_allocs, args_size);
169
170 size_t total_size = 0;
171 for (size_t i = 0; i < count; ++i) {
172
173 /* Ignore the pointer argument for now */
174 va_arg(args_size, void **);
175
176 size_t alloc_size = va_arg(args_size, size_t);
177 total_size += AWS_ALIGN_ROUND_UP(alloc_size, S_ALIGNMENT);
178 }
179 va_end(args_size);
180
181 void *allocation = NULL;
182
183 if (total_size > 0) {
184
185 allocation = aws_mem_acquire(allocator, total_size);
186 AWS_PANIC_OOM(allocation, "Unhandled OOM encountered in aws_mem_acquire with allocator");
187
188 uint8_t *current_ptr = allocation;
189
190 for (size_t i = 0; i < count; ++i) {
191
192 void **out_ptr = va_arg(args_allocs, void **);
193
194 size_t alloc_size = va_arg(args_allocs, size_t);
195 alloc_size = AWS_ALIGN_ROUND_UP(alloc_size, S_ALIGNMENT);
196
197 *out_ptr = current_ptr;
198 current_ptr += alloc_size;
199 }
200 }
201
202 va_end(args_allocs);
203 return allocation;
204 }
205
206 #undef AWS_ALIGN_ROUND_UP
207
aws_mem_release(struct aws_allocator * allocator,void * ptr)208 void aws_mem_release(struct aws_allocator *allocator, void *ptr) {
209 AWS_FATAL_PRECONDITION(allocator != NULL);
210 AWS_FATAL_PRECONDITION(allocator->mem_release != NULL);
211
212 if (ptr != NULL) {
213 allocator->mem_release(allocator, ptr);
214 }
215 }
216
aws_mem_realloc(struct aws_allocator * allocator,void ** ptr,size_t oldsize,size_t newsize)217 int aws_mem_realloc(struct aws_allocator *allocator, void **ptr, size_t oldsize, size_t newsize) {
218 AWS_FATAL_PRECONDITION(allocator != NULL);
219 AWS_FATAL_PRECONDITION(allocator->mem_realloc || allocator->mem_acquire);
220 AWS_FATAL_PRECONDITION(allocator->mem_release);
221
222 /* Protect against https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations */
223 if (newsize == 0) {
224 aws_mem_release(allocator, *ptr);
225 *ptr = NULL;
226 return AWS_OP_SUCCESS;
227 }
228
229 if (allocator->mem_realloc) {
230 void *newptr = allocator->mem_realloc(allocator, *ptr, oldsize, newsize);
231 AWS_PANIC_OOM(newptr, "Unhandled OOM encountered in aws_mem_acquire with allocator");
232
233 *ptr = newptr;
234 return AWS_OP_SUCCESS;
235 }
236
237 /* Since the allocator doesn't support realloc, we'll need to emulate it (inefficiently). */
238 if (oldsize >= newsize) {
239 return AWS_OP_SUCCESS;
240 }
241
242 void *newptr = allocator->mem_acquire(allocator, newsize);
243 AWS_PANIC_OOM(newptr, "Unhandled OOM encountered in aws_mem_acquire with allocator");
244
245 memcpy(newptr, *ptr, oldsize);
246 memset((uint8_t *)newptr + oldsize, 0, newsize - oldsize);
247
248 aws_mem_release(allocator, *ptr);
249
250 *ptr = newptr;
251
252 return AWS_OP_SUCCESS;
253 }
254
255 /* Wraps a CFAllocator around aws_allocator. For Mac only. */
256 #ifdef __MACH__
257
258 static CFStringRef s_cf_allocator_description = CFSTR("CFAllocator wrapping aws_allocator.");
259
260 /* note we don't have a standard specification stating sizeof(size_t) == sizeof(void *) so we have some extra casts */
s_cf_allocator_allocate(CFIndex alloc_size,CFOptionFlags hint,void * info)261 static void *s_cf_allocator_allocate(CFIndex alloc_size, CFOptionFlags hint, void *info) {
262 (void)hint;
263
264 struct aws_allocator *allocator = info;
265
266 void *mem = aws_mem_acquire(allocator, (size_t)alloc_size + sizeof(size_t));
267
268 size_t allocation_size = (size_t)alloc_size + sizeof(size_t);
269 memcpy(mem, &allocation_size, sizeof(size_t));
270 return (void *)((uint8_t *)mem + sizeof(size_t));
271 }
272
s_cf_allocator_deallocate(void * ptr,void * info)273 static void s_cf_allocator_deallocate(void *ptr, void *info) {
274 struct aws_allocator *allocator = info;
275
276 void *original_allocation = (uint8_t *)ptr - sizeof(size_t);
277
278 aws_mem_release(allocator, original_allocation);
279 }
280
s_cf_allocator_reallocate(void * ptr,CFIndex new_size,CFOptionFlags hint,void * info)281 static void *s_cf_allocator_reallocate(void *ptr, CFIndex new_size, CFOptionFlags hint, void *info) {
282 (void)hint;
283
284 struct aws_allocator *allocator = info;
285 AWS_ASSERT(allocator->mem_realloc);
286
287 void *original_allocation = (uint8_t *)ptr - sizeof(size_t);
288 size_t original_size = 0;
289 memcpy(&original_size, original_allocation, sizeof(size_t));
290
291 aws_mem_realloc(allocator, &original_allocation, original_size, (size_t)new_size);
292
293 size_t new_allocation_size = (size_t)new_size;
294 memcpy(original_allocation, &new_allocation_size, sizeof(size_t));
295
296 return (void *)((uint8_t *)original_allocation + sizeof(size_t));
297 }
298
s_cf_allocator_copy_description(const void * info)299 static CFStringRef s_cf_allocator_copy_description(const void *info) {
300 (void)info;
301
302 return s_cf_allocator_description;
303 }
304
s_cf_allocator_preferred_size(CFIndex size,CFOptionFlags hint,void * info)305 static CFIndex s_cf_allocator_preferred_size(CFIndex size, CFOptionFlags hint, void *info) {
306 (void)hint;
307 (void)info;
308
309 return size + sizeof(size_t);
310 }
311
aws_wrapped_cf_allocator_new(struct aws_allocator * allocator)312 CFAllocatorRef aws_wrapped_cf_allocator_new(struct aws_allocator *allocator) {
313 CFAllocatorRef cf_allocator = NULL;
314
315 CFAllocatorReallocateCallBack reallocate_callback = NULL;
316
317 if (allocator->mem_realloc) {
318 reallocate_callback = s_cf_allocator_reallocate;
319 }
320
321 CFAllocatorContext context = {
322 .allocate = s_cf_allocator_allocate,
323 .copyDescription = s_cf_allocator_copy_description,
324 .deallocate = s_cf_allocator_deallocate,
325 .reallocate = reallocate_callback,
326 .info = allocator,
327 .preferredSize = s_cf_allocator_preferred_size,
328 .release = NULL,
329 .retain = NULL,
330 .version = 0,
331 };
332
333 cf_allocator = CFAllocatorCreate(NULL, &context);
334
335 AWS_FATAL_ASSERT(cf_allocator && "creation of cf allocator failed!");
336
337 return cf_allocator;
338 }
339
aws_wrapped_cf_allocator_destroy(CFAllocatorRef allocator)340 void aws_wrapped_cf_allocator_destroy(CFAllocatorRef allocator) {
341 CFRelease(allocator);
342 }
343
344 #endif /*__MACH__ */
345