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