Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 52 additions & 48 deletions lib/tre-compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@

*/

/*
TODO:
- Fix tre_ast_to_tnfa() to recurse using a stack instead of recursive
function calls.
*/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
Expand Down Expand Up @@ -1777,61 +1770,70 @@ tre_make_trans(tre_pos_and_tags_t *p1, tre_pos_and_tags_t *p2,
/* Converts the syntax tree to a TNFA. All the transitions in the TNFA are
labelled with one character range (there are no transitions on empty
strings). The TNFA takes O(n^2) space in the worst case, `n' is size of
the regexp. */
the regexp.

This is the iterative version using an explicit stack; the previous
recursive implementation could overflow the C stack for large patterns. */
static reg_errcode_t
tre_ast_to_tnfa(tre_ast_node_t *node, tre_tnfa_transition_t *transitions,
tre_ast_to_tnfa(tre_stack_t *stack, tre_ast_node_t *node,
tre_tnfa_transition_t *transitions,
int *counts, int *offs)
{
tre_union_t *uni;
tre_catenation_t *cat;
tre_iteration_t *iter;
reg_errcode_t errcode = REG_OK;

/* XXX - recurse using a stack!. */
switch (node->type)
{
case LITERAL:
break;
case UNION:
uni = (tre_union_t *)node->obj;
errcode = tre_ast_to_tnfa(uni->left, transitions, counts, offs);
if (errcode != REG_OK)
return errcode;
errcode = tre_ast_to_tnfa(uni->right, transitions, counts, offs);
break;

case CATENATION:
cat = (tre_catenation_t *)node->obj;
/* Add a transition from each position in cat->left->lastpos
to each position in cat->right->firstpos. */
errcode = tre_make_trans(cat->left->lastpos, cat->right->firstpos,
transitions, counts, offs);
if (errcode != REG_OK)
return errcode;
errcode = tre_ast_to_tnfa(cat->left, transitions, counts, offs);
if (errcode != REG_OK)
return errcode;
errcode = tre_ast_to_tnfa(cat->right, transitions, counts, offs);
break;
STACK_PUSHR(stack, voidptr, node);

case ITERATION:
iter = (tre_iteration_t *)node->obj;
assert(iter->max == -1 || iter->max == 1);
while (tre_stack_num_items(stack))
{
node = tre_stack_pop_voidptr(stack);

if (iter->max == -1)
switch (node->type)
{
assert(iter->min == 0 || iter->min == 1);
/* Add a transition from each last position in the iterated
expression to each first position. */
errcode = tre_make_trans(iter->arg->lastpos, iter->arg->firstpos,
case LITERAL:
break;

case UNION:
uni = (tre_union_t *)node->obj;
/* Push right before left so that left is processed first,
preserving the traversal order of the recursive version. */
STACK_PUSHR(stack, voidptr, uni->right);
STACK_PUSHR(stack, voidptr, uni->left);
break;

case CATENATION:
cat = (tre_catenation_t *)node->obj;
/* Add a transition from each position in cat->left->lastpos
to each position in cat->right->firstpos. */
errcode = tre_make_trans(cat->left->lastpos, cat->right->firstpos,
transitions, counts, offs);
if (errcode != REG_OK)
return errcode;
STACK_PUSHR(stack, voidptr, cat->right);
STACK_PUSHR(stack, voidptr, cat->left);
break;

case ITERATION:
iter = (tre_iteration_t *)node->obj;
assert(iter->max == -1 || iter->max == 1);

if (iter->max == -1)
{
assert(iter->min == 0 || iter->min == 1);
/* Add a transition from each last position in the iterated
expression to each first position. */
errcode = tre_make_trans(iter->arg->lastpos, iter->arg->firstpos,
transitions, counts, offs);
if (errcode != REG_OK)
return errcode;
}
STACK_PUSHR(stack, voidptr, iter->arg);
break;
}
errcode = tre_ast_to_tnfa(iter->arg, transitions, counts, offs);
break;
}
return errcode;
return REG_OK;
}

#define ERROR_EXIT(err) \
Expand Down Expand Up @@ -2002,7 +2004,9 @@ tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags)

for (i = 0; i < numpos; i++)
counts[i] = 0;
tre_ast_to_tnfa(tree, NULL, counts, NULL);
errcode = tre_ast_to_tnfa(stack, tree, NULL, counts, NULL);
if (errcode != REG_OK)
ERROR_EXIT(errcode);

add = 0;
for (i = 0; i < numpos; i++)
Expand All @@ -2018,7 +2022,7 @@ tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags)
tnfa->num_transitions = add;

DPRINT(("Converting to TNFA:\n"));
errcode = tre_ast_to_tnfa(tree, transitions, counts, offs);
errcode = tre_ast_to_tnfa(stack, tree, transitions, counts, offs);
if (errcode != REG_OK)
ERROR_EXIT(errcode);

Expand Down
53 changes: 53 additions & 0 deletions tests/test-limits.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
#include <stdlib.h>
#include <string.h>

#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
# include <pthread.h>
# define TEST_STACK_DEPTH 1
#endif

#ifdef USE_SYSTEM_REGEX
# include <regex.h>
# ifndef REG_BASIC
Expand All @@ -33,6 +38,25 @@ static void notok(void) { fputc('-', stderr); ntests++; }
static void done(void) { fputc('\n', stderr); exit(nok == ntests ? 0 : 1); }
#define check(expr) do { ((expr) ? ok() : notok()); } while (0)

#ifdef TEST_STACK_DEPTH
/* Compile a pattern in a small-stack thread. Compile only: with
TRE_USE_ALLOCA, regexec() allocates buffers proportional to the
automaton size on the stack, which cannot fit in a small stack no
matter how compilation is implemented. */
static void *
compile_large(void *arg)
{
const char *pat = arg;
regex_t re;
intptr_t err;

err = regcomp(&re, pat, REG_EXTENDED);
if (err == REG_OK)
regfree(&re);
return (void *)err;
}
#endif /* TEST_STACK_DEPTH */

int
main(void)
{
Expand Down Expand Up @@ -137,5 +161,34 @@ main(void)
}
free(buf);
}

/* Compiling a large regex must not overflow the C stack: the TNFA
conversion recursed once per AST node until it was converted to
iteration. Run the compilation in a thread with a small stack so
the recursive implementation would crash here even on platforms
with large default stacks. */
#ifdef TEST_STACK_DEPTH
{
pthread_t thr;
pthread_attr_t attr;
void *ret = (void *)-1;
size = 60000; /* <= TRE_MAX_RE, deep enough to need several MB of
C stack at one stack frame per AST node */
if ((buf = malloc(size + 1)) == NULL) {
notok();
} else {
memset(buf, 'a', size);
buf[size] = '\0';
fprintf(stderr, "S");
check(pthread_attr_init(&attr) == 0
&& pthread_attr_setstacksize(&attr, 1024 * 1024) == 0
&& pthread_create(&thr, &attr, compile_large, buf) == 0
&& pthread_join(thr, &ret) == 0
&& ret == (void *)REG_OK);
free(buf);
}
}
#endif /* TEST_STACK_DEPTH */

done();
}