You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
7.2 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 AccessExpression = HilExp.AccessExpression
module F = Format
val pp_exp : F.formatter -> AccessExpression.t -> unit
(** language sensitive pretty-printer *)
module Access : sig
type t =
| Read of {exp: AccessExpression.t} (** Field or array read *)
| Write of {exp: AccessExpression.t} (** Field or array write *)
| ContainerRead of {exp: AccessExpression.t; pname: Typ.Procname.t}
(** Read of container object *)
| ContainerWrite of {exp: AccessExpression.t; pname: Typ.Procname.t}
(** Write to container object *)
| InterfaceCall of Typ.Procname.t
(** Call to method of interface not annotated with @ThreadSafe *)
[@@deriving compare]
[racerd] replace quandary traces with explicit ones Summary: Context: "quandary" traces optimise for space by only storing a call site (plus analysis element) in a summary, as opposed to a list of call sites plus the element (i.e., a trace). When forming a report, the trace is expanded to a full one by reading the summary of the called function, and then matching up the current element with one from the summary, iterating until the trace cannot be expanded any more. In the best case, this can give a quadratic saving, as a real trace gets longer the higher one goes in the call stack, and therefore the total cost of saving that trace in each summary is quadratic in the length of the trace. Quandary traces give a linear cost. HOWEVER, these have been a source of many subtle bugs. 1. The trace expansion strategy is very arbitrary and cannot distinguish between expanded traces that are invalid (i.e., end with a call and not an originating point, such as a field access in RacerD). Plus the strategy does not explore all expansions, just the left-most one, meaning the left most may be invalid in the above sense, but another (not left-most) isn't even though it's not discovered by the expansion. This is fixable with major surgery. 2. All real traces that lead to the same endpoint are conflated -- this is to save space because there may be exponentially many such traces. That's OK, but these traces may have different locking contexts -- one may take the lock along the way, and another may not. The expansion cannot make sure that if we are reporting a trace we have recorded as taking the lock, will actually do so. This has resulted in very confusing race reports that are superficially false positives (even though they point to the existence of a real race). 3. Expansion completely breaks down in the java/buck integration when the trace goes through f -> g -> h and f,g,h are all in distinct buck targets F,G,H and F does not depend directly on H. In that case, the summary of h is simply not available when reporting/expanding in f, so the expanded trace comes out as truncated and invalid. These are filtered out, but the filtering is buggy and kills real races too. This diff completely replaces quandary traces in RacerD with plain explicit traces. - This will incur the quadratic space/time cost previously saved. See test plan: there is indeed a 30% increase in summary size, but there is no slowdown. In fact, on openssl there is a 10-20% perf increase. - For each endpoint, up to a single trace is used, as before, so no exponential explosion. However, because there is no such thing as expansion, we cannot get it wrong and change the locking context of a trace. - This diff is emulating the previous reporting format as much as possible to allow good signal from the CI. Further diffs up this stack will remove quandary-trace specific things, and simplify further the code. - 2 is not fully addressed -- it will require pushing the `AccessSnapshot` structure inside `TraceElem`. Further diffs. Reviewed By: jberdine Differential Revision: D14405600 fbshipit-source-id: d239117aa
6 years ago
include ExplicitTrace.Element with type t := t
val get_access_exp : t -> AccessExpression.t option
end
module TraceElem : sig
[racerd] replace quandary traces with explicit ones Summary: Context: "quandary" traces optimise for space by only storing a call site (plus analysis element) in a summary, as opposed to a list of call sites plus the element (i.e., a trace). When forming a report, the trace is expanded to a full one by reading the summary of the called function, and then matching up the current element with one from the summary, iterating until the trace cannot be expanded any more. In the best case, this can give a quadratic saving, as a real trace gets longer the higher one goes in the call stack, and therefore the total cost of saving that trace in each summary is quadratic in the length of the trace. Quandary traces give a linear cost. HOWEVER, these have been a source of many subtle bugs. 1. The trace expansion strategy is very arbitrary and cannot distinguish between expanded traces that are invalid (i.e., end with a call and not an originating point, such as a field access in RacerD). Plus the strategy does not explore all expansions, just the left-most one, meaning the left most may be invalid in the above sense, but another (not left-most) isn't even though it's not discovered by the expansion. This is fixable with major surgery. 2. All real traces that lead to the same endpoint are conflated -- this is to save space because there may be exponentially many such traces. That's OK, but these traces may have different locking contexts -- one may take the lock along the way, and another may not. The expansion cannot make sure that if we are reporting a trace we have recorded as taking the lock, will actually do so. This has resulted in very confusing race reports that are superficially false positives (even though they point to the existence of a real race). 3. Expansion completely breaks down in the java/buck integration when the trace goes through f -> g -> h and f,g,h are all in distinct buck targets F,G,H and F does not depend directly on H. In that case, the summary of h is simply not available when reporting/expanding in f, so the expanded trace comes out as truncated and invalid. These are filtered out, but the filtering is buggy and kills real races too. This diff completely replaces quandary traces in RacerD with plain explicit traces. - This will incur the quadratic space/time cost previously saved. See test plan: there is indeed a 30% increase in summary size, but there is no slowdown. In fact, on openssl there is a 10-20% perf increase. - For each endpoint, up to a single trace is used, as before, so no exponential explosion. However, because there is no such thing as expansion, we cannot get it wrong and change the locking context of a trace. - This diff is emulating the previous reporting format as much as possible to allow good signal from the CI. Further diffs up this stack will remove quandary-trace specific things, and simplify further the code. - 2 is not fully addressed -- it will require pushing the `AccessSnapshot` structure inside `TraceElem`. Further diffs. Reviewed By: jberdine Differential Revision: D14405600 fbshipit-source-id: d239117aa
6 years ago
include ExplicitTrace.TraceElem with type elem_t = Access.t
val is_write : t -> bool
(** is it a write OR a container write *)
val is_container_write : t -> bool
val map : f:(AccessExpression.t -> AccessExpression.t) -> t -> t
val make_container_access :
AccessExpression.t -> Typ.Procname.t -> is_write:bool -> Location.t -> t
val make_field_access : AccessExpression.t -> is_write:bool -> Location.t -> t
end
(** Overapproximation of number of locks that are currently held *)
module LocksDomain : sig
type t
val acquire_lock : t -> t
(** record acquisition of a lock *)
val release_lock : t -> t
(** record release of a lock *)
val integrate_summary : caller_astate:t -> callee_astate:t -> t
(** integrate current state with a callee summary *)
end
[thread-safety] Change meaning of @ThreadSafe to "can run in parallel with any thread including itself" Summary: Previously, annotating something ThreadSafe meant "check that it is safe to run all of this procedure's methods in parallel with each other" (including self-parallelization). This makes sense, but it means that if the user writes no annotations, we do no checking. I'm moving toward a model of inferring when an access might happen on a thread that can run concurrently with other threads, then automatically checking that it is thread-safe w.r.t to all other accesses to the same memory (on or off the current thread thread). This will let us report even when there are no `ThreadSafe` annotations. Any method that is known to run on a new thread (e.g., `Runnable.run`) will be modeled as running on a thread that can run in parallel with other threads, and so will any method that is `synchronized` or acquires a lock. In this setup, adding `ThreadSafe` to a method just means: "assume that the current method can run in parallel with any thread, including another thread that includes a different invocation of the same method (a self race) unless you see evidence to the contrary" (e.g., calling `assertMainThread` or annotating with `UiThread`). The key step in this diff is changing the threads domain to abstract *what threads the current thread may run in parallel with* rather than *what the current thread* is. This makes things much simpler. Reviewed By: jberdine Differential Revision: D5895242 fbshipit-source-id: 2e23d1e
8 years ago
(** Abstraction of threads that may run in parallel with the current thread.
NoThread < AnyThreadExceptSelf < AnyThread *)
module ThreadsDomain : sig
type t =
[thread-safety] Change meaning of @ThreadSafe to "can run in parallel with any thread including itself" Summary: Previously, annotating something ThreadSafe meant "check that it is safe to run all of this procedure's methods in parallel with each other" (including self-parallelization). This makes sense, but it means that if the user writes no annotations, we do no checking. I'm moving toward a model of inferring when an access might happen on a thread that can run concurrently with other threads, then automatically checking that it is thread-safe w.r.t to all other accesses to the same memory (on or off the current thread thread). This will let us report even when there are no `ThreadSafe` annotations. Any method that is known to run on a new thread (e.g., `Runnable.run`) will be modeled as running on a thread that can run in parallel with other threads, and so will any method that is `synchronized` or acquires a lock. In this setup, adding `ThreadSafe` to a method just means: "assume that the current method can run in parallel with any thread, including another thread that includes a different invocation of the same method (a self race) unless you see evidence to the contrary" (e.g., calling `assertMainThread` or annotating with `UiThread`). The key step in this diff is changing the threads domain to abstract *what threads the current thread may run in parallel with* rather than *what the current thread* is. This makes things much simpler. Reviewed By: jberdine Differential Revision: D5895242 fbshipit-source-id: 2e23d1e
8 years ago
| NoThread
(** No threads can run in parallel with the current thread (concretization: empty set). We
assume this by default unless we see evidence to the contrary (annotations, use of locks,
etc.) *)
| AnyThreadButSelf
(** Current thread can run in parallel with other threads, but not with a copy of itself.
(concretization : {% \{ t | t \in TIDs ^ t != t_cur \} %} ) *)
[thread-safety] Change meaning of @ThreadSafe to "can run in parallel with any thread including itself" Summary: Previously, annotating something ThreadSafe meant "check that it is safe to run all of this procedure's methods in parallel with each other" (including self-parallelization). This makes sense, but it means that if the user writes no annotations, we do no checking. I'm moving toward a model of inferring when an access might happen on a thread that can run concurrently with other threads, then automatically checking that it is thread-safe w.r.t to all other accesses to the same memory (on or off the current thread thread). This will let us report even when there are no `ThreadSafe` annotations. Any method that is known to run on a new thread (e.g., `Runnable.run`) will be modeled as running on a thread that can run in parallel with other threads, and so will any method that is `synchronized` or acquires a lock. In this setup, adding `ThreadSafe` to a method just means: "assume that the current method can run in parallel with any thread, including another thread that includes a different invocation of the same method (a self race) unless you see evidence to the contrary" (e.g., calling `assertMainThread` or annotating with `UiThread`). The key step in this diff is changing the threads domain to abstract *what threads the current thread may run in parallel with* rather than *what the current thread* is. This makes things much simpler. Reviewed By: jberdine Differential Revision: D5895242 fbshipit-source-id: 2e23d1e
8 years ago
| AnyThread
(** Current thread can run in parallel with any thread, including itself (concretization:
set of all TIDs ) *)
val can_conflict : t -> t -> bool
(** return true if two accesses with these thread values can run concurrently *)
val is_any : t -> bool
val integrate_summary : caller_astate:t -> callee_astate:t -> t
(** integrate current state with a callee summary *)
end
(** snapshot of the relevant state at the time of a heap access: concurrent thread(s), lock(s) held,
ownership precondition *)
module AccessSnapshot : sig
(** precondition for owned access; access is owned if it evaluates to true *)
module OwnershipPrecondition : sig
type t =
| Conjunction of IntSet.t
(** Conjunction of "formal index must be owned" predicates. true if empty *)
| False
include PrettyPrintable.PrintableOrderedType with type t := t
val is_true : t -> bool
(** return [true] if the precondition evaluates to true *)
end
type t = private
[racerd] replace quandary traces with explicit ones Summary: Context: "quandary" traces optimise for space by only storing a call site (plus analysis element) in a summary, as opposed to a list of call sites plus the element (i.e., a trace). When forming a report, the trace is expanded to a full one by reading the summary of the called function, and then matching up the current element with one from the summary, iterating until the trace cannot be expanded any more. In the best case, this can give a quadratic saving, as a real trace gets longer the higher one goes in the call stack, and therefore the total cost of saving that trace in each summary is quadratic in the length of the trace. Quandary traces give a linear cost. HOWEVER, these have been a source of many subtle bugs. 1. The trace expansion strategy is very arbitrary and cannot distinguish between expanded traces that are invalid (i.e., end with a call and not an originating point, such as a field access in RacerD). Plus the strategy does not explore all expansions, just the left-most one, meaning the left most may be invalid in the above sense, but another (not left-most) isn't even though it's not discovered by the expansion. This is fixable with major surgery. 2. All real traces that lead to the same endpoint are conflated -- this is to save space because there may be exponentially many such traces. That's OK, but these traces may have different locking contexts -- one may take the lock along the way, and another may not. The expansion cannot make sure that if we are reporting a trace we have recorded as taking the lock, will actually do so. This has resulted in very confusing race reports that are superficially false positives (even though they point to the existence of a real race). 3. Expansion completely breaks down in the java/buck integration when the trace goes through f -> g -> h and f,g,h are all in distinct buck targets F,G,H and F does not depend directly on H. In that case, the summary of h is simply not available when reporting/expanding in f, so the expanded trace comes out as truncated and invalid. These are filtered out, but the filtering is buggy and kills real races too. This diff completely replaces quandary traces in RacerD with plain explicit traces. - This will incur the quadratic space/time cost previously saved. See test plan: there is indeed a 30% increase in summary size, but there is no slowdown. In fact, on openssl there is a 10-20% perf increase. - For each endpoint, up to a single trace is used, as before, so no exponential explosion. However, because there is no such thing as expansion, we cannot get it wrong and change the locking context of a trace. - This diff is emulating the previous reporting format as much as possible to allow good signal from the CI. Further diffs up this stack will remove quandary-trace specific things, and simplify further the code. - 2 is not fully addressed -- it will require pushing the `AccessSnapshot` structure inside `TraceElem`. Further diffs. Reviewed By: jberdine Differential Revision: D14405600 fbshipit-source-id: d239117aa
6 years ago
{ access: TraceElem.t
; thread: ThreadsDomain.t
; lock: bool
; ownership_precondition: OwnershipPrecondition.t }
include PrettyPrintable.PrintableOrderedType with type t := t
val make :
[racerd] replace quandary traces with explicit ones Summary: Context: "quandary" traces optimise for space by only storing a call site (plus analysis element) in a summary, as opposed to a list of call sites plus the element (i.e., a trace). When forming a report, the trace is expanded to a full one by reading the summary of the called function, and then matching up the current element with one from the summary, iterating until the trace cannot be expanded any more. In the best case, this can give a quadratic saving, as a real trace gets longer the higher one goes in the call stack, and therefore the total cost of saving that trace in each summary is quadratic in the length of the trace. Quandary traces give a linear cost. HOWEVER, these have been a source of many subtle bugs. 1. The trace expansion strategy is very arbitrary and cannot distinguish between expanded traces that are invalid (i.e., end with a call and not an originating point, such as a field access in RacerD). Plus the strategy does not explore all expansions, just the left-most one, meaning the left most may be invalid in the above sense, but another (not left-most) isn't even though it's not discovered by the expansion. This is fixable with major surgery. 2. All real traces that lead to the same endpoint are conflated -- this is to save space because there may be exponentially many such traces. That's OK, but these traces may have different locking contexts -- one may take the lock along the way, and another may not. The expansion cannot make sure that if we are reporting a trace we have recorded as taking the lock, will actually do so. This has resulted in very confusing race reports that are superficially false positives (even though they point to the existence of a real race). 3. Expansion completely breaks down in the java/buck integration when the trace goes through f -> g -> h and f,g,h are all in distinct buck targets F,G,H and F does not depend directly on H. In that case, the summary of h is simply not available when reporting/expanding in f, so the expanded trace comes out as truncated and invalid. These are filtered out, but the filtering is buggy and kills real races too. This diff completely replaces quandary traces in RacerD with plain explicit traces. - This will incur the quadratic space/time cost previously saved. See test plan: there is indeed a 30% increase in summary size, but there is no slowdown. In fact, on openssl there is a 10-20% perf increase. - For each endpoint, up to a single trace is used, as before, so no exponential explosion. However, because there is no such thing as expansion, we cannot get it wrong and change the locking context of a trace. - This diff is emulating the previous reporting format as much as possible to allow good signal from the CI. Further diffs up this stack will remove quandary-trace specific things, and simplify further the code. - 2 is not fully addressed -- it will require pushing the `AccessSnapshot` structure inside `TraceElem`. Further diffs. Reviewed By: jberdine Differential Revision: D14405600 fbshipit-source-id: d239117aa
6 years ago
TraceElem.t
-> LocksDomain.t
-> ThreadsDomain.t
-> OwnershipPrecondition.t
-> Procdesc.t
-> t option
[racerd] replace quandary traces with explicit ones Summary: Context: "quandary" traces optimise for space by only storing a call site (plus analysis element) in a summary, as opposed to a list of call sites plus the element (i.e., a trace). When forming a report, the trace is expanded to a full one by reading the summary of the called function, and then matching up the current element with one from the summary, iterating until the trace cannot be expanded any more. In the best case, this can give a quadratic saving, as a real trace gets longer the higher one goes in the call stack, and therefore the total cost of saving that trace in each summary is quadratic in the length of the trace. Quandary traces give a linear cost. HOWEVER, these have been a source of many subtle bugs. 1. The trace expansion strategy is very arbitrary and cannot distinguish between expanded traces that are invalid (i.e., end with a call and not an originating point, such as a field access in RacerD). Plus the strategy does not explore all expansions, just the left-most one, meaning the left most may be invalid in the above sense, but another (not left-most) isn't even though it's not discovered by the expansion. This is fixable with major surgery. 2. All real traces that lead to the same endpoint are conflated -- this is to save space because there may be exponentially many such traces. That's OK, but these traces may have different locking contexts -- one may take the lock along the way, and another may not. The expansion cannot make sure that if we are reporting a trace we have recorded as taking the lock, will actually do so. This has resulted in very confusing race reports that are superficially false positives (even though they point to the existence of a real race). 3. Expansion completely breaks down in the java/buck integration when the trace goes through f -> g -> h and f,g,h are all in distinct buck targets F,G,H and F does not depend directly on H. In that case, the summary of h is simply not available when reporting/expanding in f, so the expanded trace comes out as truncated and invalid. These are filtered out, but the filtering is buggy and kills real races too. This diff completely replaces quandary traces in RacerD with plain explicit traces. - This will incur the quadratic space/time cost previously saved. See test plan: there is indeed a 30% increase in summary size, but there is no slowdown. In fact, on openssl there is a 10-20% perf increase. - For each endpoint, up to a single trace is used, as before, so no exponential explosion. However, because there is no such thing as expansion, we cannot get it wrong and change the locking context of a trace. - This diff is emulating the previous reporting format as much as possible to allow good signal from the CI. Further diffs up this stack will remove quandary-trace specific things, and simplify further the code. - 2 is not fully addressed -- it will require pushing the `AccessSnapshot` structure inside `TraceElem`. Further diffs. Reviewed By: jberdine Differential Revision: D14405600 fbshipit-source-id: d239117aa
6 years ago
val make_from_snapshot : TraceElem.t -> t -> t option
val is_unprotected : t -> bool
(** return true if not protected by lock, thread, or ownership *)
end
(** map of access metadata |-> set of accesses. the map should hold all accesses to a
possibly-unowned access path *)
module AccessDomain : sig
include AbstractDomain.FiniteSetS with type elt = AccessSnapshot.t
val add_opt : elt option -> t -> t
end
module OwnershipAbstractValue : sig
type t = private
| OwnedIf of IntSet.t
(** Owned if the formals at the given indexes are owned in the caller; unconditionally owned
if the set of formals is empty = bottom of the lattice *)
| Unowned (** Unowned value; top of the lattice *)
[@@deriving compare]
val owned : t
val unowned : t
val make_owned_if : int -> t
end
module OwnershipDomain : sig
type t
val empty : t
val add : AccessExpression.t -> OwnershipAbstractValue.t -> t -> t
val get_owned : AccessExpression.t -> t -> OwnershipAbstractValue.t
val propagate_assignment : AccessExpression.t -> HilExp.t -> t -> t
val propagate_return : AccessExpression.t -> OwnershipAbstractValue.t -> HilExp.t list -> t -> t
[racerd] replace quandary traces with explicit ones Summary: Context: "quandary" traces optimise for space by only storing a call site (plus analysis element) in a summary, as opposed to a list of call sites plus the element (i.e., a trace). When forming a report, the trace is expanded to a full one by reading the summary of the called function, and then matching up the current element with one from the summary, iterating until the trace cannot be expanded any more. In the best case, this can give a quadratic saving, as a real trace gets longer the higher one goes in the call stack, and therefore the total cost of saving that trace in each summary is quadratic in the length of the trace. Quandary traces give a linear cost. HOWEVER, these have been a source of many subtle bugs. 1. The trace expansion strategy is very arbitrary and cannot distinguish between expanded traces that are invalid (i.e., end with a call and not an originating point, such as a field access in RacerD). Plus the strategy does not explore all expansions, just the left-most one, meaning the left most may be invalid in the above sense, but another (not left-most) isn't even though it's not discovered by the expansion. This is fixable with major surgery. 2. All real traces that lead to the same endpoint are conflated -- this is to save space because there may be exponentially many such traces. That's OK, but these traces may have different locking contexts -- one may take the lock along the way, and another may not. The expansion cannot make sure that if we are reporting a trace we have recorded as taking the lock, will actually do so. This has resulted in very confusing race reports that are superficially false positives (even though they point to the existence of a real race). 3. Expansion completely breaks down in the java/buck integration when the trace goes through f -> g -> h and f,g,h are all in distinct buck targets F,G,H and F does not depend directly on H. In that case, the summary of h is simply not available when reporting/expanding in f, so the expanded trace comes out as truncated and invalid. These are filtered out, but the filtering is buggy and kills real races too. This diff completely replaces quandary traces in RacerD with plain explicit traces. - This will incur the quadratic space/time cost previously saved. See test plan: there is indeed a 30% increase in summary size, but there is no slowdown. In fact, on openssl there is a 10-20% perf increase. - For each endpoint, up to a single trace is used, as before, so no exponential explosion. However, because there is no such thing as expansion, we cannot get it wrong and change the locking context of a trace. - This diff is emulating the previous reporting format as much as possible to allow good signal from the CI. Further diffs up this stack will remove quandary-trace specific things, and simplify further the code. - 2 is not fully addressed -- it will require pushing the `AccessSnapshot` structure inside `TraceElem`. Further diffs. Reviewed By: jberdine Differential Revision: D14405600 fbshipit-source-id: d239117aa
6 years ago
val get_precondition : AccessExpression.t -> t -> AccessSnapshot.OwnershipPrecondition.t
end
(** attribute attached to a boolean variable specifying what it means when the boolean is true *)
module Choice : sig
type t =
| OnMainThread (** the current procedure is running on the main thread *)
| LockHeld (** a lock is currently held *)
include PrettyPrintable.PrintableOrderedType with type t := t
end
module Attribute : sig
type t =
| Functional (** holds a value returned from a callee marked @Functional *)
| Choice of Choice.t (** holds a boolean choice variable *)
include PrettyPrintable.PrintableOrderedType with type t := t
end
module AttributeSetDomain : sig
type t
val empty : t
end
module AttributeMapDomain : sig
type t
val find : AccessExpression.t -> t -> AttributeSetDomain.t
val add : AccessExpression.t -> AttributeSetDomain.t -> t -> t
val has_attribute : AccessExpression.t -> Attribute.t -> t -> bool
val get_choices : AccessExpression.t -> t -> Choice.t list
(** get the choice attributes associated with the given access path *)
val add_attribute : AccessExpression.t -> Attribute.t -> t -> t
val propagate_assignment : AccessExpression.t -> HilExp.t -> t -> t
(** propagate attributes from the leaves to the root of an RHS Hil expression *)
end
type t =
{ threads: ThreadsDomain.t (** current thread: main, background, or unknown *)
; locks: LocksDomain.t (** boolean that is true if a lock must currently be held *)
; accesses: AccessDomain.t
(** read and writes accesses performed without ownership permissions *)
; ownership: OwnershipDomain.t (** map of access paths to ownership predicates *)
; attribute_map: AttributeMapDomain.t
(** map of access paths to attributes such as owned, functional, ... *) }
(** same as astate, but without [attribute_map] (since these involve locals) and with the addition
of the ownership/attributes associated with the return value as well as the set of formals which
may escape *)
type summary =
{ threads: ThreadsDomain.t
; locks: LocksDomain.t
; accesses: AccessDomain.t
; return_ownership: OwnershipAbstractValue.t
; return_attributes: AttributeSetDomain.t }
val empty_summary : summary
include AbstractDomain.WithBottom with type t := t
val pp_summary : F.formatter -> summary -> unit
val add_unannotated_call_access : Typ.Procname.t -> Location.t -> Procdesc.t -> t -> t