Beispiel #1
0
static const Class* get_cls(CVarRef class_or_object) {
    Class* cls = NULL;
    if (class_or_object.is(KindOfObject)) {
        ObjectData* obj = class_or_object.toCObjRef().get();
        cls = obj->getVMClass();
    } else {
        cls = Unit::loadClass(class_or_object.toString().get());
    }
    return cls;
}
static const Class* get_cls(const Variant& class_or_object) {
  Class* cls = nullptr;
  if (class_or_object.is(KindOfObject)) {
    ObjectData* obj = class_or_object.toCObjRef().get();
    cls = obj->getVMClass();
  } else if (class_or_object.isArray()) {
    // do nothing but avoid the toString conversion notice
  } else {
    cls = Unit::loadClass(class_or_object.toString().get());
  }
  return cls;
}
Beispiel #3
0
bool f_is_callable(CVarRef v, bool syntax /* = false */,
                   VRefParam name /* = null */) {
  bool ret = true;
  if (LIKELY(!syntax)) {
    CallerFrame cf;
    ObjectData* obj = NULL;
    HPHP::Class* cls = NULL;
    StringData* invName = NULL;
    const HPHP::Func* f = vm_decode_function(v, cf(), false, obj, cls,
                                                 invName, false);
    if (f == NULL) {
      ret = false;
    }
    if (invName != NULL) {
      decRefStr(invName);
    }
    if (!name.isReferenced()) return ret;
  }

  auto const tv_func = v.asCell();
  if (IS_STRING_TYPE(tv_func->m_type)) {
    if (name.isReferenced()) name = tv_func->m_data.pstr;
    return ret;
  }

  if (tv_func->m_type == KindOfArray) {
    CArrRef arr = tv_func->m_data.parr;
    CVarRef clsname = arr.rvalAtRef(int64_t(0));
    CVarRef mthname = arr.rvalAtRef(int64_t(1));
    if (arr.size() != 2 ||
        &clsname == &null_variant ||
        &mthname == &null_variant) {
      name = v.toString();
      return false;
    }

    auto const tv_meth = mthname.asCell();
    if (!IS_STRING_TYPE(tv_meth->m_type)) {
      if (name.isReferenced()) name = v.toString();
      return false;
    }

    auto const tv_cls = clsname.asCell();
    if (tv_cls->m_type == KindOfObject) {
      name = tv_cls->m_data.pobj->o_getClassName();
    } else if (IS_STRING_TYPE(tv_cls->m_type)) {
      name = tv_cls->m_data.pstr;
    } else {
      name = v.toString();
      return false;
    }

    name = concat3(name, s_colon2, tv_meth->m_data.pstr);
    return ret;
  }

  if (tv_func->m_type == KindOfObject) {
    ObjectData *d = tv_func->m_data.pobj;
    const Func* invoke = d->getVMClass()->lookupMethod(s__invoke.get());
    if (name.isReferenced()) {
      if (d->instanceof(c_Closure::classof())) {
        // Hack to stop the mangled name from showing up
        name = s_Closure__invoke;
      } else {
        name = d->o_getClassName() + "::__invoke";
      }
    }
    return invoke != NULL;
  }

  return false;
}
Beispiel #4
0
Object c_Closure::t_bindto(const Variant& newthis, const Variant& scope) {
  if (RuntimeOption::RepoAuthoritative &&
      RuntimeOption::EvalAllowScopeBinding) {
    raise_warning("Closure binding is not supported in RepoAuthoritative mode");
    return Object{};
  }

  auto const cls = getVMClass();
  auto const invoke = cls->getCachedInvoke();

  ObjectData* od = nullptr;
  if (newthis.isObject()) {
    if (invoke->isStatic()) {
      raise_warning("Cannot bind an instance to a static closure");
    } else {
      od = newthis.getObjectData();
    }
  } else if (!newthis.isNull()) {
    raise_warning("Closure::bindto() expects parameter 1 to be object");
    return Object{};
  }

  auto const curscope = invoke->cls();
  auto newscope = curscope;

  if (scope.isObject()) {
    newscope = scope.getObjectData()->getVMClass();
  } else if (scope.isString()) {
    auto const className = scope.getStringData();

    if (!className->equal(s_static.get())) {
      newscope = Unit::loadClass(className);
      if (!newscope) {
        raise_warning("Class '%s' not found", className->data());
        return Object{};
      }
    }
  } else if (scope.isNull()) {
    newscope = nullptr;
  } else {
    raise_warning("Closure::bindto() expects parameter 2 "
                  "to be string or object");
    return Object{};
  }

  if (od && !newscope) {
    // Bound closures should be scoped.  If no scope is specified, scope it to
    // the Closure class.
    newscope = static_cast<Class*>(c_Closure::classof());
  }

  bool thisNotOfCtx = od && !od->getVMClass()->classof(newscope);

  if (!RuntimeOption::EvalAllowScopeBinding) {
    if (newscope != curscope) {
      raise_warning("Re-binding closure scopes is disabled");
      return Object{};
    }

    if (thisNotOfCtx) {
      raise_warning("Binding to objects not subclassed from closure "
                    "context is disabled");
      return Object{};
    }
  }

  c_Closure* clone = Clone(this);
  clone->setClass(nullptr);

  Attr curattrs = invoke->attrs();
  Attr newattrs = static_cast<Attr>(curattrs & ~AttrHasForeignThis);

  if (od) {
    od->incRefCount();
    clone->setThis(od);

    if (thisNotOfCtx) {
      // If the bound $this is not a subclass of the context class, then we
      // have to pessimize translation.
      newattrs |= AttrHasForeignThis;
    }
  } else if (newscope) {
    // If we attach a scope to a function with no bound $this we need to make
    // the function static.
    newattrs |= AttrStatic;
    clone->setClass(newscope);
  }

  // If we are changing either the scope or the attributes of the closure, we
  // need to re-scope its Closure subclass.
  if (newscope != curscope || newattrs != curattrs) {
    assert(newattrs != AttrNone);

    auto newcls = cls->rescope(newscope, newattrs);
    clone->setVMClass(newcls);
  }

  return Object(clone);
}
Beispiel #5
0
bool f_is_callable(CVarRef v, bool syntax /* = false */,
                   VRefParam name /* = null */) {
  bool ret = true;
  if (LIKELY(!syntax)) {
    if (hhvm) {
      CallerFrame cf;
      ObjectData* obj = NULL;
      HPHP::VM::Class* cls = NULL;
      StringData* invName = NULL;
      const HPHP::VM::Func* f = vm_decode_function(v, cf(), false, obj, cls,
                                                   invName, false);
      if (f == NULL) {
        ret = false;
      }
      if (invName != NULL) {
        LITSTR_DECREF(invName);
      }
    } else {
      MethodCallPackage mcp;
      String classname, methodname;
      bool doBind;
      ret = get_user_func_handler(v, true, mcp,
                                  classname, methodname, doBind, false);
      if (ret && mcp.ci->m_flags & (CallInfo::Protected|CallInfo::Private)) {
        classname = mcp.getClassName();
        if (!ClassInfo::HasAccess(classname, *mcp.name,
                                  mcp.ci->m_flags & CallInfo::StaticMethod ||
                                  !mcp.obj,
                                  mcp.obj)) {
          ret = false;
        }
      }
    }
    if (!name.isReferenced()) return ret;
  }

  Variant::TypedValueAccessor tv_func = v.getTypedAccessor();
  if (Variant::IsString(tv_func)) {
    if (name.isReferenced()) name = Variant::GetStringData(tv_func);
    return ret;
  }

  if (Variant::GetAccessorType(tv_func) == KindOfArray) {
    CArrRef arr = Variant::GetAsArray(tv_func);
    CVarRef clsname = arr.rvalAtRef(0LL);
    CVarRef mthname = arr.rvalAtRef(1LL);
    if (arr.size() != 2 ||
        &clsname == &null_variant ||
        &mthname == &null_variant) {
      name = v.toString();
      return false;
    }

    Variant::TypedValueAccessor tv_meth = mthname.getTypedAccessor();
    if (!Variant::IsString(tv_meth)) {
      if (name.isReferenced()) name = v.toString();
      return false;
    }

    Variant::TypedValueAccessor tv_cls = clsname.getTypedAccessor();
    if (Variant::GetAccessorType(tv_cls) == KindOfObject) {
      name = Variant::GetObjectData(tv_cls)->o_getClassName();
    } else if (Variant::IsString(tv_cls)) {
      name = Variant::GetStringData(tv_cls);
    } else {
      name = v.toString();
      return false;
    }

    name = concat3(name, "::", Variant::GetAsString(tv_meth));
    return ret;
  }

  if (Variant::GetAccessorType(tv_func) == KindOfObject) {
    ObjectData *d = Variant::GetObjectData(tv_func);
    if (hhvm) {
      static const StringData* sd__invoke
        = StringData::GetStaticString("__invoke");
      const VM::Func* invoke = d->getVMClass()->lookupMethod(sd__invoke);
      if (name.isReferenced()) {
        if (d->o_instanceof("closure")) {
          // Hack to stop the mangled name from showing up
          name = "Closure::__invoke";
        } else {
          name = d->o_getClassName() + "::__invoke";
        }
      }
      return invoke != NULL;
    } else {
      void *extra;
      if (d->t___invokeCallInfoHelper(extra)) {
        name = d->o_getClassName() + "::__invoke";
        return ret;
      }
      if (name.isReferenced()) {
        name = v.toString();
      }
    }
  }

  return false;
}
Beispiel #6
0
xdebug_xml_node* xdebug_var_export_xml_node(const char* name,
                                            const char* fullName,
                                            const char* facet,
                                            const Variant& var,
                                            XDebugExporter& exporter) {
  // Setup the node. Each const cast is necessary due to xml api
  xdebug_xml_node* node = xdebug_xml_node_init("property");
  if (name != nullptr) {
    xdebug_xml_add_attribute_ex(node, "name", const_cast<char*>(name), 0, 1);
  }
  if (fullName != nullptr) {
    xdebug_xml_add_attribute_ex(node, "fullname", const_cast<char*>(fullName),
                                0, 1);
  }
  if (facet != nullptr) {
    xdebug_xml_add_attribute_ex(node, "facet", const_cast<char*>(facet), 0, 1);
  }
  xdebug_xml_add_attribute_ex(node, "address",
                              xdebug_sprintf("%ld", (long) &var), 0, 1);

  // Case on the type for the rest
  if (var.isBoolean()) {
    xdebug_xml_add_attribute(node, "type", "bool");
    xdebug_xml_add_text(node, xdebug_sprintf("%d",  var.toBoolean()));
  } else if (var.isNull()) {
    xdebug_xml_add_attribute(node, "type", "null");
  } else if (var.isInteger()) {
    xdebug_xml_add_attribute(node, "type", "int");
    xdebug_xml_add_text(node, xdebug_sprintf("%ld", var.toInt64()));
  } else if (var.isDouble()) {
    xdebug_xml_add_attribute(node, "type", "float");
    xdebug_xml_add_text(node, xdebug_sprintf("%lG", var.toDouble()));
  } else if (var.isString()) {
    // Add the type and the original size
    String str = var.toString();
    xdebug_xml_add_attribute(node, "type", "string");
    xdebug_xml_add_attribute_ex(node, "size",
                                xdebug_sprintf("%d", str.size()), 0, 1);

    // Possibly shrink the string, then add it to the node
    if (exporter.max_data != 0 && str.size() > exporter.max_data) {
      str = str.substr(0, exporter.max_data);
    }
    xdebug_xml_add_text_encodel(node, xdstrdup(str.data()), str.size());
  } else if (var.isArray()) {
    Array arr = var.toArray();
    xdebug_xml_add_attribute(node, "type", "array");
    xdebug_xml_add_attribute(node, "children",
                             const_cast<char*>(arr.size() > 0 ? "1" : "0"));

    // If we've already seen this object, return
    if (exporter.counts[arr.get()]++ > 0) {
      xdebug_xml_add_attribute(node, "recursive", "1");
      return node;
    }

    // Write the # of children then short-circuit if we are too deep
    xdebug_xml_add_attribute_ex(node, "numchildren",
                                xdebug_sprintf("%d", arr.size()), 0, 1);
    if (exporter.level++ >= exporter.max_depth) {
      return node;
    }

    // Compute the page and the start/end indices
    // Note that php xdebug doesn't support pages except for at the top level
    uint32_t page = exporter.level == 1 ? exporter.page : 0;
    uint32_t start = page * exporter.max_children;
    uint32_t end = (page + 1) * exporter.max_children;
    xdebug_xml_add_attribute_ex(node, "page", xdebug_sprintf("%d", page), 0, 1);
    xdebug_xml_add_attribute_ex(node, "pagesize",
                                xdebug_sprintf("%d", exporter.max_children),
                                0, 1);
    // Add each child
    ArrayIter iter(arr);
    iter.setPos(start);
    for (uint32_t i = start; i < end && iter; i++, ++iter) {
      xdebug_array_element_export_xml_node(*node, name,
                                           iter.first(),
                                           iter.second(),
                                           exporter);
    }

    // Done at this level
    exporter.level--;
    exporter.counts[arr.get()]--;
  } else if (var.isObject()) {
    // TODO(#3704) This could be merged into the above array code. For now,
    // it's separate as this was pulled originally from xdebug
    ObjectData* obj = var.toObject().get();
    Class* cls = obj->getVMClass();
    Array props = get_object_props(obj);

    // Add object info
    xdebug_xml_add_attribute(node, "type", "object");
    xdebug_xml_add_attribute_ex(node, "classname",
                                xdstrdup(cls->name()->data()), 0, 1);
    xdebug_xml_add_attribute(node, "children",
                             const_cast<char*>(props.size() ? "1" : "0"));

    // If we've already seen this object, return
    if (exporter.counts[obj]++ > 0) {
      xdebug_xml_add_attribute(node, "recursive", "1");
      return node;
    }

    // Add the # of props then short circuit if we are too deep
    xdebug_xml_add_attribute_ex(node, "numchildren",
                                xdebug_sprintf("%d", props.size()), 0, 1);
    if (exporter.level++ >= exporter.max_depth) {
      return node;
    }

    // Compute the page and the start/end indices
    // Note that php xdebug doesn't support pages except for at the top level
    uint32_t page = exporter.level == 1 ? exporter.page : 0;
    uint32_t start = page * exporter.max_children;
    uint32_t end = (page + 1) * exporter.max_children;
    xdebug_xml_add_attribute_ex(node, "page", xdebug_sprintf("%d", page), 0, 1);
    xdebug_xml_add_attribute_ex(node, "pagesize",
                                xdebug_sprintf("%d", exporter.max_children),
                                0, 1);

    // Add each property
    ArrayIter iter(props);
    iter.setPos(start);
    for (uint32_t i = start; i < end && iter; i++, ++iter) {
      xdebug_object_element_export_xml_node(*node, name, obj,
                                            iter.first(),
                                            iter.second(),
                                            exporter);
    }

    // Done at this level
    exporter.level--;
    exporter.counts[(void*) obj]--;
  } else if (var.isResource()) {
    ResourceData* res = var.toResource().get();
    xdebug_xml_add_attribute(node, "type", "resource");
    const char* text = xdebug_sprintf("resource id='%ld' type='%s'",
                                      res->o_getId(),
                                      res->o_getResourceName().data());
    xdebug_xml_add_text(node, const_cast<char*>(text));
  } else {
    xdebug_xml_add_attribute(node, "type", "null");
  }
  return node;
}
Beispiel #7
0
static Variant eval_for_assert(ActRec* const curFP, const String& codeStr) {
  String prefixedCode = concat3("<?php return ", codeStr, ";");

  auto const oldErrorLevel =
    s_option_data->assertQuietEval ? HHVM_FN(error_reporting)(Variant(0)) : 0;
  SCOPE_EXIT {
    if (s_option_data->assertQuietEval) HHVM_FN(error_reporting)(oldErrorLevel);
  };

  auto const unit = g_context->compileEvalString(prefixedCode.get());
  if (unit == nullptr) {
    raise_recoverable_error("Syntax error in assert()");
    // Failure to compile the eval string doesn't count as an
    // assertion failure.
    return Variant(true);
  }

  if (!(curFP->func()->attrs() & AttrMayUseVV)) {
    throw_not_supported("assert()",
                        "assert called from non-varenv function");
  }

  if (!curFP->hasVarEnv()) {
    curFP->setVarEnv(VarEnv::createLocal(curFP));
  }
  auto varEnv = curFP->getVarEnv();

  if (curFP != vmfp()) {
    // If we aren't using FCallBuiltin, the stack frame of the call to assert
    // will be in middle of the code we are about to eval and our caller, whose
    // varEnv we want to use. The invokeFunc below will get very confused if
    // this is the case, since it will be using a varEnv that belongs to the
    // wrong function on the stack. So, we rebind it here, to match what
    // invokeFunc will expect.
    assert(!vmfp()->hasVarEnv());
    vmfp()->setVarEnv(varEnv);
    varEnv->enterFP(curFP, vmfp());
  }

  ObjectData* thiz = nullptr;
  Class* cls = nullptr;
  Class* ctx = curFP->func()->cls();
  if (ctx) {
    if (curFP->hasThis()) {
      thiz = curFP->getThis();
      cls = thiz->getVMClass();
    } else {
      cls = curFP->getClass();
    }
  }
  auto const func = unit->getMain(ctx);
  return Variant::attach(
    g_context->invokeFunc(
      func,
      init_null_variant,
      thiz,
      cls,
      varEnv,
      nullptr,
      ExecutionContext::InvokePseudoMain
    )
  );
}
Beispiel #8
0
void gc_detect_cycles(const std::string& filename) {
  TRACE(1, "GC: starting gc_detect_cycles\n");

  GCState state;
  collect_algorithm<GarbageDetector>(state);

  std::ofstream out(filename.c_str());
  if (!out.is_open()) {
    raise_error("couldn't open output file for gc_detect_cycles, %s",
                strerror(errno));
    return;
  }

  uint32_t nextNodeId = 1;
  hphp_hash_map<void*,uint32_t> nodeIds;

  out << "graph [\n"
         "  directed 1\n";

  // Print nodes.
  for (TypedObjSet::const_iterator it = state.m_cyclicGarbage.begin();
      it != state.m_cyclicGarbage.end();
      ++it) {
    uint32_t thisNodeId = nextNodeId++;
    nodeIds[it->second] = thisNodeId;

    const char* name;
    const char* color;
    switch (it->first) {
    case KindOfObject: {
      ObjectData* od = static_cast<ObjectData*>(it->second);
      name = od->getVMClass()->nameRef().data();
      color = "#FFCC00";
      break;
    }
    case KindOfArray:
      name = "array()";
      color = "#CCCCFF";
      break;
    case KindOfRef:
      name = "RefData";
      color = "#33CCCC";
      break;
    default:
      not_reached();
    }
    out << "  node [ id " << thisNodeId << "\n"
           "    graphics [\n"
           "      type \"roundrectangle\"\n"
           "      fill \"" << color << "\"\n"
           "    ]\n"
           "    LabelGraphics [\n"
           "      anchor \"e\"\n"
           "      alignment \"left\"\n"
           "      fontName \"Consolas\"\n"
           "      text \"" << name << "\"\n"
           "    ]\n"
           "  ]\n";
  }

  // Print edges.
  for (TypedObjSet::const_iterator it = state.m_cyclicGarbage.begin();
      it != state.m_cyclicGarbage.end();
      ++it) {
    EdgePrinter p(nodeIds, nodeIds[it->second], out);
    switch (it->first) {
    case KindOfObject:
      trace(p, static_cast<ObjectData*>(it->second));
      break;
    case KindOfRef:
      trace(p, static_cast<RefData*>(it->second));
      break;
    case KindOfArray:
      trace(p, static_cast<ArrayData*>(it->second));
      break;
    default:
      assert(false);
    }
  }

  out << "]\n";

  TRACE(1, "GC: %zu objects were part of cycles; wrote to %s\n",
           state.m_cyclicGarbage.size(),
           filename.c_str());
}
Beispiel #9
0
bool f_is_callable(CVarRef v, bool syntax /* = false */,
                   VRefParam name /* = null */) {
  bool ret = true;
  if (LIKELY(!syntax)) {
    CallerFrame cf;
    ObjectData* obj = NULL;
    HPHP::VM::Class* cls = NULL;
    StringData* invName = NULL;
    const HPHP::VM::Func* f = vm_decode_function(v, cf(), false, obj, cls,
                                                 invName, false);
    if (f == NULL) {
      ret = false;
    }
    if (invName != NULL) {
      decRefStr(invName);
    }
    if (!name.isReferenced()) return ret;
  }

  Variant::TypedValueAccessor tv_func = v.getTypedAccessor();
  if (Variant::IsString(tv_func)) {
    if (name.isReferenced()) name = Variant::GetStringData(tv_func);
    return ret;
  }

  if (Variant::GetAccessorType(tv_func) == KindOfArray) {
    CArrRef arr = Variant::GetAsArray(tv_func);
    CVarRef clsname = arr.rvalAtRef(int64_t(0));
    CVarRef mthname = arr.rvalAtRef(int64_t(1));
    if (arr.size() != 2 ||
        &clsname == &null_variant ||
        &mthname == &null_variant) {
      name = v.toString();
      return false;
    }

    Variant::TypedValueAccessor tv_meth = mthname.getTypedAccessor();
    if (!Variant::IsString(tv_meth)) {
      if (name.isReferenced()) name = v.toString();
      return false;
    }

    Variant::TypedValueAccessor tv_cls = clsname.getTypedAccessor();
    if (Variant::GetAccessorType(tv_cls) == KindOfObject) {
      name = Variant::GetObjectData(tv_cls)->o_getClassName();
    } else if (Variant::IsString(tv_cls)) {
      name = Variant::GetStringData(tv_cls);
    } else {
      name = v.toString();
      return false;
    }

    name = concat3(name, "::", Variant::GetAsString(tv_meth));
    return ret;
  }

  if (Variant::GetAccessorType(tv_func) == KindOfObject) {
    ObjectData *d = Variant::GetObjectData(tv_func);
    static const StringData* sd__invoke
      = StringData::GetStaticString("__invoke");
    const VM::Func* invoke = d->getVMClass()->lookupMethod(sd__invoke);
    if (name.isReferenced()) {
      if (d->instanceof(c_Closure::s_cls)) {
        // Hack to stop the mangled name from showing up
        name = "Closure::__invoke";
      } else {
        name = d->o_getClassName() + "::__invoke";
      }
    }
    return invoke != NULL;
  }

  return false;
}
Object c_GenVectorWaitHandle::ti_create(const Variant& dependencies) {
  ObjectData* obj;
  if (UNLIKELY(!dependencies.isObject() ||
      !(obj = dependencies.getObjectData())->isCollection() ||
      obj->collectionType() != CollectionType::Vector)) {
    SystemLib::throwInvalidArgumentExceptionObject(
      "Expected dependencies to be an instance of Vector");
  }
  assertx(collections::isType(obj->getVMClass(), CollectionType::Vector));
  auto deps = req::ptr<c_Vector>::attach(c_Vector::Clone(obj));
  auto ctx_idx = std::numeric_limits<context_idx_t>::max();
  for (int64_t iter_pos = 0; iter_pos < deps->size(); ++iter_pos) {
    Cell* current = deps->at(iter_pos);

    auto const child = c_WaitHandle::fromCell(current);
    if (UNLIKELY(!child)) {
      SystemLib::throwInvalidArgumentExceptionObject(
        "Expected dependencies to be a vector of WaitHandle instances");
    }

    if (!child->isFinished()) {
      ctx_idx = std::min(
        ctx_idx,
        static_cast<c_WaitableWaitHandle*>(child)->getContextIdx()
      );
    }
  }

  Object exception;
  for (int64_t iter_pos = 0; iter_pos < deps->size(); ++iter_pos) {

    auto current = tvAssertCell(deps->at(iter_pos));
    assert(current->m_type == KindOfObject);
    assert(current->m_data.pobj->instanceof(c_WaitHandle::classof()));
    auto child = static_cast<c_WaitHandle*>(current->m_data.pobj);

    if (child->isSucceeded()) {
      auto result = child->getResult();
      deps->set(iter_pos, &result);
    } else if (child->isFailed()) {
      putException(exception, child->getException());
    } else {
      assert(child->instanceof(c_WaitableWaitHandle::classof()));
      auto child_wh = static_cast<c_WaitableWaitHandle*>(child);

      auto my_wh = req::make<c_GenVectorWaitHandle>();
      my_wh->initialize(exception, deps.get(), iter_pos, ctx_idx, child_wh);
      AsioSession* session = AsioSession::Get();
      if (UNLIKELY(session->hasOnGenVectorCreate())) {
        session->onGenVectorCreate(my_wh.get(), dependencies);
      }
      return Object(std::move(my_wh));
    }
  }

  if (exception.isNull()) {
    return Object::attach(c_StaticWaitHandle::CreateSucceeded(
      make_tv<KindOfObject>(deps.detach())));
  } else {
    return Object::attach(c_StaticWaitHandle::CreateFailed(exception.detach()));
  }
}