Skip to content

Commit 23e84ff

Browse files
jvillardfacebook-github-bot
authored andcommitted
[pulse] detect contradictions on non-integer integer values
Summary: A known weakness of the arithmetic engine in Pulse is that it treats values as rationals instead of integers. This mitigates this weakness by introducing an `is_int()` predicate that gets added on each load and store of/to a known-integer value. The only rule currently implemented for it is that `is_int(e)` is `true` if `e` is a known integer literal, and `false` (contradiction) if `e` is a known non-integer literal. Reviewed By: da319 Differential Revision: D31932970 fbshipit-source-id: c92cae436
1 parent 8427c1b commit 23e84ff

File tree

12 files changed

+152
-16
lines changed

12 files changed

+152
-16
lines changed

infer/src/pulse/Pulse.ml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ module PulseTransferFunctions = struct
290290
(astates, ret_vars)
291291

292292

293+
let and_is_int_if_integer_type typ v astate =
294+
if Typ.is_int typ then PulseArithmetic.and_is_int v astate else Ok astate
295+
296+
293297
let exec_instr_aux ({PathContext.timestamp} as path) (astate : ExecutionDomain.t)
294298
(astate_n : NonDisjDomain.t)
295299
({InterproceduralAnalysis.tenv; proc_desc; err_log} as analysis_data) _cfg_node
@@ -302,18 +306,20 @@ module PulseTransferFunctions = struct
302306
([astate], path, astate_n)
303307
| ContinueProgram astate -> (
304308
match instr with
305-
| Load {id= lhs_id; e= rhs_exp; loc} ->
309+
| Load {id= lhs_id; e= rhs_exp; loc; typ} ->
306310
(* [lhs_id := *rhs_exp] *)
307311
let deref_rhs astate =
308312
let results =
309313
if Config.pulse_isl then
310314
PulseOperations.eval_deref_isl path loc rhs_exp astate
311315
|> List.map ~f:(fun result ->
312-
let+ astate, rhs_addr_hist = result in
313-
PulseOperations.write_id lhs_id rhs_addr_hist astate )
316+
let* astate, rhs_addr_hist = result in
317+
and_is_int_if_integer_type typ (fst rhs_addr_hist) astate
318+
>>| PulseOperations.write_id lhs_id rhs_addr_hist )
314319
else
315-
[ (let+ astate, rhs_addr_hist = PulseOperations.eval_deref path loc rhs_exp astate in
316-
PulseOperations.write_id lhs_id rhs_addr_hist astate ) ]
320+
[ (let* astate, rhs_addr_hist = PulseOperations.eval_deref path loc rhs_exp astate in
321+
and_is_int_if_integer_type typ (fst rhs_addr_hist) astate
322+
>>| PulseOperations.write_id lhs_id rhs_addr_hist ) ]
317323
in
318324
PulseReport.report_results tenv proc_desc err_log loc results
319325
in
@@ -347,7 +353,7 @@ module PulseTransferFunctions = struct
347353
[astate]
348354
in
349355
(List.concat_map set_global_astates ~f:deref_rhs, path, astate_n)
350-
| Store {e1= lhs_exp; e2= rhs_exp; loc} ->
356+
| Store {e1= lhs_exp; e2= rhs_exp; loc; typ} ->
351357
(* [*lhs_exp := rhs_exp] *)
352358
let event =
353359
match lhs_exp with
@@ -379,6 +385,7 @@ module PulseTransferFunctions = struct
379385
let astates =
380386
List.concat_map ls_astate_lhs_addr_hist ~f:(fun result ->
381387
let<*> astate, lhs_addr_hist = result in
388+
let<*> astate = and_is_int_if_integer_type typ rhs_addr astate in
382389
write_function lhs_addr_hist astate )
383390
in
384391
let astates =

infer/src/pulse/PulseArithmetic.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,7 @@ let has_no_assumptions astate =
7878
PathCondition.has_no_assumptions astate.AbductiveDomain.path_condition
7979

8080

81+
let and_is_int v astate = map_path_condition astate ~f:(fun phi -> PathCondition.and_is_int v phi)
82+
8183
let and_equal_instanceof v1 v2 t astate =
8284
map_path_condition astate ~f:(fun phi -> PathCondition.and_eq_instanceof v1 v2 t phi)

infer/src/pulse/PulseArithmetic.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ val is_unsat_cheap : AbductiveDomain.t -> bool
6161

6262
val has_no_assumptions : AbductiveDomain.t -> bool
6363

64+
val and_is_int : AbstractValue.t -> AbductiveDomain.t -> AbductiveDomain.t AccessResult.t
65+
6466
val and_equal_instanceof :
6567
AbstractValue.t
6668
-> AbstractValue.t

infer/src/pulse/PulseFormula.ml

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ module Term = struct
249249
| BitShiftRight of t * t
250250
| BitXor of t * t
251251
| IsInstanceOf of Var.t * Typ.t
252+
| IsInt of t
252253
[@@deriving compare, equal, yojson_of]
253254

