3636
3737
3838class ReachabilityStatus (str , Enum ):
39- REACHABLE = "YES "
40- UNKNOWN = "UNKNOWN "
41- NOT_REACHABLE = "NO "
39+ REACHABLE = "yes "
40+ UNKNOWN = "unknown "
41+ NOT_REACHABLE = "no "
4242
4343
4444def normalize_text (content ):
@@ -347,21 +347,21 @@ def analyze(
347347 return vuln_meta , fixed_meta , language
348348
349349
350- def classify_reachability (evidence ):
350+ def classify_reachability (tool_details ):
351351 """
352352 Classify the reachability status of a vulnerability based on the
353- collected evidence from :class:` ResourcePatchMatcher` .
353+ collected tool_details from ResourcePatchMatcher.
354354 """
355- if not evidence :
355+ if not tool_details :
356356 return ReachabilityStatus .NOT_REACHABLE
357357
358358 status = ReachabilityStatus .NOT_REACHABLE
359- for item in evidence .values ():
360- is_called = bool (item .get ("called " ))
359+ for item in tool_details .values ():
360+ is_called = bool (item .get ("is_called " ))
361361 has_path = bool (item .get ("reachable_from" ))
362- is_defined = bool (item .get ("defined " ))
363- is_imported = bool (item .get ("imported " ))
364- is_exact = bool (item .get ("fingerprint " ))
362+ is_defined = bool (item .get ("is_defined " ))
363+ is_imported = bool (item .get ("is_imported " ))
364+ is_exact = bool (item .get ("is_exact " ))
365365
366366 if is_exact or (is_imported and (is_called or has_path )):
367367 return ReachabilityStatus .REACHABLE
@@ -461,16 +461,68 @@ def __init__(self, resource_index):
461461 self .imports = resource_index .get ("imports" , {})
462462 self .callers_of = resource_index .get ("callers_of" , {})
463463 self .separator = resource_index .get ("separator" , "." )
464+ self .wildcard_modules = self .imports .get ("*" , [])
465+
466+ def _matches_first_component (
467+ self , qualified_name , abs_path , local_name , import_call_names
468+ ):
469+ """
470+ Check if the first component of qualified_name
471+ matches the end of abs_path.
472+ """
473+ first_component = qualified_name .split (self .separator , 1 )[0 ]
474+ if first_component != qualified_name and (
475+ abs_path .endswith (self .separator + first_component )
476+ or abs_path == first_component
477+ ):
478+ remaining = qualified_name [len (first_component ) :]
479+ import_call_names .add (f"{ local_name } { remaining } " )
480+ return True
481+ return False
482+
483+ def _matches_wildcard (self , qualified_name ):
484+ """Check if the qualified_name is covered by a wildcard import."""
485+ return any (
486+ qualified_name == mod or qualified_name .startswith (mod + self .separator )
487+ for mod in self .wildcard_modules
488+ )
489+
490+ def _get_import_info (self , qualified_name ):
491+ """Check if qualified_name is imported and return possible call names."""
492+ import_call_names = set ()
493+ imported = False
494+
495+ for local_name , abs_path in self .imports .items ():
496+ if local_name == "*" :
497+ continue
498+
499+ if qualified_name in (local_name , abs_path ):
500+ imported = True
501+ import_call_names .add (local_name )
502+ elif qualified_name .startswith (local_name + self .separator ):
503+ imported = True
504+ import_call_names .add (qualified_name )
505+ elif qualified_name .startswith (abs_path + self .separator ):
506+ imported = True
507+ remaining = qualified_name [len (abs_path ) :]
508+ import_call_names .add (f"{ local_name } { remaining } " )
509+ elif abs_path .endswith (self .separator + qualified_name ):
510+ imported = True
511+ import_call_names .add (local_name )
512+ elif self ._matches_first_component (
513+ qualified_name , abs_path , local_name , import_call_names
514+ ):
515+ imported = True
516+
517+ if not imported and self ._matches_wildcard (qualified_name ):
518+ return True , import_call_names
519+
520+ return imported , import_call_names
464521
465522 def match (self , patch_symbols_metadata ):
466523 """
467524 Match a set of patch symbols against the resource index and
468- return evidence for each matched symbol.
469-
470- For each symbol in patch_symbols_metadata, the method
471- checks whether it is defined, imported, called, or has an
472- exact fingerprint match in the resource. If at least one of
473- these conditions is true, an evidence entry is created.
525+ return tool_details for each matched symbol.
474526 """
475527 if not patch_symbols_metadata or not self .resource_index :
476528 return {}
@@ -480,42 +532,56 @@ def match(self, patch_symbols_metadata):
480532 qualified_name = metadata ["qualified_name" ]
481533 fingerprint = metadata ["fingerprint" ]
482534 defined = qualified_name in self .definitions
483- fingerprint_hit = bool (fingerprint and fingerprint in self .fingerprints )
484-
485- imported = (
486- qualified_name in self .imports
487- or qualified_name in self .imports .values ()
535+ is_exact = bool (
536+ fingerprint
537+ and fingerprint in self .fingerprints
538+ and qualified_name in self .definitions
488539 )
540+ short_name = (
541+ qualified_name .rsplit (self .separator , 1 )[- 1 ]
542+ if self .separator in qualified_name
543+ else qualified_name
544+ )
545+
546+ imported , import_call_names = self ._get_import_info (qualified_name )
489547
490- callers = set (self .callers_of .get (qualified_name , set ()))
491- called = bool (callers )
548+ possible_call_names = {qualified_name }
549+ if imported or defined :
550+ possible_call_names .add (short_name )
551+ possible_call_names .update (import_call_names )
492552
493- if not (defined or fingerprint_hit or called or imported ):
553+ callers = set ()
554+ for call_name in possible_call_names :
555+ callers .update (self .callers_of .get (call_name , set ()))
556+
557+ called = bool (callers ) and (
558+ imported or defined or bool (self .wildcard_modules )
559+ )
560+ if called and not imported and not defined and self .wildcard_modules :
561+ imported = True
562+
563+ if not (defined or is_exact or called or imported ):
494564 continue
495565
496566 entry = matched .setdefault (
497567 qualified_name ,
498568 {
499569 "symbol_name" : qualified_name ,
500- "called " : False ,
501- "defined " : False ,
502- "imported " : False ,
503- "fingerprint " : None ,
570+ "is_called " : False ,
571+ "is_defined " : False ,
572+ "is_imported " : False ,
573+ "is_exact " : False ,
504574 "reachable_from" : [],
505575 },
506576 )
507577
508- if defined :
509- entry ["defined " ] = True
510- if imported :
511- entry ["imported " ] = True
578+ entry [ "is_defined" ] = entry [ "is_defined" ] or defined
579+ entry [ "is_imported" ] = entry ["is_imported " ] or imported
580+ entry [ "is_exact" ] = entry [ "is_exact" ] or is_exact
581+ entry [ "is_called" ] = entry ["is_called " ] or called
512582 if called :
513- entry ["called" ] = True
514583 entry ["reachable_from" ] = sorted (callers )
515584
516- if fingerprint_hit :
517- entry ["fingerprint" ] = fingerprint
518-
519585 return matched
520586
521587
0 commit comments