Merge branch 'ab/pcre-jit-fixes'

A few simplification and bugfixes to PCRE interface.

* ab/pcre-jit-fixes:
  grep: under --debug, show whether PCRE JIT is enabled
  grep: do not enter PCRE2_UTF mode on fixed matching
  grep: stess test PCRE v2 on invalid UTF-8 data
  grep: create a "is_fixed" member in "grep_pat"
  grep: consistently use "p->fixed" in compile_regexp()
  grep: stop using a custom JIT stack with PCRE v1
  grep: stop "using" a custom JIT stack with PCRE v2
  grep: remove overly paranoid BUG(...) code
  grep: use PCRE v2 for optimized fixed-string search
  grep: remove the kwset optimization
  grep: drop support for \0 in --fixed-strings <pattern>
  grep: make the behavior for NUL-byte in patterns sane
  grep tests: move binary pattern tests into their own file
  grep tests: move "grep binary" alongside the rest
  grep: inline the return value of a function call used only once
  t4210: skip more command-line encoding tests on MinGW
  grep: don't use PCRE2?_UTF8 with "log --encoding=<non-utf8>"
  log tests: test regex backends in "--encode=<enc>" tests
This commit is contained in:
Junio C Hamano
2019-10-11 14:24:47 +09:00
9 changed files with 285 additions and 221 deletions

176
grep.c
View File

