diff --git a/modules/plugin/test/mochitest/Makefile.in b/modules/plugin/test/mochitest/Makefile.in index 1690595465e..12a6c5ea1db 100644 --- a/modules/plugin/test/mochitest/Makefile.in +++ b/modules/plugin/test/mochitest/Makefile.in @@ -117,6 +117,7 @@ endif ifeq (gtk2,$(MOZ_WIDGET_TOOLKIT)) _MOCHITEST_FILES += \ test_copyText.html \ + test_crash_nested_loop.html \ $(NULL) endif diff --git a/modules/plugin/test/mochitest/test_crash_nested_loop.html b/modules/plugin/test/mochitest/test_crash_nested_loop.html new file mode 100644 index 00000000000..bad44294d33 --- /dev/null +++ b/modules/plugin/test/mochitest/test_crash_nested_loop.html @@ -0,0 +1,40 @@ + + Plugin crashing in nested loop + + + + + + + diff --git a/modules/plugin/test/testplugin/nptest.cpp b/modules/plugin/test/testplugin/nptest.cpp index 5e996ebf68a..a23cb1f28c2 100644 --- a/modules/plugin/test/testplugin/nptest.cpp +++ b/modules/plugin/test/testplugin/nptest.cpp @@ -60,6 +60,8 @@ #define PLUGIN_VERSION "1.0.0.0" #define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0])) +#define STATIC_ASSERT(condition) \ + extern void np_static_assert(int arg[(condition) ? 1 : -1]) // // Intentional crash @@ -67,7 +69,7 @@ int gCrashCount = 0; -static void +void NoteIntentionalCrash() { char* bloatLog = getenv("XPCOM_MEM_BLOAT_LOG"); @@ -157,6 +159,7 @@ static bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount static bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); static bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); static bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); static const NPUTF8* sPluginMethodIdentifierNames[] = { @@ -200,10 +203,11 @@ static const NPUTF8* sPluginMethodIdentifierNames[] = { "getClipboardText", "callOnDestroy", "reinitWidget", + "crashInNestedLoop", "propertyAndMethod" }; static NPIdentifier sPluginMethodIdentifiers[ARRAY_LENGTH(sPluginMethodIdentifierNames)]; -static const ScriptableFunction sPluginMethodFunctions[ARRAY_LENGTH(sPluginMethodIdentifierNames)] = { +static const ScriptableFunction sPluginMethodFunctions[] = { npnEvaluateTest, npnInvokeTest, npnInvokeDefaultTest, @@ -244,8 +248,13 @@ static const ScriptableFunction sPluginMethodFunctions[ARRAY_LENGTH(sPluginMetho getClipboardText, callOnDestroy, reinitWidget, + crashPluginInNestedLoop, propertyAndMethod }; + +STATIC_ASSERT(ARRAY_LENGTH(sPluginMethodIdentifierNames) == + ARRAY_LENGTH(sPluginMethodFunctions)); + static const NPUTF8* sPluginPropertyIdentifierNames[] = { "propertyAndMethod" }; @@ -2711,12 +2720,29 @@ getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, return true; } +bool +crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + return pluginCrashInNestedLoop(id); +} + #else bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) { - /// XXX Not implemented! + // XXX Not implemented! + return false; +} + +bool +crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + // XXX Not implemented! return false; } #endif diff --git a/modules/plugin/test/testplugin/nptest.h b/modules/plugin/test/testplugin/nptest.h index 327efcea8f7..779d4f9b795 100644 --- a/modules/plugin/test/testplugin/nptest.h +++ b/modules/plugin/test/testplugin/nptest.h @@ -127,4 +127,6 @@ typedef struct InstanceData { void notifyDidPaint(InstanceData* instanceData); +void NoteIntentionalCrash(); + #endif // nptest_h_ diff --git a/modules/plugin/test/testplugin/nptest_gtk2.cpp b/modules/plugin/test/testplugin/nptest_gtk2.cpp index ef5a9d073a5..b395ed389c4 100644 --- a/modules/plugin/test/testplugin/nptest_gtk2.cpp +++ b/modules/plugin/test/testplugin/nptest_gtk2.cpp @@ -35,12 +35,15 @@ #include "nptest_platform.h" #include "npapi.h" +#include #include #ifdef MOZ_X11 #include #include #endif +#include #include +#include using namespace std; @@ -635,3 +638,79 @@ pluginGetClipboardText(InstanceData* instanceData) return retText; } + +//----------------------------------------------------------------------------- +// NB: this test is quite gross in that it's not only +// nondeterministic, but dependent on the guts of the nested glib +// event loop handling code in PluginModule. We first sleep long +// enough to make sure that the "detection timer" will be pending when +// we enter the nested glib loop, then similarly for the "process browser +// events" timer. Then we "schedule" the crasher thread to run at about the +// same time we expect that the PluginModule "process browser events" task +// will run. If all goes well, the plugin process will crash and generate the +// XPCOM "plugin crashed" task, and the browser will run that task while still +// in the "process some events" loop. + +static void* +CrasherThread(void* data) +{ + // Give the parent thread a chance to send the message. + usleep(200); + + // Exit (without running atexit hooks) rather than crashing with a signal + // so as to make timing more reliable. The process terminates immediately + // rather than waiting for a thread in the parent process to attach and + // generate a minidump. + _exit(1); + + // not reached + return(NULL); +} + +bool +pluginCrashInNestedLoop(InstanceData* instanceData) +{ + // wait at least long enough for nested loop detector task to be pending ... + sleep(1); + + // Run the nested loop detector by processing all events that are waiting. + bool found_event = false; + while (g_main_context_iteration(NULL, FALSE)) { + found_event = true; + } + if (!found_event) { + g_warning("DetectNestedEventLoop did not fire"); + return true; // trigger a test failure + } + + // wait at least long enough for the "process browser events" task to be + // pending ... + sleep(1); + + // we'll be crashing soon, note that fact now to avoid messing with + // timing too much + NoteIntentionalCrash(); + + // schedule the crasher thread ... + pthread_t crasherThread; + if (0 != pthread_create(&crasherThread, NULL, CrasherThread, NULL)) { + g_warning("Failed to create thread"); + return true; // trigger a test failure + } + + // .. and hope it crashes at about the same time as the "process browser + // events" task (that should run in this loop) is being processed in the + // parent. + found_event = false; + while (g_main_context_iteration(NULL, FALSE)) { + found_event = true; + } + if (found_event) { + g_warning("Should have crashed in ProcessBrowserEvents"); + } else { + g_warning("ProcessBrowserEvents did not fire"); + } + + // if we get here without crashing, then we'll trigger a test failure + return true; +} diff --git a/modules/plugin/test/testplugin/nptest_platform.h b/modules/plugin/test/testplugin/nptest_platform.h index 2e85306ecba..08f028edad1 100644 --- a/modules/plugin/test/testplugin/nptest_platform.h +++ b/modules/plugin/test/testplugin/nptest_platform.h @@ -126,4 +126,12 @@ void pluginDoInternalConsistencyCheck(InstanceData* instanceData, std::string& e */ std::string pluginGetClipboardText(InstanceData* instanceData); +/** + * Crash while in a nested event loop. The goal is to catch the + * browser processing the XPCOM event generated from the plugin's + * crash while other plugin code is still on the stack. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=550026. + */ +bool pluginCrashInNestedLoop(InstanceData* instanceData); + #endif // nptest_platform_h_