gecko/js/src/jscompartment.cpp

869 lines
26 KiB
C++
Raw Normal View History

2010-09-24 10:54:39 -07:00
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=99:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
* May 28, 2008.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "jscntxt.h"
2010-09-24 10:54:39 -07:00
#include "jscompartment.h"
#include "jsgc.h"
#include "jsgcmark.h"
#include "jsiter.h"
#include "jsmath.h"
2010-09-24 10:54:39 -07:00
#include "jsproxy.h"
#include "jsscope.h"
#include "jstracer.h"
#include "jswrapper.h"
#include "assembler/wtf/Platform.h"
#include "yarr/BumpPointerAllocator.h"
#include "methodjit/MethodJIT.h"
2010-09-24 10:54:39 -07:00
#include "methodjit/PolyIC.h"
#include "methodjit/MonoIC.h"
#include "vm/Debugger.h"
2010-09-24 10:54:39 -07:00
#include "jsgcinlines.h"
2011-03-13 19:20:06 -07:00
#include "jsscopeinlines.h"
2010-09-24 10:54:39 -07:00
#if ENABLE_YARR_JIT
#include "assembler/jit/ExecutableAllocator.h"
#endif
2010-09-24 10:54:39 -07:00
using namespace js;
using namespace js::gc;
JSCompartment::JSCompartment(JSRuntime *rt)
: rt(rt),
principals(NULL),
gcBytes(0),
gcTriggerBytes(0),
gcLastBytes(0),
hold(false),
#ifdef JS_TRACER
traceMonitor_(NULL),
#endif
data(NULL),
active(false),
hasDebugModeCodeToDrop(false),
#ifdef JS_METHODJIT
jaegerCompartment_(NULL),
#endif
#if ENABLE_YARR_JIT
regExpAllocator(NULL),
#endif
2011-02-25 13:07:29 -08:00
propertyTree(thisForCtor()),
emptyArgumentsShape(NULL),
emptyBlockShape(NULL),
emptyCallShape(NULL),
emptyDeclEnvShape(NULL),
emptyEnumeratorShape(NULL),
emptyWithShape(NULL),
initialRegExpShape(NULL),
initialStringShape(NULL),
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
debugModeBits(rt->debugMode * DebugFromC),
mathCache(NULL),
breakpointSites(rt)
2010-09-24 10:54:39 -07:00
{
JS_INIT_CLIST(&scripts);
PodArrayZero(scriptsToGC);
2010-09-24 10:54:39 -07:00
}
JSCompartment::~JSCompartment()
{
#if ENABLE_YARR_JIT
Foreground::delete_(regExpAllocator);
#endif
#ifdef JS_METHODJIT
Foreground::delete_(jaegerCompartment_);
#endif
#ifdef JS_TRACER
Foreground::delete_(traceMonitor_);
#endif
Foreground::delete_(mathCache);
#ifdef DEBUG
for (size_t i = 0; i != JS_ARRAY_LENGTH(scriptsToGC); ++i)
JS_ASSERT(!scriptsToGC[i]);
#endif
2010-09-24 10:54:39 -07:00
}
bool
JSCompartment::init()
{
chunk = NULL;
for (unsigned i = 0; i < FINALIZE_LIMIT; i++)
arenas[i].init();
freeLists.init();
if (!crossCompartmentWrappers.init())
return false;
if (!scriptFilenameTable.init())
return false;
regExpAllocator = rt->new_<WTF::BumpPointerAllocator>();
if (!regExpAllocator)
return false;
if (!backEdgeTable.init())
return false;
return debuggees.init() && breakpointSites.init();
2010-09-24 10:54:39 -07:00
}
#ifdef JS_METHODJIT
bool
JSCompartment::ensureJaegerCompartmentExists(JSContext *cx)
{
if (jaegerCompartment_)
return true;
mjit::JaegerCompartment *jc = cx->new_<mjit::JaegerCompartment>();
if (!jc)
return false;
if (!jc->Initialize()) {
cx->delete_(jc);
return false;
}
jaegerCompartment_ = jc;
return true;
2010-09-24 10:54:39 -07:00
}
size_t
JSCompartment::getMjitCodeSize() const
{
return jaegerCompartment_ ? jaegerCompartment_->execAlloc()->getCodeSize() : 0;
}
#endif
2010-09-24 10:54:39 -07:00
bool
JSCompartment::arenaListsAreEmpty()
{
for (unsigned i = 0; i < FINALIZE_LIMIT; i++) {
if (!arenas[i].isEmpty())
2010-09-24 10:54:39 -07:00
return false;
}
return true;
}
static bool
IsCrossCompartmentWrapper(JSObject *wrapper)
{
return wrapper->isWrapper() &&
!!(JSWrapper::wrapperHandler(wrapper)->flags() & JSWrapper::CROSS_COMPARTMENT);
}
2010-09-24 10:54:39 -07:00
bool
JSCompartment::wrap(JSContext *cx, Value *vp)
{
JS_ASSERT(cx->compartment == this);
uintN flags = 0;
JS_CHECK_RECURSION(cx, return false);
/* Only GC things have to be wrapped or copied. */
if (!vp->isMarkable())
return true;
if (vp->isString()) {
JSString *str = vp->toString();
/* Static atoms do not have to be wrapped. */
if (str->isStaticAtom())
return true;
/* If the string is already in this compartment, we are done. */
if (str->compartment() == this)
return true;
/* If the string is an atom, we don't have to copy. */
if (str->isAtom()) {
JS_ASSERT(str->compartment() == cx->runtime->atomsCompartment);
return true;
}
}
2010-09-24 10:54:39 -07:00
/*
* Wrappers should really be parented to the wrapped parent of the wrapped
* object, but in that case a wrapped global object would have a NULL
* parent without being a proper global object (JSCLASS_IS_GLOBAL). Instead
,
* we parent all wrappers to the global object in their home compartment.
* This loses us some transparency, and is generally very cheesy.
*/
JSObject *global;
if (cx->hasfp()) {
global = cx->fp()->scopeChain().getGlobal();
} else {
global = cx->globalObject;
if (!NULLABLE_OBJ_TO_INNER_OBJECT(cx, global))
return false;
}
2010-09-24 10:54:39 -07:00
/* Unwrap incoming objects. */
if (vp->isObject()) {
JSObject *obj = &vp->toObject();
/* If the object is already in this compartment, we are done. */
if (obj->compartment() == this)
2010-09-24 10:54:39 -07:00
return true;
/* Translate StopIteration singleton. */
if (obj->getClass() == &js_StopIterationClass)
return js_FindClassObject(cx, NULL, JSProto_StopIteration, vp);
2010-09-24 10:54:39 -07:00
/* Don't unwrap an outer window proxy. */
if (!obj->getClass()->ext.innerObject) {
obj = vp->toObject().unwrap(&flags);
vp->setObject(*obj);
if (obj->getCompartment() == this)
return true;
if (cx->runtime->preWrapObjectCallback) {
obj = cx->runtime->preWrapObjectCallback(cx, global, obj, flags);
if (!obj)
return false;
}
vp->setObject(*obj);
if (obj->getCompartment() == this)
return true;
} else {
if (cx->runtime->preWrapObjectCallback) {
obj = cx->runtime->preWrapObjectCallback(cx, global, obj, flags);
if (!obj)
return false;
}
JS_ASSERT(!obj->isWrapper() || obj->getClass()->ext.innerObject);
vp->setObject(*obj);
2010-09-24 10:54:39 -07:00
}
#ifdef DEBUG
{
JSObject *outer = obj;
OBJ_TO_OUTER_OBJECT(cx, outer);
JS_ASSERT(outer && outer == obj);
}
#endif
2010-09-24 10:54:39 -07:00
}
/* If we already have a wrapper for this value, use it. */
if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) {
*vp = p->value;
if (vp->isObject()) {
JSObject *obj = &vp->toObject();
JS_ASSERT(IsCrossCompartmentWrapper(obj));
if (obj->getParent() != global) {
do {
obj->setParent(global);
obj = obj->getProto();
} while (obj && IsCrossCompartmentWrapper(obj));
}
}
2010-09-24 10:54:39 -07:00
return true;
}
if (vp->isString()) {
Value orig = *vp;
JSString *str = vp->toString();
const jschar *chars = str->getChars(cx);
if (!chars)
return false;
JSString *wrapped = js_NewStringCopyN(cx, chars, str->length());
2010-09-24 10:54:39 -07:00
if (!wrapped)
return false;
vp->setString(wrapped);
return crossCompartmentWrappers.put(orig, *vp);
}
JSObject *obj = &vp->toObject();
/*
* Recurse to wrap the prototype. Long prototype chains will run out of
* stack, causing an error in CHECK_RECURSE.
*
* Wrapping the proto before creating the new wrapper and adding it to the
* cache helps avoid leaving a bad entry in the cache on OOM. But note that
* if we wrapped both proto and parent, we would get infinite recursion
* here (since Object.prototype->parent->proto leads to Object.prototype
* itself).
*/
JSObject *proto = obj->getProto();
if (!wrap(cx, &proto))
return false;
/*
* We hand in the original wrapped object into the wrap hook to allow
* the wrap hook to reason over what wrappers are currently applied
* to the object.
*/
JSObject *wrapper = cx->runtime->wrapObjectCallback(cx, obj, proto, global, flags);
if (!wrapper)
return false;
vp->setObject(*wrapper);
wrapper->setProto(proto);
if (!crossCompartmentWrappers.put(wrapper->getProxyPrivate(), *vp))
return false;
2010-09-24 10:54:39 -07:00
wrapper->setParent(global);
return true;
}
bool
JSCompartment::wrap(JSContext *cx, JSString **strp)
{
AutoValueRooter tvr(cx, StringValue(*strp));
if (!wrap(cx, tvr.addr()))
return false;
*strp = tvr.value().toString();
return true;
}
bool
JSCompartment::wrap(JSContext *cx, JSObject **objp)
{
if (!*objp)
return true;
AutoValueRooter tvr(cx, ObjectValue(**objp));
if (!wrap(cx, tvr.addr()))
return false;
*objp = &tvr.value().toObject();
return true;
}
bool
JSCompartment::wrapId(JSContext *cx, jsid *idp)
{
if (JSID_IS_INT(*idp))
return true;
AutoValueRooter tvr(cx, IdToValue(*idp));
if (!wrap(cx, tvr.addr()))
return false;
return ValueToId(cx, tvr.value(), idp);
}
bool
JSCompartment::wrap(JSContext *cx, PropertyOp *propp)
{
Value v = CastAsObjectJsval(*propp);
if (!wrap(cx, &v))
return false;
*propp = CastAsPropertyOp(v.toObjectOrNull());
return true;
}
bool
JSCompartment::wrap(JSContext *cx, StrictPropertyOp *propp)
{
Value v = CastAsObjectJsval(*propp);
if (!wrap(cx, &v))
return false;
*propp = CastAsStrictPropertyOp(v.toObjectOrNull());
return true;
}
2010-09-24 10:54:39 -07:00
bool
JSCompartment::wrap(JSContext *cx, PropertyDescriptor *desc)
{
return wrap(cx, &desc->obj) &&
(!(desc->attrs & JSPROP_GETTER) || wrap(cx, &desc->getter)) &&
(!(desc->attrs & JSPROP_SETTER) || wrap(cx, &desc->setter)) &&
wrap(cx, &desc->value);
}
bool
JSCompartment::wrap(JSContext *cx, AutoIdVector &props)
{
jsid *vector = props.begin();
jsint length = props.length();
for (size_t n = 0; n < size_t(length); ++n) {
if (!wrapId(cx, &vector[n]))
return false;
}
return true;
}
#if defined JS_METHODJIT && defined JS_MONOIC
/*
* Check if the pool containing the code for jit should be destroyed, per the
* heuristics in JSCompartment::sweep.
*/
static inline bool
ScriptPoolDestroyed(JSContext *cx, mjit::JITScript *jit,
uint32 releaseInterval, uint32 &counter)
{
JSC::ExecutablePool *pool = jit->code.m_executablePool;
if (pool->m_gcNumber != cx->runtime->gcNumber) {
/*
* The m_destroy flag may have been set in a previous GC for a pool which had
* references we did not remove (e.g. from the compartment's ExecutableAllocator)
* and is still around. Forget we tried to destroy it in such cases.
*/
pool->m_destroy = false;
pool->m_gcNumber = cx->runtime->gcNumber;
if (--counter == 0) {
pool->m_destroy = true;
counter = releaseInterval;
}
}
return pool->m_destroy;
}
#endif
/*
* This method marks pointers that cross compartment boundaries. It should be
* called only for per-compartment GCs, since full GCs naturally follow pointers
* across compartments.
*/
void
JSCompartment::markCrossCompartmentWrappers(JSTracer *trc)
{
JS_ASSERT(trc->context->runtime->gcCurrentCompartment);
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront())
MarkValue(trc, e.front().key, "cross-compartment wrapper");
}
2010-09-24 10:54:39 -07:00
void
JSCompartment::sweep(JSContext *cx, uint32 releaseInterval)
2010-09-24 10:54:39 -07:00
{
chunk = NULL;
2010-09-24 10:54:39 -07:00
/* Remove dead wrappers from the table. */
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
JS_ASSERT_IF(IsAboutToBeFinalized(cx, e.front().key.toGCThing()) &&
!IsAboutToBeFinalized(cx, e.front().value.toGCThing()),
e.front().key.isString());
if (IsAboutToBeFinalized(cx, e.front().key.toGCThing()) ||
IsAboutToBeFinalized(cx, e.front().value.toGCThing())) {
2010-09-24 10:54:39 -07:00
e.removeFront();
}
2010-09-24 10:54:39 -07:00
}
/* Remove dead empty shapes. */
if (emptyArgumentsShape && IsAboutToBeFinalized(cx, emptyArgumentsShape))
emptyArgumentsShape = NULL;
if (emptyBlockShape && IsAboutToBeFinalized(cx, emptyBlockShape))
emptyBlockShape = NULL;
if (emptyCallShape && IsAboutToBeFinalized(cx, emptyCallShape))
emptyCallShape = NULL;
if (emptyDeclEnvShape && IsAboutToBeFinalized(cx, emptyDeclEnvShape))
emptyDeclEnvShape = NULL;
if (emptyEnumeratorShape && IsAboutToBeFinalized(cx, emptyEnumeratorShape))
emptyEnumeratorShape = NULL;
if (emptyWithShape && IsAboutToBeFinalized(cx, emptyWithShape))
emptyWithShape = NULL;
if (initialRegExpShape && IsAboutToBeFinalized(cx, initialRegExpShape))
initialRegExpShape = NULL;
if (initialStringShape && IsAboutToBeFinalized(cx, initialStringShape))
initialStringShape = NULL;
sweepBreakpoints(cx);
#ifdef JS_TRACER
if (hasTraceMonitor())
traceMonitor()->sweep(cx);
#endif
#if defined JS_METHODJIT && defined JS_MONOIC
/*
* The release interval is the frequency with which we should try to destroy
* executable pools by releasing all JIT code in them, zero to never destroy pools.
* Initialize counter so that the first pool will be destroyed, and eventually drive
* the amount of JIT code in never-used compartments to zero. Don't discard anything
* for compartments which currently have active stack frames.
*/
uint32 counter = 1;
bool discardScripts = !active && (releaseInterval != 0 || hasDebugModeCodeToDrop);
if (discardScripts)
hasDebugModeCodeToDrop = false;
for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) {
JSScript *script = reinterpret_cast<JSScript *>(cursor);
if (script->hasJITCode()) {
mjit::ic::SweepCallICs(cx, script, discardScripts);
if (discardScripts) {
if (script->jitNormal &&
ScriptPoolDestroyed(cx, script->jitNormal, releaseInterval, counter)) {
mjit::ReleaseScriptCode(cx, script);
continue;
}
if (script->jitCtor &&
ScriptPoolDestroyed(cx, script->jitCtor, releaseInterval, counter)) {
mjit::ReleaseScriptCode(cx, script);
}
}
}
}
#endif /* JS_METHODJIT && JS_MONOIC */
active = false;
2010-09-24 10:54:39 -07:00
}
void
JSCompartment::purge(JSContext *cx)
{
freeLists.purge();
dtoaCache.purge();
/* Destroy eval'ed scripts. */
js_DestroyScriptsToGC(cx, this);
nativeIterCache.purge();
toSourceCache.destroyIfConstructed();
#ifdef JS_TRACER
/*
* If we are about to regenerate shapes, we have to flush the JIT cache,
* which will eventually abort any current recording.
*/
if (cx->runtime->gcRegenShapes)
if (hasTraceMonitor())
traceMonitor()->needFlush = JS_TRUE;
#endif
2010-09-24 10:54:39 -07:00
#ifdef JS_METHODJIT
js::CheckCompartmentScripts(this);
2010-09-24 10:54:39 -07:00
for (JSScript *script = (JSScript *)scripts.next;
&script->links != &scripts;
script = (JSScript *)script->links.next) {
if (script->hasJITCode()) {
2010-09-24 10:54:39 -07:00
# if defined JS_POLYIC
mjit::ic::PurgePICs(cx, script);
# endif
# if defined JS_MONOIC
/*
* MICs do not refer to data which can be GC'ed and do not generate stubs
* which might need to be discarded, but are sensitive to shape regeneration.
2010-09-24 10:54:39 -07:00
*/
if (cx->runtime->gcRegenShapes)
mjit::ic::PurgeMICs(cx, script);
# endif
}
}
#endif
}
MathCache *
JSCompartment::allocMathCache(JSContext *cx)
{
JS_ASSERT(!mathCache);
mathCache = cx->new_<MathCache>();
if (!mathCache)
js_ReportOutOfMemory(cx);
return mathCache;
}
#ifdef JS_TRACER
TraceMonitor *
JSCompartment::allocAndInitTraceMonitor(JSContext *cx)
{
JS_ASSERT(!traceMonitor_);
traceMonitor_ = cx->new_<TraceMonitor>();
if (!traceMonitor_)
return NULL;
if (!traceMonitor_->init(cx->runtime)) {
Foreground::delete_(traceMonitor_);
return NULL;
}
return traceMonitor_;
}
#endif
size_t
JSCompartment::backEdgeCount(jsbytecode *pc) const
{
if (BackEdgeMap::Ptr p = backEdgeTable.lookup(pc))
return p->value;
return 0;
}
size_t
JSCompartment::incBackEdgeCount(jsbytecode *pc)
{
if (BackEdgeMap::Ptr p = backEdgeTable.lookupWithDefault(pc, 0))
return ++p->value;
return 1; /* oom not reported by backEdgeTable, so ignore. */
}
bool
JSCompartment::isAboutToBeCollected(JSGCInvocationKind gckind)
{
return !hold && (arenaListsAreEmpty() || gckind == GC_LAST_CONTEXT);
}
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
bool
JSCompartment::hasScriptsOnStack(JSContext *cx)
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
{
for (AllFramesIter i(cx->stack.space()); !i.done(); ++i) {
JSScript *script = i.fp()->maybeScript();
if (script && script->compartment == this)
return true;
}
return false;
}
bool
JSCompartment::setDebugModeFromC(JSContext *cx, bool b)
{
bool enabledBefore = debugMode();
bool enabledAfter = (debugModeBits & ~uintN(DebugFromC)) || b;
// Debug mode can be enabled only when no scripts from the target
// compartment are on the stack. It would even be incorrect to discard just
// the non-live scripts' JITScripts because they might share ICs with live
// scripts (bug 632343).
//
// We do allow disabling debug mode while scripts are on the stack. In
// that case the debug-mode code for those scripts remains, so subsequently
// hooks may be called erroneously, even though debug mode is supposedly
// off, and we have to live with it.
//
bool onStack = false;
if (enabledBefore != enabledAfter) {
onStack = hasScriptsOnStack(cx);
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
if (b && onStack) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_IDLE);
return false;
}
}
debugModeBits = (debugModeBits & ~uintN(DebugFromC)) | (b * DebugFromC);
JS_ASSERT(debugMode() == enabledAfter);
if (enabledBefore != enabledAfter && !onStack)
updateForDebugMode(cx);
return true;
}
void
JSCompartment::updateForDebugMode(JSContext *cx)
{
#ifdef JS_METHODJIT
bool enabled = debugMode();
if (enabled) {
JS_ASSERT(!hasScriptsOnStack(cx));
} else if (hasScriptsOnStack(cx)) {
hasDebugModeCodeToDrop = true;
return;
}
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
// Discard JIT code for any scripts that change debugMode. This assumes
// that 'comp' is in the same thread as 'cx'.
for (JSScript *script = (JSScript *) scripts.next;
&script->links != &scripts;
script = (JSScript *) script->links.next)
{
if (script->debugMode != enabled) {
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
mjit::ReleaseScriptCode(cx, script);
script->debugMode = enabled;
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
}
}
hasDebugModeCodeToDrop = false;
Automatically turn debug mode on/off when adding/removing debuggees. This allows most of the tests to run without the -d command-line flag. Now a compartment is in debug mode if * JSD1 wants debug mode on, thanks to a JS_SetDebugMode* call; OR * JSD2 wants debug mode on, because a live Debug object has a debuggee global in that compartment. Since this patch only adds the second half of the rule, JSD1 should be unaffected. The new rule has three issues: 1. When removeDebuggee is called, it can cause debug mode to be turned off for a compartment. If any scripts from that compartment are on the stack, and the methodjit is enabled, returning to those stack frames will crash. 2. When a Debug object is GC'd, it can cause debug mode to be turned off for one or more compartments. This causes the same problem with returning to deleted methodjit code, but the fix is different: such Debug objects simply should not be GC'd. 3. Setting .enabled to false still does not turn off debug mode anywhere, so it does not reduce overhead as much as it should. A possible fix for issue #1 would be to make such removeDebuggee calls throw; a different possibility is to turn off debug mode but leave all the scripts alone, accepting the performance loss (as we do for JSD1 in JSCompartment::setDebugModeFromC). The fix to issues #2 and #3 is to tweak the rule--and to tweak the rule for Debug object GC-reachability. --HG-- rename : js/src/jit-test/tests/debug/Debug-ctor.js => js/src/jit-test/tests/debug/Debug-ctor-01.js
2011-06-02 19:58:46 -07:00
#endif
}
bool
JSCompartment::addDebuggee(JSContext *cx, js::GlobalObject *global)
{
bool wasEnabled = debugMode();
if (!debuggees.put(global)) {
js_ReportOutOfMemory(cx);
return false;
}
debugModeBits |= DebugFromJS;
if (!wasEnabled)
updateForDebugMode(cx);
return true;
}
void
JSCompartment::removeDebuggee(JSContext *cx,
js::GlobalObject *global,
js::GlobalObjectSet::Enum *debuggeesEnum)
{
bool wasEnabled = debugMode();
JS_ASSERT(debuggees.has(global));
if (debuggeesEnum)
debuggeesEnum->removeFront();
else
debuggees.remove(global);
if (debuggees.empty()) {
debugModeBits &= ~DebugFromJS;
if (wasEnabled && !debugMode())
updateForDebugMode(cx);
}
}
BreakpointSite *
JSCompartment::getBreakpointSite(jsbytecode *pc)
{
BreakpointSiteMap::Ptr p = breakpointSites.lookup(pc);
return p ? p->value : NULL;
}
BreakpointSite *
JSCompartment::getOrCreateBreakpointSite(JSContext *cx, JSScript *script, jsbytecode *pc, JSObject *scriptObject)
{
JS_ASSERT(script->code <= pc);
JS_ASSERT(pc < script->code + script->length);
JS_ASSERT_IF(scriptObject, scriptObject->isScript() || scriptObject->isFunction());
JS_ASSERT_IF(scriptObject && scriptObject->isFunction(),
scriptObject->getFunctionPrivate()->script() == script);
JS_ASSERT_IF(scriptObject && scriptObject->isScript(), scriptObject->getScript() == script);
BreakpointSiteMap::AddPtr p = breakpointSites.lookupForAdd(pc);
if (!p) {
BreakpointSite *site = cx->runtime->new_<BreakpointSite>(script, pc);
if (!site || !breakpointSites.add(p, pc, site)) {
js_ReportOutOfMemory(cx);
return NULL;
}
}
BreakpointSite *site = p->value;
if (site->scriptObject)
JS_ASSERT_IF(scriptObject, site->scriptObject == scriptObject);
else
site->scriptObject = scriptObject;
return site;
}
void
Rename Debug to Debugger. --HG-- rename : js/src/jit-test/tests/debug/Debug-clearAllBreakpoints-01.js => js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js rename : js/src/jit-test/tests/debug/Debug-ctor-01.js => js/src/jit-test/tests/debug/Debugger-ctor-01.js rename : js/src/jit-test/tests/debug/Debug-ctor-02.js => js/src/jit-test/tests/debug/Debugger-ctor-02.js rename : js/src/jit-test/tests/debug/Debug-ctor-03.js => js/src/jit-test/tests/debug/Debugger-ctor-03.js rename : js/src/jit-test/tests/debug/Debug-debuggees-01.js => js/src/jit-test/tests/debug/Debugger-debuggees-01.js rename : js/src/jit-test/tests/debug/Debug-debuggees-02.js => js/src/jit-test/tests/debug/Debugger-debuggees-02.js rename : js/src/jit-test/tests/debug/Debug-debuggees-03.js => js/src/jit-test/tests/debug/Debugger-debuggees-03.js rename : js/src/jit-test/tests/debug/Debug-debuggees-04.js => js/src/jit-test/tests/debug/Debugger-debuggees-04.js rename : js/src/jit-test/tests/debug/Debug-debuggees-05.js => js/src/jit-test/tests/debug/Debugger-debuggees-05.js rename : js/src/jit-test/tests/debug/Debug-debuggees-06.js => js/src/jit-test/tests/debug/Debugger-debuggees-06.js rename : js/src/jit-test/tests/debug/Debug-debuggees-07.js => js/src/jit-test/tests/debug/Debugger-debuggees-07.js rename : js/src/jit-test/tests/debug/Debug-debuggees-08.js => js/src/jit-test/tests/debug/Debugger-debuggees-08.js rename : js/src/jit-test/tests/debug/Debug-debuggees-09.js => js/src/jit-test/tests/debug/Debugger-debuggees-09.js rename : js/src/jit-test/tests/debug/Debug-debuggees-10.js => js/src/jit-test/tests/debug/Debugger-debuggees-10.js rename : js/src/jit-test/tests/debug/Debug-debuggees-11.js => js/src/jit-test/tests/debug/Debugger-debuggees-11.js rename : js/src/jit-test/tests/debug/Debug-debuggees-12.js => js/src/jit-test/tests/debug/Debugger-debuggees-12.js rename : js/src/jit-test/tests/debug/Debug-debuggees-13.js => js/src/jit-test/tests/debug/Debugger-debuggees-13.js rename : js/src/jit-test/tests/debug/Debug-debuggees-14.js => js/src/jit-test/tests/debug/Debugger-debuggees-14.js rename : js/src/jit-test/tests/debug/Debug-debuggees-15.js => js/src/jit-test/tests/debug/Debugger-debuggees-15.js rename : js/src/jit-test/tests/debug/Debug-debuggees-16.js => js/src/jit-test/tests/debug/Debugger-debuggees-16.js rename : js/src/jit-test/tests/debug/Debug-enabled-01.js => js/src/jit-test/tests/debug/Debugger-enabled-01.js rename : js/src/jit-test/tests/debug/Debug-getYoungestFrame-01.js => js/src/jit-test/tests/debug/Debugger-getYoungestFrame-01.js rename : js/src/jit-test/tests/debug/Debug-getYoungestFrame-02.js => js/src/jit-test/tests/debug/Debugger-getYoungestFrame-02.js rename : js/src/jit-test/tests/debug/Debug-multi-01.js => js/src/jit-test/tests/debug/Debugger-multi-01.js rename : js/src/jit-test/tests/debug/Debug-multi-02.js => js/src/jit-test/tests/debug/Debugger-multi-02.js rename : js/src/jit-test/tests/debug/Debug-multi-03.js => js/src/jit-test/tests/debug/Debugger-multi-03.js
2011-07-05 05:48:26 -07:00
JSCompartment::clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSScript *script,
JSObject *handler)
{
JS_ASSERT_IF(script, script->compartment == this);
for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
BreakpointSite *site = e.front().value;
if (!script || site->script == script) {
Breakpoint *nextbp;
for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
nextbp = bp->nextInSite();
if ((!dbg || bp->debugger == dbg) && (!handler || bp->getHandler() == handler))
bp->destroy(cx, &e);
}
}
}
}
void
JSCompartment::clearTraps(JSContext *cx, JSScript *script)
{
for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
BreakpointSite *site = e.front().value;
if (!script || site->script == script)
site->clearTrap(cx, &e);
}
}
bool
JSCompartment::markBreakpointsIteratively(JSTracer *trc)
{
bool markedAny = false;
for (BreakpointSiteMap::Range r = breakpointSites.all(); !r.empty(); r.popFront()) {
BreakpointSite *site = r.front().value;
// Mark jsdbgapi state if any. But if we know the scriptObject, put off
// marking trap state until we know the scriptObject is live.
if (site->trapHandler && (!site->scriptObject || site->scriptObject->isMarked())) {
if (site->trapClosure.isObject() && !site->trapClosure.toObject().isMarked())
markedAny = true;
MarkValue(trc, site->trapClosure, "trap closure");
}
// Mark js::Debugger breakpoints. If either the debugger or the script is
// collected, then the breakpoint is collected along with it. So do not
// mark the handler in that case.
//
// If scriptObject is non-null, examine it to see if the script will be
// collected. If scriptObject is null, then site->script is an eval
// script on the stack, so it is definitely live.
//
if (!site->scriptObject || site->scriptObject->isMarked()) {
for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
if (bp->debugger->toJSObject()->isMarked() &&
bp->handler &&
!bp->handler->isMarked())
{
MarkObject(trc, *bp->handler, "breakpoint handler");
markedAny = true;
}
}
}
}
return markedAny;
}
void
JSCompartment::sweepBreakpoints(JSContext *cx)
{
for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
BreakpointSite *site = e.front().value;
if (site->scriptObject) {
// clearTrap and nextbp are necessary here to avoid possibly
// reading *site or *bp after destroying it.
bool scriptGone = IsAboutToBeFinalized(cx, site->scriptObject);
bool clearTrap = scriptGone && site->hasTrap();
Breakpoint *nextbp;
for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
nextbp = bp->nextInSite();
if (scriptGone || IsAboutToBeFinalized(cx, bp->debugger->toJSObject()))
bp->destroy(cx, &e);
}
if (clearTrap)
site->clearTrap(cx, &e);
}
}
}