@ -368,18 +368,6 @@ static int is_fixed(const char *s, size_t len)
return 1;
}
static int has_null(const char *s, size_t len)
{
/*
* regcomp cannot accept patterns with NULs so when using it
* we consider any pattern containing a NUL fixed.
*/
if (memchr(s, 0, len))
return 1;
return 0;
}
#ifdef USE_LIBPCRE1
static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
{
@ -388,11 +376,11 @@ static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
int options = PCRE_MULTILINE;
if (opt->ignore_case) {
if (has_non_ascii(p->pattern))
if (!opt->ignore_locale && has_non_ascii(p->pattern))
p->pcre1_tables = pcre_maketables();
options |= PCRE_CASELESS;
}
if (is_utf8_locale() && has_non_ascii(p->pattern))
if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern))
options |= PCRE_UTF8;
p->pcre1_regexp = pcre_compile(p->pattern, options, &error, &erroffset,
@ -406,15 +394,8 @@ static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
#ifdef GIT_PCRE1_USE_JIT
pcre_config(PCRE_CONFIG_JIT, &p->pcre1_jit_on);
if (p->pcre1_jit_on == 1) {
p->pcre1_jit_stack = pcre_jit_stack_alloc(1, 1024 * 1024);
if (!p->pcre1_jit_stack)
die("Couldn't allocate PCRE JIT stack");
pcre_assign_jit_stack(p->pcre1_extra_info, NULL, p->pcre1_jit_stack);
} else if (p->pcre1_jit_on != 0) {
BUG("The pcre1_jit_on variable should be 0 or 1, not %d",
p->pcre1_jit_on);
}
if (opt->debug)
fprintf(stderr, "pcre1_jit_on=%d\n", p->pcre1_jit_on);
#endif
}
@ -426,18 +407,9 @@ static int pcre1match(struct grep_pat *p, const char *line, const char *eol,
if (eflags & REG_NOTBOL)
flags |= PCRE_NOTBOL;
#ifdef GIT_PCRE1_USE_JIT
if (p->pcre1_jit_on) {
ret = pcre_jit_exec(p->pcre1_regexp, p->pcre1_extra_info, line,
eol - line, 0, flags, ovector,
ARRAY_SIZE(ovector), p->pcre1_jit_stack);
} else
#endif
{
ret = pcre_exec(p->pcre1_regexp, p->pcre1_extra_info, line,
eol - line, 0, flags, ovector,
ARRAY_SIZE(ovector));
}
ret = pcre_exec(p->pcre1_regexp, p->pcre1_extra_info, line,
eol - line, 0, flags, ovector,
ARRAY_SIZE(ovector));
if (ret < 0 && ret != PCRE_ERROR_NOMATCH)
die("pcre_exec failed with error code %d", ret);
@ -454,14 +426,11 @@ static void free_pcre1_regexp(struct grep_pat *p)
{
pcre_free(p->pcre1_regexp);
#ifdef GIT_PCRE1_USE_JIT
if (p->pcre1_jit_on) {
if (p->pcre1_jit_on)
pcre_free_study(p->pcre1_extra_info);
pcre_jit_stack_free(p->pcre1_jit_stack);
} else
else
#endif
{
pcre_free(p->pcre1_extra_info);
}
pcre_free((void *)p->pcre1_tables);
}
#else /* !USE_LIBPCRE1 */
@ -498,14 +467,15 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
p->pcre2_compile_context = NULL;
if (opt->ignore_case) {
if (has_non_ascii(p->pattern)) {
if (!opt->ignore_locale && has_non_ascii(p->pattern)) {
character_tables = pcre2_maketables(NULL);
p->pcre2_compile_context = pcre2_compile_context_create(NULL);
pcre2_set_character_tables(p->pcre2_compile_context, character_tables);
}
options |= PCRE2_CASELESS;
}
if (is_utf8_locale() && has_non_ascii(p->pattern))
if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern) &&
!(!opt->ignore_case && (p->fixed || p->is_fixed)))
options |= PCRE2_UTF;
p->pcre2_pattern = pcre2_compile((PCRE2_SPTR)p->pattern,
@ -522,7 +492,9 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
}
pcre2_config(PCRE2_CONFIG_JIT, &p->pcre2_jit_on);
if (p->pcre2_jit_on == 1) {
if (opt->debug)
fprintf(stderr, "pcre2_jit_on=%d\n", p->pcre2_jit_on);
if (p->pcre2_jit_on) {
jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE);
if (jitret)
die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret);
@ -547,19 +519,11 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
BUG("pcre2_pattern_info() failed: %d", patinforet);
if (jitsizearg == 0) {
p->pcre2_jit_on = 0;
if (opt->debug)
fprintf(stderr, "pcre2_jit_on=%d: (*NO_JIT) in regex\n",
p->pcre2_jit_on);
return;
}
p->pcre2_jit_stack = pcre2_jit_stack_create(1, 1024 * 1024, NULL);
if (!p->pcre2_jit_stack)
die("Couldn't allocate PCRE2 JIT stack");
p->pcre2_match_context = pcre2_match_context_create(NULL);
if (!p->pcre2_match_context)
die("Couldn't allocate PCRE2 match context");
pcre2_jit_stack_assign(p->pcre2_match_context, NULL, p->pcre2_jit_stack);
} else if (p->pcre2_jit_on != 0) {
BUG("The pcre2_jit_on variable should be 0 or 1, not %d",
p->pcre2_jit_on);
}
}
@ -603,8 +567,6 @@ static void free_pcre2_pattern(struct grep_pat *p)
pcre2_compile_context_free(p->pcre2_compile_context);
pcre2_code_free(p->pcre2_pattern);
pcre2_match_data_free(p->pcre2_match_data);
pcre2_jit_stack_free(p->pcre2_jit_stack);
pcre2_match_context_free(p->pcre2_match_context);
}
#else /* !USE_LIBPCRE2 */
static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt)
@ -626,7 +588,6 @@ static int pcre2match(struct grep_pat *p, const char *line, const char *eol,
static void free_pcre2_pattern(struct grep_pat *p)
{
}
#endif /* !USE_LIBPCRE2 */
static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt)
{
@ -647,46 +608,66 @@ static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt)
compile_regexp_failed(p, errbuf);
}
}
#endif /* !USE_LIBPCRE2 */
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
int ascii_only;
int err;
int regflags = REG_NEWLINE;
p->word_regexp = opt->word_regexp;
p->ignore_case = opt->ignore_case;
ascii_only = !has_non_ascii(p->pattern);
p->fixed = opt->fixed;
/*
* Even when -F (fixed) asks us to do a non-regexp search, we
* may not be able to correctly case-fold when -i
* (ignore-case) is asked (in which case, we'll synthesize a
* regexp to match the pattern that matches regexp special
* characters literally, while ignoring case differences). On
* the other hand, even without -F, if the pattern does not
* have any regexp special characters and there is no need for
* case-folding search, we can internally turn it into a
* simple string match using kws. p->fixed tells us if we
* want to use kws.
*/
if (opt->fixed ||
has_null(p->pattern, p->patternlen) ||
is_fixed(p->pattern, p->patternlen))
p->fixed = !p->ignore_case || ascii_only;
if (memchr(p->pattern, 0, p->patternlen) && !opt->pcre2)
die(_("given pattern contains NULL byte (via -f <file>). This is only supported with -P under PCRE v2"));
if (p->fixed) {
p->kws = kwsalloc(p->ignore_case ? tolower_trans_tbl : NULL);
kwsincr(p->kws, p->pattern, p->patternlen);
kwsprep(p->kws);
return;
} else if (opt->fixed) {
/*
* We come here when the pattern has the non-ascii
* characters we cannot case-fold, and asked to
* ignore-case.
*/
p->is_fixed = is_fixed(p->pattern, p->patternlen);
#ifdef USE_LIBPCRE2
if (!p->fixed && !p->is_fixed) {
const char *no_jit = "(*NO_JIT)";
const int no_jit_len = strlen(no_jit);
if (starts_with(p->pattern, no_jit) &&
is_fixed(p->pattern + no_jit_len,
p->patternlen - no_jit_len))
p->is_fixed = 1;
}
#endif
if (p->fixed || p->is_fixed) {
#ifdef USE_LIBPCRE2
opt->pcre2 = 1;
if (p->is_fixed) {
compile_pcre2_pattern(p, opt);
} else {
/*
* E.g. t7811-grep-open.sh relies on the
* pattern being restored.
*/
char *old_pattern = p->pattern;
size_t old_patternlen = p->patternlen;
struct strbuf sb = STRBUF_INIT;
/*
* There is the PCRE2_LITERAL flag, but it's
* only in PCRE v2 10.30 and later. Needing to
* ifdef our way around that and dealing with
* it + PCRE2_MULTILINE being an error is more
* complex than just quoting this ourselves.
*/
strbuf_add(&sb, "\\Q", 2);
strbuf_add(&sb, p->pattern, p->patternlen);
strbuf_add(&sb, "\\E", 2);
p->pattern = sb.buf;
p->patternlen = sb.len;
compile_pcre2_pattern(p, opt);
p->pattern = old_pattern;
p->patternlen = old_patternlen;
strbuf_release(&sb);
}
#else /* !USE_LIBPCRE2 */
compile_fixed_regexp(p, opt);
#endif /* !USE_LIBPCRE2 */
return;
}
@ -1053,9 +1034,7 @@ void free_grep_patterns(struct grep_opt *opt)
case GREP_PATTERN: /* atom */
case GREP_PATTERN_HEAD:
case GREP_PATTERN_BODY:
if (p->kws)
kwsfree(p->kws);
else if (p->pcre1_regexp)
if (p->pcre1_regexp)
free_pcre1_regexp(p);
else if (p->pcre2_pattern)
free_pcre2_pattern(p);
@ -1115,29 +1094,12 @@ static void show_name(struct grep_opt *opt, const char *name)
opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
}
static int fixmatch(struct grep_pat *p, char *line, char *eol,
regmatch_t *match)
{
struct kwsmatch kwsm;
size_t offset = kwsexec(p->kws, line, eol - line, &kwsm);
if (offset == -1) {
match->rm_so = match->rm_eo = -1;
return REG_NOMATCH;
} else {
match->rm_so = offset;
match->rm_eo = match->rm_so + kwsm.size[0];
return 0;
}
}
static int patmatch(struct grep_pat *p, char *line, char *eol,
regmatch_t *match, int eflags)
{
int hit;
if (p->fixed)
hit = !fixmatch(p, line, eol, match);
else if (p->pcre1_regexp)
if (p->pcre1_regexp)
hit = !pcre1match(p, line, eol, match, eflags);
else if (p->pcre2_pattern)
hit = !pcre2match(p, line, eol, match, eflags);