Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

notes on Quirky exceptions article #1

Closed
timotheecour opened this issue Jan 7, 2019 · 1 comment
Closed

notes on Quirky exceptions article #1

timotheecour opened this issue Jan 7, 2019 · 1 comment

Comments

@timotheecour
Copy link

A C++ destructor should not raise an exception as it can be called in an exception handler and then it's not clear what to do, see https://isocpp.org/wiki/faq/exceptions#dtors-shouldnt-throw for more details

this is not accurate AFAIK, there are well defined cases where it's legal to do so since c++11, with noexcept(false) see spec here https://en.cppreference.com/w/cpp/language/destructor
and for more on this, see https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/ ; there are valid use cases.

Furthermore setjmp is an expensive call because it has to store a stack snapshot and this cost is always paid, whether an exception was raised or not.

but on the flip side, what's not mentioned here is that the go-style error handling incurs an extra if branch evaluation for every function in call stack between the raising function and the catching function, which is not the case for setjmp; so which one is more costly depends on factors such as how deeply nested are exceptions, how frequently they throw, and size of snapshot

Other solutions are conceivable too, including a novel static analyis that detects a rule like "the result of procs that can raise must not be written to a heap location"

that's not sufficient, it should also check the proc has no side effect (eg (recursively) calling printf or other C function); and even that's not sufficient, eg with re-entrant code; eg consider this code:

proc ensure(a: bool) = if not a: raise newException("err")
proc factorial(n: int) : int=
  ensure(a>=0)
  if n == 0: 1
  else: fact(n-1) * n
try: factorial(readLine().toInt)
except: echo "invalid input"

and call it with echo -1 | ./main => will give stack overflow with quirky exceptions, even though there aren't side effects (beside setting currentException which I'm assuming you're not treating as side-effect, otherwise any proc that can raise will have by definition a side-effect, making this distinction useless)

In more complex situations it maybe be much harder to detect. I don't even know if it's even tractable in general.

Try the araq-quirky-exceptions branch of Nim, compile your code with --define:nimQuirky and try it for yourself.

overall I'd be quite nervous with a codebase using quirky exceptions. Unless static analysis can guarantee 100% no change in semantics by skipping the implicit returnOnError by identifying any potential side effect (or stack overflow, see above example), I'd need to audit entire codebase to make sure each function call is safe to continue on error.

alternative

I'd be curious about the more traditional (go-style except it'd be implicit) approach code transformation that handles exceptions by inserting if (unlikely(currentException != nullptr)) goto catchBlock; after every proc that can raise, and how it compares performance wise to setjmp approach; at least (in theory) there should be no semantic difference, unlike the quirky exceptions approach.

links

@Araq
Copy link
Owner

Araq commented Jan 7, 2019

In more complex situations it maybe be much harder to detect. I don't even know if it's even tractable in general.

Maybe read it again, I know about these problems, they don't seem "intractable" at all to me.

overall I'd be quite nervous with a codebase using quirky exceptions. Unless static analysis can guarantee 100% no change in semantics by skipping the implicit returnOnError by identifying any potential side effect (or stack overflow, see above example), I'd need to audit entire codebase to make sure each function call is safe to continue on error.

Maybe read it again then. I said "we can port the stdlib to support this mode, if there is enough interest".

And also: I ported a big complex system in one hour and yet you give me some toy fac as a "counterexample" as if I don't understand what I'm writing about.

@Araq Araq closed this as completed Jan 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants