Expectify Rich Polymorphic Error Handling with llvm::Expected

Stefan Gränitz Freelance Dev C++ / Compilers / Audio

C++ User Group Berlin 19. September 2017

No answer for that.. s n tio

E ! S E Y

p e c x

NO Ex

cep ti

ons

Error Handling in the exception-free codebase

Whats the matter? → → → → →

ad-hoc approaches to indicate errors return bool, int, nullptr or std::error_code no concept for context information made for enumerable errors suffer from lack of enforcement

C++ has an answer for this

→ → → → →

type -safe s ad-hoc approaches to indicate errors hand n o i t p lers e c x E return bool, int, nullptr or std::error_code no concept for context information made for errors usenumerable er-defin d suffer from ofeenforcement errolack r types total ent m e c r o f en

How to get these benefits without using exceptions? Error foo(...); Expected bar(...);

Polymorphic Error as a Return Value scheme

Idiomatic usage Error foo(...); // conversion to bool "checks" error if (auto err = foo(...)) return err; // error case // success case

Idiomatic usage Error foo(...); Expected bar(...); foo(...); // unchecked Error triggers abort bar(...); // so does unchecked Expected

strong ent m e c r o f en

Idiomatic usage Error foo(...); Expected bar(...);

Don’t sile

ntly

disappea

r or dupli

(like Exce

ptions)

cate

// errors can only be moved, not copied Error err1 = foo(...); Error err2 = std::move(err1); // ... same for Expected ...

