Bug 233945

Summary: ActiveDOMObject::suspendIfNeeded() should not be called within constructors
Product: WebKit Reporter: Antoine Quint <graouts>
Component: New BugsAssignee: Antoine Quint <graouts>
Status: RESOLVED FIXED    
Severity: Normal CC: alecflett, beidson, benjamin, calvaris, cdumez, changseok, darin, eric.carlson, esprehn+autocc, ews-watchlist, glenn, gyuyoung.kim, hta, jer.noble, jsbell, kangil.han, macpherson, menard, philipj, sergio, tommyw, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Patch
none
Patch for landing
none
Patch
none
Patch
ews-feeder: commit-queue-
Patch
none
Patch
none
Patch for landing
ews-feeder: commit-queue-
Patch for landing
none
Patch
none
Patch
none
Patch for landing
none
Patch
none
Patch none

Description Antoine Quint 2021-12-07 11:51:57 PST
ActiveDOMObject::suspendIfNeeded() should not be called within constructors
Comment 1 Antoine Quint 2021-12-07 11:52:35 PST
Created attachment 446215 [details]
Patch
Comment 2 Chris Dumez 2021-12-07 11:54:29 PST
Comment on attachment 446215 [details]
Patch

r=me assuming the bots are happy
Comment 3 Darin Adler 2021-12-07 12:08:00 PST
Comment on attachment 446215 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=446215&action=review

Really great patch. Too bad we can’t get the compiler or an assertion to catch this mistake, but this is nicely thorough.

Maybe we should rename this function suspendIfNeededDoNotCallInConstructor (just kidding, but maybe not kidding).

> Source/WebCore/Modules/cache/DOMCache.h:40
> +    static Ref<DOMCache> create(ScriptExecutionContext&, String&&, uint64_t, Ref<CacheStorageConnection>&&);

Not sure that we should remove *all* of these names. The argument names "name" and "identifier" are probably still helpful.

> Source/WebCore/Modules/geolocation/Geolocation.cpp:137
> -    geolocation.get().suspendIfNeeded();
> +    geolocation->suspendIfNeeded();

