Bug 21329 - REGRESSION: crash in ScriptElement::notifyFinished
Summary: REGRESSION: crash in ScriptElement::notifyFinished
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: 528+ (Nightly build)
Hardware: PC Windows XP
: P1 Critical
Assignee: Darin Fisher (:fishd, Google)
URL: http://www.kino.de/kinosuche.php4
Keywords:
Depends on:
Blocks:
 
Reported: 2008-10-03 01:05 PDT by Darin Fisher (:fishd, Google)
Modified: 2008-10-06 06:08 PDT (History)
3 users (show)

See Also:


Attachments
v1 patch (1.43 KB, patch)
2008-10-03 17:14 PDT, Darin Fisher (:fishd, Google)
no flags Details | Formatted Diff | Diff
v2 patch - same as before, but includes bug title in ChangeLog (1.48 KB, patch)
2008-10-03 18:08 PDT, Darin Fisher (:fishd, Google)
eric: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Darin Fisher (:fishd, Google) 2008-10-03 01:05:22 PDT
REGRESSION: crash in ScriptElement::notifyFinished

I observed this crash on http://www.kino.de/kinosuche.php4
(you might have to load that site a couple times to repro the crash)

I found that some code changed recently that probably explains the crash.

Compare:

-void HTMLScriptElement::notifyFinished(CachedResource* o)
-{
-    CachedScript* cs = static_cast<CachedScript*>(o);
-
-    ASSERT(cs == m_cachedScript);
-
-    // Evaluating the script could lead to a garbage collection which
-    // can delete the script element so we need to protect it.
-    RefPtr<HTMLScriptElement> protect(this);
-    
-    if (cs->errorOccurred())
-        dispatchHTMLEvent(errorEvent, true, false);
-    else {
-        evaluateScript(cs->url(), cs->script());
-        dispatchHTMLEvent(loadEvent, false, false);
-    }
-
-    // script evaluation may have dereffed it already
-    if (m_cachedScript) {
-        m_cachedScript->removeClient(this);
-        m_cachedScript = 0;
-    }
-}

To:

void ScriptElementData::notifyFinished(CachedResource* o)
{
    CachedScript* cs = static_cast<CachedScript*>(o);
    ASSERT(cs == m_cachedScript);

    if (cs->errorOccurred())
        m_scriptElement->dispatchErrorEvent();
    else {
        RefPtr<Element> protector(m_element);
        evaluateScript(cs->url(), cs->script());
        m_scriptElement->dispatchLoadEvent();
    }

    stopLoadRequest();
}

The crash I get is inside stopLoadRequest() with |this| having been trashed.  m_element owns |this|.

Here's the interesting portion of the crash stack:

 	chrome.dll!WTF::HashTable<WebCore::RenderObject *,std::pair<WebCore::RenderObject *,WebCore::RenderBlock::FloatingObject 
*>,WTF::PairFirstExtractor<std::pair<WebCore::RenderObject *,WebCore::RenderBlock::FloatingObject *> >,WTF::PtrHash<WebCore::RenderObject 
*>,WTF::PairHashTraits<WTF::HashTraits<WebCore::RenderObject *>,WTF::HashTraits<WebCore::RenderBlock::FloatingObject *> 
>,WTF::HashTraits<WebCore::RenderObject *> >::find<WebCore::RenderObject *,WTF::IdentityHashTranslator<WebCore::RenderObject 
*,std::pair<WebCore::RenderObject *,WebCore::RenderBlock::FloatingObject *>,WTF::PtrHash<WebCore::RenderObject *> > >(WebCore::RenderObject * const & 
key=0x0232775c)  Line 751	C++
 	chrome.dll!WebCore::CachedResource::removeClient(WebCore::CachedResourceClient * c=0x0232775c)  Line 131 + 0x12 bytes	C++
>	chrome.dll!WebCore::ScriptElementData::notifyFinished(WebCore::CachedResource * o=0x02330e58)  Line 191 + 0xd bytes	C++
 	chrome.dll!WebCore::CachedScript::checkNotify()  Line 95 + 0xa bytes	C++
 	chrome.dll!WebCore::CachedScript::data(WTF::PassRefPtr<WebCore::SharedBuffer> data={...}, bool allDataReceived=true)  Line 85 + 0xe bytes	
C++
 	chrome.dll!WebCore::Loader::Host::didFinishLoading(WebCore::SubresourceLoader * loader=0x034297f8)  Line 278	C++
 	chrome.dll!WebCore::SubresourceLoader::didFinishLoading()  Line 195	C++
 	chrome.dll!WebCore::ResourceLoader::didFinishLoading(WebCore::ResourceHandle * __formal=0x01f31cf8)  Line 399	C++

Originally identified here:
http://code.google.com/p/chromium/issues/detail?id=3056

