diff --git a/src/analyses/base.ml b/src/analyses/base.ml
index 5c7480ad74..c497c68191 100644
--- a/src/analyses/base.ml
+++ b/src/analyses/base.ml
@@ -536,6 +536,7 @@ struct
| `Struct s -> ValueDomain.Structs.fold (fun k v acc -> AD.join (reachable_from_value ask gs st v t description) acc) s empty
| `Int _ -> empty
| `Float _ -> empty
+ | `MutexAttr _ -> empty
| `Thread _ -> empty (* thread IDs are abstract and nothing known can be reached from them *)
| `JmpBuf _ -> empty (* Jump buffers are abstract and nothing known can be reached from them *)
| `Mutex -> empty (* mutexes are abstract and nothing known can be reached from them *)
@@ -677,6 +678,7 @@ struct
ValueDomain.Structs.fold f s (empty, TS.bot (), false)
| `Int _ -> (empty, TS.bot (), false)
| `Float _ -> (empty, TS.bot (), false)
+ | `MutexAttr _ -> (empty, TS.bot (), false)
| `Thread _ -> (empty, TS.bot (), false) (* TODO: is this right? *)
| `JmpBuf _ -> (empty, TS.bot (), false) (* TODO: is this right? *)
| `Mutex -> (empty, TS.bot (), false) (* TODO: is this right? *)
@@ -1254,6 +1256,12 @@ struct
end
| Q.EvalInt e ->
query_evalint (Analyses.ask_of_ctx ctx) ctx.global ctx.local e
+ | Q.EvalMutexAttr e -> begin
+ let e:exp = Lval (Cil.mkMem ~addr:e ~off:NoOffset) in
+ match eval_rv (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with
+ | `MutexAttr a -> a
+ | v -> MutexAttrDomain.top ()
+ end
| Q.EvalLength e -> begin
match eval_rv_address (Analyses.ask_of_ctx ctx) ctx.global ctx.local e with
| `Address a ->
@@ -2095,6 +2103,26 @@ struct
| _ -> ()
end;
raise Deadcode
+ | MutexAttrSetType {attr = attr; typ = mtyp}, _ ->
+ begin
+ let get_type lval =
+ let address = eval_lv (Analyses.ask_of_ctx ctx) gs st lval in
+ AD.get_type address
+ in
+ let dst_lval = mkMem ~addr:(Cil.stripCasts attr) ~off:NoOffset in
+ let dest_typ = get_type dst_lval in
+ let dest_a = eval_lv (Analyses.ask_of_ctx ctx) gs st dst_lval in
+ match eval_rv (Analyses.ask_of_ctx ctx) gs st mtyp with
+ | `Int x ->
+ begin
+ match ID.to_int x with
+ | Some z ->
+ if M.tracing then M.tracel "attr" "setting\n";
+ set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (`MutexAttr (ValueDomain.MutexAttr.of_int z))
+ | None -> set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (`MutexAttr (ValueDomain.MutexAttr.top ()))
+ end
+ | _ -> set ~ctx (Analyses.ask_of_ctx ctx) gs st dest_a dest_typ (`MutexAttr (ValueDomain.MutexAttr.top ()))
+ end
| Identity e, _ ->
begin match lv with
| Some x -> assign ctx x e
diff --git a/src/analyses/libraryDesc.ml b/src/analyses/libraryDesc.ml
index e3d52a9cc3..61212fa4d0 100644
--- a/src/analyses/libraryDesc.ml
+++ b/src/analyses/libraryDesc.ml
@@ -54,6 +54,8 @@ type special =
| ThreadExit of { ret_val: Cil.exp; }
| Signal of Cil.exp
| Broadcast of Cil.exp
+ | MutexAttrSetType of { attr:Cil.exp; typ: Cil.exp; }
+ | MutexInit of { mutex:Cil.exp; attr: Cil.exp; }
| Wait of { cond: Cil.exp; mutex: Cil.exp; }
| TimedWait of { cond: Cil.exp; mutex: Cil.exp; abstime: Cil.exp; (** Unused *) }
| Math of { fun_args: math; }
diff --git a/src/analyses/libraryFunctions.ml b/src/analyses/libraryFunctions.ml
index e9747f4d7b..dc7dae59e0 100644
--- a/src/analyses/libraryFunctions.ml
+++ b/src/analyses/libraryFunctions.ml
@@ -147,6 +147,8 @@ let pthread_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
("pthread_cond_broadcast", special [__ "cond" []] @@ fun cond -> Broadcast cond);
("pthread_cond_wait", special [__ "cond" []; __ "mutex" []] @@ fun cond mutex -> Wait {cond; mutex});
("pthread_cond_timedwait", special [__ "cond" []; __ "mutex" []; __ "abstime" [r]] @@ fun cond mutex abstime -> TimedWait {cond; mutex; abstime});
+ ("pthread_mutexattr_settype", special [__ "attr" []; __ "type" []] @@ fun attr typ -> MutexAttrSetType {attr; typ});
+ ("pthread_mutex_init", special [__ "mutex" []; __ "attr" []] @@ fun mutex attr -> MutexInit {mutex; attr});
("pthread_attr_destroy", unknown [drop "attr" [f]]);
("pthread_setspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []; drop "value" [w_deep]]);
("pthread_getspecific", unknown ~attrs:[InvalidateGlobals] [drop "key" []]);
diff --git a/src/analyses/mayLocks.ml b/src/analyses/mayLocks.ml
index d2edeba776..0f636f6f7e 100644
--- a/src/analyses/mayLocks.ml
+++ b/src/analyses/mayLocks.ml
@@ -1,20 +1,43 @@
-(** May lockset analysis ([maylocks]). *)
-
-(* TODO: unused *)
+(** May lockset analysis and analysis of double locking ([maylocks]). *)
open Analyses
+open GoblintCil
+module LF = LibraryFunctions
-module Arg =
+module Arg:LocksetAnalysis.MayArg =
struct
- module D = LockDomain.MayLockset
+ module D = LockDomain.MayLocksetNoRW
module G = DefaultSpec.G
module V = DefaultSpec.V
- let add ctx l =
- D.add l ctx.local
+ let add ctx (l,r) =
+ if D.mem l ctx.local then
+ let default () =
+ M.warn ~category:M.Category.Behavior.Undefined.double_locking "Acquiring a (possibly non-recursive) mutex that may be already held";
+ ctx.local
+ in
+ match D.Addr.to_var_offset l with
+ | Some (v,o) ->
+ (let mtype = ctx.ask (Queries.MutexType (v, Lval.OffsetNoIdx.of_offs o)) in
+ match mtype with
+ | `Lifted MutexAttrDomain.MutexKind.Recursive -> ctx.local
+ | `Lifted MutexAttrDomain.MutexKind.NonRec ->
+ M.warn ~category:M.Category.Behavior.Undefined.double_locking "Acquiring a non-recursive mutex that may be already held";
+ ctx.local
+ | _ -> default ())
+ | _ -> default ()
+ else
+ D.add l ctx.local
let remove ctx l =
- D.remove (l, true) (D.remove (l, false) ctx.local)
+ if not (D.mem l ctx.local) then M.warn "Releasing a mutex that is definitely not held";
+ match D.Addr.to_var_offset l with
+ | Some (v,o) ->
+ (let mtype = ctx.ask (Queries.MutexType (v, Lval.OffsetNoIdx.of_offs o)) in
+ match mtype with
+ | `Lifted MutexAttrDomain.MutexKind.NonRec -> D.remove l ctx.local
+ | _ -> ctx.local (* we cannot remove them here *))
+ | None -> ctx.local (* we cannot remove them here *)
end
module Spec =
@@ -23,6 +46,17 @@ struct
let name () = "maylocks"
let exitstate v = D.top () (* TODO: why? *)
+
+ let return ctx exp fundec =
+ if not (D.is_bot ctx.local) && ThreadReturn.is_current (Analyses.ask_of_ctx ctx) then M.warn "Exiting thread while still holding a mutex!";
+ ctx.local
+
+ let special ctx (lv:lval option) (f: varinfo) (args: exp list) =
+ (match(LF.find f).special args with
+ | ThreadExit _ -> if not @@ D.is_bot ctx.local then M.warn "Exiting thread while still holding a mutex!"
+ | _ -> ())
+ ;
+ ctx.local
end
let _ =
diff --git a/src/analyses/mutexAnalysis.ml b/src/analyses/mutexAnalysis.ml
index 775a0bae5e..88bccd4bbb 100644
--- a/src/analyses/mutexAnalysis.ml
+++ b/src/analyses/mutexAnalysis.ml
@@ -71,6 +71,7 @@ struct
D.add l ctx.local
let remove ctx l =
+ if not (D.mem (l,true) ctx.local || D.mem (l,false) ctx.local) then M.warn "unlocking mutex which may not be held";
D.remove (l, true) (D.remove (l, false) ctx.local)
let remove_all ctx =
@@ -78,6 +79,7 @@ struct
ctx.emit (MustUnlock m)
) (D.export_locks ctx.local); *)
(* TODO: used to have remove_nonspecial, which kept v.vname.[0] = '{' variables *)
+ M.warn "unlocking unknown mutex which may not be held";
D.empty ()
end
include LocksetAnalysis.MakeMust (Arg)
diff --git a/src/analyses/mutexTypeAnalysis.ml b/src/analyses/mutexTypeAnalysis.ml
new file mode 100644
index 0000000000..feb7fb413e
--- /dev/null
+++ b/src/analyses/mutexTypeAnalysis.ml
@@ -0,0 +1,75 @@
+(** An analysis tracking the type of a mutex. *)
+
+open GoblintCil
+open Analyses
+
+module MAttr = ValueDomain.MutexAttr
+module LF = LibraryFunctions
+
+module Spec : Analyses.MCPSpec with module D = Lattice.Unit and module C = Lattice.Unit =
+struct
+ include Analyses.IdentitySpec
+
+ let name () = "pthreadMutexType"
+ module D = Lattice.Unit
+ module C = Lattice.Unit
+
+ (* Removing indexes here avoids complicated lookups and allows to have the LVals as vars here, at the price that different types of mutexes in arrays are not dinstinguished *)
+ module O = Lval.OffsetNoIdx
+
+ module V = struct
+ include Printable.Prod(CilType.Varinfo)(O)
+ let is_write_only _ = false
+ end
+
+ module G = MAttr
+
+ (* transfer functions *)
+ let assign ctx (lval:lval) (rval:exp) : D.t =
+ match lval with
+ | (Var v, o) ->
+ (* There's no way to use the PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP etc for accesses via pointers *)
+ let rec helper o t = function
+ | Field ({fname = "__data"; _}, Field ({fname = "__kind"; _}, NoOffset)) when ValueDomain.Compound.is_mutex_type t ->
+ let kind =
+ (match Cil.constFold true rval with
+ | Const (CInt (c, _, _)) -> MAttr.of_int c
+ | _ -> `Top)
+ in
+ ctx.sideg (v,o) kind;
+ ctx.local
+ | Index (i,o') ->
+ let o'' = O.of_offs (`Index (i, `NoOffset)) in
+ helper (O.add_offset o o'') (Cilfacade.typeOffset t (Index (i,NoOffset))) o'
+ | Field (f,o') ->
+ let o'' = O.of_offs (`Field (f, `NoOffset)) in
+ helper (O.add_offset o o'') (Cilfacade.typeOffset t (Field (f,NoOffset))) o'
+ | NoOffset -> ctx.local
+ in
+ helper `NoOffset v.vtype o
+ | _ -> ctx.local
+
+ let special ctx (lval: lval option) (f:varinfo) (arglist:exp list) : D.t =
+ let desc = LF.find f in
+ match desc.special arglist with
+ | MutexInit {mutex = mutex; attr = attr} ->
+ let attr = ctx.ask (Queries.EvalMutexAttr attr) in
+ let mutexes = ctx.ask (Queries.MayPointTo mutex) in
+ (* It is correct to iter over these sets here, as mutexes need to be intialized before being used, and an analysis that detects usage before initialization is a different analysis. *)
+ Queries.LS.iter (function (v, o) -> ctx.sideg (v,O.of_offs o) attr) mutexes;
+ ctx.local
+ | _ -> ctx.local
+
+ let startstate v = D.bot ()
+ let threadenter ctx lval f args = [D.top ()]
+ let threadspawn ctx lval f args fctx = ctx.local
+ let exitstate v = D.top ()
+
+ let query ctx (type a) (q: a Queries.t): a Queries.result =
+ match q with
+ | Queries.MutexType (v,o) -> (ctx.global (v,o):MutexAttrDomain.t)
+ | _ -> Queries.Result.top q
+end
+
+let _ =
+ MCP.register_analysis (module Spec : MCPSpec)
diff --git a/src/analyses/threadAnalysis.ml b/src/analyses/threadAnalysis.ml
index 275de3b005..007a73b34c 100644
--- a/src/analyses/threadAnalysis.ml
+++ b/src/analyses/threadAnalysis.ml
@@ -23,7 +23,6 @@ struct
let should_join = D.equal
(* transfer functions *)
-
let return ctx (exp:exp option) (f:fundec) : D.t =
let tid = ThreadId.get_current (Analyses.ask_of_ctx ctx) in
begin match tid with
diff --git a/src/autoTune.ml b/src/autoTune.ml
index 8ad5f8d655..a267c3bf9b 100644
--- a/src/autoTune.ml
+++ b/src/autoTune.ml
@@ -204,7 +204,7 @@ let activateLongjmpAnalysesWhenRequired () =
let isLongjmp = function
| LibraryDesc.Longjmp _ -> true
| _ -> false
-in
+ in
if hasFunction isLongjmp then (
print_endline @@ "longjmp -> enabling longjmp analyses \"" ^ (String.concat ", " longjmpAnalyses) ^ "\"";
enableAnalyses longjmpAnalyses;
diff --git a/src/cdomains/lockDomain.ml b/src/cdomains/lockDomain.ml
index 6a4649b002..a107dbf91a 100644
--- a/src/cdomains/lockDomain.ml
+++ b/src/cdomains/lockDomain.ml
@@ -43,13 +43,7 @@ struct
)
end
- (* TODO: use SetDomain.Reverse *)
- module ReverseAddrSet = SetDomain.ToppedSet (Lock)
- (struct let topname = "All mutexes" end)
-
- module AddrSet = Lattice.Reverse (ReverseAddrSet)
-
- include AddrSet
+ include SetDomain.Reverse(SetDomain.ToppedSet (Lock) (struct let topname = "All mutexes" end))
let rec may_be_same_offset of1 of2 =
match of1, of2 with
@@ -62,7 +56,7 @@ struct
let add (addr,rw) set =
match (Addr.to_var_offset addr) with
- | Some (_,x) when Offs.is_definite x -> ReverseAddrSet.add (addr,rw) set
+ | Some (_,x) when Offs.is_definite x -> add (addr,rw) set
| _ -> set
let remove (addr,rw) set =
@@ -73,18 +67,9 @@ struct
| None -> false
in
match (Addr.to_var_offset addr) with
- | Some (_,x) when Offs.is_definite x -> ReverseAddrSet.remove (addr,rw) set
- | Some x -> ReverseAddrSet.filter (collect_diff_varinfo_with x) set
- | _ -> AddrSet.top ()
-
- let empty = ReverseAddrSet.empty
- let is_empty = ReverseAddrSet.is_empty
-
- let filter = ReverseAddrSet.filter
- let fold = ReverseAddrSet.fold
- let singleton = ReverseAddrSet.singleton
- let mem = ReverseAddrSet.mem
- let exists = ReverseAddrSet.exists
+ | Some (_,x) when Offs.is_definite x -> remove (addr,rw) set
+ | Some x -> filter (collect_diff_varinfo_with x) set
+ | _ -> top ()
let export_locks ls =
let f (x,_) set = Mutexes.add x set in
@@ -101,6 +86,11 @@ struct
let bot = Lockset.top
end
+module MayLocksetNoRW =
+struct
+ include PreValueDomain.AD
+end
+
module Symbolic =
struct
(* TODO: use SetDomain.Reverse *)
diff --git a/src/cdomains/lval.ml b/src/cdomains/lval.ml
index ea4acac920..656797a3b8 100644
--- a/src/cdomains/lval.ml
+++ b/src/cdomains/lval.ml
@@ -443,32 +443,38 @@ struct
end
end
-(** Lvalue lattice with sublattice representatives for {!DisjointDomain}. *)
-module NormalLatRepr (Idx: IntDomain.Z) =
-struct
- include NormalLat (Idx)
-
+(* Helper for offsets without abstract values for index offsets, i.e. with unit index offsets.*)
+module NoIdxOffsetBase = struct
module UnitIdxDomain =
struct
include Lattice.Unit
let equal_to _ _ = `Top
let to_int _ = None
end
+
+ let rec of_offs = function
+ | `NoOffset -> `NoOffset
+ | `Field (f,o) -> `Field (f, of_offs o)
+ | `Index (i,o) -> `Index (UnitIdxDomain.top (), of_offs o)
+end
+
+(** Lvalue lattice with sublattice representatives for {!DisjointDomain}. *)
+module NormalLatRepr (Idx: IntDomain.Z) =
+struct
+ include NormalLat (Idx)
+
(** Representatives for lvalue sublattices as defined by {!NormalLat}. *)
module R: DisjointDomain.Representative with type elt = t =
struct
type elt = t
+ open NoIdxOffsetBase
(* Offset module for representative without abstract values for index offsets, i.e. with unit index offsets.
Reason: The offset in the representative (used for buckets) should not depend on the integer domains,
since different integer domains may be active at different program points. *)
include Normal (UnitIdxDomain)
- let rec of_elt_offset: (fieldinfo, Idx.t) offs -> (fieldinfo, UnitIdxDomain.t) offs =
- function
- | `NoOffset -> `NoOffset
- | `Field (f,o) -> `Field (f, of_elt_offset o)
- | `Index (_,o) -> `Index (UnitIdxDomain.top (), of_elt_offset o) (* all indices to same bucket *)
+ let of_elt_offset: (fieldinfo, Idx.t) offs -> (fieldinfo, UnitIdxDomain.t) offs = of_offs
let of_elt (x: elt): t = match x with
| Addr (v, o) -> Addr (v, of_elt_offset o) (* addrs grouped by var and part of offset *)
@@ -628,3 +634,11 @@ struct
end
)
end
+
+module OffsetNoIdx =
+struct
+ include NoIdxOffsetBase
+ include Offset(UnitIdxDomain)
+
+ let name () = "offset without index"
+end
diff --git a/src/cdomains/mutexAttrDomain.ml b/src/cdomains/mutexAttrDomain.ml
new file mode 100644
index 0000000000..76669fa3a0
--- /dev/null
+++ b/src/cdomains/mutexAttrDomain.ml
@@ -0,0 +1,44 @@
+module MutexKind =
+struct
+ include Printable.StdLeaf
+
+ (* NonRec represents any of PTHREAD_MUTEX_ERRORCHECK / PTHREAD_MUTEX_NORMAL / PTHREAD_MUTEX_DEFAULT *)
+ (* Once Goblint supports the notion of failing lock operations, this should be replaced with more precise definitions *)
+ type t = NonRec | Recursive [@@deriving eq, ord, hash, to_yojson]
+ let name () = "mutexKind"
+ let show x = match x with
+ | NonRec -> "fast/error_checking"
+ | Recursive -> "recursive"
+
+ include Printable.SimpleShow (struct
+ type nonrec t = t
+ let show = show
+ end)
+end
+
+include Lattice.Flat(MutexKind) (struct let bot_name = "Uninitialized" let top_name = "Top" end)
+
+(* Needed because OS X is weird and assigns different constants than normal systems... :( *)
+let recursive_int = lazy (
+ let res = ref (Z.of_int 2) in (* Use OS X as the default, it doesn't have the enum *)
+ GoblintCil.iterGlobals !Cilfacade.current_file (function
+ | GEnumTag (einfo, _) ->
+ List.iter (fun (name, exp, _) ->
+ if name = "PTHREAD_MUTEX_RECURSIVE" then
+ res := Option.get @@ GoblintCil.getInteger exp
+ ) einfo.eitems
+ | _ -> ()
+ );
+ !res
+)
+
+
+let of_int z =
+ if Z.equal z Z.zero then
+ `Lifted MutexKind.NonRec
+ else
+ let recursive_int = Lazy.force recursive_int in
+ if Z.equal z recursive_int then
+ `Lifted MutexKind.Recursive
+ else
+ `Top
diff --git a/src/cdomains/valueDomain.ml b/src/cdomains/valueDomain.ml
index 544ac9f27a..7cba43ecc2 100644
--- a/src/cdomains/valueDomain.ml
+++ b/src/cdomains/valueDomain.ml
@@ -8,6 +8,7 @@ include PreValueDomain
module Offs = Lval.OffsetLat (IndexDomain)
module M = Messages
module BI = IntOps.BigIntOps
+module MutexAttr = MutexAttrDomain
module VDQ = ValueDomainQueries
module LS = VDQ.LS
module AddrSetDomain = SetDomain.ToppedSet(Addr)(struct let topname = "All" end)
@@ -29,6 +30,7 @@ sig
val smart_widen: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> t
val smart_leq: (exp -> BI.t option) -> (exp -> BI.t option) -> t -> t -> bool
val is_immediate_type: typ -> bool
+ val is_mutex_type: typ -> bool
val bot_value: ?varAttr:attributes -> typ -> t
val is_bot_value: t -> bool
val init_value: ?varAttr:attributes -> typ -> t
@@ -86,6 +88,7 @@ module rec Compound: S with type t = [
| `Thread of Threads.t
| `JmpBuf of JmpBufs.t
| `Mutex
+ | `MutexAttr of MutexAttr.t
| `Bot
] and type offs = (fieldinfo,IndexDomain.t) Lval.offs =
struct
@@ -101,9 +104,13 @@ struct
| `Thread of Threads.t
| `JmpBuf of JmpBufs.t
| `Mutex
+ | `MutexAttr of MutexAttrDomain.t
| `Bot
] [@@deriving eq, ord, hash]
+ let is_mutexattr_type (t:typ): bool = match t with
+ | TNamed (info, attr) -> info.tname = "pthread_mutexattr_t"
+ | _ -> false
let is_mutex_type (t: typ): bool = match t with
| TNamed (info, attr) -> info.tname = "pthread_mutex_t" || info.tname = "spinlock_t" || info.tname = "pthread_spinlock_t"
@@ -138,6 +145,7 @@ struct
let len = array_length_idx (IndexDomain.bot ()) length in
`Array (CArrays.make ~varAttr ~typAttr len (bot_value ai))
| t when is_thread_type t -> `Thread (ConcDomain.ThreadSet.empty ())
+ | t when is_mutexattr_type t -> `MutexAttr (MutexAttrDomain.bot ())
| t when is_jmp_buf_type t -> `JmpBuf (JmpBufs.Bufs.empty (), false)
| TNamed ({ttype=t; _}, _) -> bot_value ~varAttr (unrollType t)
| _ -> `Bot
@@ -154,6 +162,7 @@ struct
| `Thread x -> Threads.is_bot x
| `JmpBuf x -> JmpBufs.is_bot x
| `Mutex -> true
+ | `MutexAttr x -> MutexAttr.is_bot x
| `Bot -> true
| `Top -> false
@@ -161,6 +170,7 @@ struct
match t with
| t when is_mutex_type t -> `Mutex
| t when is_jmp_buf_type t -> `JmpBuf (JmpBufs.top ())
+ | t when is_mutexattr_type t -> `MutexAttr (MutexAttrDomain.top ())
| TInt (ik,_) -> `Int (ID.top_of ik)
| TFloat (fkind, _) when not (Cilfacade.isComplexFKind fkind) -> `Float (FD.top_of fkind)
| TPtr _ -> `Address AD.top_ptr
@@ -179,6 +189,7 @@ struct
match t with
| _ when is_mutex_type t -> `Mutex
| t when is_jmp_buf_type t -> `JmpBuf (JmpBufs.top ())
+ | t when is_mutexattr_type t -> `MutexAttr (MutexAttrDomain.top ())
| TInt (ik,_) -> `Int (ID.(cast_to ik (top_of ik)))
| TFloat (fkind, _) when not (Cilfacade.isComplexFKind fkind) -> `Float (FD.top_of fkind)
| TPtr _ -> `Address AD.top_ptr
@@ -202,6 +213,7 @@ struct
| `Array x -> CArrays.is_top x
| `Blob x -> Blobs.is_top x
| `Thread x -> Threads.is_top x
+ | `MutexAttr x -> MutexAttr.is_top x
| `JmpBuf x -> JmpBufs.is_top x
| `Mutex -> true
| `Top -> true
@@ -211,6 +223,7 @@ struct
match t with
| _ when is_mutex_type t -> `Mutex
| t when is_jmp_buf_type t -> `JmpBuf (JmpBufs.top ())
+ | t when is_mutexattr_type t -> `MutexAttr (MutexAttrDomain.top ())
| TInt (ikind, _) -> `Int (ID.of_int ikind BI.zero)
| TFloat (fkind, _) when not (Cilfacade.isComplexFKind fkind) -> `Float (FD.of_const fkind 0.0)
| TPtr _ -> `Address AD.null_ptr
@@ -234,7 +247,7 @@ struct
| _ -> `Top
let tag_name : t -> string = function
- | `Top -> "Top" | `Int _ -> "Int" | `Float _ -> "Float" | `Address _ -> "Address" | `Struct _ -> "Struct" | `Union _ -> "Union" | `Array _ -> "Array" | `Blob _ -> "Blob" | `Thread _ -> "Thread" | `Mutex -> "Mutex" | `JmpBuf _ -> "JmpBuf" | `Bot -> "Bot"
+ | `Top -> "Top" | `Int _ -> "Int" | `Float _ -> "Float" | `Address _ -> "Address" | `Struct _ -> "Struct" | `Union _ -> "Union" | `Array _ -> "Array" | `Blob _ -> "Blob" | `Thread _ -> "Thread" | `Mutex -> "Mutex" | `MutexAttr _ -> "MutexAttr" | `JmpBuf _ -> "JmpBuf" | `Bot -> "Bot"
include Printable.Std
let name () = "compound"
@@ -259,6 +272,7 @@ struct
| `Array n -> CArrays.pretty () n
| `Blob n -> Blobs.pretty () n
| `Thread n -> Threads.pretty () n
+ | `MutexAttr n -> MutexAttr.pretty () n
| `JmpBuf n -> JmpBufs.pretty () n
| `Mutex -> text "mutex"
| `Bot -> text bot_name
@@ -276,6 +290,7 @@ struct
| `Thread n -> Threads.show n
| `JmpBuf n -> JmpBufs.show n
| `Mutex -> "mutex"
+ | `MutexAttr x -> MutexAttr.show x
| `Bot -> bot_name
| `Top -> top_name
@@ -398,6 +413,7 @@ struct
| `Bot
| `Thread _
| `Mutex
+ | `MutexAttr _
| `JmpBuf _ ->
v
| _ ->
@@ -509,6 +525,7 @@ struct
| (`Address x, `Thread y) -> true
| (`JmpBuf x, `JmpBuf y) -> JmpBufs.leq x y
| (`Mutex, `Mutex) -> true
+ | (`MutexAttr x, `MutexAttr y) -> MutexAttr.leq x y
| _ -> warn_type "leq" x y; false
let rec join x y =
@@ -542,6 +559,7 @@ struct
`Thread y (* TODO: ignores address! *)
| (`JmpBuf x, `JmpBuf y) -> `JmpBuf (JmpBufs.join x y)
| (`Mutex, `Mutex) -> `Mutex
+ | (`MutexAttr x, `MutexAttr y) -> `MutexAttr (MutexAttr.join x y)
| _ ->
warn_type "join" x y;
`Top
@@ -576,6 +594,7 @@ struct
`Thread y (* TODO: ignores address! *)
| (`Mutex, `Mutex) -> `Mutex
| (`JmpBuf x, `JmpBuf y) -> `JmpBuf (JmpBufs.widen x y)
+ | (`MutexAttr x, `MutexAttr y) -> `MutexAttr (MutexAttr.widen x y)
| _ ->
warn_type "widen" x y;
`Top
@@ -635,6 +654,7 @@ struct
`Address x (* TODO: ignores thread! *)
| (`Mutex, `Mutex) -> `Mutex
| (`JmpBuf x, `JmpBuf y) -> `JmpBuf (JmpBufs.meet x y)
+ | (`MutexAttr x, `MutexAttr y) -> `MutexAttr (MutexAttr.meet x y)
| _ ->
warn_type "meet" x y;
`Bot
@@ -659,6 +679,7 @@ struct
| (`Thread y, `Address x) ->
`Address x (* TODO: ignores thread! *)
| (`Mutex, `Mutex) -> `Mutex
+ | (`MutexAttr x, `MutexAttr y) -> `MutexAttr (MutexAttr.narrow x y)
| x, `Top | `Top, x -> x
| x, `Bot | `Bot, x -> `Bot
| _ ->
@@ -1130,6 +1151,7 @@ struct
| `Array n -> CArrays.printXml f n
| `Blob n -> Blobs.printXml f n
| `Thread n -> Threads.printXml f n
+ | `MutexAttr n -> MutexAttr.printXml f n
| `JmpBuf n -> JmpBufs.printXml f n
| `Mutex -> BatPrintf.fprintf f "\n\nmutex\n\n\n"
| `Bot -> BatPrintf.fprintf f "\n\nbottom\n\n\n"
@@ -1144,6 +1166,7 @@ struct
| `Array n -> CArrays.to_yojson n
| `Blob n -> Blobs.to_yojson n
| `Thread n -> Threads.to_yojson n
+ | `MutexAttr n -> MutexAttr.to_yojson n
| `JmpBuf n -> JmpBufs.to_yojson n
| `Mutex -> `String "mutex"
| `Bot -> `String "⊥"
@@ -1198,6 +1221,7 @@ struct
| `Blob n -> `Blob (Blobs.relift n)
| `Thread n -> `Thread (Threads.relift n)
| `JmpBuf n -> `JmpBuf (JmpBufs.relift n)
+ | `MutexAttr n -> `MutexAttr (MutexAttr.relift n)
| `Mutex -> `Mutex
| `Bot -> `Bot
| `Top -> `Top
diff --git a/src/domains/queries.ml b/src/domains/queries.ml
index 2d1b25eca9..344a3c62ca 100644
--- a/src/domains/queries.ml
+++ b/src/domains/queries.ml
@@ -83,7 +83,9 @@ type _ t =
| HeapVar: VI.t t
| IsHeapVar: varinfo -> MayBool.t t (* TODO: is may or must? *)
| IsMultiple: varinfo -> MustBool.t t (* Is no other copy of this local variable reachable via pointers? *)
+ | MutexType: varinfo * Lval.OffsetNoIdx.t -> MutexAttrDomain.t t
| EvalThread: exp -> ConcDomain.ThreadSet.t t
+ | EvalMutexAttr: exp -> MutexAttrDomain.t t
| EvalJumpBuf: exp -> JmpBufDomain.JmpBufSet.t t
| ActiveJumpBuf: JmpBufDomain.ActiveLongjmps.t t
| ValidLongJmp: JmpBufDomain.JmpBufSet.t t
@@ -132,6 +134,7 @@ struct
| MustBeUniqueThread -> (module MustBool)
| EvalInt _ -> (module ID)
| EvalLength _ -> (module ID)
+ | EvalMutexAttr _ -> (module MutexAttrDomain)
| EvalValue _ -> (module VD)
| BlobSize _ -> (module ID)
| CurrentThreadId -> (module ThreadIdDomain.ThreadLifted)
@@ -143,6 +146,7 @@ struct
| DYojson -> (module FlatYojson)
| PartAccess _ -> Obj.magic (module Unit: Lattice.S) (* Never used, MCP handles PartAccess specially. Must still return module (instead of failwith) here, but the module is never used. *)
| IsMultiple _ -> (module MustBool) (* see https://github.com/goblint/analyzer/pull/310#discussion_r700056687 on why this needs to be MustBool *)
+ | MutexType _ -> (module MutexAttrDomain)
| EvalThread _ -> (module ConcDomain.ThreadSet)
| EvalJumpBuf _ -> (module JmpBufDomain.JmpBufSet)
| ActiveJumpBuf -> (module JmpBufDomain.ActiveLongjmps)
@@ -185,12 +189,14 @@ struct
| MayBePublicWithout _ -> MayBool.top ()
| MayBeThreadReturn -> MayBool.top ()
| IsHeapVar _ -> MayBool.top ()
+ | MutexType _ -> MutexAttrDomain.top ()
| MustBeProtectedBy _ -> MustBool.top ()
| MustBeAtomic -> MustBool.top ()
| MustBeSingleThreaded _ -> MustBool.top ()
| MustBeUniqueThread -> MustBool.top ()
| EvalInt _ -> ID.top ()
| EvalLength _ -> ID.top ()
+ | EvalMutexAttr _ -> MutexAttrDomain.top ()
| EvalValue _ -> VD.top ()
| BlobSize _ -> ID.top ()
| CurrentThreadId -> ThreadIdDomain.ThreadLifted.top ()
@@ -272,6 +278,8 @@ struct
| Any ActiveJumpBuf -> 46
| Any ValidLongJmp -> 47
| Any (MayBeModifiedSinceSetjmp _) -> 48
+ | Any (MutexType _) -> 49
+ | Any (EvalMutexAttr _ ) -> 50
let rec compare a b =
let r = Stdlib.compare (order a) (order b) in
@@ -292,6 +300,7 @@ struct
| Any (EvalInt e1), Any (EvalInt e2) -> CilType.Exp.compare e1 e2
| Any (EvalStr e1), Any (EvalStr e2) -> CilType.Exp.compare e1 e2
| Any (EvalLength e1), Any (EvalLength e2) -> CilType.Exp.compare e1 e2
+ | Any (EvalMutexAttr e1), Any (EvalMutexAttr e2) -> CilType.Exp.compare e1 e2
| Any (EvalValue e1), Any (EvalValue e2) -> CilType.Exp.compare e1 e2
| Any (BlobSize e1), Any (BlobSize e2) -> CilType.Exp.compare e1 e2
| Any (CondVars e1), Any (CondVars e2) -> CilType.Exp.compare e1 e2
@@ -312,6 +321,7 @@ struct
| Any (Invariant i1), Any (Invariant i2) -> compare_invariant_context i1 i2
| Any (InvariantGlobal vi1), Any (InvariantGlobal vi2) -> Stdlib.compare (Hashtbl.hash vi1) (Hashtbl.hash vi2)
| Any (IterSysVars (vq1, vf1)), Any (IterSysVars (vq2, vf2)) -> VarQuery.compare vq1 vq2 (* not comparing fs *)
+ | Any (MutexType (v1,o1)), Any (MutexType (v2,o2)) -> [%ord: CilType.Varinfo.t * Lval.OffsetNoIdx.t] (v1,o1) (v2,o2)
| Any (MustProtectedVars m1), Any (MustProtectedVars m2) -> compare_mustprotectedvars m1 m2
| Any (MayBeModifiedSinceSetjmp e1), Any (MayBeModifiedSinceSetjmp e2) -> JmpBufDomain.BufferEntry.compare e1 e2
| Any (MustBeSingleThreaded {since_start=s1;}), Any (MustBeSingleThreaded {since_start=s2;}) -> Stdlib.compare s1 s2
@@ -334,6 +344,7 @@ struct
| Any (EvalInt e) -> CilType.Exp.hash e
| Any (EvalStr e) -> CilType.Exp.hash e
| Any (EvalLength e) -> CilType.Exp.hash e
+ | Any (EvalMutexAttr e) -> CilType.Exp.hash e
| Any (EvalValue e) -> CilType.Exp.hash e
| Any (BlobSize e) -> CilType.Exp.hash e
| Any (CondVars e) -> CilType.Exp.hash e
@@ -347,6 +358,7 @@ struct
| Any (EvalJumpBuf e) -> CilType.Exp.hash e
| Any (WarnGlobal vi) -> Hashtbl.hash vi
| Any (Invariant i) -> hash_invariant_context i
+ | Any (MutexType (v,o)) -> 31*CilType.Varinfo.hash v + Lval.OffsetNoIdx.hash o
| Any (InvariantGlobal vi) -> Hashtbl.hash vi
| Any (MustProtectedVars m) -> hash_mustprotectedvars m
| Any (MayBeModifiedSinceSetjmp e) -> JmpBufDomain.BufferEntry.hash e
@@ -400,6 +412,8 @@ struct
| Any (WarnGlobal vi) -> Pretty.dprintf "WarnGlobal _"
| Any (IterSysVars _) -> Pretty.dprintf "IterSysVars _"
| Any (InvariantGlobal i) -> Pretty.dprintf "InvariantGlobal _"
+ | Any (MutexType (v,o)) -> Pretty.dprintf "MutexType _"
+ | Any (EvalMutexAttr a) -> Pretty.dprintf "EvalMutexAttr _"
| Any MayAccessed -> Pretty.dprintf "MayAccessed"
| Any MayBeTainted -> Pretty.dprintf "MayBeTainted"
| Any DYojson -> Pretty.dprintf "DYojson"
diff --git a/src/util/messageCategory.ml b/src/util/messageCategory.ml
index ef8ee5d6a9..2cc4553005 100644
--- a/src/util/messageCategory.ml
+++ b/src/util/messageCategory.ml
@@ -12,6 +12,7 @@ type undefined_behavior =
| NullPointerDereference
| UseAfterFree
| Uninitialized
+ | DoubleLocking
| Other
[@@deriving eq, ord, hash]
@@ -63,6 +64,7 @@ struct
let nullpointer_dereference: category = create @@ NullPointerDereference
let use_after_free: category = create @@ UseAfterFree
let uninitialized: category = create @@ Uninitialized
+ let double_locking: category = create @@ DoubleLocking
let other: category = create @@ Other
module ArrayOutOfBounds =
@@ -98,6 +100,7 @@ struct
| "nullpointer_dereference" -> nullpointer_dereference
| "use_after_free" -> use_after_free
| "uninitialized" -> uninitialized
+ | "double_locking" -> double_locking
| "other" -> other
| _ -> Unknown
@@ -107,6 +110,7 @@ struct
| NullPointerDereference -> ["NullPointerDereference"]
| UseAfterFree -> ["UseAfterFree"]
| Uninitialized -> ["Uninitialized"]
+ | DoubleLocking -> ["DoubleLocking"]
| Other -> ["Other"]
end
@@ -215,6 +219,7 @@ let behaviorName = function
|NullPointerDereference -> "NullPointerDereference"
|UseAfterFree -> "UseAfterFree"
|Uninitialized -> "Uninitialized"
+ |DoubleLocking -> "DoubleLocking"
|Other -> "Other"
| ArrayOutOfBounds aob -> match aob with
| PastEnd -> "PastEnd"
diff --git a/tests/regression/71-doublelocking/01-simple.c b/tests/regression/71-doublelocking/01-simple.c
new file mode 100644
index 0000000000..f2c961cb63
--- /dev/null
+++ b/tests/regression/71-doublelocking/01-simple.c
@@ -0,0 +1,36 @@
+// PARAM: --set ana.activated[+] 'maylocks'
+#include
+#include
+#include
+#include
+
+int g;
+
+pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
+
+void* f1(void* ptr) {
+ int top;
+
+ g = 1;
+ if(top) {
+ pthread_mutex_lock(&mut);
+ }
+ pthread_mutex_lock(&mut); //WARN
+ pthread_mutex_unlock(&mut);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_join(t1, NULL);
+
+ pthread_mutex_lock(&mut); //NOWARN
+ pthread_mutex_unlock(&mut);
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/02-unknown.c b/tests/regression/71-doublelocking/02-unknown.c
new file mode 100644
index 0000000000..9ad70a03b3
--- /dev/null
+++ b/tests/regression/71-doublelocking/02-unknown.c
@@ -0,0 +1,36 @@
+// PARAM: --set ana.activated[+] 'maylocks'
+#include
+#include
+#include
+#include
+
+pthread_mutex_t mut[8];
+
+void* f1(void* ptr) {
+ int top;
+ int x = 2;
+ if(top) {
+ x = 3;
+ }
+
+
+ pthread_mutex_lock(&mut[x]);
+ pthread_mutex_lock(&mut[3]); //WARN
+ pthread_mutex_unlock(&mut[3]);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_join(t1, NULL);
+
+ pthread_mutex_lock(&mut[0]); //NOWARN
+ pthread_mutex_unlock(&mut[0]);
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/03-thread-exit-with-mutex.c b/tests/regression/71-doublelocking/03-thread-exit-with-mutex.c
new file mode 100644
index 0000000000..d71f3fb616
--- /dev/null
+++ b/tests/regression/71-doublelocking/03-thread-exit-with-mutex.c
@@ -0,0 +1,39 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#include
+#include
+#include
+#include
+#include
+
+pthread_mutex_t mut[8];
+
+void* f1(void* ptr) {
+ int top;
+ int x = 2;
+ if(top) {
+ x = 3;
+ }
+
+ pthread_mutex_lock(&mut[x]);
+
+ if(top) {
+ pthread_exit(NULL); //WARN
+ }
+
+ return NULL; //WARN
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_join(t1, NULL);
+
+ pthread_mutex_lock(&mut[0]); //NOWARN
+ pthread_mutex_unlock(&mut[0]);
+
+ return 0; // We would actually want to not warn here, but the mutex type analysis is currently too imprecise
+}
diff --git a/tests/regression/71-doublelocking/04-unlock.c b/tests/regression/71-doublelocking/04-unlock.c
new file mode 100644
index 0000000000..1cc5f26654
--- /dev/null
+++ b/tests/regression/71-doublelocking/04-unlock.c
@@ -0,0 +1,35 @@
+#include
+#include
+#include
+#include
+
+int g;
+
+pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t mut2 = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+void* f1(void* ptr) {
+ int top;
+
+ pthread_mutex_lock(&mut);
+ pthread_mutex_unlock(&mut2); //WARN
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_join(t1, NULL);
+
+ pthread_mutex_lock(&mut);
+ pthread_mutex_unlock(&mut); //NOWARN
+
+ pthread_cond_wait(&cond,&mut); //WARN
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/05-rec.c b/tests/regression/71-doublelocking/05-rec.c
new file mode 100644
index 0000000000..5bc94dbeda
--- /dev/null
+++ b/tests/regression/71-doublelocking/05-rec.c
@@ -0,0 +1,47 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+
+int g;
+
+pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
+
+#ifndef __APPLE__
+pthread_mutex_t mut2 = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+#endif
+
+
+void* f1(void* ptr) {
+ int top;
+
+ g = 1;
+ if(top) {
+ pthread_mutex_lock(&mut);
+ }
+ pthread_mutex_lock(&mut); //WARN
+ pthread_mutex_unlock(&mut);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_join(t1, NULL);
+
+#ifndef __APPLE__
+ pthread_mutex_lock(&mut2); //NOWARN
+ pthread_mutex_lock(&mut2); //NOWARN
+ pthread_mutex_unlock(&mut2);
+ pthread_mutex_unlock(&mut2);
+#endif
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/06-rec-dyn.c b/tests/regression/71-doublelocking/06-rec-dyn.c
new file mode 100644
index 0000000000..aed19210c5
--- /dev/null
+++ b/tests/regression/71-doublelocking/06-rec-dyn.c
@@ -0,0 +1,43 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+int g;
+
+void* f1(void* ptr) {
+ pthread_mutex_t* mut = (pthread_mutex_t*) ptr;
+
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_unlock(mut);
+ pthread_mutex_unlock(mut);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_mutex_t mut;
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mut, &attr);
+
+
+ pthread_create(&t1,NULL,f1,&mut);
+
+
+ pthread_mutex_lock(&mut); //NOWARN
+ pthread_mutex_lock(&mut); //NOWARN
+ pthread_mutex_unlock(&mut);
+ pthread_mutex_unlock(&mut);
+
+ pthread_join(t1, NULL);
+
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/07-rec-dyn-osx.c b/tests/regression/71-doublelocking/07-rec-dyn-osx.c
new file mode 100644
index 0000000000..bb3cf65657
--- /dev/null
+++ b/tests/regression/71-doublelocking/07-rec-dyn-osx.c
@@ -0,0 +1,103 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType' --set pre.cppflags[+] "-DGOBLINT_NO_PTHREAD_ONCE"
+// Here, we do not include pthread.h, so MutexAttr.recursive_int remains at `2`, emulating the behavior of OS X.
+#define GOBLINT_NO_PTHREAD_ONCE 1
+typedef signed char __int8_t;
+typedef unsigned char __uint8_t;
+typedef short __int16_t;
+typedef unsigned short __uint16_t;
+typedef int __int32_t;
+typedef unsigned int __uint32_t;
+typedef long long __int64_t;
+typedef unsigned long long __uint64_t;
+typedef long __darwin_intptr_t;
+typedef unsigned int __darwin_natural_t;
+typedef int __darwin_ct_rune_t;
+
+
+struct __darwin_pthread_handler_rec {
+ void (*__routine)(void *);
+ void *__arg;
+ struct __darwin_pthread_handler_rec *__next;
+};
+
+struct _opaque_pthread_attr_t {
+ long __sig;
+ char __opaque[56];
+};
+
+struct _opaque_pthread_mutex_t {
+ long __sig;
+ char __opaque[56];
+};
+
+struct _opaque_pthread_mutexattr_t {
+ long __sig;
+ char __opaque[8];
+};
+
+struct _opaque_pthread_t {
+ long __sig;
+ struct __darwin_pthread_handler_rec *__cleanup_stack;
+ char __opaque[8176];
+};
+
+typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;
+typedef struct _opaque_pthread_mutex_t __darwin_pthread_mutex_t;
+typedef struct _opaque_pthread_mutexattr_t __darwin_pthread_mutexattr_t;
+typedef struct _opaque_pthread_t *__darwin_pthread_t;
+
+typedef __darwin_pthread_attr_t pthread_attr_t;
+typedef __darwin_pthread_mutex_t pthread_mutex_t;
+typedef __darwin_pthread_mutexattr_t pthread_mutexattr_t;
+typedef __darwin_pthread_t pthread_t;
+
+int pthread_create(pthread_t _Nullable restrict,
+ const pthread_attr_t * _Nullable restrict,
+ void * ,
+ void *);
+
+int pthread_join(pthread_t , void *);
+int pthread_mutex_init(pthread_mutex_t * restrict, const pthread_mutexattr_t * _Nullable restrict);
+int pthread_mutex_lock(pthread_mutex_t *);
+int pthread_mutex_unlock(pthread_mutex_t *);
+int pthread_mutexattr_destroy(pthread_mutexattr_t *);
+int pthread_mutexattr_init(pthread_mutexattr_t *);
+int pthread_mutexattr_settype(pthread_mutexattr_t *, int);
+
+
+int g;
+
+void* f1(void* ptr) {
+ pthread_mutex_t* mut = (pthread_mutex_t*) ptr;
+
+ pthread_mutex_lock(mut);
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_unlock(mut);
+ pthread_mutex_unlock(mut);
+ return ((void *)0);
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_mutex_t mut;
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_settype(&attr, 2);
+ pthread_mutex_init(&mut, &attr);
+
+
+ pthread_create(&t1,((void *)0),f1,&mut);
+
+
+ pthread_mutex_lock(&mut);
+ pthread_mutex_lock(&mut); //NOWARN
+ pthread_mutex_unlock(&mut);
+ pthread_mutex_unlock(&mut);
+
+ pthread_join(t1, ((void *)0));
+
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/08-other-type.c b/tests/regression/71-doublelocking/08-other-type.c
new file mode 100644
index 0000000000..0f4a2f7887
--- /dev/null
+++ b/tests/regression/71-doublelocking/08-other-type.c
@@ -0,0 +1,66 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+
+int g;
+
+pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
+
+#ifndef __APPLE__
+pthread_mutex_t mut2 = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+pthread_mutex_t mut3 = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
+#else
+// OS X does not define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
+// we thus use the default one there, which should also create warnings
+pthread_mutex_t mut3;
+#endif
+
+
+void* f1(void* ptr) {
+ int top;
+
+ g = 1;
+ if(top) {
+ pthread_mutex_lock(&mut);
+ }
+ pthread_mutex_lock(&mut); //WARN
+ pthread_mutex_unlock(&mut);
+ return NULL;
+}
+
+void* f2(void* ptr) {
+ int top;
+
+ g = 1;
+ if(top) {
+ pthread_mutex_lock(&mut3);
+ }
+ pthread_mutex_lock(&mut3); //WARN
+ pthread_mutex_unlock(&mut3);
+ return NULL;
+}
+
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_create(&t2,NULL,f2,NULL);
+ pthread_join(t1, NULL);
+
+#ifndef __APPLE__
+ pthread_mutex_lock(&mut2); //NOWARN
+ pthread_mutex_lock(&mut2); //NOWARN
+ pthread_mutex_unlock(&mut2); //NOWARN
+ pthread_mutex_unlock(&mut2);
+#endif
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/09-other-dyn.c b/tests/regression/71-doublelocking/09-other-dyn.c
new file mode 100644
index 0000000000..cb4ef064f9
--- /dev/null
+++ b/tests/regression/71-doublelocking/09-other-dyn.c
@@ -0,0 +1,55 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+int g;
+
+void* f1(void* ptr) {
+ pthread_mutex_t* mut = (pthread_mutex_t*) ptr;
+
+ pthread_mutex_lock(mut);
+ pthread_mutex_lock(mut); //WARN
+
+ return NULL;
+}
+
+
+void* f2(void* ptr) {
+ pthread_mutex_t* mut = (pthread_mutex_t*) ptr;
+
+ pthread_mutex_lock(mut);
+ pthread_mutex_unlock(mut);
+
+ // To check that this is now actually removed from the may lockset
+ return NULL; //NOWARN
+}
+
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+ pthread_mutex_t mut;
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+ pthread_mutex_init(&mut, &attr);
+
+
+ pthread_create(&t1,NULL,f1,&mut);
+
+
+ pthread_mutex_lock(&mut);
+ pthread_mutex_lock(&mut); //WARN
+
+
+ pthread_join(t1, NULL);
+
+ pthread_create(&t2,NULL,f2,&mut);
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/10-thread-exit-recursive.c b/tests/regression/71-doublelocking/10-thread-exit-recursive.c
new file mode 100644
index 0000000000..f360a98e48
--- /dev/null
+++ b/tests/regression/71-doublelocking/10-thread-exit-recursive.c
@@ -0,0 +1,33 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+int g;
+
+void* f1(void* ptr) {
+ pthread_mutex_t* mut = (pthread_mutex_t*) ptr;
+
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_unlock(mut);
+ return NULL; //WARN
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_mutex_t mut;
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mut, &attr);
+
+
+ pthread_create(&t1,NULL,f1,&mut);
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/11-rec-dyn-branch.c b/tests/regression/71-doublelocking/11-rec-dyn-branch.c
new file mode 100644
index 0000000000..4fee99a765
--- /dev/null
+++ b/tests/regression/71-doublelocking/11-rec-dyn-branch.c
@@ -0,0 +1,50 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+// Like 06, but tests mutexattr survives joins
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+int g;
+
+void* f1(void* ptr) {
+ pthread_mutex_t* mut = (pthread_mutex_t*) ptr;
+
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_unlock(mut);
+ pthread_mutex_unlock(mut);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_mutex_t mut;
+
+ pthread_mutexattr_t attr;
+
+ if(argc == 2) {
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ } else {
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ }
+
+ pthread_mutex_init(&mut, &attr);
+
+
+ pthread_create(&t1,NULL,f1,&mut);
+
+
+ pthread_mutex_lock(&mut); //NOWARN
+ pthread_mutex_lock(&mut); //NOWARN
+ pthread_mutex_unlock(&mut);
+ pthread_mutex_unlock(&mut);
+
+ pthread_join(t1, NULL);
+
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/12-rec-dyn-struct.c b/tests/regression/71-doublelocking/12-rec-dyn-struct.c
new file mode 100644
index 0000000000..bf30db6898
--- /dev/null
+++ b/tests/regression/71-doublelocking/12-rec-dyn-struct.c
@@ -0,0 +1,56 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+// Like 06, but tests mutexattr survives joins
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+int g;
+struct s {
+ pthread_mutex_t mut;
+};
+
+typedef struct s s_t;
+
+
+void* f1(void* ptr) {
+ pthread_mutex_t* mut = &(((s_t*) ptr)->mut);
+
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_lock(mut); //NOWARN
+ pthread_mutex_unlock(mut);
+ pthread_mutex_unlock(mut);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ s_t mut_str;
+
+ pthread_mutexattr_t attr;
+
+ if(argc == 2) {
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ } else {
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ }
+
+ pthread_mutex_init(&mut_str.mut, &attr);
+
+
+ pthread_create(&t1,NULL,f1,&mut_str);
+
+
+ pthread_mutex_lock(&mut_str.mut); //NOWARN
+ pthread_mutex_lock(&mut_str.mut); //NOWARN
+ pthread_mutex_unlock(&mut_str.mut);
+ pthread_mutex_unlock(&mut_str.mut);
+
+ pthread_join(t1, NULL);
+
+
+ return 0;
+}
diff --git a/tests/regression/71-doublelocking/13-rec-struct.c b/tests/regression/71-doublelocking/13-rec-struct.c
new file mode 100644
index 0000000000..272353e33b
--- /dev/null
+++ b/tests/regression/71-doublelocking/13-rec-struct.c
@@ -0,0 +1,53 @@
+// PARAM: --set ana.activated[+] 'maylocks' --set ana.activated[+] 'pthreadMutexType'
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+
+int g;
+
+struct s {
+ pthread_mutex_t m;
+};
+
+typedef struct s s_t;
+
+s_t mut = { PTHREAD_MUTEX_INITIALIZER };
+
+#ifndef __APPLE__
+s_t mut2 = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP };
+#endif
+
+
+void* f1(void* ptr) {
+ int top;
+
+ g = 1;
+ if(top) {
+ pthread_mutex_lock(&mut.m);
+ }
+ pthread_mutex_lock(&mut.m); //WARN
+ pthread_mutex_unlock(&mut.m);
+ return NULL;
+}
+
+
+int main(int argc, char const *argv[])
+{
+ pthread_t t1;
+ pthread_t t2;
+
+ pthread_create(&t1,NULL,f1,NULL);
+ pthread_join(t1, NULL);
+
+#ifndef __APPLE__
+ pthread_mutex_lock(&mut2.m); //NOWARN
+ pthread_mutex_lock(&mut2.m); //NOWARN
+ pthread_mutex_unlock(&mut2.m);
+ pthread_mutex_unlock(&mut2.m);
+#endif
+
+ return 0;
+}