Changeset 261391 in webkit
- Timestamp:
- May 8, 2020 10:14:15 AM (4 years ago)
- Location:
- trunk/LayoutTests
- Files:
-
- 1 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r261383 r261391 1 2020-05-07 Yusuke Suzuki <ysuzuki@apple.com> 2 3 Code pattern in GC tests in LayoutTests is broken 4 https://bugs.webkit.org/show_bug.cgi?id=211595 5 6 Reviewed by Saam Barati. 7 8 LayoutTests have several tests which attempt to ensure that document is not leaked. But they are broken since these tests are not correctly 9 handling conservative GC of JavaScriptCore. These tests are taking a following approach. 10 11 1. Allocate *one* iframe doing something inside it. 12 2. Remove iframe from the parent document to make it released. 13 3. Repeatedly invoke GC and test whether (1)'s document gets collected. 14 15 This is not the right approach. Since JavaScriptCore has conservative GC, it is easily possible that (1)'s pointer value remains in some of 16 conservative roots: CPU registers, stack etc. And JavaScriptCore conservative GC scans it and keeps it alive. The above approach makes the 17 test super flaky and any unrelated changes in JavaScriptCore and C compiler can make this test failed. 18 19 This is not a problem in practice. Web pages are executing various kind of code and they clobber conservative roots. However, tests in 20 LayoutTests are too simple to keep it alive accidentally. 21 22 The right approach for conservative GC is the following. 23 24 1. Allocate *many* iframes doing something inside them with loop. By creating iframes with loop, for every iteration, it is likely that 25 the same CPU registers and stack locations are overwritten by the last created iframe reference. This dramatically reduces the possibility 26 for GC to find these addresses in conservative roots. 27 2. Remove iframes from the parent document to make them released. 28 3. Repeatedly invoke GC and test whether *one of (1) iframes* gets collected at least. Theoretically this is still possible that completely 29 unrelated integer value in conservative roots can point to the reference of (1). By allocating many iframes in (1) and testing one of them, 30 we can reduce the possibility of this conflict. 31 32 This patch adds testDocumentIsNotLeaked helper function to enforce this pattern. And rewrite broken tests with this helper to make it less-flaky. 33 34 * highlight/highlight-world-leak.html: 35 * http/tests/IndexedDB/collect-IDB-objects.https.html: 36 * http/tests/media/clearkey/collect-webkit-media-session.html: 37 * http/tests/media/media-stream/collect-media-devices.https.html: 38 * http/tests/resources/gc.js: Added. 39 (window.gc.gcRec): 40 (window.gc.window.gc): 41 (nukeArray): 42 (async testDocumentIsNotLeaked): 43 * intersection-observer/no-document-leak.html: 44 * performance-api/performance-observer-no-document-leak.html: 45 * resources/gc.js: 46 (nukeArray): 47 (async testDocumentIsNotLeaked): 48 * webanimations/leak-document-with-web-animation.html: 49 1 50 2020-05-08 Lauro Moura <lmoura@igalia.com> 2 51 -
trunk/LayoutTests/highlight/highlight-world-leak.html
r254008 r261391 2 2 <html> 3 3 <body> 4 <iframe id="testFrame" src="resources/highlight-frame.html"></iframe>5 4 <script src="../resources/js-test-pre.js"></script> 5 <script src="../resources/gc.js"></script> 6 6 <script> 7 7 description("Tests that using Highlight does not cause the document to get leaked."); 8 8 window.jsTestIsAsync = true; 9 9 10 function documentShouldDie(documentIdentifier) 11 { 12 return new Promise(function(resolve, reject) { 13 handle = setInterval(function() { 14 gc(); 15 if (internals && !internals.isDocumentAlive(documentIdentifier)) { 16 clearInterval(handle); 17 resolve(); 10 window.onload = function () { 11 testDocumentIsNotLeaked( 12 async function initAndRemove(frameCount) 13 { 14 let frames = await new Promise((resolve, reject) => { 15 let frames = []; 16 let counter = 0; 17 function onLoad() { 18 counter++; 19 if (counter == frameCount) 20 resolve(frames); 21 } 22 for (let i = 0; i < frameCount; ++i) { 23 let frame = document.createElement('iframe'); 24 frame.src = "resources/highlight-frame.html"; 25 frame.onload = onLoad; 26 document.body.appendChild(frame); 27 frames.push(frame); 28 } 29 }); 30 let frameIdentifiers = []; 31 for (let i = 0; i < frameCount; ++i) { 32 let frame = frames[i]; 33 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 34 frame.remove(); 18 35 } 19 }, 10); 20 }); 21 } 36 nukeArray(frames); 37 frames = null; 38 return frameIdentifiers; 39 } 40 ).then( 41 () => testPassed("Document did not leak"), 42 (error) => testFailed(error.message) 43 ).finally(finishJSTest); 44 }; 22 45 23 var testFrame = document.getElementById("testFrame");24 onload = function() {25 let frameDocumentIdentifier = internals.documentIdentifier(testFrame.contentDocument);26 testFrame.remove();27 setTimeout(function() {28 documentShouldDie(frameDocumentIdentifier).then(function() {29 testPassed("Document did not leak");30 finishJSTest();31 });32 });33 };34 46 </script> 35 47 <script src="../resources/js-test-post.js"></script> -
trunk/LayoutTests/http/tests/IndexedDB/collect-IDB-objects.https.html
r235819 r261391 3 3 <script src="/resources/testharness.js"></script> 4 4 <script src="/resources/testharnessreport.js"></script> 5 <script src="/resources/gc.js"></script> 5 6 <script> 6 function waitFor(duration)7 {8 return new Promise((resolve) => setTimeout(resolve, duration));9 }10 7 11 var resolveCallback, rejectCallback;12 8 var promise = new Promise((resolve, reject) => { 13 resolveCallback = resolve; 14 rejectCallback = reject; 9 if (!window.internals) { 10 reject("Test require internals API"); 11 return; 12 } 13 window.onload = function () { 14 let promise = testDocumentIsNotLeaked( 15 async function initAndRemove(frameCount) 16 { 17 let frames = await new Promise((resolve, reject) => { 18 let frames = []; 19 let counter = 0; 20 function onMessage() { 21 counter++; 22 if (counter == frameCount) 23 resolve(frames); 24 } 25 window.addEventListener("message", onMessage); 26 for (let i = 0; i < frameCount; ++i) { 27 let frame = document.createElement('iframe'); 28 frame.src = "resources/myidbframe.htm"; 29 document.body.appendChild(frame); 30 frames.push(frame); 31 } 32 }); 33 let frameIdentifiers = []; 34 for (let i = 0; i < frameCount; ++i) { 35 let frame = frames[i]; 36 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 37 frame.src = "non-existent-frame"; 38 } 39 nukeArray(frames); 40 frames = null; 41 return frameIdentifiers; 42 } 43 ); 44 resolve(promise); 45 }; 15 46 }); 16 47 17 async function done()18 {19 try {20 const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);21 iframe.src = "non-existent-frame";22 let counter = 0;23 while (++counter < 50) {24 if (!internals.isDocumentAlive(frameIdentifier)) {25 resolveCallback();26 return;27 }28 if (window.GCController)29 GCController.collect();30 31 await waitFor(50);32 }33 rejectCallback("Test failed");34 } catch (e) {35 rejectCallback("Test failed: exception " + e);36 }37 }38 39 window.addEventListener("message", done);40 41 48 promise_test((test) => { 42 if (!window.internals)43 rejectCallback("Test require internals API");44 49 return promise; 45 50 }, "Ensuring frame document gets collected after being stopped in the middle of IDB operations"); 46 51 47 52 </script> 48 <iframe src="resources/myidbframe.htm" id="iframe"></iframe> -
trunk/LayoutTests/http/tests/media/clearkey/collect-webkit-media-session.html
r235819 r261391 3 3 <script src="/resources/testharness.js"></script> 4 4 <script src="/resources/testharnessreport.js"></script> 5 <script src="/resources/gc.js"></script> 5 6 <script> 6 function waitFor(duration)7 {8 return new Promise((resolve) => setTimeout(resolve, duration));9 }10 7 11 var resolveCallback, rejectCallback;12 8 var promise = new Promise((resolve, reject) => { 13 resolveCallback = resolve; 14 rejectCallback = reject; 9 if (!window.internals) { 10 reject("Test require internals API"); 11 return; 12 } 13 window.onload = function () { 14 let promise = testDocumentIsNotLeaked( 15 async function initAndRemove(frameCount) 16 { 17 let frames = await new Promise((resolve, reject) => { 18 let frames = []; 19 let counter = 0; 20 function onMessage() { 21 counter++; 22 if (counter == frameCount) 23 resolve(frames); 24 } 25 window.addEventListener("message", onMessage); 26 for (let i = 0; i < frameCount; ++i) { 27 let frame = document.createElement('iframe'); 28 frame.src = "resources/mywebkitmediasessionframe.htm"; 29 document.body.appendChild(frame); 30 frames.push(frame); 31 } 32 }); 33 let frameIdentifiers = []; 34 for (let i = 0; i < frameCount; ++i) { 35 let frame = frames[i]; 36 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 37 frame.src = "non-existent-frame"; 38 } 39 nukeArray(frames); 40 frames = null; 41 return frameIdentifiers; 42 } 43 ); 44 resolve(promise); 45 }; 15 46 }); 16 47 17 async function done()18 {19 try {20 const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);21 iframe.src = "non-existent-frame";22 let counter = 0;23 while (++counter < 50) {24 if (!internals.isDocumentAlive(frameIdentifier)) {25 resolveCallback();26 return;27 }28 if (window.GCController)29 GCController.collect();30 31 await waitFor(50);32 }33 rejectCallback("Test failed");34 } catch (e) {35 rejectCallback("Test failed: exception " + e);36 }37 }38 39 window.addEventListener("message", done);40 41 48 promise_test((test) => { 42 if (!window.internals)43 rejectCallback("Test require internals API");44 49 return promise; 45 50 }, "Ensure that the frame's document get collected after being stopped while doing some webkit media session calls"); 46 51 47 52 </script> 48 <iframe src="resources/mywebkitmediasessionframe.htm" id="iframe"></iframe> -
trunk/LayoutTests/http/tests/media/media-stream/collect-media-devices.https.html
r235819 r261391 3 3 <script src="/resources/testharness.js"></script> 4 4 <script src="/resources/testharnessreport.js"></script> 5 <script src="/resources/gc.js"></script> 5 6 <script> 6 function waitFor(duration)7 {8 return new Promise((resolve) => setTimeout(resolve, duration));9 }10 7 11 var resolveCallback, rejectCallback;12 8 var promise = new Promise((resolve, reject) => { 13 resolveCallback = resolve; 14 rejectCallback = reject; 9 if (!window.internals) { 10 reject("Test require internals API"); 11 return; 12 } 13 window.onload = function () { 14 let promise = testDocumentIsNotLeaked( 15 async function initAndRemove(frameCount) 16 { 17 let frames = await new Promise((resolve, reject) => { 18 let frames = []; 19 let counter = 0; 20 function onMessage() { 21 counter++; 22 if (counter == frameCount) 23 resolve(frames); 24 } 25 window.addEventListener("message", onMessage); 26 for (let i = 0; i < frameCount; ++i) { 27 let frame = document.createElement('iframe'); 28 frame.src = "resources/mymediadevicesframe.htm"; 29 document.body.appendChild(frame); 30 frames.push(frame); 31 } 32 }); 33 let frameIdentifiers = []; 34 for (let i = 0; i < frameCount; ++i) { 35 let frame = frames[i]; 36 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 37 frame.src = "non-existent-frame"; 38 } 39 nukeArray(frames); 40 frames = null; 41 return frameIdentifiers; 42 } 43 ); 44 resolve(promise); 45 }; 15 46 }); 16 47 17 async function done()18 {19 try {20 const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);21 iframe.src = "non-existent-frame";22 let counter = 0;23 while (++counter < 50) {24 if (!internals.isDocumentAlive(frameIdentifier)) {25 resolveCallback();26 return;27 }28 if (window.GCController)29 GCController.collect();30 31 await waitFor(50);32 }33 rejectCallback("Test failed");34 } catch (e) {35 rejectCallback("Test failed: exception " + e);36 }37 }38 39 window.addEventListener("message", done);40 41 48 promise_test((test) => { 42 if (!window.internals)43 rejectCallback("Test require internals API");44 49 return promise; 45 50 }, "Ensure that the frame's document get collected after being stopped while using MediaDevices"); 46 51 47 52 </script> 48 <iframe src="resources/mymediadevicesframe.htm" id="iframe"></iframe> -
trunk/LayoutTests/intersection-observer/no-document-leak.html
r235736 r261391 3 3 <head> 4 4 <script src="../resources/js-test-pre.js"></script> 5 <script src="../resources/gc.js"></script> 5 6 </head> 6 7 <body> 7 <iframe id="testFrame" src= "resources/no-document-leak-frame.html"></iframe>8 <iframe id="testFrame" src=></iframe> 8 9 <script> 9 10 description("Tests that using IntersectionObserver does not cause the document to get leaked."); 10 11 window.jsTestIsAsync = true; 11 12 12 function documentShouldDie(documentIdentifier) 13 { 14 return new Promise(function(resolve, reject) { 15 handle = setInterval(function() { 16 gc(); 17 if (internals && !internals.isDocumentAlive(documentIdentifier) && internals.numberOfIntersectionObservers(document) == 0) { 18 clearInterval(handle); 19 resolve(); 13 let totalCount = 0; 14 window.onload = function () { 15 testDocumentIsNotLeaked( 16 async function initAndRemove(frameCount) 17 { 18 totalCount = frameCount; 19 let frames = await new Promise((resolve, reject) => { 20 let frames = []; 21 let counter = 0; 22 function onLoad() { 23 counter++; 24 if (counter == frameCount) 25 resolve(frames); 26 } 27 for (let i = 0; i < frameCount; ++i) { 28 let frame = document.createElement('iframe'); 29 frame.src = "resources/no-document-leak-frame.html"; 30 frame.onload = onLoad; 31 document.body.appendChild(frame); 32 frames.push(frame); 33 } 34 }); 35 totalCount = internals.numberOfIntersectionObservers(document); 36 let frameIdentifiers = []; 37 for (let i = 0; i < frameCount; ++i) { 38 let frame = frames[i]; 39 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 40 frame.remove(); 20 41 } 21 }, 10); 22 }); 23 } 42 nukeArray(frames); 43 frames = null; 44 return frameIdentifiers; 45 }, 46 { 47 additionalCheck: function() 48 { 49 let count = internals.numberOfIntersectionObservers(document); 50 return count < totalCount || count === 0; 51 } 52 } 53 ).then( 54 () => testPassed("Document did not leak"), 55 (error) => testFailed(error.message) 56 ).finally(finishJSTest); 57 }; 24 58 25 var testFrame = document.getElementById("testFrame");26 testFrame.onload = function() {27 let frameDocumentIdentifier = internals.documentIdentifier(testFrame.contentDocument);28 testFrame.remove();29 setTimeout(function() {30 documentShouldDie(frameDocumentIdentifier).then(function() {31 testPassed("Document did not leak");32 finishJSTest();33 });34 });35 };36 59 </script> 37 60 <script src="../resources/js-test-post.js"></script> -
trunk/LayoutTests/performance-api/performance-observer-no-document-leak.html
r233490 r261391 3 3 <head> 4 4 <script src="../resources/js-test-pre.js"></script> 5 <script src="../resources/gc.js"></script> 5 6 </head> 6 7 <body> 7 <iframe id="testFrame" src="resources/performance-observer-no-document-leak-frame.html"></iframe>8 8 <script> 9 9 description("Tests that using PerformanceObserver does not cause the document to get leaked."); 10 10 window.jsTestIsAsync = true; 11 11 12 function documentShouldDie(documentIdentifier) 13 { 14 return new Promise(function(resolve, reject) { 15 handle = setInterval(function() { 16 gc(); 17 if (!internals.isDocumentAlive(documentIdentifier)) { 18 clearInterval(handle); 19 resolve(); 12 window.onload = function () { 13 testDocumentIsNotLeaked( 14 async function initAndRemove(frameCount) 15 { 16 let frames = await new Promise((resolve, reject) => { 17 let frames = []; 18 let counter = 0; 19 function onLoad() { 20 counter++; 21 if (counter == frameCount) 22 resolve(frames); 23 } 24 for (let i = 0; i < frameCount; ++i) { 25 let frame = document.createElement('iframe'); 26 frame.src = "resources/performance-observer-no-document-leak-frame.html"; 27 frame.onload = onLoad; 28 document.body.appendChild(frame); 29 frames.push(frame); 30 } 31 }); 32 let frameIdentifiers = []; 33 for (let i = 0; i < frameCount; ++i) { 34 let frame = frames[i]; 35 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 36 frame.remove(); 20 37 } 21 }, 10); 22 }); 23 } 38 nukeArray(frames); 39 frames = null; 40 return frameIdentifiers; 41 } 42 ).then( 43 () => testPassed("Document did not leak"), 44 (error) => testFailed(error.message) 45 ).finally(finishJSTest); 46 }; 24 47 25 onload = function() {26 setTimeout(function() {27 let frameDocumentIdentifier = internals.documentIdentifier(testFrame.contentDocument);28 testFrame.remove();29 documentShouldDie(frameDocumentIdentifier).then(function() {30 testPassed("Document did not leak");31 finishJSTest();32 });33 }, 10);34 }35 48 </script> 36 49 <script src="../resources/js-test-post.js"></script> -
trunk/LayoutTests/resources/gc.js
r185586 r261391 21 21 } 22 22 } 23 24 // Fill array with null to avoid potential GC reachability. frames = null can be enough in 99.9% cases, 25 // but we are extra careful to make tests non-flaky: considering about the case that array is captured 26 // somewhere (like DFG constants). 27 function nukeArray(array) 28 { 29 for (let index = 0; index < array.length; ++index) 30 array[index] = null; 31 array.length = 0; 32 } 33 34 async function testDocumentIsNotLeaked(init, options = {}) 35 { 36 let gcCount = options.gcCount ?? 50; 37 let documentCount = options.documentCount ?? 20; 38 let additionalCheck = options.additionalCheck ?? function () { return true; }; 39 40 function waitFor(duration) 41 { 42 return new Promise((resolve) => setTimeout(resolve, duration)); 43 } 44 45 let frameIdentifiers = await init(documentCount); 46 for (let counter = 0; counter < gcCount; ++counter) { 47 for (let i = 0; i < documentCount; ++i) { 48 let frameIdentifier = frameIdentifiers[i]; 49 if (!internals.isDocumentAlive(frameIdentifier) && additionalCheck()) 50 return "Document did not leak"; 51 } 52 gc(); 53 await waitFor(50); 54 } 55 56 throw new Error("Document is leaked"); 57 } -
trunk/LayoutTests/webanimations/leak-document-with-web-animation.html
r251742 r261391 3 3 <body onload="runTest()"> 4 4 <script src="../resources/js-test.js"></script> 5 <script src="../resources/gc.js"></script> 5 6 <script> 6 7 description("This test asserts that Document doesn't leak when a Web Animation is created."); 7 8 8 if (window.internals) 9 if (window.internals) { 9 10 jsTestIsAsync = true; 10 11 11 function runTest() { 12 if (!window.internals) 13 return; 14 15 var frame = document.body.appendChild(document.createElement("iframe")); 16 17 frame.onload = function() { 18 if (frame.src === 'about:blank') 19 return true; 20 21 documentIdentifier = internals.documentIdentifier(frame.contentDocument); 22 debug("The iframe has finished loading."); 23 24 frame.remove(); 25 frame = null; 26 27 gc(); 28 timeout = 0; 29 handle = setInterval(() => { 30 if (!internals.isDocumentAlive(documentIdentifier)) { 31 clearInterval(handle); 32 testPassed("The document was destroyed"); 33 finishJSTest(); 34 return; 12 window.onload = function () { 13 testDocumentIsNotLeaked( 14 async function initAndRemove(frameCount) 15 { 16 let frames = await new Promise((resolve, reject) => { 17 let frames = []; 18 let counter = 0; 19 function onLoad() { 20 counter++; 21 if (counter == frameCount) 22 resolve(frames); 23 } 24 for (let i = 0; i < frameCount; ++i) { 25 let frame = document.createElement('iframe'); 26 frame.src = "resources/web-animation-leak-iframe.html"; 27 frame.onload = onLoad; 28 document.body.appendChild(frame); 29 frames.push(frame); 30 } 31 }); 32 debug("The iframe has finished loading."); 33 let frameIdentifiers = []; 34 for (let i = 0; i < frameCount; ++i) { 35 let frame = frames[i]; 36 frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument)); 37 frame.remove(); 38 } 39 nukeArray(frames); 40 frames = null; 41 return frameIdentifiers; 35 42 } 36 timeout++; 37 if (timeout == 500) { 38 clearInterval(handle); 39 testFailed("The document was leaked"); 40 finishJSTest(); 41 return; 42 } 43 gc(); 44 }, 10); 45 } 46 47 frame.src = 'resources/web-animation-leak-iframe.html'; 43 ).then( 44 () => testPassed("The document was destroyed"), 45 (error) => testFailed(error.message) 46 ).finally(finishJSTest); 47 }; 48 48 } 49 49
Note: See TracChangeset
for help on using the changeset viewer.