diff --git a/infer/src/clang/cPredicates.ml b/infer/src/clang/cPredicates.ml index 5cd370c7e..4786d2fc3 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -361,6 +361,7 @@ let type_ptr_equal_type type_ptr type_str = (string_of_int (pos.pos_cnum - pos.pos_bol + 1)) in let parse_type_string str = + L.(debug Linters Medium) "Starting parsing type string '%s'@\n" str; let lexbuf = Lexing.from_string str in try (Types_parser.abs_ctype token lexbuf) @@ -383,18 +384,17 @@ let type_ptr_equal_type type_ptr type_str = Ctl_parser_types.c_type_equal c_type' abs_ctype | _ -> L.(debug Linters Medium) "Couldn't find type....@\n"; false -let has_type an _typ = - match an, _typ with - | Ctl_parser_types.Stmt stmt, ALVar.Const typ -> +let get_ast_node_type_ptr an = + match an with + | Ctl_parser_types.Stmt stmt -> (match Clang_ast_proj.get_expr_tuple stmt with - | Some (_, _, expr_info) -> - type_ptr_equal_type expr_info.ei_qual_type.qt_type_ptr typ - | _ -> false) - | Ctl_parser_types.Decl decl, ALVar.Const typ -> - (match CAst_utils.type_of_decl decl with - | Some type_ptr -> - type_ptr_equal_type type_ptr typ - | _ -> false) + | Some (_, _, expr_info) -> Some expr_info.ei_qual_type.qt_type_ptr + | _ -> None) + | Ctl_parser_types.Decl decl -> CAst_utils.type_of_decl decl + +let has_type an _typ = + match get_ast_node_type_ptr an, _typ with + | Some pt, ALVar.Const typ -> type_ptr_equal_type pt typ | _ -> false let method_return_type an _typ = @@ -406,6 +406,45 @@ let method_return_type an _typ = type_ptr_equal_type qual_type.Clang_ast_t.qt_type_ptr typ | _ -> false +let rec check_protocol_hiearachy decls_ptr _prot_name = + let open Clang_ast_t in + let is_this_protocol di_opt = + match di_opt with + | Some di -> ALVar.compare_str_with_alexp di.ni_name _prot_name + | _ -> false in + match decls_ptr with + | [] -> false + | pt :: decls' -> + let di, protocols = (match CAst_utils.get_decl pt with + | Some ObjCProtocolDecl (_, di, _, _, opcdi) -> + Some di, opcdi.opcdi_protocols + | _ -> None, []) in + if (is_this_protocol di) + || List.exists ~f:(fun dr -> is_this_protocol dr.dr_name) protocols then + true + else + let super_prot = List.map ~f:(fun dr -> dr.dr_decl_pointer) protocols in + check_protocol_hiearachy (super_prot @ decls') _prot_name + +let has_type_subprotocol_of an _prot_name = + let open Clang_ast_t in + let rec check_subprotocol t = + match t with + | Some ObjCObjectPointerType (_, qt) -> + check_subprotocol (CAst_utils.get_type qt.qt_type_ptr) + | Some ObjCObjectType (_, ooti) -> + if List.length ooti.protocol_decls_ptr > 0 then + check_protocol_hiearachy ooti.protocol_decls_ptr _prot_name + else + List.exists + ~f:(fun qt -> check_subprotocol (CAst_utils.get_type qt.qt_type_ptr)) ooti.type_args + | Some ObjCInterfaceType (_, pt) -> + check_protocol_hiearachy [pt] _prot_name + | _ -> false in + match get_ast_node_type_ptr an with + | Some tp -> check_subprotocol (CAst_utils.get_type tp) + | _ -> false + let within_responds_to_selector_block (cxt:CLintersContext.context) an = let open Clang_ast_t in match an with diff --git a/infer/src/clang/cPredicates.mli b/infer/src/clang/cPredicates.mli index 56d94aa80..7b435c4a3 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -74,6 +74,8 @@ val has_type : Ctl_parser_types.ast_node -> ALVar.alexp -> bool val method_return_type : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +val has_type_subprotocol_of : Ctl_parser_types.ast_node -> ALVar.alexp -> bool + val get_available_attr_ios_sdk : Ctl_parser_types.ast_node -> string option val within_responds_to_selector_block : diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index 621faf55a..fd3d57178 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -25,6 +25,7 @@ type transitions = | Parameters (** decl to decl *) | Cond | PointerToDecl (** stmt to decl *) + | Protocol (** decl to decl *) (* In formulas below prefix "E" means "exists a path" @@ -106,6 +107,7 @@ module Debug = struct | Super -> Format.pp_print_string fmt "Super" | Parameters -> Format.pp_print_string fmt "Parameters" | Cond -> Format.pp_print_string fmt "Cond" + | Protocol -> Format.pp_print_string fmt "Protocol" | PointerToDecl -> Format.pp_print_string fmt "PointerToDecl" in match trans_opt with | Some trans -> pp_aux fmt trans @@ -576,6 +578,18 @@ let transition_decl_to_decl_via_super d = decl_opt_to_ast_node_opt (CAst_utils.get_decl_opt_with_decl_ref idi.otdi_super) | _ -> [] + +let transition_decl_to_decl_via_protocol d = + let open Clang_ast_t in + let get_nodes dr = + match CAst_utils.get_decl dr.dr_decl_pointer with + | Some d -> Some (Decl d) + | None -> None in + match d with + | Clang_ast_t.ObjCProtocolDecl (_, _, _, _, opdi) -> + List.filter_map ~f:get_nodes opdi.opcdi_protocols + | _ -> [] + let transition_stmt_to_stmt_via_condition st = let open Clang_ast_t in match st with @@ -612,6 +626,7 @@ let next_state_via_transition an trans = | Decl d, Parameters -> transition_decl_to_decl_via_parameters d | Decl d, InitExpr | Decl d, Body -> transition_decl_to_stmt d trans + | Decl d, Protocol -> transition_decl_to_decl_via_protocol d | Stmt st, Cond -> transition_stmt_to_stmt_via_condition st | Stmt st, PointerToDecl -> transition_stmt_to_decl_via_pointer st | _, _ -> [] @@ -659,6 +674,8 @@ let rec eval_Atomic _pred_name args an lcxt = CPredicates.objc_method_has_nth_parameter_of_type an num typ | "using_namespace", [namespace], an -> CPredicates.using_namespace an namespace + | "has_type_subprotocol_of", [protname], an -> + CPredicates.has_type_subprotocol_of an protname | _ -> failwith ("ERROR: Undefined Predicate or wrong set of arguments: '" ^ pred_name ^ "'") diff --git a/infer/src/clang/cTL.mli b/infer/src/clang/cTL.mli index 7dc773124..ea2711361 100644 --- a/infer/src/clang/cTL.mli +++ b/infer/src/clang/cTL.mli @@ -23,6 +23,7 @@ type transitions = | Parameters (* decl to decl *) | Cond | PointerToDecl (* stmt to decl *) + | Protocol (** decl to decl *) (* In formulas below prefix "E" means "exists a path" diff --git a/infer/src/clang/ctl_parser.mly b/infer/src/clang/ctl_parser.mly index 0ad0fd763..55e7e229d 100644 --- a/infer/src/clang/ctl_parser.mly +++ b/infer/src/clang/ctl_parser.mly @@ -222,6 +222,7 @@ actual_params: transition_label: | identifier { match $1 with | "Body" | "body" -> "Body", Some CTL.Body + | "Protocol" | "protocol" -> "Protocol", Some CTL.Protocol | "InitExpr" | "initexpr" -> "InitExpr", Some CTL.InitExpr | "Cond" | "cond" -> "Cond", Some CTL.Cond | "Parameters" | "parameters" -> "Parameters", Some CTL.Parameters @@ -245,11 +246,20 @@ formula: | formula AU formula { L.(debug Linters Verbose) "\tParsed AU@\n"; CTL.AU (None,$1, $3) } | formula AF { L.(debug Linters Verbose) "\tParsed AF@\n"; CTL.AF (None,$1) } | formula EX { L.(debug Linters Verbose) "\tParsed EX@\n"; CTL.EX (None, $1) } + | formula EX WITH_TRANSITION transition_label + { L.(debug Linters Verbose) "\tParsed EX WITH-TRANSITION '%s'@\n" (fst $4); + CTL.EX (snd $4, $1) } | formula AX { L.(debug Linters Verbose) "\tParsed AX@\n"; CTL.AX (None, $1) } + | formula AX WITH_TRANSITION transition_label + { L.(debug Linters Verbose) "\tParsed AX WITH-TRANSITION '%s'@\n" (fst $4); + CTL.AX (snd $4, $1) } | formula EG { L.(debug Linters Verbose) "\tParsed EG@\n"; CTL.EG (None, $1) } | formula AG { L.(debug Linters Verbose) "\tParsed AG@\n"; CTL.AG (None, $1) } | formula EH node_list { L.(debug Linters Verbose) "\tParsed EH@\n"; CTL.EH ($3, $1) } | formula EF { L.(debug Linters Verbose) "\tParsed EF@\n"; CTL.EF (None, $1) } + | formula EF WITH_TRANSITION transition_label + { L.(debug Linters Verbose) "\tParsed EF WITH-TRANSITION '%s'@\n" (fst $4); + CTL.EF(snd $4, $1) } | WHEN formula HOLDS_IN_NODE node_list { L.(debug Linters Verbose) "\tParsed InNode@\n"; CTL.InNode ($4, $2)} | ET node_list WITH_TRANSITION transition_label formula_EF diff --git a/infer/src/clang/ctl_parser_types.ml b/infer/src/clang/ctl_parser_types.ml index 8e096e16b..e0bc1ae28 100644 --- a/infer/src/clang/ctl_parser_types.ml +++ b/infer/src/clang/ctl_parser_types.ml @@ -145,6 +145,7 @@ type abs_ctype = | BuiltIn of builtin_kind | Pointer of abs_ctype | TypeName of ALVar.alexp + | ObjCGenProt of abs_ctype * abs_ctype (* Objective-C Protocol or Generics *) let display_equality_warning () = L.(debug Linters Medium) @@ -157,6 +158,8 @@ let rec abs_ctype_to_string t = | BuiltIn t' -> "BuiltIn (" ^ (builtin_kind_to_string t') ^ ")" | Pointer t' -> "Pointer (" ^ (abs_ctype_to_string t') ^ ")" | TypeName ae -> "TypeName (" ^ (ALVar.alexp_to_string ae) ^ ")" + | ObjCGenProt (b,p) -> + "ObjCGenProt (" ^ (abs_ctype_to_string b) ^ "," ^ (abs_ctype_to_string p) ^")" let builtin_type_kind_assoc = [ @@ -209,25 +212,51 @@ let rec pointer_type_equal p ap = match p, ap with | PointerType (_, qt), Pointer abs_ctype' | ObjCObjectPointerType (_, qt), Pointer abs_ctype' -> - (match CAst_utils.get_type qt.qt_type_ptr with - | Some c_type' -> - c_type_equal c_type' abs_ctype' - | None -> false) + check_type_ptr qt.qt_type_ptr abs_ctype' | _, _ -> display_equality_warning (); false +and objc_object_type_equal c_type abs_ctype = + let open Clang_ast_t in + let check_type_args abs_arg_type qt = + check_type_ptr qt.qt_type_ptr abs_arg_type in + let check_prot prot_name pointer = + match prot_name with + | TypeName ae -> typename_equal pointer ae + | _ -> false in + match c_type, abs_ctype with + | ObjCObjectType (_, ooti), ObjCGenProt (base, args) -> + (match (CAst_utils.get_type ooti.base_type), ooti.protocol_decls_ptr, ooti.type_args with + | Some base_type, _::_, [] -> + c_type_equal base_type base && + (List.for_all ~f:(check_prot args) ooti.protocol_decls_ptr) + | Some base_type, [], _::_ -> + c_type_equal base_type base && + (List.for_all ~f:(check_type_args args) ooti.type_args) + | _ -> false) + | _ -> false + + and typename_equal pointer typename = match typename_to_string pointer with | Some name -> + L.(debug Linters Medium) + "Comparing typename '%s' and pointer '%s' for equality...@\n" + (ALVar.alexp_to_string typename) name; ALVar.compare_str_with_alexp name typename | None -> false +and check_type_ptr type_ptr abs_ctype = + match CAst_utils.get_type type_ptr with + | Some c_type' -> c_type_equal c_type' abs_ctype + | None -> false + (* Temporary, partial equality function. Cover only what's covered by the types_parser. It needs to be replaced by a real comparison function for Clang_ast_t.c_type *) and c_type_equal c_type abs_ctype = L.(debug Linters Medium) - "Comparing c_type/abs_ctype for equality... \ + "@\nComparing c_type/abs_ctype for equality... \ Type compared: @\nc_type = `%s` @\nabs_ctype =`%s`@\n" (Clang_ast_j.string_of_c_type c_type) (abs_ctype_to_string abs_ctype); @@ -238,10 +267,18 @@ and c_type_equal c_type abs_ctype = | PointerType _, Pointer _ | ObjCObjectPointerType _, Pointer _ -> pointer_type_equal c_type abs_ctype + | ObjCObjectPointerType (_, qt), ObjCGenProt _ -> + check_type_ptr qt.qt_type_ptr abs_ctype + | ObjCObjectType _, ObjCGenProt _ -> + objc_object_type_equal c_type abs_ctype | ObjCInterfaceType (_, pointer), TypeName ae -> typename_equal pointer ae | TypedefType (_, tdi), TypeName ae -> typename_equal tdi.tti_decl_ptr ae + | TypedefType (ti, _), ObjCGenProt _ -> + (match ti.ti_desugared_type with + | Some dt -> check_type_ptr dt abs_ctype + | None -> false) | _, _ -> display_equality_warning (); false diff --git a/infer/src/clang/ctl_parser_types.mli b/infer/src/clang/ctl_parser_types.mli index 42716eaf6..b419dec93 100644 --- a/infer/src/clang/ctl_parser_types.mli +++ b/infer/src/clang/ctl_parser_types.mli @@ -66,6 +66,7 @@ type abs_ctype = | BuiltIn of builtin_kind | Pointer of abs_ctype | TypeName of ALVar.alexp + | ObjCGenProt of abs_ctype * abs_ctype (* Objective-C Protocol or Generics *) val c_type_equal : Clang_ast_t.c_type -> abs_ctype -> bool diff --git a/infer/src/clang/types_lexer.mll b/infer/src/clang/types_lexer.mll index 23f5407fc..c04bdd4f0 100644 --- a/infer/src/clang/types_lexer.mll +++ b/infer/src/clang/types_lexer.mll @@ -59,6 +59,8 @@ rule token = parse | "REGEXP" { REGEXP } | "(" { LEFT_PAREN } | ")" { RIGHT_PAREN } + | "<" { LEFT_ANGLE } + | ">" { RIGHT_ANGLE } | id { IDENTIFIER (Lexing.lexeme lexbuf) } | ''' { read_string (Buffer.create 80) lexbuf } | _ { raise (SyntaxError ("Unexpected char: '" ^ (Lexing.lexeme lexbuf) ^"'")) } diff --git a/infer/src/clang/types_parser.mly b/infer/src/clang/types_parser.mly index e3404092b..b4c3af00b 100644 --- a/infer/src/clang/types_parser.mly +++ b/infer/src/clang/types_parser.mly @@ -61,6 +61,8 @@ %token REGEXP %token LEFT_PAREN %token RIGHT_PAREN +%token LEFT_ANGLE +%token RIGHT_ANGLE %token IDENTIFIER %token STRING %token REARG @@ -76,6 +78,7 @@ abs_ctype: ; ctype_specifier_seq: +| protocol_or_generics_type_spec { $1 } | noptr_type_spec { $1 } | ptr_type_spec { $1 } | type_name { $1 } @@ -85,10 +88,30 @@ ptr_type_spec: | noptr_type_spec STAR { Pointer $1 } | ptr_type_spec STAR { Pointer $1 } | type_name STAR { Pointer $1 } +| protocol_or_generics_type_spec STAR { Pointer $1 } ; +protocol_or_generics_type_spec: +| type_name_or_objid LEFT_ANGLE ctype_specifier_seq RIGHT_ANGLE { + let tname = $1 in + L.(debug Linters Verbose) "\tProtocol or Generics parsed: `%s<%s>`@\n" + (Ctl_parser_types.abs_ctype_to_string tname) + (Ctl_parser_types.abs_ctype_to_string $3); + ObjCGenProt (tname, $3) + } +; + + +type_name_or_objid: + | OBJCID { BuiltIn ObjCId} + | type_name { $1 } + ; + type_name: - | alexp { TypeName $1 } + | alexp { + L.(debug Linters Verbose) "\tType_name parsed: `%s`@\n" + (ALVar.alexp_to_string $1); + TypeName $1 } noptr_type_spec: | trailing_type_specifier_seq diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp index e79b27af1..6370f70c2 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp @@ -10,6 +10,19 @@ codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 11, TEST_IMPLICI codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 11, TEST_VAR_TYPE_CHECK, [] codetoanalyze/objc/linters-for-test-only/namespace.mm, Linters_dummy_method, 9, TEST_DEFINE_NAMESPACE, [] codetoanalyze/objc/linters-for-test-only/namespace.mm, Linters_dummy_method, 17, TEST_USING_NAMESPACE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithA:, 24, TEST_INSTANCE_TYPE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithA:, 24, TEST_PROTOCOL_TYPE_INHERITANCE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithB:, 25, TEST_INSTANCE_TYPE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithB:, 25, TEST_PROTOCOL_TYPE_INHERITANCE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithC:, 26, TEST_INSTANCE_TYPE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithC:, 26, TEST_PROTOCOL_TYPE_INHERITANCE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithCs:, 27, TEST_GENERICS_TYPE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithCs:, 27, TEST_INSTANCE_TYPE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithCs:, 27, TEST_PROTOCOL_TYPE_INHERITANCE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Foo_newWithD:, 28, TEST_BUILTIN_TYPE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Linters_dummy_method, 11, TEST_PROTOCOL_DEF_INHERITANCE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Linters_dummy_method, 14, TEST_PROTOCOL_DEF_INHERITANCE, [] +codetoanalyze/objc/linters-for-test-only/protocols.m, Linters_dummy_method, 17, TEST_PROTOCOL_DEF_INHERITANCE, [] codetoanalyze/objc/linters-for-test-only/subclassing.m, A_foo:, 13, TEST_BUILTIN_TYPE, [] codetoanalyze/objc/linters-for-test-only/subclassing.m, A_foo:, 13, TEST_PARAM_TYPE_CHECK2, [] codetoanalyze/objc/linters-for-test-only/subclassing.m, A_foo:, 13, TEST_RETURN_METHOD, [] diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al b/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al index 4e48574cc..306f4a53e 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al @@ -221,6 +221,63 @@ DEFINE-CHECKER TEST_NTH_PARAM_TYPE_CHECK = { SET severity = "LIKE"; }; +DEFINE-CHECKER TEST_PROTOCOL_DEF_INHERITANCE = { + + LET is_subprotocol_of(x) = + declaration_has_name(x) HOLDS-EVENTUALLY WITH-TRANSITION Protocol; + + SET report_when = + WHEN + is_subprotocol_of("A") + HOLDS-IN-NODE ObjCProtocolDecl; + + SET message = "Protocol inherit from A"; +}; + +DEFINE-CHECKER TEST_PROTOCOL_TYPE_INHERITANCE = { + + LET method_has_parameter_subprotocol_of(x) = + WHEN + HOLDS-NEXT WITH-TRANSITION Parameters + (has_type_subprotocol_of(x)) + HOLDS-IN-NODE ObjCMethodDecl; + + SET report_when = + WHEN + declaration_has_name(REGEXP("^newWith.*:$")) AND + method_has_parameter_subprotocol_of("A") + HOLDS-IN-NODE ObjCMethodDecl; + + SET message = "Method declared with parameter whose type inherit from protocol A"; +}; + +DEFINE-CHECKER TEST_GENERICS_TYPE = { + + LET method_has_parameter_type(x) = + WHEN + HOLDS-NEXT WITH-TRANSITION Parameters + (has_type(x)) + HOLDS-IN-NODE ObjCMethodDecl; + + SET report_when = + WHEN + method_has_parameter_type ("NSArray>*") + HOLDS-IN-NODE ObjCMethodDecl; + + SET message = "Method declared with parameter whose type NSArray>*"; +}; + + +DEFINE-CHECKER TEST_INSTANCE_TYPE = { + + SET report_when = + WHEN + has_type("instancetype") + HOLDS-IN-NODE ObjCMethodDecl; + + SET message = "Method declared has return type instancetype"; +}; + DEFINE-CHECKER TEST_DEFINE_NAMESPACE = { SET report_when = diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/protocols.m b/infer/tests/codetoanalyze/objc/linters-for-test-only/protocols.m new file mode 100644 index 000000000..80eb30116 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/protocols.m @@ -0,0 +1,29 @@ +/* + * 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 + +@protocol A +@end + +@protocol B +@end + +@protocol C +@end + +@protocol D +@end + +@interface Foo : NSObject ++ (instancetype)newWithA:(id)A; // A is a known "bad" protocol, so fire here ++ (instancetype)newWithB:(id)B; // B inherits from A, so this line also fires ++ (instancetype)newWithC:(id)C; // C eventually inherits from A; also fires ++ (instancetype)newWithCs:(NSArray>*)Cs; // Collections should also fire ++ (void)newWithD:(id)D; // D doesn't inherit from A; don't fire +@end