diff --git a/infer/src/clang/cPredicates.ml b/infer/src/clang/cPredicates.ml index d6bd711ef..1c069c9c5 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -499,6 +499,14 @@ let call_instance_method an mname = false +let adhere_to_protocol an = + match an with + | Ctl_parser_types.Decl (Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, idi)) -> + not (List.is_empty idi.otdi_protocols) + | _ -> + false + + let is_objc_extension lcxt = CGeneral_utils.is_objc_extension lcxt.CLintersContext.translation_unit_context @@ -527,6 +535,26 @@ let is_qual_type_const an = false +let objc_class_has_only_one_constructor_method_named an re = + let open Clang_ast_t in + let is_class_method d = + match d with + | Clang_ast_t.ObjCMethodDecl (_, _, omdi) -> + not omdi.omdi_is_instance_method + | _ -> + false + in + match an with + | Ctl_parser_types.Decl (ObjCImplementationDecl (_, _, decls, _, _)) -> ( + match List.filter decls ~f:(fun d -> is_class_method d) with + | [n] -> + is_objc_class_method_named (Ctl_parser_types.Decl n) re + | _ -> + false ) + | _ -> + false + + let has_init_list_const_expr an = let rec fold lexp = List.fold ~f:(fun acc e -> is_const_expr' e && acc) ~init:true lexp and is_const_expr' exp = @@ -789,6 +817,26 @@ let is_in_cxx_destructor context name = false +let cxx_construct_expr_has_no_parameters an = + match an with + | Ctl_parser_types.Stmt (Clang_ast_t.CXXConstructExpr (_, [], _, _)) -> + true + | _ -> + false + + +let cxx_construct_expr_has_name an name = + match an with + | Ctl_parser_types.Stmt (Clang_ast_t.CXXConstructExpr (_, _, _, xcei)) -> ( + match xcei.xcei_decl_ref.dr_name with + | Some ni -> + ALVar.compare_str_with_alexp ni.ni_name name + | _ -> + false ) + | _ -> + false + + let is_in_block context = match context.CLintersContext.current_method with Some (BlockDecl _) -> true | _ -> false @@ -930,6 +978,16 @@ let is_receiver_super an = false +let is_receiver_self an = + match an with + | Ctl_parser_types.Stmt (Clang_ast_t.ObjCMessageExpr (_, fst_param :: _, _, _)) -> + CAst_utils.exists_eventually_st + (decl_ref_name ~kind:`ImplicitParam) + (ALVar.Const "self") fst_param + | _ -> + false + + let captures_cxx_references an = List.length (captured_variables_cxx_ref an) > 0 let is_binop_with_kind an alexp_kind = diff --git a/infer/src/clang/cPredicates.mli b/infer/src/clang/cPredicates.mli index 5cf246939..4e60ddde9 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -196,6 +196,9 @@ val is_objc_category_implementation_on_subclass_of : * inherits from a class whose name matches the provided REGEXP *) +val adhere_to_protocol : Ctl_parser_types.ast_node -> bool +(** true if an objC class adhere to a protocol *) + val is_objc_protocol_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool (** * Checks if the current node is an ObjCProtocolDecl node @@ -223,6 +226,11 @@ val is_objc_method_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool val is_objc_constructor : CLintersContext.context -> bool (** 'is_in_objc_constructor context' is true if the curent node is within an ObjC constructor *) +val objc_class_has_only_one_constructor_method_named : + Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** true if an ObjC class has only one class method and is a constructor +whose name matches the provided REGEXP *) + val is_objc_dealloc : CLintersContext.context -> bool (** 'is_in_objc_dealloc context' is true if the curent node is within an ObjC dealloc method *) @@ -445,11 +453,20 @@ val is_receiver_super : Ctl_parser_types.ast_node -> bool * Matches on [super myMethod]; *) +val is_receiver_self : Ctl_parser_types.ast_node -> bool +(** + * Checks if the current node is an ObjCMessageExpr node and has a + * receiver which is equal to 'self'. + *) + val is_at_selector_with_name : Ctl_parser_types.ast_node -> ALVar.alexp -> bool (** an is an expression @selector with whose name in the language of re *) val has_visibility_attribute : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +val cxx_construct_expr_has_name : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +(** true if the node is a CXXConstruct with name matching the provided REGEXP *) + val has_used_attribute : Ctl_parser_types.ast_node -> bool val iphoneos_target_sdk_version_by_path : CLintersContext.context -> string option @@ -472,3 +489,6 @@ val is_cxx_copy_constructor : Ctl_parser_types.ast_node -> bool val is_init_expr_cxx11_constant : Ctl_parser_types.ast_node -> bool (** true if the current node is classified as C++11 constant expression by the AST. It works only for VarDecl init expr *) + +val cxx_construct_expr_has_no_parameters : Ctl_parser_types.ast_node -> bool +(** true if a construct expr has no subexpressions *) diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index e877ef7e8..78bbecd6d 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -1014,6 +1014,8 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.is_global_var an | "is_static_local_var", [], an -> CPredicates.is_static_local_var an + | "adhere_to_protocol", [], an -> + CPredicates.adhere_to_protocol an | "is_in_block", [], _ -> CPredicates.is_in_block lcxt | "is_in_cxx_constructor", [name], _ -> @@ -1066,6 +1068,8 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.is_node an nodename | "is_objc_constructor", [], _ -> CPredicates.is_objc_constructor lcxt + | "objc_class_has_only_one_constructor_method_named", [name], an -> + CPredicates.objc_class_has_only_one_constructor_method_named an name | "is_objc_dealloc", [], _ -> CPredicates.is_objc_dealloc lcxt | "is_objc_extension", [], _ -> @@ -1126,6 +1130,8 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.is_receiver_class_named lcxt an name | "is_receiver_super", [], an -> CPredicates.is_receiver_super an + | "is_receiver_self", [], an -> + CPredicates.is_receiver_self an | "iphoneos_target_sdk_version_greater_or_equal", [version], _ -> CPredicates.iphoneos_target_sdk_version_greater_or_equal lcxt (ALVar.alexp_to_string version) | "method_return_type", [typ], an -> @@ -1136,6 +1142,8 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.using_namespace an namespace | "is_at_selector_with_name", [name], an -> CPredicates.is_at_selector_with_name an name + | "cxx_construct_expr_has_name", [name], an -> + CPredicates.cxx_construct_expr_has_name an name | "has_type_const_ptr_to_objc_class", [], an -> CPredicates.has_type_const_ptr_to_objc_class an | "has_type_subprotocol_of", [protname], an -> @@ -1152,6 +1160,8 @@ let rec eval_Atomic pred_name_ args an lcxt = CPredicates.is_cxx_copy_constructor an | "is_init_expr_cxx11_constant", [], an -> CPredicates.is_init_expr_cxx11_constant an + | "cxx_construct_expr_has_no_parameters", [], an -> + CPredicates.cxx_construct_expr_has_no_parameters an | _ -> L.(die ExternalError) "Undefined Predicate or wrong set of arguments: '%s'" pred_name diff --git a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp index 9b7c52eab..a5c787202 100644 --- a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp +++ b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp @@ -27,4 +27,6 @@ codetoanalyze/objcpp/linters-for-test-only/TestStructFieldChecks.mm, buttonCompo codetoanalyze/objcpp/linters-for-test-only/TestStructFieldChecks.mm, buttonComponent, 33, TITLE_NOT_INITIALIZED, no_bucket, WARNING, [] codetoanalyze/objcpp/linters-for-test-only/TestStructFieldChecks.mm, buttonComponent, 37, TITLE_NOT_INITIALIZED, no_bucket, WARNING, [] codetoanalyze/objcpp/linters-for-test-only/hash_test.mm, std::hash_NSObject_*__operator(), 12, DISCOURAGED_HASH_METHOD_INVOCATION, no_bucket, WARNING, [] +codetoanalyze/objcpp/linters-for-test-only/stateless.m, Linters_dummy_method, 21, ADHERE_TO_PROTOCOL, no_bucket, WARNING, [] +codetoanalyze/objcpp/linters-for-test-only/stateless.m, Linters_dummy_method, 24, ONLY_ONE_CLASS_METHOD, no_bucket, WARNING, [] codetoanalyze/objcpp/linters-for-test-only/static.m, objc_block_1, 18, OBJC_BLOCK_CAPTURING_VALUES, no_bucket, WARNING, [] diff --git a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al index 554fdd83e..202a1720f 100644 --- a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al +++ b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al @@ -209,3 +209,27 @@ DEFINE-CHECKER OBJC_BLOCK_CAPTURING_VALUES = { SET message = "ObjC Block capturing values"; SET mode = "ON"; }; + +DEFINE-CHECKER ADHERE_TO_PROTOCOL = { + + SET report_when = + WHEN + adhere_to_protocol() + HOLDS-IN-NODE ObjCInterfaceDecl; + + SET message = "Found class adhering to protocol"; + SET mode = "ON"; + +}; + +DEFINE-CHECKER ONLY_ONE_CLASS_METHOD = { + + SET report_when = + WHEN + objc_class_has_only_one_constructor_method_named(REGEXP("newWith.+")) + HOLDS-IN-NODE ObjCImplementationDecl; + + SET message = "Found class with only one class method"; + SET mode = "ON"; + +}; diff --git a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/stateless.m b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/stateless.m new file mode 100644 index 000000000..dcd4b7ecd --- /dev/null +++ b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/stateless.m @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#import + +@interface B : NSObject +@end + +@implementation B ++ (void)newWithB { +} + ++ (void)foo { +} + +@end + +@interface A +@end + +@implementation A + +- (int)foo { + return 0; +} + ++ (int)newWithBA { + return 0; +} + +@end