diff --git a/infer/lib/linter_rules/linters.al b/infer/lib/linter_rules/linters.al index 89a70310f..3a234b206 100644 --- a/infer/lib/linter_rules/linters.al +++ b/infer/lib/linter_rules/linters.al @@ -117,7 +117,7 @@ DEFINE-CHECKER REGISTERED_OBSERVER_BEING_DEALLOCATED = { HOLDS-EVENTUALLY; LET eventually_removeObserver = - IN-NODE ObjCImplementationDecl, ObjCProtocolDecl WITH-TRANSITION _ + IN-NODE ObjCImplementationDecl, ObjCProtocolDecl WITH-TRANSITION Any (remove_observer_in_method OR remove_observer_in_method HOLDS-IN-SOME-SUPERCLASS-OF ObjCImplementationDecl) HOLDS-EVENTUALLY; diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index 05d12b69d..54151df39 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -20,6 +20,7 @@ type transitions = | Body (** decl to stmt *) | InitExpr (** decl to stmt *) | Super (** decl to decl *) + | ParameterName of ALVar.alexp (** stmt to stmt, decl to decl *) | Parameters (** decl to decl *) | Cond | PointerToDecl (** stmt to decl *) @@ -124,6 +125,8 @@ module Debug = struct -> Format.pp_print_string fmt "InitExpr" | Super -> Format.pp_print_string fmt "Super" + | ParameterName name + -> Format.pp_print_string fmt ("ParameterName " ^ ALVar.alexp_to_string name) | Parameters -> Format.pp_print_string fmt "Parameters" | Cond @@ -702,6 +705,35 @@ let transition_decl_to_decl_via_parameters dec = | _ -> [] +let parameter_of_corresp_name method_name args name = + let names = + List.filter (String.split ~on:':' method_name) ~f:(fun label -> not (String.is_empty label)) + in + match List.zip names args with + | Some names_args + -> ( + let names_arg_opt = + List.find names_args ~f:(fun (arg_label, _) -> ALVar.compare_str_with_alexp arg_label name) + in + match names_arg_opt with Some (_, arg) -> Some arg | None -> None ) + | None + -> None + +let transition_via_parameter_name an name = + match an with + | Stmt ObjCMessageExpr (_, stmt_list, _, omei) + -> ( + let arg_stmt_opt = parameter_of_corresp_name omei.omei_selector stmt_list name in + match arg_stmt_opt with Some arg -> [Stmt arg] | None -> [] ) + | Decl ObjCMethodDecl (_, named_decl_info, omdi) + -> ( + let arg_decl_opt = + parameter_of_corresp_name named_decl_info.ni_name omdi.omdi_parameters name + in + match arg_decl_opt with Some arg -> [Decl arg] | None -> [] ) + | _ + -> [] + (* given a node an returns a list of nodes an' such that an transition to an' via label trans *) let next_state_via_transition an trans = match (an, trans) with @@ -717,6 +749,8 @@ let next_state_via_transition an trans = -> transition_stmt_to_stmt_via_condition st | Stmt st, PointerToDecl -> transition_stmt_to_decl_via_pointer st + | an, ParameterName name + -> transition_via_parameter_name an name | _, _ -> [] diff --git a/infer/src/clang/cTL.mli b/infer/src/clang/cTL.mli index 22cf6b2d5..00a7b5704 100644 --- a/infer/src/clang/cTL.mli +++ b/infer/src/clang/cTL.mli @@ -23,6 +23,8 @@ type transitions = (* decl to stmt *) | Super (* decl to decl *) + | ParameterName of ALVar.alexp + (* stmt to stmt, decl to decl *) | Parameters (* decl to decl *) | Cond @@ -122,4 +124,6 @@ val create_ctl_evaluation_tracker : SourceFile.t -> unit module Debug : sig val pp_formula : Format.formatter -> t -> unit + + val pp_transition : Format.formatter -> transitions option -> unit end diff --git a/infer/src/clang/ctl_lexer.mll b/infer/src/clang/ctl_lexer.mll index 3ea2d5945..9834f683a 100644 --- a/infer/src/clang/ctl_lexer.mll +++ b/infer/src/clang/ctl_lexer.mll @@ -71,6 +71,14 @@ rule token = parse | "NOT" { NOT } | "IMPLIES" { IMPLIES } | "REGEXP" { REGEXP } + | "Any" {ANY} + | "Parameters" { PARAMETERS } + | "ParameterName" { PARAMETER_NAME } + | "Body" {BODY} + | "Protocol" {PROTOCOL} + | "InitExpr" {INIT_EXPR} + | "Cond" {COND} + | "PointerToDecl" {POINTER_TO_DECL} | id { IDENTIFIER (Lexing.lexeme lexbuf) } | file_id { FILE_IDENTIFIER (Lexing.lexeme lexbuf) } | '"' { read_string (Buffer.create 80) lexbuf } diff --git a/infer/src/clang/ctl_parser.mly b/infer/src/clang/ctl_parser.mly index 5d0d0b680..a5d9ce512 100644 --- a/infer/src/clang/ctl_parser.mly +++ b/infer/src/clang/ctl_parser.mly @@ -71,6 +71,14 @@ %token STRING %token WHITELIST_PATH %token BLACKLIST_PATH +%token ANY +%token BODY +%token COND +%token INIT_EXPR +%token PARAMETERS +%token PARAMETER_NAME +%token POINTER_TO_DECL +%token PROTOCOL %token EOF /* associativity and priority (lower to higher) of operators */ @@ -221,14 +229,14 @@ 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 - | "PointerToDecl" | "pointertodecl" -> "PointerToDecl", Some CTL.PointerToDecl - | _ -> "None", None } + | ANY { None } + | BODY { Some CTL.Body } + | COND { Some CTL.Cond } + | INIT_EXPR { Some CTL.InitExpr } + | PARAMETERS { Some CTL.Parameters } + | PARAMETER_NAME alexp { Some (CTL.ParameterName $2) } + | POINTER_TO_DECL { Some CTL.PointerToDecl } + | PROTOCOL { Some CTL.Protocol } ; formula_EF: @@ -248,33 +256,34 @@ formula: | 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) } + { L.(debug Linters Verbose) "\tParsed EX WITH-TRANSITION '%a'@\n" CTL.Debug.pp_transition $4; + CTL.EX ($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) } + { L.(debug Linters Verbose) "\tParsed AX WITH-TRANSITION '%a'@\n" CTL.Debug.pp_transition $4; + CTL.AX ($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) } + { L.(debug Linters Verbose) "\tParsed EF WITH-TRANSITION '%a'@\n" CTL.Debug.pp_transition $4; + CTL.EF($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 - { L.(debug Linters Verbose) "\tParsed ET with transition '%s'@\n" (fst $4); - CTL.ET ($2, snd $4, $5)} + { L.(debug Linters Verbose) "\tParsed ET with transition '%a'@\n" CTL.Debug.pp_transition $4; + CTL.ET ($2, $4, $5)} | ETX node_list WITH_TRANSITION transition_label formula_EF - { L.(debug Linters Verbose) "\tParsed ETX ith transition '%s'@\n" (fst $4); - CTL.ETX ($2, snd $4, $5)} + { L.(debug Linters Verbose) "\tParsed ETX ith transition '%a'@\n" CTL.Debug.pp_transition $4; + CTL.ETX ($2, $4, $5)} | EX WITH_TRANSITION transition_label formula_with_paren - { L.(debug Linters Verbose) "\tParsed EX with transition '%s'@\n" (fst $3); - CTL.EX (snd $3, $4)} + { L.(debug Linters Verbose) "\tParsed EX with transition '%a'@\n" CTL.Debug.pp_transition $3; + CTL.EX ($3, $4)} | AX WITH_TRANSITION transition_label formula_with_paren - { L.(debug Linters Verbose) "\tParsed AX with transition '%s'@\n" (fst $3); - CTL.AX (snd $3, $4)} + { L.(debug Linters Verbose) + "\tParsed AX with transition '%a'@\n" CTL.Debug.pp_transition $3; + CTL.AX ($3, $4)} | formula AND formula { L.(debug Linters Verbose) "\tParsed AND@\n"; CTL.And ($1, $3) } | formula OR formula { L.(debug Linters Verbose) "\tParsed OR@\n"; CTL.Or ($1, $3) } | formula IMPLIES formula diff --git a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm new file mode 100644 index 000000000..d38096964 --- /dev/null +++ b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm @@ -0,0 +1,39 @@ +/* + * 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. + */ +#include + +#import + +struct SomeStruct { + NSString* someLabel; +}; + +@interface SomeButton : NSObject + ++ (instancetype)newWithStruct:(SomeStruct)aStruct + map:(const std::unordered_map&)aMap + object:(id)anObject + number:(int)n; + +@end + +SomeButton* buttonComponent(void); +SomeButton* buttonComponent(void) { + return [SomeButton newWithStruct:{} map:{} object:nil number:0]; +}; + +SomeButton* anotherButtonComponent(void); +SomeButton* anotherButtonComponent(void) { + return [SomeButton newWithStruct:{.someLabel = @"hi"} + map:{ + { 1, @"some title" } + } + object:@"a string object" + number:5]; +}; diff --git a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp index 2e4933c3c..f5090a1d3 100644 --- a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp +++ b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/issues.exp @@ -4,3 +4,7 @@ codetoanalyze/objcpp/linters-for-test-only/ReferenceTest.mm, ReferenceTest_newWi codetoanalyze/objcpp/linters-for-test-only/ReferenceTest.mm, ReferenceTest_newWithActionRef:, 40, TEST_REFERENCE, [] codetoanalyze/objcpp/linters-for-test-only/ReferenceTest.mm, ReferenceTest_newWithConstAction:, 21, TEST_REFERENCE, [] codetoanalyze/objcpp/linters-for-test-only/ReferenceTest.mm, ReferenceTest_newWithConstAction:, 36, TEST_REFERENCE, [] +codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm, anotherButtonComponent, 33, TEST_PARAMETER_LABEL, [] +codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm, anotherButtonComponent, 33, TEST_PARAMETER_LABEL_REGEXP, [] +codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm, buttonComponent, 28, TEST_PARAMETER_LABEL, [] +codetoanalyze/objcpp/linters-for-test-only/TestParamterLabelsChecks.mm, buttonComponent, 28, TEST_PARAMETER_LABEL_REGEXP, [] diff --git a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al index 382cde0f7..d3f291b3c 100644 --- a/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al +++ b/infer/tests/codetoanalyze/objcpp/linters-for-test-only/linters_example.al @@ -7,3 +7,31 @@ DEFINE-CHECKER TEST_REFERENCE = { HOLDS-IN-NODE ObjCMethodDecl; SET message = "Found reference in parameter of method new"; }; + +DEFINE-CHECKER TEST_PARAMETER_LABEL = { + LET method_has_parameter_type = + WHEN + HOLDS-NEXT WITH-TRANSITION ParameterName "number" + (has_type("int")) + HOLDS-IN-NODE ObjCMessageExpr; + SET report_when = + WHEN + method_has_parameter_type + AND call_method(REGEXP("^new.*:$")) + HOLDS-IN-NODE ObjCMessageExpr; + SET message = "Found method with parameter labeled with `number` and with type `int`"; +}; + +DEFINE-CHECKER TEST_PARAMETER_LABEL_REGEXP = { + LET method_has_parameter_type = + WHEN + HOLDS-NEXT WITH-TRANSITION ParameterName REGEXP("num.*") + (has_type("int")) + HOLDS-IN-NODE ObjCMessageExpr; + SET report_when = + WHEN + method_has_parameter_type + AND call_method(REGEXP("^new.*:$")) + HOLDS-IN-NODE ObjCMessageExpr; + SET message = "Found method with parameter labeled with `number` and with type `int`"; +};