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