[linters] Add linter for checking api compatibility

Reviewed By: jvillard

Differential Revision: D4328623

fbshipit-source-id: 58370b7
master
Dulma Churchill 8 years ago committed by Facebook Github Bot
parent b5124c1506
commit 9f153dbafa

@ -43,7 +43,7 @@ DIRECT_TESTS += \
java_crashcontext java_harness java_crashcontext java_harness
endif endif
ifneq ($(XCODE_SELECT),no) ifneq ($(XCODE_SELECT),no)
DIRECT_TESTS += objc_frontend objc_errors objc_linters objcpp_frontend objcpp_linters DIRECT_TESTS += objc_frontend objc_errors objc_linters objc_ioslinters objcpp_frontend objcpp_linters
endif endif
.PHONY: all .PHONY: all

@ -846,6 +846,10 @@ and infer_cache =
CLOpt.mk_path_opt ~deprecated:["infer_cache"; "-infer_cache"] ~long:"infer-cache" CLOpt.mk_path_opt ~deprecated:["infer_cache"; "-infer_cache"] ~long:"infer-cache"
~meta:"dir" "Select a directory to contain the infer cache (Buck and Java only)" ~meta:"dir" "Select a directory to contain the infer cache (Buck and Java only)"
and iphoneos_target_sdk_version =
CLOpt.mk_string_opt ~long:"iphoneos-target-sdk-version" ~exes:CLOpt.[Toplevel;Clang]
"Specify the target SDK version to use for iphoneos"
and iterations = and iterations =
CLOpt.mk_int ~deprecated:["iterations"] ~long:"iterations" ~default:1 CLOpt.mk_int ~deprecated:["iterations"] ~long:"iterations" ~default:1
~meta:"int" ~meta:"int"
@ -1433,6 +1437,7 @@ and frontend_stats = !frontend_stats
and headers = !headers and headers = !headers
and icfg_dotty_outfile = !icfg_dotty_outfile and icfg_dotty_outfile = !icfg_dotty_outfile
and infer_cache = !infer_cache and infer_cache = !infer_cache
and iphoneos_target_sdk_version = !iphoneos_target_sdk_version
and iterations = !iterations and iterations = !iterations
and java_jar_compiler = !java_jar_compiler and java_jar_compiler = !java_jar_compiler
and javac_verbose_out = !verbose_out and javac_verbose_out = !verbose_out

@ -196,6 +196,7 @@ val generated_classes : string option
val headers : bool val headers : bool
val icfg_dotty_outfile : string option val icfg_dotty_outfile : string option
val infer_cache : string option val infer_cache : string option
val iphoneos_target_sdk_version : string option
val is_originator : bool val is_originator : bool
val iterations : int val iterations : int
val java_jar_compiler : string option val java_jar_compiler : string option

@ -342,3 +342,14 @@ let suppress_stderr2 f2 x1 x2 =
let f () = f2 x1 x2 in let f () = f2 x1 x2 in
let finally () = restore_stderr orig_stderr in let finally () = restore_stderr orig_stderr in
protect ~f ~finally protect ~f ~finally
let compare_versions v1 v2 =
let int_list_of_version v =
let lv = String.split ~on:'.' v in
let int_of_string_or_zero v =
try int_of_string v
with Failure _ -> 0 in
List.map ~f:int_of_string_or_zero lv in
let lv1 = int_list_of_version v1 in
let lv2 = int_list_of_version v2 in
[%compare : int list] lv1 lv2

@ -79,3 +79,8 @@ val realpath : string -> string
(** wraps a function expecting 2 arguments in another that temporarily redirects stderr to /dev/null (** wraps a function expecting 2 arguments in another that temporarily redirects stderr to /dev/null
for the duration of the function call *) for the duration of the function call *)
val suppress_stderr2 : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c val suppress_stderr2 : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c
(** [compare_versions v1 v2] returns 1 if v1 is newer than v2,
-1 if v1 is older than v2 and 0 if they are the same version.
The versions are strings of the shape "n.m.t", the order is lexicographic. *)
val compare_versions : string -> string -> int

@ -41,7 +41,6 @@ let location_from_an lcxt an =
| CTL.Stmt st -> location_from_stmt lcxt st | CTL.Stmt st -> location_from_stmt lcxt st
| CTL.Decl d -> location_from_decl lcxt d | CTL.Decl d -> location_from_decl lcxt d
let decl_name an = let decl_name an =
match an with match an with
| CTL.Decl dec -> | CTL.Decl dec ->
@ -50,6 +49,34 @@ let decl_name an =
| None -> "") | None -> "")
| _ -> "" | _ -> ""
let tag_name_of_node an =
match an with
| CTL.Stmt stmt -> Clang_ast_proj.get_stmt_kind_string stmt
| CTL.Decl decl -> Clang_ast_proj.get_decl_kind_string decl
let decl_ref_or_selector_name an =
match CTL.next_state_via_transition an (Some CTL.PointerToDecl) with
| Some (CTL.Decl ObjCMethodDecl _ as decl_an) ->
"The selector " ^ (decl_name decl_an)
| Some (CTL.Decl _ as decl_an) ->
"The reference " ^ (decl_name decl_an)
| _ -> failwith("decl_ref_or_selector_name must be called with a DeclRefExpr \
or an ObjCMessageExpr, but got " ^ (tag_name_of_node an))
let iphoneos_target_sdk_version _ =
match Config.iphoneos_target_sdk_version with
| Some f -> f
| None -> "0"
let available_ios_sdk an =
match CTL.next_state_via_transition an (Some CTL.PointerToDecl) with
| Some CTL.Decl decl ->
(match Predicates.get_available_attr_ios_sdk decl with
| Some version -> version
| None -> "")
| _ -> failwith("available_ios_sdk must be called with a DeclRefExpr \
or an ObjCMessageExpr, but got " ^ (tag_name_of_node an))
let ivar_name an = let ivar_name an =
let open Clang_ast_t in let open Clang_ast_t in
match an with match an with
@ -270,3 +297,22 @@ let ctl_captured_cxx_ref_in_objc_block_warning lctx an =
| _ -> location_from_an lctx an; | _ -> location_from_an lctx an;
} in } in
condition, Some issue_desc condition, Some issue_desc
(** If the declaration has avilability attributes, check that it's compatible with
the iphoneos_target_sdk_version *)
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
let issue_desc =
{ CIssue.name = "UNAVAILABLE_API_IN_SUPPORTED_IOS_SDK";
severity = Exceptions.Kerror;
mode = CIssue.On;
description =
"%decl_ref_or_selector_name% is not available in the required iOS SDK version \
%iphoneos_target_sdk_version% (only available from version %available_ios_sdk%)";
suggestion = Some "This could cause a crash.";
loc = location_from_an lctx an
} in
condition, Some issue_desc

@ -29,6 +29,12 @@ val ctl_direct_atomic_property_access_warning :
val ctl_captured_cxx_ref_in_objc_block_warning : val ctl_captured_cxx_ref_in_objc_block_warning :
CLintersContext.context -> CTL.ast_node -> CTL.t * CIssue.issue_desc option CLintersContext.context -> CTL.ast_node -> CTL.t * CIssue.issue_desc option
(** Unavailable_api_in_supported_os_error :
If the declaration has avilability attributes, check that it's compatible with
the iphoneos_target_sdk_version *)
val ctl_unavailable_api_in_supported_ios_sdk_error :
CLintersContext.context -> CTL.ast_node -> CTL.t * CIssue.issue_desc option
val ctl_bad_pointer_comparison_warning : val ctl_bad_pointer_comparison_warning :
CLintersContext.context -> CTL.ast_node -> CTL.t * CIssue.issue_desc option CLintersContext.context -> CTL.ast_node -> CTL.t * CIssue.issue_desc option
@ -57,3 +63,9 @@ val decl_name : CTL.ast_node -> string
val ivar_name : CTL.ast_node -> string val ivar_name : CTL.ast_node -> string
val var_name : CTL.ast_node -> string val var_name : CTL.ast_node -> string
val decl_ref_or_selector_name : CTL.ast_node -> string
val iphoneos_target_sdk_version : CTL.ast_node -> string
val available_ios_sdk : CTL.ast_node -> string

@ -11,7 +11,7 @@ open! IStd
open CFrontend_utils open CFrontend_utils
(* List of checkers on properties *) (* List of checkers on decls *)
let decl_checkers_list = [CFrontend_checkers.ctl_strong_delegate_warning; let decl_checkers_list = [CFrontend_checkers.ctl_strong_delegate_warning;
CFrontend_checkers.ctl_assign_pointer_warning; CFrontend_checkers.ctl_assign_pointer_warning;
CFrontend_checkers.ctl_ns_notification_warning; CFrontend_checkers.ctl_ns_notification_warning;
@ -22,12 +22,13 @@ let decl_checkers_list = [CFrontend_checkers.ctl_strong_delegate_warning;
ComponentKit.component_file_cyclomatic_complexity_info; ComponentKit.component_file_cyclomatic_complexity_info;
ComponentKit.component_with_multiple_factory_methods_advice;] ComponentKit.component_with_multiple_factory_methods_advice;]
(* List of checkers on ivar access *) (* List of checkers on stmts *)
let stmt_checkers_list = [CFrontend_checkers.ctl_direct_atomic_property_access_warning; let stmt_checkers_list = [CFrontend_checkers.ctl_direct_atomic_property_access_warning;
CFrontend_checkers.ctl_captured_cxx_ref_in_objc_block_warning; CFrontend_checkers.ctl_captured_cxx_ref_in_objc_block_warning;
CFrontend_checkers.ctl_bad_pointer_comparison_warning; CFrontend_checkers.ctl_bad_pointer_comparison_warning;
ComponentKit.component_file_cyclomatic_complexity_info; ComponentKit.component_file_cyclomatic_complexity_info;
ComponentKit.component_initializer_with_side_effects_advice;] ComponentKit.component_initializer_with_side_effects_advice;
CFrontend_checkers.ctl_unavailable_api_in_supported_ios_sdk_error;]
(* List of checkers on translation unit that potentially output multiple issues *) (* List of checkers on translation unit that potentially output multiple issues *)
let translation_unit_checkers_list = [ComponentKit.component_file_line_count_info;] let translation_unit_checkers_list = [ComponentKit.component_file_line_count_info;]
@ -37,6 +38,11 @@ let evaluate_place_holder ph an =
| "%ivar_name%" -> CFrontend_checkers.ivar_name an | "%ivar_name%" -> CFrontend_checkers.ivar_name an
| "%decl_name%" -> CFrontend_checkers.decl_name an | "%decl_name%" -> CFrontend_checkers.decl_name an
| "%var_name%" -> CFrontend_checkers.var_name an | "%var_name%" -> CFrontend_checkers.var_name an
| "%decl_ref_or_selector_name%" ->
CFrontend_checkers.decl_ref_or_selector_name an
| "%iphoneos_target_sdk_version%" ->
CFrontend_checkers.iphoneos_target_sdk_version an
| "%available_ios_sdk%" -> CFrontend_checkers.available_ios_sdk an
| _ -> (Logging.err "ERROR: helper function %s is unknown. Stop.\n" ph; | _ -> (Logging.err "ERROR: helper function %s is unknown. Stop.\n" ph;
assert false) assert false)

@ -19,10 +19,11 @@ open CFrontend_utils
(* Transition labels used for example to switch from decl to stmt *) (* Transition labels used for example to switch from decl to stmt *)
type transitions = type transitions =
| Body (* decl to stmt *) | Body (** decl to stmt *)
| InitExpr (* decl to stmt *) | InitExpr (** decl to stmt *)
| Super (* decl to decl *) | Super (** decl to decl *)
| Cond | Cond
| PointerToDecl (** stmt to decl *)
(* In formulas below prefix (* In formulas below prefix
"E" means "exists a path" "E" means "exists a path"
@ -60,7 +61,8 @@ module Debug = struct
| Body -> Format.pp_print_string fmt "Body" | Body -> Format.pp_print_string fmt "Body"
| InitExpr -> Format.pp_print_string fmt "InitExpr" | InitExpr -> Format.pp_print_string fmt "InitExpr"
| Super -> Format.pp_print_string fmt "Super" | Super -> Format.pp_print_string fmt "Super"
| Cond -> Format.pp_print_string fmt "Cond" in | Cond -> Format.pp_print_string fmt "Cond"
| PointerToDecl -> Format.pp_print_string fmt "PointerToDecl" in
match trans_opt with match trans_opt with
| Some trans -> pp_aux fmt trans | Some trans -> pp_aux fmt trans
| None -> Format.pp_print_string fmt "_" | None -> Format.pp_print_string fmt "_"
@ -337,6 +339,19 @@ let transition_stmt_to_stmt_via_condition st =
| WhileStmt (_, [_; cond; _]) -> Some (Stmt cond) | WhileStmt (_, [_; cond; _]) -> Some (Stmt cond)
| _ -> None | _ -> None
let transition_stmt_to_decl_via_pointer stmt =
let open Clang_ast_t in
match stmt with
| ObjCMessageExpr (_, _, _, obj_c_message_expr_info) ->
(match Ast_utils.get_decl_opt obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer with
| Some decl -> Some (Decl decl)
| None -> None)
| DeclRefExpr (_, _, _, decl_ref_expr_info) ->
(match Ast_utils.get_decl_opt_with_decl_ref decl_ref_expr_info.Clang_ast_t.drti_decl_ref with
| Some decl -> Some (Decl decl)
| None -> None)
| _ -> None
(* given a node an returns the node an' such that an transition to an' via label trans *) (* given a node an returns the node an' such that an transition to an' via label trans *)
let next_state_via_transition an trans = let next_state_via_transition an trans =
match an, trans with match an, trans with
@ -344,6 +359,8 @@ let next_state_via_transition an trans =
| Decl d, Some InitExpr | Decl d, Some InitExpr
| Decl d, Some Body -> transition_decl_to_stmt d trans | Decl d, Some Body -> transition_decl_to_stmt d trans
| Stmt st, Some Cond -> transition_stmt_to_stmt_via_condition st | Stmt st, Some Cond -> transition_stmt_to_stmt_via_condition st
| Stmt st, Some PointerToDecl ->
transition_stmt_to_decl_via_pointer st
| _, _ -> None | _, _ -> None
(* Evaluation of formulas *) (* Evaluation of formulas *)
@ -373,6 +390,8 @@ let eval_Atomic pred_name args an lcxt =
| "in_node", [nodename], Stmt st -> Predicates.is_stmt nodename st | "in_node", [nodename], Stmt st -> Predicates.is_stmt nodename st
| "in_node", [nodename], Decl d -> Predicates.is_decl nodename d | "in_node", [nodename], Decl d -> Predicates.is_decl nodename d
| "isa", [classname], Stmt st -> Predicates.isa classname st | "isa", [classname], Stmt st -> Predicates.isa classname st
| "decl_unavailable_in_supported_ios_sdk", [], Decl decl ->
Predicates.decl_unavailable_in_supported_ios_sdk decl
| _ -> failwith ("ERROR: Undefined Predicate or wrong set of arguments: " ^ pred_name) | _ -> failwith ("ERROR: Undefined Predicate or wrong set of arguments: " ^ pred_name)
(* st, lcxt |= EF phi <=> (* st, lcxt |= EF phi <=>

@ -20,6 +20,7 @@ type transitions =
| InitExpr (* decl to stmt *) | InitExpr (* decl to stmt *)
| Super (* decl to decl *) | Super (* decl to decl *)
| Cond | Cond
| PointerToDecl (* stmt to decl *)
(* In formulas below prefix (* In formulas below prefix
"E" means "exists a path" "E" means "exists a path"
@ -65,6 +66,7 @@ val eval_formula : t -> ast_node -> CLintersContext.context -> bool
val save_dotty_when_in_debug_mode : SourceFile.t -> unit val save_dotty_when_in_debug_mode : SourceFile.t -> unit
val next_state_via_transition : ast_node -> transitions option -> ast_node option
module Debug : sig module Debug : sig
val pp_formula : Format.formatter -> t -> unit val pp_formula : Format.formatter -> t -> unit

@ -191,3 +191,18 @@ DEFINE-CHECKER ctl_captured_cxx_ref_in_objc_block_warning = {
SET suggestion = "C++ References are unmanaged and may be invalid by the time the block executes."; SET suggestion = "C++ References are unmanaged and may be invalid by the time the block executes.";
}; };
DEFINE-CHECKER ctl_unavailable_api_in_supported_ios_sdk_error = {
SET report_when =
WHEN
WITH-TRANSITION PointerToDecl decl_unavailable_in_supported_ios_sdk
HOLDS-IN-NODE DeclRefExpr, ObjCMessageExpr;
SET message =
"%decl_ref_or_selector_name% is available only starting \
from ios sdk %available_ios_sdk% but we support earlier versions from \
ios sdk %iphoneos_target_sdk_version%;
SET suggestion = "This could cause a crash.";
};

@ -11,6 +11,20 @@ open! IStd
open CFrontend_utils open CFrontend_utils
let get_available_attr_ios_sdk decl =
let open Clang_ast_t in
let decl_info = Clang_ast_proj.get_decl_tuple decl in
let rec get_available_attr attrs =
match attrs with
| [] -> None
| AvailabilityAttr attr_info :: _ ->
(match attr_info.Clang_ast_t.ai_parameters with
| "ios" :: version :: _ ->
Some (String.Search_pattern.replace_all
(String.Search_pattern.create "_") ~in_:version ~with_:".")
| _ -> None)
| _ :: rest -> get_available_attr rest in
get_available_attr decl_info.Clang_ast_t.di_attributes
let get_ivar_attributes ivar_decl = let get_ivar_attributes ivar_decl =
let open Clang_ast_t in let open Clang_ast_t in
@ -203,3 +217,10 @@ let isa classname stmt =
let typ = CFrontend_utils.Ast_utils.get_desugared_type expr_info.ei_type_ptr in let typ = CFrontend_utils.Ast_utils.get_desugared_type expr_info.ei_type_ptr in
CFrontend_utils.Ast_utils.is_ptr_to_objc_class typ classname CFrontend_utils.Ast_utils.is_ptr_to_objc_class typ classname
| _ -> false | _ -> false
let decl_unavailable_in_supported_ios_sdk decl =
let available_attr_ios_sdk = get_available_attr_ios_sdk decl in
match available_attr_ios_sdk, Config.iphoneos_target_sdk_version with
| Some available_attr_ios_sdk, Some iphoneos_target_sdk_version ->
Utils.compare_versions available_attr_ios_sdk iphoneos_target_sdk_version = 1
| _ -> false

@ -54,3 +54,7 @@ val is_stmt : string -> Clang_ast_t.stmt -> bool
val is_decl : string -> Clang_ast_t.decl -> bool val is_decl : string -> Clang_ast_t.decl -> bool
val pp_predicate : Format.formatter -> t -> unit 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

@ -0,0 +1,27 @@
# Copyright (c) 2016 - 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.
TESTS_DIR = ../../..
IPHONESIMULATOR_ISYSROOT_SUFFIX = /Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
XCODEROOT = $(shell xcode-select -p)
CLANG_OPTIONS = -x objective-c \
-isysroot $(XCODEROOT)$(IPHONESIMULATOR_ISYSROOT_SUFFIX) \
-mios-simulator-version-min=8.2 --target=x86_64-apple-darwin14 -fobjc-arc -c \
ANALYZER = linters
INFER_OPTIONS = --no-filtering --debug-exceptions --project-root $(TESTS_DIR) \
--iphoneos-target-sdk-version 8.0
INFERPRINT_OPTIONS = --issues-tests
SOURCES = \
$(wildcard *.m) \
$(wildcard */*.m) \
include $(TESTS_DIR)/clang.make

@ -0,0 +1,2 @@
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, []

@ -0,0 +1,20 @@
#import <UIKit/UIKit.h>
@interface Unavailable_api_in_supported_ios_sdk : NSObject
@end
@implementation Unavailable_api_in_supported_ios_sdk
- (void)test:(int)n and:(NSData*)data {
NSDictionary* cacheData =
[NSKeyedUnarchiver unarchiveTopLevelObjectWithData:data error:nil];
}
@end
static NSDictionary* OpenURLOptionsFromSourceApplication(
NSString* sourceApplication) {
NSDictionary* options =
@{UIApplicationOpenURLOptionsSourceApplicationKey : sourceApplication};
return options;
}
Loading…
Cancel
Save