You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

283 lines
12 KiB

(*
* Copyright (c) 2015 - 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! Utils
open CFrontend_utils
(* To create a new checker you should: *)
(* 1. Define a checker function, say my_checker, in this module. *)
(* my_checker should define: *)
(* -a) a condition that determine if the checker fires *)
(* -b) a warning_desc that describes the warning (see warning_desc definition) *)
(* 2. Add your checker to the CFrontend_checkers interface *)
(* 3. Decide in which element of the AST my_checker should be evaluated. *)
(* - If it is a statement then you need to invoke my_checker from *)
(* run_frontend_checkers_on_stmt in CFrontend_error module.*)
(* - If it is a declaration invoke it from run_frontend_checkers_on_decl *)
type warning_desc = {
name : string; (* name for the checker, this will be a kind of bug *)
description : string; (* Description in the error message *)
suggestion : string; (* an optional suggestion or correction *)
loc : Location.t; (* location in the code *)
}
(* Helper functions *)
let get_ivar_attributes ivar_decl =
let open Clang_ast_t in
match ivar_decl with
| ObjCIvarDecl (ivar_decl_info, _, _, _, _) ->
(match Ast_utils.get_property_of_ivar ivar_decl_info.Clang_ast_t.di_pointer with
| Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) ->
obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes
| _ -> [])
| _ -> []
(* checks if ivar is defined among a set of fields and if it is atomic *)
let is_ivar_atomic attributes =
IList.exists (Ast_utils.property_attribute_eq `Atomic) attributes
let name_contains_word pname word =
let rexp = Str.regexp_string_case_fold word in
try
Str.search_forward rexp pname.Clang_ast_t.ni_name 0 >= 0
with Not_found -> false
let location_from_sinfo info =
CLocation.get_sil_location_from_range info.Clang_ast_t.si_source_range true
let location_from_dinfo info =
CLocation.get_sil_location_from_range info.Clang_ast_t.di_source_range true
let proc_name_from_context context =
Cfg.Procdesc.get_proc_name (CContext.get_procdesc context)
let rec is_self s =
let open Clang_ast_t in
match s with
| ImplicitCastExpr(_, [s], _, _) -> is_self s
| DeclRefExpr(_, _, _, dr) ->
(match dr.drti_decl_ref with
| Some decl_ref ->
(match decl_ref.dr_name with
| Some n -> n.ni_name = CFrontend_config.self
| _ -> false)
| _ -> false)
| _ -> false
let decl_ref_is_in names st =
match st with
| Clang_ast_t.DeclRefExpr (_, _, _, drti) ->
(match drti.drti_decl_ref with
| Some dr -> let ndi, _, _ = CFrontend_utils.Ast_utils.get_info_from_decl_ref dr in
IList.exists (fun n -> n = ndi.ni_name) names
| _ -> false)
| _ -> false
let call_function_named st names =
Ast_utils.exists_eventually_st decl_ref_is_in names st
let rec eventually_makes_a_call exp =
CFrontend_utils.Ast_utils.exists_eventually_st is_expensive_function_or_method_call () exp
and is_expensive_function_or_method_call _ st =
let white_list_functions = ["CGPointMake"] in
let open Clang_ast_t in
match st with
| CallExpr (_, st :: _, _) -> (* for C *)
not (call_function_named st white_list_functions)
| CXXConstructExpr (_, stmt_list, _, _) ->
(* Assumption: constructor is expensive iff it has a call inside *)
(IList.exists eventually_makes_a_call) stmt_list
| CXXTemporaryObjectExpr _ (* for C++ *)
| CXXMemberCallExpr _ | CXXOperatorCallExpr _
| ObjCMessageExpr _ -> true (* for ObjC *)
| _ -> false
(* Call method m and on the pn parameter pred holds *)
(* st |= call_method(m(p1,...,pn,...pk)) /\ pred(pn) *)
let call_method_on_nth pred pn m st =
match st with
| Clang_ast_t.ObjCMessageExpr (_, params, _, omei) when omei.omei_selector = m ->
(try
let p = IList.nth params pn in
pred p
with _ -> false)
| _ -> false
let dec_body_eventually atomic_pred param dec =
match dec with
| Clang_ast_t.ObjCMethodDecl (_, _, omdi) ->
(match omdi.Clang_ast_t.omdi_body with
| Some body -> Ast_utils.exists_eventually_st atomic_pred param body
| _ -> false)
| _ -> false
(* true if a variable is initialized with a method/function call.*)
(* implemented as decl |= EF is_function_or_method_call *)
let is_initialized_with_expensive_call decl =
match decl with
| Clang_ast_t.VarDecl (_, _ ,_, vdi) ->
(match vdi.vdi_init_expr with
| Some init_expr ->
eventually_makes_a_call init_expr
| _ -> false)
| _ -> false
let captured_variables_cxx_ref captured_vars =
let capture_var_is_cxx_ref reference_captured_vars captured_var =
let decl_ref_opt = captured_var.Clang_ast_t.bcv_variable in
match Ast_utils.get_decl_opt_with_decl_ref decl_ref_opt with
| Some VarDecl (_, named_decl_info, type_ptr, _)
| Some ParmVarDecl (_, named_decl_info, type_ptr, _)
| Some ImplicitParamDecl (_, named_decl_info, type_ptr, _) ->
(match Ast_utils.get_desugared_type type_ptr with
| Some RValueReferenceType _ | Some LValueReferenceType _ ->
named_decl_info::reference_captured_vars
| _ -> reference_captured_vars)
| _ -> reference_captured_vars in
IList.fold_left capture_var_is_cxx_ref [] captured_vars
(* === Warnings on properties === *)
(* Assing Pointer Warning: a property with a pointer type should not be declared `assign` *)
let assign_pointer_warning decl_info pname obj_c_property_decl_info =
let open Clang_ast_t in
let condition =
let has_assign_property () = ObjcProperty_decl.is_assign_property obj_c_property_decl_info in
let is_pointer_type () =
let type_ptr = obj_c_property_decl_info.opdi_type_ptr in
let raw_ptr = Clang_ast_types.type_ptr_to_clang_pointer type_ptr in
match Clang_ast_main.PointerMap.find raw_ptr !CFrontend_config.pointer_type_index with
| MemberPointerType _ | ObjCObjectPointerType _ | BlockPointerType _ -> true
| TypedefType (type_info, _) -> type_info.ti_raw = "id"
| exception Not_found -> false
| _ -> false in
has_assign_property() && is_pointer_type () in
if condition then
Some
{ name = "ASSIGN_POINTER_WARNING";
description =
Printf.sprintf
"Property `%s` is a pointer type marked with the `assign` attribute"
pname.ni_name;
suggestion = "Use a different attribute like `strong` or `weak`.";
loc = location_from_dinfo decl_info;
}
else None
(* Strong Delegate Warning: a property with name delegate should not be declared strong *)
let strong_delegate_warning decl_info pname obj_c_property_decl_info =
let condition = (name_contains_word pname "delegate")
&& not (name_contains_word pname "queue")
&& ObjcProperty_decl.is_strong_property obj_c_property_decl_info in
if condition then
Some { name = "STRONG_DELEGATE_WARNING";
description = "Property or ivar "^pname.Clang_ast_t.ni_name^" declared strong";
suggestion = "In general delegates should be declared weak or assign";
loc = location_from_dinfo decl_info; }
else None
(* GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL warning: a global variable initialization should not *)
(* contain calls to functions or methods as these can be expensive an delay the starting time *)
(* of an app *)
let global_var_init_with_calls_warning decl =
let decl_info, gvar =
match Clang_ast_proj.get_named_decl_tuple decl with
| Some (di, ndi) -> di, ndi.ni_name
| None -> assert false (* we cannot be here *) in
let condition = (CFrontend_utils.Ast_utils.is_objc () || CFrontend_utils.Ast_utils.is_objcpp ())
&& CFrontend_utils.Ast_utils.is_global_var decl
&& (not (CFrontend_utils.Ast_utils.is_const_expr_var decl))
&& is_initialized_with_expensive_call decl in
if condition then
Some {
name = "GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL";
description = "Global variable " ^ gvar ^
" is initialized using a function or method call";
suggestion = "If the function/method call is expensive, it can affect the starting time of the app.";
loc = location_from_dinfo decl_info; }
else None
(* Direct Atomic Property access:
a property declared atomic should not be accessed directly via its ivar *)
let direct_atomic_property_access_warning context stmt_info ivar_decl_ref =
match Ast_utils.get_decl ivar_decl_ref.Clang_ast_t.dr_decl_pointer with
| Some (ObjCIvarDecl (_, named_decl_info, _, _, _) as d) ->
let ivar_name = named_decl_info.Clang_ast_t.ni_name in
let mname = proc_name_from_context context in
let condition =
(* We give the warning when:
(1) the property has the atomic attribute and
(2) the access of the ivar is not in a getter or setter method.
(3) the access of the ivar is not in the init method
Last two conditions avoids false positives *)
is_ivar_atomic (get_ivar_attributes d)
&& not (CContext.is_curr_proc_objc_getter context ivar_name)
&& not (CContext.is_curr_proc_objc_setter context ivar_name)
&& not (Procname.is_constructor mname)
&& not (Procname.is_objc_dealloc mname) in
if condition then
Some {
name = "DIRECT_ATOMIC_PROPERTY_ACCESS";
description = "Direct access to ivar " ^ ivar_name ^
" of an atomic property";
suggestion = "Accessing an ivar of an atomic property makes the property nonatomic";
loc = location_from_sinfo stmt_info; }
else None
| _ -> None
(* CXX_REFERENCE_CAPTURED_IN_OBJC_BLOCK: C++ references
should not be captured in blocks. *)
let captured_cxx_ref_in_objc_block_warning stmt_info captured_vars =
let capt_refs = captured_variables_cxx_ref captured_vars in
let var_descs =
let var_desc vars var_named_decl_info =
vars ^ "'" ^ var_named_decl_info.Clang_ast_t.ni_name ^ "'" in
IList.fold_left var_desc "" capt_refs in
(* Fire if the list of captured references is not empty *)
let condition = IList.length capt_refs > 0 in
if condition then
Some {
name = "CXX_REFERENCE_CAPTURED_IN_OBJC_BLOCK";
description = "C++ Reference variable(s) " ^ var_descs ^
" captured by Objective-C block";
suggestion = "C++ References are unmanaged and may be invalid " ^
"by the time the block executes.";
loc = location_from_sinfo stmt_info; }
else None
(* exist m1: m1.body|- EF call_method(addObserver:) => exists m2 : m2.body |- EF call_method(removeObserver:) *)
let checker_NSNotificationCenter decl_info decls =
let exists_method_calling_addObserver =
IList.exists (dec_body_eventually (call_method_on_nth is_self 1) "addObserver:selector:name:object:") decls in
let exists_method_calling_addObserverForName =
IList.exists (dec_body_eventually (call_method_on_nth is_self 1) "addObserverForName:object:queue:usingBlock:") decls in
let eventually_addObserver = exists_method_calling_addObserver
|| exists_method_calling_addObserverForName in
let exists_method_calling_removeObserver =
IList.exists (dec_body_eventually (call_method_on_nth is_self 1) "removeObserver:") decls in
let exists_method_calling_removeObserverName =
IList.exists (dec_body_eventually (call_method_on_nth is_self 1) "removeObserver:name:object:") decls in
let eventually_removeObserver = exists_method_calling_removeObserver
|| exists_method_calling_removeObserverName in
let condition = eventually_addObserver && (not eventually_removeObserver) in
if condition then
Some {
name = Localise.to_string (Localise.registered_observer_being_deallocated);
description = Localise.registered_observer_being_deallocated_str CFrontend_config.self;
suggestion = "Consider removing the object from the notification center before its deallocation.";
loc = location_from_dinfo decl_info; }
else None