From 18782be7c9d0584c1bf5cb2493d6d5b0fa75c265 Mon Sep 17 00:00:00 2001 From: Dulma Churchill Date: Thu, 9 Nov 2017 08:31:43 -0800 Subject: [PATCH] [linters] Adding a new predicate for in an ObjC class that is subclass of some class Reviewed By: ddino Differential Revision: D6284441 fbshipit-source-id: bcdd389 --- infer/src/clang/cAst_utils.ml | 27 ++++++++++ infer/src/clang/cAst_utils.mli | 2 + infer/src/clang/cMethod_trans.ml | 52 +++++++------------ infer/src/clang/cPredicates.ml | 24 +++++++++ infer/src/clang/cPredicates.mli | 4 ++ infer/src/clang/cTL.ml | 2 + .../linters-for-test-only/InSubclassExample.m | 41 +++++++++++++++ .../objc/linters-for-test-only/issues.exp | 5 ++ .../linters-for-test-only/linters_example.al | 7 +++ 9 files changed, 130 insertions(+), 34 deletions(-) create mode 100644 infer/tests/codetoanalyze/objc/linters-for-test-only/InSubclassExample.m diff --git a/infer/src/clang/cAst_utils.ml b/infer/src/clang/cAst_utils.ml index 23a6ed821..94c66e754 100644 --- a/infer/src/clang/cAst_utils.ml +++ b/infer/src/clang/cAst_utils.ml @@ -568,3 +568,30 @@ let has_block_attribute decl = let is_implicit_decl decl = let decl_info = Clang_ast_proj.get_decl_tuple decl in decl_info.Clang_ast_t.di_is_implicit + + +let get_superclass_curr_class_objc_from_decl (decl: Clang_ast_t.decl) = + match decl with + | ObjCInterfaceDecl (_, _, _, _, otdi) -> + otdi.otdi_super + | ObjCImplementationDecl (_, ni, _, _, oi) -> ( + match + oi.Clang_ast_t.oidi_class_interface + |> Option.map ~f:(fun dr -> dr.Clang_ast_t.dr_decl_pointer) + |> Option.value_map ~f:get_decl ~default:None + with + | Some ObjCInterfaceDecl (_, _, _, _, otdi) -> + otdi.otdi_super + | _ -> + Logging.die InternalError + "Expected that ObjCImplementationDecl always has a pointer to it's interface, but wasn't the case with %s" + ni.Clang_ast_t.ni_name ) + | ObjCCategoryDecl (_, _, _, _, ocdi) -> + ocdi.odi_class_interface + | ObjCCategoryImplDecl (_, _, _, _, ocidi) -> + ocidi.ocidi_class_interface + | decl -> + Logging.die InternalError + "Expected to be called only with ObjCInterfaceDecl, ObjCImplementationDecl, ObjCCategoryDecl or ObjCCategoryImplDecl, but got %s" + (Clang_ast_proj.get_decl_kind_string decl) + diff --git a/infer/src/clang/cAst_utils.mli b/infer/src/clang/cAst_utils.mli index 6ab24a27a..0c0f76b95 100644 --- a/infer/src/clang/cAst_utils.mli +++ b/infer/src/clang/cAst_utils.mli @@ -155,3 +155,5 @@ val is_std_vector : Clang_ast_t.qual_type -> bool val has_block_attribute : Clang_ast_t.decl -> bool val is_implicit_decl : Clang_ast_t.decl -> bool + +val get_superclass_curr_class_objc_from_decl : Clang_ast_t.decl -> Clang_ast_t.decl_ref option diff --git a/infer/src/clang/cMethod_trans.ml b/infer/src/clang/cMethod_trans.ml index 9848becf0..81833a925 100644 --- a/infer/src/clang/cMethod_trans.ml +++ b/infer/src/clang/cMethod_trans.ml @@ -262,41 +262,25 @@ let get_method_name_from_clang tenv ms_opt = let get_superclass_curr_class_objc context = let open Clang_ast_t in - let super_of_decl_ref_opt decl_ref = - match - decl_ref |> Option.value_map ~f:(fun dr -> dr.dr_name) ~default:None - |> Option.map ~f:CAst_utils.get_qualified_name - with - | Some name -> - name - | None -> - assert false - in - let retreive_super_name ptr = - match CAst_utils.get_decl ptr with - | Some ObjCInterfaceDecl (_, _, _, _, otdi) -> - super_of_decl_ref_opt otdi.otdi_super - | Some ObjCImplementationDecl (_, _, _, _, oi) -> ( - match - oi.Clang_ast_t.oidi_class_interface |> Option.map ~f:(fun dr -> dr.dr_decl_pointer) - |> Option.value_map ~f:CAst_utils.get_decl ~default:None - with - | Some ObjCInterfaceDecl (_, _, _, _, otdi) -> - super_of_decl_ref_opt otdi.otdi_super - | _ -> - assert false ) - | Some ObjCCategoryDecl (_, _, _, _, ocdi) -> - super_of_decl_ref_opt ocdi.odi_class_interface - | Some ObjCCategoryImplDecl (_, _, _, _, ocidi) -> - super_of_decl_ref_opt ocidi.ocidi_class_interface - | _ -> - assert false + let decl_ref = + match CContext.get_curr_class context with + | CContext.ContextClsDeclPtr ptr -> ( + match CAst_utils.get_decl ptr with + | Some decl -> + CAst_utils.get_superclass_curr_class_objc_from_decl decl + | None -> + Logging.die InternalError + "Expected that the current class ptr in the context is a valid pointer to class decl, but didn't find declaration, ptr is %d " + ptr ) + | CContext.ContextNoCls -> + Logging.die InternalError + "This should only be called in the context of a class, but got CContext.ContextNoCls" in - match CContext.get_curr_class context with - | CContext.ContextClsDeclPtr ptr -> - Typ.Name.Objc.from_qual_name (retreive_super_name ptr) - | CContext.ContextNoCls -> - assert false + match decl_ref |> Option.value_map ~f:(fun dr -> dr.dr_name) ~default:None with + | Some name -> + Typ.Name.Objc.from_qual_name (CAst_utils.get_qualified_name name) + | None -> + Logging.die InternalError "Expected to always find a superclass, but found none" (* Gets the class name from a method signature found by clang, if search is successful *) diff --git a/infer/src/clang/cPredicates.ml b/infer/src/clang/cPredicates.ml index b8a27dc68..83915360d 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -457,6 +457,30 @@ 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 -> + is_subclass_of cls name + | None -> + 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 0991a7523..345b2b69e 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -68,6 +68,10 @@ val is_objc_constructor : CLintersContext.context -> bool val is_objc_dealloc : CLintersContext.context -> bool +val is_in_objc_subclass_of : CLintersContext.context -> ALVar.alexp -> bool + +val is_subclass_of : Clang_ast_t.decl -> ALVar.alexp -> bool + val captures_cxx_references : Ctl_parser_types.ast_node -> bool val is_binop_with_kind : Ctl_parser_types.ast_node -> ALVar.alexp -> bool diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index ee2dd87aa..5f4fdd838 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -1038,6 +1038,8 @@ let rec eval_Atomic _pred_name args an lcxt = CPredicates.is_objc_dealloc lcxt | "is_objc_extension", [], _ -> CPredicates.is_objc_extension lcxt + | "is_in_objc_subclass_of", [name], _ -> + CPredicates.is_in_objc_subclass_of lcxt name | "is_objc_interface_named", [name], an -> CPredicates.is_objc_interface_named an name | "is_property_pointer_type", [], an -> diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/InSubclassExample.m b/infer/tests/codetoanalyze/objc/linters-for-test-only/InSubclassExample.m new file mode 100644 index 000000000..dc74c612e --- /dev/null +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/InSubclassExample.m @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +#import + +@interface HappySadView : NSObject +@end + +@implementation HappySadView + +- (void)makeBadAction { + [self copy]; +} + +- (void)foo { +} + +@end + +@interface SubClassTestClass : NSObject + +@end + +@interface HappySadView2 : SubClassTestClass +@end + +@implementation HappySadView2 + +- (void)makeBadAction { + [self copy]; +} + +- (void)foo { +} + +@end 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 acd01e8ad..b4da75588 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp @@ -6,6 +6,11 @@ codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, function, 27, codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, function, 27, TEST_VAR_TYPE_CHECK, [] codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, objc_block_1, 21, TEST_VAR_TYPE_CHECK, [] codetoanalyze/objc/linters-for-test-only/InContextOfMethodsTest.m, objc_block_2, 21, TEST_IN_BLOCK_CONTEXT, [] +codetoanalyze/objc/linters-for-test-only/InSubclassExample.m, HappySadView2_foo, 38, TEST_BUILTIN_TYPE, [] +codetoanalyze/objc/linters-for-test-only/InSubclassExample.m, HappySadView2_makeBadAction, 34, TEST_BUILTIN_TYPE, [] +codetoanalyze/objc/linters-for-test-only/InSubclassExample.m, HappySadView2_makeBadAction, 35, IN_SUBCLASS_TEST, [] +codetoanalyze/objc/linters-for-test-only/InSubclassExample.m, HappySadView_foo, 20, TEST_BUILTIN_TYPE, [] +codetoanalyze/objc/linters-for-test-only/InSubclassExample.m, HappySadView_makeBadAction, 16, TEST_BUILTIN_TYPE, [] codetoanalyze/objc/linters-for-test-only/PrivateAPIChecker.m, TestView_methodd, 16, TEST_BUILTIN_TYPE, [] codetoanalyze/objc/linters-for-test-only/PrivateAPIChecker.m, TestView_methoddd, 20, TEST_BUILTIN_TYPE, [] codetoanalyze/objc/linters-for-test-only/PrivateAPIChecker.m, TestView_methoddd, 21, TEST_SELECTOR, [] diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al b/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al index 7de51a390..0d840cd34 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al @@ -423,3 +423,10 @@ DEFINE-CHECKER TEST_PARAMETER_SEL_TYPE = { SET message = "Method has parameter of type SEL"; }; + + DEFINE-CHECKER IN_SUBCLASS_TEST = { + SET report_when = + WHEN is_in_objc_subclass_of("SubClassTestClass") + HOLDS-IN-NODE ObjCMessageExpr; + SET message = "Found a message call in a class that is not subclass of A."; + };