|
|
|
@ -1,6 +1,8 @@
|
|
|
|
|
Siddhesh Poyarekar <siddhesh@redhat.com>
|
|
|
|
|
Siddhesh Poyarekar <siddhesh@redhat.com>
|
|
|
|
|
|
|
|
|
|
[BZ #14547]
|
|
|
|
|
* string/tst-strcoll-overflow.c: New test case.
|
|
|
|
|
* string/Makefile (xtests): Add tst-strcoll-overflow.
|
|
|
|
|
* string/strcoll_l.c (STRCOLL): Skip allocating memory for
|
|
|
|
|
cache if string sizes may cause integer overflow.
|
|
|
|
|
|
|
|
|
@ -12,18 +14,30 @@ Siddhesh Poyarekar <siddhesh@redhat.com>
|
|
|
|
|
(STRCOLL): Use get_next_seq_nocache and do_compare_nocache
|
|
|
|
|
when malloc fails.
|
|
|
|
|
|
|
|
|
|
[BZ #14547]
|
|
|
|
|
* string/strcoll_l.c (coll_seq): New structure.
|
|
|
|
|
(get_next_seq_cached): New function.
|
|
|
|
|
(get_next_seq): New function.
|
|
|
|
|
(do_compare): New function.
|
|
|
|
|
(STRCOLL): Use GNU style definition. Simplify implementation
|
|
|
|
|
by using get_next_seq, get_next_seq_cached and do_copare.
|
|
|
|
|
by using get_next_seq, get_next_seq_cached and do_compare.
|
|
|
|
|
|
|
|
|
|
Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
Index: glibc-2.18/string/Makefile
|
|
|
|
|
===================================================================
|
|
|
|
|
--- glibc-2.17.90.orig/string/strcoll_l.c
|
|
|
|
|
+++ glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
--- glibc-2.18.orig/string/Makefile
|
|
|
|
|
+++ glibc-2.18/string/Makefile
|
|
|
|
|
@@ -57,6 +57,8 @@ tests := tester inl-tester noinl-tester
|
|
|
|
|
tests-ifunc := $(strop-tests:%=test-%-ifunc)
|
|
|
|
|
tests += $(tests-ifunc)
|
|
|
|
|
|
|
|
|
|
+xtests = tst-strcoll-overflow
|
|
|
|
|
+
|
|
|
|
|
include ../Rules
|
|
|
|
|
|
|
|
|
|
tester-ENV = LANGUAGE=C
|
|
|
|
|
Index: glibc-2.18/string/strcoll_l.c
|
|
|
|
|
===================================================================
|
|
|
|
|
--- glibc-2.18.orig/string/strcoll_l.c
|
|
|
|
|
+++ glibc-2.18/string/strcoll_l.c
|
|
|
|
|
@@ -41,11 +41,434 @@
|
|
|
|
|
|
|
|
|
|
#include "../locale/localeinfo.h"
|
|
|
|
@ -32,15 +46,15 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+typedef struct
|
|
|
|
|
+{
|
|
|
|
|
+ int len; /* Length of the current sequence. */
|
|
|
|
|
+ int val; /* Position of the sequence relative to the
|
|
|
|
|
+ size_t val; /* Position of the sequence relative to the
|
|
|
|
|
+ previous non-ignored sequence. */
|
|
|
|
|
+ size_t idxnow; /* Current index in sequences. */
|
|
|
|
|
+ size_t idxmax; /* Maximum index in sequences. */
|
|
|
|
|
+ size_t idxcnt; /* Current count of indeces. */
|
|
|
|
|
+ size_t idxcnt; /* Current count of indices. */
|
|
|
|
|
+ size_t backw; /* Current Backward sequence index. */
|
|
|
|
|
+ size_t backw_stop; /* Index where the backward sequences stop. */
|
|
|
|
|
+ const USTRING_TYPE *us; /* The string. */
|
|
|
|
|
+ int32_t *idxarr; /* Array to cache weight indeces. */
|
|
|
|
|
+ int32_t *idxarr; /* Array to cache weight indices. */
|
|
|
|
|
+ unsigned char *rulearr; /* Array to cache rules. */
|
|
|
|
|
+ unsigned char rule; /* Saved rule for the first sequence. */
|
|
|
|
|
+ int32_t idx; /* Index to weight of the current sequence. */
|
|
|
|
@ -50,14 +64,14 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ const USTRING_TYPE *back_us; /* Beginning of the backward sequence. */
|
|
|
|
|
+} coll_seq;
|
|
|
|
|
+
|
|
|
|
|
+/* Get next sequence. The weight indeces are cached, so we don't need to
|
|
|
|
|
+/* Get next sequence. The weight indices are cached, so we don't need to
|
|
|
|
|
+ traverse the string. */
|
|
|
|
|
+static void
|
|
|
|
|
+get_next_seq_cached (coll_seq *seq, int nrules, int pass,
|
|
|
|
|
+ const unsigned char *rulesets,
|
|
|
|
|
+ const USTRING_TYPE *weights)
|
|
|
|
|
+{
|
|
|
|
|
+ int val = seq->val = 0;
|
|
|
|
|
+ size_t val = seq->val = 0;
|
|
|
|
|
+ int len = seq->len;
|
|
|
|
|
+ size_t backw_stop = seq->backw_stop;
|
|
|
|
|
+ size_t backw = seq->backw;
|
|
|
|
@ -72,7 +86,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ ++val;
|
|
|
|
|
+ if (backw_stop != ~0ul)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* The is something pushed. */
|
|
|
|
|
+ /* There is something pushed. */
|
|
|
|
|
+ if (backw == backw_stop)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* The last pushed character was handled. Continue
|
|
|
|
@ -84,7 +98,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ /* Nothing anymore. The backward sequence
|
|
|
|
|
+ /* Nothing any more. The backward sequence
|
|
|
|
|
+ ended with the last sequence in the string. */
|
|
|
|
|
+ idxnow = ~0ul;
|
|
|
|
|
+ break;
|
|
|
|
@ -110,7 +124,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ {
|
|
|
|
|
+ /* No sequence at all or just one. */
|
|
|
|
|
+ if (idxcnt == idxmax)
|
|
|
|
|
+ /* Note that seq1len is still zero. */
|
|
|
|
|
+ /* Note that LEN is still zero. */
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ backw_stop = ~0ul;
|
|
|
|
@ -139,7 +153,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ const USTRING_TYPE *extra, const int32_t *indirect)
|
|
|
|
|
+{
|
|
|
|
|
+#include WEIGHT_H
|
|
|
|
|
+ int val = seq->val = 0;
|
|
|
|
|
+ size_t val = seq->val = 0;
|
|
|
|
|
+ int len = seq->len;
|
|
|
|
|
+ size_t backw_stop = seq->backw_stop;
|
|
|
|
|
+ size_t backw = seq->backw;
|
|
|
|
@ -155,7 +169,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ ++val;
|
|
|
|
|
+ if (backw_stop != ~0ul)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* The is something pushed. */
|
|
|
|
|
+ /* There is something pushed. */
|
|
|
|
|
+ if (backw == backw_stop)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* The last pushed character was handled. Continue
|
|
|
|
@ -166,8 +180,8 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ backw_stop = ~0ul;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ /* Nothing anymore. The backward sequence ended with
|
|
|
|
|
+ the last sequence in the string. Note that seq2len
|
|
|
|
|
+ /* Nothing any more. The backward sequence ended with
|
|
|
|
|
+ the last sequence in the string. Note that LEN
|
|
|
|
|
+ is still zero. */
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
@ -196,7 +210,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ {
|
|
|
|
|
+ /* No sequence at all or just one. */
|
|
|
|
|
+ if (idxcnt == idxmax || backw_stop > idxcnt)
|
|
|
|
|
+ /* Note that seq1len is still zero. */
|
|
|
|
|
+ /* Note that LEN is still zero. */
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ backw_stop = ~0ul;
|
|
|
|
@ -229,7 +243,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ int pass)
|
|
|
|
|
+{
|
|
|
|
|
+#include WEIGHT_H
|
|
|
|
|
+ int val = seq->val = 0;
|
|
|
|
|
+ size_t val = seq->val = 0;
|
|
|
|
|
+ int len = seq->len;
|
|
|
|
|
+ size_t backw_stop = seq->backw_stop;
|
|
|
|
|
+ size_t backw = seq->backw;
|
|
|
|
@ -243,7 +257,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ ++val;
|
|
|
|
|
+ if (backw_stop != ~0ul)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* The is something pushed. */
|
|
|
|
|
+ /* There is something pushed. */
|
|
|
|
|
+ if (backw == backw_stop)
|
|
|
|
|
+ {
|
|
|
|
|
+ /* The last pushed character was handled. Continue
|
|
|
|
@ -267,7 +281,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ /* XXX Traverse BACKW sequences from the beginning of
|
|
|
|
|
+ BACKW_STOP to get the next sequence. Is ther a quicker way
|
|
|
|
|
+ to do this? */
|
|
|
|
|
+ int i = backw_stop;
|
|
|
|
|
+ size_t i = backw_stop;
|
|
|
|
|
+ us = seq->back_us;
|
|
|
|
|
+ while (i < backw)
|
|
|
|
|
+ {
|
|
|
|
@ -333,7 +347,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ len = weights[idx++];
|
|
|
|
|
+ /* Skip over indeces of previous levels. */
|
|
|
|
|
+ /* Skip over indices of previous levels. */
|
|
|
|
|
+ for (int i = 0; i < pass; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ idx += len;
|
|
|
|
@ -361,8 +375,8 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+{
|
|
|
|
|
+ int seq1len = seq1->len;
|
|
|
|
|
+ int seq2len = seq2->len;
|
|
|
|
|
+ int val1 = seq1->val;
|
|
|
|
|
+ int val2 = seq2->val;
|
|
|
|
|
+ size_t val1 = seq1->val;
|
|
|
|
|
+ size_t val2 = seq2->val;
|
|
|
|
|
+ int idx1 = seq1->idx;
|
|
|
|
|
+ int idx2 = seq2->idx;
|
|
|
|
|
+ int result = 0;
|
|
|
|
@ -370,7 +384,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ /* Test for position if necessary. */
|
|
|
|
|
+ if (position && val1 != val2)
|
|
|
|
|
+ {
|
|
|
|
|
+ result = val1 - val2;
|
|
|
|
|
+ result = val1 > val2 ? 1 : -1;
|
|
|
|
|
+ goto out;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
@ -411,8 +425,8 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+{
|
|
|
|
|
+ int seq1len = seq1->len;
|
|
|
|
|
+ int seq2len = seq2->len;
|
|
|
|
|
+ int val1 = seq1->val;
|
|
|
|
|
+ int val2 = seq2->val;
|
|
|
|
|
+ size_t val1 = seq1->val;
|
|
|
|
|
+ size_t val2 = seq2->val;
|
|
|
|
|
+ int32_t *idx1arr = seq1->idxarr;
|
|
|
|
|
+ int32_t *idx2arr = seq2->idxarr;
|
|
|
|
|
+ int idx1now = seq1->idxnow;
|
|
|
|
@ -422,7 +436,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ /* Test for position if necessary. */
|
|
|
|
|
+ if (position && val1 != val2)
|
|
|
|
|
+ {
|
|
|
|
|
+ result = val1 - val2;
|
|
|
|
|
+ result = val1 > val2 ? 1 : -1;
|
|
|
|
|
+ goto out;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
@ -528,7 +542,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
/* Perform the first pass over the string and while doing this find
|
|
|
|
|
and store the weights for each character. Since we want this to
|
|
|
|
|
be as fast as possible we are using `alloca' to store the temporary
|
|
|
|
|
@@ -127,411 +516,124 @@ STRCOLL (s1, s2, l)
|
|
|
|
|
@@ -127,411 +516,122 @@ STRCOLL (s1, s2, l)
|
|
|
|
|
|
|
|
|
|
Please note that the localedef programs makes sure that `position'
|
|
|
|
|
is not used at the first level. */
|
|
|
|
@ -557,7 +571,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
- rule1arr = (unsigned char *) alloca (s1len);
|
|
|
|
|
- rule2arr = (unsigned char *) alloca (s2len);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- idx1cnt = 0;
|
|
|
|
|
- idx2cnt = 0;
|
|
|
|
|
- idx1max = 0;
|
|
|
|
@ -575,10 +589,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
- {
|
|
|
|
|
- val1 = 0;
|
|
|
|
|
- val2 = 0;
|
|
|
|
|
+ coll_seq seq1, seq2;
|
|
|
|
|
+ bool use_malloc = false;
|
|
|
|
|
+ int result = 0;
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- /* Get the next non-IGNOREd element for string `s1'. */
|
|
|
|
|
- if (seq1len == 0)
|
|
|
|
|
- do
|
|
|
|
@ -723,8 +734,6 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
- result = val1 - val2;
|
|
|
|
|
- goto free_and_return;
|
|
|
|
|
- }
|
|
|
|
|
+ memset (&seq1, 0, sizeof (seq1));
|
|
|
|
|
+ seq2 = seq1;
|
|
|
|
|
|
|
|
|
|
- /* Compare the two sequences. */
|
|
|
|
|
- do
|
|
|
|
@ -735,26 +744,33 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
- result = weights[idx1arr[idx1now]] - weights[idx2arr[idx2now]];
|
|
|
|
|
- goto free_and_return;
|
|
|
|
|
- }
|
|
|
|
|
+ size_t size_max = SIZE_MAX / (sizeof (int32_t) + 1);
|
|
|
|
|
+ coll_seq seq1, seq2;
|
|
|
|
|
+ bool use_malloc = false;
|
|
|
|
|
+ int result = 0;
|
|
|
|
|
|
|
|
|
|
- /* Increment the offsets. */
|
|
|
|
|
- ++idx1arr[idx1now];
|
|
|
|
|
- ++idx2arr[idx2now];
|
|
|
|
|
+ /* If the strings are long enough to cause overflow in the size request, then
|
|
|
|
|
+ skip the allocation and proceed with the non-cached routines. */
|
|
|
|
|
+ if (MIN (s1len, s2len) > size_max
|
|
|
|
|
+ || MAX (s1len, s2len) > size_max - MIN (s1len, s2len))
|
|
|
|
|
+ goto begin_collate;
|
|
|
|
|
+ memset (&seq1, 0, sizeof (seq1));
|
|
|
|
|
+ seq2 = seq1;
|
|
|
|
|
|
|
|
|
|
- --seq1len;
|
|
|
|
|
- --seq2len;
|
|
|
|
|
- }
|
|
|
|
|
- while (seq1len > 0 && seq2len > 0);
|
|
|
|
|
+ if (! __libc_use_alloca ((s1len + s2len) * (sizeof (int32_t) + 1)))
|
|
|
|
|
+ {
|
|
|
|
|
+ seq1.idxarr = (int32_t *) malloc ((s1len + s2len) * (sizeof (int32_t) + 1));
|
|
|
|
|
+ size_t size_max = SIZE_MAX / (sizeof (int32_t) + 1);
|
|
|
|
|
|
|
|
|
|
- if (position && seq1len != seq2len)
|
|
|
|
|
+ if (MIN (s1len, s2len) > size_max
|
|
|
|
|
+ || MAX (s1len, s2len) > size_max - MIN (s1len, s2len))
|
|
|
|
|
+ {
|
|
|
|
|
+ /* If the strings are long enough to cause overflow in the size request,
|
|
|
|
|
+ then skip the allocation and proceed with the non-cached routines. */
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (! __libc_use_alloca ((s1len + s2len) * (sizeof (int32_t) + 1)))
|
|
|
|
|
+ {
|
|
|
|
|
+ seq1.idxarr = (int32_t *) malloc ((s1len + s2len) * (sizeof (int32_t) + 1));
|
|
|
|
|
+
|
|
|
|
|
+ /* If we failed to allocate memory, we leave everything as NULL so that
|
|
|
|
|
+ we use the nocache version of traversal and comparison functions. */
|
|
|
|
|
+ if (seq1.idxarr != NULL)
|
|
|
|
@ -775,13 +791,11 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ seq2.rulearr = (unsigned char *) alloca (s2len);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int rule;
|
|
|
|
|
+ int rule = 0;
|
|
|
|
|
|
|
|
|
|
- /* Now the remaining passes over the weights. We now use the
|
|
|
|
|
- indeces we found before. */
|
|
|
|
|
- for (pass = 1; pass < nrules; ++pass)
|
|
|
|
|
+ begin_collate:
|
|
|
|
|
+ rule = 0;
|
|
|
|
|
+ /* Cache values in the first pass and if needed, use them in subsequent
|
|
|
|
|
+ passes. */
|
|
|
|
|
+ for (int pass = 0; pass < nrules; ++pass)
|
|
|
|
@ -796,7 +810,7 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
+ seq2.backw = ~0ul;
|
|
|
|
|
+
|
|
|
|
|
+ /* We need the elements of the strings as unsigned values since they
|
|
|
|
|
+ are used as indeces. */
|
|
|
|
|
+ are used as indices. */
|
|
|
|
|
+ seq1.us = (const USTRING_TYPE *) s1;
|
|
|
|
|
+ seq2.us = (const USTRING_TYPE *) s2;
|
|
|
|
|
+
|
|
|
|
@ -1024,3 +1038,69 @@ Index: glibc-2.17.90/string/strcoll_l.c
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
Index: glibc-2.18/string/tst-strcoll-overflow.c
|
|
|
|
|
===================================================================
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ glibc-2.18/string/tst-strcoll-overflow.c
|
|
|
|
|
@@ -0,0 +1,61 @@
|
|
|
|
|
+/* Copyright (C) 2013 Free Software Foundation, Inc.
|
|
|
|
|
+ This file is part of the GNU C Library.
|
|
|
|
|
+
|
|
|
|
|
+ The GNU C Library is free software; you can redistribute it and/or
|
|
|
|
|
+ modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
+ License as published by the Free Software Foundation; either
|
|
|
|
|
+ version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
+
|
|
|
|
|
+ The GNU C Library is distributed in the hope that it will be useful,
|
|
|
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
+ Lesser General Public License for more details.
|
|
|
|
|
+
|
|
|
|
|
+ You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
+ License along with the GNU C Library; if not, see
|
|
|
|
|
+ <http://www.gnu.org/licenses/>. */
|
|
|
|
|
+
|
|
|
|
|
+#include <locale.h>
|
|
|
|
|
+#include <stdio.h>
|
|
|
|
|
+#include <stdint.h>
|
|
|
|
|
+#include <stdlib.h>
|
|
|
|
|
+#include <string.h>
|
|
|
|
|
+
|
|
|
|
|
+/* Verify that strcoll does not crash for large strings for which it cannot
|
|
|
|
|
+ cache weight lookup results. The size is large enough to cause integer
|
|
|
|
|
+ overflows on 32-bit as well as buffer overflows on 64-bit. The test should
|
|
|
|
|
+ work reasonably reliably when overcommit is disabled, but it obviously
|
|
|
|
|
+ depends on how much memory the system has. There's a limitation to this
|
|
|
|
|
+ test in that it does not run to completion. Actually collating such a
|
|
|
|
|
+ large string can take days and we can't have xcheck running that long. For
|
|
|
|
|
+ that reason, we run the test for about 5 minutes and then assume that
|
|
|
|
|
+ everything is fine if there are no crashes. */
|
|
|
|
|
+#define SIZE 0x40000000ul
|
|
|
|
|
+
|
|
|
|
|
+int
|
|
|
|
|
+do_test (void)
|
|
|
|
|
+{
|
|
|
|
|
+ if (setlocale (LC_COLLATE, "en_GB.UTF-8") == NULL)
|
|
|
|
|
+ {
|
|
|
|
|
+ puts ("setlocale failed, cannot test for overflow");
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ char *p = malloc (SIZE);
|
|
|
|
|
+
|
|
|
|
|
+ if (p == NULL)
|
|
|
|
|
+ {
|
|
|
|
|
+ puts ("could not allocate memory");
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ memset (p, 'x', SIZE - 1);
|
|
|
|
|
+ p[SIZE - 1] = 0;
|
|
|
|
|
+ printf ("%d\n", strcoll (p, p));
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#define TIMEOUT 300
|
|
|
|
|
+#define EXPECTED_SIGNAL SIGALRM
|
|
|
|
|
+#define TEST_FUNCTION do_test ()
|
|
|
|
|
+#include "../test-skeleton.c"
|
|
|
|
|