Skip to content

Commit

Permalink
Reformulation of the user-facing slot-access API
Browse files Browse the repository at this point in the history
- The internal [backtrace_slot] type is not exposed anymore, instead
  accessors function return orthogonal information
  (is_raise, location). This is both more extensible and more
  user-friendly.

- The [raw_backtrace_slot] is exposed separately as a low-level type
  that most users should never use. The unsafety of marshalling is
  documented. Instead of defining
  [raw_backtrace = raw_backtrace_slot array], I kept [raw_backtrace]
  an abstract type with [length] and [get] functions for
  random-access. This should allow us to change the implementation in
  the future to be more robust wrt. marshalling (boxing the trace in
  a Custom block, or even possibly the raw slots at access time).

git-svn-id: http://caml.inria.fr/svn/ocaml/trunk@14784 f963ae5c-01c2-4b8c-9fe0-0dff7051ff02
  • Loading branch information
gasche committed May 10, 2014
1 parent 286fbaa commit 755b196
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 67 deletions.
84 changes: 66 additions & 18 deletions stdlib/printexc.ml
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,20 @@ let convert_raw_backtrace rbckt =
try Some (Array.map convert_raw_backtrace_slot rbckt)
with Failure _ -> None

let format_backtrace_slot pos li =
let is_raise =
match li with
| Known_location(is_raise, _, _, _, _) -> is_raise
| Unknown_location(is_raise) -> is_raise in
let info =
let format_backtrace_slot pos slot =
let info is_raise =
if is_raise then
if pos = 0 then "Raised at" else "Re-raised at"
else
if pos = 0 then "Raised by primitive operation at" else "Called from"
in
match li with
match slot with
| Unknown_location true -> (* compiler-inserted re-raise, skipped *) None
| Unknown_location false ->
Some (sprintf "%s unknown location" (info false))
| Known_location(is_raise, filename, lineno, startchar, endchar) ->
sprintf "%s file \"%s\", line %d, characters %d-%d"
info filename lineno startchar endchar
| Unknown_location(is_raise) ->
sprintf "%s unknown location"
info
Some (sprintf "%s file \"%s\", line %d, characters %d-%d"
(info is_raise) filename lineno startchar endchar)

let print_exception_backtrace outchan backtrace =
match backtrace with
Expand All @@ -132,8 +128,9 @@ let print_exception_backtrace outchan backtrace =
"(Program not linked with -g, cannot print stack backtrace)\n"
| Some a ->
for i = 0 to Array.length a - 1 do
if a.(i) <> Unknown_location true then
fprintf outchan "%s\n" (format_backtrace_slot i a.(i))
match format_backtrace_slot i a.(i) with
| None -> ()
| Some str -> fprintf outchan "%s\n" str
done

let print_raw_backtrace outchan raw_backtrace =
Expand All @@ -150,14 +147,67 @@ let backtrace_to_string backtrace =
| Some a ->
let b = Buffer.create 1024 in
for i = 0 to Array.length a - 1 do
if a.(i) <> Unknown_location true then
bprintf b "%s\n" (format_backtrace_slot i a.(i))
match format_backtrace_slot i a.(i) with
| None -> ()
| Some str -> bprintf b "%s\n" str
done;
Buffer.contents b

let raw_backtrace_to_string raw_backtrace =
backtrace_to_string (convert_raw_backtrace raw_backtrace)

let backtrace_slot_is_raise = function
| Known_location(is_raise, _, _, _, _) -> is_raise
| Unknown_location(is_raise) -> is_raise

type location = {
filename : string;
line_number : int;
start_char : int;
end_char : int;
}

let backtrace_slot_location = function
| Unknown_location _ -> None
| Known_location(_is_raise, filename, line_number,
start_char, end_char) ->
Some {
filename;
line_number;
start_char;
end_char;
}

