Making AL distinguish class methods from instance methods

Reviewed By: dulmarod

Differential Revision: D4826401

fbshipit-source-id: 97c2570
master
Dino Distefano 8 years ago committed by Facebook Github Bot
parent 6b9a37f5cd
commit 7cef8ae3b5

@ -40,14 +40,14 @@ DEFINE-CHECKER ASSIGN_POINTER_WARNING = {
// Fires whenever a NSNumber is dangerously coerced to a boolean in a comparison
DEFINE-CHECKER BAD_POINTER_COMPARISON = {
LET is_binop = is_node(BinaryOperator);
LET is_binop_eq = is_binop_with_kind(EQ);
LET is_binop_ne = is_binop_with_kind(NE);
LET is_binop = is_node("BinaryOperator");
LET is_binop_eq = is_binop_with_kind("EQ");
LET is_binop_ne = is_binop_with_kind("NE");
LET is_binop_neq = is_binop_eq OR is_binop_ne;
LET is_unop_lnot = is_unop_with_kind(LNot);
LET is_implicit_cast_expr = is_node(ImplicitCastExpr);
LET is_expr_with_cleanups = is_node(ExprWithCleanups);
LET is_nsnumber = isa(NSNumber);
LET is_unop_lnot = is_unop_with_kind("LNot");
LET is_implicit_cast_expr = is_node("ImplicitCastExpr");
LET is_expr_with_cleanups = is_node("ExprWithCleanups");
LET is_nsnumber = isa("NSNumber");
LET eu =(
(NOT is_binop_neq)
@ -81,10 +81,10 @@ DEFINE-CHECKER BAD_POINTER_COMPARISON = {
DEFINE-CHECKER REGISTERED_OBSERVER_BEING_DEALLOCATED = {
LET exists_method_calling_addObserver =
call_method_strict(addObserver:selector:name:object:) HOLDS-EVENTUALLY;
call_method_strict("addObserver:selector:name:object:") HOLDS-EVENTUALLY;
LET exists_method_calling_addObserverForName =
call_method_strict(addObserverForName:object:queue:usingBlock:) HOLDS-EVENTUALLY;
call_method_strict("addObserverForName:object:queue:usingBlock:") HOLDS-EVENTUALLY;
LET add_observer =
exists_method_calling_addObserver OR exists_method_calling_addObserverForName;
@ -95,10 +95,10 @@ DEFINE-CHECKER REGISTERED_OBSERVER_BEING_DEALLOCATED = {
HOLDS-EVENTUALLY;
LET exists_method_calling_removeObserver =
call_method_strict(removeObserver:) HOLDS-EVENTUALLY;
call_method_strict("removeObserver:") HOLDS-EVENTUALLY;
LET exists_method_calling_removeObserverName =
call_method_strict(removeObserver:name:object:) HOLDS-EVENTUALLY;
call_method_strict("removeObserver:name:object:") HOLDS-EVENTUALLY;
LET remove_observer =
exists_method_calling_removeObserver OR exists_method_calling_removeObserverName;
@ -137,9 +137,11 @@ DEFINE-CHECKER REGISTERED_OBSERVER_BEING_DEALLOCATED = {
DEFINE-CHECKER STRONG_DELEGATE_WARNING = {
LET name_contains_delegate = property_name_contains_word(delegate);
LET name_does_not_contain_delegates = NOT property_name_contains_word(delegates);
LET name_does_not_contains_queue = NOT property_name_contains_word(queue);
LET name_contains_delegate = property_name_contains_word(REGEXP("[dD]elegate"));
LET name_does_not_contain_delegates =
NOT property_name_contains_word(REGEXP("[dD]elegates"));
LET name_does_not_contains_queue =
NOT property_name_contains_word(REGEXP("[qQ]ueue"));
SET report_when =
WHEN
@ -157,11 +159,11 @@ DEFINE-CHECKER GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL = {
is_objc_extension() AND is_global_var() AND (NOT is_const_var());
LET makes_an_expensive_call =
(is_node(CallExpr) AND NOT call_function_named(CGPointMake))
OR is_node(CXXTemporaryObjectExpr)
OR is_node(CXXMemberCallExpr)
OR is_node(CXXOperatorCallExpr)
OR is_node(ObjCMessageExpr);
(is_node("CallExpr") AND NOT call_function_named("CGPointMake"))
OR is_node("CXXTemporaryObjectExpr")
OR is_node("CXXMemberCallExpr")
OR is_node("CXXOperatorCallExpr")
OR is_node("ObjCMessageExpr");
LET is_initialized_with_expensive_call =
IN-NODE VarDecl WITH-TRANSITION InitExpr
@ -183,7 +185,7 @@ DEFINE-CHECKER GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL = {
DEFINE-CHECKER CXX_REFERENCE_CAPTURED_IN_OBJC_BLOCK = {
SET report_when =
WHEN
((is_node(BlockDecl) AND captures_cxx_references())
((is_node("BlockDecl") AND captures_cxx_references())
HOLDS-NEXT)
HOLDS-IN-NODE BlockExpr;

@ -20,6 +20,7 @@ type formula_id = Formula_id of string[@@deriving compare]
type alexp =
| Const of string
| Regexp of string
| Var of string
| FId of formula_id
[@@deriving compare]
@ -35,6 +36,7 @@ let formula_id_to_string fid =
let alexp_to_string e =
match e with
| Const s
| Regexp s
| Var s
| FId (Formula_id s) -> s

@ -19,6 +19,7 @@ type formula_id = Formula_id of string
type alexp =
| Const of string
| Regexp of string
| Var of string
| FId of formula_id

@ -11,8 +11,6 @@ open! IStd
open Lexing
open Ctl_lexer
exception ALParsingException of string
let parse_al_file fname channel : CTL.al_file option =
let pos_str lexbuf =
let pos = lexbuf.lex_curr_p in
@ -20,9 +18,12 @@ let parse_al_file fname channel : CTL.al_file option =
(string_of_int (pos.pos_cnum - pos.pos_bol + 1)) in
let parse_with_error lexbuf =
try Some (Ctl_parser.al_file token lexbuf) with
| Ctl_parser_types.ALParsingException s ->
raise (Ctl_parser_types.ALParsingException
(s ^ " at " ^ (pos_str lexbuf)))
| SyntaxError _
| Ctl_parser.Error ->
raise (ALParsingException ( "SYNTAX ERROR at " ^ (pos_str lexbuf))) in
raise (Ctl_parser_types.ALParsingException ( "SYNTAX ERROR at " ^ (pos_str lexbuf))) in
let lexbuf = Lexing.from_channel channel in
lexbuf.lex_curr_p <- { lexbuf.lex_curr_p with pos_fname = fname };
parse_with_error lexbuf

@ -145,10 +145,12 @@ let check_def_well_expanded vars expanded_formula =
let open CTL in
let check_const c =
match c with
| ALVar.Regexp c
| ALVar.Const c when List.mem vars (ALVar.Var c) ->
failwith ("[ERROR]: Const '" ^ c ^
failwith ("[ERROR]: Const/Regexp '" ^ c ^
"' is used as formal parameter of some LET definition.")
| ALVar.Const _ -> ()
| ALVar.Const _
| ALVar.Regexp _ -> ()
| ALVar.Var v
| ALVar.FId (Formula_id v) ->
failwith ("[ERROR]: Variable '" ^ v ^

@ -56,14 +56,13 @@ let captured_variables_cxx_ref an =
List.fold ~f:capture_var_is_cxx_ref ~init:[] bdi.bdi_captured_variables
| _ -> []
type t = ALVar.formula_id * ALVar.alexp list(* (name, [param1,...,paramK]) *)
(* true if and only if string contained occurs in container *)
let str_contains container contained =
let rexp = Str.regexp_string_case_fold contained in
(* true if and only if a substring of container matches the regular
expression defined by contained
*)
let str_match_regex container contained =
let rexp = Str.regexp contained in
try
Str.search_forward rexp container 0 >= 0
with Not_found -> false
@ -88,7 +87,7 @@ let is_objc_interface_named_strict an expected_name =
(* is an objc interface with name expected_name *)
let is_objc_interface_named an expected_name =
_is_objc_interface_named (str_contains) an expected_name
_is_objc_interface_named (str_match_regex) an expected_name
let _is_object_of_class_named comp receiver cname =
let open Clang_ast_t in
@ -121,9 +120,35 @@ let call_method_strict an m =
(* an |= call_method(m) where we check is the name contains m *)
let call_method an m =
_call_method (str_contains) an m
_call_method (str_match_regex) an m
let is_receiver_kind_class comp omei cname =
let open Clang_ast_t in
match omei.omei_receiver_kind with
| `Class ptr ->
(match CAst_utils.get_desugared_type ptr with
| Some ObjCInterfaceType (_, ptr) ->
(match CAst_utils.get_decl ptr with
| Some ObjCInterfaceDecl (_, ndi, _, _, _) ->
comp ndi.ni_name cname
| _ -> false)
| _ -> false)
| _ -> false
let _call_class_method comp an cname mname =
match an with
| Ctl_parser_types.Stmt (Clang_ast_t.ObjCMessageExpr (_, _, _, omei)) ->
is_receiver_kind_class comp omei cname &&
comp omei.omei_selector mname
| _ -> false
let call_class_method_strict an cname mname =
_call_class_method (String.equal) an cname mname
let call_class_method an cname mname =
_call_class_method (str_match_regex) an cname mname
let _call_instance_method comp an cname mname =
match an with
| Ctl_parser_types.Stmt (Clang_ast_t.ObjCMessageExpr (_, receiver :: _, _, omei)) ->
is_object_of_class_named receiver cname &&
@ -133,20 +158,20 @@ let _call_class_method comp an cname mname =
(* an is a node calling method mname of class cname.
The equality is strict.
*)
let call_class_method_strict an cname mname =
_call_class_method (String.equal) an cname mname
let call_instance_method_strict an cname mname =
_call_instance_method (String.equal) an cname mname
(* an is a node calling method whose name contains mname of a
class whose name contains cname.
*)
let call_class_method an cname mname =
_call_class_method (str_contains) an cname mname
let call_instance_method an cname mname =
_call_instance_method (str_match_regex) an cname mname
let property_name_contains_word word an =
match an with
| Ctl_parser_types.Decl decl ->
(match Clang_ast_proj.get_named_decl_tuple decl with
| Some (_, n) -> str_contains n.Clang_ast_t.ni_name word
| Some (_, n) -> str_match_regex n.Clang_ast_t.ni_name word
| _ -> false)
| _ -> false
@ -309,7 +334,7 @@ let _declaration_has_name comp an name =
(* an is a declaration whose name contains a regexp defined by re *)
let declaration_has_name an re =
_declaration_has_name (str_contains) an re
_declaration_has_name (str_match_regex) an re
(* an is a declaration called precisely name *)
let declaration_has_name_strict an name =
@ -323,7 +348,7 @@ let _is_class comp an re =
| _ -> false
let is_class an re =
_is_class (str_contains) an re
_is_class (str_match_regex) an re
let is_class_strict an name =
_is_class (String.equal) an name

@ -21,6 +21,10 @@ val call_class_method : Ctl_parser_types.ast_node -> string -> string -> bool
val call_class_method_strict : Ctl_parser_types.ast_node -> string -> string -> bool
val call_instance_method : Ctl_parser_types.ast_node -> string -> string -> bool
val call_instance_method_strict : Ctl_parser_types.ast_node -> string -> string -> bool
val is_objc_interface_named_strict : Ctl_parser_types.ast_node -> string -> bool
val is_objc_interface_named : Ctl_parser_types.ast_node -> string -> bool

@ -576,9 +576,14 @@ let rec eval_Atomic _pred_name _args an lcxt =
match pred_name, args, an with
| "call_method", [m], an -> CPredicates.call_method an m
| "call_method_strict", [m], an -> CPredicates.call_method_strict an m
| "call_class_method", [c; m], an -> CPredicates.call_class_method an c m
| "call_class_method", [c; m], an ->
CPredicates.call_class_method an c m
| "call_class_method_strict", [c; m], an ->
CPredicates.call_class_method_strict an c m
| "call_instance_method", [c; m], an ->
CPredicates.call_instance_method an c m
| "call_instance_method_strict", [c; m], an ->
CPredicates.call_instance_method_strict an c m
| "is_objc_interface_named", [name], an ->
CPredicates.is_objc_interface_named an name
| "is_objc_interface_named_strict", [name], an ->
@ -617,7 +622,8 @@ let rec eval_Atomic _pred_name _args an lcxt =
| "within_responds_to_selector_block", [], an ->
CPredicates.within_responds_to_selector_block lcxt an
| _ -> failwith
("ERROR: Undefined Predicate or wrong set of arguments: " ^ pred_name)
("\nERROR: Undefined Predicate or wrong set of arguments: '"
^ pred_name ^ "'\n")
(* an, lcxt |= EF phi <=>
an, lcxt |= phi or exists an' in Successors(st): an', lcxt |= EF phi

@ -65,6 +65,7 @@ rule token = parse
| "OR" { OR }
| "NOT" { NOT }
| "IMPLIES" { IMPLIES }
| "REGEXP" { REGEXP }
| id { IDENTIFIER (Lexing.lexeme lexbuf) }
| file_id { FILE_IDENTIFIER (Lexing.lexeme lexbuf) }
| '"' { read_string (Buffer.create 80) lexbuf }

@ -8,14 +8,22 @@
*/
%{
let formal_params : (ALVar.t list) ref = ref []
let is_not_infer_reserved_id id =
if Str.string_match (Str.regexp_string Ctl_parser_types.infer_prefix) id 0 then
failwith
raise (Ctl_parser_types.ALParsingException
("ERROR: " ^ id ^ " contains __infer_ctl_ that is a reserved keyword "
^ "which cannot be used in identifiers.")
else id
^ "which cannot be used in identifiers:"))
else ()
let is_defined_identifier id =
if (List.mem (ALVar.Var id) !formal_params) then
Logging.out "\tParsed exp '%s' as variable" id
else
raise (Ctl_parser_types.ALParsingException
("ERROR: Variable '" ^ id ^ "' is undefined"))
%}
@ -53,6 +61,7 @@
%token OR
%token NOT
%token IMPLIES
%token REGEXP
%token <string> IDENTIFIER
%token <string> FILE_IDENTIFIER
%token <string> STRING
@ -73,6 +82,11 @@ var_list:
| identifier COMMA var_list { ALVar.Var($1) :: $3 }
;
node_list:
| identifier { [ALVar.Const $1] }
| identifier COMMA node_list { ALVar.Const($1) :: $3 }
;
formal_params:
| var_list { formal_params := $1; $1}
@ -85,12 +99,13 @@ al_file:
import_files:
| { [] }
| HASHIMPORT LESS_THAN file_identifier GREATER_THAN import_files
{ $3 :: $5 }
{ Logging.out "Parsed import clauses...\n\n"; $3 :: $5 }
;
global_macros:
| { [] }
| GLOBAL_MACROS LEFT_BRACE let_clause_list RIGHT_BRACE SEMICOLON { $3 }
| GLOBAL_MACROS LEFT_BRACE let_clause_list RIGHT_BRACE SEMICOLON
{ Logging.out "Parsed global macro definitions...\n\n"; $3 }
;
checkers_list:
@ -101,7 +116,7 @@ checkers_list:
checker:
DEFINE_CHECKER identifier ASSIGNMENT LEFT_BRACE clause_list RIGHT_BRACE
{
Logging.out "\nParsed checker definition";
Logging.out "\nParsed checker definition\n";
let c = { CTL.name = $2; CTL.definitions = $5 } in
CTL.print_checker c;
c
@ -168,20 +183,8 @@ atomic_formula:
actual_params:
| {[]}
| identifier { if (List.mem (ALVar.Var $1) !formal_params) then
(Logging.out "\tParsed exp '%s' as variable \n" $1;
[ALVar.Var $1])
else
(Logging.out "\tParsed exp '%s' as constant \n" $1;
[ALVar.Const $1])
}
| identifier COMMA actual_params {
(if (List.mem (ALVar.Var $1) !formal_params) then
(Logging.out "\tParsed exp '%s' as variable \n" $1;
ALVar.Var $1)
else (Logging.out "\tParsed exp '%s' as constant \n" $1;
ALVar.Const $1)
) :: $3 }
| alexp { [$1] }
| alexp COMMA actual_params { $1 :: $3 }
;
transition_label:
@ -212,13 +215,13 @@ formula:
| formula AX { Logging.out "\tParsed AX\n"; CTL.AX ($1) }
| formula EG { Logging.out "\tParsed EG\n"; CTL.EG (None, $1) }
| formula AG { Logging.out "\tParsed AG\n"; CTL.AG ($1) }
| formula EH actual_params { Logging.out "\tParsed EH\n"; CTL.EH ($3, $1) }
| formula EH node_list { Logging.out "\tParsed EH\n"; CTL.EH ($3, $1) }
| formula EF { Logging.out "\tParsed EF\n"; CTL.EF (None, $1) }
| WHEN formula HOLDS_IN_NODE actual_params
| WHEN formula HOLDS_IN_NODE node_list
{ Logging.out "\tParsed InNode\n"; CTL.InNode ($4, $2)}
| ET actual_params WITH_TRANSITION transition_label formula_EF
| ET node_list WITH_TRANSITION transition_label formula_EF
{ Logging.out "\tParsed ET\n"; CTL.ET ($2, $4, $5)}
| ETX actual_params WITH_TRANSITION transition_label formula_EF
| ETX node_list WITH_TRANSITION transition_label formula_EF
{ Logging.out "\tParsed ETX\n"; CTL.ETX ($2, $4, $5)}
| EX WITH_TRANSITION transition_label formula_with_paren
{ Logging.out "\tParsed EX\n"; CTL.EX ($3, $4)}
@ -228,11 +231,24 @@ formula:
| NOT formula { Logging.out "\tParsed NOT\n"; CTL.Not ($2) }
;
alexp:
| STRING { is_not_infer_reserved_id $1;
Logging.out "\tParsed string constant '%s' \n" $1;
ALVar.Const $1 }
| REGEXP LEFT_PAREN STRING RIGHT_PAREN
{ Logging.out "\tParsed regular expression '%s' \n" $3;
ALVar.Regexp $3 }
| identifier { is_defined_identifier $1; ALVar.Var $1 }
;
identifier:
| IDENTIFIER { is_not_infer_reserved_id $1 }
| IDENTIFIER { is_not_infer_reserved_id $1;
Logging.out "\tParsed identifier '%s' \n" $1; $1 }
;
file_identifier:
| FILE_IDENTIFIER { is_not_infer_reserved_id $1 }
| FILE_IDENTIFIER { is_not_infer_reserved_id $1;
Logging.out "\tParsed file identifier '%s' \n" $1; $1 }
;
%%

@ -24,3 +24,5 @@ let message_const = "message"
let suggestion_const = "suggestion"
let severity_const = "severity"
let mode_const = "mode"
exception ALParsingException of string

@ -1,3 +1,4 @@
codetoanalyze/objc/linters-for-test-only/subclassing.m, B_bar, 35, TEST_ALL_METHODS, []
codetoanalyze/objc/linters-for-test-only/subclassing.m, B_bar, 36, MACRO_TEST1, []
codetoanalyze/objc/linters-for-test-only/subclassing.m, B_bar, 36, MACRO_TEST2, []
codetoanalyze/objc/linters-for-test-only/subclassing.m, B_bar, 36, MACRO_TEST3, []

@ -12,7 +12,7 @@ GLOBAL-MACROS {
DEFINE-CHECKER SUBCLASSING_TEST_EXAMPLE = {
SET report_when =
is_class(A) HOLDS-IN-SOME-SUPERCLASS-OF ObjCInterfaceDecl;
is_class("A") HOLDS-IN-SOME-SUPERCLASS-OF ObjCInterfaceDecl;
SET message = "This is subclassing A. Class A should not be subclassed.";
@ -22,7 +22,7 @@ DEFINE-CHECKER MACRO_TEST1 = {
LET call_two_methods(x,y) = call_method(x) OR call_method(y);
SET report_when = call_two_methods(foo, bar);
SET report_when = call_two_methods("foo", "bar");
SET message = "Error message here";
@ -31,9 +31,9 @@ DEFINE-CHECKER MACRO_TEST1 = {
// Test reverse parameter of macro
DEFINE-CHECKER MACRO_TEST2 = {
LET my_macro_to_call_method_of_class(x,y) = call_class_method(y,x);
LET my_macro_to_call_method_of_class(x,y) = call_instance_method(y,x);
SET report_when = my_macro_to_call_method_of_class(foo, A);
SET report_when = my_macro_to_call_method_of_class("foo", "A");
SET message = "Error message here";
@ -42,11 +42,11 @@ DEFINE-CHECKER MACRO_TEST2 = {
// Test macro call macro
DEFINE-CHECKER MACRO_TEST3 = {
LET my_macro_to_call_method_of_class(x,y) = call_class_method(y,x);
LET my_macro_to_call_method_of_class(x,y) = call_instance_method(y,x);
LET call_my_macro(t,v) = my_macro_to_call_method_of_class(t,v);
SET report_when = call_my_macro(foo, A);
SET report_when = call_my_macro("foo", "A");
SET message = "Error message here";
@ -57,7 +57,7 @@ DEFINE-CHECKER LOCAL_MACRO_SUBCLASS = {
LET is_subclass_of(x) =
is_class(x) HOLDS-IN-SOME-SUPERCLASS-OF ObjCInterfaceDecl;
SET report_when = is_subclass_of(A);
SET report_when = is_subclass_of("A");
SET message = "This is subclassing A. Class A should not be subclassed.";
@ -65,7 +65,7 @@ DEFINE-CHECKER LOCAL_MACRO_SUBCLASS = {
DEFINE-CHECKER GLOBAL_MACRO_SUBCLASS = {
SET report_when = global_is_subclass_of(A);
SET report_when = global_is_subclass_of("A");
SET message = "This is subclassing A. Class A should not be subclassed.";
@ -73,8 +73,16 @@ DEFINE-CHECKER GLOBAL_MACRO_SUBCLASS = {
DEFINE-CHECKER IMPORTED_MACRO_SUBCLASS = {
SET report_when = imported_is_subclass_of(A);
SET report_when = imported_is_subclass_of("A");
SET message = "This is subclassing A. Class A should not be subclassed.";
};
DEFINE-CHECKER TEST_ALL_METHODS = {
SET report_when = call_class_method(REGEXP(".*"), REGEXP(".*"));
SET message = "Method call...";
};

Loading…
Cancel
Save