This is an archive of the discontinued LLVM Phabricator instance.

[Error] Add FileError helper; upgrade StringError behavior
ClosedPublic

Authored by aganea on Aug 15 2018, 1:48 PM.

Details

Summary

Introducing a new error helper, FileError; and upgrading StringError.

FileError

is meant to encapsulate both an Error and a file name/path. It should be used in cases where an Error occurs deep down the call chain, and we want to return it to the caller along with the file name.

Previously, the caller had to know the file name, and in some cases it was unavailable. An example of this could be the loading of a COFF object (OBJ). Those files kinds can contain an indirection to another PDB or OBJ (precompiled headers) file. If that indirection fails to load for some reason, the caller might not have that indirect file name in hand. For those cases, a FileError would be inserted at the right place in the call chain.

Currently FileError logs with the following format: '{file}': {error}, such as: 'test.bin': no such file or directory

StringError

was updated to display the error messages in different ways. These can be:

  1. display the error_code message, and convert to the same error_code (ECError behavior)
  2. display an arbitrary string, and convert to a provided error_code (current StringError behavior)
  3. display both an error_code message and a string, in this order; and convert to the same error_code

These behaviors can be triggered depending on the constructor. The goal is to use StringError as a base class, when a library needs to provide a explicit Error type.
The following illustrates these three cases:

return make_error<CodeViewError>(cv_error_code::corrupt_record); // case 1

return make_error<CodeViewError>(cv_error_code::corrupt_record, "Invalid frame data record format!"); // case 2

return make_error<CodeViewError>("Invalid type index"); // case 3

...CodeViewError being a subtype of StringError only with a minimal implementation:

class CodeViewError : public ErrorInfo<CodeViewError, StringError> {
public:
  using ErrorInfo<CodeViewError, StringError>::ErrorInfo; // inherit constructors
  CodeViewError(const Twine &S) : ErrorInfo(S, cv_error_code::unspecified) {}
  static char ID;
};

Usages are illustrated in D50664 (pending).

Tested with asserts enabled to ensure all Errors are checked in the unit tests.

Diff Detail

Repository
rL LLVM

Event Timeline

aganea created this revision.Aug 15 2018, 1:48 PM

Is it possible for

include/llvm/Support/Error.h
1217–1218 ↗(On Diff #160905)

I wonder if this should have a more general name. There's nothing really specific to Debug Info in this implementation, it's basically just a wrapper around a std::error_code and std::string

1248 ↗(On Diff #160905)

I wonder if we can get away with removing the EC template argument. A std::error_condition is essentially a wrapper around an error code and an error category, so ultimately when we call back into the category to format we're losing the info about the error code type anyway. It doesn't matter what the enum type is, the only thing that matters is that we know which class can format it.

So I think we can just make this an int.

Furthermore, I wonder if we need the ManagedStatic at all. All of these categories are cheap to construct, and I think they are all default constructible anyway, so perhaps our convertToErrorCode() function could just look like:

return std::error_code(Code, ECat{});

In all existing use cases, we never pass the same category with a different error code type, so we already havea 1:1 mapping anyway, and I don't think this is too restrictive. What do you think?

aganea added inline comments.Aug 22 2018, 11:27 AM
include/llvm/Support/Error.h
1217–1218 ↗(On Diff #160905)

Will do. This new DebugErrorInfo class ressembles StringError, however the behavior for printing the message is different.

  • StringError only prints the message
  • DebugErrorInfo prints the message if the error_code is unspecified; otherwise it prints the associated error_code message, along with the user-provided message.
  • Similarily, ECError is in the same problem domain as the two above, except that it always prints its associated error_code message.

I'll see if I can work out a more elegant solution, without adding a new class.

1248 ↗(On Diff #160905)

Agreed, see above comment.

As for convertToErrorCode(), category objects cannot be created on the fly, the reference must be pointing to a singleton, as stated here and here.

The following shows how std::error_category shouldn't be used:

enum class WrongCatError {
  TestError = 1,
};

class WrongErrorCategory final : public std::error_category {
public:
  const char *name() const noexcept override { return "wrong"; }
  std::string message(int condition) const override { return ""; }
};

} // namespace

namespace std {
template <> struct is_error_code_enum<WrongCatError> : std::true_type {};
} // namespace std

namespace {

std::error_code make_error_code(WrongCatError Error) {
  auto E = std::error_code(static_cast<int>(Error), WrongErrorCategory()); // category is a temporary
  return E;
}

std::error_code SubFn() {
  return WrongCatError::TestError; // implicit call to make_error_code in
                                   // std::error_code's constructor
}

std::error_code SubFn2() {
  int a[200] = {0};
  return SubFn();
}

TEST(Error, WrongCategoryUsage) {
  auto E1 = SubFn();
  auto E2 = SubFn2();
  EXPECT_NE(E1, E2); // we fail because WrongErrorCategory is passed as a temporary
}

An example of such misuse can be found in clang/trunk/lib/Frontend/PrecompiledPreamble.cpp:

std::error_code clang::make_error_code(BuildPreambleError Error) {
  return std::error_code(static_cast<int>(Error), BuildPreambleErrorCategory());
}

The standard says that std::error_category shall compare by pointer, which of course wouldn't work in this case. The std::error_code object ends up here with a dangling pointer (at least in my VS2017 15.8 implementation of STL).

Unfortunately, the issue comes from the API to std::error_code which is misleading and cannot express the singleton storage requirement:

class error_code {
...
error_code( int ec, const error_category& ecat ); // 'ecat' is a reference to an error_category that we won't modify
...
}

...when in reality the API contract should say something more like:

class error_code {
...
error_code( int ec, const error_category static& ecat ); // 'ecat' would be a reference to an static error_category that we won't modify
...
}

...which we cannot write evidently.

This issue is vaguely described in P0824R1.

aganea updated this revision to Diff 162936.Aug 28 2018, 1:18 PM
aganea edited the summary of this revision. (Show Details)

Updated following previous comments:

  • Removed DebugErrorInfo and merged behavior into StringError.
  • Moved back ManagedStatic<> into .cpp implementation(s) - see ErrorTest.cpp for example.
  • Updated summary with use-cases.
aganea retitled this revision from [Error] Add FileError and DebugErrorInfo helpers to [Error] Add FileError helper; upgrade StringError behavior.Aug 28 2018, 1:25 PM
zturner accepted this revision.Aug 28 2018, 1:28 PM

Sorry for the delay, fell off my radar. LGTM

This revision is now accepted and ready to land.Aug 28 2018, 1:28 PM
aganea added inline comments.Aug 28 2018, 1:40 PM
include/llvm/Support/Error.h
1143 ↗(On Diff #162936)

I've noticed @lhames mentions here that usage of inconvertibleErrorCode() shall be explicit. I'll change that. Please let me know if there's anything else.

Thank you Zachary.

This revision was automatically updated to reflect the committed changes.