(* * 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 *) (* 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 | _ -> []) | _ -> [] let is_method_property_accessor_of_ivar method_decl ivar_pointer = let open Clang_ast_t in match method_decl with | ObjCMethodDecl (_, _, obj_c_method_decl_info) -> if obj_c_method_decl_info.Clang_ast_t.omdi_is_property_accessor then let prperty_opt = obj_c_method_decl_info.Clang_ast_t.omdi_property_decl in match Ast_utils.get_decl_opt_with_decl_ref prperty_opt with | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) -> (match obj_c_property_decl_info.Clang_ast_t.opdi_ivar_decl with | Some decl_ref -> decl_ref.Clang_ast_t.dr_decl_pointer = ivar_pointer | None -> false) | _ -> false else false | _ -> false (* 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 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 { CIssue.issue = CIssue.Assign_pointer_warning; CIssue.description = Printf.sprintf "Property `%s` is a pointer type marked with the `assign` attribute" pname.ni_name; CIssue.suggestion = Some "Use a different attribute like `strong` or `weak`."; CIssue.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 { CIssue.issue = CIssue.Strong_delegate_warning; CIssue.description = "Property or ivar "^pname.Clang_ast_t.ni_name^" declared strong"; CIssue.suggestion = Some "In general delegates should be declared weak or assign"; CIssue.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 { CIssue.issue = CIssue.Global_variable_initialized_with_function_or_method_call; CIssue.description = "Global variable " ^ gvar ^ " is initialized using a function or method call"; CIssue.suggestion = Some "If the function/method call is expensive, it can affect the starting time of the app."; CIssue.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 method_decl stmt_info ivar_decl_ref = let ivar_pointer = ivar_decl_ref.Clang_ast_t.dr_decl_pointer in match Ast_utils.get_decl ivar_pointer with | Some (ObjCIvarDecl (_, named_decl_info, _, _, _) as d) -> let method_name = match Clang_ast_proj.get_named_decl_tuple method_decl with | Some (_, method_named_decl) -> method_named_decl.Clang_ast_t.ni_name | _ -> "" in let ivar_name = named_decl_info.Clang_ast_t.ni_name 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 (is_method_property_accessor_of_ivar method_decl ivar_pointer) && not (Procname.is_objc_constructor method_name) && not (Procname.is_objc_dealloc method_name) in if condition then Some { CIssue.issue = CIssue.Direct_atomic_property_access; CIssue.description = "Direct access to ivar " ^ ivar_name ^ " of an atomic property"; CIssue.suggestion = Some "Accessing an ivar of an atomic property makes the property nonatomic"; CIssue.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 { CIssue.issue = CIssue.Cxx_reference_captured_in_objc_block; CIssue.description = "C++ Reference variable(s) " ^ var_descs ^ " captured by Objective-C block"; CIssue.suggestion = Some ("C++ References are unmanaged and may be invalid " ^ "by the time the block executes."); CIssue.loc = location_from_sinfo stmt_info } else None (* BAD_POINTER_COMPARISON: Fires whenever a NSNumber is dangerously coerced to a boolean in a comparison *) let bad_pointer_comparison_warning stmt_info stmts = let rec condition stmts = let condition_aux stmt = match stmt with | Clang_ast_t.CallExpr _ | Clang_ast_t.CXXMemberCallExpr _ | Clang_ast_t.CXXOperatorCallExpr _ | Clang_ast_t.ObjCMessageExpr _ -> false | Clang_ast_t.BinaryOperator (_, _, _, boi) when (boi.boi_kind = `NE) || (boi.boi_kind = `EQ) -> false | Clang_ast_t.UnaryOperator (_, stmts, _, uoi) when uoi.uoi_kind = `LNot -> condition stmts | stmt -> match Clang_ast_proj.get_expr_tuple stmt with | Some (_, stmts, expr_info) -> let typ = CFrontend_utils.Ast_utils.get_desugared_type expr_info.ei_type_ptr in if CFrontend_utils.Ast_utils.is_ptr_to_objc_class typ "NSNumber" then true else condition stmts | _ -> false in IList.exists condition_aux stmts in if condition stmts then Some { CIssue. issue = CIssue.Bad_pointer_comparison; description = "Implicitly checking whether NSNumber pointer is nil"; suggestion = Some ("Did you mean to compare against the unboxed value instead? " ^ "Please either explicitly compare the NSNumber instance to nil, " ^ "or use one of the NSNumber accessors before the comparison."); 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 { CIssue.issue = CIssue.Registered_observer_being_deallocated; CIssue.description = Localise.registered_observer_being_deallocated_str CFrontend_config.self; CIssue.suggestion = Some "Consider removing the object from the notification center before its deallocation."; CIssue.loc = location_from_dinfo decl_info } else None