tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Environment.cpp (18995B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "debugger/Environment-inl.h"
      8 
      9 #include "mozilla/Assertions.h"  // for AssertionConditionType
     10 #include "mozilla/Maybe.h"       // for Maybe, Some, Nothing
     11 #include "mozilla/Vector.h"      // for Vector
     12 
     13 #include <string.h>  // for strlen, size_t
     14 
     15 #include "debugger/Debugger.h"  // for Env, Debugger, ValueToIdentifier
     16 #include "debugger/Object.h"    // for DebuggerObject
     17 #include "debugger/Script.h"    // for DebuggerScript
     18 #include "gc/Tracer.h"    // for TraceManuallyBarrieredCrossCompartmentEdge
     19 #include "js/CallArgs.h"  // for CallArgs
     20 #include "js/friend/ErrorMessages.h"  // for GetErrorMessage, JSMSG_*
     21 #include "js/HeapAPI.h"               // for IsInsideNursery
     22 #include "js/RootingAPI.h"            // for Rooted, MutableHandle
     23 #include "util/Identifier.h"          // for IsIdentifier
     24 #include "vm/Compartment.h"           // for Compartment
     25 #include "vm/JSAtomUtils.h"           // for Atomize
     26 #include "vm/JSContext.h"             // for JSContext
     27 #include "vm/JSFunction.h"            // for JSFunction
     28 #include "vm/JSObject.h"              // for JSObject, RequireObject,
     29 #include "vm/NativeObject.h"          // for NativeObject, JSObject::is
     30 #include "vm/Realm.h"                 // for AutoRealm, ErrorCopier
     31 #include "vm/Scope.h"                 // for ScopeKind, ScopeKindString
     32 #include "vm/StringType.h"            // for JSAtom
     33 
     34 #include "gc/StableCellHasher-inl.h"
     35 #include "vm/Compartment-inl.h"        // for Compartment::wrap
     36 #include "vm/EnvironmentObject-inl.h"  // for JSObject::enclosingEnvironment
     37 #include "vm/JSObject-inl.h"  // for IsInternalFunctionObject, NewObjectWithGivenProtoAndKind
     38 #include "vm/ObjectOperations-inl.h"  // for HasProperty, GetProperty
     39 #include "vm/Realm-inl.h"             // for AutoRealm::AutoRealm
     40 
     41 namespace js {
     42 class GlobalObject;
     43 }
     44 
     45 using namespace js;
     46 
     47 using mozilla::Maybe;
     48 using mozilla::Nothing;
     49 using mozilla::Some;
     50 
     51 const JSClassOps DebuggerEnvironment::classOps_ = {
     52    nullptr,                               // addProperty
     53    nullptr,                               // delProperty
     54    nullptr,                               // enumerate
     55    nullptr,                               // newEnumerate
     56    nullptr,                               // resolve
     57    nullptr,                               // mayResolve
     58    nullptr,                               // finalize
     59    nullptr,                               // call
     60    nullptr,                               // construct
     61    CallTraceMethod<DebuggerEnvironment>,  // trace
     62 };
     63 
     64 const JSClass DebuggerEnvironment::class_ = {
     65    "Environment",
     66    JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS),
     67    &classOps_,
     68 };
     69 
     70 void DebuggerEnvironment::trace(JSTracer* trc) {
     71  // There is a barrier on private pointers, so the Unbarriered marking
     72  // is okay.
     73  if (Env* referent = maybeReferent()) {
     74    TraceManuallyBarrieredCrossCompartmentEdge(trc, this, &referent,
     75                                               "Debugger.Environment referent");
     76    if (referent != maybeReferent()) {
     77      setReservedSlotGCThingAsPrivateUnbarriered(ENV_SLOT, referent);
     78    }
     79  }
     80 }
     81 
     82 static DebuggerEnvironment* DebuggerEnvironment_checkThis(
     83    JSContext* cx, const CallArgs& args) {
     84  JSObject* thisobj = RequireObject(cx, args.thisv());
     85  if (!thisobj) {
     86    return nullptr;
     87  }
     88  if (!thisobj->is<DebuggerEnvironment>()) {
     89    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
     90                              JSMSG_INCOMPATIBLE_PROTO, "Debugger.Environment",
     91                              "method", thisobj->getClass()->name);
     92    return nullptr;
     93  }
     94 
     95  return &thisobj->as<DebuggerEnvironment>();
     96 }
     97 
     98 struct MOZ_STACK_CLASS DebuggerEnvironment::CallData {
     99  JSContext* cx;
    100  const CallArgs& args;
    101 
    102  Handle<DebuggerEnvironment*> environment;
    103 
    104  CallData(JSContext* cx, const CallArgs& args,
    105           Handle<DebuggerEnvironment*> env)
    106      : cx(cx), args(args), environment(env) {}
    107 
    108  bool typeGetter();
    109  bool scopeKindGetter();
    110  bool parentGetter();
    111  bool objectGetter();
    112  bool calleeScriptGetter();
    113  bool inspectableGetter();
    114  bool optimizedOutGetter();
    115 
    116  bool namesMethod();
    117  bool findMethod();
    118  bool getVariableMethod();
    119  bool setVariableMethod();
    120 
    121  using Method = bool (CallData::*)();
    122 
    123  template <Method MyMethod>
    124  static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
    125 };
    126 
    127 template <DebuggerEnvironment::CallData::Method MyMethod>
    128 /* static */
    129 bool DebuggerEnvironment::CallData::ToNative(JSContext* cx, unsigned argc,
    130                                             Value* vp) {
    131  CallArgs args = CallArgsFromVp(argc, vp);
    132 
    133  Rooted<DebuggerEnvironment*> environment(
    134      cx, DebuggerEnvironment_checkThis(cx, args));
    135  if (!environment) {
    136    return false;
    137  }
    138 
    139  CallData data(cx, args, environment);
    140  return (data.*MyMethod)();
    141 }
    142 
    143 /* static */
    144 bool DebuggerEnvironment::construct(JSContext* cx, unsigned argc, Value* vp) {
    145  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
    146                            "Debugger.Environment");
    147  return false;
    148 }
    149 
    150 static bool IsDeclarative(Env* env) {
    151  return env->is<DebugEnvironmentProxy>() &&
    152         env->as<DebugEnvironmentProxy>().isForDeclarative();
    153 }
    154 
    155 template <typename T>
    156 static bool IsDebugEnvironmentWrapper(Env* env) {
    157  return env->is<DebugEnvironmentProxy>() &&
    158         env->as<DebugEnvironmentProxy>().environment().is<T>();
    159 }
    160 
    161 bool DebuggerEnvironment::CallData::typeGetter() {
    162  if (!environment->requireDebuggee(cx)) {
    163    return false;
    164  }
    165 
    166  DebuggerEnvironmentType type = environment->type();
    167 
    168  const char* s;
    169  switch (type) {
    170    case DebuggerEnvironmentType::Declarative:
    171      s = "declarative";
    172      break;
    173    case DebuggerEnvironmentType::With:
    174      s = "with";
    175      break;
    176    case DebuggerEnvironmentType::Object:
    177      s = "object";
    178      break;
    179  }
    180 
    181  JSAtom* str = Atomize(cx, s, strlen(s));
    182  if (!str) {
    183    return false;
    184  }
    185 
    186  args.rval().setString(str);
    187  return true;
    188 }
    189 
    190 bool DebuggerEnvironment::CallData::scopeKindGetter() {
    191  if (!environment->requireDebuggee(cx)) {
    192    return false;
    193  }
    194 
    195  Maybe<ScopeKind> kind = environment->scopeKind();
    196  if (kind.isSome()) {
    197    const char* s = ScopeKindString(*kind);
    198    JSAtom* str = Atomize(cx, s, strlen(s));
    199    if (!str) {
    200      return false;
    201    }
    202    args.rval().setString(str);
    203  } else {
    204    args.rval().setNull();
    205  }
    206 
    207  return true;
    208 }
    209 
    210 bool DebuggerEnvironment::CallData::parentGetter() {
    211  if (!environment->requireDebuggee(cx)) {
    212    return false;
    213  }
    214 
    215  Rooted<DebuggerEnvironment*> result(cx);
    216  if (!environment->getParent(cx, &result)) {
    217    return false;
    218  }
    219 
    220  args.rval().setObjectOrNull(result);
    221  return true;
    222 }
    223 
    224 bool DebuggerEnvironment::CallData::objectGetter() {
    225  if (!environment->requireDebuggee(cx)) {
    226    return false;
    227  }
    228 
    229  if (environment->type() == DebuggerEnvironmentType::Declarative) {
    230    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    231                              JSMSG_DEBUG_NO_ENV_OBJECT);
    232    return false;
    233  }
    234 
    235  Rooted<DebuggerObject*> result(cx);
    236  if (!environment->getObject(cx, &result)) {
    237    return false;
    238  }
    239 
    240  args.rval().setObject(*result);
    241  return true;
    242 }
    243 
    244 bool DebuggerEnvironment::CallData::calleeScriptGetter() {
    245  if (!environment->requireDebuggee(cx)) {
    246    return false;
    247  }
    248 
    249  Rooted<DebuggerScript*> result(cx);
    250  if (!environment->getCalleeScript(cx, &result)) {
    251    return false;
    252  }
    253 
    254  args.rval().setObjectOrNull(result);
    255  return true;
    256 }
    257 
    258 bool DebuggerEnvironment::CallData::inspectableGetter() {
    259  args.rval().setBoolean(environment->isDebuggee());
    260  return true;
    261 }
    262 
    263 bool DebuggerEnvironment::CallData::optimizedOutGetter() {
    264  args.rval().setBoolean(environment->isOptimized());
    265  return true;
    266 }
    267 
    268 bool DebuggerEnvironment::CallData::namesMethod() {
    269  if (!environment->requireDebuggee(cx)) {
    270    return false;
    271  }
    272 
    273  RootedIdVector ids(cx);
    274  if (!DebuggerEnvironment::getNames(cx, environment, &ids)) {
    275    return false;
    276  }
    277 
    278  JSObject* obj = IdVectorToArray(cx, ids);
    279  if (!obj) {
    280    return false;
    281  }
    282 
    283  args.rval().setObject(*obj);
    284  return true;
    285 }
    286 
    287 bool DebuggerEnvironment::CallData::findMethod() {
    288  if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1)) {
    289    return false;
    290  }
    291 
    292  RootedId id(cx);
    293  if (!ValueToIdentifier(cx, args[0], &id)) {
    294    return false;
    295  }
    296 
    297  if (!environment->requireDebuggee(cx)) {
    298    return false;
    299  }
    300 
    301  Rooted<DebuggerEnvironment*> result(cx);
    302  if (!DebuggerEnvironment::find(cx, environment, id, &result)) {
    303    return false;
    304  }
    305 
    306  args.rval().setObjectOrNull(result);
    307  return true;
    308 }
    309 
    310 bool DebuggerEnvironment::CallData::getVariableMethod() {
    311  if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1)) {
    312    return false;
    313  }
    314 
    315  RootedId id(cx);
    316  if (!ValueToIdentifier(cx, args[0], &id)) {
    317    return false;
    318  }
    319 
    320  if (!environment->requireDebuggee(cx)) {
    321    return false;
    322  }
    323 
    324  return DebuggerEnvironment::getVariable(cx, environment, id, args.rval());
    325 }
    326 
    327 bool DebuggerEnvironment::CallData::setVariableMethod() {
    328  if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2)) {
    329    return false;
    330  }
    331 
    332  RootedId id(cx);
    333  if (!ValueToIdentifier(cx, args[0], &id)) {
    334    return false;
    335  }
    336 
    337  if (!environment->requireDebuggee(cx)) {
    338    return false;
    339  }
    340 
    341  if (!DebuggerEnvironment::setVariable(cx, environment, id, args[1])) {
    342    return false;
    343  }
    344 
    345  args.rval().setUndefined();
    346  return true;
    347 }
    348 
    349 bool DebuggerEnvironment::requireDebuggee(JSContext* cx) const {
    350  if (!isDebuggee()) {
    351    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    352                              JSMSG_DEBUG_NOT_DEBUGGEE, "Debugger.Environment",
    353                              "environment");
    354 
    355    return false;
    356  }
    357 
    358  return true;
    359 }
    360 
    361 const JSPropertySpec DebuggerEnvironment::properties_[] = {
    362    JS_DEBUG_PSG("type", typeGetter),
    363    JS_DEBUG_PSG("scopeKind", scopeKindGetter),
    364    JS_DEBUG_PSG("parent", parentGetter),
    365    JS_DEBUG_PSG("object", objectGetter),
    366    JS_DEBUG_PSG("calleeScript", calleeScriptGetter),
    367    JS_DEBUG_PSG("inspectable", inspectableGetter),
    368    JS_DEBUG_PSG("optimizedOut", optimizedOutGetter),
    369    JS_PS_END,
    370 };
    371 
    372 const JSFunctionSpec DebuggerEnvironment::methods_[] = {
    373    JS_DEBUG_FN("names", namesMethod, 0),
    374    JS_DEBUG_FN("find", findMethod, 1),
    375    JS_DEBUG_FN("getVariable", getVariableMethod, 1),
    376    JS_DEBUG_FN("setVariable", setVariableMethod, 2),
    377    JS_FS_END,
    378 };
    379 
    380 /* static */
    381 NativeObject* DebuggerEnvironment::initClass(JSContext* cx,
    382                                             Handle<GlobalObject*> global,
    383                                             HandleObject dbgCtor) {
    384  return InitClass(cx, dbgCtor, nullptr, nullptr, "Environment", construct, 0,
    385                   properties_, methods_, nullptr, nullptr);
    386 }
    387 
    388 /* static */
    389 DebuggerEnvironment* DebuggerEnvironment::create(
    390    JSContext* cx, HandleObject proto, HandleObject referent,
    391    Handle<NativeObject*> debugger) {
    392  DebuggerEnvironment* obj =
    393      IsInsideNursery(referent)
    394          ? NewObjectWithGivenProto<DebuggerEnvironment>(cx, proto)
    395          : NewTenuredObjectWithGivenProto<DebuggerEnvironment>(cx, proto);
    396  if (!obj) {
    397    return nullptr;
    398  }
    399 
    400  obj->setReservedSlotGCThingAsPrivate(ENV_SLOT, referent);
    401  obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
    402 
    403  return obj;
    404 }
    405 
    406 /* static */
    407 DebuggerEnvironmentType DebuggerEnvironment::type() const {
    408  // Don't bother switching compartments just to check env's type.
    409  if (IsDeclarative(referent())) {
    410    return DebuggerEnvironmentType::Declarative;
    411  }
    412  if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) {
    413    return DebuggerEnvironmentType::With;
    414  }
    415  return DebuggerEnvironmentType::Object;
    416 }
    417 
    418 mozilla::Maybe<ScopeKind> DebuggerEnvironment::scopeKind() const {
    419  if (!referent()->is<DebugEnvironmentProxy>()) {
    420    return Nothing();
    421  }
    422  EnvironmentObject& env =
    423      referent()->as<DebugEnvironmentProxy>().environment();
    424  Scope* scope = GetEnvironmentScope(env);
    425  return scope ? Some(scope->kind()) : Nothing();
    426 }
    427 
    428 bool DebuggerEnvironment::getParent(
    429    JSContext* cx, MutableHandle<DebuggerEnvironment*> result) const {
    430  // Don't bother switching compartments just to get env's parent.
    431  Rooted<Env*> parent(cx, referent()->enclosingEnvironment());
    432  if (!parent) {
    433    result.set(nullptr);
    434    return true;
    435  }
    436 
    437  return owner()->wrapEnvironment(cx, parent, result);
    438 }
    439 
    440 bool DebuggerEnvironment::getObject(
    441    JSContext* cx, MutableHandle<DebuggerObject*> result) const {
    442  MOZ_ASSERT(type() != DebuggerEnvironmentType::Declarative);
    443 
    444  // Don't bother switching compartments just to get env's object.
    445  RootedObject object(cx);
    446  if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) {
    447    object.set(&referent()
    448                    ->as<DebugEnvironmentProxy>()
    449                    .environment()
    450                    .as<WithEnvironmentObject>()
    451                    .object());
    452  } else if (IsDebugEnvironmentWrapper<NonSyntacticVariablesObject>(
    453                 referent())) {
    454    object.set(&referent()
    455                    ->as<DebugEnvironmentProxy>()
    456                    .environment()
    457                    .as<NonSyntacticVariablesObject>());
    458  } else {
    459    object.set(referent());
    460    MOZ_ASSERT(!object->is<DebugEnvironmentProxy>());
    461  }
    462 
    463  return owner()->wrapDebuggeeObject(cx, object, result);
    464 }
    465 
    466 bool DebuggerEnvironment::getCalleeScript(
    467    JSContext* cx, MutableHandle<DebuggerScript*> result) const {
    468  if (!referent()->is<DebugEnvironmentProxy>()) {
    469    result.set(nullptr);
    470    return true;
    471  }
    472 
    473  JSObject& scope = referent()->as<DebugEnvironmentProxy>().environment();
    474  if (!scope.is<CallObject>()) {
    475    result.set(nullptr);
    476    return true;
    477  }
    478 
    479  Rooted<BaseScript*> script(cx, scope.as<CallObject>().callee().baseScript());
    480 
    481  DebuggerScript* scriptObject = owner()->wrapScript(cx, script);
    482  if (!scriptObject) {
    483    return false;
    484  }
    485 
    486  result.set(scriptObject);
    487  return true;
    488 }
    489 
    490 bool DebuggerEnvironment::isDebuggee() const {
    491  MOZ_ASSERT(referent());
    492  MOZ_ASSERT(!referent()->is<EnvironmentObject>());
    493 
    494  return owner()->observesGlobal(&referent()->nonCCWGlobal());
    495 }
    496 
    497 bool DebuggerEnvironment::isOptimized() const {
    498  return referent()->is<DebugEnvironmentProxy>() &&
    499         referent()->as<DebugEnvironmentProxy>().isOptimizedOut();
    500 }
    501 
    502 /* static */
    503 bool DebuggerEnvironment::getNames(JSContext* cx,
    504                                   Handle<DebuggerEnvironment*> environment,
    505                                   MutableHandleIdVector result) {
    506  MOZ_ASSERT(environment->isDebuggee());
    507  MOZ_ASSERT(result.empty());
    508 
    509  Rooted<Env*> referent(cx, environment->referent());
    510  {
    511    Maybe<AutoRealm> ar;
    512    ar.emplace(cx, referent);
    513 
    514    ErrorCopier ec(ar);
    515    if (!GetPropertyKeys(cx, referent, JSITER_HIDDEN, result)) {
    516      return false;
    517    }
    518  }
    519 
    520  result.eraseIf([](PropertyKey key) {
    521    return !key.isAtom() || !IsIdentifier(key.toAtom());
    522  });
    523 
    524  for (size_t i = 0; i < result.length(); ++i) {
    525    cx->markAtom(result[i].toAtom());
    526  }
    527 
    528  return true;
    529 }
    530 
    531 /* static */
    532 bool DebuggerEnvironment::find(JSContext* cx,
    533                               Handle<DebuggerEnvironment*> environment,
    534                               HandleId id,
    535                               MutableHandle<DebuggerEnvironment*> result) {
    536  MOZ_ASSERT(environment->isDebuggee());
    537 
    538  Rooted<Env*> env(cx, environment->referent());
    539  Debugger* dbg = environment->owner();
    540 
    541  {
    542    Maybe<AutoRealm> ar;
    543    ar.emplace(cx, env);
    544 
    545    cx->markId(id);
    546 
    547    // This can trigger resolve hooks.
    548    ErrorCopier ec(ar);
    549    for (; env; env = env->enclosingEnvironment()) {
    550      bool found;
    551      if (!HasProperty(cx, env, id, &found)) {
    552        return false;
    553      }
    554      if (found) {
    555        break;
    556      }
    557    }
    558  }
    559 
    560  if (!env) {
    561    result.set(nullptr);
    562    return true;
    563  }
    564 
    565  return dbg->wrapEnvironment(cx, env, result);
    566 }
    567 
    568 /* static */
    569 bool DebuggerEnvironment::getVariable(JSContext* cx,
    570                                      Handle<DebuggerEnvironment*> environment,
    571                                      HandleId id, MutableHandleValue result) {
    572  MOZ_ASSERT(environment->isDebuggee());
    573 
    574  Rooted<Env*> referent(cx, environment->referent());
    575  Debugger* dbg = environment->owner();
    576 
    577  {
    578    Maybe<AutoRealm> ar;
    579    ar.emplace(cx, referent);
    580 
    581    cx->markId(id);
    582 
    583    // This can trigger getters.
    584    ErrorCopier ec(ar);
    585 
    586    bool found;
    587    if (!HasProperty(cx, referent, id, &found)) {
    588      return false;
    589    }
    590    if (!found) {
    591      result.setUndefined();
    592      return true;
    593    }
    594 
    595    // For DebugEnvironmentProxys, we get sentinel values for optimized out
    596    // slots and arguments instead of throwing (the default behavior).
    597    //
    598    // See wrapDebuggeeValue for how the sentinel values are wrapped.
    599    if (referent->is<DebugEnvironmentProxy>()) {
    600      Rooted<DebugEnvironmentProxy*> env(
    601          cx, &referent->as<DebugEnvironmentProxy>());
    602      if (!DebugEnvironmentProxy::getMaybeSentinelValue(cx, env, id, result)) {
    603        return false;
    604      }
    605    } else {
    606      if (!GetProperty(cx, referent, referent, id, result)) {
    607        return false;
    608      }
    609    }
    610  }
    611 
    612  // When we've faked up scope chain objects for optimized-out scopes,
    613  // declarative environments may contain internal JSFunction objects, which
    614  // we shouldn't expose to the user.
    615  if (result.isObject()) {
    616    RootedObject obj(cx, &result.toObject());
    617    if (obj->is<JSFunction>() &&
    618        IsInternalFunctionObject(obj->as<JSFunction>()))
    619      result.setMagic(JS_OPTIMIZED_OUT);
    620  }
    621 
    622  return dbg->wrapDebuggeeValue(cx, result);
    623 }
    624 
    625 /* static */
    626 bool DebuggerEnvironment::setVariable(JSContext* cx,
    627                                      Handle<DebuggerEnvironment*> environment,
    628                                      HandleId id, HandleValue value_) {
    629  MOZ_ASSERT(environment->isDebuggee());
    630 
    631  Rooted<Env*> referent(cx, environment->referent());
    632  Debugger* dbg = environment->owner();
    633 
    634  RootedValue value(cx, value_);
    635  if (!dbg->unwrapDebuggeeValue(cx, &value)) {
    636    return false;
    637  }
    638 
    639  {
    640    Maybe<AutoRealm> ar;
    641    ar.emplace(cx, referent);
    642    if (!cx->compartment()->wrap(cx, &value)) {
    643      return false;
    644    }
    645    cx->markId(id);
    646 
    647    // This can trigger setters.
    648    ErrorCopier ec(ar);
    649 
    650    // Make sure the environment actually has the specified binding.
    651    bool found;
    652    if (!HasProperty(cx, referent, id, &found)) {
    653      return false;
    654    }
    655    if (!found) {
    656      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    657                                JSMSG_DEBUG_VARIABLE_NOT_FOUND);
    658      return false;
    659    }
    660 
    661    // Just set the property.
    662    if (!SetProperty(cx, referent, id, value)) {
    663      return false;
    664    }
    665  }
    666 
    667  return true;
    668 }