Interface class ErrorInfoBase { public: virtual ~ErrorInfoBase() = default; /// Print an error message to an output stream. virtual void log(std::ostream &OS) const = 0; /// Return the error message as a string. virtual std::string message() const; /// Convert this error to a std::error_code. virtual std::error_code convertToErrorCode() const = 0; };

Implementation class StringError : public ErrorInfo { public: static char ID; StringError(std::string Msg, std::error_code EC); void log(std::ostream &OS) const override; std::error_code convertToErrorCode() const override; const std::string &getMessage() const { return Msg; } private: std::string Msg; std::error_code EC; };

user-de fined error ty pes

Composition Error

ErrorInfoBase *Payload StringError

std::string Message std::error_code ErrorCode

JITSymbolNotFound std::string SymbolName ErrorList ...

Composition Expected

T

std::unique_ptr storage

T

...

Composition Expected

T

std::unique_ptr storage StringError

std::string Message std::error_code ErrorCode

JITSymbolNotFound std::string SymbolName ErrorList ...

Utilities: make_error make_error( "Bad executable", std::make_error_code( std::errc::executable_format_error));

Utilities: type-safe handlers Error foo(...);

type -safe hand lers

handleErrors( foo(...), [](const MyError &err){ ... }, [](SomeOtherError &err){ ... });

Interop with std::error_code std::error_code errorToErrorCode(Error err); Error errorCodeToError(std::error_code ec); → useful when for porting a codebase → similar to Exploding Return Codes https://groups.google.com/forum/#!msg/comp.lang.c++.moderated/BkZqPfoq3ys/H_PMR8Sat4oJ

Example bool simpleExample(); int main() { if (simpleExample()) // ... more code ... return 0; }

Example . expected bool simpleExample() { std::string fileName = "[a*.txt"; Expected pattern = GlobPattern::create(std::move(fileName)); if (auto err = pattern.takeError()) { logAllUnhandledErrors(std::move(err), std::cerr, "[Glob Error] "); return false; } return pattern->match("..."); } Output: [Glob Error] invalid glob pattern: [a*.txt

Example . error_code bool simpleExample() { std::string fileName = "[a*.txt"; GlobPattern pattern; if (std::error_code ec = GlobPattern::create(fileName, pattern)) { std::cerr << "[Glob Error] " << getErrorDescription(ec) << ": "; std::cerr << fileName << "\n"; return false; } return pattern.match("..."); } Output: [Glob Error] invalid_argument: [a*.txt

Example . modified std::error_code simpleExample(bool &result, std::string &errorFileName) { GlobPattern pattern; std::string fileName = "[a*.txt"; if (std::error_code ec = GlobPattern::create(fileName, pattern)) { errorFileName = fileName; return ec; } result = pattern.match("..."); return std::error_code(); }

Example . clever std::error_code simpleExample(bool &result, std::string *&errorFileName) { GlobPattern pattern; std::string fileName = "[a*.txt"; if (std::error_code ec = GlobPattern::create(fileName, pattern)) { errorFileName = new std::string(fileName); return ec; } result = pattern.match("..."); return std::error_code(); }

Example . modified int main() { bool res; std::string *errorFileName = nullptr; // heap alloc in error case if (std::error_code ec = simpleExample(res, errorFileName)) { std::cerr << "[simpleExample Error] " << getErrorDescription(ec) << " "; std::cerr << *errorFileName << "\n"; delete errorFileName; return 0; } // ... more code ... return 0; }

Example . before bool simpleExample() { std::string fileName = "[a*.txt"; Expected pattern = GlobPattern::create(std::move(fileName)); if (auto err = pattern.takeError()) { logAllUnhandledErrors(std::move(err), std::cerr, "[Glob Error] "); return false; } return pattern->match("..."); }

Example . after Expected simpleExample() { std::string fileName = "[a*.txt"; Expected pattern = GlobPattern::create(std::move(fileName)); if (!pattern) return pattern.takeError();

return pattern->match("..."); }

Example . after int main() { Expected res = simpleExample(); if (auto err = res.takeError()) { logAllUnhandledErrors(std::move(err), errs(), "[simpleExample Error] "); return 0; } // ... more code ... return 0; }

Example . before int main() { if ( simpleExample()) { // ... more code ...

}

return 0; }

Performance → Concerned about NRVO when seeing code like this? return std::move(error); → Concerned about returning polymorphic objects? Instead of bool, int, nullptr, std::error_code Yes, or course! We only pay for what we get!

Expected overhead category? ~50ns

~2ns

~3ns

balanced short Branch

close no-inline Call

~6ns

virtual function Call

Heap Allocation

Minimal example . std::error_code __attribute__((noinline)) static std::error_code Minimal_ErrorCode(int successRate, int &res) noexcept { if (fastrand() % 100 > successRate) return std::error_code(9, std::system_category()); res = successRate; return std::error_code(); }

Minimal example . Expected __attribute__((noinline)) static llvm::Expected Minimal_Expected(int successRate) noexcept { if (fastrand() % 100 > successRate) return llvm::make_error( "Error Message", llvm::inconvertibleErrorCode()); return successRate; }

Minimal example

127

Time [ns]

std::error_code Expected

87

47

8

16

16

6 100%

66%

33%

8 0%

Success Rate

Previous example . after

607 533

Time [ns]

std::error_code Expected

435 362 257

52

219

43

100%

66%

33%

0%

Success Rate

Expected vs. error code ✓ avoid vulnerabilities due to missed errors ✓ arbitrarily detailed error descriptions ✓ easily propagate errors up the stack ✓ no performance loss in success case

Differentiation Alexandrescu’s proposed Expected → made for interop with Exceptions (won’t compile with -fno-exceptions) → may pull in implementation-dependent trouble: typedef /*unspecified*/ exception_ptr; → supports Expected where LLVM has Error

Differentiation boost::outcome / std::experimental::expected → interop with exceptions or error codes → expected has error type as template parameter - hard to build handy utilities around it - IMHO same mistake as static exception specifiers bad versionability, bad scalability: http://www.artima.com/intv/handcuffsP.html

→ in progress, currently v2, maybe C++20

llvm::Expected vs. others ✓ works in real code today ✓ supports error concatenation ✓ supports error type hierarchies ✓ great interop with std::error_code for converting APIs ✓ easy to understand, no unnecessary complexity not header-only

Test Idea → → → → → → →

Run a piece of code Count the number N of valid Expected instances Execute the code i = 1..N times Turn the i'th valid instance into an error instance Each error path will be executed Potential issues show up Consider running with AddressSanitizer etc.

Dump Example Expected simpleExample() { std::string fileName = "[a*.txt"; Expected pattern = GlobPattern::create(std::move(fileName));

if (pattern) // success case, frequently taken, good coverage return pattern->match("...");

int x = *(int*)0; // runtime error, unlikely to show up in regular tests return pattern.takeError(); }

Naive Implementation #ifndef NDEBUG template Expected(OtherT &&Val, typename std::enable_if<...>::type * = nullptr) : HasError(false), Unchecked(true) { if (ForceAllErrors::TurnInstanceIntoError()) { HasError = true; new (getErrorStorage()) error_type(ForceAllErrors::mockError()); return; } new (getStorage()) storage_type(std::forward(Val)); } #else ...

Naive Testing int breakInstance = 1..N; ForceAllErrorsInScope FAE(breakInstance); Expected expected = simpleExample(); EXPECT_FALSE(isInSuccessState(expected)); bool success = false; handleAllErrors(expected.takeError(), [&](const ErrorInfoBase &err) { // no specific type information! success = true; }); EXPECT_TRUE(success);

Towards an Error Sanitizer → Mock correct error type - extra info from static analysis → hack Clang - runtime support → extend & link LLVM Compiler-RT → Support cascading errors - if error causes more errors, rerun and break all these too → Avoid breaking instances multiple times - deduplicate according to __FILE__ and __LINE__

Towards an Error Sanitizer → Biggest challenge: Missed side effects can cause false-positive results static int SideEffectValue = 0; llvm::Expected SideEffectExample(bool returnInt) { if (returnInt) return 0; // ESan breaks the instance created here SideEffectValue = 1; // regular errors include this side effect return llvm::make_error("Message"); }

Towards an Error Sanitizer → Opinions welcome! → More news maybe next year

Thks! Questions? LLVM Programmer’s Manual http://llvm.org/docs/ProgrammersManual.html#recoverable-errors Stripped-down Version of LLVM https://github.com/weliveindetail/llvm-expected Series of Blog Posts http://weliveindetail.github.io/blog/ Naive Testing Implementation https://github.com/weliveindetail/llvm-ForceAllErrors

Expectify Rich Polymorphic Error Handling with llvm ... - GitHub

7 days ago - Composition. Expected T std::unique_ptr storage. T .. .... multiple times. - deduplicate according to __FILE__ and __LINE__ ...

641KB Sizes 10 Downloads 246 Views

Recommend Documents

How to make error handling less error-prone - GitHub
“Back to the Future™” is a trademark of Universal City Studios, Inc. and Amblin ... In the Apple OpenSSL “goto fail;” bug, the cleanup code was written incorrectly, ...

Emscripten: An LLVM-to-JavaScript Compiler - GitHub
May 14, 2013 - Emscripten, or (2) Compile a language's entire runtime into ...... html.) • Poppler and FreeType: Poppler12 is an open source. PDF rendering ...

Handling pedigrees - GitHub
A 3-column data.frame or matrix with the codes for each individual and its parents. • A family effect is easily translated into a pedigree: – use the family code as ...

Emscripten: An LLVM-to-JavaScript Compiler - GitHub
Apr 6, 2011 - written in languages other than JavaScript on the web: (1). Compile code ... pile that into JavaScript using Emscripten, or (2) Compile a ... detail the methods used in Emscripten to deal with those ..... All the tests were run on a Len

Mining API Error-Handling Specifications from Source ...
file system failures, and slow system response. As a simple ... error handling, a send procedure, which sends the content of a file across the network as ... tection and recovery. ...... IEEE Standard for Information Technology - Portable Operating.

Off the rich list - GitHub
embed_fonts("demo.pdf", outfile="demo_embed.pdf"). ## Warning: package 'ggplot2' was built under R version 3.2.4. ## Registering fonts with R. 0. 50. 100. 150. 200. 1996. 2000. 2005. 2010. 2015. 0. 500. 1000. 1500. 2000. Number in Russia. Rest of wor

Clean Code “Error Handling” - GitHub
It's your job. Do it. Error handling isn't a curse, or a bother. Things can and do go wrong. Thus, error handling is an essential part of programming. And, since it ...

Dynamic Race Detection with LLVM Compiler - Research at Google
Data races are among the most difficult to detect and costly ... other functions that imply happens-before relations in the real world pro- .... 2.5x on the big tests.

Compiling SML# with LLVM: a Challenge of ...
Jul 22, 2014 - accordingly. Such development requires huge amount of efforts and ... Due to these costs, implementing a custom code generator is now unrealistic. ... We use fastcc calling convention for ML function applications rather than ...

MeqParm: Parameter Handling in the MeqTree System - GitHub
Astronomical Data Analysis Software and Systems XV. P78 ... nected ParmTable, the best solution should be determined from either a default funklet or via ...

OpenMP Support in Clang - LLVM
proposal for syntax analysis, semantic analysis, and AST representation of ... Quick summary of the OpenMP 3.1 API C/C++ syntax and semantics is available.

OpenMP Support in Clang - LLVM
Authors would like to acknowledge valuable advice provided by Hal Finkel [4], ... [4] Hal Finkel, [LLVMdev] [RFC] Parallelization metadata and intrinsics in LLVM.

Polymorphic Systems with Arrays, 2-Counter Machines ...
variables, and call such systems polymorphic systems with arrays (PSAs). For generality and ..... Fundamenta Informaticae 47, 283–294, IOS Press, 2001. ... Parameterized Systems, Proceedings of the 13th International Conference on. 9 ... [21] A.W.

Polymorphic Blending Attacks
801 Atlantic Drive, Atlanta, Georgia 30332 .... instruction reordering, register shuffling, and garbage ... spectrum analysis to evade IDS that use data mining.

CP2K with LIBXSMM - GitHub
make ARCH=Linux-x86-64-intel VERSION=psmp AVX=2. To target for instance “Knights ... //manual.cp2k.org/trunk/CP2K_INPUT/GLOBAL/DBCSR.html).

Java with Generators - GitHub
processes the control flow graph and transforms it into a state machine. This is required because we can then create states function SCOPEMANGLE(node).

Development of nine polymorphic microsatellite ...
the interspersed distribution feature of the L1-like element. .... for a heterozygote excess (HWE test) are given, locus by locus and for all loci, for two populations, ...

[1603-7C1509] Air Handling Unit & Modular Air Handling UnitA4.pdf ...
iOS Version Android Version. Midea CAC After-service Application. iOS Version ... 2001 Cooperated with Copeland to develop the digital scroll VRF system.

Handling Branches in TLS Systems with Multi-Path ...
Figure 2. Normalized speedup with varying misprediction rate for sequential execution and for TLS systems with 2,. 4, and 8 cores. Speedups are normalized to ...

OpenBMS connection with CAN - GitHub
Arduino with BMS- and CAN-bus shield as BMS a master. - LTC6802-2 or LTC6803-2 based boards as cell-level boards. - CAN controlled Eltek Valere as a ...

Better performance with WebWorkers - GitHub
Chrome52 on this Laptop. » ~14kbyte. String => 133ms ... 3-4 Seks processing time on samsung galaxy S5 with crosswalk to finish the transition with ... Page 17 ...

Data-capable network prioritization with reject code handling
Mar 27, 2009 - System”, Non-Access Stratum functions related to Mobile Station. (MS) in idle mode, .... calls and/or sending and receiving data over a wireless com munication .... 3 portable e-mail devices). In particular, a GPRS/GSM-capable networ