1/* xztest.c -- Test for libbacktrace LZMA decoder.
2   Copyright (C) 2020-2022 Free Software Foundation, Inc.
3   Written by Ian Lance Taylor, Google.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are
7met:
8
9    (1) Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11
12    (2) Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in
14    the documentation and/or other materials provided with the
15    distribution.
16
17    (3) The name of the author may not be used to
18    endorse or promote products derived from this software without
19    specific prior written permission.
20
21THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
25INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31POSSIBILITY OF SUCH DAMAGE.  */
32
33#include "config.h"
34
35#include <errno.h>
36#include <limits.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <time.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43
44#ifdef HAVE_LIBLZMA
45#include <lzma.h>
46#endif
47
48#include "backtrace.h"
49#include "backtrace-supported.h"
50
51#include "internal.h"
52#include "testlib.h"
53
54#ifndef HAVE_CLOCK_GETTIME
55
56typedef int xclockid_t;
57
58static int
59xclock_gettime (xclockid_t id ATTRIBUTE_UNUSED,
60		struct timespec *ts ATTRIBUTE_UNUSED)
61{
62  errno = EINVAL;
63  return -1;
64}
65
66#define clockid_t xclockid_t
67#define clock_gettime xclock_gettime
68#undef CLOCK_REALTIME
69#define CLOCK_REALTIME 0
70
71#endif /* !defined(HAVE_CLOCK_GETTIME) */
72
73#ifdef CLOCK_PROCESS_CPUTIME_ID
74#define LIBLZMA_CLOCK_GETTIME_ARG CLOCK_PROCESS_CPUTIME_ID
75#else
76#define LIBLZMA_CLOCK_GETTIME_ARG CLOCK_REALTIME
77#endif
78
79/* Some tests for the local lzma inflation code.  */
80
81struct lzma_test
82{
83  const char *name;
84  const char *uncompressed;
85  size_t uncompressed_len;
86  const char *compressed;
87  size_t compressed_len;
88};
89
90/* Error callback.  */
91
92static void
93error_callback_compress (void *vdata ATTRIBUTE_UNUSED, const char *msg,
94			 int errnum)
95{
96  fprintf (stderr, "%s", msg);
97  if (errnum > 0)
98    fprintf (stderr, ": %s", strerror (errnum));
99  fprintf (stderr, "\n");
100  exit (EXIT_FAILURE);
101}
102
103static const struct lzma_test tests[] =
104{
105  {
106    "empty",
107    "",
108    0,
109    ("\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x00\x00\x00\x00"
110     "\x1c\xdf\x44\x21\x1f\xb6\xf3\x7d\x01\x00\x00\x00\x00\x04\x59\x5a"),
111    32,
112  },
113  {
114    "hello",
115    "hello, world\n",
116    0,
117    ("\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01"
118     "\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x0c\x68\x65\x6c\x6c\x6f"
119     "\x2c\x20\x77\x6f\x72\x6c\x64\x0a\x00\x00\x00\x00\x7b\x46\x5a\x81"
120     "\xc9\x12\xb8\xea\x00\x01\x25\x0d\x71\x19\xc4\xb6\x1f\xb6\xf3\x7d"
121     "\x01\x00\x00\x00\x00\x04\x59\x5a"),
122    72,
123  },
124  {
125    "goodbye",
126    "goodbye, world",
127    0,
128    ("\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01"
129     "\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x0d\x67\x6f\x6f\x64\x62"
130     "\x79\x65\x2c\x20\x77\x6f\x72\x6c\x64\x00\x00\x00\xf6\xf8\xa3\x33"
131     "\x8c\x4e\xc9\x68\x00\x01\x26\x0e\x08\x1b\xe0\x04\x1f\xb6\xf3\x7d"
132     "\x01\x00\x00\x00\x00\x04\x59\x5a"),
133    72,
134  },
135};
136
137/* Test the hand coded samples.  */
138
139static void
140test_samples (struct backtrace_state *state)
141{
142  size_t i;
143
144  for (i = 0; i < sizeof tests / sizeof tests[0]; ++i)
145    {
146      unsigned char *uncompressed;
147      size_t uncompressed_len;
148
149      uncompressed = NULL;
150      uncompressed_len = 0;
151      if (!backtrace_uncompress_lzma (state,
152				      ((const unsigned char *)
153				       tests[i].compressed),
154				      tests[i].compressed_len,
155				      error_callback_compress, NULL,
156				      &uncompressed, &uncompressed_len))
157	{
158	  fprintf (stderr, "test %s: uncompress failed\n", tests[i].name);
159	  ++failures;
160	}
161      else
162	{
163	  size_t v;
164
165	  v = tests[i].uncompressed_len;
166	  if (v == 0)
167	    v = strlen (tests[i].uncompressed);
168	  if (uncompressed_len != v)
169	    {
170	      fprintf (stderr,
171		       "test %s: got uncompressed length %zu, want %zu\n",
172		       tests[i].name, uncompressed_len, v);
173	      ++failures;
174	    }
175	  else if (v > 0 && memcmp (tests[i].uncompressed, uncompressed, v) != 0)
176	    {
177	      size_t j;
178
179	      fprintf (stderr, "test %s: uncompressed data mismatch\n",
180		       tests[i].name);
181	      for (j = 0; j < v; ++j)
182		if (tests[i].uncompressed[j] != uncompressed[j])
183		  fprintf (stderr, "  %zu: got %#x want %#x\n", j,
184			   uncompressed[j], tests[i].uncompressed[j]);
185	      ++failures;
186	    }
187	  else
188	    printf ("PASS: lzma %s\n", tests[i].name);
189
190	  backtrace_free (state, uncompressed, uncompressed_len,
191			  error_callback_compress, NULL);
192	}
193    }
194}
195
196#if HAVE_LIBLZMA
197
198/* Given a set of TRIALS timings, discard the lowest and highest
199   values and return the mean average of the rest.  */
200
201static size_t
202average_time (const size_t *times, size_t trials)
203{
204  size_t imax;
205  size_t max;
206  size_t imin;
207  size_t min;
208  size_t i;
209  size_t sum;
210
211  imin = 0;
212  imax = 0;
213  min = times[0];
214  max = times[0];
215  for (i = 1; i < trials; ++i)
216    {
217      if (times[i] < min)
218	{
219	  imin = i;
220	  min = times[i];
221	}
222      if (times[i] > max)
223	{
224	  imax = i;
225	  max = times[i];
226	}
227    }
228
229  sum = 0;
230  for (i = 0; i < trials; ++i)
231    {
232      if (i != imax && i != imin)
233	sum += times[i];
234    }
235  return sum / (trials - 2);
236}
237
238#endif
239
240/* Test a larger text, if available.  */
241
242static void
243test_large (struct backtrace_state *state ATTRIBUTE_UNUSED)
244{
245#if HAVE_LIBLZMA
246  unsigned char *orig_buf;
247  size_t orig_bufsize;
248  size_t i;
249  lzma_stream initial_stream = LZMA_STREAM_INIT;
250  lzma_stream stream;
251  unsigned char *compressed_buf;
252  size_t compressed_bufsize;
253  unsigned char *uncompressed_buf;
254  size_t uncompressed_bufsize;
255  unsigned char *spare_buf;
256  int r;
257  clockid_t cid;
258  struct timespec ts1;
259  struct timespec ts2;
260  size_t ctime;
261  size_t ztime;
262  const size_t trials = 16;
263  size_t ctimes[16];
264  size_t ztimes[16];
265  static const char * const names[] = {
266    "Isaac.Newton-Opticks.txt",
267    "../libgo/go/testdata/Isaac.Newton-Opticks.txt",
268  };
269
270  orig_buf = NULL;
271  orig_bufsize = 0;
272  uncompressed_buf = NULL;
273  compressed_buf = NULL;
274
275  for (i = 0; i < sizeof names / sizeof names[0]; ++i)
276    {
277      size_t len;
278      char *namebuf;
279      FILE *e;
280      struct stat st;
281      char *rbuf;
282      size_t got;
283
284      len = strlen (SRCDIR) + strlen (names[i]) + 2;
285      namebuf = malloc (len);
286      if (namebuf == NULL)
287	{
288	  perror ("malloc");
289	  goto fail;
290	}
291      snprintf (namebuf, len, "%s/%s", SRCDIR, names[i]);
292      e = fopen (namebuf, "r");
293      free (namebuf);
294      if (e == NULL)
295	continue;
296      if (fstat (fileno (e), &st) < 0)
297	{
298	  perror ("fstat");
299	  fclose (e);
300	  continue;
301	}
302      rbuf = malloc (st.st_size);
303      if (rbuf == NULL)
304	{
305	  perror ("malloc");
306	  goto fail;
307	}
308      got = fread (rbuf, 1, st.st_size, e);
309      fclose (e);
310      if (got > 0)
311	{
312	  orig_buf = (unsigned char *) rbuf;
313	  orig_bufsize = got;
314	  break;
315	}
316      free (rbuf);
317    }
318
319  if (orig_buf == NULL)
320    {
321      /* We couldn't find an input file.  */
322      printf ("UNSUPPORTED: lzma large\n");
323      return;
324    }
325
326  stream = initial_stream;
327  r =  lzma_easy_encoder (&stream, 6, LZMA_CHECK_CRC32);
328  if (r != LZMA_OK)
329    {
330      fprintf (stderr, "lzma_easy_encoder failed: %d\n", r);
331      goto fail;
332    }
333
334  compressed_bufsize = orig_bufsize + 100;
335  compressed_buf = malloc (compressed_bufsize);
336  if (compressed_buf == NULL)
337    {
338      perror ("malloc");
339      goto fail;
340    }
341
342  stream.next_in = orig_buf;
343  stream.avail_in = orig_bufsize;
344  stream.next_out = compressed_buf;
345  stream.avail_out = compressed_bufsize;
346
347  do
348    {
349      r = lzma_code (&stream, LZMA_FINISH);
350      if (r != LZMA_OK && r != LZMA_STREAM_END)
351	{
352	  fprintf (stderr, "lzma_code failed: %d\n", r);
353	  goto fail;
354	}
355    }
356  while (r != LZMA_STREAM_END);
357
358  compressed_bufsize = stream.total_out;
359
360  if (!backtrace_uncompress_lzma (state, (unsigned char *) compressed_buf,
361				  compressed_bufsize,
362				  error_callback_compress, NULL,
363				  &uncompressed_buf, &uncompressed_bufsize))
364    {
365      fprintf (stderr, "lzma large: backtrace_uncompress_lzma failed\n");
366      goto fail;
367    }
368
369  if (uncompressed_bufsize != orig_bufsize)
370    {
371      fprintf (stderr,
372	       "lzma large: got uncompressed length %zu, want %zu\n",
373	       uncompressed_bufsize, orig_bufsize);
374      goto fail;
375    }
376
377  if (memcmp (uncompressed_buf, orig_buf, uncompressed_bufsize) != 0)
378    {
379      fprintf (stderr, "lzma large: uncompressed data mismatch\n");
380      goto fail;
381    }
382
383  printf ("PASS: lzma large\n");
384
385  spare_buf = malloc (orig_bufsize);
386  if (spare_buf == NULL)
387    {
388      perror ("malloc");
389      goto fail;
390    }
391
392  for (i = 0; i < trials; ++i)
393    {
394      cid = LIBLZMA_CLOCK_GETTIME_ARG;
395      if (clock_gettime (cid, &ts1) < 0)
396	{
397	  if (errno == EINVAL)
398	    return;
399	  perror ("clock_gettime");
400	  return;
401	}
402
403      if (!backtrace_uncompress_lzma (state,
404				      (unsigned char *) compressed_buf,
405				      compressed_bufsize,
406				      error_callback_compress, NULL,
407				      &uncompressed_buf,
408				      &uncompressed_bufsize))
409	{
410	  fprintf (stderr,
411		   ("lzma large: "
412		    "benchmark backtrace_uncompress_lzma failed\n"));
413	  return;
414	}
415
416      if (clock_gettime (cid, &ts2) < 0)
417	{
418	  perror ("clock_gettime");
419	  return;
420	}
421
422      ctime = (ts2.tv_sec - ts1.tv_sec) * 1000000000;
423      ctime += ts2.tv_nsec - ts1.tv_nsec;
424      ctimes[i] = ctime;
425
426      stream = initial_stream;
427
428      r = lzma_auto_decoder (&stream, UINT64_MAX, 0);
429      if (r != LZMA_OK)
430	{
431	  fprintf (stderr, "lzma_stream_decoder failed: %d\n", r);
432	  goto fail;
433	}
434
435      stream.next_in = compressed_buf;
436      stream.avail_in = compressed_bufsize;
437      stream.next_out = spare_buf;
438      stream.avail_out = orig_bufsize;
439
440      if (clock_gettime (cid, &ts1) < 0)
441	{
442	  perror("clock_gettime");
443	  return;
444	}
445
446      do
447	{
448	  r = lzma_code (&stream, LZMA_FINISH);
449	  if (r != LZMA_OK && r != LZMA_STREAM_END)
450	    {
451	      fprintf (stderr, "lzma_code failed: %d\n", r);
452	      goto fail;
453	    }
454	}
455      while (r != LZMA_STREAM_END);
456
457      if (clock_gettime (cid, &ts2) < 0)
458	{
459	  perror ("clock_gettime");
460	  return;
461	}
462
463      ztime = (ts2.tv_sec - ts1.tv_sec) * 1000000000;
464      ztime += ts2.tv_nsec - ts1.tv_nsec;
465      ztimes[i] = ztime;
466    }
467
468  /* Toss the highest and lowest times and average the rest.  */
469  ctime = average_time (ctimes, trials);
470  ztime = average_time (ztimes, trials);
471
472  printf ("backtrace: %zu ns\n", ctime);
473  printf ("liblzma  : %zu ns\n", ztime);
474  printf ("ratio    : %g\n", (double) ztime / (double) ctime);
475
476  return;
477
478 fail:
479  printf ("FAIL: lzma large\n");
480  ++failures;
481
482  if (orig_buf != NULL)
483    free (orig_buf);
484  if (compressed_buf != NULL)
485    free (compressed_buf);
486  if (uncompressed_buf != NULL)
487    free (uncompressed_buf);
488
489#else /* !HAVE_LIBLZMA */
490
491 printf ("UNSUPPORTED: lzma large\n");
492
493#endif /* !HAVE_LIBLZMA */
494}
495
496int
497main (int argc ATTRIBUTE_UNUSED, char **argv)
498{
499  struct backtrace_state *state;
500
501  state = backtrace_create_state (argv[0], BACKTRACE_SUPPORTS_THREADS,
502				  error_callback_create, NULL);
503
504  test_samples (state);
505  test_large (state);
506
507  exit (failures != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
508}
509