The Motivating Case
On a recent change of mine it was pointed out that asserts like this
(https://github.com/llvm/llvm-project/blob/24060db3a93297ca77abef8feae301af2e9163b5/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp#L290)
were potentially dangerous.
This assert basically checks that our register information is not corrupted
in such a way that we would read (or in the case of a live process, write)
outside of the buffer we have the register stored in.
If this happened with assert
, an asserts build would fail at that point.
However a release build would simply carry on, which would return bogus results
or in the case of a write, make the situation worse.
Initally I proposed a new macro LLDB_ASSERT_OR.
#ifdef NDEBUG
#define LLDB_ASSERT_OR_RETURN(expr, retval) \
if (!(expr)) \
return retval;
#else
#define LLDB_ASSERT_OR_RETURN(expr, retval) assert(expr);
#endif
If we have asserts on, use the assert. If not, check the condition and if it does
not hold, return some value. In the case of ReadRegister, a Status, elsewhere it
might be false, nullptr, etc.
However, some did argue that given that we know state is corrupted, we shouldnāt
continue at all even in release mode.
Which led me to llvm::report_fatal_error()
, but then, looking at our guidelines
(Contributing - š LLDB)
we have:
Assertions. Assertions (from assert.h) should be used liberally to assert internal consistency.
So in some sense, assert for the mentioned cases is fine because register information
isnāt a user input (not usually at least) and should be consistent.
Also:
Fatal errors. Aborting LLDBās process using llvm::report_fatal_error() or abort() should be avoided at all costs. Itās acceptable to use llvm_unreachable() for actually unreachable code such as the default in an otherwise exhaustive switch statement.
On the general theme of donāt crash the debugger when you could continue and still be
of some use.
I could argue that if you canāt read a register, the debugger is probably not much use
though.
The guidelines for lldbassert say:
New code should not be using lldbassert() and existing uses should be replaced by other means of error handling.
This was added in ā D59911 Don't abort() in lldb_assert and document why. where there was some previous discussion.
Previous Discussion
Some points from there (some editorialisation when abbreviating):
- Pavel is annoyed that he has to write
lldbassert
of a condition, then immediately follow
it withif
that same condition. And wonders if lldbassert can be made to combine the two. - Returning error/result types is better if it can be done. Especially with anything
thatās user input. - Having a way to assert in development builds but log a message on release builds is useful.
The example given is a new DWARF item, this way a user will at least get some hint that
their version of lldb doesnāt support this. - Overusing that would cause a lot of noise, Pavel suggests a construct that reports only once to address this.
lldbassert
is for things we can recover from.- Adrian points out that even when
lldbassert
prints the message, itās not in a place you
can easily see it if itās behind an IDE.
My Current Understanding
Half baked pseudocode ahead. I mostly align with the existing docs except that lldbassert is still hazy for me.
Result Types
Use these when parsing user input, opening files, etc.
llvm::Expected<SomeDWARFAttribute> GetSomeDwarfAttribute(...) {
return llvm error "DWARF DIE should have had SomeDwarfAttribute but nothing was found"
Assertions
Used for internal state, things that lldb makes and constructs all on its own so they should
be correct.
void DoThingWithTarget(TargetSP target) {
assert(target && "Oops, this is supposed to be non-null but couldn't be a ref for some reason")
I would put the aforementioned asserts in core file here too. The things they assert are 90% built into lldb, they are checking that lldbās logic is sound (that 10% is things like variable sized notes).
I suppose one could craft a strange corefile and trip these asserts, but I donāt think that proves much. Said corefile wouldnāt be produced by any standard system.
Logging
Non fatal issues that are easily recovered from and do not result in an unusable debugger.
/* ... Parse some fields from XML register information ... */
if (!some_required_field)
log("Dropping XML entity foo because some_required_field was missing");
/* ... carry on procesing the rest of the XML info ... */
I do this in the register field parsing. Since it is an extra bonus feature itās fine to drop it.
Soft Assertions
Some situation that should be obvious to a developer using an asserts build but will only
log in release builds. Like the example from Phab about some new DWARF type being added.
default:
lldbassert("Found type unknown type value %d", type);
return {};
Where this new type being present wouldnāt cause everything else to break. For example
if we expected to see things in a very specific order, then we might use an assert
or Expected to raise a āproperā error to the user.
This situation seems like the most vague and case by case to me.
Questions
- Should the asserts like the ones in https://github.com/llvm/llvm-project/blob/main/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp#L290 remain
assert
? What would you replace them with if not? - What is the status of
lldbassert
and its eventual replacement? - What would be the goals of said replacement?
Iād like to put a few code/pseudocode examples in the docs eventually, feel free to use some to back up your comments.
I know we are all busy and asserts arenāt the top of anyoneās TODO list but at minimum I can record the conclusions into a tracking issue that can give context to the comments we already have in the documentation.