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
31static int fail_alloc_counter = _TEST_INT_MAX;
32static int n_failures_per_failure = 1;
33static int n_failures_this_failure = 0;
34static 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 */
45static void
46set_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 */
57static int
58get_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 */
71static void
72set_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 */
87static int
88decrement_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 */
116int
117test_get_malloc_blocks_outstanding (void)
118{
119  return n_blocks_outstanding;
120}
121
122void*
123test_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
145void*
146test_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
172void
173test_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
183char*
184test_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
203static int
204run_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 */
239int
240test_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