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

(*
* 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