Reviewed By: mbouaziz, ngorogiannis Differential Revision: D10027627 fbshipit-source-id: a5ec6b11dmaster
parent
93690dfa0e
commit
43b3f80de5
@ -0,0 +1,93 @@
|
||||
(*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
open! IStd
|
||||
module F = Format
|
||||
module L = Logging
|
||||
|
||||
let debug fmt = L.(debug Analysis Verbose fmt)
|
||||
|
||||
(* A simple purity checker *)
|
||||
|
||||
module Payload = SummaryPayload.Make (struct
|
||||
type t = PurityDomain.summary
|
||||
|
||||
let update_payloads post (payloads : Payloads.t) = {payloads with purity= Some post}
|
||||
|
||||
let of_payloads (payloads : Payloads.t) = payloads.purity
|
||||
end)
|
||||
|
||||
module TransferFunctions (CFG : ProcCfg.S) = struct
|
||||
module CFG = CFG
|
||||
module Domain = PurityDomain.Pure
|
||||
|
||||
type extras = ProcData.no_extras
|
||||
|
||||
let rec is_heap_access ae =
|
||||
match ae with
|
||||
| AccessExpression.FieldOffset _ | AccessExpression.ArrayOffset _ ->
|
||||
true
|
||||
| AccessExpression.Dereference ae | AccessExpression.AddressOf ae ->
|
||||
is_heap_access ae
|
||||
| AccessExpression.Base _ ->
|
||||
false
|
||||
|
||||
|
||||
let exec_instr (astate : Domain.astate) {ProcData.pdesc; tenv} _ (instr : HilInstr.t) =
|
||||
if astate then
|
||||
match instr with
|
||||
| Assign (ae, _, _) ->
|
||||
not (is_heap_access ae)
|
||||
| Call (_, Direct called_pname, _, _, _) -> (
|
||||
match InvariantModels.Call.dispatch tenv called_pname [] with
|
||||
| Some inv ->
|
||||
InvariantModels.is_invariant inv
|
||||
| None ->
|
||||
Payload.read pdesc called_pname
|
||||
|> Option.value_map ~default:false ~f:(fun summary ->
|
||||
debug "Reading from %a \n" Typ.Procname.pp called_pname ;
|
||||
summary ) )
|
||||
| Call (_, Indirect _, _, _, _) ->
|
||||
(* This should never happen in Java. Fail if it does. *)
|
||||
L.(die InternalError) "Unexpected indirect call %a" HilInstr.pp instr
|
||||
| _ ->
|
||||
astate
|
||||
else astate
|
||||
|
||||
|
||||
let pp_session_name _node fmt = F.pp_print_string fmt "purity checker"
|
||||
end
|
||||
|
||||
module Analyzer = LowerHil.MakeAbstractInterpreter (ProcCfg.Exceptional) (TransferFunctions)
|
||||
|
||||
let should_report pure pdesc =
|
||||
match Procdesc.get_proc_name pdesc with
|
||||
| Typ.Procname.Java java_pname as proc_name ->
|
||||
pure
|
||||
&& (not (Typ.Procname.is_constructor proc_name))
|
||||
&& not (Typ.Procname.Java.is_class_initializer java_pname)
|
||||
| _ ->
|
||||
L.(die InternalError "Not supposed to run on non-Java code.")
|
||||
|
||||
|
||||
let checker {Callbacks.tenv; summary; proc_desc} : Summary.t =
|
||||
let initial = true in
|
||||
let proc_name = Procdesc.get_proc_name proc_desc in
|
||||
let proc_data = ProcData.make_default proc_desc tenv in
|
||||
let report_pure () =
|
||||
let loc = Procdesc.get_loc proc_desc in
|
||||
let exp_desc = F.asprintf "Side-effect free function %a" Typ.Procname.pp proc_name in
|
||||
let ltr = [Errlog.make_trace_element 0 loc exp_desc []] in
|
||||
Reporting.log_error summary ~loc ~ltr IssueType.pure_function exp_desc
|
||||
in
|
||||
match Analyzer.compute_post proc_data ~initial with
|
||||
| Some pure ->
|
||||
if should_report pure proc_desc then report_pure () ;
|
||||
Payload.update_summary pure summary
|
||||
| None ->
|
||||
L.internal_error "Analyzer failed to compute purity information for %a@." Typ.Procname.pp
|
||||
proc_name ;
|
||||
summary
|
@ -0,0 +1,13 @@
|
||||
(*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
open! IStd
|
||||
module F = Format
|
||||
module Pure = AbstractDomain.BooleanAnd
|
||||
|
||||
type summary = Pure.astate
|
||||
|
||||
let pp_summary fmt summary = F.fprintf fmt "@\n Purity summary: %a @\n" Pure.pp summary
|
@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2018-present, Facebook, Inc.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
TESTS_DIR = ../../..
|
||||
|
||||
ANALYZER = checkers
|
||||
INFER_OPTIONS = --purity-only --debug-exceptions
|
||||
INFERPRINT_OPTIONS = --issues-tests
|
||||
SOURCES = $(wildcard *.java)
|
||||
|
||||
include $(TESTS_DIR)/javac.make
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import java.util.ArrayList;
|
||||
|
||||
class Test {
|
||||
|
||||
private int a = 0;
|
||||
static Integer[] global_arr;
|
||||
|
||||
void Test(int size) {
|
||||
global_arr = new Integer[size];
|
||||
}
|
||||
|
||||
void set_bad(int x, int y) {
|
||||
a = x + y;
|
||||
}
|
||||
|
||||
void global_array_set_bad(int x, int y) {
|
||||
global_arr[0] = x + y;
|
||||
}
|
||||
|
||||
int local_write_ok(int x, int y) {
|
||||
int k = x + y;
|
||||
k++;
|
||||
return k;
|
||||
}
|
||||
|
||||
void call_pure_ok(int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
local_write_ok(i, size);
|
||||
}
|
||||
}
|
||||
|
||||
void call_impure_bad(int size) {
|
||||
int d = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
set_bad(i, size);
|
||||
}
|
||||
}
|
||||
|
||||
// as soon as we allocate with new, function is marked impure.
|
||||
int local_alloc_bad_FP(int x, int y) {
|
||||
ArrayList<Integer> list = new ArrayList<Integer>(x + y);
|
||||
for (Integer el : list) {
|
||||
call_pure_ok(el);
|
||||
}
|
||||
return list.size();
|
||||
}
|
||||
|
||||
|
||||
void parameter_field_write_bad(Test test, boolean b) {
|
||||
int c = b ? 0 : 1;
|
||||
test.a = c;
|
||||
}
|
||||
|
||||
|
||||
int parameter_field_access_ok(Test test) {
|
||||
return test.a;
|
||||
}
|
||||
|
||||
// expected to be impure since y points to x
|
||||
void local_field_write_bad(Test x) {
|
||||
Test y = x;
|
||||
y.a = 0;
|
||||
}
|
||||
|
||||
void swap_bad(int[] array, int i, int j) {
|
||||
int tmp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = tmp;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
codetoanalyze/java/purity/Test.java, Test.call_pure_ok(int):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void Test.call_pure_ok(int)]
|
||||
codetoanalyze/java/purity/Test.java, Test.local_write_ok(int,int):int, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function int Test.local_write_ok(int,int)]
|
||||
codetoanalyze/java/purity/Test.java, Test.parameter_field_access_ok(Test):int, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function int Test.parameter_field_access_ok(Test)]
|
Loading…
Reference in new issue