(* * 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 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 (* 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 (* st |= EF (atomic_pred param) *) let rec exists_eventually_st atomic_pred param st = if atomic_pred param st then true else let _, st_list = Clang_ast_proj.get_stmt_tuple st in IList.exists (exists_eventually_st atomic_pred param) st_list 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 -> exists_eventually_st atomic_pred param body | _ -> false) | _ -> false (* === 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 (* 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_name = let tenv = CContext.get_tenv context in let mname = proc_name_from_context context in let ivar, cname = match ivar_name with | Some n -> General_utils.mk_class_field_name n, Ast_utils.get_class_name_from_member n | _ -> Ident.create_fieldname (Mangled.from_string "") 0, "" in let tname = Typename.TN_csu (Csu.Class Csu.Objc, Mangled.from_string cname) in let condition = match Tenv.lookup tenv tname with | Some { Sil.instance_fields; static_fields } -> (* 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 *) (CField_decl.is_ivar_atomic ivar (instance_fields @ static_fields)) && not (CContext.is_curr_proc_objc_getter context ivar) && not (CContext.is_curr_proc_objc_setter context ivar) && not (Procname.is_constructor mname) && not (Procname.is_objc_dealloc mname) | _ -> false in if condition then Some { name = "DIRECT_ATOMIC_PROPERTY_ACCESS"; description = "Direct access to ivar " ^ (Ident.fieldname_to_string ivar) ^ " of an atomic property"; suggestion = "Accessing an ivar of an atomic property makes the property nonatomic"; loc = location_from_sinfo stmt_info; } else 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 is_cxx_ref (_, typ) = match typ with | Sil.Tptr(_, Sil.Pk_reference) -> true | _ -> false in let capt_refs = IList.filter is_cxx_ref captured_vars in let pvar_descs = IList.fold_left (fun s (v, _) -> s ^ " '" ^ (Pvar.to_string v) ^ "' ") "" 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) " ^ pvar_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