Changeset 249566 in webkit
- Timestamp:
- Sep 6, 2019 1:14:08 AM (5 years ago)
- Location:
- trunk
- Files:
-
- 17 added
- 4 deleted
- 5 edited
- 2 copied
- 2 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r249565 r249566 1 2019-09-05 Joseph Pecoraro <pecoraro@apple.com> 2 3 Tail Deleted Frames shown in Web Inspector are sometimes incorrect (Shadow Chicken) 4 https://bugs.webkit.org/show_bug.cgi?id=201366 5 6 Reviewed by Saam Barati. 7 8 * inspector/debugger/tail-deleted-frames-expected.txt: Removed. 9 * inspector/debugger/tail-deleted-frames-from-vm-entry-expected.txt: Removed. 10 * inspector/debugger/tail-deleted-frames-from-vm-entry.html: Removed. 11 * inspector/debugger/tail-deleted-frames-this-value-expected.txt: Removed. 12 * inspector/debugger/tail-deleted-frames-this-value.html: Removed. 13 * inspector/debugger/tail-deleted-frames.html: Removed. 14 Remove legacy tests that are difficult to read. 15 16 * inspector/debugger/tail-deleted-frames/resources/stack-trace-utilities.js: Added. 17 (TestPage.registerInitializer.window.getAsyncStackTrace): 18 (TestPage.registerInitializer.async.logThisObject): 19 (TestPage.registerInitializer.async.logScope): 20 (TestPage.registerInitializer.async.logCallFrame): 21 (TestPage.registerInitializer): 22 * inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-intermediate-frames.js: Added. 23 * inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-intermediate-native-tail-deleted-calls.js: Added. 24 * inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-intermediate-tail-deleted-frames.js: Added. 25 * inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-scopes.js: Added. 26 * inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-this-value.js: Added. 27 * inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-vm-entry.js: Added. 28 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-intermediate-frames-expected.txt: Added. 29 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-intermediate-frames.html: Added. 30 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-intermediate-tail-deleted-frames-expected.txt: Added. 31 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-intermediate-tail-deleted-frames.html: Added. 32 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-scopes-expected.txt: Added. 33 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-scopes.html: Added. 34 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-this-value-expected.txt: Added. 35 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-this-value.html: Added. 36 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-vm-entry-expected.txt: Added. 37 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-vm-entry.html: Added. 38 Include modern tests that are easier to read. 39 40 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-intermediate-native-tail-deleted-calls-expected.txt: Added. 41 * inspector/debugger/tail-deleted-frames/tail-deleted-frames-intermediate-native-tail-deleted-calls.html: Added. 42 Include a test that is known to produce bad output, since we have reproductive steps. 43 44 * platform/mac/TestExpectations: 45 Updated pathes. 46 1 47 2019-09-06 Andres Gonzalez <andresg_22@apple.com> 2 48 -
trunk/LayoutTests/inspector/debugger/evaluateOnCallFrame-exception.html
r239753 r249566 14 14 const returnByValue = true; 15 15 16 InspectorTest.debug();17 16 let suite = InspectorTest.createAsyncSuite("Debugger.evaluateOnCallFrame.Exception"); 18 17 -
trunk/LayoutTests/inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-intermediate-native-tail-deleted-calls.js
r249565 r249566 2 2 function a() { 3 3 let x = 20; 4 x;4 debugger; 5 5 return x; 6 6 } … … 11 11 function c() { 12 12 let z = 60; 13 return b(); 13 return b(); 14 14 } 15 15 function startABC() { 16 for (let i = 0; i < 5; ++i) 17 THIS_DOES_NOT_CALL_c(); 16 18 c(); 17 19 } 20 function THIS_DOES_NOT_CALL_c() { 21 return Math.random(); 22 } -
trunk/LayoutTests/inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-scopes.js
r249565 r249566 2 2 function a() { 3 3 let x = 20; 4 x;4 debugger; 5 5 return x; 6 6 } … … 11 11 function c() { 12 12 let z = 60; 13 return b(); 13 return b(); 14 14 } 15 15 function startABC() { -
trunk/LayoutTests/inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-this-value.js
r249565 r249566 2 2 function a() { 3 3 let x = 20; 4 x;4 debugger; 5 5 return x; 6 6 } 7 7 function b() { 8 8 let y = 40; 9 return a ();9 return a.call({aThis: 2}); 10 10 } 11 11 function c() { 12 12 let z = 60; 13 return b ();13 return b.call({bThis: 1}); 14 14 } 15 15 function startABC() { 16 c ();16 c.call({cThis: 0}); 17 17 } -
trunk/LayoutTests/inspector/debugger/tail-deleted-frames/resources/tail-deleted-frames-vm-entry.js
r249565 r249566 6 6 if (i > 0) 7 7 return bar(i - 1); 8 return 25; 8 debugger; 9 return 99; 9 10 } -
trunk/LayoutTests/platform/mac/TestExpectations
r249561 r249566 1060 1060 webkit.org/b/167711 [ Debug ] inspector/debugger/probe-manager-add-remove-actions.html [ Slow ] 1061 1061 webkit.org/b/168399 [ Debug ] inspector/debugger/search-scripts.html [ Pass Timeout ] 1062 webkit.org/b/181952 [ Debug ] inspector/debugger/tail-deleted-frames-from-vm-entry.html [ Slow ] 1063 webkit.org/b/169119 [ Debug ] inspector/debugger/tail-deleted-frames-this-value.html [ Pass Timeout ] 1062 webkit.org/b/181952 [ Debug ] inspector/debugger/tail-deleted-frames/tail-deleted-frames-vm-entry.html [ Slow ] 1064 1063 webkit.org/b/168387 [ Debug ] inspector/debugger/tail-recursion.html [ Pass Timeout ] 1065 1064 webkit.org/b/170127 inspector/dom-debugger/dom-breakpoints.html [ Pass Timeout ] -
trunk/Source/JavaScriptCore/ChangeLog
r249556 r249566 1 2019-09-05 Joseph Pecoraro <pecoraro@apple.com> 2 3 Tail Deleted Frames shown in Web Inspector are sometimes incorrect (Shadow Chicken) 4 https://bugs.webkit.org/show_bug.cgi?id=201366 5 6 Reviewed by Saam Barati. 7 8 It is possible for the log buffer to be full right as someone is trying to 9 log a function prologue. In such a case the machine stack has already been 10 updated to include the new JavaScript call frame, but the prologue packet 11 cannot be included in the update because the log is full. This would mean 12 that the update fails to rationalize the machine stack with the shadow 13 log / stack. Namely, the current JavaScript call frame is unable to 14 find a matching prologue (the one we are holding to include after the update) 15 and inserts a questionable value into the stack; and in the process 16 missing and removing real potential tail calls. 17 18 For example: 19 20 "use strict"; 21 function third() { return 1; } 22 function second() { return third(); } 23 function first() { return second(); } 24 function start() { return first(); } 25 26 If the the log fills up just as we are entering `b` then we may have a list 27 full log of packets looking like: 28 29 Shadow Log: 30 ... 31 { prologue-packet: entering `start` ... } 32 { prologue-packet: entering `first` ... } 33 { tail-packet: leaving `first` with a tail call } 34 35 Incoming Packet: 36 { prologue-packet: entering `second` ... } 37 38 Current JS Stack: 39 second 40 start 41 42 Since the Current JavaScript stack already has `second`, if we process the 43 log without the prologue for `second` then we push a confused entry on the 44 shadow stack and clear the log such that we eventually lose the tail-call 45 information for `first` to `second`. 46 47 This patch solves this issue by providing enough extra space in the log 48 to always process the incoming packet when that forces an update. This way 49 clients can continue to behave exactly as they are. 50 51 -- 52 53 We also document a corner case in some circumstances where the shadow 54 log may currently be insufficient to know how to reconcile: 55 56 For example: 57 58 "use strict"; 59 function third() { return 1; } 60 function second() { return third(); } 61 function first() { return second(); } 62 function doNothingTail() { return Math.random() } 63 function start() { 64 for (i=0;i<1000;++i) doNothingTail(); 65 return first(); 66 } 67 68 In this case the ShadowChicken log may be processed multiple times due 69 to the many calls to `doNothingTail` / `Math.random()`. When calling the 70 Native function no prologue packet is emitted, so it is unclear that we 71 temporarly go deeper and come back out on the stack, so the log appears 72 to have lots of doNothingTail calls reusing the same frame: 73 74 Shadow Log: 75 ... 76 , [123] {callee = 0x72a21aee0, frame = 0x7ffeef897270, callerFrame = 0x7ffeef8972e0, name = start} 77 , [124] {callee = 0x72a21af10, frame = 0x7ffeef8971f0, callerFrame = 0x7ffeef897270, name = doNothingTail} 78 , [125] tail-packet:{frame = 0x7ffeef8971f0} 79 , [126] {callee = 0x72a21af10, frame = 0x7ffeef8971f0, callerFrame = 0x7ffeef897270, name = doNothingTail} 80 , [127] tail-packet:{frame = 0x7ffeef8971f0} 81 ... 82 , [140] {callee = 0x72a21af10, frame = 0x7ffeef8971f0, callerFrame = 0x7ffeef897270, name = doNothingTail} 83 , [141] tail-packet:{frame = 0x7ffeef8971f0} 84 , [142] {callee = 0x72a21af10, frame = 0x7ffeef8971f0, callerFrame = 0x7ffeef897270, name = doNothingTail} 85 , [143] tail-packet:{frame = 0x7ffeef8971f0} 86 , [144] {callee = 0x72a21aeb0, frame = 0x7ffeef8971f0, callerFrame = 0x7ffeef897270, name = first} 87 , [145] tail-packet:{frame = 0x7ffeef8971f0} 88 , [146] {callee = 0x72a21ae80, frame = 0x7ffeef8971f0, callerFrame = 0x7ffeef897270, name = second} 89 ... 90 91 This log would seem to be indistinguishable from real tail recursion, such as: 92 93 "use strict"; 94 function third() { return 1; } 95 function second() { return third(); } 96 function first() { return second(); } 97 function doNothingTail(n) { 98 return n ? doNothingTail(n-1) : first(); 99 } 100 function start() { 101 return doNothingTail(1000); 102 } 103 104 Likewise there are more cases where the shadow log appears to be ambiguous with determining 105 the appropriate parent call frame with intermediate function calls. In practice this may 106 not be too problematic, as this is a best effort reconstruction of tail deleted frames. 107 It seems likely we would only show additional frames that did in fact happen serially 108 between JavaScript call frames, but may not actually be the proper parent frames 109 heirachy in the stack. 110 111 * interpreter/ShadowChicken.cpp: 112 (JSC::ShadowChicken::Packet::dump const): 113 (JSC::ShadowChicken::Frame::dump const): 114 (JSC::ShadowChicken::dump const): 115 Improved debugging output. Especially for functions. 116 117 (JSC::ShadowChicken::ShadowChicken): 118 Make space in the log for 1 additional packet to process when we slow log. 119 120 (JSC::ShadowChicken::log): 121 Include this packet in our update. 122 123 (JSC::ShadowChicken::update): 124 Address an edge case where we can eliminate tail-deleted frames that don't make sense. 125 1 126 2019-09-05 Mark Lam <mark.lam@apple.com> 2 127 -
trunk/Source/JavaScriptCore/interpreter/ShadowChicken.cpp
r232983 r249566 46 46 47 47 if (isPrologue()) { 48 String name = "?"_s; 49 if (auto* function = jsDynamicCast<JSFunction*>(callee->vm(), callee)) { 50 name = function->name(callee->vm()); 51 if (name.isEmpty()) 52 name = "?"_s; 53 } 54 48 55 out.print( 49 56 "{callee = ", RawPointer(callee), ", frame = ", RawPointer(frame), ", callerFrame = ", 50 RawPointer(callerFrame), " }");57 RawPointer(callerFrame), ", name = ", name, "}"); 51 58 return; 52 59 } … … 63 70 void ShadowChicken::Frame::dump(PrintStream& out) const 64 71 { 72 String name = "?"_s; 73 if (auto* function = jsDynamicCast<JSFunction*>(callee->vm(), callee)) { 74 name = function->name(callee->vm()); 75 if (name.isEmpty()) 76 name = "?"_s; 77 } 78 65 79 out.print( 66 "{callee = ", RawPointer(callee), ", frame = ", RawPointer(frame), ", isTailDeleted = ",67 isTailDeleted, " }");80 "{callee = ", *callee, ", frame = ", RawPointer(frame), ", isTailDeleted = ", 81 isTailDeleted, ", name = ", name, "}"); 68 82 } 69 83 … … 71 85 : m_logSize(Options::shadowChickenLogSize()) 72 86 { 73 m_log = static_cast<Packet*>(fastZeroedMalloc(sizeof(Packet) * m_logSize)); 87 // Allow one additional packet beyond m_logEnd. This is useful for the moment we 88 // log a packet when the log is full and force an update. At that moment the packet 89 // that is being logged should be included in the update because it may be 90 // a critical prologue needed to rationalize the current machine stack with the 91 // shadow stack. 92 m_log = static_cast<Packet*>(fastZeroedMalloc(sizeof(Packet) * m_logSize + 1)); 74 93 m_logCursor = m_log; 75 94 m_logEnd = m_log + m_logSize; … … 83 102 void ShadowChicken::log(VM& vm, ExecState* exec, const Packet& packet) 84 103 { 104 // This write is allowed because we construct the log with space for 1 additional packet. 105 *m_logCursor++ = packet; 85 106 update(vm, exec); 86 *m_logCursor++ = packet;87 107 } 88 108 … … 143 163 } 144 164 145 146 165 if (ShadowChickenInternal::verbose) 147 166 dataLog(" Revised stack: ", listDump(m_stack), "\n"); … … 289 308 290 309 CallFrame* callFrame = visitor->callFrame(); 291 if (ShadowChickenInternal::verbose) 292 dataLog(" Examining ", RawPointer(callFrame), "\n"); 310 if (ShadowChickenInternal::verbose) { 311 dataLog(" Examining callFrame:", RawPointer(callFrame), ", callee:", RawPointer(callFrame->jsCallee()), ", callerFrame:", RawPointer(callFrame->callerFrame()), "\n"); 312 JSObject* callee = callFrame->jsCallee(); 313 if (auto* function = jsDynamicCast<JSFunction*>(callee->vm(), callee)) 314 dataLog(" Function = ", function->name(callee->vm()), "\n"); 315 } 316 293 317 if (callFrame == highestPointSinceLastTime) { 294 318 if (ShadowChickenInternal::verbose) 295 dataLog(" Bailing at ", RawPointer(callFrame), " because it's the highest point since last time.\n"); 319 dataLog(" Bailing at ", RawPointer(callFrame), " because it's the highest point since last time\n"); 320 321 // FIXME: At this point the shadow stack may still have tail deleted frames 322 // that do not run into the current call frame but are left in the shadow stack. 323 // Those tail deleted frames should be validated somehow. 324 296 325 return StackVisitor::Done; 297 326 } … … 319 348 && m_log[indexInLog].frame == toPush.last().frame) { 320 349 if (ShadowChickenInternal::verbose) 321 dataLog(" Going to loop through to find tail deleted frames with indexInLog = ", indexInLog, " and push-stack top = ", toPush.last(), "\n");350 dataLog(" Going to loop through to find tail deleted frames using ", RawPointer(callFrame), " with indexInLog = ", indexInLog, " and push-stack top = ", toPush.last(), "\n"); 322 351 for (;;) { 323 352 ASSERT(m_log[indexInLog].frame == toPush.last().frame); … … 341 370 } 342 371 indexInLog--; // Skip over the tail packet. 372 373 // FIXME: After a few iterations the tail packet referenced frame may not be the 374 // same as the original callFrame for the real stack frame we started with. 375 // It is unclear when we should break. 343 376 344 377 if (!advanceIndexInLogTo(tailPacket.frame, nullptr, nullptr)) { … … 380 413 381 414 if (ShadowChickenInternal::verbose) 382 dataLog(" After pushing: ", *this, "\n");415 dataLog(" After pushing: ", listDump(m_stack), "\n"); 383 416 384 417 // Remove tail frames until the number of tail deleted frames is small enough. … … 448 481 out.print("\n"); 449 482 for (unsigned i = 0; i < limit; ++i) 450 out.print("\t", comma, m_log[i], "\n");483 out.print("\t", comma, "[", i, "] ", m_log[i], "\n"); 451 484 out.print("]}"); 452 485 }
Note: See TracChangeset
for help on using the changeset viewer.