Skip to content

Commit f2c4520

Browse files
Merge pull request #1777 from goblint/issue_1418
Consider option that `malloc(0)` may return `NULL`
2 parents bb1bd51 + 558e445 commit f2c4520

File tree

8 files changed

+88
-60
lines changed

8 files changed

+88
-60
lines changed

src/analyses/base.ml

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ struct
143143
* Initializing my variables
144144
**************************************************************************)
145145

146-
let heap_var on_stack man =
147-
let info = match (man.ask (Q.AllocVar {on_stack})) with
146+
let alloced_var location man =
147+
let info = match (man.ask (Q.AllocVar location)) with
148148
| `Lifted vinfo -> vinfo
149149
| _ -> failwith("Ran without a malloc analysis.") in
150150
info
@@ -2501,9 +2501,31 @@ struct
25012501
| _ ->
25022502
set ~man st lv_a lv_typ (VD.top_value (unrollType lv_typ))
25032503
end
2504-
in
2504+
in
2505+
(* Returns a tuple, the first is the address of the blob if one was allocated, the second is the returned address (may contain null pointer or be only null-pointer) *)
2506+
let alloc loc size =
2507+
(* Whether malloc(0) is assumed to return the null pointer, a valid pointer, or both cases need to be considered. *)
2508+
let malloc_zero_null, malloc_zero_pointer =
2509+
match get_string "sem.malloc.zero" with
2510+
| "null" -> true, false
2511+
| "pointer" -> false, true
2512+
| "either" -> true, true
2513+
| _ -> failwith "Invalid value for sem.malloc.zero."
2514+
in
2515+
let bytes = eval_int ~man st size in
2516+
let cmp_bytes_with_zero = ID.equal_to Z.zero bytes in
2517+
let bytes_may_be_zero = cmp_bytes_with_zero <> `Neq in
2518+
let bytes_may_be_nonzero = cmp_bytes_with_zero <> `Eq in
2519+
let include_null = (bytes_may_be_nonzero && get_bool "sem.malloc.fail") || (bytes_may_be_zero && malloc_zero_null) in
2520+
let include_pointer = bytes_may_be_nonzero || malloc_zero_pointer in
2521+
if not include_pointer then
2522+
(None, AD.null_ptr)
2523+
else
2524+
let var = AD.of_var (alloced_var loc man) in
2525+
(Some var, if include_null then AD.join var AD.null_ptr else var)
2526+
in
25052527
(* Evaluate each functions arguments. `eval_rv` is only called for its side effects, we ignore the result. *)
2506-
List.iter (fun arg -> eval_rv ~man st arg |> ignore) args;
2528+
List.iter (fun arg -> eval_rv ~man st arg |> ignore) args;
25072529
let st = match desc.special args, f.vname with
25082530
| Memset { dest; ch; count; }, _ ->
25092531
(* TODO: check count *)
@@ -2720,53 +2742,39 @@ struct
27202742
| None, _ ->
27212743
st
27222744
end
2723-
| Alloca size, _ -> begin
2745+
| ((Alloca size) as op), _
2746+
| ((Malloc size) as op), _ ->
2747+
let open Queries.AllocationLocation in
2748+
begin
2749+
(* The behavior for alloc(0) is implementation defined. Here, we rely on the options specified for the malloc also applying to alloca. *)
27242750
match lv with
27252751
| Some lv ->
2726-
let heap_var = AD.of_var (heap_var true man) in
2752+
let loc = match op with | Alloca _ -> Stack | Malloc _ -> Heap | _ -> assert false in
2753+
let (heap_var, addr) = alloc loc size in
27272754
(* ignore @@ printf "alloca will allocate %a bytes\n" ID.pretty (eval_int ~man size); *)
2728-
set_many ~man st [(heap_var, TVoid [], Blob (VD.bot (), eval_int ~man st size, ZeroInit.malloc));
2729-
(eval_lv ~man st lv, (Cilfacade.typeOfLval lv), Address heap_var)]
2730-
| _ -> st
2731-
end
2732-
| Malloc size, _ -> begin
2733-
match lv with
2734-
| Some lv ->
2735-
let heap_var =
2736-
if (get_bool "sem.malloc.fail")
2737-
then AD.join (AD.of_var (heap_var false man)) AD.null_ptr
2738-
else AD.of_var (heap_var false man)
2739-
in
2740-
(* ignore @@ printf "malloc will allocate %a bytes\n" ID.pretty (eval_int ~man size); *)
2741-
set_many ~man st [(heap_var, TVoid [], Blob (VD.bot (), eval_int ~man st size, ZeroInit.malloc));
2742-
(eval_lv ~man st lv, (Cilfacade.typeOfLval lv), Address heap_var)]
2755+
let blob_set = Option.map_default (fun heap_var -> [(heap_var, TVoid [], VD.Blob (VD.bot (), eval_int ~man st size, ZeroInit.malloc))]) [] heap_var in
2756+
set_many ~man st ((eval_lv ~man st lv, (Cilfacade.typeOfLval lv), VD.Address addr) :: blob_set)
27432757
| _ -> st
27442758
end
27452759
| Calloc { count = n; size }, _ ->
2760+
let open Queries.AllocationLocation in
27462761
begin match lv with
27472762
| Some lv -> (* array length is set to one, as num*size is done when turning into `Calloc *)
2748-
let heap_var = heap_var false man in
2749-
let add_null addr =
2750-
if get_bool "sem.malloc.fail"
2751-
then AD.join addr AD.null_ptr (* calloc can fail and return NULL *)
2752-
else addr in
2763+
let (heap_var, addr) = alloc Queries.AllocationLocation.Heap size in
27532764
let ik = Cilfacade.ptrdiff_ikind () in
27542765
let sizeval = eval_int ~man st size in
27552766
let countval = eval_int ~man st n in
2756-
if ID.to_int countval = Some Z.one then (
2757-
set_many ~man st [
2758-
(add_null (AD.of_var heap_var), TVoid [], Blob (VD.bot (), sizeval, ZeroInit.calloc));
2759-
(eval_lv ~man st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_var heap_var)))
2760-
]
2761-
)
2762-
else (
2767+
if ID.to_int countval = Some Z.one then
2768+
let blob_set = Option.map_default (fun heap_var -> [heap_var, TVoid [], VD.Blob (VD.bot (), sizeval, ZeroInit.calloc)]) [] heap_var in
2769+
set_many ~man st ((eval_lv ~man st lv, (Cilfacade.typeOfLval lv), Address addr):: blob_set)
2770+
else
27632771
let blobsize = ID.mul (ID.cast_to ik @@ sizeval) (ID.cast_to ik @@ countval) in
2772+
let offset = `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.zero, `NoOffset) in
2773+
(* the heap_var is the base address of the allocated memory, but we need to keep track of the offset for the blob *)
2774+
let addr_offset = AD.map (fun a -> Addr.add_offset a offset) addr in
27642775
(* the memory that was allocated by calloc is set to bottom, but we keep track that it originated from calloc, so when bottom is read from memory allocated by calloc it is turned to zero *)
2765-
set_many ~man st [
2766-
(add_null (AD.of_var heap_var), TVoid [], Array (CArrays.make (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.one) (Blob (VD.bot (), blobsize, ZeroInit.calloc))));
2767-
(eval_lv ~man st lv, (Cilfacade.typeOfLval lv), Address (add_null (AD.of_mval (heap_var, `Index (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.zero, `NoOffset)))))
2768-
]
2769-
)
2776+
let blob_set = Option.map_default (fun heap_var -> [heap_var, TVoid [], VD.Array (CArrays.make (IdxDom.of_int (Cilfacade.ptrdiff_ikind ()) Z.one) (Blob (VD.bot (), blobsize, ZeroInit.calloc)))]) [] heap_var in
2777+
set_many ~man st ((eval_lv ~man st lv, (Cilfacade.typeOfLval lv), Address addr_offset) :: blob_set)
27702778
| _ -> st
27712779
end
27722780
| Realloc { ptr = p; size }, _ ->
@@ -2787,18 +2795,11 @@ struct
27872795
let p_addr_get = get ~man st p_addr' None in (* implicitly includes join of malloc value (VD.bot) *)
27882796
let size_int = eval_int ~man st size in
27892797
let heap_val:value = Blob (p_addr_get, size_int, ZeroInit.malloc) in (* copy old contents with new size *)
2790-
let heap_addr = AD.of_var (heap_var false man) in
2791-
let heap_addr' =
2792-
if get_bool "sem.malloc.fail" then
2793-
AD.join heap_addr AD.null_ptr
2794-
else
2795-
heap_addr
2796-
in
2798+
let (heap_var,addr) = alloc Queries.AllocationLocation.Heap size in
27972799
let lv_addr = eval_lv ~man st lv in
2798-
set_many ~man st [
2799-
(heap_addr, TVoid [], heap_val);
2800-
(lv_addr, Cilfacade.typeOfLval lv, Address heap_addr');
2801-
] (* TODO: free (i.e. invalidate) old blob if successful? *)
2800+
let blob_set = Option.map_default (fun heap_addr -> [heap_addr, TVoid [], heap_val]) [] heap_var in
2801+
(* TODO: free (i.e. invalidate) old blob if successful? *)
2802+
set_many ~man st ((lv_addr, Cilfacade.typeOfLval lv, Address addr) :: blob_set)
28022803
| None ->
28032804
st
28042805
end

src/analyses/mallocFresh.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ struct
3939

4040
let special man lval f args =
4141
let desc = LibraryFunctions.find f in
42-
let alloc_var on_stack =
43-
match man.ask (AllocVar {on_stack = on_stack}) with
42+
let alloc_var location =
43+
match man.ask (AllocVar location) with
4444
| `Lifted var -> D.add var man.local
4545
| _ -> man.local
4646
in
4747
match desc.special args with
4848
| Malloc _
4949
| Calloc _
50-
| Realloc _ -> alloc_var false
51-
| Alloca _ -> alloc_var true
50+
| Realloc _ -> alloc_var Heap
51+
| Alloca _ -> alloc_var Stack
5252
| _ ->
5353
match lval with
5454
| None -> man.local

src/analyses/memLeak.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ struct
206206
| Calloc _
207207
| Realloc _ ->
208208
man.sideg () true;
209-
begin match man.ask (Queries.AllocVar {on_stack = false}) with
209+
begin match man.ask (Queries.AllocVar Heap) with
210210
| `Lifted var ->
211211
ToppedVarInfoSet.add var state
212212
| _ -> state

src/analyses/useAfterFree.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ struct
230230
end
231231
| Alloca _ ->
232232
(* Create fresh heap var for the alloca() call *)
233-
begin match man.ask (Queries.AllocVar {on_stack = true}) with
233+
begin match man.ask (Queries.AllocVar Stack) with
234234
| `Lifted v -> (AllocaVars.add v (fst state), snd state)
235235
| _ -> state
236236
end

src/analyses/wrapperFunctionAnalysis.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,15 @@ module MallocWrapper : MCPSpec = struct
155155
let query (man: (D.t, G.t, C.t, V.t) man) (type a) (q: a Q.t): a Q.result =
156156
let wrapper_node, counter = man.local in
157157
match q with
158-
| Q.AllocVar {on_stack = on_stack} ->
158+
| Q.AllocVar location ->
159159
let node = match wrapper_node with
160160
| `Lifted wrapper_node -> wrapper_node
161161
| _ -> node_for_man man
162162
in
163163
let count = UniqueCallCounter.find (`Lifted node) counter in
164164
let var = NodeVarinfoMap.to_varinfo (man.ask Q.CurrentThreadId, node, count) in
165165
var.vdecl <- UpdateCil.getLoc node; (* TODO: does this do anything bad for incremental? *)
166-
if on_stack then var.vattr <- addAttribute (Attr ("stack_alloca", [])) var.vattr; (* If the call was for stack allocation, add an attr to mark the heap var *)
166+
if location = Stack then var.vattr <- addAttribute (Attr ("stack_alloca", [])) var.vattr; (* If the call was for stack allocation, add an attr to mark the heap var *)
167167
`Lifted var
168168
| Q.IsHeapVar v ->
169169
NodeVarinfoMap.mem_varinfo v && not @@ hasAttribute "stack_alloca" v.vattr

src/config/options.schema.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1644,9 +1644,16 @@
16441644
"fail": {
16451645
"title": "sem.malloc.fail",
16461646
"description":
1647-
"Consider the case where malloc or calloc fails.",
1647+
"Consider the case where malloc or calloc fails when called with an argument other than zero. For the case of zero, see `sem.malloc.zero`.",
16481648
"type": "boolean",
16491649
"default": false
1650+
},
1651+
"zero": {
1652+
"title": "sem.malloc.zero",
1653+
"description": "What happens when allocating zero bytes? 'null': Return null pointer, 'pointer': Return a pointer to valid memory of size 0, 'either': Consider both cases.",
1654+
"type": "string",
1655+
"enum": ["null", "pointer","either"],
1656+
"default": "either"
16501657
}
16511658
},
16521659
"additionalProperties": false

src/domains/queries.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ module Protection = struct
5050
type t = Strong | Weak [@@deriving ord, hash]
5151
end
5252

53+
module AllocationLocation = struct
54+
type t = Stack | Heap [@@deriving ord, hash]
55+
end
56+
5357
(* Helper definitions for deriving complex parts of Any.compare below. *)
5458
type maybepublic = {global: CilType.Varinfo.t; write: bool; protection: Protection.t} [@@deriving ord, hash]
5559
type maybepublicwithout = {global: CilType.Varinfo.t; write: bool; without_mutex: LockDomain.MustLock.t; protection: Protection.t} [@@deriving ord, hash]
@@ -101,7 +105,7 @@ type _ t =
101105
| IterVars: itervar -> Unit.t t
102106
| PathQuery: int * 'a t -> 'a t (** Query only one path under witness lifter. *)
103107
| DYojson: FlatYojson.t t (** Get local state Yojson of one path under [PathQuery]. *)
104-
| AllocVar: {on_stack: bool} -> VI.t t
108+
| AllocVar: AllocationLocation.t -> VI.t t
105109
(* Create a variable representing a dynamic allocation-site *)
106110
(* If on_stack is [true], then the dynamic allocation is on the stack (i.e., alloca() or a similar function was called). Otherwise, allocation is on the heap *)
107111
| IsAllocVar: varinfo -> MayBool.t t (* [true] if variable represents dynamically allocated memory *)
@@ -388,6 +392,7 @@ struct
388392
| Any (CondVars e1), Any (CondVars e2) -> CilType.Exp.compare e1 e2
389393
| Any (PartAccess p1), Any (PartAccess p2) -> compare_access p1 p2
390394
| Any (IterPrevVars ip1), Any (IterPrevVars ip2) -> compare_iterprevvar ip1 ip2
395+
| Any (AllocVar location), Any (AllocVar location2) -> AllocationLocation.compare location location2
391396
| Any (IterVars i1), Any (IterVars i2) -> compare_itervar i1 i2
392397
| Any (PathQuery (i1, q1)), Any (PathQuery (i2, q2)) ->
393398
let r = Stdlib.compare i1 i2 in
@@ -441,6 +446,7 @@ struct
441446
| Any (PartAccess p) -> hash_access p
442447
| Any (IterPrevVars i) -> 0
443448
| Any (IterVars i) -> 0
449+
| Any (AllocVar location) -> AllocationLocation.hash location
444450
| Any (PathQuery (i, q)) -> 31 * i + hash (Any q)
445451
| Any (IsHeapVar v) -> CilType.Varinfo.hash v
446452
| Any (MustTermLoop s) -> CilType.Stmt.hash s
@@ -497,7 +503,7 @@ struct
497503
| Any (IterPrevVars i) -> Pretty.dprintf "IterPrevVars _"
498504
| Any (IterVars i) -> Pretty.dprintf "IterVars _"
499505
| Any (PathQuery (i, q)) -> Pretty.dprintf "PathQuery (%d, %a)" i pretty (Any q)
500-
| Any (AllocVar {on_stack = on_stack}) -> Pretty.dprintf "AllocVar %b" on_stack
506+
| Any (AllocVar location) -> Pretty.dprintf "AllocVar _"
501507
| Any (IsHeapVar v) -> Pretty.dprintf "IsHeapVar %a" CilType.Varinfo.pretty v
502508
| Any (IsAllocVar v) -> Pretty.dprintf "IsAllocVar %a" CilType.Varinfo.pretty v
503509
| Any (IsMultiple v) -> Pretty.dprintf "IsMultiple %a" CilType.Varinfo.pretty v
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// PARAM: --set sem.malloc.zero either --set ana.activated[+] memOutOfBounds
2+
#include<pthread.h>
3+
#include<goblint.h>
4+
5+
int main(void){
6+
int* ptr = malloc(0);
7+
8+
if(ptr == 0) {
9+
// Reachable
10+
__goblint_check(1);
11+
} else {
12+
*ptr = 1; //WARN
13+
}
14+
}

0 commit comments

Comments
 (0)