[m-rev.] for review: Introduce io.system_error to io.m public interface.

Julien Fischer jfischer at opturion.com
Mon Aug 22 22:00:53 AEST 2022


Hi Peter,

On Mon, 22 Aug 2022, Peter Wang wrote:

> Implement the error handling proposals from February and August 2022
> on the mercury-users list.

Actually, the August discussion was on the reviews list.

> We add io.system_error to the public interface of io.m
> and document what its foreign representation is for each backend.
>
> We allow io.error to optionally contain an io.system_error value,
> and provide predicates to retrieve the io.system_error from an io.error.
> The user may then inspect the system error via foreign code.
>
> We also provide a predicate that takes an io.error and returns a name
> for the system error it contains (if any). This makes it relatively easy
> for Mercury programs to check for specific error conditions.
>
> By returning platform-specific (actually, implementation-dependent)
> error names, we are pushing the responsibility of mapping strings to
> error conditions onto the application programmer. On the other hand, it
> is not practical for us to map all possible system-specific error codes
> to some common set of values. 
> We could do it for a small set of common error codes/exceptions, perhaps.

I would argue the *if* such errors can be detected across all of our target
platforms, then we should just have more specific result types for the
operations that produce them.

> The standard library will construct io.error values containing
> io.system_errors. However, we do not yet provide a facility for user
> code to do the same.

...

> diff --git a/library/io.m b/library/io.m
> index acf43f53e..0123f8cb1 100644
> --- a/library/io.m
> +++ b/library/io.m
> @@ -130,7 +130,20 @@
>     ;       eof
>     ;       error(string, int). % error message, line number
> 
> -:- type io.error.   % Use error_message to decode it.
> +    % A value indicating an error.

       an I/O error.

> +    % This may or may not have an associated io.system_error value.
> +    %
> +:- type io.error.
> +
> +    % A system-dependent error value.
> +    % For C backends, this is either an errno value (e.g. ENOENT)
> +    % or a Windows system error code (e.g. ERROR_FILE_NOT_FOUND).
> +    % For the Java and C# backends, this is an exception object.
> +    %
> +:- type system_error.
> +:- pragma foreign_type(c, system_error, "MR_Integer").

You can set can_pass_as_mercury_type on that.
(The existing code should have done that.)

> +:- pragma foreign_type("C#", system_error, "System.Exception").
> +:- pragma foreign_type(java, system_error, "java.lang.Exception").
>
>     % whence denotes the base for a seek operation.
>     %   set - seek relative to the start of the file
> @@ -2044,15 +2057,61 @@
> % Interpreting I/O error messages.
> %
> 
> -    % Construct an error code including the specified error message.
> +    % Construct an error value with the specified error message.
> +    % The error value will not have an associated system error.
>     %
> :- func make_io_error(string) = io.error.
> 
> -    % Look up the error message corresponding to a particular error code.
> +    % Return an error message for the error value.
>     %
> :- func error_message(io.error) = string.
> :- pred error_message(io.error::in, string::out) is det.

These will now need to take the I/O state. strerror and FormatMessage depend
on the current locale. That's going to breaking change, although I think we can
live with that, given that most calls to error_message occur in contexts where
the I/O state is available anyway.

Same goes for bunch of things in the implementation.
e.g system_error_errno_message, system_error_win32_error_message -- basically
anything that leads to call to strerror or FormatMessage.

> +    % get_system_error(Error, SystemError):
> +    %
> +    % Succeeds iff SystemError is a system error associated with Error.
> +    %
> +:- pred get_system_error(io.error::in, io.system_error::out) is semidet.
> +
> +    % As above, but only succeeds if the system error is an errno value.
> +    %
> +:- pred get_errno_error(io.error::in, io.system_error::out) is semidet.
> +
> +    % As above, but only succeeds if the system error is a Windows error code.
> +    % XXX ERROR: should this refer to "Win32" error codes instead?

Based on the blurb at the start of their API documentation, Microsoft would
_really_ like you to call it the Windows API, except for all of the places where
they still refer to it as the Win32 API ;-)  I think just Windows is fine.

> +:- pred get_windows_error(io.error::in, io.system_error::out) is semidet.
> +
> +    % As above, but only if the system error is a C# or Java exception object.
> +    % XXX ERROR: how to name this?
> +    %
> +:- pred get_error_exception_object(io.error::in, io.system_error::out)
> +    is semidet.

