- Revision
- 218465
- Author
- [email protected]
- Date
- 2017-06-18 13:25:19 -0700 (Sun, 18 Jun 2017)
Log Message
Crash when re-entering MediaDevicesEnumerationRequest::cancel()
https://bugs.webkit.org/show_bug.cgi?id=173522
<rdar://problem/31185739>
Reviewed by Darin Adler.
Source/WebCore:
When a MediaDevicesRequest is started, it creates a MediaDevicesEnumerationRequest
object and passes a completion handler to that MediaDevicesEnumerationRequest
object. The completion handler holds a reference to the MediaDevicesRequest object
so that its stays alive until the MediaDevicesEnumerationRequest either completes
or is canceled. MediaDevicesRequest also holds a reference to the
MediaDevicesEnumerationRequest object via its m_enumerationRequest data member.
When the document is destroyed, both MediaDevicesRequest::contextDestroyed() and
MediaDevicesEnumerationRequest::contextDestroyed() gets called and the other is not
pre-determined. If MediaDevicesEnumerationRequest::contextDestroyed() gets called
first then it calls MediaDevicesEnumerationRequest::cancel(). Calling cancel() ends
up destroying the completion handler. Destroying the completion handler ends up
dereferencing and destroying the MediaDevicesRequest object. The MediaDevicesRequest
destructor would call MediaDevicesEnumerationRequest::cancel() again, causing us to
re-enter it and assign nullptr to the completion callback again. Re-entering
std::function's operator=(nullptr_t) is not safe because of the way it is implemented
as we end up trying to destroy the lambda twice and crashing. Using a WTF::Function
instead fixes this particular issue because re-entering WTF::Function's operator=(nullptr_t)
is safe.
However, this fix is not sufficient. Calling the MediaDevicesRequest destructor also
dereferencing and destroys the MediaDevicesEnumerationRequest object. As a result,
when MediaDevicesEnumerationRequest::contextDestroyed() returns from its call to cancel
|this| is already dead when we call ContextDestructionObserver::contextDestroyed().
To address this issue, we now protect |this| in MediaDevicesEnumerationRequest::contextDestroyed().
Test: fast/mediastream/destroy-document-while-enumerating-devices.html
* Modules/mediastream/MediaDevicesEnumerationRequest.cpp:
(WebCore::MediaDevicesEnumerationRequest::contextDestroyed):
Protect |this| as the call to cancel() may destroy |this| before calling
ContextDestructionObserver::contextDestroyed() otherwise.
* Modules/mediastream/MediaDevicesEnumerationRequest.h:
Use WTF::Function instead of std::function for the completion handler as
it is safer (in terms of re-entrency) and avoids unnecessary copying.
* Modules/mediastream/MediaDevicesRequest.cpp:
(WebCore::MediaDevicesRequest::~MediaDevicesRequest):
Stop calling MediaDevicesEnumerationRequest::cancel(). When the destructor
is called, the MediaDevicesEnumerationRequest has either completed or been
canceled so there is no need to cancel again. I added an assertion to
make sure it is the case. This avoids re-entering
MediaDevicesEnumerationRequest::cancel() is some cases, which was risky.
(WebCore::MediaDevicesRequest::start):
Add comment for clarity and capture a Ref<> instead of a RefPtr<> now that
we can since we use WTF::Function.
Tools:
Add API test for re-entering Function's assignment operators.
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WTF/Function.cpp: Added.
(TestWebKitAPI::TestObject::TestObject):
(TestWebKitAPI::TestObject::~TestObject):
(TestWebKitAPI::TestObject::operator()):
(TestWebKitAPI::TEST):
LayoutTests:
Add layout test coverage. This test would flakily crash before the fix because it
relies on the order in which contextDestroyed() is called for MediaDevicesRequest
and MediaDevicesEnumerationRequest.
* fast/mediastream/destroy-document-while-enumerating-devices-expected.txt: Added.
* fast/mediastream/destroy-document-while-enumerating-devices.html: Added.
* fast/mediastream/resources/enumerate-devices-frame.html: Added.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (218464 => 218465)
--- trunk/LayoutTests/ChangeLog 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/LayoutTests/ChangeLog 2017-06-18 20:25:19 UTC (rev 218465)
@@ -1,3 +1,19 @@
+2017-06-18 Chris Dumez <[email protected]>
+
+ Crash when re-entering MediaDevicesEnumerationRequest::cancel()
+ https://bugs.webkit.org/show_bug.cgi?id=173522
+ <rdar://problem/31185739>
+
+ Reviewed by Darin Adler.
+
+ Add layout test coverage. This test would flakily crash before the fix because it
+ relies on the order in which contextDestroyed() is called for MediaDevicesRequest
+ and MediaDevicesEnumerationRequest.
+
+ * fast/mediastream/destroy-document-while-enumerating-devices-expected.txt: Added.
+ * fast/mediastream/destroy-document-while-enumerating-devices.html: Added.
+ * fast/mediastream/resources/enumerate-devices-frame.html: Added.
+
2017-06-17 Simon Fraser <[email protected]>
Implement DOMQuad
Added: trunk/LayoutTests/fast/mediastream/destroy-document-while-enumerating-devices-expected.txt (0 => 218465)
--- trunk/LayoutTests/fast/mediastream/destroy-document-while-enumerating-devices-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/mediastream/destroy-document-while-enumerating-devices-expected.txt 2017-06-18 20:25:19 UTC (rev 218465)
@@ -0,0 +1,9 @@
+Tests that we do not crash when destroying the document while a request to enumerate devices is ongoing.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/mediastream/destroy-document-while-enumerating-devices.html (0 => 218465)
--- trunk/LayoutTests/fast/mediastream/destroy-document-while-enumerating-devices.html (rev 0)
+++ trunk/LayoutTests/fast/mediastream/destroy-document-while-enumerating-devices.html 2017-06-18 20:25:19 UTC (rev 218465)
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Tests that we do not crash when destroying the document while a request to enumerate devices is ongoing.");
+jsTestIsAsync = true;
+
+function destroyFrame()
+{
+ frame.remove();
+ frame = null;
+ gc();
+ setTimeout(function() {
+ gc();
+ finishJSTest();
+ }, 0);
+}
+
+frame = document.createElement("iframe");
+frame.src = ""
+document.body.appendChild(frame);
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/fast/mediastream/resources/enumerate-devices-frame.html (0 => 218465)
--- trunk/LayoutTests/fast/mediastream/resources/enumerate-devices-frame.html (rev 0)
+++ trunk/LayoutTests/fast/mediastream/resources/enumerate-devices-frame.html 2017-06-18 20:25:19 UTC (rev 218465)
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+_onload_ = function() {
+ parent.destroyFrame();
+ enumerationPromise = navigator.mediaDevices.enumerateDevices();
+};
+</script>
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (218464 => 218465)
--- trunk/Source/WebCore/ChangeLog 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/Source/WebCore/ChangeLog 2017-06-18 20:25:19 UTC (rev 218465)
@@ -1,5 +1,62 @@
2017-06-18 Chris Dumez <[email protected]>
+ Crash when re-entering MediaDevicesEnumerationRequest::cancel()
+ https://bugs.webkit.org/show_bug.cgi?id=173522
+ <rdar://problem/31185739>
+
+ Reviewed by Darin Adler.
+
+ When a MediaDevicesRequest is started, it creates a MediaDevicesEnumerationRequest
+ object and passes a completion handler to that MediaDevicesEnumerationRequest
+ object. The completion handler holds a reference to the MediaDevicesRequest object
+ so that its stays alive until the MediaDevicesEnumerationRequest either completes
+ or is canceled. MediaDevicesRequest also holds a reference to the
+ MediaDevicesEnumerationRequest object via its m_enumerationRequest data member.
+
+ When the document is destroyed, both MediaDevicesRequest::contextDestroyed() and
+ MediaDevicesEnumerationRequest::contextDestroyed() gets called and the other is not
+ pre-determined. If MediaDevicesEnumerationRequest::contextDestroyed() gets called
+ first then it calls MediaDevicesEnumerationRequest::cancel(). Calling cancel() ends
+ up destroying the completion handler. Destroying the completion handler ends up
+ dereferencing and destroying the MediaDevicesRequest object. The MediaDevicesRequest
+ destructor would call MediaDevicesEnumerationRequest::cancel() again, causing us to
+ re-enter it and assign nullptr to the completion callback again. Re-entering
+ std::function's operator=(nullptr_t) is not safe because of the way it is implemented
+ as we end up trying to destroy the lambda twice and crashing. Using a WTF::Function
+ instead fixes this particular issue because re-entering WTF::Function's operator=(nullptr_t)
+ is safe.
+
+ However, this fix is not sufficient. Calling the MediaDevicesRequest destructor also
+ dereferencing and destroys the MediaDevicesEnumerationRequest object. As a result,
+ when MediaDevicesEnumerationRequest::contextDestroyed() returns from its call to cancel
+ |this| is already dead when we call ContextDestructionObserver::contextDestroyed().
+ To address this issue, we now protect |this| in MediaDevicesEnumerationRequest::contextDestroyed().
+
+ Test: fast/mediastream/destroy-document-while-enumerating-devices.html
+
+ * Modules/mediastream/MediaDevicesEnumerationRequest.cpp:
+ (WebCore::MediaDevicesEnumerationRequest::contextDestroyed):
+ Protect |this| as the call to cancel() may destroy |this| before calling
+ ContextDestructionObserver::contextDestroyed() otherwise.
+
+ * Modules/mediastream/MediaDevicesEnumerationRequest.h:
+ Use WTF::Function instead of std::function for the completion handler as
+ it is safer (in terms of re-entrency) and avoids unnecessary copying.
+
+ * Modules/mediastream/MediaDevicesRequest.cpp:
+ (WebCore::MediaDevicesRequest::~MediaDevicesRequest):
+ Stop calling MediaDevicesEnumerationRequest::cancel(). When the destructor
+ is called, the MediaDevicesEnumerationRequest has either completed or been
+ canceled so there is no need to cancel again. I added an assertion to
+ make sure it is the case. This avoids re-entering
+ MediaDevicesEnumerationRequest::cancel() is some cases, which was risky.
+
+ (WebCore::MediaDevicesRequest::start):
+ Add comment for clarity and capture a Ref<> instead of a RefPtr<> now that
+ we can since we use WTF::Function.
+
+2017-06-18 Chris Dumez <[email protected]>
+
Use WTF::Function instead of std::function in WTF/
https://bugs.webkit.org/show_bug.cgi?id=173519
Modified: trunk/Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.cpp (218464 => 218465)
--- trunk/Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.cpp 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.cpp 2017-06-18 20:25:19 UTC (rev 218465)
@@ -70,6 +70,9 @@
void MediaDevicesEnumerationRequest::contextDestroyed()
{
+ // Calling cancel() may destroy ourselves.
+ Ref<MediaDevicesEnumerationRequest> protectedThis(*this);
+
cancel();
ContextDestructionObserver::contextDestroyed();
}
Modified: trunk/Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.h (218464 => 218465)
--- trunk/Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.h 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/Source/WebCore/Modules/mediastream/MediaDevicesEnumerationRequest.h 2017-06-18 20:25:19 UTC (rev 218465)
@@ -29,7 +29,7 @@
#if ENABLE(MEDIA_STREAM)
#include "ActiveDOMObject.h"
-#include <functional>
+#include <wtf/Function.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
@@ -40,7 +40,7 @@
class MediaDevicesEnumerationRequest final : public ContextDestructionObserver, public RefCounted<MediaDevicesEnumerationRequest> {
public:
- using CompletionHandler = std::function<void(const Vector<CaptureDevice>&, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess)>;
+ using CompletionHandler = WTF::Function<void(const Vector<CaptureDevice>&, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess)>;
static Ref<MediaDevicesEnumerationRequest> create(Document&, CompletionHandler&&);
@@ -49,6 +49,8 @@
void start();
void cancel();
+ bool wasCanceled() const { return !m_completionHandler; }
+
WEBCORE_EXPORT void setDeviceInfo(const Vector<CaptureDevice>&, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess);
WEBCORE_EXPORT SecurityOrigin* userMediaDocumentOrigin() const;
Modified: trunk/Source/WebCore/Modules/mediastream/MediaDevicesRequest.cpp (218464 => 218465)
--- trunk/Source/WebCore/Modules/mediastream/MediaDevicesRequest.cpp 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/Source/WebCore/Modules/mediastream/MediaDevicesRequest.cpp 2017-06-18 20:25:19 UTC (rev 218465)
@@ -55,8 +55,8 @@
MediaDevicesRequest::~MediaDevicesRequest()
{
- if (m_enumerationRequest)
- m_enumerationRequest->cancel();
+ // This should only get destroyed after the enumeration request has completed or has been canceled.
+ ASSERT(!m_enumerationRequest || m_enumerationRequest->wasCanceled());
}
SecurityOrigin* MediaDevicesRequest::securityOrigin() const
@@ -109,8 +109,8 @@
void MediaDevicesRequest::start()
{
- RefPtr<MediaDevicesRequest> protectedThis = this;
- auto completion = [this, protectedThis = WTFMove(protectedThis)] (const Vector<CaptureDevice>& captureDevices, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
+ // This lambda keeps |this| alive until the request completes or is canceled.
+ auto completion = [this, protectedThis = makeRef(*this)] (const Vector<CaptureDevice>& captureDevices, const String& deviceIdentifierHashSalt, bool originHasPersistentAccess) mutable {
m_enumerationRequest = nullptr;
Modified: trunk/Tools/ChangeLog (218464 => 218465)
--- trunk/Tools/ChangeLog 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/Tools/ChangeLog 2017-06-18 20:25:19 UTC (rev 218465)
@@ -1,3 +1,20 @@
+2017-06-18 Chris Dumez <[email protected]>
+
+ Crash when re-entering MediaDevicesEnumerationRequest::cancel()
+ https://bugs.webkit.org/show_bug.cgi?id=173522
+ <rdar://problem/31185739>
+
+ Reviewed by Darin Adler.
+
+ Add API test for re-entering Function's assignment operators.
+
+ * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+ * TestWebKitAPI/Tests/WTF/Function.cpp: Added.
+ (TestWebKitAPI::TestObject::TestObject):
+ (TestWebKitAPI::TestObject::~TestObject):
+ (TestWebKitAPI::TestObject::operator()):
+ (TestWebKitAPI::TEST):
+
2017-06-16 Dan Bernstein <[email protected]>
[Cocoa] Some declarations have missing or incorrect availability attributes
Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (218464 => 218465)
--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj 2017-06-18 19:49:12 UTC (rev 218464)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj 2017-06-18 20:25:19 UTC (rev 218465)
@@ -485,6 +485,7 @@
837A35F11D9A1E7D00663C57 /* DownloadRequestBlobURL.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 837A35F01D9A1E6400663C57 /* DownloadRequestBlobURL.html */; };
83B6DE6F1EE75221001E792F /* RestoreSessionState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83B6DE6E1EE7520F001E792F /* RestoreSessionState.cpp */; };
83BAEE8D1EF4625500DDE894 /* PluginLoadClientPolicies.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83BAEE8C1EF4625500DDE894 /* PluginLoadClientPolicies.mm */; };
+ 83DB79691EF63B3C00BFA5E5 /* Function.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83DB79671EF63B3C00BFA5E5 /* Function.cpp */; };
83DE134D1EF1C50800C1B355 /* ResponsivenessTimer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83DE134C1EF1C4FE00C1B355 /* ResponsivenessTimer.cpp */; };
8E4A85371E1D1AB200F53B0F /* GridPosition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8E4A85361E1D1AA100F53B0F /* GridPosition.cpp */; };
930AD402150698D00067970F /* lots-of-text.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 930AD401150698B30067970F /* lots-of-text.html */; };
@@ -1323,6 +1324,7 @@
83B6DE6E1EE7520F001E792F /* RestoreSessionState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RestoreSessionState.cpp; sourceTree = "<group>"; };
83B88A331C80056D00BB2418 /* HTMLParserIdioms.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTMLParserIdioms.cpp; sourceTree = "<group>"; };
83BAEE8C1EF4625500DDE894 /* PluginLoadClientPolicies.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PluginLoadClientPolicies.mm; sourceTree = "<group>"; };
+ 83DB79671EF63B3C00BFA5E5 /* Function.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Function.cpp; sourceTree = "<group>"; };
83DE134C1EF1C4FE00C1B355 /* ResponsivenessTimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ResponsivenessTimer.cpp; sourceTree = "<group>"; };
86BD19971A2DB05B006DCF0A /* RefCounter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RefCounter.cpp; sourceTree = "<group>"; };
8A2C750D16CED9550024F352 /* ResizeWindowAfterCrash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ResizeWindowAfterCrash.cpp; sourceTree = "<group>"; };
@@ -2351,6 +2353,7 @@
1CB9BC371A67482300FE5678 /* WeakPtr.cpp */,
7AA6A1511AAC0B31002B2ED3 /* WorkQueue.cpp */,
265AF54F15D1E48A00B0CB4A /* WTFString.cpp */,
+ 83DB79671EF63B3C00BFA5E5 /* Function.cpp */,
);
path = WTF;
sourceTree = "<group>";
@@ -3056,6 +3059,7 @@
7CCE7EFF1A411AE600447C4C /* LoadCanceledNoServerRedirectCallback.cpp in Sources */,
A125478F1DB18B9400358564 /* LoadDataWithNilMIMEType.mm in Sources */,
5C838F7F1DB04F900082858F /* LoadInvalidURLRequest.mm in Sources */,
+ 83DB79691EF63B3C00BFA5E5 /* Function.cpp in Sources */,
7C83E0C01D0A652700FEBCF3 /* LoadInvalidURLRequest.mm in Sources */,
7CCE7F001A411AE600447C4C /* LoadPageOnCrash.cpp in Sources */,
6356FB221EC4E0BA0044BF18 /* VisibleContentRect.mm in Sources */,
Added: trunk/Tools/TestWebKitAPI/Tests/WTF/Function.cpp (0 => 218465)
--- trunk/Tools/TestWebKitAPI/Tests/WTF/Function.cpp (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WTF/Function.cpp 2017-06-18 20:25:19 UTC (rev 218465)
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "Test.h"
+#include <wtf/Function.h>
+
+namespace TestWebKitAPI {
+
+static WTF::Function<int()> function_for_reentrancy_test;
+static unsigned testObjectDestructorCalls = 0;
+
+enum class AssignmentMode { Null, Lambda, None };
+
+class TestObject {
+public:
+ TestObject(AssignmentMode);
+ TestObject(TestObject&&);
+
+ ~TestObject();
+ int operator()();
+
+ TestObject(const TestObject&) = delete;
+ TestObject& operator=(const TestObject&) = delete;
+
+private:
+ AssignmentMode m_assignmentMode;
+};
+
+TestObject::TestObject(AssignmentMode assignmentMode)
+ : m_assignmentMode(assignmentMode)
+{
+}
+
+TestObject::TestObject(TestObject&& other)
+{
+ m_assignmentMode = std::exchange(other.m_assignmentMode, AssignmentMode::None);
+}
+
+TestObject::~TestObject()
+{
+ if (m_assignmentMode == AssignmentMode::None)
+ return;
+
+ ++testObjectDestructorCalls;
+ if (m_assignmentMode == AssignmentMode::Null)
+ function_for_reentrancy_test = nullptr;
+ else
+ function_for_reentrancy_test = [] { return -1; };
+}
+
+int TestObject::operator()()
+{
+ return 0;
+}
+
+TEST(WTF_Function, assignNullReEntersAssignNull)
+{
+ function_for_reentrancy_test = nullptr;
+ testObjectDestructorCalls = 0;
+
+ function_for_reentrancy_test = TestObject(AssignmentMode::Null);
+ EXPECT_EQ(0, function_for_reentrancy_test());
+ EXPECT_FALSE(!function_for_reentrancy_test);
+ EXPECT_EQ(0U, testObjectDestructorCalls);
+ function_for_reentrancy_test = nullptr;
+ EXPECT_EQ(1U, testObjectDestructorCalls);
+ EXPECT_TRUE(!function_for_reentrancy_test);
+}
+
+TEST(WTF_Function, assignLamdbaReEntersAssignNull)
+{
+ function_for_reentrancy_test = nullptr;
+ testObjectDestructorCalls = 0;
+
+ function_for_reentrancy_test = TestObject(AssignmentMode::Null);
+ EXPECT_EQ(0, function_for_reentrancy_test());
+ EXPECT_FALSE(!function_for_reentrancy_test);
+ EXPECT_EQ(0U, testObjectDestructorCalls);
+ function_for_reentrancy_test = [] { return 3; };
+ EXPECT_EQ(1U, testObjectDestructorCalls);
+ EXPECT_TRUE(!function_for_reentrancy_test);
+}
+
+TEST(WTF_Function, assignLamdbaReEntersAssignLamdba)
+{
+ function_for_reentrancy_test = nullptr;
+ testObjectDestructorCalls = 0;
+
+ function_for_reentrancy_test = TestObject(AssignmentMode::Lambda);
+ EXPECT_EQ(0, function_for_reentrancy_test());
+ EXPECT_FALSE(!function_for_reentrancy_test);
+ EXPECT_EQ(0, function_for_reentrancy_test());
+ EXPECT_EQ(0U, testObjectDestructorCalls);
+ function_for_reentrancy_test = [] { return 3; };
+ EXPECT_EQ(1U, testObjectDestructorCalls);
+ EXPECT_FALSE(!function_for_reentrancy_test);
+ EXPECT_EQ(-1, function_for_reentrancy_test());
+}
+
+TEST(WTF_Function, assignNullReEntersAssignLamda)
+{
+ function_for_reentrancy_test = nullptr;
+ testObjectDestructorCalls = 0;
+
+ function_for_reentrancy_test = TestObject(AssignmentMode::Lambda);
+ EXPECT_EQ(0, function_for_reentrancy_test());
+ EXPECT_FALSE(!function_for_reentrancy_test);
+ EXPECT_EQ(0, function_for_reentrancy_test());
+ EXPECT_EQ(0U, testObjectDestructorCalls);
+ function_for_reentrancy_test = nullptr;
+ EXPECT_EQ(1U, testObjectDestructorCalls);
+ EXPECT_FALSE(!function_for_reentrancy_test);
+ EXPECT_EQ(-1, function_for_reentrancy_test());
+}
+
+}