Merge branch 'ab/pcre2-grep'
authorJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2017 21:28:54 +0000 (13:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2017 21:28:54 +0000 (13:28 -0800)
"git grep" compiled with libpcre2 sometimes triggered a segfault,
which is being fixed.

* ab/pcre2-grep:
grep: fix segfault under -P + PCRE2 <=10.30 + (*NO_JIT)
test-lib: add LIBPCRE1 & LIBPCRE2 prerequisites

1  2 
grep.c
t/t7810-grep.sh
diff --combined grep.c
index a69c05edc2e4fc6787076531b43a49c7ccdc1113,e8ae0b5d8f78c68ec13038f59b53f0e1abe3482a..3d7cd0e96f1ee160a66dd500c58d4026bf24e34c
--- 1/grep.c
--- 2/grep.c
+++ b/grep.c
@@@ -477,6 -477,8 +477,8 @@@ static void compile_pcre2_pattern(struc
        int options = PCRE2_MULTILINE;
        const uint8_t *character_tables = NULL;
        int jitret;
+       int patinforet;
+       size_t jitsizearg;
  
        assert(opt->pcre2);
  
                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);
+               /*
+                * The pcre2_config(PCRE2_CONFIG_JIT, ...) call just
+                * tells us whether the library itself supports JIT,
+                * but to see whether we're going to be actually using
+                * JIT we need to extract PCRE2_INFO_JITSIZE from the
+                * pattern *after* we do pcre2_jit_compile() above.
+                *
+                * This is because if the pattern contains the
+                * (*NO_JIT) verb (see pcre2syntax(3))
+                * pcre2_jit_compile() will exit early with 0. If we
+                * then proceed to call pcre2_jit_match() further down
+                * the line instead of pcre2_match() we'll either
+                * segfault (pre PCRE 10.31) or run into a fatal error
+                * (post PCRE2 10.31)
+                */
+               patinforet = pcre2_pattern_info(p->pcre2_pattern, PCRE2_INFO_JITSIZE, &jitsizearg);
+               if (patinforet)
+                       BUG("pcre2_pattern_info() failed: %d", patinforet);
+               if (jitsizearg == 0) {
+                       p->pcre2_jit_on = 0;
+                       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");
@@@ -1476,52 -1502,31 +1502,52 @@@ static void show_funcname_line(struct g
        }
  }
  
 +static int is_empty_line(const char *bol, const char *eol);
 +
  static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
                             char *bol, char *end, unsigned lno)
  {
 -      unsigned cur = lno, from = 1, funcname_lno = 0;
 -      int funcname_needed = !!opt->funcname;
 -
 -      if (opt->funcbody && !match_funcname(opt, gs, bol, end))
 -              funcname_needed = 2;
 +      unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
 +      int funcname_needed = !!opt->funcname, comment_needed = 0;
  
        if (opt->pre_context < lno)
                from = lno - opt->pre_context;
        if (from <= opt->last_shown)
                from = opt->last_shown + 1;
 +      orig_from = from;
 +      if (opt->funcbody) {
 +              if (match_funcname(opt, gs, bol, end))
 +                      comment_needed = 1;
 +              else
 +                      funcname_needed = 1;
 +              from = opt->last_shown + 1;
 +      }
  
        /* Rewind. */
 -      while (bol > gs->buf &&
 -             cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
 +      while (bol > gs->buf && cur > from) {
 +              char *next_bol = bol;
                char *eol = --bol;
  
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                cur--;
 +              if (comment_needed && (is_empty_line(bol, eol) ||
 +                                     match_funcname(opt, gs, bol, eol))) {
 +                      comment_needed = 0;
 +                      from = orig_from;
 +                      if (cur < from) {
 +                              cur++;
 +                              bol = next_bol;
 +                              break;
 +                      }
 +              }
                if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
                        funcname_lno = cur;
                        funcname_needed = 0;
 +                      if (opt->funcbody)
 +                              comment_needed = 1;
 +                      else
 +                              from = orig_from;
                }
        }
  
diff --combined t/t7810-grep.sh
index c02ca735b9e31952811bfbb19048081dc546bd8e,c8ff50cc306126e06f58ead078ba0327fe16a48a..1797f632a388d16f23a47c7693c7a3f0f0deabf6
@@@ -60,18 -60,6 +60,18 @@@ test_expect_success setup 
                echo " line with leading space3"
                echo "line without leading space2"
        } >space &&
 +      cat >hello.ps1 <<-\EOF &&
 +      # No-op.
 +      function dummy() {}
 +
 +      # Say hello.
 +      function hello() {
 +        echo "Hello world."
 +      } # hello
 +
 +      # Still a no-op.
 +      function dummy() {}
 +      EOF
        git add . &&
        test_tick &&
        git commit -m initial
@@@ -778,27 -766,18 +778,27 @@@ test_expect_success 'grep -W shows no t
        test_cmp expected actual
  '
  
 -cat >expected <<EOF
 -hello.c=      printf("Hello world.\n");
 -hello.c:      return 0;
 -hello.c-      /* char ?? */
 -EOF
 -
  test_expect_success 'grep -W with userdiff' '
        test_when_finished "rm -f .gitattributes" &&
 -      git config diff.custom.xfuncname "(printf.*|})$" &&
 -      echo "hello.c diff=custom" >.gitattributes &&
 -      git grep -W return >actual &&
 -      test_cmp expected actual
 +      git config diff.custom.xfuncname "^function .*$" &&
 +      echo "hello.ps1 diff=custom" >.gitattributes &&
 +      git grep -W echo >function-context-userdiff-actual
 +'
 +
 +test_expect_success ' includes preceding comment' '
 +      grep "# Say hello" function-context-userdiff-actual
 +'
 +
 +test_expect_success ' includes function line' '
 +      grep "=function hello" function-context-userdiff-actual
 +'
 +
 +test_expect_success ' includes matching line' '
 +      grep ":  echo" function-context-userdiff-actual
 +'
 +
 +test_expect_success ' includes last line of the function' '
 +      grep "} # hello" function-context-userdiff-actual
  '
  
  for threads in $(test_seq 0 10)
@@@ -1131,6 -1110,12 +1131,12 @@@ test_expect_success PCRE 'grep -P patte
        test_cmp expected actual
  '
  
+ test_expect_success LIBPCRE2 "grep -P with (*NO_JIT) doesn't error out" '
+       git grep -P "(*NO_JIT)\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp expected actual
+ '
  test_expect_success !PCRE 'grep -P pattern errors without PCRE' '
        test_must_fail git grep -P "foo.*bar"
  '