let backtrace_slots raw_backtrace =
(* The documentation of this function guarantees that Some is
returned only if a part of the trace is usable. This gives us
a bit more work than just convert_raw_backtrace, but it makes the
API more user-friendly -- otherwise most users would have to
reimplement the "Program not linked with -g, sorry" logic
themselves. *)
match convert_raw_backtrace raw_backtrace with
| None -> None
| Some backtrace ->
let usable_slot = function
| Unknown_location _ -> false
| Known_location _ -> true in
let rec exists_usable = function
| (-1) -> false
| i -> usable_slot backtrace.(i) || exists_usable (i - 1) in
if exists_usable (Array.length backtrace - 1)
then Some backtrace
else None

module Slot = struct
type t = backtrace_slot
let format = format_backtrace_slot
let is_raise = backtrace_slot_is_raise
let location = backtrace_slot_location
end

let raw_backtrace_length bckt = Array.length bckt
let get_raw_backtrace_slot bckt i = Array.get bckt i

(* confusingly named:
returns the *string* corresponding to the global current backtrace *)
let get_backtrace () =
Expand All @@ -169,10 +219,8 @@ external backtrace_status: unit -> bool = "caml_backtrace_status"
let register_printer fn =
printers := fn :: !printers


external get_callstack: int -> raw_backtrace = "caml_get_current_callstack"


let exn_slot x =
let x = Obj.repr x in
if Obj.tag x = 0 then Obj.field x 0 else x
Expand Down
177 changes: 140 additions & 37 deletions stdlib/printexc.mli
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,54 @@ val register_printer: (exn -> string option) -> unit

(** {6 Raw backtraces} *)

type raw_backtrace_slot
type raw_backtrace = raw_backtrace_slot array

(** The abstract type [raw_backtrace_slot] stores a slot of a backtrace in
type raw_backtrace
(** The abstract type [raw_backtrace] stores a backtrace in
a low-level format, instead of directly exposing them as string as
the [get_backtrace()] function does.
This allows delaying the formatting of backtraces to when they are
actually printed, which might be useful if you record more
actually printed, which may be useful if you record more
backtraces than you print.
Elements of type raw_backtrace_slot can be compared and hashed: when two
elements are equal, then they represent the same source location (the
converse is not necessarily true in presence of inlining, for example).
Raw backtraces cannot be marshalled. If you need marshalling, you
should use the array returned by the [backtrace_slots] function of
the next section.
@since 4.01.0
*)

val get_raw_backtrace: unit -> raw_backtrace
(** [Printexc.get_raw_backtrace ()] returns the same exception
backtrace that [Printexc.print_backtrace] would print, but in
a raw format.
@since 4.01.0
*)

val print_raw_backtrace: out_channel -> raw_backtrace -> unit
(** Print a raw backtrace in the same format
[Printexc.print_backtrace] uses.
@since 4.01.0
*)

val raw_backtrace_to_string: raw_backtrace -> string
(** Return a string from a raw backtrace, in the same format
[Printexc.get_backtrace] uses.
@since 4.01.0
*)

(** {6 Current call stack} *)

val get_callstack: int -> raw_backtrace
(** [Printexc.get_callstack n] returns a description of the top of the
call stack on the current program point (for the current thread),
with at most [n] entries. (Note: this function is not related to
exceptions at all, despite being part of the [Printexc] module.)
@since 4.01.0
*)

(** {6 Uncaught exceptions} *)

Expand All @@ -121,46 +150,120 @@ val set_uncaught_exception_handler: (exn -> raw_backtrace -> unit) -> unit
@since 4.02.0
*)

(** {6 Backtrace slots processing} *)

type backtrace_slot =
| Known_location of bool (* is_raise *)
* string (* filename *)
* int (* line number *)
* int (* start char *)
* int (* end char *)
| Unknown_location of bool (*is_raise*)
(** {6 Manipulation of backtrace information}
Those function allow to traverse the slots of a raw backtrace,
extract information from them in a programmer-friendly format.
*)

type backtrace_slot
(** The abstract type [backtrace_slot] represents a single slot of
a backtrace.
@since 4.02
*)