In the old code, the protector was also ensuring that |this| remained valid until after stopLoadRequest() is called.  I think this is an apparent goof in the recent code change.
Comment 1 Alexey Proskuryakov 2008-10-03 01:11:31 PDT
(In reply to comment #0)
> I found that some code changed recently that probably explains the crash.

<http://trac.webkit.org/changeset/35744>.
Comment 2 Darin Fisher (:fishd, Google) 2008-10-03 01:16:13 PDT
This patch, which essentially just rolls back part of the original change, seems to resolve the bug.  I'll work on a layout test, but it may be challenging since it is dependent on GC running at the right time.

Index: ScriptElement.cpp
===================================================================
--- ScriptElement.cpp   (revision 2802)
+++ ScriptElement.cpp   (working copy)
@@ -180,10 +180,13 @@
     CachedScript* cs = static_cast<CachedScript*>(o);
     ASSERT(cs == m_cachedScript);
+    // Evaluating the script could lead to a garbage collection which can
+    // delete the script element so we need to protect it.
+    RefPtr<Element> protector(m_element);
+
     if (cs->errorOccurred())
         m_scriptElement->dispatchErrorEvent();
     else {
-        RefPtr<Element> protector(m_element);
         evaluateScript(cs->url(), cs->script());
         m_scriptElement->dispatchLoadEvent();
     }
Comment 3 Eric Seidel (no email) 2008-10-03 11:30:28 PDT
(In reply to comment #2)
> This patch, which essentially just rolls back part of the original change,
> seems to resolve the bug.  I'll work on a layout test, but it may be
> challenging since it is dependent on GC running at the right time.
> 
> Index: ScriptElement.cpp
> ===================================================================
> --- ScriptElement.cpp   (revision 2802)
> +++ ScriptElement.cpp   (working copy)
> @@ -180,10 +180,13 @@
>      CachedScript* cs = static_cast<CachedScript*>(o);
>      ASSERT(cs == m_cachedScript);
> +    // Evaluating the script could lead to a garbage collection which can
> +    // delete the script element so we need to protect it.
> +    RefPtr<Element> protector(m_element);
> +
>      if (cs->errorOccurred())
>          m_scriptElement->dispatchErrorEvent();
>      else {
> -        RefPtr<Element> protector(m_element);
>          evaluateScript(cs->url(), cs->script());
>          m_scriptElement->dispatchLoadEvent();
>      }
> 

The change looks good.  We just need a changelog, and ideally a layout test.

gc() or GCController.collect() should be able to kick off GC when you need.
Comment 4 Darin Fisher (:fishd, Google) 2008-10-03 17:14:38 PDT
Created attachment 24076 [details]
v1 patch

Here's the patch sans layout test.  Try as I might, I could not get my Mac build of WebKit to crash upon reaching ScriptElementData::stopLoadRequest despite having a junk |this| pointer.
Comment 5 Darin Fisher (:fishd, Google) 2008-10-03 18:08:53 PDT
Created attachment 24077 [details]
v2 patch - same as before, but includes bug title in ChangeLog

I tried using GuardMalloc to help create a test, but it seems like there is something else besides this issue that is causing DRT to crash under GuardMalloc.  For example, fast/dom/script-element-gc.html crashes with or without my fix under GuardMalloc.  I don't know how to get a call stack when GuardMalloc detects an error, so I don't know why DRT is crashing under GuardMalloc.
Comment 6 Eric Seidel (no email) 2008-10-04 02:59:40 PDT
Comment on attachment 24077 [details]
v2 patch - same as before, but includes bug title in ChangeLog

Looks great.  Thanks for fixing this.  It would be better w/ a layout test, but from discussions in the channel it sounds like you were having trouble getting one to work on your Mac.

I think you have commit-bit these days, so you should be able to land this yourself.
Comment 7 Eric Seidel (no email) 2008-10-04 03:00:37 PDT
(In reply to comment #5)
> Created an attachment (id=24077) [edit]
> v2 patch - same as before, but includes bug title in ChangeLog
> 
> I tried using GuardMalloc to help create a test, but it seems like there is
> something else besides this issue that is causing DRT to crash under
> GuardMalloc.  For example, fast/dom/script-element-gc.html crashes with or
> without my fix under GuardMalloc.  I don't know how to get a call stack when
> GuardMalloc detects an error, so I don't know why DRT is crashing under
> GuardMalloc.
> 

We need to file any --guard crashes for run-webkit-tests as bugs.   Or rather, when you see any, don't hesitate to file them.  We really should have a buildbot which runs run-webkit-tests --guard once a week or something.
Comment 8 Darin Fisher (:fishd, Google) 2008-10-04 23:51:05 PDT
I filed bug 21380 about how "run-webkit-tests -g" just crashes for me on seemingly every test.
Comment 9 Nikolas Zimmermann 2008-10-06 06:08:01 PDT
Oops, good catch! My fault while refactoring the code... Sorry for any headaches.