Debugging
=========
Hare supports a number of features and standards to aid in debugging. Hare
programs are built with debugging features enabled by default; to disable them
pass the -R ("release") flag to ``hare build``.
Debug symbols
-------------
Hare has limited support for `DWARFv5`_ debug symbols, which is compatible with
a number of popular debugging tools, such as gdb. Hare supports mapping
addresses to line numbers and source files, as well as basic stack unwinding.
Getting information about type details and locals is not supported.
.. _DWARFv5: https://dwarfstd.org/
.. note::
Hare identifiers such as ``hash::fnv`` are rewritten to use dots in place of
namespace separators, so this example becomes ``hash.fnv``.
Runtime errors
--------------
The following error cases are detected by the Hare runtime. For programs built
in debug mode, a stack trace is shown when an error is detected.
* Buffer overflows/out-of-bounds read or write
* Heap corruption
* Invalid/double free
* Use-after free [#besteffort]_
* Out of memory errors [#overcommit]_
* Type assertion failures
* Unhandled errors
* Errors reported by signals, such as: [#debugonly]_
* Arithmetic errors (e.g. divide by zero)
* Bus errors (e.g. unaligned writes)
* Illegal instructions
* Segmentation faults (e.g. null pointer dereference)
* Stack overflows
.. danger::
Hare provides escape hatches which can be used to circumvent these runtime
checks, such as arrays of indefinite length (e.g. ``[*]int``). When using
these features, the programmer assumes responsibility for ensuring the
correctness of their code.
.. rubric:: Caveats
.. [#besteffort] Handled on a best-effort basis. It is not always possible for
the runtime to detect these errors. Note also that memory error detection is
unavailable when linking Hare programs with libc, as we use libc's memory
allocator instead of managing memory ourselves.
.. [#overcommit] On systems with overcommit (such as Linux), memory allocations
may appear successful at the time of allocation, but cause unexpected
problems at the time of use. There is not much Hare can or should do about
that.
.. [#debugonly] Only detected by the runtime in debug mode. In release mode
these errors are handled by the operating system's default signal handler.
.. raw:: html
Slice or array out of bounds
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The "slice or array out of bounds" message is a runtime assertion which occurs
when accessing an index of a slice or array which falls outside of its defined
length.
This error message indicates that the programmer has made a design error.
.. code-block:: hare
export fn main() void = {
let x: [3]int = [1, 2, 3];
x[0]; // okay
x[1]; // okay
x[2]; // okay
x[3]; // abort
x[0..2]; // okay
x[0..10]; // abort
};
This can occur when using:
* Indexing expressions: ``x[4]``
* Slicing expressions: ``x[2..4]``
* Slice assignment expressions: ``x[..] = y[..]``
* Insert or delete expressions
.. note::
When using an insert expression, it is legal to insert a value at the end of
a slice, such that the index equals *len(slice)*. This index is not valid
in any other kind of expression. The first index which will cause this
runtime assertion to occur when using an insert expression is *len(slice)+1*.
Recommended solutions
*********************
* Ensure that you are computing indices correctly. For example, look for
off-by-one errors. Note that slices and arrays in Hare are zero-indexed, so
the first item is at 0 and the last item is at *len(x) - 1*.
* Test that your index is less than the length of the object before accessing a
value at that index:
.. code-block:: hare
let x: [3]int = [0...];
let ix = 10z;
if (ix < len(x)) {
x[ix]; // okay
};
.. raw:: html
Type assertion failed
~~~~~~~~~~~~~~~~~~~~~
The "type assertion failed" message is a runtime assertion which occurs when
the **as** operator is used on a tagged union value which is storing a value of
a type which differs from the right-hand side of the **as** operation.
This message indicates that the programmer has made a design error.
.. code-block:: hare
export fn main() void = {
let x: (int | uint) = 10i; // value is of type int
x as int; // okay
x as uint; // abort
};
Recommended solutions
*********************
* Determine why the value was of a different type than the programmer expected
at this point.
* Replace the **as** expression with a match expression which handles more
possible cases.
.. code-block:: hare
// Before:
let x: (int | uint) = 10i;
x as uint; // Will fail if x is an int
// After:
let x: (int | uint) = 10i;
match (x) {
case let x: int =>
// x is an int
case let x: uint =>
// x is a uint
};
.. raw:: html
Out of memory
~~~~~~~~~~~~~
The "out of memory" message is a runtime assertion which occurs when a memory
allocation fails without being handled by the programmer.
.. code-block:: hare
export fn main() void = {
let x: *int = alloc(42); // might fail
let x: []int = alloc([1, 2, 3]); // might fail
append(x, 42); // might fail
insert(x[0], 42); // might fail
};
Recommended solutions
*********************
* If using the **alloc** expression, use a *nullable* pointer type. In the
event of an allocation failure, the value will be null and this runtime
assertion will not occur.
.. warning::
Improving support for managing out-of-memory conditions in Hare programs is
a design area planned for future improvement.
.. raw:: html
Static insert/append exceeds slice capacity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The "static insert/append exceeds slice capacity" message is a runtime
assertion which occurs when a **static append** or **static insert** operation
would exceed the capacity of the slice. When using static insert and append,
the programmer must ensure that their program will not exceed the amount of
space allocated for the underlying slice.
This message indicates that the programmer has made a design error.
.. code-block:: hare
export fn main() void = {
let x: [4]int = [0...];
let y: []int = x[..0];
static append(x, 1);
static append(x, 2);
static append(x, 3);
static append(x, 4);
static append(x, 5); // abort
};
Recommended solutions
*********************
* Test that the capacity is sufficient before performing an insert or append,
and handle the case where there is insufficient space.
* Increase the size of the underlying array.
* If better suited to your use-case, switch to dynamically allocated slices
(i.e. drop the **static** keyword).
.. warning::
Hare will not automatically move stack- or statically-allocated slices to
the heap when they are passed to **append** et al; attempting to do so will
crash your program with a memory error. You must either heap-allocate the
slice from the start, or allocate a copy of the original slice, if you want
to change to heap-allocated slices.
.. raw:: html
Execution reached unreachable code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The "execution reached unreachable code" message is a runtime assertion which
occurs when the program reaches an area in the generated code which was thought
to be impossible by the compiler designers. This is a safeguard put in place by
the compiler to avoid introducing vulnerabilities or unexpected behavior if
such situations were to arise anyway.
The most common cause of this error is that the programmer failed to consider
all possible cases when writing a match expression. Currently the Hare compiler
does not perform exhaustivity analysis on match expressions at compile-time;
this error can be raised at runtime instead.
This can also occur in situations where a variable used in match or switch
expression has an illegal value. If you cast an integer to an enum type, the
language does not test that the final value is a valid value for that
enumeration; switching against this value may lead to this error. Similarly, if
you create an invalid tagged union (through pointer manipulation, for example),
this error may occur when using a match expression. It may also occur in
similar edge cases, such as in the case of stack or heap corruption. Most of
these cases are rare, unless your code is doing something unusual the most
likely explanation is a non-exhaustive match expression or an invalid enum
cast.
Otherwise, this message indicates that a bug is present in the Hare compiler.
Please report it to the `hare-users`_ mailing list.
.. _hare-users: https://lists.sr.ht/~sircmpwn/hare-users
.. code-block:: hare
export fn main() void = {
let x: (u8 | u16 | u32 | u64) = 0u64;
match (x) {
case u8 => // ...
case u16 => // ...
case u32 => // ...
};
};
Recommended solutions
*********************
* Double check that your match expression handles all possible cases
* Check that the objects being matched or switched against have sane values
* Otherwise, report the issue upstream
.. raw:: html
Slice allocation capacity smaller than initializer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The "slice allocation capacity smaller than initializer" message is a runtime
assertion which occurs when allocating a slice with both an initializer and a
desired capacity using an **alloc** expression if the provided initializer has
a greater length than the desired capacity.
This message indicates that the programmer has made a design error.
.. code-block:: hare
export fn main() void = {
let capacity = 3z;
let x: []int = alloc([1, 2, 3, 4, 5], capacity); // abort
};
Recommended solutions
*********************
* Ensure that the initializer has no excess elements
* Increase the requested capacity
Error occurred
~~~~~~~~~~~~~~
The "error occurred" message is a runtime assertion which occurs when the error
assertion operator (``!``) is used and an error occurred at runtime.
This message indicates that the programmer has made a design error.
.. code-block:: hare
use fmt;
// Use `hare run main.ha >/dev/full` to simulate this error
export fn main() void = {
fmt::println("Hello world!")!; // abort
};
Recommended solutions
*********************
* Determine why an error occurred where one was not expected
* Use a match expression to handle the possible error cases explicitly
* Use the error propagation operator (``?``) and update the return type to pass
the error to the function which called the relevant code
.. note::
Using ``haredoc`` to look up the function which produced the error is a good
way to quickly find out what possible outcomes you might need to handle.