val backtrace_slots : raw_backtrace -> backtrace_slot array option
(** Returns the slots of a raw backtrace, or [None] if none of them
contain useful information.
In the return array, the slot at index [0] corresponds to the most
recent function call, raise, or primitive [get_backtrace] call in
the trace.
Some possible reasons for returning [None] are as follow:
- none of the slots in the trace come from modules compiled with
debug information ([-g])
- the program is a bytecode program that has not been linked with
debug information enabled ([ocamlc -g])
*)

type location = {
filename : string;
line_number : int;
start_char : int;
end_char : int;
}
(** The type of location information found in backtraces. [start_char]
and [end_char] are positions relative to the beginning of the
line.
@since 4.02
*)

module Slot : sig
type t = backtrace_slot

val is_raise : t -> bool
(** [is_raise slot] is [true] when [slot] refers to a raising
point in the code, and [false] when it comes from a simple
function call.
@since 4.02
*)

val location : t -> location option
(** [location slot] returns the location information of the slot,
if available, and [None] otherwise.
Some possible reasons for failing to return a location are as follow:
- the slot corresponds to a compiler-inserted raise
- the slot corresponds to a part of the program that has not been
compiled with debug information ([-g])
(** [convert_raw_backtrace_slot] converts one slot of a raw backtrace
to an Ocaml algebraic datatype representing to location
information in the source file.
@since 4.02
*)

Raises [Failure] if not able to load debug information.
val format : int -> t -> string option
(** [format pos slot] returns the string representation of [slot] as
[raw_backtrace_to_string] would format it, assuming it is the
[pos]-th element of the backtrace: the [0]-th element is
pretty-printed differently than the others.
Whole-backtrace printing functions also skip some uninformative
slots; in that case, [format pos slot] returns [None].
@since 4.02
*)
end


(** {6 Raw backtrace slots} *)

type raw_backtrace_slot
(** This type allows direct access to raw backtrace slots, without any
conversion in an OCaml-usable data-structure. Being
process-specific, they must absolutely not be marshalled, and are
unsafe to use for this reason (marshalling them may not fail, but
un-marshalling and using the result will result in
undefined behavior).
Elements of this type can still be compared and hashed: when two
elements are equal, then they represent the same source location
(the converse is not necessarily true in presence of inlining,
for example).
*)
val convert_raw_backtrace_slot: raw_backtrace_slot -> backtrace_slot

(** [format_backtrace_slot pos slot] returns the string
representation of the backtrace slot [slot] as
[raw_backtrace_to_string] would format it, assuming it is the
[pos]-th element of the backtrace: the 0-th element is
pretty-printed differently than the other.
val raw_backtrace_length : raw_backtrace -> int
(** [raw_backtrace_length bckt] returns the number of slots in the
backtrace [bckt].
Note that Printexc's printing function will skip any slot equal to
[Unknown_location true]; you should as well if you wish to
reproduce its behavior.
@since 4.02
*)
val format_backtrace_slot : int -> backtrace_slot -> string

(** {6 Current call stack} *)
val get_raw_backtrace_slot : raw_backtrace -> int -> raw_backtrace_slot
(** [get_slot bckt pos] returns the slot in position [pos] in the
backtrace [bckt].
val get_callstack: int -> raw_backtrace
@since 4.02
*)

(** [Printexc.get_callstack n] returns a description of the top of the
call stack on the current program point (for the current thread),
with at most [n] entries. (Note: this function is not related to
exceptions at all, despite being part of the [Printexc] module.)
val convert_raw_backtrace_slot : raw_backtrace_slot -> backtrace_slot
(** Extracts the user-friendly [backtrace_slot] from a low-level
[raw_backtrace_slot].
@since 4.01.0
@since 4.02
*)


Expand Down
6 changes: 3 additions & 3 deletions testsuite/tests/backtrace/backtrace_deprecated.ml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ let run args =
| None -> ()
| Some trace ->
Array.iteri
(fun i slot ->
if slot <> Printexc.Unknown_location true then
print_endline (Printexc.format_backtrace_slot i slot))
(fun i slot -> match Printexc.Slot.format i slot with
| None -> ()
| Some line -> print_endline line)
trace

let _ =
Expand Down
Loading

0 comments on commit 755b196

Please sign in to comment.