From d7cf002461f02e3826dc901649323fba68104aea Mon Sep 17 00:00:00 2001 From: Brandon Kieft Date: Wed, 2 May 2018 11:06:31 -0700 Subject: [PATCH] Add new ObjC predicates, mostly related to categories Reviewed By: dulmarod Differential Revision: D7809653 fbshipit-source-id: c21f721 --- infer/src/clang/CLintersContext.ml | 9 +- infer/src/clang/CLintersContext.mli | 8 +- infer/src/clang/cFrontend_checkers_main.ml | 5 + infer/src/clang/cPredicates.ml | 201 ++++++++++++++---- infer/src/clang/cPredicates.mli | 157 ++++++++++++-- infer/src/clang/cTL.ml | 30 +++ .../linters-for-test-only/GenericTestClass.m | 145 +++++++++++++ .../al_definitions/linters_example.al | 144 ++++++++++++- .../objc/linters-for-test-only/issues.exp | 30 +++ 9 files changed, 670 insertions(+), 59 deletions(-) create mode 100644 infer/tests/codetoanalyze/objc/linters-for-test-only/GenericTestClass.m diff --git a/infer/src/clang/CLintersContext.ml b/infer/src/clang/CLintersContext.ml index ece53fd55..fdc86ca84 100644 --- a/infer/src/clang/CLintersContext.ml +++ b/infer/src/clang/CLintersContext.ml @@ -19,11 +19,13 @@ type context = ; current_method: Clang_ast_t.decl option ; parent_methods: Clang_ast_t.decl list ; in_synchronized_block: bool - (** True if the translation unit contains an ObjC class impl that's a subclass - of CKComponent or CKComponentController. *) ; is_ck_translation_unit: bool - (** If inside an objc class, contains the objc class (impl or interface) decl. *) + (** True if the translation unit contains an ObjC class impl that's a subclass + of CKComponent or CKComponentController. *) ; current_objc_class: Clang_ast_t.decl option + (** If inside an objc class, contains the objc class (impl or interface) decl. *) + ; current_objc_category: Clang_ast_t.decl option + (** If inside an objc category, contains the objc category (impl or interface) decl. *) ; et_evaluation_node: string option ; if_context: if_context option ; in_for_loop_declaration: bool } @@ -35,6 +37,7 @@ let empty translation_unit_context = ; in_synchronized_block= false ; is_ck_translation_unit= false ; current_objc_class= None + ; current_objc_category= None ; et_evaluation_node= None ; if_context= None ; in_for_loop_declaration= false } diff --git a/infer/src/clang/CLintersContext.mli b/infer/src/clang/CLintersContext.mli index 12de82e64..19be97ba7 100644 --- a/infer/src/clang/CLintersContext.mli +++ b/infer/src/clang/CLintersContext.mli @@ -19,11 +19,13 @@ type context = ; current_method: Clang_ast_t.decl option ; parent_methods: Clang_ast_t.decl list ; in_synchronized_block: bool - (** True if the translation unit contains an ObjC class impl that's a subclass - of CKComponent or CKComponentController. *) ; is_ck_translation_unit: bool - (** If inside an objc class, contains the objc class (impl or interface) decl. *) + (** True if the translation unit contains an ObjC class impl that's a subclass + of CKComponent or CKComponentController. *) ; current_objc_class: Clang_ast_t.decl option + (** If inside an objc class, contains the objc class (impl or interface) decl. *) + ; current_objc_category: Clang_ast_t.decl option + (** If inside an objc category, contains the objc category (impl or interface) decl. *) ; et_evaluation_node: string option ; if_context: if_context option ; in_for_loop_declaration: bool } diff --git a/infer/src/clang/cFrontend_checkers_main.ml b/infer/src/clang/cFrontend_checkers_main.ml index 63dd28a74..7523010a2 100644 --- a/infer/src/clang/cFrontend_checkers_main.ml +++ b/infer/src/clang/cFrontend_checkers_main.ml @@ -338,6 +338,11 @@ and do_frontend_checks_decl (context: CLintersContext.context) let context' = {context with current_objc_class= Some decl} in List.iter ~f:(do_frontend_checks_decl context' map_active) decls ; call_tableaux context' an map_active + | ObjCCategoryImplDecl (_, _, decls, _, _) | ObjCCategoryDecl (_, _, decls, _, _) -> + CFrontend_errors.invoke_set_of_checkers_on_node context an ; + let context' = {context with current_objc_category= Some decl} in + List.iter ~f:(do_frontend_checks_decl context' map_active) decls ; + call_tableaux context' an map_active | _ -> CFrontend_errors.invoke_set_of_checkers_on_node context an ; ( match Clang_ast_proj.get_decl_context_tuple decl with diff --git a/infer/src/clang/cPredicates.ml b/infer/src/clang/cPredicates.ml index af9ca847b..675dffd30 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -148,16 +148,148 @@ let pp_predicate fmt (name_, arglist_) = Format.fprintf fmt "%s(%a)" name (Pp.comma_seq Format.pp_print_string) arglist +(* an is a declaration whose name contains a regexp defined by re *) +let declaration_has_name an name = + match an with + | Ctl_parser_types.Decl d -> ( + match declaration_name d with + | Some decl_name -> + ALVar.compare_str_with_alexp decl_name name + | _ -> + false ) + | _ -> + false + + +let rec is_subclass_of decl name = + match CAst_utils.get_superclass_curr_class_objc_from_decl decl with + | Some super_ref + -> ( + let ndi = match super_ref.Clang_ast_t.dr_name with Some ni -> ni | _ -> assert false in + if ALVar.compare_str_with_alexp ndi.ni_name name then true + else + match CAst_utils.get_decl_opt_with_decl_ref (Some super_ref) with + | Some decl -> + is_subclass_of decl name + | None -> + false ) + | None -> + false + + (* is an objc interface with name expected_name *) let is_objc_interface_named an expected_name = match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCInterfaceDecl (_, ni, _, _, _)) -> - ALVar.compare_str_with_alexp ni.ni_name expected_name + | Ctl_parser_types.Decl (Clang_ast_t.ObjCInterfaceDecl _) -> + declaration_has_name an expected_name + | _ -> + false + + +(* is an objc implementation with name expected_name *) +let is_objc_implementation_named an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCImplementationDecl _) -> + declaration_has_name an expected_name + | _ -> + false + + +let is_objc_class_named an re = is_objc_interface_named an re || is_objc_implementation_named an re + +(* is an objc category interface with class name expected_name *) +let is_objc_category_interface_on_class_named an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCCategoryDecl (_, _, _, _, ocdi)) -> ( + match CAst_utils.get_decl_opt_with_decl_ref ocdi.odi_class_interface with + | Some decl_ref -> + is_objc_interface_named (Decl decl_ref) expected_name + | _ -> + false ) + | _ -> + false + + +(* is an objc category implementation with class name expected_name *) +let is_objc_category_implementation_on_class_named an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCCategoryImplDecl (_, _, _, _, ocidi)) -> ( + match CAst_utils.get_decl_opt_with_decl_ref ocidi.ocidi_class_interface with + | Some decl_ref -> + is_objc_interface_named (Decl decl_ref) expected_name + | _ -> + false ) + | _ -> + false + + +let is_objc_category_on_class_named an re = + is_objc_category_interface_on_class_named an re + || is_objc_category_implementation_on_class_named an re + + +(* is an objc category interface with superclass name expected_name *) +let is_objc_category_interface_on_subclass_of an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCCategoryDecl (_, _, _, _, ocdi)) -> ( + match CAst_utils.get_decl_opt_with_decl_ref ocdi.odi_class_interface with + | Some decl_ref -> + is_subclass_of decl_ref expected_name + | _ -> + false ) + | _ -> + false + + +(* is an objc category implementation with superclass name expected_name *) +let is_objc_category_implementation_on_subclass_of an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCCategoryImplDecl (_, _, _, _, ocidi)) -> ( + match CAst_utils.get_decl_opt_with_decl_ref ocidi.ocidi_class_interface with + | Some decl_ref -> + is_subclass_of decl_ref expected_name + | _ -> + false ) + | _ -> + false + + +let is_objc_category_on_subclass_of an expected_name = + is_objc_category_interface_on_subclass_of an expected_name + || is_objc_category_implementation_on_subclass_of an expected_name + + +(* is an objc category interface with name expected_name *) +let is_objc_category_interface_named an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCCategoryDecl _) -> + declaration_has_name an expected_name | _ -> false -(* checkes whether an object is of a certain class *) +(* is an objc category implementation with name expected_name *) +let is_objc_category_implementation_named an expected_name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCCategoryImplDecl _) -> + declaration_has_name an expected_name + | _ -> + false + + +let is_objc_category_named an re = + is_objc_category_interface_named an re || is_objc_category_implementation_named an re + + +let is_objc_method_named an name = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCMethodDecl _) -> + declaration_has_name an name + | _ -> + false + + +(* checks whether an object is of a certain class *) let is_object_of_class_named receiver cname = let open Clang_ast_t in match receiver with @@ -459,22 +591,6 @@ let is_in_block context = match context.CLintersContext.current_method with Some (BlockDecl _) -> true | _ -> false -let rec is_subclass_of decl name = - match CAst_utils.get_superclass_curr_class_objc_from_decl decl with - | Some super_ref - -> ( - let ndi = match super_ref.Clang_ast_t.dr_name with Some ni -> ni | _ -> assert false in - if ALVar.compare_str_with_alexp ndi.ni_name name then true - else - match CAst_utils.get_decl_opt_with_decl_ref (Some super_ref) with - | Some decl -> - is_subclass_of decl name - | None -> - false ) - | None -> - false - - let is_in_objc_subclass_of context name = match context.CLintersContext.current_objc_class with | Some cls -> @@ -486,7 +602,31 @@ let is_in_objc_subclass_of context name = let is_in_objc_class_named context name = match context.CLintersContext.current_objc_class with | Some cls -> - is_objc_interface_named (Decl cls) name + is_objc_class_named (Decl cls) name + | None -> + false + + +let is_in_objc_category_on_class_named context name = + match context.CLintersContext.current_objc_category with + | Some cat -> + is_objc_category_on_class_named (Decl cat) name + | None -> + false + + +let is_in_objc_category_on_subclass_of context name = + match context.CLintersContext.current_objc_category with + | Some cat -> + is_objc_category_on_subclass_of (Decl cat) name + | None -> + false + + +let is_in_objc_category_named context name = + match context.CLintersContext.current_objc_category with + | Some cat -> + is_objc_category_named (Decl cat) name | None -> false @@ -573,17 +713,7 @@ let isa an classname = false -(* an is a declaration whose name contains a regexp defined by re *) -let declaration_has_name an name = - match an with - | Ctl_parser_types.Decl d -> ( - match Clang_ast_proj.get_named_decl_tuple d with - | Some (_, ndi) -> - ALVar.compare_str_with_alexp ndi.ni_name name - | _ -> - false ) - | _ -> - false +let is_class an re = is_objc_class_named an re (* an is an expression @selector with whose name in the language of re *) @@ -595,15 +725,6 @@ let is_at_selector_with_name an re = false -let is_class an re = - match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCInterfaceDecl _) - | Ctl_parser_types.Decl (Clang_ast_t.ObjCImplementationDecl _) -> - declaration_has_name an re - | _ -> - false - - let iphoneos_target_sdk_version_by_path (cxt: CLintersContext.context) = let source_file = cxt.translation_unit_context.source_file in let regex_version_opt = diff --git a/infer/src/clang/cPredicates.mli b/infer/src/clang/cPredicates.mli index 37af73307..9baaf5510 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -20,11 +20,11 @@ val call_method : Ctl_parser_types.ast_node -> ALVar.alexp -> bool (** 'call_method an m an' is true iff node an is a call to an ObjC method with name containing string m *) val call_class_method : Ctl_parser_types.ast_node -> ALVar.alexp -> ALVar.alexp -> bool -(** 'call_class_method an cname mname' is true iff node an is a call to an ObjC method of class cname +(** 'call_class_method an cname mname' is true iff node an is a call to an ObjC method of class cname and the name of the method contains mname *) val call_instance_method : Ctl_parser_types.ast_node -> ALVar.alexp -> ALVar.alexp -> bool -(** 'call_instance_method an cname mname' is true iff an is a node calling an ObjC method of an +(** 'call_instance_method an cname mname' is true iff an is a node calling an ObjC method of an object of class cname and the method name contains mname *) @@ -36,11 +36,6 @@ val is_enum_constant : Ctl_parser_types.ast_node -> ALVar.alexp -> bool val is_enum_constant_of_enum : Ctl_parser_types.ast_node -> ALVar.alexp -> bool -val is_objc_interface_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool -(** 'is_objc_interface_named an expected_name' is true iff an is an objc interface with name expected_name *) - -val is_objc_extension : CLintersContext.context -> bool - val is_global_var : Ctl_parser_types.ast_node -> bool (** 'is_global_var an' is true iff an is a global variable (but not a static local) *) @@ -89,8 +84,114 @@ val is_in_cxx_method : CLintersContext.context -> ALVar.alexp -> bool val is_in_function : CLintersContext.context -> ALVar.alexp -> bool (** 'is_in_function context name' is true if the curent node is within a function whose name contains 'name' *) -val is_in_objc_method : CLintersContext.context -> ALVar.alexp -> bool -(** 'is_in_objc_method context name' is true if the curent node is within an ObjC method whose name contains 'name' *) +val is_objc_extension : CLintersContext.context -> bool +(** + * Checks if the current file has an ObjC file extension (I.E. '.m' or '.mm') + *) + +val is_objc_class_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCInterfaceDecl or ObjCImplementationDecl + * node whose name matches the provided REGEXP + * + * Matches on MyClass in: + * @interface MyClass + * @implementation MyClass + *) + +val is_objc_interface_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCInterfaceDecl node + * whose name matches the provided REGEXP + * + * Matches on MyClass in @interface MyClass + *) + +val is_objc_implementation_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCImplementationDecl node + * whose name matches the provided REGEXP + * + * Matches on MyClass in @implementation MyClass + *) + +val is_objc_category_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryDecl or ObjCCategoryImplDecl + * node whose name matches the provided REGEXP + * + * Matches on MyCategory in: + * @interface MyClass (MyCategory) + * @implementation MyClass (MyCategory) + *) + +val is_objc_category_interface_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryDecl node + * whose name matches the provided REGEXP + * + * Matches on MyCategory in @interface MyClass (MyCategory) + *) + +val is_objc_category_implementation_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryImplDecl node + * whose name matches the provided REGEXP + * + * Matches on MyCategory in @implementation MyClass (MyCategory) + *) + +val is_objc_category_on_class_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryDecl or ObjCCategoryImplDecl + * node whose class's name matches the provided REGEXP + * + * Matches on MyClass in: + * @interface MyClass (MyCategory) + * @implementation MyClass (MyCategory) + *) + +val is_objc_category_interface_on_class_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryDecl node + * whose class's name matches the provided REGEXP + * + * Matches on MyClass in @interface MyClass (MyCategory) + *) + +val is_objc_category_implementation_on_class_named : + Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryImplDecl node + * whose class's name matches the provided REGEXP + * + * Matches on MyClass in @implementation MyClass (MyCategory) + *) + +val is_objc_category_on_subclass_of : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryDecl or ObjCCategoryImplDecl + * node whose class inherits from a class whose name matches the provided REGEXP + *) + +val is_objc_category_interface_on_subclass_of : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryDecl node whose class + * inherits from a class whose name matches the provided REGEXP + *) + +val is_objc_category_implementation_on_subclass_of : + Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCCategoryImplDecl node whose class + * inherits from a class whose name matches the provided REGEXP + *) + +val is_objc_method_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** + * Checks if the current node is an ObjCMethodDecl node + * whose name matches the provided REGEXP + *) val is_objc_constructor : CLintersContext.context -> bool (** 'is_in_objc_constructor context' is true if the curent node is within an ObjC constructor *) @@ -99,10 +200,42 @@ val is_objc_dealloc : CLintersContext.context -> bool (** 'is_in_objc_dealloc context' is true if the curent node is within an ObjC dealloc method *) val is_in_objc_subclass_of : CLintersContext.context -> ALVar.alexp -> bool -(** 'is_in_objc_subclass_of context cname' is true if the current node is within the context of a subclass of class cname*) +(** + * Checks if the current node is a subnode of an ObjCInterfaceDecl or + * ObjCImplementationDecl node which inherits from a class whose + * name matches the provided REGEXP + *) val is_in_objc_class_named : CLintersContext.context -> ALVar.alexp -> bool -(** 'is_in_objc_class_named context cname' is true if the current node is within the context of a of class cname*) +(** + * Checks if the current node is a subnode of an ObjCInterfaceDecl or + * ObjCImplementationDecl node whose name matches the provided REGEXP + *) + +val is_in_objc_category_on_class_named : CLintersContext.context -> ALVar.alexp -> bool +(** + * Checks if the current node is a subnode of an ObjCCategoryDecl or + * ObjCCategoryImplDecl node whose class's name matches the provided REGEXP + *) + +val is_in_objc_category_on_subclass_of : CLintersContext.context -> ALVar.alexp -> bool +(** + * Checks if the current node is a subnode of an ObjCCategoryDecl or + * ObjCCategoryImplDecl node whose class inherits from a class whose + * name matches the provided REGEXP + *) + +val is_in_objc_category_named : CLintersContext.context -> ALVar.alexp -> bool +(** + * Checks if the current node is a subnode of an ObjCCategoryDecl or + * ObjCCategoryImplDecl node whose name matches the provided REGEXP + *) + +val is_in_objc_method : CLintersContext.context -> ALVar.alexp -> bool +(** + * Checks if the current node, or a parent node, is an ObjCMethodDecl node + * whose name matches the provided REGEXP + *) val captures_cxx_references : Ctl_parser_types.ast_node -> bool (** 'captures_cxx_references an' is true iff the node an captures some CXX references *) @@ -126,7 +259,7 @@ val declaration_has_name : Ctl_parser_types.ast_node -> ALVar.alexp -> bool val declaration_ref_name : ?kind:Clang_ast_t.decl_kind -> Ctl_parser_types.ast_node -> ALVar.alexp -> bool -(** 'declaration_ref_has_name an n' is true iff node an is a DeclRefExpr with name containing string n. The optional parameter kind +(** 'declaration_ref_has_name an n' is true iff node an is a DeclRefExpr with name containing string n. The optional parameter kind allow to distinguish between special kind of decl_ref_exprs like 'is_enum_constant'. *) val is_class : Ctl_parser_types.ast_node -> ALVar.alexp -> bool diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index 10494946b..ab041a120 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -1022,6 +1022,12 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.is_in_objc_class_named lcxt name | "is_in_objc_subclass_of", [name], _ -> CPredicates.is_in_objc_subclass_of lcxt name + | "is_in_objc_category_on_class_named", [name], _ -> + CPredicates.is_in_objc_category_on_class_named lcxt name + | "is_in_objc_category_on_subclass_of", [name], _ -> + CPredicates.is_in_objc_category_on_subclass_of lcxt name + | "is_in_objc_category_named", [name], _ -> + CPredicates.is_in_objc_category_named lcxt name | "is_ivar_atomic", [], an -> CPredicates.is_ivar_atomic an | "is_method_property_accessor_of_ivar", [], an -> @@ -1036,6 +1042,30 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.is_objc_extension lcxt | "is_objc_interface_named", [name], an -> CPredicates.is_objc_interface_named an name + | "is_objc_implementation_named", [name], an -> + CPredicates.is_objc_implementation_named an name + | "is_objc_class_named", [name], an -> + CPredicates.is_objc_class_named an name + | "is_objc_category_interface_on_class_named", [name], an -> + CPredicates.is_objc_category_interface_on_class_named an name + | "is_objc_category_implementation_on_class_named", [name], an -> + CPredicates.is_objc_category_implementation_on_class_named an name + | "is_objc_category_on_class_named", [cname], an -> + CPredicates.is_objc_category_on_class_named an cname + | "is_objc_category_interface_named", [name], an -> + CPredicates.is_objc_category_interface_named an name + | "is_objc_category_implementation_named", [name], an -> + CPredicates.is_objc_category_implementation_named an name + | "is_objc_category_named", [cname], an -> + CPredicates.is_objc_category_named an cname + | "is_objc_category_interface_on_subclass_of", [name], an -> + CPredicates.is_objc_category_interface_on_subclass_of an name + | "is_objc_category_implementation_on_subclass_of", [name], an -> + CPredicates.is_objc_category_implementation_on_subclass_of an name + | "is_objc_category_on_subclass_of", [name], an -> + CPredicates.is_objc_category_on_subclass_of an name + | "is_objc_method_named", [name], an -> + CPredicates.is_objc_method_named an name | "is_property_pointer_type", [], an -> CPredicates.is_property_pointer_type an | "is_strong_property", [], an -> diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/GenericTestClass.m b/infer/tests/codetoanalyze/objc/linters-for-test-only/GenericTestClass.m new file mode 100644 index 000000000..ee515b5bb --- /dev/null +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/GenericTestClass.m @@ -0,0 +1,145 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +/* ======== My Base Class ======== */ + +@protocol MyBaseClassProtocol +@required +- (void)myBaseClassProtocolRequiredMethod; +@optional +- (void)myBaseClassProtocolOptionalMethod; +@end + +@interface MyBaseClass : NSObject + +@property int myBaseClassProperty; +- (void)myBaseClassMethod; + +@end + +@interface MyBaseClass () + +- (void)myBaseClassInterfaceExtensionMethod; + +@end + +@implementation MyBaseClass + +- (void)myBaseClassMethod { +} +- (void)myBaseClassInterfaceExtensionMethod { +} +- (void)myBaseClassProtocolRequiredMethod { +} +- (void)myBaseClassProtocolOptionalMethod { +} +- (int)myBaseClassProperty { + return 0; +} +- (void)setMyBaseClassProperty:(int)value { +} + +@end + +@interface MyBaseClass (MyBaseClassCategory) + +- (void)myBaseClassCategoryMethod; + +@end + +@implementation MyBaseClass (MyBaseClassCategory) + +- (void)myBaseClassCategoryMethod { +} + +@end + +/* ======== My Subclass ======== */ + +@protocol MySubclassProtocol +@required +- (void)mySubclassProtocolRequiredMethod; +@optional +- (void)mySubclassProtocolOptionalMethod; +@end + +@protocol MySubclassProtocol2 +@required +- (void)mySubclassProtocol2RequiredMethod; +@optional +- (void)mySubclassProtocol2OptionalMethod; +@end + +@protocol MySubclassSubprotocol +@required +- (void)mySubclassSubprotocol2RequiredMethod; +@optional +- (void)mySubclassSubprotocol2OptionalMethod; +@end + +@interface MySubclass : MyBaseClass + +- (void)mySubclassMethod; + +@end + +@implementation MySubclass + +- (void)myBaseClassMethod { + [super myBaseClassMethod]; +} + +- (void)myBaseClassInterfaceExtensionMethod { + [super myBaseClassInterfaceExtensionMethod]; +} + +- (void)myBaseClassProtocolRequiredMethod { + [super myBaseClassProtocolRequiredMethod]; +} + +- (void)myBaseClassProtocolOptionalMethod { + [super myBaseClassProtocolOptionalMethod]; +} + +- (int)myBaseClassProperty { + return [super myBaseClassProperty]; +} + +- (void)setMyBaseClassProperty:(int)value { + [super setMyBaseClassProperty:value]; +} + +- (void)myBaseClassCategoryMethod { + [super myBaseClassCategoryMethod]; +} + +- (void)mySubclassMethod { +} +- (void)mySubclassProtocolRequiredMethod { +} +- (void)mySubclassProtocolOptionalMethod { +} +- (void)mySubclassProtocol2RequiredMethod { +} +- (void)mySubclassProtocol2OptionalMethod { +} +- (void)mySubclassSubprotocol2RequiredMethod { +} +- (void)mySubclassSubprotocol2OptionalMethod { +} + +@end + +@interface MySubclass (MySubclassCategory) + +- (void)mySubclassCategoryMethod; + +@end + +@implementation MySubclass (MySubclassCategory) + +- (void)mySubclassCategoryMethod { +} + +@end diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/al_definitions/linters_example.al b/infer/tests/codetoanalyze/objc/linters-for-test-only/al_definitions/linters_example.al index e52db5c13..046211d3c 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/al_definitions/linters_example.al +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/al_definitions/linters_example.al @@ -128,7 +128,7 @@ DEFINE-CHECKER TEST_BUILTIN_TYPE = { SET report_when = WHEN - is_in_objc_class_named("TestType") AND + is_in_objc_class_named("TestType") AND (method_return_type("void") OR method_return_type("bool") OR method_return_type("char") @@ -443,3 +443,145 @@ DEFINE-CHECKER TEST_PARAMETER_SEL_TYPE = { SET message = "Method %name% is not called with super."; }; + +DEFINE-CHECKER TEST_IF_METHOD_IS_IN_CATEGORY_NAMED = { + + SET report_when = + WHEN + is_in_objc_category_named("MyBaseClassCategory") + HOLDS-IN-NODE ObjCMethodDecl; + + SET message = "Method %name% is in the category named MyBaseClassCategory."; + +}; + +DEFINE-CHECKER TEST_IF_METHOD_IS_IN_CATEGORY_ON_CLASS_NAMED = { + + SET report_when = + WHEN + is_in_objc_category_on_class_named("MyBaseClass") + HOLDS-IN-NODE ObjCMethodDecl; + + SET message = "Method %name% is in the category on a class named MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_METHOD_IS_IN_CATEGORY_ON_SUBCLASS_OF = { + + SET report_when = + WHEN + is_in_objc_category_on_subclass_of("MyBaseClass") + HOLDS-IN-NODE ObjCMethodDecl; + + SET message = "Method %name% is in the category on a subclass of MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_INTERFACE_NAMED = { + + SET report_when = + is_objc_category_interface_named("MyBaseClassCategory"); + + SET message = "Node is a category interface named MyBaseClassCategory."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_IMPLEMENTATION_NAMED = { + + SET report_when = + is_objc_category_implementation_named("MyBaseClassCategory"); + + SET message = "Node is a category implementation named MyBaseClassCategory."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_NAMED = { + + SET report_when = + is_objc_category_named("MyBaseClassCategory"); + + SET message = "Node is a category named MyBaseClassCategory."; + +}; + + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_INTERFACE_ON_CLASS_NAMED = { + + SET report_when = + is_objc_category_interface_on_class_named("MyBaseClass"); + + SET message = "Node is a category interface on a class named MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_IMPLEMENTATION_ON_CLASS_NAMED = { + + SET report_when = + is_objc_category_implementation_on_class_named("MyBaseClass"); + + SET message = "Node is a category implementation on a class named MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_ON_CLASS_NAMED = { + + SET report_when = + is_objc_category_on_class_named("MyBaseClass"); + + SET message = "Node is a category on a class named MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_INTERFACE_ON_SUBCLASS_OF = { + + SET report_when = + is_objc_category_interface_on_subclass_of("MyBaseClass"); + + SET message = "Node is a category interface on a subclass of MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_IMPLEMENTATION_ON_SUBCLASS_OF = { + + SET report_when = + is_objc_category_implementation_on_subclass_of("MyBaseClass"); + + SET message = "Node is a category implementation on a subclass of MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CATEGORY_ON_SUBCLASS_OF = { + + SET report_when = + is_objc_category_on_subclass_of("MyBaseClass"); + + SET message = "Node is a category on a subclass of MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_IMPLEMENTATION_NAMED = { + + SET report_when = + is_objc_implementation_named("MyBaseClass"); + + SET message = "Node is an implementation named MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_CLASS_NAMED = { + + SET report_when = + is_objc_class_named("MyBaseClass"); + + SET message = "Node is a class named MyBaseClass."; + +}; + +DEFINE-CHECKER TEST_IF_IS_METHOD_NAMED = { + + SET report_when = + is_objc_method_named("mySubclassMethod"); + + SET message = "Node is a method named mySubclassMethod."; + +}; diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp index 21c757c03..ba57f2803 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp @@ -1,6 +1,36 @@ codetoanalyze/objc/linters-for-test-only/CallingAMethodWithSelf.m, CallingAMethodWithSelfBase_testView, 17, TEST_VAR_TYPE_CHECK, WARNING, [] codetoanalyze/objc/linters-for-test-only/CallingAMethodWithSelf.m, CallingAMethodWithSelf_methodThatShallComplain, 41, TEST_IF_VIEW_METHOD_IS_NOT_CALLED_WITH_SUPER, WARNING, [] codetoanalyze/objc/linters-for-test-only/CallingAMethodWithSelf.m, CallingAMethodWithSelf_testView, 45, TEST_VAR_TYPE_CHECK, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 14, TEST_IF_IS_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 21, TEST_IF_IS_CATEGORY_INTERFACE_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 21, TEST_IF_IS_CATEGORY_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 27, TEST_IF_IS_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 27, TEST_IF_IS_IMPLEMENTATION_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 45, TEST_IF_IS_CATEGORY_INTERFACE_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 45, TEST_IF_IS_CATEGORY_INTERFACE_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 45, TEST_IF_IS_CATEGORY_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 45, TEST_IF_IS_CATEGORY_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 51, TEST_IF_IS_CATEGORY_IMPLEMENTATION_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 51, TEST_IF_IS_CATEGORY_IMPLEMENTATION_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 51, TEST_IF_IS_CATEGORY_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 51, TEST_IF_IS_CATEGORY_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 134, TEST_IF_IS_CATEGORY_INTERFACE_ON_SUBCLASS_OF, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 134, TEST_IF_IS_CATEGORY_ON_SUBCLASS_OF, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 140, TEST_IF_IS_CATEGORY_IMPLEMENTATION_ON_SUBCLASS_OF, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, Linters_dummy_method, 140, TEST_IF_IS_CATEGORY_ON_SUBCLASS_OF, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_myBaseClassCategoryMethod, 47, TEST_IF_METHOD_IS_IN_CATEGORY_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_myBaseClassCategoryMethod, 47, TEST_IF_METHOD_IS_IN_CATEGORY_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_myBaseClassCategoryMethod, 53, TEST_IF_METHOD_IS_IN_CATEGORY_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_myBaseClassCategoryMethod, 53, TEST_IF_METHOD_IS_IN_CATEGORY_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_myBaseClassInterfaceExtensionMethod, 23, TEST_IF_METHOD_IS_IN_CATEGORY_ON_CLASS_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_myBaseClassProperty, 37, TEST_RETURN_METHOD, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MyBaseClass_setMyBaseClassProperty, 40, TEST_PARAM_TYPE_CHECK2, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MySubclass_myBaseClassProperty, 105, TEST_RETURN_METHOD, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MySubclass_mySubclassCategoryMethod, 136, TEST_IF_METHOD_IS_IN_CATEGORY_ON_SUBCLASS_OF, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MySubclass_mySubclassCategoryMethod, 142, TEST_IF_METHOD_IS_IN_CATEGORY_ON_SUBCLASS_OF, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MySubclass_mySubclassMethod, 83, TEST_IF_IS_METHOD_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MySubclass_mySubclassMethod, 117, TEST_IF_IS_METHOD_NAMED, WARNING, [] +codetoanalyze/objc/linters-for-test-only/GenericTestClass.m, MySubclass_setMyBaseClassProperty, 109, TEST_PARAM_TYPE_CHECK2, WARNING, [] codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, InContextOfMethodsTest_method, 16, TEST_IN_METHOD_CONTEXT, WARNING, [] codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, InContextOfMethodsTest_method, 16, TEST_VAR_TYPE_CHECK, WARNING, [] codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, function, 27, TEST_IN_FUNCTION_CONTEXT, WARNING, []