1 /*
2  * testOOM.c: Test out-of-memory handling
3  *
4  * See Copyright for the status of this software.
5  *
6  * Copyright 2003 Red Hat, Inc.
7  * Written by: hp@redhat.com
8  */
9 
10 #include "testOOMlib.h"
11 
12 #ifdef HAVE_STDLIB_H
13 #include <stdlib.h>
14 #endif
15 
16 #include <string.h>
17 
18 #define _TEST_INT_MAX 2147483647
19 #ifndef TRUE
20 #define TRUE (1)
21 #endif
22 #ifndef FALSE
23 #define FALSE (0)
24 #endif
25 #ifndef NULL
26 #define NULL ((void*)0)
27 #endif
28 
29 #include <libxml/xmlmemory.h>
30 
31 static int fail_alloc_counter = _TEST_INT_MAX;
32 static int n_failures_per_failure = 1;
33 static int n_failures_this_failure = 0;
34 static int n_blocks_outstanding = 0;
35 
36 /**
37  * set_fail_alloc_counter:
38  * @until_next_fail: number of successful allocs before one fails
39  *
40  * Sets the number of allocations until we simulate a failed
41  * allocation. If set to 0, the next allocation to run
42  * fails; if set to 1, one succeeds then the next fails; etc.
43  * Set to _TEST_INT_MAX to not fail anything.
44  */
45 static void
set_fail_alloc_counter(int until_next_fail)46 set_fail_alloc_counter (int until_next_fail)
47 {
48   fail_alloc_counter = until_next_fail;
49 }
50 
51 /**
52  * get_fail_alloc_counter:
53  *
54  * Returns the number of successful allocs until we'll simulate
55  * a failed alloc.
56  */
57 static int
get_fail_alloc_counter(void)58 get_fail_alloc_counter (void)
59 {
60   return fail_alloc_counter;
61 }
62 
63 /**
64  * set_fail_alloc_failures:
65  * @failures_per_failure: number to fail
66  *
67  * Sets how many mallocs to fail when the fail alloc counter reaches
68  * 0.
69  *
70  */
71 static void
set_fail_alloc_failures(int failures_per_failure)72 set_fail_alloc_failures (int failures_per_failure)
73 {
74   n_failures_per_failure = failures_per_failure;
75 }
76 
77 /**
78  * decrement_fail_alloc_counter:
79  *
80  * Called when about to alloc some memory; if
81  * it returns #TRUE, then the allocation should
82  * fail. If it returns #FALSE, then the allocation
83  * should not fail.
84  *
85  * returns #TRUE if this alloc should fail
86  */
87 static int
decrement_fail_alloc_counter(void)88 decrement_fail_alloc_counter (void)
89 {
90   if (fail_alloc_counter <= 0)
91     {
92       n_failures_this_failure += 1;
93       if (n_failures_this_failure >= n_failures_per_failure)
94         {
95           fail_alloc_counter = _TEST_INT_MAX;
96 
97           n_failures_this_failure = 0;
98         }
99 
100       return TRUE;
101     }
102   else
103     {
104       fail_alloc_counter -= 1;
105       return FALSE;
106     }
107 }
108 
109 /**
110  * test_get_malloc_blocks_outstanding:
111  *
112  * Get the number of outstanding malloc()'d blocks.
113  *
114  * Returns number of blocks
115  */
116 int
test_get_malloc_blocks_outstanding(void)117 test_get_malloc_blocks_outstanding (void)
118 {
119   return n_blocks_outstanding;
120 }
121 
122 void*
test_malloc(size_t bytes)123 test_malloc (size_t bytes)
124 {
125   if (decrement_fail_alloc_counter ())
126     {
127       /* FAIL the malloc */
128       return NULL;
129     }
130 
131   if (bytes == 0) /* some system mallocs handle this, some don't */
132     return NULL;
133   else
134     {
135       void *mem;
136       mem = xmlMemMalloc (bytes);
137 
138       if (mem)
139         n_blocks_outstanding += 1;
140 
141       return mem;
142     }
143 }
144 
145 void*
test_realloc(void * memory,size_t bytes)146 test_realloc (void  *memory,
147               size_t bytes)
148 {
149   if (decrement_fail_alloc_counter ())
150     {
151       /* FAIL */
152       return NULL;
153     }
154 
155   if (bytes == 0) /* guarantee this is safe */
156     {
157       test_free (memory);
158       return NULL;
159     }
160   else
161     {
162       void *mem;
163       mem = xmlMemRealloc (memory, bytes);
164 
165       if (memory == NULL && mem != NULL)
166         n_blocks_outstanding += 1;
167 
168       return mem;
169     }
170 }
171 
172 void
test_free(void * memory)173 test_free (void  *memory)
174 {
175   if (memory) /* we guarantee it's safe to free (NULL) */
176     {
177       n_blocks_outstanding -= 1;
178 
179       xmlMemFree (memory);
180     }
181 }
182 
183 char*
test_strdup(const char * str)184 test_strdup (const char *str)
185 {
186   int len;
187   char *copy;
188 
189   if (str == NULL)
190     return NULL;
191 
192   len = strlen (str);
193 
194   copy = test_malloc (len + 1);
195   if (copy == NULL)
196     return NULL;
197 
198   memcpy (copy, str, len + 1);
199 
200   return copy;
201 }
202 
203 static int
run_failing_each_malloc(int n_mallocs,TestMemoryFunction func,void * data)204 run_failing_each_malloc (int                n_mallocs,
205                          TestMemoryFunction func,
206                          void              *data)
207 {
208   n_mallocs += 10; /* fudge factor to ensure reallocs etc. are covered */
209 
210   while (n_mallocs >= 0)
211     {
212       set_fail_alloc_counter (n_mallocs);
213 
214       if (!(* func) (data))
215         return FALSE;
216 
217       n_mallocs -= 1;
218     }
219 
220   set_fail_alloc_counter (_TEST_INT_MAX);
221 
222   return TRUE;
223 }
224 
225 /**
226  * test_oom_handling:
227  * @func: function to call
228  * @data: data to pass to function
229  *
230  * Tests how well the given function responds to out-of-memory
231  * situations. Calls the function repeatedly, failing a different
232  * call to malloc() each time. If the function ever returns #FALSE,
233  * the test fails. The function should return #TRUE whenever something
234  * valid (such as returning an error, or succeeding) occurs, and #FALSE
235  * if it gets confused in some way.
236  *
237  * Returns #TRUE if the function never returns FALSE
238  */
239 int
test_oom_handling(TestMemoryFunction func,void * data)240 test_oom_handling (TestMemoryFunction  func,
241                    void               *data)
242 {
243   int approx_mallocs;
244 
245   /* Run once to see about how many mallocs are involved */
246 
247   set_fail_alloc_counter (_TEST_INT_MAX);
248 
249   if (!(* func) (data))
250     return FALSE;
251 
252   approx_mallocs = _TEST_INT_MAX - get_fail_alloc_counter ();
253 
254   set_fail_alloc_failures (1);
255   if (!run_failing_each_malloc (approx_mallocs, func, data))
256     return FALSE;
257 
258   set_fail_alloc_failures (2);
259   if (!run_failing_each_malloc (approx_mallocs, func, data))
260     return FALSE;
261 
262   set_fail_alloc_failures (3);
263   if (!run_failing_each_malloc (approx_mallocs, func, data))
264     return FALSE;
265 
266   set_fail_alloc_counter (_TEST_INT_MAX);
267 
268   return TRUE;
269 }
270