This is an archive of the discontinued LLVM Phabricator instance.

PECOFF: consume errors properly
ClosedPublic

Authored by compnerd on Apr 5 2023, 3:53 PM.

Details

Summary

We would not ensure that the error is consumed in the case that logging
is disabled. Ensure that we properly drop the error on the floor or we
would re-trigger the checked failure.

Diff Detail

Event Timeline

compnerd created this revision.Apr 5 2023, 3:53 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 5 2023, 3:53 PM
compnerd requested review of this revision.Apr 5 2023, 3:53 PM
bulbazord added inline comments.Apr 5 2023, 3:57 PM
lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
869–873

If logging is enabled, we are moving from the same object twice, no?

I think we should rethink the LLDB_LOG macro when it comes to errors.... :/

compnerd added inline comments.Apr 5 2023, 3:58 PM
lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
869–873

Yeah ... I was worried about that.

compnerd added inline comments.Apr 5 2023, 4:04 PM
lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
869–873

I should mention that at least on MSVC I _can_ get away with it:

ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found 
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
ObjectFilePECOFF::AppendFromExportTable - failed to get export table entry name: RVA 0x0 for export ordinal table not found
compnerd added inline comments.Apr 6 2023, 10:42 AM
lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
869–873

https://godbolt.org/z/nj4r7K8hb

UBSAN also seems to indicate it is permissible.

bulbazord added inline comments.Apr 6 2023, 11:07 AM
lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
869–873

Moving from a standard library type leaves the original object in a "valid but unspecified" state. Do we make such guarantees about llvm::Errors? This is probably going to work "correctly" but I'm not sure what might go wrong.

sgraenitz requested changes to this revision.Apr 6 2023, 3:29 PM
sgraenitz added a subscriber: alvinhochun.

First of all, yes the existing code is incorrect. Thanks for looking into this. However, I am afraid this isn't the right solution.

In the if body you can not std::move() from err twice. I guess we have to do something like:

if (log)
  LLDB_LOG(...)
else
  consumeError(...)

which resulted in a check failure under LLVM_ENABLE_ABI_BREAKING_CHANGES

You probably mean LLVM_ENABLE_ABI_BREAKING_CHECKS? For Error this enables logic to make sure the error was checked: https://github.com/llvm/llvm-project/blob/release/16.x/llvm/include/llvm/Support/Error.h#L724 I think your observation is a side effect of the issue you look at and has nothing to do with compiler optimizations like NRVO. If logging is off, the error isn't consumed.

This revision now requires changes to proceed.Apr 6 2023, 3:30 PM

Thanks for adding me in the loop. And thanks for looking into the fix, though this bit is a bit confusing to me:

Ensure that we explicitly indicate that we would like the move semantics
in the assignment. With MSVC 14.35.32215, the assignment would not
trigger a NVRO and copy constructor would be invoked

From https://llvm.org/doxygen/classllvm_1_1Error.html, llvm::Error has both copy constructor and copy assignment deleted, so I don't understand how a copy constructor could be invoked? I believe I followed https://llvm.org/docs/ProgrammersManual.html#recoverable-errors in handling the error, so if not having std::move there is really an issue, that page should also be updated.

But it is probably like @sgraenitz said, it is just an effect of the error not being consumed when logging is disabled.

If logging is enabled, we are moving from the same object twice, no?

In the if body you can not std::move() from err twice.

The doc comment of the move constructor/assignment does say "original error becomes a checked Success value, regardless of its original state" and it is exactly what happens in the code, so moving from the same error twice should be fine.

First of all, yes the existing code is incorrect. Thanks for looking into this. However, I am afraid this isn't the right solution.

You probably mean LLVM_ENABLE_ABI_BREAKING_CHECKS? For Error this enables logic to make sure the error was checked: https://github.com/llvm/llvm-project/blob/release/16.x/llvm/include/llvm/Support/Error.h#L724 I think your observation is a side effect of the issue you look at and has nothing to do with compiler optimizations like NRVO. If logging is off, the error isn't consumed.

Yes, I did indeed mean checks and not changes. I had originally tried with only the consumption in the non-logging case, but that still seemed to abort similarly. Adding the std::move on the assignment for the conditional was also required for some reason.

I do wonder if we should keep the consume to be unconditional or not as @alvinhochun points out that llvm::Error does guarantee this behaviour.

First of all, yes the existing code is incorrect. Thanks for looking into this. However, I am afraid this isn't the right solution.

You probably mean LLVM_ENABLE_ABI_BREAKING_CHECKS? For Error this enables logic to make sure the error was checked: https://github.com/llvm/llvm-project/blob/release/16.x/llvm/include/llvm/Support/Error.h#L724 I think your observation is a side effect of the issue you look at and has nothing to do with compiler optimizations like NRVO. If logging is off, the error isn't consumed.

Yes, I did indeed mean checks and not changes. I had originally tried with only the consumption in the non-logging case, but that still seemed to abort similarly. Adding the std::move on the assignment for the conditional was also required for some reason.

Hmm, now I'm wondering if I had run the wrong binary, because I do remember that I somehow ended up running the wrong binary once or twice. Let's go with removing the confusing std::move in the if because that _shouldn't_ be needed.

compnerd updated this revision to Diff 511706.Apr 7 2023, 9:40 AM
compnerd retitled this revision from PECOFF: enforce move semantics and consume errors properly to PECOFF: consume errors properly.
compnerd edited the summary of this revision. (Show Details)

I think it's a very good practice that objects in moved-from state will only ever get destroyed. While the move ctor itself has no preconditions and thus might be fine to call again, the implementation of consumeError() does inspect the object. This adds an unnecessary risk for UB. Please simply check the log state and call either fmt_consume() or consumeError(). Thanks.

compnerd updated this revision to Diff 513614.Apr 14 2023, 8:40 AM

Address feedback

sgraenitz accepted this revision.Apr 14 2023, 10:13 AM

Great. Thanks!

This revision is now accepted and ready to land.Apr 14 2023, 10:13 AM