@@ -232,6 +232,26 @@ sub _get_git_grep_flavor($s) {
232232 return q{ -P} ;
233233}
234234
235+ # Validate a search string as a PCRE pattern.
236+ # Returns undef if valid, or the error message if invalid.
237+ sub _validate_pcre_pattern ($s ) {
238+ return undef unless defined $s ;
239+ return undef if _get_git_grep_flavor($s ) eq q{ --fixed-string} ;
240+
241+ local $@ ;
242+ eval {
243+ no warnings ' regexp' ; # user patterns may have unescaped braces
244+ qr /$s / ;
245+ };
246+ if ($@ ) {
247+ my $err = $@ ;
248+ $err =~ s { at .+ line \d +.*} {} s ; # strip Perl location info
249+ $err =~ s { ^\s +|\s +$} {} g ;
250+ return $err ;
251+ }
252+ return undef ;
253+ }
254+
235255# idea use git rev-parse HEAD to include it in the cache name
236256
237257sub do_search ( $self , %opts ) {
@@ -250,6 +270,28 @@ sub do_search ( $self, %opts ) {
250270
251271 $search = _sanitize_search($search );
252272
273+ # Validate regex before running git grep — invalid PCRE would silently
274+ # return empty results, confusing users.
275+ if ( my $regex_error = _validate_pcre_pattern($search ) ) {
276+ my $elapsed = sprintf ( " %.3f" ,
277+ Time::HiRes::tv_interval( $t0 , [Time::HiRes::gettimeofday] ) );
278+ return {
279+ is_incomplete => 0,
280+ search_in_progress => 0,
281+ match => { files => 0, distros => 0 },
282+ adjusted_request => {
283+ q => {
284+ error => " Invalid regular expression: $regex_error " ,
285+ value => $search ,
286+ },
287+ },
288+ results => [],
289+ time_elapsed => $elapsed ,
290+ is_a_known_distro => 0,
291+ version => $self -> current_version(),
292+ };
293+ }
294+
253295 my $results = $self -> _do_search(%opts );
254296
255297 my $cache = $results -> {cache };
0 commit comments