[linters] Global paths

Reviewed By: ddino

Differential Revision: D5228481

fbshipit-source-id: ab8b542
master
Dulma Churchill 8 years ago committed by Facebook Github Bot
parent f8eb9c2466
commit 47f1c6ffac

@ -107,3 +107,8 @@ module FormulaIdMap = Caml.Map.Make (
struct struct
type t = formula_id[@@deriving compare] type t = formula_id[@@deriving compare]
end) end)
module VarMap = Caml.Map.Make (
struct
type t = string[@@deriving compare]
end)

@ -51,3 +51,5 @@ val str_match_regex : string -> string -> bool
val compare_str_with_alexp : string -> alexp -> bool val compare_str_with_alexp : string -> alexp -> bool
module FormulaIdMap : Caml.Map.S with type key = formula_id module FormulaIdMap : Caml.Map.S with type key = formula_id
module VarMap : Caml.Map.S with type key = string

@ -34,40 +34,55 @@ let parse_al_file fname channel : CTL.al_file option =
let already_imported_files = ref [] let already_imported_files = ref []
let rec parse_import_file import_file channel : CTL.clause list = let rec parse_import_file import_file channel =
if List.mem !already_imported_files import_file then if List.mem !already_imported_files import_file then
failwith ("Cyclic imports: file '" ^ import_file ^ "' was already imported.") failwith ("Cyclic imports: file '" ^ import_file ^ "' was already imported.")
else ( else (
match parse_al_file import_file channel with match parse_al_file import_file channel with
| Some {import_files = imports; global_macros = curr_file_macros; checkers = _} -> | Some {
import_files = imports;
global_macros = curr_file_macros;
global_paths = curr_file_paths;
checkers = _
} ->
already_imported_files := import_file :: !already_imported_files; already_imported_files := import_file :: !already_imported_files;
collect_all_macros imports curr_file_macros collect_all_macros_and_paths imports curr_file_macros curr_file_paths
| None -> L.(debug Linters Medium) "No macros found.@\n";[]) | None -> L.(debug Linters Medium) "No macros or paths found.@\n";[], [])
and collect_all_macros imports curr_file_macros = and collect_all_macros_and_paths imports curr_file_macros curr_file_paths =
L.(debug Linters Medium) "#### Start parsing import macros #####@\n"; L.(debug Linters Medium) "#### Start parsing import macros #####@\n";
let import_macros = parse_imports imports in let import_macros, import_paths = parse_imports imports in
L.(debug Linters Medium) "#### Add global macros to import macros #####@\n"; L.(debug Linters Medium) "#### Add global macros to import macros #####@\n";
List.append import_macros curr_file_macros let macros = List.append import_macros curr_file_macros in
let paths = List.append import_paths curr_file_paths in
macros, paths
(* Parse import files with macro definitions, and it returns a list of LET clauses *) (* Parse import files with macro definitions, and it returns a list of LET clauses *)
and parse_imports imports_files : CTL.clause list = and parse_imports imports_files =
let parse_one_import_file fimport macros = let parse_one_import_file fimport (macros, paths) =
L.(debug Linters Medium) " Loading import macros from file %s@\n" fimport; L.(debug Linters Medium) " Loading import macros from file %s@\n" fimport;
let in_channel = open_in fimport in let in_channel = open_in fimport in
let parsed_macros = parse_import_file fimport in_channel in let parsed_macros, parsed_paths = parse_import_file fimport in_channel in
In_channel.close in_channel; In_channel.close in_channel;
List.append parsed_macros macros in let macros = List.append parsed_macros macros in
List.fold_right ~f:parse_one_import_file ~init:[] imports_files let paths = List.append parsed_paths paths in
macros, paths in
List.fold_right ~f:parse_one_import_file ~init:([], []) imports_files
let parse_ctl_file linters_def_file channel : CFrontend_errors.linter list = let parse_ctl_file linters_def_file channel : CFrontend_errors.linter list =
match parse_al_file linters_def_file channel with match parse_al_file linters_def_file channel with
| Some {import_files = imports; global_macros = curr_file_macros; checkers = parsed_checkers} -> | Some {
import_files = imports;
global_macros = curr_file_macros;
global_paths = curr_file_paths;
checkers = parsed_checkers
} ->
already_imported_files := [linters_def_file]; already_imported_files := [linters_def_file];
let macros = collect_all_macros imports curr_file_macros in let macros, paths = collect_all_macros_and_paths imports curr_file_macros curr_file_paths in
let macros_map = CFrontend_errors.build_macros_map macros in let macros_map = CFrontend_errors.build_macros_map macros in
let paths_map = CFrontend_errors.build_paths_map paths in
L.(debug Linters Medium) "#### Start Expanding checkers #####@\n"; L.(debug Linters Medium) "#### Start Expanding checkers #####@\n";
let exp_checkers = CFrontend_errors.expand_checkers macros_map parsed_checkers in let exp_checkers = CFrontend_errors.expand_checkers macros_map paths_map parsed_checkers in
L.(debug Linters Medium) "#### Checkers Expanded #####@\n"; L.(debug Linters Medium) "#### Checkers Expanded #####@\n";
if Config.debug_mode then List.iter ~f:CTL.print_checker exp_checkers; if Config.debug_mode then List.iter ~f:CTL.print_checker exp_checkers;
CFrontend_errors.create_parsed_linters linters_def_file exp_checkers CFrontend_errors.create_parsed_linters linters_def_file exp_checkers