Ah, brevity competes with "looks like a risky pointer dereference, but is not". I think the more terse version is better, and it’s what we use almost everywhere, but I am conflicted. There’s something cool about ".get()." not looking like a pointer is involved.
Comment 4 Antoine Quint 2021-12-07 12:53:21 PST
(In reply to Darin Adler from comment #3)
> Comment on attachment 446215 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=446215&action=review
> 
> Really great patch. Too bad we can’t get the compiler or an assertion to
> catch this mistake, but this is nicely thorough.

Not quite! There are 17 cases left to fix, those were just the easy ones. I'll look into fixing the other ones piecemeal.

> Maybe we should rename this function suspendIfNeededDoNotCallInConstructor
> (just kidding, but maybe not kidding).
> 
> > Source/WebCore/Modules/cache/DOMCache.h:40
> > +    static Ref<DOMCache> create(ScriptExecutionContext&, String&&, uint64_t, Ref<CacheStorageConnection>&&);
> 
> Not sure that we should remove *all* of these names. The argument names
> "name" and "identifier" are probably still helpful.

Will restore those.

> > Source/WebCore/Modules/geolocation/Geolocation.cpp:137
> > -    geolocation.get().suspendIfNeeded();
> > +    geolocation->suspendIfNeeded();
> 
> Ah, brevity competes with "looks like a risky pointer dereference, but is
> not". I think the more terse version is better, and it’s what we use almost
> everywhere, but I am conflicted. There’s something cool about ".get()." not
> looking like a pointer is involved.

I personally don't mind either way, I stumbled onto this code looking at all call sites to ActiveDOMObject::suspendIfNeeded() and it was the only call on a Ref that called .get(). Felt right to put it in line with the rest.
Comment 5 Darin Adler 2021-12-07 15:40:54 PST
(In reply to Antoine Quint from comment #4)
> Not quite! There are 17 cases left to fix, those were just the easy ones.
> I'll look into fixing the other ones piecemeal.

!!

> Felt right to put it in line with the rest.

Agreed.
Comment 6 Antoine Quint 2021-12-14 03:12:13 PST
Created attachment 447120 [details]
Patch for landing
Comment 7 Radar WebKit Bug Importer 2021-12-14 11:52:15 PST
<rdar://problem/86481861>
Comment 8 EWS 2021-12-15 01:17:16 PST
Committed r287066 (245261@main): <https://commits.webkit.org/245261@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 447120 [details].
Comment 9 Antoine Quint 2021-12-15 03:02:25 PST
Lots to do on this left, this first commit was only part of the work.
Comment 10 Antoine Quint 2021-12-15 08:24:26 PST
Created attachment 447234 [details]
Patch
Comment 11 Chris Dumez 2021-12-15 08:38:27 PST
Comment on attachment 447234 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=447234&action=review

r=me with suggestions.

> Source/WebCore/Modules/indexeddb/IDBIndex.cpp:45
> +std::unique_ptr<IDBIndex> IDBIndex::create(ScriptExecutionContext& context, const IDBIndexInfo& info, IDBObjectStore& objectStore)

We may want to return a UniqueRef instead since this cannot return null.

> Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp:58
> +std::unique_ptr<IDBObjectStore> IDBObjectStore::create(ScriptExecutionContext& context, const IDBObjectStoreInfo& info, IDBTransaction& transaction)

ditto.
Comment 12 Antoine Quint 2021-12-15 09:42:56 PST
Created attachment 447245 [details]
Patch
Comment 13 Chris Dumez 2021-12-15 11:19:00 PST
Comment on attachment 447245 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=447245&action=review

> Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp:532
> +    m_referencedIndexes.set(indexName, std::unique_ptr<IDBIndex>(&index));

You want `index.moveToUniquePtr()`. This adopts the pointer so you will double-free.
Comment 14 Antoine Quint 2021-12-15 11:34:17 PST
Created attachment 447262 [details]
Patch
Comment 15 Antoine Quint 2021-12-15 12:22:10 PST
Created attachment 447270 [details]
Patch
Comment 16 Darin Adler 2021-12-15 12:35:49 PST
Comment on attachment 447270 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=447270&action=review

> Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp:530
> +    Ref<IDBIndex> referencedIndex { index };

Since we’re touching this code, it can likely be changed to Ref instead of Ref<IDBIndex> since we have type deduction now.

> Source/WebCore/Modules/indexeddb/IDBTransaction.cpp:753
> +    auto index = IDBIndex::create(*scriptExecutionContext(), info, objectStore);
> +    return index.moveToUniquePtr();

Should just be able to write:

    return IDBIndex::create(*scriptExecutionContext(), info, objectStore);

Move construction of unique_ptr from UniqueRef should work.
Comment 17 Antoine Quint 2021-12-15 12:46:44 PST
Created attachment 447279 [details]
Patch for landing
Comment 18 Antoine Quint 2021-12-15 22:21:15 PST
Created attachment 447327 [details]
Patch for landing
Comment 19 Antoine Quint 2021-12-16 03:31:27 PST
Committed r287129 (?): <https://commits.webkit.org/r287129>
Comment 20 Antoine Quint 2021-12-16 05:17:46 PST
Created attachment 447346 [details]
Patch
Comment 21 Antoine Quint 2021-12-16 06:20:23 PST
Created attachment 447349 [details]
Patch
Comment 22 Darin Adler 2021-12-16 11:09:23 PST
Comment on attachment 447349 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=447349&action=review

Looks fantastic

> Source/WebCore/Modules/entriesapi/FileSystemEntry.cpp:57
> +void FileSystemEntry::initialize()
> +{
> +    suspendIfNeeded();
> +}

Why do we need these initialize functions instead of calling suspendIfNeeded directly?

I’m sure that you thought about a reason, but I don’t know what it is. Would be nice to have one less function if there’s no abstraction here and suspendIfNeeded would do the same thing.

If what we’re trying to mitigate is callers neglecting to call suspendIfNeeded, I’m not sure that renaming it to initialize will help. If we do want to tackle that problem there may be some compile time techniques or some runtime ones, like adding a debug-only boolean set every time suspendIfNeeded and finding a good place to assert afterward to catch cases where it wasn’t called even once. Could even use a micro-task to do that assertion.

> Source/WebCore/Modules/fetch/FetchBodyOwner.cpp:60
> +void FetchBodyOwner::initialize()
> +{
> +    suspendIfNeeded();
> +}

Ditto.

> Source/WebCore/Modules/fetch/FetchRequest.h:57
> +    static Ref<FetchRequest> create(ScriptExecutionContext&, std::optional<FetchBody>&&, Ref<FetchHeaders>&&, ResourceRequest&&, FetchOptions&&, String&&);

Not sure we should have removed the name "referrer".

> Source/WebCore/Modules/filesystemaccess/FileSystemHandle.cpp:51
> +void FileSystemHandle::initialize()
> +{
> +    suspendIfNeeded();
> +}

Ditto.

> Source/WebCore/Modules/mediastream/RTCDTMFSender.cpp:47
> +    auto result = adoptRef(* new RTCDTMFSender(context, sender, WTFMove(backend)));

Maybe get rid of the space between the "*" and "new"?

> Source/WebCore/workers/service/ServiceWorkerContainer.h:44
> +#include <wtf/UniqueRef.h>

Not something that should block landing this patch as-is, but: This should require only a forward-declaration of UniqueRef, which should be provided by Forward.h. Files where ServiceWorkerContainer::create are called will need UniquerRef.h, but this header should not require it.

> Source/WebCore/workers/service/ServiceWorkerContainer.h:61
> +    static UniqueRef<ServiceWorkerContainer> create(ScriptExecutionContext*, NavigatorBase&);

Another thing not about this patch: Do you know why this takes ScriptExecutionContext* rather than ScriptExecutionContext&? Is nullptr OK?
Comment 23 Antoine Quint 2021-12-16 12:52:15 PST
(In reply to Darin Adler from comment #22)
> Comment on attachment 447349 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=447349&action=review
> 
> Looks fantastic
> 
> > Source/WebCore/Modules/entriesapi/FileSystemEntry.cpp:57
> > +void FileSystemEntry::initialize()
> > +{
> > +    suspendIfNeeded();
> > +}
> 
> Why do we need these initialize functions instead of calling suspendIfNeeded
> directly?
> 
> I’m sure that you thought about a reason, but I don’t know what it is. Would
> be nice to have one less function if there’s no abstraction here and
> suspendIfNeeded would do the same thing.
> 
> If what we’re trying to mitigate is callers neglecting to call
> suspendIfNeeded, I’m not sure that renaming it to initialize will help. If
> we do want to tackle that problem there may be some compile time techniques
> or some runtime ones, like adding a debug-only boolean set every time
> suspendIfNeeded and finding a good place to assert afterward to catch cases
> where it wasn’t called even once. Could even use a micro-task to do that
> assertion.

That was exactly my thought. If I were subclassing a class and looking at an existing subclass, I would see `initialize()` and think "I better call this", but if I saw `suspendIfNeeded()` without knowing necessarily much about ActiveDOMObject, it might not be as clear that it should be called.

But this is just me, and it might in fact be a missed opportunity for the person subclassing to ask themselves the right questions and engage with a domain expert about what `suspendIfNeeded()` is.

So I'm leaning towards removing initialize() and replacing its calls with `suspendIfNeeded()`.

> > Source/WebCore/Modules/fetch/FetchRequest.h:57
> > +    static Ref<FetchRequest> create(ScriptExecutionContext&, std::optional<FetchBody>&&, Ref<FetchHeaders>&&, ResourceRequest&&, FetchOptions&&, String&&);
> 
> Not sure we should have removed the name "referrer".

I'll put it back.

> > Source/WebCore/Modules/mediastream/RTCDTMFSender.cpp:47
> > +    auto result = adoptRef(* new RTCDTMFSender(context, sender, WTFMove(backend)));
> 
> Maybe get rid of the space between the "*" and "new"?

Yes, copying-and-pasting without thinking.

> > Source/WebCore/workers/service/ServiceWorkerContainer.h:44
> > +#include <wtf/UniqueRef.h>
> 
> Not something that should block landing this patch as-is, but: This should
> require only a forward-declaration of UniqueRef, which should be provided by
> Forward.h. Files where ServiceWorkerContainer::create are called will need
> UniquerRef.h, but this header should not require it.

OK, I will switch to a forward declaration and hopefully the bots will prove that is just fine.

> > Source/WebCore/workers/service/ServiceWorkerContainer.h:61
> > +    static UniqueRef<ServiceWorkerContainer> create(ScriptExecutionContext*, NavigatorBase&);
> 
> Another thing not about this patch: Do you know why this takes
> ScriptExecutionContext* rather than ScriptExecutionContext&? Is nullptr OK?

I don't know, but the ActiveDOMObject constructor takes a ScriptExecutionContext* and this just passes this along to that constructor:

ActiveDOMObject::ActiveDOMObject(ScriptExecutionContext* scriptExecutionContext)
    : ActiveDOMObject(suitableScriptExecutionContext(scriptExecutionContext), CheckedScriptExecutionContext)
{
}

… which calls this method:

static inline ScriptExecutionContext* suitableScriptExecutionContext(ScriptExecutionContext* scriptExecutionContext)
{
    // For detached documents, make sure we observe their context document instead.
    return is<Document>(scriptExecutionContext) ? &downcast<Document>(*scriptExecutionContext).contextDocument() : scriptExecutionContext;
}

… and calls this other constructor:

inline ActiveDOMObject::ActiveDOMObject(ScriptExecutionContext* context, CheckedScriptExecutionContextType)
    : ContextDestructionObserver(context)
{
    ASSERT(!is<Document>(context) || &downcast<Document>(context)->contextDocument() == downcast<Document>(context));
    if (!context)
        return;

    ASSERT(context->isContextThread());
    context->didCreateActiveDOMObject(*this);
}
Comment 24 Antoine Quint 2021-12-17 00:10:12 PST
Created attachment 447437 [details]
Patch for landing
Comment 25 Antoine Quint 2021-12-17 03:58:42 PST
Committed r287177 (245346@trunk): <https://commits.webkit.org/245346@trunk>
Comment 26 Antoine Quint 2021-12-17 04:11:52 PST
Created attachment 447447 [details]
Patch
Comment 27 Antoine Quint 2021-12-17 04:23:53 PST
Created attachment 447448 [details]
Patch
Comment 28 Chris Dumez 2021-12-17 07:26:47 PST
Comment on attachment 447448 [details]
Patch

r=me
Comment 29 EWS 2021-12-17 07:52:57 PST
Committed r287186 (245353@main): <https://commits.webkit.org/245353@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 447448 [details].
Comment 30 Darin Adler 2021-12-17 10:36:32 PST
We should also consider adding a checker that asserts if we make this error, to make sure this mistake doesn’t creep back in. I have some ideas about how to add that, for any objects that are put into our Ref/RefPtr/UniqueRef after creating them. No idea how to do the same for objects that go into unique_ptr, though.