RESOLVED FIXED 195131
Fix incorrect handling of try-finally completion values.
https://bugs.webkit.org/show_bug.cgi?id=195131
Summary Fix incorrect handling of try-finally completion values.
Mark Lam
Reported 2019-02-27 16:45:50 PST
Attachments
proposed patch. (2.90 KB, patch)
2019-02-27 16:57 PST, Mark Lam
no flags
work in progress for EWS testing only. (17.52 KB, patch)
2019-02-27 20:02 PST, Mark Lam
no flags
proposed patch. (50.80 KB, patch)
2019-03-06 11:35 PST, Mark Lam
saam: review+
patch for landing. (50.73 KB, patch)
2019-03-06 17:28 PST, Mark Lam
no flags
Mark Lam
Comment 1 2019-02-27 16:57:19 PST
Created attachment 363158 [details] proposed patch.
Mark Lam
Comment 2 2019-02-27 18:37:58 PST
Comment on attachment 363158 [details] proposed patch. Talked to Saam. This fix is incorrect. New patch coming soon.
Mark Lam
Comment 3 2019-02-27 20:02:22 PST
Created attachment 363188 [details] work in progress for EWS testing only.
Mark Lam
Comment 4 2019-03-06 08:38:08 PST
The real bug I was investigating turns out to be due to incorrect handling of completion values of try-finally blocks. The relevant specs: Section 13.15.8: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-try-statement-runtime-semantics-evaluation Section 6.3.2.4: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-updateempty
Mark Lam
Comment 5 2019-03-06 11:35:58 PST
Created attachment 363763 [details] proposed patch.
Saam Barati
Comment 6 2019-03-06 16:45:03 PST
Comment on attachment 363763 [details] proposed patch. View in context: https://bugs.webkit.org/attachment.cgi?id=363763&action=review > Source/JavaScriptCore/ChangeLog:39 > + One might be deceived into thinking that the above example should complete with > + the exception thrown at line 7. However, according to Section 13.15.8 of the > + ECMAScript spec, the 'continue' in the finally at line 9 counts as an abrupt > + completion. As a result, it overrides the throw from line 7. After the continue, > + execution resumes at the top of the top of the loop at line 5, followed by a > + normal completion at line 11. Crazy example. > Source/JavaScriptCore/ChangeLog:60 > + 4. Allocate the FinallyContext on the stack instead of as a member of the > + heap allocated ControlFlowScope. This simplifies its life-cycle management > + and reduces the amount of needed copying. I'm not sure it's worth reverting behavior on this. I feel like it'd be cleaner just to make it an inline field again and use a move constructor to avoid copying the object. > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:201 > +FinallyContext::~FinallyContext() > +{ > + m_completion.typeRegister = nullptr; > + m_completion.valueRegister = nullptr; > +} Why? The default destructor already does this. > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:273 > + // finally need to rethrow. catch does not. make this: Finally needs to rethrow. Catch does not. Alternatively, just remove the comment. > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4814 > + if (hasBreaksOrContinuesNotCoveredByJumps) { name nit: Let's call this something like hasBreaksOrContinuesThatEscapeFinally (or similar). As we discussed in person, renaming may be more apt for a follow-up > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4816 > + emitJumpIf<OpBeloweq>(context.completionTypeRegister(), CompletionType::Throw, isThrowOrNormalLabel.get()); Can you also add static_assert(return > throw && return > normal)? > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:160 > + struct { > + RefPtr<RegisterID> typeRegister; > + RefPtr<RegisterID> valueRegister; > + } m_completion; Why in a struct?
Yusuke Suzuki
Comment 7 2019-03-06 16:47:34 PST
Comment on attachment 363763 [details] proposed patch. View in context: https://bugs.webkit.org/attachment.cgi?id=363763&action=review r=me too, with some suggestions. > Source/JavaScriptCore/ChangeLog:17 > + throw ''; // line 7 OK. This produces [[Type] == throw, but > Source/JavaScriptCore/ChangeLog:19 > + continue; // line 9 This makes Completion of try-finally { continue, undefined }, and execution goes on. > Source/JavaScriptCore/ChangeLog:21 > + } Here, the completion becomes { normal, undefined }. > Source/JavaScriptCore/ChangeLog:22 > + } // line 11 Then, try-finally's completion becomes { return, 42 } because finally block produces normal completion. > Source/JavaScriptCore/ChangeLog:38 > + execution resumes at the top of the top of the loop at line 5, followed by a /the top of the top of the/the top of the/ > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:271 > + for (auto& tuple : m_exceptionHandlersToEmit) { > Ref<Label> realCatchTarget = newLabel(); > + TryData* tryData = std::get<0>(tuple); > + > OpCatch::emit(this, std::get<1>(tuple), std::get<2>(tuple)); > realCatchTarget->setLocation(*this, m_lastInstruction.offset()); > + if (std::get<3>(tuple).isValid()) { tuple hurts readability... can we replace it with a struct? > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4788 > + // completion type to Normal. We can also set the completion value to undefined, unnecessary space after "also". > Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4823 > + // Not Throw means we have a Break or Continue that should be handled by the outer context. > + // These are for Break or Continue completions that have not reached their jump targets > + // yet. The outer context needs to run its finally, and resume the jump outwards (unless > + // interrupted by another abrupt completion). So, we need to pass the completion type to > + // the outer finally. Again, we can skip the completion value because it's not used for > + // Break nor Continue. This means the following code, is it right? I think it would be nice if we can have the example for each emitFinallyCompletion's case. label: { try { try { break label; } finally { // this completion is here. } } finally { // we need to jump here next. } }
Mark Lam
Comment 8 2019-03-06 17:27:50 PST
Comment on attachment 363763 [details] proposed patch. View in context: https://bugs.webkit.org/attachment.cgi?id=363763&action=review Thanks for the reviews. >> Source/JavaScriptCore/ChangeLog:60 >> + and reduces the amount of needed copying. > > I'm not sure it's worth reverting behavior on this. I feel like it'd be cleaner just to make it an inline field again and use a move constructor to avoid copying the object. I still think the current form is easier to reason about. So, I'm going to leave it as is. >> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:201 >> +} > > Why? The default destructor already does this. I agree. Not needed. Will remove. >> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:271 >> + if (std::get<3>(tuple).isValid()) { > > tuple hurts readability... can we replace it with a struct? I'll do this in a follow up patch. >> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4814 >> + if (hasBreaksOrContinuesNotCoveredByJumps) { > > name nit: Let's call this something like hasBreaksOrContinuesThatEscapeFinally (or similar). As we discussed in person, renaming may be more apt for a follow-up Will do in a follow up. >> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4816 >> + emitJumpIf<OpBeloweq>(context.completionTypeRegister(), CompletionType::Throw, isThrowOrNormalLabel.get()); > > Can you also add static_assert(return > throw && return > normal)? I thought about adding this, but the assert we really want is that (all jumpIDs > throw && all jumpIDs > normal). I couldn't think of how to express that in a meaningful way, and ended up leaving out the assert. That said, I think I'll at least add the following to serve as documentation even if it isn't a strong guarantee about jumpIDs. >> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:4823 >> + // Break nor Continue. > > This means the following code, is it right? > I think it would be nice if we can have the example for each emitFinallyCompletion's case. > > label: { > try { > try { > break label; > } finally { > // this completion is here. > } > } finally { > // we need to jump here next. > } > } Yes. That example is correct. I agree that example JS code does help make this code easier to understand. I'll add these in a follow up patch. >> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:160 >> + } m_completion; > > Why in a struct? The spec speaks of a Completion record. This helps communicate that concept and also documents the fact that these 2 registers work together as a pair.
Mark Lam
Comment 9 2019-03-06 17:28:32 PST
Created attachment 363826 [details] patch for landing.
Saam Barati
Comment 10 2019-03-06 18:38:03 PST
Comment on attachment 363763 [details] proposed patch. View in context: https://bugs.webkit.org/attachment.cgi?id=363763&action=review >>> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:160 >>> + } m_completion; >> >> Why in a struct? > > The spec speaks of a Completion record. This helps communicate that concept and also documents the fact that these 2 registers work together as a pair. can you call it m_completionRecord then?
Mark Lam
Comment 11 2019-03-06 21:01:12 PST
(In reply to Saam Barati from comment #10) > Comment on attachment 363763 [details] > proposed patch. > > View in context: > https://bugs.webkit.org/attachment.cgi?id=363763&action=review > > >>> Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:160 > >>> + } m_completion; > >> > >> Why in a struct? > > > > The spec speaks of a Completion record. This helps communicate that concept and also documents the fact that these 2 registers work together as a pair. > > can you call it m_completionRecord then? Will do.
Mark Lam
Comment 12 2019-03-06 21:10:35 PST
Note You need to log in before you can comment on or make changes to this bug.