diff --git a/infer/documentation/issues/EXPENSIVE_AUTORELEASEPOOL_SIZE.md b/infer/documentation/issues/EXPENSIVE_AUTORELEASEPOOL_SIZE.md new file mode 100644 index 000000000..d46654140 --- /dev/null +++ b/infer/documentation/issues/EXPENSIVE_AUTORELEASEPOOL_SIZE.md @@ -0,0 +1,2 @@ +\[EXPERIMENTAL\] This warning indicates that non-constant and non-top ObjC autoreleasepool's size in +the procedure. By default, this issue type is disabled. diff --git a/infer/documentation/issues/EXPENSIVE_EXECUTION_TIME.md b/infer/documentation/issues/EXPENSIVE_EXECUTION_TIME.md new file mode 100644 index 000000000..956e62a0e --- /dev/null +++ b/infer/documentation/issues/EXPENSIVE_EXECUTION_TIME.md @@ -0,0 +1,2 @@ +\[EXPERIMENTAL\] This warning indicates that non-constant and non-top execution time complexity of +the procedure. By default, this issue type is disabled. diff --git a/infer/src/base/CostIssues.ml b/infer/src/base/CostIssues.ml index 16b0d4582..52a6f230c 100644 --- a/infer/src/base/CostIssues.ml +++ b/infer/src/base/CostIssues.ml @@ -12,7 +12,9 @@ type issue_spec = ; complexity_increase_issue: is_on_ui_thread:bool -> IssueType.t ; unreachable_issue: IssueType.t ; infinite_issue: IssueType.t - ; top_and_unreachable: bool } + ; expensive_issue: IssueType.t + ; top_and_unreachable: bool + ; expensive: bool } module CostKindMap = struct include PrettyPrintable.MakePPMap (CostKind) @@ -32,7 +34,7 @@ end let enabled_cost_map = List.fold CostKind.enabled_cost_kinds ~init:CostKindMap.empty - ~f:(fun acc CostKind.{kind; top_and_unreachable} -> + ~f:(fun acc CostKind.{kind; top_and_unreachable; expensive} -> let kind_spec = { name= Format.asprintf "The %a" CostKind.pp kind ; extract_cost_f= (fun c -> CostKind.to_json_cost_info c kind) @@ -40,6 +42,8 @@ let enabled_cost_map = (fun ~is_on_ui_thread -> IssueType.complexity_increase ~kind ~is_on_ui_thread) ; unreachable_issue= IssueType.unreachable_cost_call ~kind ; infinite_issue= IssueType.infinite_cost_call ~kind - ; top_and_unreachable } + ; expensive_issue= IssueType.expensive_cost_call ~kind + ; top_and_unreachable + ; expensive } in CostKindMap.add kind kind_spec acc ) diff --git a/infer/src/base/CostIssues.mli b/infer/src/base/CostIssues.mli index df34fb430..b7f2c0820 100644 --- a/infer/src/base/CostIssues.mli +++ b/infer/src/base/CostIssues.mli @@ -13,7 +13,9 @@ type issue_spec = ; complexity_increase_issue: is_on_ui_thread:bool -> IssueType.t ; unreachable_issue: IssueType.t ; infinite_issue: IssueType.t - ; top_and_unreachable: bool } + ; expensive_issue: IssueType.t + ; top_and_unreachable: bool + ; expensive: bool } module CostKindMap : sig include PrettyPrintable.PPMap with type key = CostKind.t diff --git a/infer/src/base/IssueType.ml b/infer/src/base/IssueType.ml index 428bf9e86..a085466c6 100644 --- a/infer/src/base/IssueType.ml +++ b/infer/src/base/IssueType.ml @@ -225,6 +225,7 @@ end = struct ; ( "EXECUTION_TIME_UNREACHABLE_AT_EXIT" , [%blob "../../documentation/issues/EXECUTION_TIME_UNREACHABLE_AT_EXIT.md"] ) ; ("INFINITE_EXECUTION_TIME", [%blob "../../documentation/issues/INFINITE_EXECUTION_TIME.md"]) + ; ("EXPENSIVE_EXECUTION_TIME", [%blob "../../documentation/issues/EXPENSIVE_EXECUTION_TIME.md"]) ; ( "AUTORELEASEPOOL_SIZE_COMPLEXITY_INCREASE" , [%blob "../../documentation/issues/AUTORELEASEPOOL_SIZE_COMPLEXITY_INCREASE.md"] ) ; ( "AUTORELEASEPOOL_SIZE_COMPLEXITY_INCREASE_UI_THREAD" @@ -233,7 +234,9 @@ end = struct ; ( "AUTORELEASEPOOL_SIZE_UNREACHABLE_AT_EXIT" , [%blob "../../documentation/issues/AUTORELEASEPOOL_SIZE_UNREACHABLE_AT_EXIT.md"] ) ; ( "INFINITE_AUTORELEASEPOOL_SIZE" - , [%blob "../../documentation/issues/INFINITE_AUTORELEASEPOOL_SIZE.md"] ) ] + , [%blob "../../documentation/issues/INFINITE_AUTORELEASEPOOL_SIZE.md"] ) + ; ( "EXPENSIVE_AUTORELEASEPOOL_SIZE" + , [%blob "../../documentation/issues/EXPENSIVE_AUTORELEASEPOOL_SIZE.md"] ) ] (** cost issues are already registered below.*) @@ -590,6 +593,8 @@ let exposed_insecure_intent_handling = register ~id:"EXPOSED_INSECURE_INTENT_HANDLING" Error Quandary ~user_documentation:"Undocumented." +let expensive_cost_call ~kind = register_cost ~enabled:false "EXPENSIVE_%s" ~kind + let failure_exe = register_hidden ~is_silent:true ~id:"Failure_exe" Info Biabduction let field_not_null_checked = diff --git a/infer/src/base/IssueType.mli b/infer/src/base/IssueType.mli index 0024285b5..7fb0cd57b 100644 --- a/infer/src/base/IssueType.mli +++ b/infer/src/base/IssueType.mli @@ -192,6 +192,8 @@ val eradicate_meta_class_is_nullsafe : t val exposed_insecure_intent_handling : t +val expensive_cost_call : kind:CostKind.t -> t + val failure_exe : t val field_not_null_checked : t diff --git a/infer/src/base/costKind.ml b/infer/src/base/costKind.ml index 31be5e2e4..2ac1c4c39 100644 --- a/infer/src/base/costKind.ml +++ b/infer/src/base/costKind.ml @@ -49,8 +49,8 @@ let to_json_cost_info c = function c.Jsonbug_t.autoreleasepool_size -type kind_spec = {kind: t; (* for non-diff analysis *) top_and_unreachable: bool} +type kind_spec = {kind: t; (* for non-diff analysis *) top_and_unreachable: bool; expensive: bool} let enabled_cost_kinds = - [ {kind= OperationCost; top_and_unreachable= true} - ; {kind= AutoreleasepoolSize; top_and_unreachable= true} ] + [ {kind= OperationCost; top_and_unreachable= true; expensive= false} + ; {kind= AutoreleasepoolSize; top_and_unreachable= true; expensive= true} ] diff --git a/infer/src/base/costKind.mli b/infer/src/base/costKind.mli index 0d8ea5837..db2df7a21 100644 --- a/infer/src/base/costKind.mli +++ b/infer/src/base/costKind.mli @@ -9,7 +9,7 @@ open! IStd type t = OperationCost | AllocationCost | AutoreleasepoolSize [@@deriving compare] -type kind_spec = {kind: t; (* for non-diff analysis *) top_and_unreachable: bool} +type kind_spec = {kind: t; (* for non-diff analysis *) top_and_unreachable: bool; expensive: bool} val compare : t -> t -> int diff --git a/infer/src/bufferoverrun/polynomials.ml b/infer/src/bufferoverrun/polynomials.ml index 84ec25f57..d867b67d1 100644 --- a/infer/src/bufferoverrun/polynomials.ml +++ b/infer/src/bufferoverrun/polynomials.ml @@ -49,6 +49,9 @@ module Degree = struct NonNegativeInt.pp f d.linear ; if not (NonNegativeInt.is_zero d.log) then F.fprintf f " + %a%slog" NonNegativeInt.pp d.log SpecialChars.dot_operator + + + let is_constant {linear; log} = NonNegativeInt.is_zero linear && NonNegativeInt.is_zero log end module NonNegativeBoundWithDegreeKind = struct diff --git a/infer/src/bufferoverrun/polynomials.mli b/infer/src/bufferoverrun/polynomials.mli index fe7a97e9c..cf553e183 100644 --- a/infer/src/bufferoverrun/polynomials.mli +++ b/infer/src/bufferoverrun/polynomials.mli @@ -16,6 +16,8 @@ module Degree : sig val encode_to_int : t -> int (** Encodes the complex type [t] to an integer that can be used for comparison. *) + + val is_constant : t -> bool end module NonNegativeNonTopPolynomial : sig diff --git a/infer/src/cost/cost.ml b/infer/src/cost/cost.ml index 70a4cf951..02f3af8d4 100644 --- a/infer/src/cost/cost.ml +++ b/infer/src/cost/cost.ml @@ -287,35 +287,56 @@ let is_report_suppressed pname = module Check = struct - let report_top_and_unreachable kind pname proc_desc err_log loc ~name ~cost - {CostIssues.unreachable_issue; infinite_issue} = - let report issue suffix = - let is_autoreleasepool_trace = - match (kind : CostKind.t) with - | AutoreleasepoolSize -> - true - | OperationCost | AllocationCost -> - false - in - let message = F.asprintf "%s of the function %a %s" name Procname.pp pname suffix in + let is_autoreleasepool_trace kind = + match (kind : CostKind.t) with + | AutoreleasepoolSize -> + true + | OperationCost | AllocationCost -> + false + + + let mk_report proc_desc pname err_log loc ~name ~is_autoreleasepool_trace cost = + let message suffix = F.asprintf "%s of the function %a %s" name Procname.pp pname suffix in + fun issue suffix -> Reporting.log_issue proc_desc err_log ~loc ~ltr:(BasicCostWithReason.polynomial_traces ~is_autoreleasepool_trace cost) - ~extras:(compute_errlog_extras cost) Cost issue message - in + ~extras:(compute_errlog_extras cost) Cost issue (message suffix) + + + let report_top_and_unreachable ~report ~unreachable_issue ~infinite_issue cost = if BasicCostWithReason.is_top cost then report infinite_issue "cannot be computed" else if BasicCostWithReason.is_unreachable cost then report unreachable_issue "cannot be computed since the program's exit state is never reachable" + let report_expensive ~report ~expensive_issue cost = + Option.iter (BasicCostWithReason.degree cost) ~f:(fun degree -> + if not (Polynomials.Degree.is_constant degree) then + report expensive_issue "has non-constant cost" ) + + let check_and_report {InterproceduralAnalysis.proc_desc; err_log} cost = let pname = Procdesc.get_proc_name proc_desc in - let proc_loc = Procdesc.get_loc proc_desc in if not (is_report_suppressed pname) then CostIssues.CostKindMap.iter2 CostIssues.enabled_cost_map cost - ~f:(fun kind (CostIssues.{name; top_and_unreachable} as issue_spec) cost -> + ~f:(fun kind + CostIssues. + { name + ; unreachable_issue + ; infinite_issue + ; expensive_issue + ; top_and_unreachable + ; expensive } + cost + -> + let report = + mk_report proc_desc pname err_log (Procdesc.get_loc proc_desc) ~name + ~is_autoreleasepool_trace:(is_autoreleasepool_trace kind) cost + in if top_and_unreachable then - report_top_and_unreachable kind pname proc_desc err_log proc_loc ~name ~cost issue_spec ) + report_top_and_unreachable ~report ~unreachable_issue ~infinite_issue cost ; + if expensive then report_expensive ~report ~expensive_issue cost ) end type bound_map = BasicCost.t Node.IdMap.t diff --git a/infer/tests/codetoanalyze/objc/autoreleasepool/issues.exp b/infer/tests/codetoanalyze/objc/autoreleasepool/issues.exp index e69de29bb..46aeba8eb 100644 --- a/infer/tests/codetoanalyze/objc/autoreleasepool/issues.exp +++ b/infer/tests/codetoanalyze/objc/autoreleasepool/issues.exp @@ -0,0 +1,9 @@ +codetoanalyze/objc/autoreleasepool/arc_block.m, ArcBlock.callIndexOfObjectPassingTest_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{x->elements.length.ub},Modeled call to indexOfObjectPassingTest:,autorelease,Call to NoArcCallee.giveMeObject,Modeled call to NSObject.autorelease] +codetoanalyze/objc/autoreleasepool/arc_block.m, ArcBlock.callIndexOfObjectPassingTest_param_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{x->elements.length.ub},Modeled call to indexOfObjectPassingTest:,autorelease,Call to NoArcCallee.giveMeObject,Modeled call to NSObject.autorelease] +codetoanalyze/objc/autoreleasepool/arc_caller.m, ArcCaller.callGiveMeObject_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{n},Loop,autorelease,Call to NoArcCallee.giveMeObject,Modeled call to NSObject.autorelease] +codetoanalyze/objc/autoreleasepool/arc_enumerator.m, ArcEnumerator.callMyEnumerator_linear_FP:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{x->elements.length.ub + 2},Loop,{x->elements.length.ub + 1},Call to MyEnumerator.nextObject,Loop] +codetoanalyze/objc/autoreleasepool/arc_enumerator.m, ArcEnumerator.callMyEnumerator_nextObject_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{x->elements.length.ub + 1},Call to MyEnumerator.nextObject,Loop] +codetoanalyze/objc/autoreleasepool/basic.m, Basic.autoreleased_in_loop_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{n},Loop,autorelease,Call to Basic.call_autorelease_constant,Modeled call to NSObject.autorelease] +codetoanalyze/objc/autoreleasepool/basic.m, Basic.autoreleased_in_loop_sequential_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{n},Loop,autorelease,Call to Basic.call_autorelease_constant,Modeled call to NSObject.autorelease] +codetoanalyze/objc/autoreleasepool/basic.m, Basic.multiple_autorelease_constants:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{n},Loop,autorelease,Call to Basic.call_autorelease_constant,Modeled call to NSObject.autorelease] +codetoanalyze/objc/autoreleasepool/no_arc_caller.m, NoArcCaller.callGiveMeObject_linear:, 0, EXPENSIVE_AUTORELEASEPOOL_SIZE, no_bucket, ERROR, [{n},Loop,autorelease,ARC function call to ArcCallee.giveMeObject from non-ARC caller]