@ -64,6 +64,9 @@ let pp_linters fmt linters =
formula was already expanded and, if yes we have a cyclic definifion *) formula was already expanded and, if yes we have a cyclic definifion *)
type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t
(* Map a path name to a list of paths. *)
type paths_map = (ALVar.t list) ALVar.VarMap.t
let single_to_multi checker = let single_to_multi checker =
fun ctx an -> fun ctx an ->
let issue_desc_opt = checker ctx an in let issue_desc_opt = checker ctx an in
@ -285,6 +288,18 @@ let expand_formula phi _map _error_msg =
| ETX (tl, sw, f1) -> ETX (tl, sw, expand f1 map error_msg) in | ETX (tl, sw, f1) -> ETX (tl, sw, expand f1 map error_msg) in
expand phi _map _error_msg expand phi _map _error_msg
let rec expand_path paths path_map =
match paths with
| [] -> []
| ALVar.Var path_var :: rest ->
(try
let paths = ALVar.VarMap.find path_var path_map in
List.append paths (expand_path rest path_map)
with Not_found -> failwithf "Path variable %s not found. " path_var)
| path :: rest ->
path :: (expand_path rest path_map)
let _build_macros_map macros init_map = let _build_macros_map macros init_map =
let macros_map = List.fold ~f:(fun map' data -> match data with let macros_map = List.fold ~f:(fun map' data -> match data with
| CTL.CLet (key, params, formula) -> | CTL.CLet (key, params, formula) ->
@ -299,8 +314,18 @@ let build_macros_map macros =
let init_map : macros_map = ALVar.FormulaIdMap.empty in let init_map : macros_map = ALVar.FormulaIdMap.empty in
_build_macros_map macros init_map _build_macros_map macros init_map
let build_paths_map paths =
let build_paths_map_aux paths init_map =
let paths_map = List.fold ~f:(fun map' data -> match data with
| (path_name, paths) ->
if ALVar.VarMap.mem path_name map' then
failwith ("Path '" ^ path_name ^ "' has more than one definition.")
else ALVar.VarMap.add path_name paths map') ~init:init_map paths in
paths_map in
build_paths_map_aux paths ALVar.VarMap.empty
(* expands use of let defined formula id in checkers with their definition *) (* expands use of let defined formula id in checkers with their definition *)
let expand_checkers macro_map checkers = let expand_checkers macro_map path_map checkers =
let open CTL in let open CTL in
let expand_one_checker c = let expand_one_checker c =
L.(debug Linters Medium) " +Start expanding %s@\n" c.name; L.(debug Linters Medium) " +Start expanding %s@\n" c.name;
@ -310,6 +335,9 @@ let expand_checkers macro_map checkers =
| CSet (report_when_const, phi) -> | CSet (report_when_const, phi) ->
L.(debug Linters Medium) " -Expanding report_when@\n"; L.(debug Linters Medium) " -Expanding report_when@\n";
CSet (report_when_const, expand_formula phi map "") :: defs CSet (report_when_const, expand_formula phi map "") :: defs
| CPath (black_or_white_list, paths) ->
L.(debug Linters Medium) " -Expanding path@\n";
CPath (black_or_white_list, expand_path paths path_map) :: defs
| cl -> cl :: defs) ~init:[] c.definitions in | cl -> cl :: defs) ~init:[] c.definitions in
{ c with definitions = exp_defs} in { c with definitions = exp_defs} in
List.map ~f:expand_one_checker checkers List.map ~f:expand_one_checker checkers