254255
let equal_syntax = [%compare.equal: t]
@@ -285,7 +286,8 @@ module Term = struct
285286
| LessEqual _
286287
| Equal _
287288
| NotEqual _
288-
| IsInstanceOf _ ->
289+
| IsInstanceOf _
290+
| IsInt _ ->
289291
true
290292

291293

@@ -348,6 +350,8 @@ module Term = struct
348350
F.fprintf fmt "%a≠%a" (pp_paren pp_var ~needs_paren) t1 (pp_paren pp_var ~needs_paren) t2
349351
| IsInstanceOf (v, t) ->
350352
F.fprintf fmt "(%a instanceof %a)" pp_var v (Typ.pp Pp.text) t
353+
| IsInt t ->
354+
F.fprintf fmt "is_int(%a)" (pp_no_paren pp_var) t
351355

352356

353357
let of_q q = Const q
@@ -437,18 +441,20 @@ module Term = struct
437441
let t' = if !changed then FunctionApplication {f= t_f'; actuals= actuals'} else t in
438442
(acc, t')
439443
(* one sub-term *)
440-
| Minus t_not | BitNot t_not | Not t_not ->
441-
let acc, t_not' = f init t_not in
444+
| Minus sub_t | BitNot sub_t | Not sub_t | IsInt sub_t ->
445+
let acc, sub_t' = f init sub_t in
442446
let t' =
443-
if phys_equal t_not t_not' then t
447+
if phys_equal sub_t sub_t' then t
444448
else
445449
match[@warning "-8"] t with
446450
| Minus _ ->
447-
Minus t_not'
451+
Minus sub_t'
448452
| BitNot _ ->
449-
BitNot t_not'
453+
BitNot sub_t'
450454
| Not _ ->
451-
Not t_not'
455+
Not sub_t'
456+
| IsInt _ ->
457+
IsInt sub_t'
452458
in
453459
(acc, t')
454460
(* two sub-terms *)
@@ -564,7 +570,8 @@ module Term = struct
564570
| BitNot _
565571
| BitShiftLeft _
566572
| BitShiftRight _
567-
| BitXor _ ->
573+
| BitXor _
574+
| IsInt _ ->
568575
fold_map_direct_subterms t ~init ~f:(fun acc t' -> fold_subst_variables t' ~init:acc ~f)
569576

570577

@@ -605,6 +612,13 @@ module Term = struct
605612
t0
606613
| Linear l ->
607614
LinArith.get_as_const l |> Option.value_map ~default:t0 ~f:(fun c -> Const c)
615+
| IsInt t' ->
616+
q_map t' (fun q ->
617+
if Z.(equal one) (Q.den q) then (* an integer *) Q.one
618+
else (
619+
(* a non-integer rational *)
620+
L.d_printfln ~color:Orange "CONTRADICTION: is_int(%a)" Q.pp_print q ;
621+
Q.zero ) )
608622
| Minus t' ->
609623
q_map t' Q.(mul minus_one)
610624
| Add (t1, t2) ->
@@ -812,7 +826,8 @@ module Term = struct
812826
| LessEqual _
813827
| Equal _
814828
| NotEqual _
815-
| IsInstanceOf _ ->
829+
| IsInstanceOf _
830+
| IsInt _ ->
816831
None
817832
in
818833
match aux_linearize t with
@@ -1636,6 +1651,11 @@ let and_equal_instanceof v1 v2 t phi =
16361651
and_known_atom atom phi
16371652

16381653

1654+
let and_is_int v phi =
1655+
let atom = Atom.equal (IsInt (Var v)) Term.one in
1656+
and_known_atom atom phi
1657+
1658+
16391659
let and_less_equal = and_mk_atom Atom.less_equal
16401660

16411661
let and_less_than = and_mk_atom Atom.less_than

infer/src/pulse/PulseFormula.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ val and_equal_unop : Var.t -> Unop.t -> operand -> t -> (t * new_eqs) SatUnsat.t
5656

5757
val and_equal_binop : Var.t -> Binop.t -> operand -> operand -> t -> (t * new_eqs) SatUnsat.t
5858

59+
val and_is_int : Var.t -> t -> (t * new_eqs) SatUnsat.t
60+
5961
val prune_binop : negated:bool -> Binop.t -> operand -> operand -> t -> (t * new_eqs) SatUnsat.t
6062

6163
(** {3 Operations} *)

infer/src/pulse/PulseOperations.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ let eval path mode location exp0 astate =
272272
(AbstractValueOperand addr_rhs) astate
273273
in
274274
(astate, (binop_addr, ValueHistory.Branching [hist_lhs; hist_rhs]))
275-
| Const _ | Sizeof _ | Exn _ ->
275+
| Const (Cfloat _ | Cclass _) | Sizeof _ | Exn _ ->
276276
Ok (astate, (AbstractValue.mk_fresh (), (* TODO history *) ValueHistory.Epoch))
277277
in
278278
eval path mode exp0 astate

infer/src/pulse/PulsePathCondition.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,12 @@ let prune_binop ~negated bop lhs_op rhs_op ({is_unsat; bo_itvs= _; citvs; formul
417417
({phi with is_unsat; formula}, new_eqs) )
418418

419419

420+
let and_is_int v phi =
421+
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
422+
let+| formula, new_eqs = Formula.and_is_int v formula in
423+
({is_unsat; bo_itvs; citvs; formula}, new_eqs)
424+
425+
420426
let and_eq_instanceof v1 v2 t phi =
421427
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
422428
let+| formula, new_eqs = Formula.and_equal_instanceof v1 v2 t formula in

infer/src/pulse/PulsePathCondition.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ val eval_unop : AbstractValue.t -> Unop.t -> AbstractValue.t -> t -> t * new_eqs
6767

6868
val prune_binop : negated:bool -> Binop.t -> operand -> operand -> t -> t * new_eqs
6969

70+
val and_is_int : AbstractValue.t -> t -> t * new_eqs
71+
7072
val and_eq_instanceof : AbstractValue.t -> AbstractValue.t -> Typ.t -> t -> t * new_eqs
7173

7274
(** {2 Queries} *)

infer/src/pulse/unit/PulseFormulaTest.ml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ let instanceof typ x_var y_var phi =
4949
phi
5050

5151

52+
let is_int x phi =
53+
let+ phi, _new_eqs = and_is_int x phi in
54+
phi
55+
56+
5257
let ( + ) f1 f2 phi = of_binop (PlusA None) f1 f2 phi
5358

5459
let ( - ) f1 f2 phi = of_binop (MinusA None) f1 f2 phi
@@ -272,6 +277,32 @@ let%test_module "normalization" =
272277
-1=v7∧0=v8∧1=w∧3=v∧12=z∧[-v6 -1]=x∧[1/3·v6]=y
273278
&&
274279
true (no atoms)|}]
280+
281+
(* expected: [is_int(x)] and [is_int(y)] get simplified away, [is_int(z)] is kept around *)
282+
let%expect_test _ =
283+
normalize
284+
(is_int x_var && x + x = i 4 && is_int y_var && y = i (-42) && is_int z_var && z = x + w) ;
285+
[%expect
286+
{|
287+
known=z=v7
288+
&&
289+
x = 2 ∧ y = -42 ∧ z = w +2 ∧ v6 = 4
290+
&&
291+
-42=y∧2=x∧4=v6∧[w +2]=z
292+
&&
293+
{is_int([z]) = 1},
294+
pruned=true (no atoms),
295+
both=z=v7
296+
&&
297+
x = 2 ∧ y = -42 ∧ z = w +2 ∧ v6 = 4
298+
&&
299+
-42=y∧2=x∧4=v6∧[w +2]=z
300+
&&
301+
{is_int([z]) = 1}|}]
302+
303+
let%expect_test _ =
304+
normalize (is_int x_var && x + x = i 5) ;
305+
[%expect {|unsat|}]
275306
end )
276307

277308
let%test_module "variable elimination" =
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <stdlib.h>
9+
10+
void even_cannot_be_odd_local_ok(int y) {
11+
int x = y;
12+
if (x + x == 5) {
13+
int* p = NULL;
14+
*p = 42;
15+
}
16+
}
17+
18+
void even_cannot_be_odd_parameter_ok(int x) {
19+
if (x + x == 5) {
20+
int* p = NULL;
21+
*p = 42;
22+
}
23+
}
24+
25+
void even_cannot_be_odd_float_conv_FP() {
26+
int x = random();
27+
// two causes here: 1) Pulse represents floats as arbitrary values
28+
// and 2) the int conversion is omitted by the frontend
29+
if (x + x == (int)5.5) {
30+
int* p = NULL;
31+
*p = 42;
32+
}
33+
}
34+
35+
void int_conversions_feasible_bad() {
36+
int x = random();
37+
int y = 3 / 2;
38+
int z = (int)1.234;
39+
int w = x / 2;
40+
if (w == 5 && x == 10 && y == 1 && z == 1) {
41+
int* p = NULL;
42+
*p = 42;
43+
}
44+
}
45+
46+
struct s {
47+
int i;
48+
int j;
49+
};
50+
51+
void FPlatent_even_cannot_be_odd_fields_ok(struct s* x) {
52+
if (x->i + x->i == 5 || x->i + x->i + x->j + x->j == 5) {
53+
// latent issue is FP: arithmetic does not know 2x + 2y == 5 is impossible
54+
// when x and y are ints
55+
int* p = NULL;
56+
*p = 42;
57+
}
58+
}

0 commit comments

Comments
 (0)