2003-03-24 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / test / break-loader.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-break-loader.c  Program to find byte streams that break the message loader
3  *
4  * Copyright (C) 2003  Red Hat Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include <dbus/dbus.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <sys/wait.h>
33 #include <string.h>
34
35 #define DBUS_COMPILATION 
36 #include <dbus/dbus-string.h>
37 #include <dbus/dbus-internals.h>
38 #include <dbus/dbus-test.h>
39 #include <dbus/dbus-marshal.h>
40 #undef DBUS_COMPILATION
41
42 static DBusString failure_dir;
43 static int total_attempts;
44 static int failures_this_iteration;
45
46 static int
47 random_int_in_range (int start,
48                      int end)
49 {
50   /* such elegant math */
51   double gap;
52   double v_double;
53   int v;
54
55   if (start == end)
56     return start;
57
58   _dbus_assert (end > start);
59   
60   gap = end - start - 1; /* -1 to not include "end" */
61   v_double = ((double)start) + (((double)rand ())/RAND_MAX) * gap;
62   if (v_double < 0.0)
63     v = (v_double - 0.5);
64   else
65     v = (v_double + 0.5);
66   
67   if (v < start)
68     {
69       fprintf (stderr, "random_int_in_range() generated %d for range [%d,%d)\n",
70                v, start, end);
71       v = start;
72     }
73   else if (v >= end)
74     {
75       fprintf (stderr, "random_int_in_range() generated %d for range [%d,%d)\n",
76                v, start, end);
77       v = end - 1;
78     }
79
80   /* printf ("  %d of [%d,%d)\n", v, start, end); */
81   
82   return v;
83 }
84
85 static dbus_bool_t
86 try_mutated_data (const DBusString *data)
87 {
88   int pid;
89
90   total_attempts += 1;
91   /* printf ("  attempt %d\n", total_attempts); */
92   
93   pid = fork ();
94
95   if (pid < 0)
96     {
97       fprintf (stderr, "fork() failed: %s\n",
98                strerror (errno));
99       exit (1);
100       return FALSE;
101     }
102
103   if (pid == 0)
104     {
105       /* Child, try loading the data */
106       if (!dbus_internal_do_not_use_try_message_data (data, _DBUS_MESSAGE_UNKNOWN))
107         exit (1);
108       else
109         exit (0);
110     }
111   else
112     {
113       /* Parent, wait for child */
114       int status;
115       DBusString filename;
116       dbus_bool_t failed;
117
118       if (waitpid (pid, &status, 0) < 0)
119         {
120           fprintf (stderr, "waitpid() failed: %s\n", strerror (errno));
121           exit (1);
122           return FALSE;
123         }
124
125       failed = FALSE;
126
127       if (!_dbus_string_init (&filename, _DBUS_INT_MAX) ||
128           !_dbus_string_copy (&failure_dir, 0,
129                               &filename, 0) ||
130           !_dbus_string_append_byte (&filename, '/'))
131         {
132           fprintf (stderr, "out of memory\n");
133           exit (1);
134         }
135
136       _dbus_string_append_int (&filename, total_attempts);
137
138       if (WIFEXITED (status))
139         {
140           if (WEXITSTATUS (status) != 0)
141             {
142               _dbus_string_append (&filename, "-exited-");
143               _dbus_string_append_int (&filename, WEXITSTATUS (status));
144               failed = TRUE;
145             }
146         }
147       else if (WIFSIGNALED (status))
148         {
149           _dbus_string_append (&filename, "signaled-");
150           _dbus_string_append_int (&filename, WTERMSIG (status));
151           failed = TRUE;
152         }
153
154       if (failed)
155         {
156           const char *filename_c;
157           DBusError error;
158
159           _dbus_string_append (&filename, ".message-raw");
160           
161           _dbus_string_get_const_data (&filename, &filename_c);
162           printf ("Child failed, writing %s\n",
163                   filename_c);
164
165           dbus_error_init (&error);
166           if (!_dbus_string_save_to_file (data, &filename, &error))
167             {
168               fprintf (stderr, "Failed to save failed message data: %s\n",
169                        error.message);
170               dbus_error_free (&error);
171               exit (1); /* so we can see the seed that was printed out */
172             }
173
174           failures_this_iteration += 1;
175
176           _dbus_string_free (&filename);
177           
178           return FALSE;
179         }
180       else
181         {
182           _dbus_string_free (&filename);          
183           return TRUE;
184         }
185     }
186
187   _dbus_assert_not_reached ("should not be reached");
188   return TRUE;
189 }
190
191 static void
192 randomly_shorten_or_lengthen (const DBusString *orig_data,
193                               DBusString       *mutated)
194 {
195   int delta;
196
197   if (orig_data != mutated)
198     {
199       _dbus_string_set_length (mutated, 0);
200       
201       if (!_dbus_string_copy (orig_data, 0, mutated, 0))
202         _dbus_assert_not_reached ("out of mem");
203     }
204
205   if (_dbus_string_get_length (mutated) == 0)
206     delta = random_int_in_range (0, 10);
207   else
208     delta = random_int_in_range (- _dbus_string_get_length (mutated),
209                                  _dbus_string_get_length (mutated) * 3);
210   
211   if (delta < 0)
212     _dbus_string_shorten (mutated, - delta);
213   else if (delta > 0)
214     {
215       int i = 0;
216
217       i = _dbus_string_get_length (mutated);
218       if (!_dbus_string_lengthen (mutated, delta))
219         _dbus_assert_not_reached ("couldn't lengthen string");
220
221       while (i < _dbus_string_get_length (mutated))
222         {
223           _dbus_string_set_byte (mutated,
224                                  i,
225                                  random_int_in_range (0, 256));
226           ++i;
227         }
228     }
229 }
230
231 static void
232 randomly_change_one_byte (const DBusString *orig_data,
233                           DBusString       *mutated)
234 {
235   int i;
236
237   if (orig_data != mutated)
238     {
239       _dbus_string_set_length (mutated, 0);
240       
241       if (!_dbus_string_copy (orig_data, 0, mutated, 0))
242         _dbus_assert_not_reached ("out of mem");
243     }
244
245   if (_dbus_string_get_length (mutated) == 0)
246     return;
247   
248   i = random_int_in_range (0, _dbus_string_get_length (mutated));
249
250   _dbus_string_set_byte (mutated, i,
251                          random_int_in_range (0, 256));
252 }
253
254 static void
255 randomly_remove_one_byte (const DBusString *orig_data,
256                           DBusString       *mutated)
257 {
258   int i;
259
260   if (orig_data != mutated)
261     {
262       _dbus_string_set_length (mutated, 0);
263       
264       if (!_dbus_string_copy (orig_data, 0, mutated, 0))
265         _dbus_assert_not_reached ("out of mem");
266     }
267
268   if (_dbus_string_get_length (mutated) == 0)
269     return;
270   
271   i = random_int_in_range (0, _dbus_string_get_length (mutated));
272
273   _dbus_string_delete (mutated, i, 1);
274 }
275
276
277 static void
278 randomly_add_one_byte (const DBusString *orig_data,
279                        DBusString       *mutated)
280 {
281   int i;
282
283   if (orig_data != mutated)
284     {
285       _dbus_string_set_length (mutated, 0);
286       
287       if (!_dbus_string_copy (orig_data, 0, mutated, 0))
288         _dbus_assert_not_reached ("out of mem");
289     }
290
291   i = random_int_in_range (0, _dbus_string_get_length (mutated));
292
293   _dbus_string_insert_byte (mutated, i,
294                             random_int_in_range (0, 256));
295 }
296
297 static void
298 randomly_modify_length (const DBusString *orig_data,
299                         DBusString       *mutated)
300 {
301   int i;
302   int byte_order;
303   const char *d;
304   dbus_uint32_t orig;
305   int delta;
306   
307   if (orig_data != mutated)
308     {
309       _dbus_string_set_length (mutated, 0);
310       
311       if (!_dbus_string_copy (orig_data, 0, mutated, 0))
312         _dbus_assert_not_reached ("out of mem");
313     }
314
315   if (_dbus_string_get_length (mutated) < 12)
316     return;
317   
318   _dbus_string_get_const_data (mutated, &d);
319
320   if (!(*d == DBUS_LITTLE_ENDIAN ||
321         *d == DBUS_BIG_ENDIAN))
322     return;
323
324   byte_order = *d;
325   
326   i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8);
327   i = _DBUS_ALIGN_VALUE (i, 4);
328
329   orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL);
330
331   delta = random_int_in_range (-10, 10);  
332   
333   _dbus_marshal_set_uint32 (mutated, byte_order, i,
334                             (unsigned) (orig + delta));
335 }
336
337 static void
338 randomly_set_extreme_ints (const DBusString *orig_data,
339                            DBusString       *mutated)
340 {
341   int i;
342   int byte_order;
343   const char *d;
344   dbus_uint32_t orig;
345   static int which = 0;
346   unsigned int extreme_ints[] = {
347     _DBUS_INT_MAX,
348     _DBUS_UINT_MAX,
349     _DBUS_INT_MAX - 1,
350     _DBUS_UINT_MAX - 1,
351     _DBUS_INT_MAX - 2,
352     _DBUS_UINT_MAX - 2,
353     (unsigned int) (_DBUS_INT_MAX + 1),
354     (unsigned int) (_DBUS_UINT_MAX + 1),
355     _DBUS_INT_MAX + 2,
356     _DBUS_UINT_MAX + 2,
357     0, 1, 2, 3,
358     (unsigned int) -1,
359     (unsigned int) -2,
360     (unsigned int) -3
361   };
362     
363   if (orig_data != mutated)
364     {
365       _dbus_string_set_length (mutated, 0);
366       
367       if (!_dbus_string_copy (orig_data, 0, mutated, 0))
368         _dbus_assert_not_reached ("out of mem");
369     }
370
371   if (_dbus_string_get_length (mutated) < 12)
372     return;
373   
374   _dbus_string_get_const_data (mutated, &d);
375
376   if (!(*d == DBUS_LITTLE_ENDIAN ||
377         *d == DBUS_BIG_ENDIAN))
378     return;
379
380   byte_order = *d;
381   
382   i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8);
383   i = _DBUS_ALIGN_VALUE (i, 4);
384
385   orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL);
386
387   which = random_int_in_range (0, _DBUS_N_ELEMENTS (extreme_ints));
388
389   _dbus_assert (which >= 0);
390   _dbus_assert (which < _DBUS_N_ELEMENTS (extreme_ints));
391   
392   _dbus_marshal_set_uint32 (mutated, byte_order, i,
393                             extreme_ints[which]);
394 }
395
396 static int times_we_did_each_thing[6] = { 0, };
397
398 static void
399 randomly_do_n_things (const DBusString *orig_data,
400                       DBusString       *mutated,
401                       int               n)
402 {
403   int i;
404   void (* functions[]) (const DBusString *orig_data,
405                         DBusString       *mutated) =
406     {
407       randomly_shorten_or_lengthen,
408       randomly_change_one_byte,
409       randomly_add_one_byte,
410       randomly_remove_one_byte,
411       randomly_modify_length,
412       randomly_set_extreme_ints
413     };
414
415   _dbus_string_set_length (mutated, 0);
416
417   if (!_dbus_string_copy (orig_data, 0, mutated, 0))
418     _dbus_assert_not_reached ("out of mem");
419
420   i = 0;
421   while (i < n)
422     {
423       int which;
424
425       which = random_int_in_range (0, _DBUS_N_ELEMENTS (functions));
426
427       (* functions[which]) (mutated, mutated);
428       times_we_did_each_thing[which] += 1;
429       
430       ++i;
431     }
432 }
433
434 static dbus_bool_t
435 find_breaks_based_on (const DBusString   *filename,
436                       dbus_bool_t         is_raw,
437                       DBusMessageValidity expected_validity,
438                       void               *data)
439 {
440   DBusString orig_data;
441   DBusString mutated;
442   const char *filename_c;
443   dbus_bool_t retval;
444   int i;
445
446   _dbus_string_get_const_data (filename, &filename_c);
447
448   retval = FALSE;
449
450   if (!_dbus_string_init (&orig_data, _DBUS_INT_MAX))
451     _dbus_assert_not_reached ("could not allocate string\n");
452
453   if (!_dbus_string_init (&mutated, _DBUS_INT_MAX))
454     _dbus_assert_not_reached ("could not allocate string\n");
455
456   if (!dbus_internal_do_not_use_load_message_file (filename, is_raw,
457                                                    &orig_data))
458     {
459       fprintf (stderr, "could not load file %s\n", filename_c);
460       goto failed;
461     }
462
463   i = 0;
464   while (i < 100)
465     {
466       randomly_change_one_byte (&orig_data, &mutated);
467       try_mutated_data (&mutated);
468
469       ++i;
470     }
471
472   i = 0;
473   while (i < 50)
474     {
475       randomly_modify_length (&orig_data, &mutated);
476       try_mutated_data (&mutated);
477
478       ++i;
479     }
480   
481   i = 0;
482   while (i < 50)
483     {
484       randomly_remove_one_byte (&orig_data, &mutated);
485       try_mutated_data (&mutated);
486
487       ++i;
488     }
489
490   i = 0;
491   while (i < 50)
492     {
493       randomly_add_one_byte (&orig_data, &mutated);
494       try_mutated_data (&mutated);
495
496       ++i;
497     }
498
499   i = 0;
500   while (i < 50)
501     {
502       randomly_set_extreme_ints (&orig_data, &mutated);
503       try_mutated_data (&mutated);
504
505       ++i;
506     }
507   
508   i = 0;
509   while (i < 15)
510     {
511       randomly_shorten_or_lengthen (&orig_data, &mutated);
512       try_mutated_data (&mutated);
513
514       ++i;
515     }
516
517   i = 0;
518   while (i < 42)
519     {
520       randomly_do_n_things (&orig_data, &mutated, 2);
521       try_mutated_data (&mutated);
522
523       ++i;
524     }
525
526   i = 0;
527   while (i < 42)
528     {
529       randomly_do_n_things (&orig_data, &mutated, 3);
530       try_mutated_data (&mutated);
531
532       ++i;
533     }
534
535   i = 0;
536   while (i < 42)
537     {
538       randomly_do_n_things (&orig_data, &mutated, 4);
539       try_mutated_data (&mutated);
540
541       ++i;
542     }
543   
544   retval = TRUE;
545   
546  failed:
547
548   _dbus_string_free (&orig_data);
549   _dbus_string_free (&mutated);
550
551   /* FALSE means end the whole process */
552   return retval;
553 }
554
555 static unsigned int
556 get_random_seed (void)
557 {
558   DBusString bytes;
559   unsigned int seed;
560   int fd;
561   const char *s;
562
563   seed = 0;
564
565   if (!_dbus_string_init (&bytes, _DBUS_INT_MAX))
566     exit (1);
567
568   fd = open ("/dev/urandom", O_RDONLY);
569   if (fd < 0)
570     goto use_fallback;
571
572   if (_dbus_read (fd, &bytes, 4) != 4)
573     goto use_fallback;
574
575   close (fd);
576
577   _dbus_string_get_const_data (&bytes, &s);
578
579   seed = * (unsigned int*) s;
580   goto out;
581
582  use_fallback:
583   {
584     long tv_usec;
585
586     fprintf (stderr, "could not open/read /dev/urandom, using current time for seed\n");
587
588     _dbus_get_current_time (NULL, &tv_usec);
589
590     seed = tv_usec;
591   }
592
593  out:
594   _dbus_string_free (&bytes);
595
596   return seed;
597 }
598
599 int
600 main (int    argc,
601       char **argv)
602 {
603   const char *test_data_dir;
604   const char *failure_dir_c;
605   int total_failures_found;
606   
607   if (argc > 1)
608     test_data_dir = argv[1];
609   else
610     {
611       fprintf (stderr, "Must specify a top_srcdir/test/data directory\n");
612       return 1;
613     }
614
615   total_failures_found = 0;
616   total_attempts = 0;
617
618   if (!_dbus_string_init (&failure_dir, _DBUS_INT_MAX))
619     return 1;
620
621   /* so you can leave it overnight safely */
622 #define MAX_FAILURES 1000
623
624   while (total_failures_found < MAX_FAILURES)
625     {
626       unsigned int seed;
627
628       failures_this_iteration = 0;
629
630       seed = get_random_seed ();
631
632       _dbus_string_set_length (&failure_dir, 0);
633
634       if (!_dbus_string_append (&failure_dir, "failures-"))
635         return 1;
636
637       if (!_dbus_string_append_uint (&failure_dir, seed))
638         return 1;
639
640       _dbus_string_get_const_data (&failure_dir, &failure_dir_c);
641
642       if (mkdir (failure_dir_c, 0700) < 0)
643         {
644           if (errno != EEXIST)
645             fprintf (stderr, "didn't mkdir %s: %s\n",
646                      failure_dir_c, strerror (errno));
647         }
648
649       printf ("next seed = %u \ttotal failures %d of %d attempts\n",
650               seed, total_failures_found, total_attempts);
651
652       srand (seed);
653
654       if (!dbus_internal_do_not_use_foreach_message_file (test_data_dir,
655                                                           find_breaks_based_on,
656                                                           NULL))
657         {
658           fprintf (stderr, "fatal error iterating over message files\n");
659           rmdir (failure_dir_c);
660           return 1;
661         }
662
663       printf ("  did %d random mutations: %d %d %d %d %d %d\n",
664               _DBUS_N_ELEMENTS (times_we_did_each_thing),
665               times_we_did_each_thing[0],
666               times_we_did_each_thing[1],
667               times_we_did_each_thing[2],
668               times_we_did_each_thing[3],
669               times_we_did_each_thing[4],
670               times_we_did_each_thing[5]);
671       
672       printf ("Found %d failures with seed %u stored in %s\n",
673               failures_this_iteration, seed, failure_dir_c);
674
675       total_failures_found += failures_this_iteration;
676
677       rmdir (failure_dir_c); /* does nothing if non-empty */
678     }
679
680   return 0;
681 }