pcretest.c revision 4d553781254e46f1dfc8d86b79667a74fb8a3eb5
/*************************************************
* PCRE testing program *
*************************************************/
/* This program was hacked up as a tester for PCRE. I really should have
written it more tidily in the first place. Will I ever learn? It has grown and
been extended and consequently is now rather untidy in places.
-----------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
* Neither the name of the University of Cambridge nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------------------------------------------
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <locale.h>
#include <errno.h>
/* We need the internal info for displaying the results of pcre_study(). Also
for getting the opcodes for showing compiled code. */
#define PCRE_SPY /* For Win32 build, import data, not export */
#include "internal.h"
/* It is possible to compile this test program without including support for
testing the POSIX interface, though this is not available via the standard
Makefile. */
#if !defined NOPOSIX
#include "pcreposix.h"
#endif
#ifndef CLOCKS_PER_SEC
#ifdef CLK_TCK
#define CLOCKS_PER_SEC CLK_TCK
#else
#define CLOCKS_PER_SEC 100
#endif
#endif
#define LOOPREPEAT 500000
#define BUFFER_SIZE 30000
#define PBUFFER_SIZE BUFFER_SIZE
#define DBUFFER_SIZE BUFFER_SIZE
static int log_store = 0;
static int callout_count;
static int callout_extra;
static int callout_fail_count;
static int callout_fail_id;
static int first_callout;
static int show_malloc;
static int use_utf8;
static size_t gotten_store;
static const int utf8_table1[] = {
0x0000007f, 0x000007ff, 0x0000ffff, 0x001fffff, 0x03ffffff, 0x7fffffff};
static const int utf8_table2[] = {
0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc};
static const int utf8_table3[] = {
0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01};
/*************************************************
* Print compiled regex *
*************************************************/
/* The code for doing this is held in a separate file that is also included in
pcre.c when it is compiled with the debug switch. It defines a function called
print_internals(), which uses a table of opcode lengths defined by the macro
OP_LENGTHS, whose name must be OP_lengths. It also uses a table that translates
Unicode property names to numbers; this is kept in a separate file. */
#include "ucp.h"
#include "ucptypetable.c"
#include "printint.c"
/*************************************************
* Read number from string *
*************************************************/
/* We don't use strtoul() because SunOS4 doesn't have it. Rather than mess
around with conditional compilation, just do the job by hand. It is only used
for unpicking the -o argument, so just keep it simple.
Arguments:
str string to be converted
endptr where to put the end pointer
Returns: the unsigned long
*/
static int
{
int result = 0;
return(result);
}
/*************************************************
* Convert character value to UTF-8 *
*************************************************/
/* This function takes an integer value in the range 0 - 0x7fffffff
and encodes it as a UTF-8 character in 0 to 6 bytes.
Arguments:
cvalue the character value
buffer pointer to buffer for result - at least 6 bytes long
Returns: number of characters placed in the buffer
-1 if input character is negative
0 if input character is positive but too big (only when
int is longer than 32 bits)
*/
static int
{
register int i, j;
for (i = 0; i < sizeof(utf8_table1)/sizeof(int); i++)
if (cvalue <= utf8_table1[i]) break;
if (i >= sizeof(utf8_table1)/sizeof(int)) return 0;
if (cvalue < 0) return -1;
buffer += i;
for (j = i; j > 0; j--)
{
cvalue >>= 6;
}
return i + 1;
}
/*************************************************
* Convert UTF-8 string to value *
*************************************************/
/* This function takes one or more bytes that represents a UTF-8 character,
and returns the value of the character.
Argument:
buffer a pointer to the byte vector
vptr a pointer to an int to receive the value
Returns: > 0 => the number of bytes consumed
-6 to 0 => malformed UTF-8 character at offset = (-return)
*/
static int
{
int c = *buffer++;
int d = c;
int i, j, s;
for (i = -1; i < 6; i++) /* i is number of additional bytes */
{
if ((d & 0x80) == 0) break;
d <<= 1;
}
if (i == 0 || i == 6) return 0; /* invalid UTF-8 */
/* i now has a value in the range 1-5 */
s = 6*i;
d = (c & utf8_table3[i]) << s;
for (j = 0; j < i; j++)
{
c = *buffer++;
if ((c & 0xc0) != 0x80) return -(j+1);
s -= 6;
d |= (c & 0x3f) << s;
}
/* Check that encoding was the correct unique one */
for (j = 0; j < sizeof(utf8_table1)/sizeof(int); j++)
if (d <= utf8_table1[j]) break;
if (j != i) return -(i+1);
/* Valid value */
*vptr = d;
return i+1;
}
/*************************************************
* Print character string *
*************************************************/
/* Character string printing function. Must handle UTF-8 strings in utf8
mode. Yields number of characters printed. If handed a NULL file, just counts
chars without printing. */
{
int c;
int yield = 0;
while (length-- > 0)
{
if (use_utf8)
{
{
p += rc;
if (c < 256 && isprint(c))
{
yield++;
}
else
{
int n;
yield += n;
}
continue;
}
}
/* Not UTF-8, or malformed UTF-8 */
if (isprint(c = *(p++)))
{
yield++;
}
else
{
yield += 4;
}
}
return yield;
}
/*************************************************
* Callout function *
*************************************************/
/* Called from PCRE as a result of the (?C) item. We print out where we are in
the match. Yield zero unless more callouts than the fail count, or the callout
data is not zero. */
{
if (callout_extra)
{
fprintf(f, "Callout %d: last capture = %d\n",
{
if (cb->offset_vector[i] < 0)
else
{
fprintf(f, "\n");
}
}
}
/* Re-print the subject in canonical form, the first time or if giving full
datails. On subsequent calls in the same match, we use pchars just to find the
printed lengths of the substrings. */
/* Always print appropriate indicators, with callout number if not already
shown. For automatic callouts, show the pattern offset. */
{
}
else
{
}
if (post_start > 0)
{
}
first_callout = 0;
{
if (callout_data != 0)
{
return callout_data;
}
}
}
/*************************************************
* Local malloc functions *
*************************************************/
/* Alternative malloc function, to test functionality and show the size of the
compiled re. */
{
gotten_store = size;
if (show_malloc)
return block;
}
{
if (show_malloc)
}
{
if (show_malloc)
return block;
}
static void stack_free(void *block)
{
if (show_malloc)
}
/*************************************************
* Call pcre_fullinfo() *
*************************************************/
/* Get one piece of information from the pcre_fullinfo() function */
{
int rc;
}
/*************************************************
* Byte flipping function *
*************************************************/
static long int
{
}
/*************************************************
* Main Program *
*************************************************/
/* Read lines from named file or stdin and write to named file or stdout; lines
consist of a regular expression, in delimiters and optionally followed by
options, followed by a set of test data, terminated by an empty line. */
{
int options = 0;
int study_options = 0;
int op = 1;
int timeit = 0;
int showinfo = 0;
int showstore = 0;
int size_offsets = 45;
int size_offsets_max;
int *offsets;
#if !defined NOPOSIX
int posix = 0;
#endif
int debug = 0;
int done = 0;
unsigned char *buffer;
unsigned char *dbuffer;
/* Get buffers from malloc() so that Electric Fence will check their misuse
when I am debugging. */
/* The outfile variable is static so that new_malloc can use it. The _setmode()
stuff is some magic that I don't understand, but which apparently does good
things in Windows. It's related to line terminations. */
#endif /* defined(_WIN32) || defined(WIN32) */
/* Scan options */
{
unsigned char *endptr;
showstore = 1;
*endptr == 0))
{
op++;
argc--;
}
#if !defined NOPOSIX
#endif
{
int rc;
printf("Compiled with\n");
exit(0);
}
else
{
printf("Usage: pcretest [-d] [-i] [-o <n>] [-p] [-s] [-t] [<input> [<output>]]\n");
printf(" -C show PCRE compile-time options and exit\n");
printf(" -d debug: show compiled code; implies -i\n"
" -i show information about compiled pattern\n"
" -m output memory used information\n"
" -o <n> set size of offsets vector to <n>\n");
#if !defined NOPOSIX
printf(" -p use POSIX interface\n");
#endif
printf(" -s output store (memory) used information\n"
" -t time compilation and execution\n");
return 1;
}
op++;
argc--;
}
/* Get the store for the offsets vector, and remember what it was */
{
printf("** Failed to get %d bytes of memory for offsets vector\n",
size_offsets_max * sizeof(int));
return 1;
}
/* Sort out the input and output files */
if (argc > 1)
{
{
return 1;
}
}
if (argc > 2)
{
{
return 1;
}
}
/* Set alternative malloc function */
/* Heading line, then prompt for first regex if stdin */
/* Main loop */
while (!done)
{
#if !defined NOPOSIX /* There are still compilers that require no indent */
int do_posix = 0;
#endif
const char *error;
unsigned long int true_size, true_study_size = 0;
int do_study = 0;
int do_G = 0;
int do_g = 0;
int do_showinfo = showinfo;
int do_showrest = 0;
int do_flip = 0;
use_utf8 = 0;
p = buffer;
while (isspace(*p)) p++;
if (*p == 0) continue;
/* See if the pattern is to be loaded pre-compiled from a file. */
{
unsigned long int magic;
FILE *f;
p++;
*pp = 0;
f = fopen((char *)p, "rb");
if (f == NULL)
{
continue;
}
if (magic != MAGIC_NUMBER)
{
{
do_flip = 1;
}
else
{
fclose(f);
continue;
}
}
/* Need to know if UTF-8 for printing data strings */
/* Now see if there is any following study data */
if (true_study_size != 0)
{
{
fclose(f);
continue;
}
}
fclose(f);
goto SHOW_INFO;
}
/* In-line pattern (the usual case). Get the delimiter and seek the end of
the pattern; if is isn't complete, read more. */
delimiter = *p++;
{
goto SKIP_DATA;
}
pp = p;
for(;;)
{
while (*pp != 0)
{
pp++;
}
if (*pp != 0) break;
if (len < 256)
{
goto SKIP_DATA;
}
{
done = 1;
goto CONTINUE;
}
}
/* If the first character after the delimiter is backslash, make
the pattern end with backslash. This is purely to provide a way
of testing for the error message when a pattern ends with backslash. */
/* Terminate the pattern at the delimiter, and save a copy of the pattern
for callouts. */
*pp++ = 0;
/* Look for options after final delimiter */
options = 0;
study_options = 0;
while (*pp != 0)
{
switch (*pp++)
{
#if !defined NOPOSIX
#endif
case 'L':
*ppp = 0;
{
goto SKIP_DATA;
}
tables = pcre_maketables();
break;
case '>':
*pp = 0;
break;
case '\n': case ' ': break;
default:
goto SKIP_DATA;
}
}
/* Handle compiling via the POSIX interface, which doesn't support the
timing, showing, or debugging options, nor the ability to pass over
local character tables. */
#if !defined NOPOSIX
{
int rc;
int cflags = 0;
/* Compilation failed; go back for another re, skipping to blank line
if non-interactive. */
if (rc != 0)
{
goto SKIP_DATA;
}
}
/* Handle compiling via the native interface */
else
#endif /* !defined NOPOSIX */
{
if (timeit)
{
register int i;
for (i = 0; i < LOOPREPEAT; i++)
{
}
(double)CLOCKS_PER_SEC);
}
/* Compilation failed; go back for another re, skipping to blank line
if non-interactive. */
{
{
for (;;)
{
{
done = 1;
goto CONTINUE;
}
if (len == 0) break;
}
}
goto CONTINUE;
}
/* Compilation succeeded; print data if required. There are now two
info-returning functions. The old one has a limited interface and
returns only limited data. Check that it agrees with the newer one. */
if (log_store)
(int)(gotten_store -
sizeof(real_pcre) -
/* Extract the size for possible writing before possibly flipping it,
and remember the store that was got. */
/* If /S was present, study the regexp to generate additional info to
help with the matching. */
if (do_study)
{
if (timeit)
{
register int i;
for (i = 0; i < LOOPREPEAT; i++)
(double)CLOCKS_PER_SEC);
}
}
/* If the 'F' option was present, we flip the bytes of all the integer
fields in the regex data block and the study block. This is to make it
possible to test PCRE's handling of byte-flipped patterns, e.g. those
compiled on a different architecture. */
if (do_flip)
{
sizeof(rre->name_table_offset));
sizeof(rre->name_entry_size));
{
}
}
/* Extract information from the compiled data if required */
if (do_showinfo)
{
unsigned long int get_options, all_options;
int nameentrysize, namecount;
if (do_debug)
{
}
"Error %d from pcre_info()\n", count);
else
{
"Count disagreement: pcre_fullinfo=%d pcre_info=%d\n", count,
"First char disagreement: pcre_fullinfo=%d pcre_info=%d\n",
"Options disagreement: pcre_fullinfo=%ld pcre_info=%d\n",
}
"Size disagreement: pcre_fullinfo=%d call to malloc for %d\n",
if (backrefmax > 0)
if (namecount > 0)
{
while (namecount-- > 0)
{
}
}
/* The NOPARTIAL bit is a private bit in the options, so we have
to fish it out via out back door */
if (do_flip)
{
}
if ((all_options & PCRE_NOPARTIAL) != 0)
if (first_char == -1)
{
}
else if (first_char < 0)
{
}
else
{
"" : " (caseless)";
else
}
if (need_char < 0)
{
}
else
{
"" : " (caseless)";
else
}
/* Don't output study size; at present it is in any case a fixed
value, but it varies, depending on the computer architecture, and
so messes up the test suite. (And with the /F option, it might be
flipped.) */
if (do_study)
{
else
{
if (start_bits == NULL)
else
{
int i;
int c = 24;
for (i = 0; i < 256; i++)
{
{
if (c > 75)
{
c = 2;
}
if (isprint(i) && i != ' ')
{
c += 2;
}
else
{
c += 5;
}
}
}
}
}
}
}
/* If the '>' option was present, we write out the regex to a file, and
that is all. The first 8 bytes of the file are the regex length and then
the study length, in big-endian order. */
{
if (f == NULL)
{
}
else
{
{
}
else
{
{
{
}
}
}
fclose(f);
}
continue; /* With next regex */
}
} /* End of non-POSIX compile */
/* Read data lines and test them */
for (;;)
{
unsigned char *q;
int *use_offsets = offsets;
int use_size_offsets = size_offsets;
int callout_data = 0;
int callout_data_set = 0;
int count, c;
int copystrings = 0;
int find_match_limit = 0;
int getstrings = 0;
int getlist = 0;
int gmatched = 0;
int start_offset = 0;
int g_notempty = 0;
options = 0;
first_callout = 1;
callout_extra = 0;
callout_count = 0;
callout_fail_count = 999999;
callout_fail_id = -1;
show_malloc = 0;
{
done = 1;
goto CONTINUE;
}
if (len == 0) break;
p = buffer;
while (isspace(*p)) p++;
q = dbuffer;
while ((c = *p++) != 0)
{
int i = 0;
int n = 0;
if (c == '\\') switch ((c = *p++))
{
case 'a': c = 7; break;
case 'b': c = '\b'; break;
case 'e': c = 27; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c -= '0';
c = c * 8 + *p++ - '0';
break;
case 'x':
/* Handle \x{..} specially - new Perl thing for utf8 */
if (*p == '{')
{
unsigned char *pt = p;
c = 0;
if (*pt == '}')
{
unsigned char buff8[8];
p = pt + 1;
break;
}
/* Not correct form; fall through */
}
/* Ordinary \x */
c = 0;
while (i++ < 2 && isxdigit(*p))
{
p++;
}
break;
case 0: /* \ followed by EOF allows for an empty line */
p--;
continue;
case '>':
continue;
case 'A': /* Option setting */
options |= PCRE_ANCHORED;
continue;
case 'B':
options |= PCRE_NOTBOL;
continue;
case 'C':
if (isdigit(*p)) /* Set copy string */
{
copystrings |= 1 << n;
}
else if (isalnum(*p))
{
*npp = 0;
if (n < 0)
else copystrings |= 1 << n;
}
else if (*p == '+')
{
callout_extra = 1;
p++;
}
else if (*p == '-')
{
pcre_callout = NULL;
p++;
}
else if (*p == '!')
{
callout_fail_id = 0;
p++;
while(isdigit(*p))
callout_fail_count = 0;
if (*p == '!')
{
p++;
while(isdigit(*p))
}
}
else if (*p == '*')
{
int sign = 1;
callout_data = 0;
while(isdigit(*p))
callout_data *= sign;
callout_data_set = 1;
}
continue;
case 'G':
if (isdigit(*p))
{
getstrings |= 1 << n;
}
else if (isalnum(*p))
{
*npp = 0;
if (n < 0)
else getstrings |= 1 << n;
}
continue;
case 'L':
getlist = 1;
continue;
case 'M':
find_match_limit = 1;
continue;
case 'N':
options |= PCRE_NOTEMPTY;
continue;
case 'O':
if (n > size_offsets_max)
{
size_offsets_max = n;
{
printf("** Failed to get %d bytes of memory for offsets vector\n",
size_offsets_max * sizeof(int));
return 1;
}
}
use_size_offsets = n;
continue;
case 'P':
options |= PCRE_PARTIAL;
continue;
case 'S':
show_malloc = 1;
continue;
case 'Z':
options |= PCRE_NOTEOL;
continue;
case '?':
continue;
}
*q++ = c;
}
*q = 0;
/* Handle matching via the POSIX interface, which does not
support timing or playing with the match limit or callout data. */
#if !defined NOPOSIX
{
int rc;
int eflags = 0;
if (use_size_offsets > 0)
if (rc != 0)
{
}
else
{
size_t i;
for (i = 0; i < (size_t)use_size_offsets; i++)
{
{
if (i == 0 && do_showrest)
{
outfile);
}
}
}
}
}
/* Handle matching via the native interface - repeats for /g and /G */
else
#endif /* !defined NOPOSIX */
for (;; gmatched++) /* Loop for /g or /G */
{
if (timeit)
{
register int i;
for (i = 0; i < LOOPREPEAT; i++)
(double)CLOCKS_PER_SEC);
}
/* If find_match_limit is set, we want to do repeated matches with
varying limits in order to find the minimum value. */
if (find_match_limit)
{
int min = 0;
int mid = 64;
int max = -1;
{
}
for (;;)
{
if (count == PCRE_ERROR_MATCHLIMIT)
{
/* fprintf(outfile, "Testing match limit = %d\n", mid); */
}
{
{
break;
}
/* fprintf(outfile, "Testing match limit = %d\n", mid); */
}
else break; /* Some other error */
}
}
/* If callout_data is set, use the interface with additional data */
else if (callout_data_set)
{
{
}
}
/* The normal case is just to do the match once, with the default
value of match_limit. */
else
{
}
if (count == 0)
{
}
/* Matched */
if (count >= 0)
{
int i;
{
if (use_offsets[i] < 0)
else
{
if (i == 0)
{
if (do_showrest)
{
outfile);
}
}
}
}
for (i = 0; i < 32; i++)
{
if ((copystrings & (1 << i)) != 0)
{
char copybuffer[16];
i, copybuffer, sizeof(copybuffer));
if (rc < 0)
else
}
}
for (i = 0; i < 32; i++)
{
if ((getstrings & (1 << i)) != 0)
{
const char *substring;
i, &substring);
if (rc < 0)
else
{
/* free((void *)substring); */
}
}
}
if (getlist)
{
const char **stringlist;
&stringlist);
if (rc < 0)
else
{
for (i = 0; i < count; i++)
if (stringlist[i] != NULL)
/* free((void *)stringlist); */
}
}
}
/* There was a partial match */
else if (count == PCRE_ERROR_PARTIAL)
{
break; /* Out of the /g loop */
}
/* Failed to match. If this is a /g or /G loop and we previously set
g_notempty after a null match, this is not necessarily the end.
We want to advance the start offset, and continue. In the case of UTF-8
matching, the advance must be one character, not one byte. Fudge the
offset values to achieve this. We won't be at the end of the string -
that was checked before setting g_notempty. */
else
{
if (g_notempty != 0)
{
int onechar = 1;
use_offsets[0] = start_offset;
if (use_utf8)
{
{
if (tb <= 127) break;
tb &= 0xc0;
}
}
}
else
{
if (count == PCRE_ERROR_NOMATCH)
{
}
break; /* Out of the /g loop */
}
}
/* If not /g or /G we are done */
/* If we have matched an empty string, first check to see if we are at
the end of the subject. If so, the /g loop is over. Otherwise, mimic
what Perl's /g options does. This turns out to be rather cunning. First
we set PCRE_NOTEMPTY and PCRE_ANCHORED and try the match again at the
same point. If this fails (picked up above) we advance to the next
character. */
g_notempty = 0;
{
if (use_offsets[0] == len) break;
}
/* For /g, update the start offset, leaving the rest alone */
/* For /G, update the pointer and length */
else
{
}
} /* End of loop for /g and /G */
} /* End of loop for data lines */
#if !defined NOPOSIX
#endif
{
}
}
return 0;
}
/* End */