diff --git a/infer/src/IR/Localise.ml b/infer/src/IR/Localise.ml index 1bff18aaa..042b95be8 100644 --- a/infer/src/IR/Localise.ml +++ b/infer/src/IR/Localise.ml @@ -42,6 +42,7 @@ let class_cast_exception = "CLASS_CAST_EXCEPTION" let comparing_floats_for_equality = "COMPARING_FLOAT_FOR_EQUALITY" let condition_is_assignment = "CONDITION_IS_ASSIGNMENT" let component_factory_function = "COMPONENT_FACTORY_FUNCTION" +let component_file_line_count = "COMPONENT_FILE_LINE_COUNT" let component_initializer_with_side_effects = "COMPONENT_INITIALIZER_WITH_SIDE_EFFECTS" let component_with_multiple_factory_methods = "COMPONENT_WITH_MULTIPLE_FACTORY_METHODS" let component_with_unconventional_superclass = "COMPONENT_WITH_UNCONVENTIONAL_SUPERCLASS" diff --git a/infer/src/IR/Localise.mli b/infer/src/IR/Localise.mli index 93cf1137e..51bbec9bb 100644 --- a/infer/src/IR/Localise.mli +++ b/infer/src/IR/Localise.mli @@ -42,6 +42,7 @@ val condition_always_false : t val condition_always_true : t val context_leak : t val component_factory_function : t +val component_file_line_count : t val component_initializer_with_side_effects : t val component_with_multiple_factory_methods : t val component_with_unconventional_superclass : t diff --git a/infer/src/base/DB.ml b/infer/src/base/DB.ml index 7cd6b7adc..3b9c0a15d 100644 --- a/infer/src/base/DB.ml +++ b/infer/src/base/DB.ml @@ -17,6 +17,11 @@ module L = Logging (** {2 Source Files} *) +let count_newlines (path: string): int = + let open Core.Std in + let f file = In_channel.fold_lines file ~init:0 ~f:(fun i _ -> i + 1) in + In_channel.with_file path ~f + type source_file = | Absolute of string | Relative of string @@ -74,6 +79,10 @@ let source_file_to_abs_path fname = | Relative path -> Filename.concat Config.project_root path | Absolute path -> path +let source_file_line_count source_file = + let abs_path = source_file_to_abs_path source_file in + count_newlines abs_path + let inode_equal sf1 sf2 = let stat1 = Unix.stat (source_file_to_abs_path sf1) in let stat2 = Unix.stat (source_file_to_abs_path sf2) in diff --git a/infer/src/base/DB.mli b/infer/src/base/DB.mli index 243ab21d4..05ac0976e 100644 --- a/infer/src/base/DB.mli +++ b/infer/src/base/DB.mli @@ -89,6 +89,9 @@ module SourceFileSet : Set.S with type elt = source_file (** comparison of source files *) val source_file_compare : source_file -> source_file -> int +(** compute line count of a source file *) +val source_file_line_count : source_file -> int + (** equality of source files *) val source_file_equal : source_file -> source_file -> bool diff --git a/infer/src/base/IList.ml b/infer/src/base/IList.ml index 1e3a85c59..32c110f69 100644 --- a/infer/src/base/IList.ml +++ b/infer/src/base/IList.ml @@ -266,3 +266,8 @@ let mem_assoc equal a l = (** Like List.assoc but without builtin equality *) let assoc equal a l = snd (find (fun x -> equal a (fst x)) l) + +let range i j = + let rec aux n acc = + if n < i then acc else aux (n-1) (n :: acc) in + aux j [] diff --git a/infer/src/base/IList.mli b/infer/src/base/IList.mli index c8d2b98b7..71574492b 100644 --- a/infer/src/base/IList.mli +++ b/infer/src/base/IList.mli @@ -119,3 +119,8 @@ val find_map_opt : ('a -> 'b option) -> 'a list -> 'b option val find_mapi_opt : (int -> 'a -> 'b option) -> 'a list -> 'b option val to_string : ('a -> string) -> 'a list -> string + +(** Creates an list, inclusive. E.g. `range 2 4` -> [2, 3, 4]. + + Not tail-recursive.*) +val range : int -> int -> int list diff --git a/infer/src/clang/ComponentKit.ml b/infer/src/clang/ComponentKit.ml index f7c04b1e4..54b601694 100644 --- a/infer/src/clang/ComponentKit.ml +++ b/infer/src/clang/ComponentKit.ml @@ -305,3 +305,27 @@ let component_initializer_with_side_effects_advice | CTL.Stmt (CallExpr (_, called_func_stmt :: _, _)) -> _component_initializer_with_side_effects_advice context called_func_stmt | _ -> CTL.False, None (* only to be called in CallExpr *) + +(** Returns one issue per line of code, with the column set to 0. + + This still needs to be in infer b/c only files that have a valid component + kit class impl should be analyzed. *) +let component_file_line_count_info (context: CLintersContext.context) dec = + let condition = Config.compute_analytics && context.is_ck_translation_unit in + match dec with + | Clang_ast_t.TranslationUnitDecl _ when condition -> + let source_file = + context.translation_unit_context.CFrontend_config.source_file in + let line_count = DB.source_file_line_count source_file in + IList.map (fun i -> { + CIssue.issue = CIssue.Component_file_line_count; + CIssue.description = "Line count analytics"; + CIssue.suggestion = None; + CIssue.loc = { + Location.line = i; + Location.col = 0; + Location.file = source_file + } + } + ) (IList.range 1 line_count) + | _ -> [] diff --git a/infer/src/clang/ComponentKit.mli b/infer/src/clang/ComponentKit.mli index 3e9240b72..8deaa2827 100644 --- a/infer/src/clang/ComponentKit.mli +++ b/infer/src/clang/ComponentKit.mli @@ -28,3 +28,6 @@ val component_with_multiple_factory_methods_advice : val component_initializer_with_side_effects_advice : CLintersContext.context -> CTL.ast_node -> CTL.t * CIssue.issue_desc option + +val component_file_line_count_info : + CLintersContext.context -> Clang_ast_t.decl -> CIssue.issue_desc list diff --git a/infer/src/clang/cFrontend_checkers_main.ml b/infer/src/clang/cFrontend_checkers_main.ml index 4002aebef..4213fb9b8 100644 --- a/infer/src/clang/cFrontend_checkers_main.ml +++ b/infer/src/clang/cFrontend_checkers_main.ml @@ -134,6 +134,7 @@ let do_frontend_checks trans_unit_ctx ast = | Clang_ast_t.TranslationUnitDecl(_, decl_list, _, _) -> let context = context_with_ck_set (CLintersContext.empty trans_unit_ctx) decl_list in + ignore (CFrontend_errors.run_translation_unit_checker context ast); let is_decl_allowed decl = let decl_info = Clang_ast_proj.get_decl_tuple decl in CLocation.should_do_frontend_check trans_unit_ctx decl_info.Clang_ast_t.di_source_range in diff --git a/infer/src/clang/cFrontend_errors.ml b/infer/src/clang/cFrontend_errors.ml index 9e04ccf4a..da79766a6 100644 --- a/infer/src/clang/cFrontend_errors.ml +++ b/infer/src/clang/cFrontend_errors.ml @@ -27,6 +27,9 @@ let stmt_checkers_list = [CFrontend_checkers.ctl_direct_atomic_property_access_ CFrontend_checkers.ctl_bad_pointer_comparison_warning; ComponentKit.component_initializer_with_side_effects_advice;] +(* List of checkers on translation unit that potentially output multiple issues *) +let translation_unit_checkers_list = [ComponentKit.component_file_line_count_info;] + @@ -76,3 +79,12 @@ let run_frontend_checkers_on_an (context: CLintersContext.context) an = | _ -> context in invoke_set_of_checkers_an an context'; context' + +let run_translation_unit_checker (context: CLintersContext.context) dec = + IList.iter (fun checker -> + let issue_desc_list = checker context dec in + IList.iter (fun issue_desc -> + let key = Ast_utils.generate_key_decl dec in + log_frontend_issue context.CLintersContext.translation_unit_context + context.CLintersContext.current_method key issue_desc + ) issue_desc_list) translation_unit_checkers_list diff --git a/infer/src/clang/cFrontend_errors.mli b/infer/src/clang/cFrontend_errors.mli index 5929b0a6a..c927ce7fa 100644 --- a/infer/src/clang/cFrontend_errors.mli +++ b/infer/src/clang/cFrontend_errors.mli @@ -15,3 +15,9 @@ open! Utils (* Run frontend checkers on an AST node *) val run_frontend_checkers_on_an : CLintersContext.context -> CTL.ast_node -> CLintersContext.context + +(** Same as run_frontend_checkers_on_an except special-cased on the translation + unit. Translation unit level checkers may return multiple issues, which is + why special-casing is necessary here. *) +val run_translation_unit_checker : + CLintersContext.context -> Clang_ast_t.decl -> unit diff --git a/infer/src/clang/cIssue.ml b/infer/src/clang/cIssue.ml index 1968ec1de..494704e9e 100644 --- a/infer/src/clang/cIssue.ml +++ b/infer/src/clang/cIssue.ml @@ -11,6 +11,7 @@ type issue = | Assign_pointer_warning | Bad_pointer_comparison | Component_factory_function + | Component_file_line_count | Component_initializer_with_side_effects | Component_with_multiple_factory_methods | Component_with_unconventional_superclass @@ -30,6 +31,8 @@ let to_string issue = Localise.bad_pointer_comparison | Component_factory_function -> Localise.component_factory_function + | Component_file_line_count -> + Localise.component_file_line_count | Component_initializer_with_side_effects -> Localise.component_initializer_with_side_effects | Component_with_multiple_factory_methods -> @@ -64,6 +67,7 @@ let severity_of_issue issue = | Component_with_multiple_factory_methods | Component_with_unconventional_superclass | Mutable_local_variable_in_component_file -> Exceptions.Kadvice + | Component_file_line_count -> Exceptions.Kinfo type issue_desc = { diff --git a/infer/src/clang/cIssue.mli b/infer/src/clang/cIssue.mli index 2fc28ca90..ce0bfe315 100644 --- a/infer/src/clang/cIssue.mli +++ b/infer/src/clang/cIssue.mli @@ -11,6 +11,7 @@ type issue = | Assign_pointer_warning | Bad_pointer_comparison | Component_factory_function + | Component_file_line_count | Component_initializer_with_side_effects | Component_with_multiple_factory_methods | Component_with_unconventional_superclass diff --git a/infer/tests/build_systems/expected_outputs/componentkit_analytics_report.json b/infer/tests/build_systems/expected_outputs/componentkit_analytics_report.json index fe51488c7..d91327dc7 100644 --- a/infer/tests/build_systems/expected_outputs/componentkit_analytics_report.json +++ b/infer/tests/build_systems/expected_outputs/componentkit_analytics_report.json @@ -1 +1,77 @@ -[] +[ + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + }, + { + "bug_type": "COMPONENT_FILE_LINE_COUNT", + "file": "TestComponentKitAnalytics.mm", + "procedure": "Linters_dummy_method" + } +] \ No newline at end of file