From 248eaf87c76aee0bda13496f2fe4178e0cc2b61d Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Tue, 19 May 2020 09:49:29 -0700 Subject: [PATCH] explicit inter-checker dependencies Summary: Before: `RegisterCheckers` activates each checker based on a boolean condition about which other checkers can enable it, eg for pulse: ``` (* registerCheckers.ml *) active= Config.(is_checker_enabled Pulse || is_checker_enabled Impurity) ``` After: `Checker` declares for each checker the list of its dependencies, eg for impurity: ``` (* Checker.ml *) name= "impurity"; activates= [Pulse] ``` Now `Config` computes for each checker whether it was transitively activated by other checkers or not. It saves us from having to encode the logic from before everywhere we want to know "is checker X running?"; this was prone to errors. It will also allow us to display which checkers actually run to the user more easily. Reviewed By: ezgicicek Differential Revision: D21622198 fbshipit-source-id: 004931192 --- infer/src/backend/registerCheckers.ml | 96 ++++------- infer/src/base/Checker.ml | 236 ++++++++++++++------------ infer/src/base/Checker.mli | 23 ++- infer/src/base/Config.ml | 95 ++++++++--- infer/src/base/IssueType.ml | 57 ++++--- infer/src/biabduction/errdesc.ml | 2 +- 6 files changed, 277 insertions(+), 232 deletions(-) diff --git a/infer/src/backend/registerCheckers.ml b/infer/src/backend/registerCheckers.ml index 4421258eb..b6607910f 100644 --- a/infer/src/backend/registerCheckers.ml +++ b/infer/src/backend/registerCheckers.ml @@ -71,30 +71,23 @@ let intraprocedural_with_field_dependency payload_field checker = Procedure (CallbackOfChecker.intraprocedural_with_field_dependency payload_field checker) -type callback = callback_fun * Language.t - -type checker = {name: string; active: bool; callbacks: callback list} +type checker = {checker: Checker.t; callbacks: (callback_fun * Language.t) list} let all_checkers = (* The order of the list is important for those checkers that depend on other checkers having run before them. *) - [ { name= "Self captured in block checker" - ; active= Config.is_checker_enabled SelfInBlock - ; callbacks= [(intraprocedural SelfInBlock.checker, Clang)] } - ; { name= "Class loading analysis" - ; active= Config.is_checker_enabled ClassLoads + [ {checker= SelfInBlock; callbacks= [(intraprocedural SelfInBlock.checker, Clang)]} + ; { checker= ClassLoads ; callbacks= [(interprocedural Payloads.Fields.class_loads ClassLoads.analyze_procedure, Java)] } - ; { name= "purity" - ; active= Config.(is_checker_enabled Purity || is_checker_enabled LoopHoisting) + ; { checker= Purity ; callbacks= (let purity = interprocedural2 Payloads.Fields.purity Payloads.Fields.buffer_overrun_analysis Purity.checker in [(purity, Java); (purity, Clang)] ) } - ; { name= "Starvation analysis" - ; active= Config.is_checker_enabled Starvation + ; { checker= Starvation ; callbacks= (let starvation = interprocedural Payloads.Fields.starvation Starvation.analyze_procedure in let starvation_file_reporting = @@ -104,8 +97,7 @@ let all_checkers = ; (starvation_file_reporting, Java) ; (starvation, Clang) ; (starvation_file_reporting, Clang) ] ) } - ; { name= "loop hoisting" - ; active= Config.is_checker_enabled LoopHoisting + ; { checker= LoopHoisting ; callbacks= (let hoisting = interprocedural3 @@ -115,102 +107,74 @@ let all_checkers = Hoisting.checker in [(hoisting, Clang); (hoisting, Java)] ) } - ; { name= "cost analysis" - ; active= - Config.( - is_checker_enabled Cost - || (is_checker_enabled LoopHoisting && hoisting_report_only_expensive)) + ; { checker= Cost ; callbacks= (let checker = interprocedural3 ~set_payload:(Field.fset Payloads.Fields.cost) Payloads.Fields.cost Payloads.Fields.buffer_overrun_analysis Payloads.Fields.purity Cost.checker in [(checker, Clang); (checker, Java)] ) } - ; { name= "uninitialized variables" - ; active= Config.is_checker_enabled Uninit - ; callbacks= [(interprocedural Payloads.Fields.uninit Uninit.checker, Clang)] } - ; { name= "SIOF" - ; active= Config.is_checker_enabled SIOF - ; callbacks= [(interprocedural Payloads.Fields.siof Siof.checker, Clang)] } - ; { name= "litho-required-props" - ; active= Config.is_checker_enabled LithoRequiredProps + ; {checker= Uninit; callbacks= [(interprocedural Payloads.Fields.uninit Uninit.checker, Clang)]} + ; {checker= SIOF; callbacks= [(interprocedural Payloads.Fields.siof Siof.checker, Clang)]} + ; { checker= LithoRequiredProps ; callbacks= [(interprocedural Payloads.Fields.litho_required_props RequiredProps.checker, Java)] } ; (* toy resource analysis to use in the infer lab, see the lab/ directory *) - { name= "resource leak" - ; active= Config.is_checker_enabled ResourceLeakLabExercise + { checker= ResourceLeakLabExercise ; callbacks= [ ( (* the checked-in version is intraprocedural, but the lab asks to make it interprocedural later on *) interprocedural Payloads.Fields.lab_resource_leaks ResourceLeaks.checker , Java ) ] } - ; { name= "RacerD" - ; active= Config.is_checker_enabled RacerD + ; { checker= RacerD ; callbacks= (let racerd_proc = interprocedural Payloads.Fields.racerd RacerD.analyze_procedure in let racerd_file = file RacerDIssues Payloads.Fields.racerd RacerD.file_analysis in [(racerd_proc, Clang); (racerd_proc, Java); (racerd_file, Clang); (racerd_file, Java)] ) } - ; { name= "quandary" - ; active= Config.(is_checker_enabled Quandary) + ; { checker= Quandary ; callbacks= [ (interprocedural Payloads.Fields.quandary JavaTaintAnalysis.checker, Java) ; (interprocedural Payloads.Fields.quandary ClangTaintAnalysis.checker, Clang) ] } - ; { name= "pulse" - ; active= Config.(is_checker_enabled Pulse || is_checker_enabled Impurity) + ; { checker= Pulse ; callbacks= (let pulse = interprocedural Payloads.Fields.pulse Pulse.checker in [(pulse, Clang); (pulse, Java)] ) } - ; { name= "impurity" - ; active= Config.is_checker_enabled Impurity + ; { checker= Impurity ; callbacks= (let impurity = intraprocedural_with_field_dependency Payloads.Fields.pulse Impurity.checker in [(impurity, Java); (impurity, Clang)] ) } - ; { name= "printf args" - ; active= Config.is_checker_enabled PrintfArgs - ; callbacks= [(intraprocedural PrintfArgs.checker, Java)] } - ; { name= "liveness" - ; active= Config.is_checker_enabled Liveness - ; callbacks= [(intraprocedural Liveness.checker, Clang)] } - ; { name= "inefficient keyset iterator" - ; active= Config.is_checker_enabled InefficientKeysetIterator + ; {checker= PrintfArgs; callbacks= [(intraprocedural PrintfArgs.checker, Java)]} + ; {checker= Liveness; callbacks= [(intraprocedural Liveness.checker, Clang)]} + ; { checker= InefficientKeysetIterator ; callbacks= [(intraprocedural InefficientKeysetIterator.checker, Java)] } - ; { name= "immutable cast" - ; active= Config.is_checker_enabled ImmutableCast + ; { checker= ImmutableCast ; callbacks= [(intraprocedural_with_payload Payloads.Fields.nullsafe ImmutableChecker.analyze, Java)] } - ; { name= "fragment retains view" - ; active= Config.is_checker_enabled FragmentRetainsView + ; { checker= FragmentRetainsView ; callbacks= [(intraprocedural FragmentRetainsViewChecker.callback_fragment_retains_view, Java)] } - ; { name= "eradicate" - ; active= Config.is_checker_enabled Eradicate + ; { checker= Eradicate ; callbacks= [ (intraprocedural_with_payload Payloads.Fields.nullsafe Eradicate.analyze_procedure, Java) ; (file NullsafeFileIssues Payloads.Fields.nullsafe FileLevelAnalysis.analyze_file, Java) ] } - ; { name= "buffer overrun checker" - ; active= Config.(is_checker_enabled BufferOverrun) + ; { checker= BufferOverrunChecker ; callbacks= (let bo_checker = interprocedural2 Payloads.Fields.buffer_overrun_checker Payloads.Fields.buffer_overrun_analysis BufferOverrunChecker.checker in [(bo_checker, Clang); (bo_checker, Java)] ) } - ; { name= "buffer overrun analysis" - ; active= - Config.( - is_checker_enabled BufferOverrun || is_checker_enabled Cost - || is_checker_enabled LoopHoisting || is_checker_enabled Purity) + ; { checker= BufferOverrunAnalysis ; callbacks= (let bo_analysis = interprocedural Payloads.Fields.buffer_overrun_analysis BufferOverrunAnalysis.analyze_procedure in [(bo_analysis, Clang); (bo_analysis, Java)] ) } - ; { name= "biabduction" - ; active= Config.(is_checker_enabled Biabduction || is_checker_enabled TOPL) + ; { checker= Biabduction ; callbacks= (let biabduction = dynamic_dispatch Payloads.Fields.biabduction @@ -219,8 +183,7 @@ let all_checkers = else Interproc.analyze_procedure ) in [(biabduction, Clang); (biabduction, Java)] ) } - ; { name= "annotation reachability" - ; active= Config.is_checker_enabled AnnotationReachability + ; { checker= AnnotationReachability ; callbacks= (let annot_reach = interprocedural Payloads.Fields.annot_map AnnotationReachability.checker @@ -229,12 +192,13 @@ let all_checkers = let get_active_checkers () = - let filter_checker {active} = active in + let filter_checker {checker} = Config.is_checker_enabled checker in List.filter ~f:filter_checker all_checkers let register checkers = - let register_one {name; callbacks} = + let register_one {checker; callbacks} = + let name = (Checker.config checker).name in let register_callback (callback, language) = match callback with | Procedure procedure_cb -> @@ -252,12 +216,12 @@ let register checkers = module LanguageSet = Caml.Set.Make (Language) -let pp_checker fmt {name; callbacks} = +let pp_checker fmt {checker; callbacks} = let langs_of_callbacks = List.fold_left callbacks ~init:LanguageSet.empty ~f:(fun langs (_, lang) -> LanguageSet.add lang langs ) |> LanguageSet.elements in - F.fprintf fmt "%s (%a)" name + F.fprintf fmt "%s (%a)" (Checker.config checker).name (Pp.seq ~sep:", " (Pp.of_string ~f:Language.to_string)) langs_of_callbacks diff --git a/infer/src/base/Checker.ml b/infer/src/base/Checker.ml index 13bf51ba3..cba12564b 100644 --- a/infer/src/base/Checker.ml +++ b/infer/src/base/Checker.ml @@ -10,7 +10,8 @@ open! IStd type t = | AnnotationReachability | Biabduction - | BufferOverrun + | BufferOverrunAnalysis + | BufferOverrunChecker | ClassLoads | Cost | Eradicate @@ -38,13 +39,15 @@ type t = type support = NoSupport | Support | ExperimentalSupport | ToySupport +type cli_flags = {long: string; deprecated: string list; show_in_help: bool} + type config = - { support: Language.t -> support + { name: string + ; support: Language.t -> support ; short_documentation: string - ; cli_flag: string - ; show_in_help: bool + ; cli_flags: cli_flags option ; enabled_by_default: bool - ; cli_deprecated_flags: string list } + ; activates: t list } (* support for languages should be consistent with the corresponding callbacks registered. Or maybe with the issues reported in link @@ -63,197 +66,210 @@ let config checker = in match checker with | AnnotationReachability -> - { support= supports_clang_and_java + { name= "annotation reachability" + ; support= supports_clang_and_java ; short_documentation= "the annotation reachability checker. Given a pair of source and sink annotation, e.g. \ @PerformanceCritical and @Expensive, this checker will warn whenever some method \ annotated with @PerformanceCritical calls, directly or indirectly, another method \ annotated with @Expensive" - ; show_in_help= true - ; cli_flag= "annotation-reachability" + ; cli_flags= Some {long= "annotation-reachability"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | Biabduction -> - { support= supports_clang_and_java + { name= "biabduction" + ; support= supports_clang_and_java ; short_documentation= "the separation logic based bi-abduction analysis using the checkers framework" - ; show_in_help= true - ; cli_flag= "biabduction" + ; cli_flags= Some {long= "biabduction"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } - | BufferOverrun -> - { support= supports_clang_and_java + ; activates= [] } + | BufferOverrunAnalysis -> + { name= "buffer overrun analysis" + ; support= supports_clang_and_java + ; short_documentation= + "internal part of the buffer overrun analysis that computes values at each program \ + point, automatically triggered when analyses that depend on these are run" + ; cli_flags= None + ; enabled_by_default= false + ; activates= [] } + | BufferOverrunChecker -> + { name= "buffer overrun checker" + ; support= supports_clang_and_java ; short_documentation= "the buffer overrun analysis" - ; show_in_help= true - ; cli_flag= "bufferoverrun" + ; cli_flags= Some {long= "bufferoverrun"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [BufferOverrunAnalysis] } | ClassLoads -> - { support= supports_java + { name= "Class loading analysis" + ; support= supports_java ; short_documentation= "Java class loading analysis" - ; show_in_help= true - ; cli_flag= "class-loads" + ; cli_flags= Some {long= "class-loads"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | Cost -> - { support= supports_clang_and_java + { name= "cost analysis" + ; support= supports_clang_and_java ; short_documentation= "checker for performance cost analysis" - ; show_in_help= true - ; cli_flag= "cost" + ; cli_flags= Some {long= "cost"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [BufferOverrunAnalysis] } | Eradicate -> - { support= supports_java + { name= "eradicate" + ; support= supports_java ; short_documentation= "the eradicate @Nullable checker for Java annotations" - ; show_in_help= true - ; cli_flag= "eradicate" + ; cli_flags= Some {long= "eradicate"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | FragmentRetainsView -> - { support= supports_java + { name= "fragment retains view" + ; support= supports_java ; short_documentation= "detects when Android fragments are not explicitly nullified before becoming unreabable" - ; show_in_help= true - ; cli_flag= "fragment-retains-view" + ; cli_flags= Some {long= "fragment-retains-view"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | ImmutableCast -> - { support= supports_java + { name= "immutable cast" + ; support= supports_java ; short_documentation= "the detection of object cast from immutable type to mutable type. For instance, it will \ detect cast from ImmutableList to List, ImmutableMap to Map, and ImmutableSet to Set." - ; show_in_help= true - ; cli_flag= "immutable-cast" + ; cli_flags= Some {long= "immutable-cast"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | Impurity -> - { support= supports_clang_and_java_experimental + { name= "impurity" + ; support= supports_clang_and_java_experimental ; short_documentation= "[EXPERIMENTAL] Impurity analysis" - ; show_in_help= true - ; cli_flag= "impurity" + ; cli_flags= Some {long= "impurity"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [Pulse] } | InefficientKeysetIterator -> - { support= supports_java + { name= "inefficient keyset iterator" + ; support= supports_java ; short_documentation= "Check for inefficient uses of keySet iterator that access both the key and the value." - ; show_in_help= true - ; cli_flag= "inefficient-keyset-iterator" + ; cli_flags= Some {long= "inefficient-keyset-iterator"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | Linters -> - { support= supports_clang + { name= "AST Language (AL) linters" + ; support= supports_clang ; short_documentation= "syntactic linters" - ; show_in_help= true - ; cli_flag= "linters" + ; cli_flags= Some {long= "linters"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | LithoRequiredProps -> - { support= supports_java_experimental + { name= "litho-required-props" + ; support= supports_java_experimental ; short_documentation= "[EXPERIMENTAL] Required Prop check for Litho" - ; show_in_help= true - ; cli_flag= "litho-required-props" + ; cli_flags= Some {long= "litho-required-props"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | Liveness -> - { support= supports_clang + { name= "liveness" + ; support= supports_clang ; short_documentation= "the detection of dead stores and unused variables" - ; show_in_help= true - ; cli_flag= "liveness" + ; cli_flags= Some {long= "liveness"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | LoopHoisting -> - { support= supports_clang_and_java + { name= "loop hoisting" + ; support= supports_clang_and_java ; short_documentation= "checker for loop-hoisting" - ; show_in_help= true - ; cli_flag= "loop-hoisting" + ; cli_flags= Some {long= "loop-hoisting"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [BufferOverrunAnalysis; Purity] } | NullsafeDeprecated -> - { support= (fun _ -> NoSupport) + { name= "nullsafe" + ; support= (fun _ -> NoSupport) ; short_documentation= "[RESERVED] Reserved for nullsafe typechecker, use --eradicate for now" - ; show_in_help= false - ; cli_flag= "nullsafe" + ; cli_flags= + Some + { long= "nullsafe" + ; deprecated= ["-check-nullable"; "-suggest-nullable"] + ; show_in_help= false } ; enabled_by_default= false - ; cli_deprecated_flags= ["-check-nullable"; "-suggest-nullable"] } + ; activates= [] } | PrintfArgs -> - { support= supports_java + { name= "printf args" + ; support= supports_java ; short_documentation= "the detection of mismatch between the Java printf format strings and the argument types \ For, example, this checker will warn about the type error in `printf(\"Hello %d\", \ \"world\")`" - ; show_in_help= true - ; cli_flag= "printf-args" + ; cli_flags= Some {long= "printf-args"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | Pulse -> - { support= supports_clang_and_java_experimental + { name= "pulse" + ; support= supports_clang_and_java_experimental ; short_documentation= "[EXPERIMENTAL] memory and lifetime analysis" - ; show_in_help= true - ; cli_flag= "pulse" + ; cli_flags= Some {long= "pulse"; deprecated= ["-ownership"]; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= ["-ownership"] } + ; activates= [] } | Purity -> - { support= supports_clang_and_java_experimental + { name= "purity" + ; support= supports_clang_and_java_experimental ; short_documentation= "[EXPERIMENTAL] Purity analysis" - ; show_in_help= true - ; cli_flag= "purity" + ; cli_flags= Some {long= "purity"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [BufferOverrunAnalysis] } | Quandary -> - { support= supports_clang_and_java + { name= "quandary" + ; support= supports_clang_and_java ; short_documentation= "the quandary taint analysis" - ; show_in_help= true - ; cli_flag= "quandary" + ; cli_flags= Some {long= "quandary"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | RacerD -> - { support= supports_clang_and_java + { name= "RacerD" + ; support= supports_clang_and_java ; short_documentation= "the RacerD thread safety analysis" - ; show_in_help= true - ; cli_flag= "racerd" + ; cli_flags= Some {long= "racerd"; deprecated= ["-threadsafety"]; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= ["-threadsafety"] } + ; activates= [] } | ResourceLeakLabExercise -> - { support= (fun _ -> ToySupport) + { name= "resource leak lab exercise" + ; support= (fun _ -> ToySupport) ; short_documentation= "" - ; show_in_help= false - ; cli_flag= "resource-leak" + ; cli_flags= Some {long= "resource-leak"; deprecated= []; show_in_help= false} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [] } | SIOF -> - { support= supports_clang + { name= "SIOF" + ; support= supports_clang ; short_documentation= "the Static Initialization Order Fiasco analysis (C++ only)" - ; show_in_help= true - ; cli_flag= "siof" + ; cli_flags= Some {long= "siof"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | SelfInBlock -> - { support= supports_clang + { name= "Self captured in block checker" + ; support= supports_clang ; short_documentation= "checker to flag incorrect uses of when Objective-C blocks capture self" - ; show_in_help= true - ; cli_flag= "self_in_block" + ; cli_flags= Some {long= "self_in_block"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | Starvation -> - { support= supports_clang_and_java + { name= "Starvation analysis" + ; support= supports_clang_and_java ; short_documentation= "starvation analysis" - ; show_in_help= true - ; cli_flag= "starvation" + ; cli_flags= Some {long= "starvation"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } | TOPL -> - { support= supports_clang_and_java_experimental + { name= "TOPL" + ; support= supports_clang_and_java_experimental ; short_documentation= "TOPL" - ; show_in_help= true - ; cli_flag= "topl" + ; cli_flags= Some {long= "topl"; deprecated= []; show_in_help= true} ; enabled_by_default= false - ; cli_deprecated_flags= [] } + ; activates= [Biabduction] } | Uninit -> - { support= supports_clang + { name= "uninitialized variables" + ; support= supports_clang ; short_documentation= "checker for use of uninitialized values" - ; show_in_help= true - ; cli_flag= "uninit" + ; cli_flags= Some {long= "uninit"; deprecated= []; show_in_help= true} ; enabled_by_default= true - ; cli_deprecated_flags= [] } + ; activates= [] } diff --git a/infer/src/base/Checker.mli b/infer/src/base/Checker.mli index 65ad658b3..1e8f82483 100644 --- a/infer/src/base/Checker.mli +++ b/infer/src/base/Checker.mli @@ -10,7 +10,8 @@ open! IStd type t = | AnnotationReachability | Biabduction - | BufferOverrun + | BufferOverrunAnalysis + | BufferOverrunChecker | ClassLoads | Cost | Eradicate @@ -45,15 +46,21 @@ type support = (** the checker is for teaching purposes only (like experimental but with no plans to improve it) *) +type cli_flags = + { long: string + (** The flag to enable this option on the command line, without the leading "--" (like the + [~long] argument of {!CommandLineOption} functions). *) + ; deprecated: string list + (** More command-line flags, similar to [~deprecated] arguments in {!CommandLineOption}. *) + ; show_in_help: bool } + type config = - { support: Language.t -> support + { name: string + ; support: Language.t -> support ; short_documentation: string - ; cli_flag: string - (** the flag to enable this option on the command line, without the leading "--" (like the - [~long] argument of [CommandLineOption] functions) *) - ; show_in_help: bool + ; cli_flags: cli_flags option + (** If [None] then the checker cannot be enabled/disabled from the command line. *) ; enabled_by_default: bool - ; cli_deprecated_flags: string list - (** more command-line flags, similar to [~deprecated] arguments *) } + ; activates: t list (** TODO doc *) } val config : t -> config diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index d68920dbf..29ce48283 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -531,34 +531,41 @@ and analyzer = instance, to enable only the biabduction analysis, run with $(b,--biabduction-only)." -and _checkers = +(* checkers *) +and () = let open Checker in let in_analyze_help = InferCommand.[(Analyze, manual_generic)] in let mk_checker ?f checker = let config = Checker.config checker in - let in_help = if config.show_in_help then in_analyze_help else [] in let var = - CLOpt.mk_bool ?f ~long:config.cli_flag ~in_help ~default:config.enabled_by_default - ~deprecated:config.cli_deprecated_flags config.short_documentation + match config.cli_flags with + | None -> + (* HACK: return a constant ref if the checker cannot be enabled/disabled from the command line *) + ref config.enabled_by_default + | Some {long; deprecated; show_in_help} -> + let in_help = if show_in_help then in_analyze_help else [] in + CLOpt.mk_bool ?f ~long ~in_help ~default:config.enabled_by_default ~deprecated + config.short_documentation in all_checkers := (checker, config, var) :: !all_checkers in List.iter Checker.all ~f:mk_checker ; let mk_only (_checker, config, var) = - let (_ : bool ref) = - CLOpt.mk_bool_group ~long:(config.cli_flag ^ "-only") - ~in_help:InferCommand.[(Analyze, manual_generic)] - ~f:(fun b -> - disable_all_checkers () ; - var := b ; - b ) - ( if config.show_in_help then - Printf.sprintf "Enable $(b,--%s) and disable all other checkers" config.cli_flag - else "" ) - [] (* do all the work in ~f *) [] - (* do all the work in ~f *) - in - () + Option.iter config.cli_flags ~f:(fun {long; show_in_help} -> + let (_ : bool ref) = + CLOpt.mk_bool_group ~long:(long ^ "-only") + ~in_help:InferCommand.[(Analyze, manual_generic)] + ~f:(fun b -> + disable_all_checkers () ; + var := b ; + b ) + ( if show_in_help then + Printf.sprintf "Enable $(b,--%s) and disable all other checkers" long + else "" ) + [] (* do all the work in ~f *) [] + (* do all the work in ~f *) + in + () ) in List.iter ~f:mk_only !all_checkers ; let _default_checkers : bool ref = @@ -568,8 +575,11 @@ and _checkers = ( "Default checkers: " ^ ( List.rev_filter_map ~f:(fun (_, config, _) -> - if config.enabled_by_default then Some (Printf.sprintf "$(b,--%s)" config.cli_flag) - else None ) + match config.cli_flags with + | Some {long} when config.enabled_by_default -> + Some (Printf.sprintf "$(b,--%s)" long) + | _ -> + None ) !all_checkers |> String.concat ~sep:", " ) ) ~f:(fun b -> @@ -3101,10 +3111,47 @@ and xcpretty = !xcpretty (** Configuration values derived from command-line options *) -let is_checker_enabled c = - List.find_map_exn checkers ~f:(fun (checker, enabled) -> - if Checker.equal checker c then Some enabled else None ) +let mem_checkers enabled_checkers (c : Checker.t) = List.mem ~equal:Checker.equal enabled_checkers c + +let enabled_checkers = + (* invariant: [newly_enabled_checkers] is *included* in [enabled_checkers] and [enabled_checkers] + has at most one occurrence of each checker. + + NOTE: the complexity of this is quadratic in the number of checkers but this is fine as we + don't have hundreds of checkers (yet). Also we have few dependencies and shallow dependency + chains so we don't even get close to the worst case. *) + let rec fixpoint newly_enabled_checkers enabled_checkers = + let newly_enabled_checkers, enabled_checkers' = + List.fold newly_enabled_checkers ~init:([], enabled_checkers) + ~f:(fun (newly_enabled_checkers, enabled_checkers) checker -> + let to_enable = + (Checker.config checker).activates + |> List.filter ~f:(fun checker_dep -> not (mem_checkers enabled_checkers checker_dep)) + in + match to_enable with + | [] -> + (newly_enabled_checkers, enabled_checkers) + | _ :: _ -> + (to_enable @ newly_enabled_checkers, to_enable @ enabled_checkers) ) + in + if List.is_empty newly_enabled_checkers then enabled_checkers + else fixpoint newly_enabled_checkers enabled_checkers' + in + let enabled_checkers = + let enabled0 = + List.filter_map checkers ~f:(fun (checker, active) -> if active then Some checker else None) + in + fixpoint enabled0 enabled0 + in + if + hoisting_report_only_expensive + && mem_checkers enabled_checkers LoopHoisting + && not (mem_checkers enabled_checkers Cost) + then fixpoint [Checker.Cost] (Checker.Cost :: enabled_checkers) + else enabled_checkers + +let is_checker_enabled c = mem_checkers enabled_checkers c let clang_frontend_action_string = let text = if capture then ["translating"] else [] in @@ -3123,7 +3170,7 @@ let clang_frontend_action_string = a call to unknown code and true triggers lazy dynamic dispatch. The latter mode follows the JVM semantics and creates procedure descriptions during symbolic execution using the type information found in the abstract state *) -let dynamic_dispatch = is_checker_enabled Biabduction || is_checker_enabled TOPL +let dynamic_dispatch = is_checker_enabled Biabduction (** Check if a Java package is external to the repository *) let java_package_is_external package = diff --git a/infer/src/base/IssueType.ml b/infer/src/base/IssueType.ml index d311c2b43..255c1fc24 100644 --- a/infer/src/base/IssueType.ml +++ b/infer/src/base/IssueType.ml @@ -137,23 +137,29 @@ let assert_failure = register_from_string ~id:"Assert_failure" [Biabduction] let bad_footprint = register_from_string ~id:"Bad_footprint" [Biabduction] -let buffer_overrun_l1 = register_from_string ~id:"BUFFER_OVERRUN_L1" [BufferOverrun] +let buffer_overrun_l1 = register_from_string ~id:"BUFFER_OVERRUN_L1" [BufferOverrunChecker] -let buffer_overrun_l2 = register_from_string ~id:"BUFFER_OVERRUN_L2" [BufferOverrun] +let buffer_overrun_l2 = register_from_string ~id:"BUFFER_OVERRUN_L2" [BufferOverrunChecker] -let buffer_overrun_l3 = register_from_string ~id:"BUFFER_OVERRUN_L3" [BufferOverrun] +let buffer_overrun_l3 = register_from_string ~id:"BUFFER_OVERRUN_L3" [BufferOverrunChecker] -let buffer_overrun_l4 = register_from_string ~enabled:false ~id:"BUFFER_OVERRUN_L4" [BufferOverrun] +let buffer_overrun_l4 = + register_from_string ~enabled:false ~id:"BUFFER_OVERRUN_L4" [BufferOverrunChecker] -let buffer_overrun_l5 = register_from_string ~enabled:false ~id:"BUFFER_OVERRUN_L5" [BufferOverrun] -let buffer_overrun_r2 = register_from_string ~id:"BUFFER_OVERRUN_R2" [BufferOverrun] +let buffer_overrun_l5 = + register_from_string ~enabled:false ~id:"BUFFER_OVERRUN_L5" [BufferOverrunChecker] -let buffer_overrun_s2 = register_from_string ~id:"BUFFER_OVERRUN_S2" [BufferOverrun] -let buffer_overrun_t1 = register_from_string ~id:"BUFFER_OVERRUN_T1" [BufferOverrun] +let buffer_overrun_r2 = register_from_string ~id:"BUFFER_OVERRUN_R2" [BufferOverrunChecker] + +let buffer_overrun_s2 = register_from_string ~id:"BUFFER_OVERRUN_S2" [BufferOverrunChecker] + +let buffer_overrun_t1 = register_from_string ~id:"BUFFER_OVERRUN_T1" [BufferOverrunChecker] + +let buffer_overrun_u5 = + register_from_string ~enabled:false ~id:"BUFFER_OVERRUN_U5" [BufferOverrunChecker] -let buffer_overrun_u5 = register_from_string ~enabled:false ~id:"BUFFER_OVERRUN_U5" [BufferOverrun] let cannot_star = register_from_string ~id:"Cannot_star" [Biabduction] @@ -219,11 +225,12 @@ let component_with_unconventional_superclass = let condition_always_false = - register_from_string ~enabled:false ~id:"CONDITION_ALWAYS_FALSE" [Biabduction; BufferOverrun] + register_from_string ~enabled:false ~id:"CONDITION_ALWAYS_FALSE" + [Biabduction; BufferOverrunChecker] let condition_always_true = - register_from_string ~enabled:false ~id:"CONDITION_ALWAYS_TRUE" [Biabduction; BufferOverrun] + register_from_string ~enabled:false ~id:"CONDITION_ALWAYS_TRUE" [Biabduction; BufferOverrunChecker] let constant_address_dereference = @@ -379,20 +386,24 @@ let inefficient_keyset_iterator = register_from_string ~id:"INEFFICIENT_KEYSET_ITERATOR" [InefficientKeysetIterator] -let inferbo_alloc_is_big = register_from_string ~id:"INFERBO_ALLOC_IS_BIG" [BufferOverrun] +let inferbo_alloc_is_big = register_from_string ~id:"INFERBO_ALLOC_IS_BIG" [BufferOverrunChecker] + +let inferbo_alloc_is_negative = + register_from_string ~id:"INFERBO_ALLOC_IS_NEGATIVE" [BufferOverrunChecker] + -let inferbo_alloc_is_negative = register_from_string ~id:"INFERBO_ALLOC_IS_NEGATIVE" [BufferOverrun] +let inferbo_alloc_is_zero = register_from_string ~id:"INFERBO_ALLOC_IS_ZERO" [BufferOverrunChecker] -let inferbo_alloc_is_zero = register_from_string ~id:"INFERBO_ALLOC_IS_ZERO" [BufferOverrun] +let inferbo_alloc_may_be_big = + register_from_string ~id:"INFERBO_ALLOC_MAY_BE_BIG" [BufferOverrunChecker] -let inferbo_alloc_may_be_big = register_from_string ~id:"INFERBO_ALLOC_MAY_BE_BIG" [BufferOverrun] let inferbo_alloc_may_be_negative = - register_from_string ~id:"INFERBO_ALLOC_MAY_BE_NEGATIVE" [BufferOverrun] + register_from_string ~id:"INFERBO_ALLOC_MAY_BE_NEGATIVE" [BufferOverrunChecker] let inferbo_alloc_may_be_tainted = - register_from_string ~id:"INFERBO_ALLOC_MAY_BE_TAINTED" [BufferOverrun] + register_from_string ~id:"INFERBO_ALLOC_MAY_BE_TAINTED" [BufferOverrunChecker] let infinite_cost_call ~kind = register_from_cost_string ~enabled:false "INFINITE_%s" ~kind @@ -403,18 +414,18 @@ let inherently_dangerous_function = let insecure_intent_handling = register_from_string ~id:"INSECURE_INTENT_HANDLING" [Quandary] -let integer_overflow_l1 = register_from_string ~id:"INTEGER_OVERFLOW_L1" [BufferOverrun] +let integer_overflow_l1 = register_from_string ~id:"INTEGER_OVERFLOW_L1" [BufferOverrunChecker] -let integer_overflow_l2 = register_from_string ~id:"INTEGER_OVERFLOW_L2" [BufferOverrun] +let integer_overflow_l2 = register_from_string ~id:"INTEGER_OVERFLOW_L2" [BufferOverrunChecker] let integer_overflow_l5 = - register_from_string ~enabled:false ~id:"INTEGER_OVERFLOW_L5" [BufferOverrun] + register_from_string ~enabled:false ~id:"INTEGER_OVERFLOW_L5" [BufferOverrunChecker] -let integer_overflow_r2 = register_from_string ~id:"INTEGER_OVERFLOW_R2" [BufferOverrun] +let integer_overflow_r2 = register_from_string ~id:"INTEGER_OVERFLOW_R2" [BufferOverrunChecker] let integer_overflow_u5 = - register_from_string ~enabled:false ~id:"INTEGER_OVERFLOW_U5" [BufferOverrun] + register_from_string ~enabled:false ~id:"INTEGER_OVERFLOW_U5" [BufferOverrunChecker] let interface_not_thread_safe = register_from_string ~id:"INTERFACE_NOT_THREAD_SAFE" [RacerD] @@ -544,7 +555,7 @@ let unary_minus_applied_to_unsigned_expression = let uninitialized_value = register_from_string ~id:"UNINITIALIZED_VALUE" [Uninit] -let unreachable_code_after = register_from_string ~id:"UNREACHABLE_CODE" [BufferOverrun] +let unreachable_code_after = register_from_string ~id:"UNREACHABLE_CODE" [BufferOverrunChecker] let use_after_delete = register_from_string ~id:"USE_AFTER_DELETE" [Pulse] diff --git a/infer/src/biabduction/errdesc.ml b/infer/src/biabduction/errdesc.ml index 2fa42c115..029a9d6c0 100644 --- a/infer/src/biabduction/errdesc.ml +++ b/infer/src/biabduction/errdesc.ml @@ -135,7 +135,7 @@ let rec find_normal_variable_load_ tenv (seen : Exp.Set.t) node id : DExp.t opti in Some (DExp.Dretcall (fun_dexp, args_dexp, loc, call_flags)) | Sil.Store {e1= Exp.Lvar pvar; e2= Exp.Var id0} - when Config.(is_checker_enabled Biabduction || is_checker_enabled TOPL) + when Config.is_checker_enabled Biabduction && Ident.equal id id0 && not (Pvar.is_frontend_tmp pvar) -> (* this case is a hack to make bucketing continue to work in the presence of copy