Given that all the other names have the form get_N_error, I suggest either

    get_exception_error

or:

    get_exception_object_error

...

> +    % get_system_error_name(Error, ErrorName):
> +    %
> +    % Succeeds if Error has an associated system error, otherwise fails.
> +    % On success, ErrorName is a name for that system error as follows.
> +    %
> +    % For C backends, a system error is usually an errno value. If the errno
> +    % value is recognised by the Mercury system, then ErrorName will be the
> +    % name for that errno value as defined in <errno.h>, e.g. "ENOENT".
> +    % Otherwise, ErrorName will be "errno: N" where N is a decimal number.
> +    %
> +    % For C backends on Windows, a system error may instead be a Windows system
> +    % error code. If the error code is recognised by the Mercury system, then
> +    % ErrorName will be the name for that error code in the Win32 API,

s/Win32/Windows/

> +    % e.g. "ERROR_FILE_NOT_FOUND". Otherwise, ErrorName will be
> +    % "System error: 0xN" where 0xN is a hexadecimal number.
> +    %
> +    % For the C# backend, ErrorName will be the the fully qualified class name

Double "the"

> +    % of an exception object, e.g. "System.IO.FileNotFoundException".
> +    %
> +    % For the Java backend, ErrorName will be the fully qualified class name
> +    % of an exception object, e.g. "java.io.FileNotFoundException".
> +    %
> +:- pred get_system_error_name(io.error::in, string::out) is semidet.

...

> -    % make_err_msg(Error, MessagePrefix, Message, !IO):
> -    % Message is an error message obtained by looking up the message for the
> -    % given errno value and prepending MessagePrefix.
> +    % Make an io.error from a message prefix and system error.
> +    % On Windows, the system error is assumed to be a errno value.
>     %
> -:- pred make_err_msg(system_error::in, string::in, string::out,
> -    io::di, io::uo) is det.
> +:- func make_io_error_from_system_error(string, io.system_error) = io.error.
> 
> -    % make_maybe_win32_err_msg(Error, MessagePrefix, Message, !IO):
> +    % Make an io.error from a message prefix and system error.
> +    % On Windows, the system error is assumed to be a Windows system error
> +    % code.

I would add: obtained by calling GetLastError().

> -    % Message is an error message obtained by looking up the error message
> -    % for Error and prepending MessagePrefix.
> -    % On Win32 systems, Error is obtained by calling GetLastError.
> -    % On other systems Error is obtained by reading errno.
> -    %
> -:- pred make_maybe_win32_err_msg(system_error::in, string::in, string::out,
> -    io::di, io::uo) is det.
> +:- func make_io_error_from_maybe_win32_error(string, io.system_error) =
> +    io.error.
>
>     % For use by bitmap.m, and other standard library modules
>     % that want to do I/O.
> @@ -2378,7 +2418,7 @@ io_state_compare(_, _, _) :-
> 
> open_input(FileName, Result, !IO) :-
>     do_open_text(FileName, "r", OpenCount, NewStream, Error, !IO),
> -    is_error(Error, "can't open input file: ", MaybeIOError, !IO),
> +    is_error(Error, "can't open input file: ", MaybeIOError),
>     (
>         MaybeIOError = yes(IOError),
>         Result = error(IOError)

...

> +%---------------------%
> +
> +    % XXX is FormatMessage thread-safe? Nothing suggests that it is not.

That information really ought to be easier to find than it apparently is :-(

> +    %
> +:- pred system_error_win32_error_message(io.system_error::in, string::out)
> +    is det.
> +
> +:- pragma foreign_proc("C",
> +    system_error_win32_error_message(ErrorCode::in, Name::out),
> +    [will_not_call_mercury, promise_pure, thread_safe, may_not_export_body],
> +"

...

> diff --git a/runtime/mercury_win32_error_name.c b/runtime/mercury_win32_error_name.c
> new file mode 100644
> index 000000000..72755462a
> --- /dev/null
> +++ b/runtime/mercury_win32_error_name.c

...

> diff --git a/runtime/mercury_win32_error_name.h b/runtime/mercury_win32_error_name.h
> new file mode 100644
> index 000000000..318dc2e8f

I would be inclined to name these mercury_windows_error_name.[ch].

Julien.


More information about the reviews mailing list