@ -27,6 +27,9 @@ val pp_linters : Format.formatter -> linter list -> unit
formula was already expanded and, if yes we have a cyclic definifion *) formula was already expanded and, if yes we have a cyclic definifion *)
type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t
(* Map a path name to a list of paths. *)
type paths_map = (ALVar.t list) ALVar.VarMap.t
(* List of checkers that will be filled after parsing them from a file *) (* List of checkers that will be filled after parsing them from a file *)
val parsed_linters : linter list ref val parsed_linters : linter list ref
@ -37,7 +40,9 @@ val invoke_set_of_checkers_on_node : CLintersContext.context -> Ctl_parser_types
val build_macros_map : CTL.clause list -> macros_map val build_macros_map : CTL.clause list -> macros_map
val expand_checkers : macros_map -> CTL.ctl_checker list -> CTL.ctl_checker list val build_paths_map : (string * ALVar.alexp list) list -> paths_map
val expand_checkers : macros_map -> paths_map -> CTL.ctl_checker list -> CTL.ctl_checker list
val create_parsed_linters : string -> CTL.ctl_checker list -> linter list val create_parsed_linters : string -> CTL.ctl_checker list -> linter list

@ -92,6 +92,7 @@ type ctl_checker = {
type al_file = { type al_file = {
import_files : string list; import_files : string list;
global_macros : clause list; global_macros : clause list;
global_paths : (string * ALVar.alexp list) list;
checkers : ctl_checker list checkers : ctl_checker list
} }

@ -90,6 +90,7 @@ type ctl_checker = {
type al_file = { type al_file = {
import_files : string list; import_files : string list;
global_macros : clause list; global_macros : clause list;
global_paths : (string * ALVar.alexp list) list;
checkers : ctl_checker list checkers : ctl_checker list
} }

@ -49,6 +49,7 @@ rule token = parse
| "WITH-TRANSITION" {WITH_TRANSITION} | "WITH-TRANSITION" {WITH_TRANSITION}
| "DEFINE-CHECKER" { DEFINE_CHECKER } | "DEFINE-CHECKER" { DEFINE_CHECKER }
| "GLOBAL-MACROS" { GLOBAL_MACROS } | "GLOBAL-MACROS" { GLOBAL_MACROS }
| "GLOBAL-PATHS" { GLOBAL_PATHS }
| "#IMPORT" { HASHIMPORT } | "#IMPORT" { HASHIMPORT }
| "SET" { SET } | "SET" { SET }
| "LET" { LET } | "LET" { LET }

@ -21,12 +21,13 @@
^ "which cannot be used in identifiers:")) ^ "which cannot be used in identifiers:"))
else () else ()
let is_defined_identifier id = let is_defined_identifier id =
if (List.mem ~equal:ALVar.equal !formal_params (ALVar.Var id)) then if (List.mem ~equal:ALVar.equal !formal_params (ALVar.Var id)) then
L.(debug Linters Verbose) "\tParsed exp '%s' as variable" id L.(debug Linters Verbose) "\tParsed exp '%s' as variable" id
else else
raise (Ctl_parser_types.ALParsingException raise (Ctl_parser_types.ALParsingException
("ERROR: Variable '" ^ id ^ "' is undefined")) ("ERROR: Variable '" ^ id ^ "' is undefined"))
%} %}
%token EU %token EU
@ -40,6 +41,7 @@
%token EH %token EH
%token DEFINE_CHECKER %token DEFINE_CHECKER
%token GLOBAL_MACROS %token GLOBAL_MACROS
%token GLOBAL_PATHS
%token HASHIMPORT %token HASHIMPORT
%token LESS_THAN %token LESS_THAN
%token GREATER_THAN %token GREATER_THAN
@ -95,8 +97,8 @@ formal_params:
| var_list { formal_params := $1; $1} | var_list { formal_params := $1; $1}
al_file: al_file:
| import_files global_macros checkers_list { | import_files global_macros global_paths checkers_list {
{ CTL.import_files = $1; CTL.global_macros = $2; CTL.checkers = $3 } { CTL.import_files = $1; CTL.global_macros = $2; CTL.global_paths = $3; CTL.checkers = $4 }
} }
; ;
@ -112,6 +114,21 @@ global_macros:
{ L.(debug Linters Verbose) "Parsed global macro definitions...@\n@\n"; $3 } { L.(debug Linters Verbose) "Parsed global macro definitions...@\n@\n"; $3 }
; ;
global_path_declaration:
| LET identifier ASSIGNMENT LEFT_BRACE path_list RIGHT_BRACE SEMICOLON { ($2, $5) }
;
global_paths_list:
| global_path_declaration { [$1] }
| global_path_declaration SEMICOLON global_paths_list { $1 :: $3 }
;
global_paths:
| { [] }
| GLOBAL_PATHS LEFT_BRACE global_paths_list RIGHT_BRACE SEMICOLON
{ L.(debug Linters Verbose) "Parsed global path definitions...@\n"; $3 }
;
checkers_list: checkers_list:
| EOF { [] } | EOF { [] }
| checker SEMICOLON checkers_list { $1::$3 } | checker SEMICOLON checkers_list { $1::$3 }
@ -128,8 +145,8 @@ checker:
; ;
path_list: path_list:
| alexp { [$1] } | alexp_path { [$1] }
| alexp COMMA path_list { $1 :: $3 } | alexp_path COMMA path_list { $1 :: $3 }
; ;
clause_list: clause_list:
@ -254,16 +271,29 @@ formula:
; ;
alexp: alexp_const: STRING
| STRING { is_not_infer_reserved_id $1; { is_not_infer_reserved_id $1;
L.(debug Linters Verbose) "\tParsed string constant '%s'@\n" $1; L.(debug Linters Verbose) "\tParsed string constant '%s'@\n" $1;
ALVar.Const $1 } ALVar.Const $1 }
| REGEXP LEFT_PAREN STRING RIGHT_PAREN
alexp_regex: REGEXP LEFT_PAREN STRING RIGHT_PAREN
{ L.(debug Linters Verbose) "\tParsed regular expression '%s'@\n" $3; { L.(debug Linters Verbose) "\tParsed regular expression '%s'@\n" $3;
ALVar.Regexp $3 } ALVar.Regexp $3 }
| identifier { is_defined_identifier $1; ALVar.Var $1 }
alexp_var: identifier { is_defined_identifier $1; ALVar.Var $1 }
alexp:
| alexp_const {$1}
| alexp_regex {$1}
| alexp_var { $1}
; ;
alexp_path:
| alexp_const {$1}
| alexp_regex {$1}
| identifier { ALVar.Var $1 }
;
identifier: identifier:
| IDENTIFIER { is_not_infer_reserved_id $1; | IDENTIFIER { is_not_infer_reserved_id $1;
L.(debug Linters Verbose) "\tParsed identifier '%s'@\n" $1; $1 } L.(debug Linters Verbose) "\tParsed identifier '%s'@\n" $1; $1 }

@ -5,3 +5,8 @@ GLOBAL-MACROS {
is_class(x) HOLDS-IN-SOME-SUPERCLASS-OF ObjCInterfaceDecl; is_class(x) HOLDS-IN-SOME-SUPERCLASS-OF ObjCInterfaceDecl;
}; };
GLOBAL-PATHS {
LET all_files = {REGEXP(".*") };
};

@ -30,6 +30,10 @@ GLOBAL-MACROS {
}; };
GLOBAL-PATHS {
LET filtered_files = {REGEXP("codetoanalyze/objc/linters-for-test-only/filter_by_path/.*") };
};
//Check that class A is not subclassed. //Check that class A is not subclassed.
DEFINE-CHECKER SUBCLASSING_TEST_EXAMPLE = { DEFINE-CHECKER SUBCLASSING_TEST_EXAMPLE = {
@ -242,7 +246,7 @@ DEFINE-CHECKER FILTER_BY_PATH_EXAMPLE = {
WHEN declaration_has_name("main") WHEN declaration_has_name("main")
HOLDS-IN-NODE FunctionDecl; HOLDS-IN-NODE FunctionDecl;
SET message = "Found main method"; SET message = "Found main method";
SET whitelist_path = { REGEXP("codetoanalyze/objc/linters-for-test-only/filter_by_path/.*"), "A.m" }; SET whitelist_path = { filtered_files, "A.m" };
}; };
DEFINE-CHECKER ALL_PATH_NO_FILTER_EXAMPLE = { DEFINE-CHECKER ALL_PATH_NO_FILTER_EXAMPLE = {
@ -273,6 +277,6 @@ DEFINE-CHECKER WHITE_BLACKLIST_PATH_EXAMPLE = {
WHEN declaration_has_name("main") WHEN declaration_has_name("main")
HOLDS-IN-NODE FunctionDecl; HOLDS-IN-NODE FunctionDecl;
SET message = "Found main method"; SET message = "Found main method";
SET whitelist_path = { REGEXP(".*") }; SET whitelist_path = { all_files };
SET blacklist_path = { REGEXP("codetoanalyze/objc/linters-for-test-only/filter_by_path/.*") }; SET blacklist_path = { filtered_files };
}; };

Loading…
Cancel
Save