diff --git a/infer/lib/linter_rules/linters.al b/infer/lib/linter_rules/linters.al index 83cecdcd1..b01eecff6 100644 --- a/infer/lib/linter_rules/linters.al +++ b/infer/lib/linter_rules/linters.al @@ -208,7 +208,8 @@ DEFINE-CHECKER CXX_REFERENCE_CAPTURED_IN_OBJC_BLOCK = { // DEFINE-CHECKER ctl_unavailable_api_in_supported_ios_sdk_error = { // SET report_when = // WHEN -// WITH-TRANSITION PointerToDecl decl_unavailable_in_supported_ios_sdk +// WITH-TRANSITION PointerToDecl +// (decl_unavailable_in_supported_ios_sdk AND NOT within_responds_to_selector_block) // HOLDS-IN-NODE DeclRefExpr, ObjCMessageExpr; // // SET message = diff --git a/infer/src/clang/CLintersContext.ml b/infer/src/clang/CLintersContext.ml index c5d075060..c3644ecb2 100644 --- a/infer/src/clang/CLintersContext.ml +++ b/infer/src/clang/CLintersContext.ml @@ -10,7 +10,7 @@ open! IStd type if_context = { - in_responds_to_selector_block : string list; + within_responds_to_selector_block : string list; } type context = { diff --git a/infer/src/clang/cFrontend_checkers.ml b/infer/src/clang/cFrontend_checkers.ml index 4100814c2..53b44d0ce 100644 --- a/infer/src/clang/cFrontend_checkers.ml +++ b/infer/src/clang/cFrontend_checkers.ml @@ -104,7 +104,10 @@ let ctl_unavailable_api_in_supported_ios_sdk_error lctx an = let open CTL in let condition = InNode(["DeclRefExpr"; "ObjCMessageExpr"], - EX (Some PointerToDecl, (Atomic ("decl_unavailable_in_supported_ios_sdk", [])))) in + EX (Some PointerToDecl, ( + And + (Atomic ("decl_unavailable_in_supported_ios_sdk", []), + Not (Atomic ("within_responds_to_selector_block", [])))))) in let issue_desc = { CIssue.name = "UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK"; severity = Exceptions.Kerror; diff --git a/infer/src/clang/cFrontend_checkers_main.ml b/infer/src/clang/cFrontend_checkers_main.ml index 41a6264e0..e3abf1248 100644 --- a/infer/src/clang/cFrontend_checkers_main.ml +++ b/infer/src/clang/cFrontend_checkers_main.ml @@ -41,8 +41,28 @@ let parse_ctl_file linters_files = | None -> Logging.out "No linters found.\n"); In_channel.close inx) linters_files -let compute_if_context _ _ = - None (* to be extended *) + +let rec get_responds_to_selector stmt = + let open Clang_ast_t in + match stmt with + | ObjCMessageExpr (_, [_; ObjCSelectorExpr (_, _, _, method_name)], _, mdi) + when String.equal mdi.Clang_ast_t.omei_selector "respondsToSelector:" -> + [method_name] + | BinaryOperator (_, [stmt1;stmt2], _, bo_info) + when PVariant.(=) bo_info.Clang_ast_t.boi_kind `LAnd -> + List.append (get_responds_to_selector stmt1) (get_responds_to_selector stmt2) + | ImplicitCastExpr (_, [stmt], _, _) -> + get_responds_to_selector stmt + | _ -> [] + +let compute_if_context (context:CLintersContext.context) stmt = + let prev_responds_to_selector = + match context.if_context with + | Some if_context -> if_context.within_responds_to_selector_block + | None -> [] in + let within_responds_to_selector_block = + List.append (get_responds_to_selector stmt) (prev_responds_to_selector) in + Some ({within_responds_to_selector_block} : CLintersContext.if_context) let is_factory_method (context: CLintersContext.context) decl = let interface_decl_opt = diff --git a/infer/src/clang/cPredicates.ml b/infer/src/clang/cPredicates.ml index 63e1261c2..760d3fb83 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -222,3 +222,14 @@ let decl_unavailable_in_supported_ios_sdk decl = | Some available_attr_ios_sdk, Some iphoneos_target_sdk_version -> Int.equal (Utils.compare_versions available_attr_ios_sdk iphoneos_target_sdk_version) 1 | _ -> false + +let within_responds_to_selector_block (cxt:CLintersContext.context) decl = + let open Clang_ast_t in + match decl with + | ObjCMethodDecl (_, named_decl_info, _) -> + (match cxt.if_context with + | Some if_context -> + let in_selector_block = if_context.within_responds_to_selector_block in + List.mem ~equal:String.equal in_selector_block named_decl_info.Clang_ast_t.ni_name + | None -> false) + | _ -> false diff --git a/infer/src/clang/cPredicates.mli b/infer/src/clang/cPredicates.mli index 11913496f..7b6a0ce27 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -58,3 +58,5 @@ val pp_predicate : Format.formatter -> t -> unit val decl_unavailable_in_supported_ios_sdk : Clang_ast_t.decl -> bool val get_available_attr_ios_sdk : Clang_ast_t.decl -> string option + +val within_responds_to_selector_block : CLintersContext.context -> Clang_ast_t.decl -> bool diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index f2fbbc85a..bce46307e 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -417,6 +417,8 @@ let rec eval_Atomic pred_name args an lcxt = | "isa", _, Decl _ -> false | "decl_unavailable_in_supported_ios_sdk", [], Decl decl -> CPredicates.decl_unavailable_in_supported_ios_sdk decl + | "within_responds_to_selector_block", [], Decl decl -> + CPredicates.within_responds_to_selector_block lcxt decl | "decl_unavailable_in_supported_ios_sdk", _, Stmt _ -> false | _ -> failwith ("ERROR: Undefined Predicate or wrong set of arguments: " ^ pred_name) diff --git a/infer/tests/codetoanalyze/objc/iosLinters/unavailable_api_allowed_cases.m b/infer/tests/codetoanalyze/objc/iosLinters/unavailable_api_allowed_cases.m new file mode 100644 index 000000000..e4f355bcf --- /dev/null +++ b/infer/tests/codetoanalyze/objc/iosLinters/unavailable_api_allowed_cases.m @@ -0,0 +1,76 @@ +/* + * 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 Unavailable_api_allowed_cases : NSObject + +- (void)m NS_AVAILABLE(10_12, 10_0); + +- (void)n NS_AVAILABLE(10_12, 10_0); + +@end + +@implementation Unavailable_api_allowed_cases + +- (void)m { +} + +- (void)n { +} + +// no bug +- (void)with_responds_to_selector:(Unavailable_api_allowed_cases*)a { + if ([a respondsToSelector:@selector(m)]) { + int x = 1; + [a m]; + x = 3; + } +} +// no bug +- (void)with_responds_to_selector:(Unavailable_api_allowed_cases*)a + and:(BOOL)ok { + if ([a respondsToSelector:@selector(m)] && ok) { + [a m]; + } +} + +// bug +- (void)without_responds_to_selector:(Unavailable_api_allowed_cases*)a { + [a m]; +} + +// bug +- (void)with_responds_to_selector_in_else:(Unavailable_api_allowed_cases*)a { + if ([a respondsToSelector:@selector(m)]) { + } else { + [a m]; + } +} + +// no bug +- (void)with_responds_to_selector_nested_if:(Unavailable_api_allowed_cases*)a { + if ([a respondsToSelector:@selector(m)]) { + if ([a respondsToSelector:@selector(n)]) { + [a m]; + [a n]; + } + } +} + +// no bug +- (void)with_responds_to_selector_two_selectors: + (Unavailable_api_allowed_cases*)a { + if ([a respondsToSelector:@selector(m)] && + [a respondsToSelector:@selector(n)]) { + [a m]; + [a n]; + } +} + +@end diff --git a/infer/tests/codetoanalyze/objc/ioslinters/issues.exp b/infer/tests/codetoanalyze/objc/ioslinters/issues.exp index 778b18db4..9d08b1ba4 100644 --- a/infer/tests/codetoanalyze/objc/ioslinters/issues.exp +++ b/infer/tests/codetoanalyze/objc/ioslinters/issues.exp @@ -1,2 +1,4 @@ +codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_with_responds_to_selector_in_else:, 52, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] +codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_without_responds_to_selector:, 45, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] codetoanalyze/objc/ioslinters/unavailable_api_in_supported_ios_sdk.m, OpenURLOptionsFromSourceApplication, 18, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] codetoanalyze/objc/ioslinters/unavailable_api_in_supported_ios_sdk.m, Unavailable_api_in_supported_ios_sdk_test:and:, 11, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, []