You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
7.6 KiB
209 lines
7.6 KiB
(*
|
|
* Copyright (c) 2013 - 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.
|
|
*)
|
|
|
|
(** Module for code queries. *)
|
|
|
|
module L = Logging
|
|
module F = Format
|
|
open Utils
|
|
|
|
let verbose = false
|
|
let query = ref None
|
|
|
|
let parse_query s =
|
|
let buf = Lexing.from_string s in
|
|
try
|
|
match CodequeryParser.query CodequeryLexer.token buf with
|
|
| None ->
|
|
L.stdout "empty rule@.";
|
|
assert false
|
|
| Some query ->
|
|
if verbose then L.stdout "%a@." CodeQueryAst.pp_query query;
|
|
query
|
|
with
|
|
| Parsing.Parse_error ->
|
|
L.stdout "@.parsing stops on line %d: \n\n%s@." !CodequeryLexer.line_number buf.Lexing.lex_buffer;
|
|
assert false
|
|
|
|
let query_ast =
|
|
lazy (match !query with None -> parse_query "x(y)" | Some s -> parse_query s)
|
|
|
|
module Err = struct
|
|
(** Update the summary with stats from the checker. *)
|
|
let update_summary proc_name proc_desc =
|
|
let old_summ = Specs.get_summary_unsafe proc_name in
|
|
let nodes = list_map (fun n -> Cfg.Node.get_id n) (Cfg.Procdesc.get_nodes proc_desc) in
|
|
let specs =
|
|
let spec =
|
|
{ Specs.pre = Specs.Jprop.Prop (1, Prop.prop_emp);
|
|
Specs.posts = [];
|
|
Specs.visited = Specs.Visitedset.empty
|
|
} in
|
|
[(Specs.spec_normalize spec)] in
|
|
let new_summ = { old_summ with
|
|
Specs.loc = Cfg.Procdesc.get_loc proc_desc;
|
|
Specs.nodes = nodes;
|
|
Specs.payload = Specs.PrePosts specs } in
|
|
Specs.add_summary proc_name new_summ
|
|
|
|
let add_error_to_spec proc_name s node loc =
|
|
Checkers.ST.pname_add proc_name "codequery" "active"; (* force creation of .specs files *)
|
|
State.set_node node;
|
|
let exn = Exceptions.Codequery (Localise.verbatim_desc s) in
|
|
Reporting.log_error proc_name ~loc: (Some loc) exn
|
|
end
|
|
|
|
(** Matcher for rules. *)
|
|
module Match = struct
|
|
type value =
|
|
| Vfun of Procname.t
|
|
| Vval of Sil.exp
|
|
|
|
let pp_value fmt = function
|
|
| Vval e -> F.fprintf fmt "%a" (Sil.pp_exp pe_text) e
|
|
| Vfun pn -> F.fprintf fmt "%s" (Procname.to_string pn)
|
|
|
|
let value_equal v1 v2 = match v1, v2 with
|
|
| Vval e1, Vval e2 -> Sil.exp_equal e1 e2
|
|
| Vval _, _ -> false
|
|
| _, Vval _ -> false
|
|
| Vfun pn1, Vfun pn2 -> Procname.equal pn1 pn2
|
|
|
|
let init_env () = Hashtbl.create 1
|
|
|
|
let env_copy env = Hashtbl.copy env
|
|
|
|
let env_add env id value =
|
|
try
|
|
let value' = Hashtbl.find env id in
|
|
value_equal value value'
|
|
with Not_found ->
|
|
Hashtbl.add env id value;
|
|
true
|
|
let pp_env fmt env =
|
|
let pp_item id value =
|
|
F.fprintf fmt "%s=%a " id pp_value value in
|
|
Hashtbl.iter pp_item env
|
|
|
|
let exp_match env ae value = match ae, value with
|
|
| CodeQueryAst.Null, Vval e -> Sil.exp_equal e Sil.exp_zero
|
|
| CodeQueryAst.Null, _ -> false
|
|
| CodeQueryAst.ConstString s, (Vfun pn) -> string_contains s (Procname.to_string pn)
|
|
| CodeQueryAst.ConstString s, _ -> false
|
|
| CodeQueryAst.Ident id, x ->
|
|
env_add env id x
|
|
|
|
let rec exp_list_match env ael vl = match ael, vl with
|
|
| [], [] -> true
|
|
| [], _:: _ -> false
|
|
| _:: _, [] -> false
|
|
| ae:: ael', v:: vl' ->
|
|
if exp_match env ae v then exp_list_match env ael' vl'
|
|
else false
|
|
|
|
let opt_match match_elem env x_opt y = match x_opt with
|
|
| None -> true
|
|
| Some x -> match_elem env x y
|
|
|
|
let binop_match op1 op2 = match op1, op2 with
|
|
| Sil.Eq, "==" -> true
|
|
| Sil.Ne, "!=" -> true
|
|
| _ -> false
|
|
|
|
let rec cond_match env idenv cond (ae1, op, ae2) = match cond with
|
|
| Sil.BinOp (bop, _e1, _e2) ->
|
|
let e1 = Idenv.expand_expr idenv _e1 in
|
|
let e2 = Idenv.expand_expr idenv _e2 in
|
|
binop_match bop op && exp_match env ae1 (Vval e1) && exp_match env ae2 (Vval e2)
|
|
| Sil.UnOp (Sil.LNot, (Sil.BinOp (Sil.Eq, e1, e2)), _) ->
|
|
cond_match env idenv (Sil.BinOp (Sil.Ne, e1, e2)) (ae1, op, ae2)
|
|
| Sil.UnOp (Sil.LNot, (Sil.BinOp (Sil.Ne, e1, e2)), _) ->
|
|
cond_match env idenv (Sil.BinOp (Sil.Eq, e1, e2)) (ae1, op, ae2)
|
|
| _ -> false
|
|
|
|
(** Iterate over the instructions of the linearly succ nodes. *)
|
|
let rec iter_succ_nodes node iter =
|
|
match Cfg.Node.get_succs node with
|
|
| [node'] ->
|
|
let instrs = Cfg.Node.get_instrs node in
|
|
list_iter (fun instr -> iter (node', instr)) instrs;
|
|
iter_succ_nodes node' iter
|
|
| [] -> ()
|
|
| _:: _ -> ()
|
|
|
|
let linereader = Printer.LineReader.create ()
|
|
|
|
let print_action env action proc_name node loc = match action with
|
|
| CodeQueryAst.Noaction ->
|
|
L.stdout "%a@." pp_env env
|
|
| CodeQueryAst.Source source_range_opt ->
|
|
let x, y = match source_range_opt with
|
|
| None -> 10, 10
|
|
| Some (x, y) -> x, y in
|
|
L.stdout "%a@." (Checkers.PP.pp_loc_range linereader x y) loc
|
|
| CodeQueryAst.Error s_opt ->
|
|
let err_name = match s_opt with
|
|
| None -> "codequery"
|
|
| Some s -> s in
|
|
Err.add_error_to_spec proc_name err_name node loc
|
|
|
|
let rec match_query show env idenv node caller_pn (rule, action) proc_name node instr =
|
|
match rule, instr with
|
|
| CodeQueryAst.Call (ae1, ae2), Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _) ->
|
|
if exp_match env ae1 (Vfun caller_pn) && exp_match env ae2 (Vfun pn) then
|
|
begin
|
|
if show then print_action env action proc_name node loc;
|
|
true
|
|
end
|
|
else false
|
|
| CodeQueryAst.Call _, _ -> false
|
|
| CodeQueryAst.MethodCall (ae1, ae2, ael_opt), Sil.Call (_, Sil.Const (Sil.Cfun pn), (_e1, t1):: params, loc, { Sil.cf_virtual = true }) ->
|
|
let e1 = Idenv.expand_expr idenv _e1 in
|
|
let vl = list_map (function _e, t -> Vval (Idenv.expand_expr idenv _e)) params in
|
|
if exp_match env ae1 (Vval e1) && exp_match env ae2 (Vfun pn) && opt_match exp_list_match env ael_opt vl then
|
|
begin
|
|
if show then print_action env action proc_name node loc;
|
|
true
|
|
end
|
|
else false
|
|
| CodeQueryAst.MethodCall _, _ -> false
|
|
| CodeQueryAst.If (ae1, op, ae2, body_rule), Sil.Prune (cond, loc, true_branch, ik) ->
|
|
if true_branch && cond_match env idenv cond (ae1, op, ae2) then
|
|
begin
|
|
let found = ref false in
|
|
let iter (node', instr') =
|
|
let env' = env_copy env in
|
|
if not !found && match_query false env' idenv node' caller_pn (body_rule, action) proc_name node instr'
|
|
then found := true in
|
|
iter_succ_nodes node iter;
|
|
let line_contains_null () =
|
|
match Printer.LineReader.from_loc linereader loc with
|
|
| None -> false
|
|
| Some s -> string_contains "null" s in
|
|
if !found && line_contains_null () (* TODO: this replaces lack of typing where null and 0 and false are the same *) then
|
|
begin
|
|
L.stdout "conditional %a@." (Sil.pp_exp pe_text) cond;
|
|
print_action env action proc_name node loc
|
|
end;
|
|
!found
|
|
end
|
|
else false
|
|
| CodeQueryAst.If _, _ -> false
|
|
|
|
end
|
|
|
|
let code_query_callback all_procs get_proc_desc idenv tenv proc_name proc_desc =
|
|
let do_instr node instr =
|
|
let env = Match.init_env () in
|
|
let _found = Match.match_query true env idenv node proc_name (Lazy.force query_ast) proc_name node instr in
|
|
() in
|
|
if verbose then L.stdout "code_query_callback on %a@." Procname.pp proc_name;
|
|
Cfg.Procdesc.iter_instrs do_instr proc_desc;
|
|
Err.update_summary proc_name proc_desc
|