gecko/js/jsd/jsd_val.cpp
Jason Orendorff f427cf7043 Bug 645416, part 22 - Remove JSCompartment::wrapId. r=terrence.
This is unnecessary now that object jsids no longer exist. Both string and
symbol jsids point only to GC things in the atoms compartment, which are safe
to pass to any compartment without wrapping.

--HG--
extra : rebase_source : 82c21e8474df05b1bb42c14d872c981205bbe879
2014-06-23 10:57:02 -05:00

747 lines
19 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* JavaScript Debugging support - Value and Property support
*/
#include "jsd.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "jswrapper.h"
#include "nsCxPusher.h"
using mozilla::AutoSafeJSContext;
#ifdef DEBUG
void JSD_ASSERT_VALID_VALUE(JSDValue* jsdval)
{
MOZ_ASSERT(jsdval);
MOZ_ASSERT(jsdval->nref > 0);
if(!JS_CLIST_IS_EMPTY(&jsdval->props))
{
MOZ_ASSERT(CHECK_BIT_FLAG(jsdval->flags, GOT_PROPS));
MOZ_ASSERT(!jsdval->val.isPrimitive());
}
if(jsdval->proto)
{
MOZ_ASSERT(CHECK_BIT_FLAG(jsdval->flags, GOT_PROTO));
MOZ_ASSERT(jsdval->proto->nref > 0);
}
if(jsdval->parent)
{
MOZ_ASSERT(CHECK_BIT_FLAG(jsdval->flags, GOT_PARENT));
MOZ_ASSERT(jsdval->parent->nref > 0);
}
if(jsdval->ctor)
{
MOZ_ASSERT(CHECK_BIT_FLAG(jsdval->flags, GOT_CTOR));
MOZ_ASSERT(jsdval->ctor->nref > 0);
}
}
void JSD_ASSERT_VALID_PROPERTY(JSDProperty* jsdprop)
{
MOZ_ASSERT(jsdprop);
MOZ_ASSERT(jsdprop->name);
MOZ_ASSERT(jsdprop->name->nref > 0);
MOZ_ASSERT(jsdprop->val);
MOZ_ASSERT(jsdprop->val->nref > 0);
if(jsdprop->alias)
MOZ_ASSERT(jsdprop->alias->nref > 0);
}
#endif
bool
jsd_IsValueObject(JSDContext* jsdc, JSDValue* jsdval)
{
return !jsdval->val.isPrimitive() || jsdval->val.isNull();
}
bool
jsd_IsValueNumber(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isNumber();
}
bool
jsd_IsValueInt(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isInt32();
}
bool
jsd_IsValueDouble(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isDouble();
}
bool
jsd_IsValueString(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isString();
}
bool
jsd_IsValueBoolean(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isBoolean();
}
bool
jsd_IsValueNull(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isNull();
}
bool
jsd_IsValueVoid(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isUndefined();
}
bool
jsd_IsValuePrimitive(JSDContext* jsdc, JSDValue* jsdval)
{
return jsdval->val.isPrimitive();
}
bool
jsd_IsValueFunction(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx; // NB: Actually unused.
return !jsdval->val.isPrimitive() &&
JS_ObjectIsCallable(cx, jsdval->val.toObjectOrNull());
}
bool
jsd_IsValueNative(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedFunction fun(cx);
if(jsd_IsValueFunction(jsdc, jsdval))
{
JSAutoCompartment ac(cx, jsdval->val.toObjectOrNull());
AutoSaveExceptionState as(cx);
bool ok = false;
fun = JSD_GetValueFunction(jsdc, jsdval);
if(fun)
ok = JS_GetFunctionScript(cx, fun) ? false : true;
MOZ_ASSERT(fun);
return ok;
}
return !jsdval->val.isPrimitive();
}
/***************************************************************************/
bool
jsd_GetValueBoolean(JSDContext* jsdc, JSDValue* jsdval)
{
jsval val = jsdval->val;
if(!val.isBoolean())
return false;
return val.toBoolean();
}
int32_t
jsd_GetValueInt(JSDContext* jsdc, JSDValue* jsdval)
{
jsval val = jsdval->val;
if(!val.isInt32())
return 0;
return val.toInt32();
}
double
jsd_GetValueDouble(JSDContext* jsdc, JSDValue* jsdval)
{
if(!jsdval->val.isDouble())
return 0;
return jsdval->val.toDouble();
}
JSString*
jsd_GetValueString(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedValue stringval(cx);
JS::RootedString string(cx);
JS::RootedObject scopeObj(cx);
if(jsdval->string)
return jsdval->string;
/* Reuse the string without copying or re-rooting it */
if(jsdval->val.isString()) {
jsdval->string = jsdval->val.toString();
return jsdval->string;
}
/* Objects call JS_ValueToString in their own compartment. */
scopeObj = !jsdval->val.isPrimitive() ? jsdval->val.toObjectOrNull() : jsdc->glob;
{
JSAutoCompartment ac(cx, scopeObj);
AutoSaveExceptionState as(cx);
JS::RootedValue v(cx, jsdval->val);
string = JS::ToString(cx, v);
}
JSAutoCompartment ac2(cx, jsdc->glob);
if(string) {
stringval = STRING_TO_JSVAL(string);
}
if(!string || !JS_WrapValue(cx, &stringval)) {
return nullptr;
}
jsdval->string = stringval.toString();
if(!JS::AddNamedStringRoot(cx, &jsdval->string, "ValueString"))
jsdval->string = nullptr;
return jsdval->string;
}
JSString*
jsd_GetValueFunctionId(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedFunction fun(cx);
if(!jsdval->funName && jsd_IsValueFunction(jsdc, jsdval))
{
JSAutoCompartment ac(cx, jsdval->val.toObjectOrNull());
AutoSaveExceptionState as(cx);
fun = JSD_GetValueFunction(jsdc, jsdval);
if(!fun)
return nullptr;
jsdval->funName = JS_GetFunctionId(fun);
/* For compatibility we return "anonymous", not an empty string here. */
if (!jsdval->funName)
jsdval->funName = JS_GetAnonymousString(jsdc->jsrt);
}
return jsdval->funName;
}
/***************************************************************************/
/*
* Create a new JSD value referring to a jsval. Copy string values into the
* JSD compartment. Leave all other GCTHINGs in their native compartments
* and access them through cross-compartment calls.
*/
JSDValue*
jsd_NewValue(JSDContext* jsdc, jsval value)
{
JS::RootedValue val(jsdc->jsrt, value);
AutoSafeJSContext cx;
JSDValue* jsdval;
if(!(jsdval = (JSDValue*) calloc(1, sizeof(JSDValue))))
return nullptr;
if(val.isGCThing())
{
bool ok;
JSAutoCompartment ac(cx, jsdc->glob);
ok = JS::AddNamedValueRoot(cx, &jsdval->val, "JSDValue");
if(ok && val.isString()) {
if(!JS_WrapValue(cx, &val)) {
ok = false;
}
}
if(!ok)
{
free(jsdval);
return nullptr;
}
}
jsdval->val = val;
jsdval->nref = 1;
JS_INIT_CLIST(&jsdval->props);
return jsdval;
}
void
jsd_DropValue(JSDContext* jsdc, JSDValue* jsdval)
{
MOZ_ASSERT(jsdval->nref > 0);
if(0 == --jsdval->nref)
{
jsd_RefreshValue(jsdc, jsdval);
if(jsdval->val.isGCThing())
{
AutoSafeJSContext cx;
JSAutoCompartment ac(cx, jsdc->glob);
JS::RemoveValueRoot(cx, &jsdval->val);
}
free(jsdval);
}
}
jsval
jsd_GetValueWrappedJSVal(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedValue val(cx, jsdval->val);
if (!val.isPrimitive()) {
JS::RootedObject obj(cx, &val.toObject());
JSAutoCompartment ac(cx, obj);
obj = JS_ObjectToOuterObject(cx, obj);
if (!obj)
{
JS_ClearPendingException(cx);
val = JSVAL_NULL;
}
else
val = JS::ObjectValue(*obj);
}
return val;
}
static JSDProperty* _newProperty(JSDContext* jsdc, JS::HandleValue propId,
JS::HandleValue propValue, JS::HandleValue propAlias,
uint8_t propFlags, unsigned additionalFlags)
{
JSDProperty* jsdprop;
if(!(jsdprop = (JSDProperty*) calloc(1, sizeof(JSDProperty))))
return nullptr;
JS_INIT_CLIST(&jsdprop->links);
jsdprop->nref = 1;
jsdprop->flags = propFlags | additionalFlags;
if(!(jsdprop->name = jsd_NewValue(jsdc, propId)))
goto new_prop_fail;
if(!(jsdprop->val = jsd_NewValue(jsdc, propValue)))
goto new_prop_fail;
if((jsdprop->flags & JSDPD_ALIAS) &&
!(jsdprop->alias = jsd_NewValue(jsdc, propAlias)))
goto new_prop_fail;
return jsdprop;
new_prop_fail:
jsd_DropProperty(jsdc, jsdprop);
return nullptr;
}
static void _freeProps(JSDContext* jsdc, JSDValue* jsdval)
{
JSDProperty* jsdprop;
while(jsdprop = (JSDProperty*)jsdval->props.next,
jsdprop != (JSDProperty*)&jsdval->props)
{
JS_REMOVE_AND_INIT_LINK(&jsdprop->links);
jsd_DropProperty(jsdc, jsdprop);
}
MOZ_ASSERT(JS_CLIST_IS_EMPTY(&jsdval->props));
CLEAR_BIT_FLAG(jsdval->flags, GOT_PROPS);
}
static bool _buildProps(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedObject obj(cx);
JSPropertyDescArray pda;
unsigned i;
MOZ_ASSERT(JS_CLIST_IS_EMPTY(&jsdval->props));
MOZ_ASSERT(!(CHECK_BIT_FLAG(jsdval->flags, GOT_PROPS)));
MOZ_ASSERT(!jsdval->val.isPrimitive());
if(jsdval->val.isPrimitive())
return false;
obj = jsdval->val.toObjectOrNull();
JSAutoCompartment ac(cx, obj);
if(!JS_GetPropertyDescArray(cx, obj, &pda))
{
return false;
}
JS::RootedValue propId(cx);
JS::RootedValue propValue(cx);
JS::RootedValue propAlias(cx);
uint8_t propFlags;
for(i = 0; i < pda.length; i++)
{
propId = pda.array[i].id;
propValue = pda.array[i].value;
propAlias = pda.array[i].alias;
propFlags = pda.array[i].flags;
JSDProperty* prop = _newProperty(jsdc, propId, propValue, propAlias, propFlags, 0);
if(!prop)
{
_freeProps(jsdc, jsdval);
break;
}
JS_APPEND_LINK(&prop->links, &jsdval->props);
}
JS_PutPropertyDescArray(cx, &pda);
SET_BIT_FLAG(jsdval->flags, GOT_PROPS);
return !JS_CLIST_IS_EMPTY(&jsdval->props);
}
#undef DROP_CLEAR_VALUE
#define DROP_CLEAR_VALUE(jsdc, x) if(x){jsd_DropValue(jsdc,x); x = nullptr;}
void
jsd_RefreshValue(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
if(jsdval->string)
{
/* if the jsval is a string, then we didn't need to root the string */
if(!jsdval->val.isString())
{
JSAutoCompartment ac(cx, jsdc->glob);
JS::RemoveStringRoot(cx, &jsdval->string);
}
jsdval->string = nullptr;
}
jsdval->funName = nullptr;
jsdval->className = nullptr;
DROP_CLEAR_VALUE(jsdc, jsdval->proto);
DROP_CLEAR_VALUE(jsdc, jsdval->parent);
DROP_CLEAR_VALUE(jsdc, jsdval->ctor);
_freeProps(jsdc, jsdval);
jsdval->flags = 0;
}
/***************************************************************************/
unsigned
jsd_GetCountOfProperties(JSDContext* jsdc, JSDValue* jsdval)
{
JSDProperty* jsdprop;
unsigned count = 0;
if(!(CHECK_BIT_FLAG(jsdval->flags, GOT_PROPS)))
if(!_buildProps(jsdc, jsdval))
return 0;
for(jsdprop = (JSDProperty*)jsdval->props.next;
jsdprop != (JSDProperty*)&jsdval->props;
jsdprop = (JSDProperty*)jsdprop->links.next)
{
count++;
}
return count;
}
JSDProperty*
jsd_IterateProperties(JSDContext* jsdc, JSDValue* jsdval, JSDProperty **iterp)
{
JSDProperty* jsdprop = *iterp;
if(!(CHECK_BIT_FLAG(jsdval->flags, GOT_PROPS)))
{
MOZ_ASSERT(!jsdprop);
if(!_buildProps(jsdc, jsdval))
return nullptr;
}
if(!jsdprop)
jsdprop = (JSDProperty*)jsdval->props.next;
if(jsdprop == (JSDProperty*)&jsdval->props)
return nullptr;
*iterp = (JSDProperty*)jsdprop->links.next;
MOZ_ASSERT(jsdprop);
jsdprop->nref++;
return jsdprop;
}
JSDProperty*
jsd_GetValueProperty(JSDContext* jsdc, JSDValue* jsdval, JSString* nameStr)
{
JS::RootedString name(jsdc->jsrt, nameStr);
AutoSafeJSContext cx;
JSAutoCompartment acBase(cx, jsdc->glob);
JSDProperty* jsdprop;
JSDProperty* iter = nullptr;
JS::RootedObject obj(cx);
JS::RootedValue val(cx), nameval(cx);
JS::RootedId nameid(cx);
JS::RootedValue propId(cx);
JS::RootedValue propValue(cx);
JS::RootedValue propAlias(cx);
uint8_t propFlags;
if(!jsd_IsValueObject(jsdc, jsdval))
return nullptr;
/* If we already have the prop, then return it */
while(nullptr != (jsdprop = jsd_IterateProperties(jsdc, jsdval, &iter)))
{
JSString* propName = jsd_GetValueString(jsdc, jsdprop->name);
if(propName) {
int result;
if (JS_CompareStrings(cx, propName, name, &result) && !result)
return jsdprop;
}
JSD_DropProperty(jsdc, jsdprop);
}
/* Not found in property list, look it up explicitly */
nameval = STRING_TO_JSVAL(name);
if(!JS_ValueToId(cx, nameval, &nameid))
return nullptr;
if(!(obj = jsdval->val.toObjectOrNull()))
return nullptr;
JS::Rooted<JSPropertyDescriptor> desc(cx);
{
JSAutoCompartment ac(cx, obj);
JS::RootedId id(cx, nameid);
if(!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc))
return nullptr;
if(!desc.object())
return nullptr;
JS_ClearPendingException(cx);
if(!JS_GetPropertyById(cx, obj, id, &val))
{
if (JS_IsExceptionPending(cx))
{
if (!JS_GetPendingException(cx, &propValue))
{
return nullptr;
}
propFlags = JSPD_EXCEPTION;
}
else
{
propFlags = JSPD_ERROR;
propValue = JSVAL_VOID;
}
}
else
{
propValue = val;
}
}
if (!JS_IdToValue(cx, nameid, &propId))
return nullptr;
propAlias = JSVAL_NULL;
propFlags |= desc.isEnumerable() ? JSPD_ENUMERATE : 0
| desc.isReadonly() ? JSPD_READONLY : 0
| desc.isPermanent() ? JSPD_PERMANENT : 0;
return _newProperty(jsdc, propId, propValue, propAlias, propFlags, JSDPD_HINTED);
}
/*
* Retrieve a JSFunction* from a JSDValue*. This differs from
* JS_ValueToFunction by fully unwrapping the object first.
*/
JSFunction*
jsd_GetValueFunction(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedObject obj(cx);
JS::RootedFunction fun(cx);
if (jsdval->val.isPrimitive())
return nullptr;
obj = js::UncheckedUnwrap(jsdval->val.toObjectOrNull());
JSAutoCompartment ac(cx, obj);
JS::RootedValue funval(cx, JS::ObjectValue(*obj));
fun = JS_ValueToFunction(cx, funval);
return fun;
}
JSDValue*
jsd_GetValuePrototype(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
if(!(CHECK_BIT_FLAG(jsdval->flags, GOT_PROTO)))
{
JS::RootedObject obj(cx);
JS::RootedObject proto(cx);
MOZ_ASSERT(!jsdval->proto);
SET_BIT_FLAG(jsdval->flags, GOT_PROTO);
if(jsdval->val.isPrimitive())
return nullptr;
obj = jsdval->val.toObjectOrNull();
if(!JS_GetPrototype(cx, obj, &proto))
return nullptr;
if(!proto)
return nullptr;
jsdval->proto = jsd_NewValue(jsdc, OBJECT_TO_JSVAL(proto));
}
if(jsdval->proto)
jsdval->proto->nref++;
return jsdval->proto;
}
JSDValue*
jsd_GetValueParent(JSDContext* jsdc, JSDValue* jsdval)
{
if(!(CHECK_BIT_FLAG(jsdval->flags, GOT_PARENT)))
{
AutoSafeJSContext cx;
JS::RootedObject obj(cx);
JS::RootedObject parent(cx);
MOZ_ASSERT(!jsdval->parent);
SET_BIT_FLAG(jsdval->flags, GOT_PARENT);
if(jsdval->val.isPrimitive())
return nullptr;
obj = jsdval->val.toObjectOrNull();
{
JSAutoCompartment ac(cx, obj);
parent = JS_GetParentOrScopeChain(cx, obj);
}
if(!parent)
return nullptr;
jsdval->parent = jsd_NewValue(jsdc, OBJECT_TO_JSVAL(parent));
}
if(jsdval->parent)
jsdval->parent->nref++;
return jsdval->parent;
}
JSDValue*
jsd_GetValueConstructor(JSDContext* jsdc, JSDValue* jsdval)
{
if(!(CHECK_BIT_FLAG(jsdval->flags, GOT_CTOR)))
{
AutoSafeJSContext cx;
JS::RootedObject obj(cx);
JS::RootedObject proto(cx);
JS::RootedObject ctor(cx);
MOZ_ASSERT(!jsdval->ctor);
SET_BIT_FLAG(jsdval->flags, GOT_CTOR);
if(jsdval->val.isPrimitive())
return nullptr;
obj = jsdval->val.toObjectOrNull();
if(!JS_GetPrototype(cx, obj, &proto))
return nullptr;
if(!proto)
return nullptr;
{
JSAutoCompartment ac(cx, obj);
ctor = JS_GetConstructor(cx, proto);
}
if(!ctor)
return nullptr;
jsdval->ctor = jsd_NewValue(jsdc, OBJECT_TO_JSVAL(ctor));
}
if(jsdval->ctor)
jsdval->ctor->nref++;
return jsdval->ctor;
}
const char*
jsd_GetValueClassName(JSDContext* jsdc, JSDValue* jsdval)
{
jsval val = jsdval->val;
if(!jsdval->className && !val.isPrimitive())
{
JS::RootedObject obj(jsdc->jsrt, val.toObjectOrNull());
AutoSafeJSContext cx;
JSAutoCompartment ac(cx, obj);
jsdval->className = JS_GetDebugClassName(obj);
}
return jsdval->className;
}
JSDScript*
jsd_GetScriptForValue(JSDContext* jsdc, JSDValue* jsdval)
{
AutoSafeJSContext cx;
JS::RootedValue val(cx, jsdval->val);
JS::RootedScript script(cx);
JSDScript* jsdscript;
if (!jsd_IsValueFunction(jsdc, jsdval))
return nullptr;
{
JSAutoCompartment ac(cx, val.toObjectOrNull());
AutoSaveExceptionState as(cx);
JS::RootedFunction fun(cx, JSD_GetValueFunction(jsdc, jsdval));
if (fun)
script = JS_GetFunctionScript(cx, fun);
}
if (!script)
return nullptr;
JSD_LOCK_SCRIPTS(jsdc);
jsdscript = jsd_FindJSDScript(jsdc, script);
JSD_UNLOCK_SCRIPTS(jsdc);
return jsdscript;
}
/***************************************************************************/
/***************************************************************************/
JSDValue*
jsd_GetPropertyName(JSDContext* jsdc, JSDProperty* jsdprop)
{
jsdprop->name->nref++;
return jsdprop->name;
}
JSDValue*
jsd_GetPropertyValue(JSDContext* jsdc, JSDProperty* jsdprop)
{
jsdprop->val->nref++;
return jsdprop->val;
}
JSDValue*
jsd_GetPropertyAlias(JSDContext* jsdc, JSDProperty* jsdprop)
{
if(jsdprop->alias)
jsdprop->alias->nref++;
return jsdprop->alias;
}
unsigned
jsd_GetPropertyFlags(JSDContext* jsdc, JSDProperty* jsdprop)
{
return jsdprop->flags;
}
void
jsd_DropProperty(JSDContext* jsdc, JSDProperty* jsdprop)
{
MOZ_ASSERT(jsdprop->nref > 0);
if(0 == --jsdprop->nref)
{
MOZ_ASSERT(JS_CLIST_IS_EMPTY(&jsdprop->links));
DROP_CLEAR_VALUE(jsdc, jsdprop->val);
DROP_CLEAR_VALUE(jsdc, jsdprop->name);
DROP_CLEAR_VALUE(jsdc, jsdprop->alias);
free(jsdprop);
}
}