Raw File
wasm_pvm_errors.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com>                *)
(* Copyright (c) 2022 TriliTech <contact@trili.tech>                         *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

open Tezos_webassembly_interpreter

type truncated_string = Truncated of string [@@ocaml.unboxed]

let messages_maximum_size = 128

let truncate_message msg =
  if String.length msg > messages_maximum_size then
    Truncated (String.sub msg 0 messages_maximum_size)
  else Truncated msg

type interpreter_error = {
  raw_exception : truncated_string;
  explanation : truncated_string option;
}

type fallback_cause =
  | Decode_cause of interpreter_error
  | Link_cause of truncated_string
  | Init_cause of interpreter_error

type t =
  | Decode_error of interpreter_error
  | Link_error of truncated_string
  | Init_error of interpreter_error
  | Eval_error of interpreter_error
  | Invalid_state of truncated_string
  | Unknown_error of truncated_string
  | Too_many_ticks
  | Too_many_reboots
  | No_fallback_kernel of fallback_cause

let decode_state_to_string_raw = function
  | Decode.Byte_vector_step -> "Byte_vector_step"
  | Instr_step -> "Instr_step"
  | Instr_block_step -> "Instr_block_step"
  | Block_step -> "Block_step"
  | Name_step -> "Name_step"
  | Func_type_step -> "Func_type_step"
  | Import_step -> "Import_step"
  | Export_step -> "Export_step"
  | Code_step -> "Code_step"
  | Elem_step -> "Elem_step"
  | Data_step -> "Data_step"
  | Module_step -> "Module_step"

let decode_state_to_string exn =
  decode_state_to_string_raw exn |> truncate_message

let init_state_to_string_raw = function
  | Eval.Init_step -> "Init_step"
  | Map_step -> "Map_step"
  | Map_concat_step -> "Map_concat_step"
  | Join_step -> "Join_step"
  | Section_step -> "Section_step"
  | Eval_const -> "Eval_const"
  | Create_global_step -> "Create_global_step"
  | Run_data_step -> "Run_data_step"

let init_state_to_string exn = init_state_to_string_raw exn |> truncate_message

let durable_exn_explanation_raw = function
  | Durable.Invalid_key path -> Some ("Invalid_key: " ^ path)
  | Durable.Value_not_found -> Some "Value not found"
  | Durable.Tree_not_found -> Some "Tree not found"
  | Durable.Durable_empty | Durable_storage.Durable_empty ->
      Some "Empty durable storage"
  | _ -> None

let durable_exn_explanation exn =
  durable_exn_explanation_raw exn |> Option.map truncate_message

let eval_state_to_string_raw = function
  | Eval.Invoke_step msg -> "Invoke_step: " ^ msg
  | Label_step -> "Label_step"
  | Frame_step -> "Frame_step"
  | Eval_step -> "Eval_step"

let eval_state_to_string exn = eval_state_to_string_raw exn |> truncate_message

let reveal_error_to_string_raw = function
  | Eval.Reveal_step -> "Reveal_step"
  | Reveal_hash_decoding msg ->
      Format.sprintf "Unknown error during hash decoding: %s" msg
  | Reveal_payload_decoding msg ->
      Format.sprintf "Unknown error during payload decoding: %s" msg

let reveal_error_to_string exn =
  reveal_error_to_string_raw exn |> truncate_message

let extract_interpreter_error exn =
  let open Tezos_lazy_containers in
  let raw_exception = Printexc.to_string exn |> truncate_message in
  match exn with
  (* The locations are removed during encoding, they won't be usable in practice. *)
  | Binary_exn.Decode_error.Error (_, explanation)
  | Binary_exn.Encode_error.Error (_, explanation)
  | Binary_exn.Floating_point.Error (_, explanation)
  | Valid.Invalid (_, explanation)
  | Eval.Link (_, explanation)
  | Eval.Trap (_, explanation)
  | Eval.Crash (_, explanation)
  | Eval.Exhaustion (_, explanation)
  | Import.Unknown (_, explanation) ->
      `Interpreter
        {raw_exception; explanation = Some (truncate_message explanation)}
  | Values.TypeError _ | Binary_exn.EOS | Binary_exn.Utf8
  | Lazy_map.UnexpectedAccess | Lazy_vector.Bounds | Lazy_vector.SizeOverflow
  | Chunked_byte_vector.Bounds | Chunked_byte_vector.SizeOverflow | Table.Type
  | Table.SizeLimit | Table.OutOfMemory | Table.Bounds | Table.SizeOverflow
  | Memory.Type | Memory.SizeLimit | Memory.OutOfMemory | Memory.Bounds
  | Memory.SizeOverflow | Global.Type | Global.NotMutable | Ixx.Overflow
  | Ixx.DivideByZero | Ixx.InvalidConversion | Input_buffer.Bounds
  | Input_buffer.SizeOverflow | Input_buffer.Cannot_store_an_earlier_message
  | Input_buffer.Dequeue_from_empty_queue ->
      `Interpreter {raw_exception; explanation = None}
  | Decode.Step_error state ->
      `Interpreter
        {raw_exception; explanation = Some (decode_state_to_string state)}
  | Eval.Init_step_error state ->
      `Interpreter
        {raw_exception; explanation = Some (init_state_to_string state)}
  | Eval.Evaluation_step_error state ->
      `Interpreter
        {raw_exception; explanation = Some (eval_state_to_string state)}
  | Eval.Reveal_error state ->
      `Interpreter
        {raw_exception; explanation = Some (reveal_error_to_string state)}
  | Eval.Missing_memory_0_export ->
      `Interpreter
        {
          raw_exception;
          explanation = Some (truncate_message "Module must export memory 0");
        }
  | Durable.Invalid_key _ | Durable.Value_not_found | Durable.Tree_not_found
  | Durable.Durable_empty | Durable_storage.Durable_empty ->
      `Interpreter {raw_exception; explanation = durable_exn_explanation exn}
  | _ -> `Unknown raw_exception

let invalid_state m = Invalid_state (truncate_message m)

let truncated_string_encoding =
  Data_encoding.(conv (fun (Truncated s) -> s) (fun s -> Truncated s) string)

let interpreter_error_encoding prefix =
  let open Data_encoding in
  conv
    (fun {raw_exception; explanation} -> (raw_exception, explanation))
    (fun (raw_exception, explanation) -> {raw_exception; explanation})
    (obj2
       (req (prefix ^ "_raw_exception") truncated_string_encoding)
       (req (prefix ^ "_explanation") (option truncated_string_encoding)))

let fallback_cause_encoding =
  let open Data_encoding in
  union
    [
      case
        (Tag 0)
        ~title:"Decode_cause"
        (interpreter_error_encoding "decode")
        (function Decode_cause cause -> Some cause | _ -> None)
        (fun cause -> Decode_cause cause);
      case
        (Tag 1)
        ~title:"Link_cause"
        (obj1 (req "link" truncated_string_encoding))
        (function Link_cause cause -> Some cause | _ -> None)
        (fun cause -> Link_cause cause);
      case
        (Tag 2)
        ~title:"Init_cause"
        (interpreter_error_encoding "init")
        (function Init_cause cause -> Some cause | _ -> None)
        (fun cause -> Init_cause cause);
    ]

let encoding =
  let open Data_encoding in
  union
    [
      case
        (Tag 0)
        ~title:"Decode_error"
        (interpreter_error_encoding "decode")
        (function Decode_error err -> Some err | _ -> None)
        (fun err -> Decode_error err);
      case
        (Tag 1)
        ~title:"Link_error"
        (obj1 (req "link" truncated_string_encoding))
        (function Link_error err -> Some err | _ -> None)
        (fun err -> Link_error err);
      case
        (Tag 2)
        ~title:"Init_error"
        (interpreter_error_encoding "init")
        (function Init_error err -> Some err | _ -> None)
        (fun err -> Init_error err);
      case
        (Tag 3)
        ~title:"Eval_error"
        (interpreter_error_encoding "eval")
        (function Eval_error err -> Some err | _ -> None)
        (fun err -> Eval_error err);
      case
        (Tag 4)
        ~title:"Invalid_state"
        (obj1 (req "invalid_state" truncated_string_encoding))
        (function Invalid_state msg -> Some msg | _ -> None)
        (fun msg -> Invalid_state msg);
      case
        (Tag 5)
        ~title:"Unknown_error"
        (obj1 (req "unknown_error" truncated_string_encoding))
        (function Unknown_error exn -> Some exn | _ -> None)
        (fun exn -> Unknown_error exn);
      case
        (Tag 6)
        ~title:"Too_many_ticks"
        (constant "too_many_ticks")
        (function Too_many_ticks -> Some () | _ -> None)
        (fun () -> Too_many_ticks);
      case
        (Tag 7)
        ~title:"Too_many_reboots"
        (constant "too_many_reboots")
        (function Too_many_reboots -> Some () | _ -> None)
        (fun () -> Too_many_reboots);
      case
        (Tag 8)
        ~title:"No_fallback_kernel"
        (obj1 (req "no_kernel_fallback" fallback_cause_encoding))
        (function No_fallback_kernel cause -> Some cause | _ -> None)
        (fun cause -> No_fallback_kernel cause);
    ]

let link_error kind ~module_name ~item_name =
  match kind with
  | `Item ->
      Link_error
        (Format.sprintf "Unexpected import: %s.%s" module_name item_name
        |> truncate_message)
  | `Module ->
      Link_error
        (Format.sprintf "Unexpected module import: %s" module_name
        |> truncate_message)
back to top