diff --git a/infer/src/clang/CLintersContext.ml b/infer/src/clang/CLintersContext.ml index c3644ecb2..c0c2e59dc 100644 --- a/infer/src/clang/CLintersContext.ml +++ b/infer/src/clang/CLintersContext.ml @@ -11,6 +11,7 @@ open! IStd type if_context = { within_responds_to_selector_block : string list; + ios_version_guard : string list } type context = { diff --git a/infer/src/clang/CiOSVersionNumbers.ml b/infer/src/clang/CiOSVersionNumbers.ml new file mode 100644 index 000000000..f80883f83 --- /dev/null +++ b/infer/src/clang/CiOSVersionNumbers.ml @@ -0,0 +1,74 @@ +(* + * 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. + *) +open! IStd + +(* version macros like kCFCoreFoundationVersionNumber_iOS_9_0 are + tied to specific float values, e.g. 1240.1. + To be found in CoreFoundation/CFBase.h *) + +type machine_readable_version = float +type human_readable_version = string +type t = machine_readable_version * human_readable_version + +let version_numbers : t list = + [(478.23, "2.0"); + (478.26, "2.1"); + (478.29, "2.2"); + (478.47, "3.0"); + (478.52, "3.1"); + (478.61, "3.2"); + (550.32, "4.0"); + (550.38, "4.1"); + (550.52, "4.3"); + (675.00, "5.0"); + (690.10, "5.1"); + (793.00, "6.1"); + (847.20, "7.0"); + (847.24, "7.1"); + (1140.1, "8.0"); + (1141.14, "8.1"); + (1142.16, "8.2"); + (1144.17, "8.3"); + (1145.15, "8.4"); + (1240.1, "9.0"); + (1241.11, "9.1"); + (1242.13, "9.3"); + (1280.38, "9.4"); + (1348.0, "10.0"); + (1348.22, "10.2");] + +let sort_versions versions = + let compare (version_float1, _) (version_float2, _) = + Float.compare version_float1 version_float2 in + List.sort ~cmp:compare versions + +let version_of number_s : human_readable_version option = + let epsilon = 0.001 in + let rec version_of_aux version_numbers number = + match version_numbers with + | (version_n, version_s) :: (next_version_n, next_version_s) :: rest -> + if (number -. version_n) < epsilon && (number -. version_n) > -. epsilon then Some version_s + else if (number >= (version_n +. epsilon) && number <= (next_version_n -. epsilon)) + then Some next_version_s + else version_of_aux ((next_version_n, next_version_s) :: rest) number + | [version_n, version_s] -> + if number >= version_n then Some version_s + else None + | [] -> None in + let number_opt = + try Some (float_of_string number_s) + with Failure _ -> None in + match number_opt with + | None -> None + | Some number -> version_of_aux (sort_versions version_numbers) number + +let pp_diff_of_version_opt fmt (expected, actual) = + let option_to_string opt = Option.value ~default:"" opt in + Format.fprintf fmt + "Expected: [%s] Found: [%s]" (option_to_string expected) (option_to_string actual) diff --git a/infer/src/clang/CiOSVersionNumbers.mli b/infer/src/clang/CiOSVersionNumbers.mli new file mode 100644 index 000000000..ed56352fa --- /dev/null +++ b/infer/src/clang/CiOSVersionNumbers.mli @@ -0,0 +1,17 @@ +(* + * 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. + *) + +type machine_readable_version = float +type human_readable_version = string +type t = machine_readable_version * human_readable_version + +val version_of : string -> human_readable_version option + +val pp_diff_of_version_opt : Format.formatter -> + (human_readable_version option * human_readable_version option) -> unit diff --git a/infer/src/clang/cFrontend_checkers_main.ml b/infer/src/clang/cFrontend_checkers_main.ml index 4252f1cbc..4698962c0 100644 --- a/infer/src/clang/cFrontend_checkers_main.ml +++ b/infer/src/clang/cFrontend_checkers_main.ml @@ -56,14 +56,63 @@ let rec get_responds_to_selector stmt = get_responds_to_selector stmt | _ -> [] +let rec is_core_foundation_version_number stmt = + let open Clang_ast_t in + match stmt with + | DeclRefExpr (_, _, _, decl_ref_info) -> + (match decl_ref_info.drti_decl_ref with + | Some decl_ref_info -> + let name_info, _, _ = CAst_utils.get_info_from_decl_ref decl_ref_info in + String.equal name_info.ni_name "kCFCoreFoundationVersionNumber" + | None -> false) + | ImplicitCastExpr (_, [stmt], _, _) -> + is_core_foundation_version_number stmt + | _ -> false + +let rec current_os_version_constant stmt = + let open Clang_ast_t in + match stmt with + | FloatingLiteral (_, _, _, number) -> + CiOSVersionNumbers.version_of number + | IntegerLiteral (_, _, _, info) -> + CiOSVersionNumbers.version_of info.ili_value + | ImplicitCastExpr (_, [stmt], _, _) -> + current_os_version_constant stmt + | _ -> None + +let rec get_current_os_version stmt = + let open Clang_ast_t in + match stmt with + | BinaryOperator (_, [stmt1;stmt2], _, bo_info) when + PVariant.(=) bo_info.Clang_ast_t.boi_kind `GE && + is_core_foundation_version_number stmt1 -> + Option.to_list (current_os_version_constant stmt2) + | BinaryOperator (_, [stmt1;stmt2], _, bo_info) when + PVariant.(=) bo_info.Clang_ast_t.boi_kind `LE && + is_core_foundation_version_number stmt2 -> + Option.to_list (current_os_version_constant stmt1) + | BinaryOperator (_, [stmt1;stmt2], _, bo_info) when + PVariant.(=) bo_info.Clang_ast_t.boi_kind `LAnd -> + List.append (get_current_os_version stmt1) (get_current_os_version stmt2) + | ImplicitCastExpr (_, [stmt], _, _) + | ParenExpr (_, [stmt], _) + | ExprWithCleanups(_, [stmt], _, _) -> + get_current_os_version stmt + | _ -> [] + let compute_if_context (context:CLintersContext.context) stmt = - let prev_responds_to_selector = + let selector = get_responds_to_selector stmt in + let os_version = get_current_os_version stmt in + let within_responds_to_selector_block, ios_version_guard = 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) + | Some if_context -> + let within_responds_to_selector_block = + List.append selector if_context.within_responds_to_selector_block in + let ios_version_guard = + List.append os_version if_context.ios_version_guard in + within_responds_to_selector_block, ios_version_guard + | None -> selector, os_version in + Some ({within_responds_to_selector_block; ios_version_guard} : 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 64d9f609b..7babf3e0f 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -232,11 +232,20 @@ let isa classname an = | _ -> false) | _ -> false -let decl_unavailable_in_supported_ios_sdk an = + +let decl_unavailable_in_supported_ios_sdk (cxt : CLintersContext.context) an = + let allowed_os_versions = + match Config.iphoneos_target_sdk_version, + (cxt.if_context : CLintersContext.if_context option) with + | Some iphoneos_target_sdk_version, Some if_context -> + iphoneos_target_sdk_version :: if_context.ios_version_guard + | Some iphoneos_target_sdk_version, None -> [iphoneos_target_sdk_version] + | _ -> [] in + let max_allowed_version_opt = List.max_elt allowed_os_versions ~cmp:Utils.compare_versions in let available_attr_ios_sdk = get_available_attr_ios_sdk an in - match available_attr_ios_sdk, Config.iphoneos_target_sdk_version with - | Some available_attr_ios_sdk, Some iphoneos_target_sdk_version -> - Int.equal (Utils.compare_versions available_attr_ios_sdk iphoneos_target_sdk_version) 1 + match available_attr_ios_sdk, max_allowed_version_opt with + | Some available_attr_ios_sdk, Some max_allowed_version -> + (Utils.compare_versions available_attr_ios_sdk max_allowed_version) > 0 | _ -> false diff --git a/infer/src/clang/cPredicates.mli b/infer/src/clang/cPredicates.mli index a6951d7b0..427f0e8c0 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -53,7 +53,8 @@ val is_node : string -> Ctl_parser_types.ast_node -> bool val pp_predicate : Format.formatter -> t -> unit -val decl_unavailable_in_supported_ios_sdk : Ctl_parser_types.ast_node -> bool +val decl_unavailable_in_supported_ios_sdk : CLintersContext.context -> Ctl_parser_types.ast_node + -> bool val get_available_attr_ios_sdk : Ctl_parser_types.ast_node -> string option diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index 978196ab0..da1e1c74b 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -433,7 +433,7 @@ let rec eval_Atomic pred_name args an lcxt = | "in_node", [nodename], an -> CPredicates.is_node nodename an | "isa", [classname], an -> CPredicates.isa classname an | "decl_unavailable_in_supported_ios_sdk", [], an -> - CPredicates.decl_unavailable_in_supported_ios_sdk an + CPredicates.decl_unavailable_in_supported_ios_sdk lcxt an | "within_responds_to_selector_block", [], an -> CPredicates.within_responds_to_selector_block lcxt an | _ -> failwith ("ERROR: Undefined Predicate or wrong set of arguments: " ^ pred_name) diff --git a/infer/src/unit/CiOSVersionNumbersTests.ml b/infer/src/unit/CiOSVersionNumbersTests.ml new file mode 100644 index 000000000..e11a5dfd8 --- /dev/null +++ b/infer/src/unit/CiOSVersionNumbersTests.ml @@ -0,0 +1,55 @@ +(* + * 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. + *) + +open! IStd +open OUnit2 + +let test_correct_ios_version = + let create_test (version : string) (expected_version : string option) _ = + let output = CiOSVersionNumbers.version_of version in + let cmp = fun s1 s2 -> Option.equal String.equal s1 s2 in + assert_equal ~pp_diff:CiOSVersionNumbers.pp_diff_of_version_opt + ~cmp:cmp expected_version output in + [ + ( + "test_correct_ios_version_some_version", + "847.20", + (Some "7.0") + ); + ( + "test_correct_ios_version_edge_version", + "1348.22", + (Some "10.2") + ); + ( + "test_correct_ios_version_ck", + "1223.1", + (Some "9.0") + ); + ( + "test_correct_ios_version_9", + "1240.0999", + (Some "9.0") + ); + ( + "test_correct_ios_version_2", + "478.230001", + (Some "2.0") + ); + ( + "test_correct_ios_version_smaller", + "1.49", + (None) + ) + ] + |> List.map + ~f:(fun (name, test_input, expected_output) -> + name >:: create_test test_input expected_output) + +let tests = "cios_version_numbers_suite" >::: test_correct_ios_version diff --git a/infer/src/unit/inferunit.ml b/infer/src/unit/inferunit.ml index 78bedfb59..5a7e57af5 100644 --- a/infer/src/unit/inferunit.ml +++ b/infer/src/unit/inferunit.ml @@ -20,6 +20,7 @@ let () = AddressTakenTests.tests; BoundedCallTreeTests.tests; CopyPropagationTests.tests; + CiOSVersionNumbersTests.tests; ProcCfgTests.tests; LivenessTests.tests; SchedulerTests.tests; diff --git a/infer/tests/codetoanalyze/objc/iosLinters/unavailable_api_allowed_cases.m b/infer/tests/codetoanalyze/objc/iosLinters/unavailable_api_allowed_cases.m deleted file mode 100644 index 2391f3c46..000000000 --- a/infer/tests/codetoanalyze/objc/iosLinters/unavailable_api_allowed_cases.m +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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]; - } -} - -// no bug -- (void)uifont_with_respondstoselector:(CGFloat)size { - UIFont* font; - if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { - font = [UIFont systemFontOfSize:size weight:0]; - } -} - -// bug -- (void)uifont_without_respondstoselector:(CGFloat)size { - UIFont* font = [UIFont systemFontOfSize:size weight:0]; -} - -@end diff --git a/infer/tests/codetoanalyze/objc/ioslinters/issues.exp b/infer/tests/codetoanalyze/objc/ioslinters/issues.exp index 4ccf7fa1a..eccb76c2a 100644 --- a/infer/tests/codetoanalyze/objc/ioslinters/issues.exp +++ b/infer/tests/codetoanalyze/objc/ioslinters/issues.exp @@ -1,5 +1,7 @@ -codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_uifont_without_respondstoselector:, 86, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] -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_allowed_cases.m, Unavailable_api_allowed_cases_m2, 120, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] +codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_m3:, 128, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] +codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_uifont_without_respondstoselector:, 102, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] +codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_with_responds_to_selector_in_else:, 68, UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK, [] +codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m, Unavailable_api_allowed_cases_without_responds_to_selector:, 61, 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, [] 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..5848ba667 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/ioslinters/unavailable_api_allowed_cases.m @@ -0,0 +1,188 @@ +/* + * 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 + +#define CK_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= 1223.1) + +#define AT_LEAST_IOS9 \ + (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) + +#ifndef kCFCoreFoundationVersionNumber_iOS_10_0 +#define kCFCoreFoundationVersionNumber_iOS_10_0 1348 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_10_2 +#define kCFCoreFoundationVersionNumber_iOS_10_2 1348.22 +#endif + +#define AT_LEAST_IOS10 \ + (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) + +@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]; + } +} + +// no bug +- (void)uifont_with_respondstoselector:(CGFloat)size { + UIFont* font; + if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { + font = [UIFont systemFontOfSize:size weight:0]; + } +} + +// bug +- (void)uifont_without_respondstoselector:(CGFloat)size { + UIFont* font = [UIFont systemFontOfSize:size weight:0]; +} + +// no bug +- (void)m1 { + NSDictionary* destinationPixelBufferAttributes; + if (kCFCoreFoundationVersionNumber >= + kCFCoreFoundationVersionNumber_iOS_9_0) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +// bug +- (void)m2 { + NSDictionary* destinationPixelBufferAttributes; + destinationPixelBufferAttributes = + @{(NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES }; +} + +// bug +- (void)m3:(BOOL)ok { + NSDictionary* destinationPixelBufferAttributes; + if (ok) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +// no bug +- (void)m4:(BOOL)ok { + NSDictionary* destinationPixelBufferAttributes; + if (kCFCoreFoundationVersionNumber >= + kCFCoreFoundationVersionNumber_iOS_9_0 && + ok) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +// no bug +- (void)m5 { + NSDictionary* destinationPixelBufferAttributes; + if (kCFCoreFoundationVersionNumber >= + kCFCoreFoundationVersionNumber_iOS_9_0 && + kCFCoreFoundationVersionNumber >= + kCFCoreFoundationVersionNumber_iOS_7_0) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +// no bug +- (void)m6 { + NSDictionary* destinationPixelBufferAttributes; + if (AT_LEAST_IOS9) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +// no bug +- (void)m7 { + NSDictionary* destinationPixelBufferAttributes; + if (CK_AT_LEAST_IOS9) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +// no bug +- (void)m8 { + NSDictionary* destinationPixelBufferAttributes; + if (AT_LEAST_IOS10) { + destinationPixelBufferAttributes = @{ + (NSString*)kCVPixelBufferOpenGLESTextureCacheCompatibilityKey : @YES + }; + } +} + +@end