Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements around external modules and error handling #605

Merged
merged 4 commits into from
Apr 16, 2024

Conversation

AltGr
Copy link
Contributor

@AltGr AltGr commented Apr 13, 2024

This allows external modules to access all modules (see first patch), protects
the interpreter for better reporting of exceptions raised from modules (third
patch), and register exception printers in the OCaml runtime.

Quoting here the message from the second patch because it mentions some
important points, that may warrant discussion:

OCaml runtime: register fallback exception printers

This was a pending TODO: now the Catala program compiled into OCaml should
return better messages and a little more information about uncaught
exceptions.

Note that this also concerns, at the moment, compiled modules called from
the Catala interpreter: in this case, it's already better than nothing, but
what we need is proper interoperation between the runtime exceptions and
the interpreter handling (EmptyError should already be handled properly
since it is critical to the computation flow, but "error" exceptions are
left uncaught and will kill the interpreter).

This may be part of a bigger task on unifying the output of the runtime and
toplevel, which also concerns computation traces.

Note 2: All runtime exceptions don't have a position available, which is
quite unfortunate when your program hits an error. With OCAMLRUNPARAM=b
and if compiled with -g (which should normally be the case), you can get
an OCaml backtrace but that's not very friendly. Ideas for improvement:

  • The runtime could force-enable backtrace recording (Printexc.record_backtrace true) to supersede the need for OCAMLRUNPARAM. We can also record our own
    handler to print the file position and/or backtrace in the way we see fit

  • The printer of OCaml code in Catala could insert line directives so that the
    positions in the backtrace actually trace automatically back to the Catala
    code

  • If we don't want to leverage any OCaml machinery in this way, the compiler
    should add position information to any operator that might fail (e.g.
    divisions, date comparisons, etc.).

Note that running in trace mode might already help pinpoint the location of the
error ?

This would deserve a further look into it, but for now it's impeding development
without providing any meaningful security.
This was a pending TODO: now the Catala program compiled into OCaml should
return better messages and a little more information about uncaught exceptions.

Note that this also concerns, at the moment, compiled modules called from the
Catala interpreter: in this case, it's already better than nothing, but what we
need is proper interoperation between the runtime exceptions and the interpreter
handling (`EmptyError` should already be handled properly since it is critical
to the computation flow, but "error" exceptions are left uncaught and will kill
the interpreter).

This may be part of a bigger task on unifying the output of the runtime and
toplevel, which also concerns computation traces.

Note 2: All runtime exceptions don't have a position available, which is quite
unfortunate when your program hits an error. With `OCAMLRUNPARAM=b` and if
compiled with `-g` (which should normally be the case), you can get an OCaml
backtrace but that's not very friendly. Ideas for improvement:
- The runtime could force-enable backtrace recording (`Printexc.record_backtrace
  true`) to supersede the need for `OCAMLRUNPARAM`. We can also record our own
  handler to print the file position and/or backtrace in the way we see fit
- The printer of OCaml code in Catala could insert line directives so that the
  positions in the backtrace actually trace automatically back to the Catala
  code
- If we don't want to leverage any OCaml machinery in this way, the compiler
  should add position information to any operator that might fail (e.g.
  divisions, date comparisons, etc.).
Note that running in trace mode might already help pinpoint the location of the
error ?
Module names must be capitalised (start with a capital letter), and the name of
the file on disk must match ; however, matching up to capitalisation is allowed,
i.e. the file on disk can start with a lowercase letter.

A mismatch between Clerk assuming generated module artifacts would match the
capitalised module name, and `catala depends` matching the names of files on
disk (because it would otherwise mean treating dependencies differently
depending on if they originate from modules or not) was causing "file not found"
errors later on in the compilation chain.

This patch enforces that the capitalisation of the original file name on
disk (which is always known) takes precedence in Clerk, matching the behaviour
of `catala depends` and fixing the issue. It's also actually a small
simplification in Clerk code.
Copy link
Contributor

@denismerigoux denismerigoux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much @AltGr! Nonetheless I still don't understand something, see my comments.

Comment on lines +1145 to +1151
Dynlink.allow_only
(List.filter (( <> ) "Driver__Plugin") (Dynlink.all_units ()));
(* From here on, no plugin registration is allowed. However, the interpreter
may yet use Dynlink to load external modules. - TODO: This used to allow
only "Runtime_ocaml__Runtime", but forbidding external Catala modules to
use the OCaml Stdlib was a bit much. We should examine how to re-add some
more filtering here without being too restrictive. *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This policy sounds more reasonable.

Comment on lines +675 to +679
| exception e ->
Format.ksprintf
(fun s -> raise (CatalaException (Crash s, pos)))
"@[<hv 2>This call to code from a module failed with:@ %s@]"
(Printexc.to_string e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here I don't understand why you're not more precise and match each of the exceptions that the OCaml runtime can raise :

exception EmptyError
exception AssertionFailed of source_position
exception ConflictError of source_position
exception UncomparableDurations
exception IndivisibleDurations
exception ImpossibleDate
exception NoValueProvided of source_position

Something like :

| Runtime_ocaml.NoValueProvided pos -> 
   Message.error ~pos:(runtime_position_to_pos pos) "No value provided here"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we call Printexc.to_string which will resort to the NoValueProvided printer registered here , so the position will be available in the message and we don't gain much with special catching here.


(* TODO: register exception printers for the above
(Printexc.register_printer) *)
(* Register exceptions printers *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so you're going through OCaml's exception printing mechanism, which is more general. Why go through that very general mechanism that forces you to print source code positions in a crude way when you could catch in interpreter.ml the exceptions of Runtime_ocaml properly and access to their positions in a structured way (and that you can print beautifully)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, the position types in the runtime are different from the ones in the compiler, so we would need specific printing functions ; the interpreter doesn't seem like the right place to put those ?

Ideally, like for backtraces, we'd like unified printing for interpreter and runtime errors ; it would probably be best to start with unification of the underlying types. That's ongoing work but I wanted a quick fix: having a message with a usable position was the priority here.

@@ -76,6 +76,7 @@ exception UncomparableDurations
exception IndivisibleDurations
exception ImpossibleDate
exception NoValueProvided of source_position
exception Division_by_zero (* Shadows the stdlib definition *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

@AltGr AltGr merged commit 7820095 into CatalaLang:master Apr 16, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants