hphp/runtime/ext/reflection/ext_reflection.cpp (2,154 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | | Copyright (c) 1997-2010 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/ext/reflection/ext_reflection.h" #include "hphp/runtime/base/array-init.h" #include "hphp/runtime/base/builtin-functions.h" #include "hphp/runtime/base/file.h" #include "hphp/runtime/base/runtime-option.h" #include "hphp/runtime/base/string-hash-set.h" #include "hphp/runtime/base/string-util.h" #include "hphp/runtime/base/tv-refcount.h" #include "hphp/runtime/base/type-structure.h" #include "hphp/runtime/base/type-variant.h" #include "hphp/runtime/base/unit-cache.h" #include "hphp/runtime/base/vanilla-dict.h" #include "hphp/runtime/vm/jit/translator-inline.h" #include "hphp/runtime/vm/jit/translator-runtime.h" #include "hphp/runtime/vm/native-data.h" #include "hphp/runtime/vm/native-prop-handler.h" #include "hphp/runtime/server/source-root-info.h" #include "hphp/runtime/ext/debugger/ext_debugger.h" #include "hphp/runtime/ext/std/ext_std_closure.h" #include "hphp/runtime/ext/collections/ext_collections-set.h" #include "hphp/runtime/ext/std/ext_std_misc.h" #include "hphp/runtime/ext/string/ext_string.h" #include "hphp/runtime/ext/extension-registry.h" #include "hphp/system/systemlib.h" #include <functional> #include <boost/algorithm/string/predicate.hpp> namespace HPHP { /////////////////////////////////////////////////////////////////////////////// const StaticString s_name("name"), s___name("__name"), s_version("version"), s_info("info"), s_ini("ini"), s_constants("constants"), s_constructor("constructor"), s_functions("functions"), s_classes("classes"), s_access("access"), s_public("public"), s_protected("protected"), s_private("private"), s_file("file"), s_line1("line1"), s_line2("line2"), s_doc("doc"), s_modifiers("modifiers"), s_class("class"), s_prototype("prototype"), s_ref("ref"), s_inout("inout"), s_readonly("readonly"), s_index("index"), s_type("type"), s_nullable("nullable"), s_msg("msg"), s_is_optional("is_optional"), s_is_variadic("is_variadic"), s_default("default"), s_defaultValue("defaultValue"), s_defaultText("defaultText"), s_params("params"), s_final("final"), s_abstract("abstract"), s_instantiable("instantiable"), s_internal("internal"), s_is_async("is_async"), s_is_closure("is_closure"), s_is_generator("is_generator"), s_hphp("hphp"), s_static_variables("static_variables"), s_extension("extension"), s_interfaces("interfaces"), s_traits("traits"), s_interface("interface"), s_trait("trait"), s_methods("methods"), s_properties("properties"), s_private_properties("private_properties"), s_properties_index("properties_index"), s_private_properties_index("private_properties_index"), s_attributes("attributes"), s_function("function"), s_trait_aliases("trait_aliases"), s_varg("varg"), s___invoke("__invoke"), s_return_type("return_type"), s_accessible("accessible"), s_reflectionexception("ReflectionException"), s_reflectionextension("ReflectionExtension"), s_type_hint("type_hint"), s_type_hint_builtin("type_hint_builtin"), s_type_hint_nullable("type_hint_nullable"), s_is_reified("is_reified"), s_is_soft("is_soft"), s_is_warn("is_warn"); Class* Reflection::s_ReflectionExceptionClass = nullptr; Class* Reflection::s_ReflectionExtensionClass = nullptr; Class* get_cls(const Variant& class_or_object) { if (class_or_object.is(KindOfObject)) { return class_or_object.asCObjRef()->getVMClass(); } else if (class_or_object.isClass()) { return class_or_object.toClassVal(); } return Class::load(class_or_object.toString().get()); } const Func* get_method_func(const Class* cls, const String& meth_name) { const Func* func = cls->lookupMethod(meth_name.get()); if (!func) { if (cls->attrs() & (AttrInterface | AttrAbstract | AttrTrait)) { const Class::InterfaceMap& ifaces = cls->allInterfaces(); for (int i = 0, size = ifaces.size(); i < size; i++) { func = ifaces[i]->lookupMethod(meth_name.get()); if (func) { break; } } } } assertx(func == nullptr || func->isMethod()); return func; } Variant default_arg_from_php_code(const Func::ParamInfo& fpi, const Func* func, unsigned argIdx) { assertx(fpi.hasDefaultValue()); if (fpi.hasScalarDefaultValue()) { // Most of the time the default value is scalar, so we can // avoid evaling in the common case return tvAsVariant((TypedValue*)&fpi.defaultValue); } // Eval PHP code to get the default value. Note that eval here can throw a // fatal, e.g. due to the use of undefined class constants or static:: . // When this happens, instead of fataling the execution, set the default // value to null and raise a warning. auto const on_eval_exn = [&] { raise_warning("Failed to eval default value of %s()'s $%s argument for " "reflection, assigning NULL instead", func->fullNameStr().data(), func->localNames()[argIdx]->data()); return init_null_variant; }; try { return g_context->getEvaledArg( fpi.phpCode, // We use cls() instead of implCls() because we want the namespace and // class context for which the closure is scoped, not that of the // Closure subclass (which, among other things, is always globally // namespaced). func->cls() ? func->cls()->nameStr() : func->nameStr(), func->unit() ); } catch (const Exception&) { return on_eval_exn(); } catch (const Object&) { if (RO::EvalFixDefaultArgReflection > 1) { return on_eval_exn(); } else { if (RO::EvalFixDefaultArgReflection > 0) { raise_notice("Evaluation of default arg initializer in func=%s, arg=%s " "threw a PHP exception--this reflection call won't throw " "in future HHVMs!", func->fullNameStr().data(), func->localNames()[argIdx]->data()); } throw; } } } Array HHVM_FUNCTION(hphp_get_extension_info, const String& name) { Extension *ext = ExtensionRegistry::get(name); return make_dict_array( s_name, name, s_version, ext ? ext->getVersion() : "", s_info, empty_string(), s_ini, empty_dict_array(), s_constants, empty_dict_array(), s_functions, empty_dict_array(), s_classes, empty_dict_array() ); } int get_modifiers(Attr attrs, bool cls, bool prop) { int php_modifier = 0; if (!prop) { // These bits have different meanings with properties if (attrs & AttrAbstract) php_modifier |= cls ? 0x20 : 0x02; if (attrs & AttrFinal) php_modifier |= cls ? 0x40 : 0x04; } if (!cls) { // AttrPublic bits are not valid on class (have other meaning) if (attrs & AttrStatic) php_modifier |= 0x01; if (attrs & AttrPublic) php_modifier |= 0x100; if (attrs & AttrProtected) php_modifier |= 0x200; if (attrs & AttrPrivate) php_modifier |= 0x400; } return php_modifier; } ALWAYS_INLINE static Attr attrs_from_modifiers(int php_modifier, bool cls) { Attr attrs = (Attr) 0; if (php_modifier & (cls ? 0x20 : 0x02)) { attrs = (Attr)(attrs | AttrAbstract); } if (php_modifier & (cls ? 0x40 : 0x04)) { attrs = (Attr)(attrs | AttrFinal); } if (php_modifier & 0x01) { attrs = (Attr)(attrs | AttrStatic); } if (!cls) { // AttrPublic bits are not valid on class (have other meaning) if (php_modifier & 0x100) { attrs = (Attr)(attrs | AttrPublic); } if (php_modifier & 0x200) { attrs = (Attr)(attrs | AttrProtected); } if (php_modifier & 0x400) { attrs = (Attr)(attrs | AttrPrivate); } } return attrs; } static void set_attrs(Array& ret, int modifiers) { if (modifiers & 0x100) { ret.set(s_access, VarNR(s_public).tv()); ret.set(s_accessible, make_tv<KindOfBoolean>(true)); } else if (modifiers & 0x200) { ret.set(s_access, VarNR(s_protected).tv()); ret.set(s_accessible, make_tv<KindOfBoolean>(false)); } else if (modifiers & 0x400) { ret.set(s_access, VarNR(s_private).tv()); ret.set(s_accessible, make_tv<KindOfBoolean>(false)); } else { assertx(false); } ret.set(s_modifiers, make_tv<KindOfInt64>(modifiers)); if (modifiers & 0x1) { ret.set(s_static, make_tv<KindOfBoolean>(true)); } if (modifiers & 0x44) { ret.set(s_final, make_tv<KindOfBoolean>(true)); } if (modifiers & 0x22) { ret.set(s_abstract, make_tv<KindOfBoolean>(true)); } } static void set_empty_doc_comment(Array& ret) { ret.set(s_doc, make_tv<KindOfBoolean>(false)); } static void set_doc_comment(Array& ret, const StringData* comment, bool isBuiltin) { assertx(ret.isDict()); if (comment == nullptr || comment->empty()) { set_empty_doc_comment(ret); } else if (isBuiltin && !HHVM_FUNCTION(hphp_debugger_attached)) { set_empty_doc_comment(ret); } else { assertx(!comment->isRefCounted()); ret.set(s_doc, make_tv<KindOfPersistentString>(comment)); } } static void set_instance_prop_info(Array& ret, const Class::Prop* prop, TypedValue default_val) { assertx(ret.isDict()); ret.set(s_name, make_tv<KindOfPersistentString>(prop->name)); ret.set(s_default, make_tv<KindOfBoolean>(true)); ret.set(s_defaultValue, default_val); set_attrs(ret, get_modifiers(prop->attrs, false, true) & ~0x66); ret.set(s_class, make_tv<KindOfPersistentString>(prop->cls->name())); set_doc_comment(ret, prop->preProp->docComment(), prop->cls->isBuiltin()); auto const user_type = prop->preProp->userType(); if (user_type && user_type->size()) { ret.set(s_type, make_tv<KindOfPersistentString>(user_type)); } else { ret.set(s_type, make_tv<KindOfBoolean>(false)); } } static void set_dyn_prop_info( Array &ret, TypedValue name, const StringData* className) { assertx(ret.isDict()); ret.set(s_name, name); set_attrs(ret, get_modifiers(AttrPublic, false, true) & ~0x66); ret.set(s_class, make_tv<KindOfPersistentString>(className)); set_empty_doc_comment(ret); ret.set(s_type, make_tv<KindOfBoolean>(false)); } static void set_static_prop_info(Array &ret, const Class::SProp* prop) { assertx(ret.isDict()); ret.set(s_name, make_tv<KindOfPersistentString>(prop->name)); ret.set(s_default, make_tv<KindOfBoolean>(true)); ret.set(s_defaultValue, prop->val); set_attrs(ret, get_modifiers(prop->attrs, false, true) & ~0x66); ret.set(s_class, make_tv<KindOfPersistentString>(prop->cls->name())); set_doc_comment(ret, prop->preProp->docComment(), prop->cls->isBuiltin()); auto const user_type = prop->preProp->userType(); if (user_type && user_type->size()) { ret.set(s_type, make_tv<KindOfPersistentString>(user_type)); } else { ret.set(s_type, make_tv<KindOfBoolean>(false)); } } static bool resolveConstant(const char *p, int64_t len, Variant &cns) { // ltrim while (len && (*p == ' ')) { p++; len--; } // rtrim while (len && (p[len-1] == ' ')) { len--; } String cname(p, len, CopyString); if (!f_defined(cname)) { cns = uninit_null(); return false; } cns = f_constant(cname); return true; } bool resolveDefaultParameterConstant(const char *value, int64_t valueLen, Variant &cns) { const char *p = value; const char *e = value + valueLen; const char *s; bool isLval = false; int64_t lval = 0; while ((s = strchr(p, '|'))) { isLval = true; if (!resolveConstant(p, s - p, cns)) { return false; } lval |= cns.toInt64(); p = s + 1; } if (!resolveConstant(p, e - p, cns)) { return false; } if (isLval) { cns = cns.toInt64() | lval; } return true; } static bool isConstructor(const Func* func) { PreClass* pcls = func->preClass(); if (!pcls || (pcls->attrs() & AttrInterface)) { return false; } if (func->implCls()) { return func == func->implCls()->getCtor(); } if (0 == strcasecmp("__construct", func->name()->data())) { return true; } /* A same named function is not a constructor in a trait */ if (pcls->attrs() & AttrTrait) return false; return pcls->name()->isame(func->name()); } static const Class* get_prototype_class_from_interfaces(const Class *cls, const Func *func) { // only looks at the interfaces if the method is public if (!func->isPublic()) return nullptr; const Class::InterfaceMap& interfaces = cls->allInterfaces(); for (unsigned int i = 0, size = interfaces.size(); i < size; i++) { const Class* iface = interfaces[i]; if (iface->preClass()->hasMethod(func->name())) return iface; } return nullptr; } Variant HHVM_FUNCTION(hphp_invoke, const String& name, const Variant& params) { return invoke(name, params); } static const StaticString s_invoke_not_instanceof_error( "Given object is not an instance of the class this method was declared in" ); static const StaticString s_invoke_non_object( "Non-object passed to Invoke()" ); Variant HHVM_FUNCTION(hphp_invoke_method, const Variant& obj, const String& cls, const String& name, const Variant& params) { if (obj.isNull()) { return invoke_static_method(cls, name, params); } if (!obj.is(KindOfObject)) { Reflection::ThrowReflectionExceptionObject(s_invoke_non_object); } auto const providedClass = Class::load(cls.get()); if (!providedClass) { raise_error("Call to undefined method %s::%s()", cls.data(), name.data()); } auto const selectedFunc = providedClass->lookupMethod(name.get()); if (!selectedFunc) { raise_error("Call to undefined method %s::%s()", cls.data(), name.data()); } auto const objData = obj.asCObjRef().get(); auto const implementingClass = selectedFunc->implCls(); if (!objData->instanceof(implementingClass)) { Reflection::ThrowReflectionExceptionObject(s_invoke_not_instanceof_error); } // Get the CallCtx this way instead of using vm_decode_function() because // vm_decode_function() has no way to specify a class independent from the // class::function being called. // Note that this breaks the rules for name lookup (for protected and private) // but that's okay because so does Zend's implementation. CallCtx ctx; ctx.cls = providedClass; ctx.this_ = objData; ctx.func = selectedFunc; ctx.dynamic = true; return Variant::attach( g_context->invokeFunc(ctx, params, RuntimeCoeffects::fixme()) ); } Object HHVM_FUNCTION(hphp_create_object, const String& name, const Variant& params) { return Object::attach(g_context->createObject(name.get(), params)); } Object HHVM_FUNCTION(hphp_create_object_without_constructor, const String& name) { return Object::attach( g_context->createObject(name.get(), init_null_variant, false) ); } Variant HHVM_FUNCTION(hphp_get_property, const Object& obj, const String& cls, const String& prop) { /* It's possible to get a ReflectionProperty for a property which * no longer exists. Silentyly fail to match PHP5 behavior */ return obj->o_get(prop, false /* error */, cls); } void HHVM_FUNCTION(hphp_set_property, const Object& obj, const String& cls, const String& prop, const Variant& value) { if (!cls.empty() && RuntimeOption::EvalAuthoritativeMode) { raise_error( "We've already made many assumptions about private variables. " "You can't change accessibility in Whole Program mode" ); } obj->o_set(prop, value, cls); } Variant HHVM_FUNCTION(hphp_get_static_property, const String& cls, const String& prop, bool force) { auto const sd = cls.get(); auto const class_ = Class::lookup(sd); if (!class_) { raise_error("Non-existent class %s", sd->data()); } VMRegAnchor _; CoeffectsAutoGuard _2; auto const lookup = class_->getSPropIgnoreLateInit( force ? class_ : arGetContextClass(vmfp()), prop.get() ); if (!lookup.val) { raise_error("Class %s does not have a property named %s", sd->data(), prop.get()->data()); } if (!lookup.accessible) { raise_error("Invalid access to class %s's property %s", sd->data(), prop.get()->data()); } return tvAsVariant(lookup.val); } void HHVM_FUNCTION(hphp_set_static_property, const String& cls, const String& prop, const Variant& value, bool force) { if (RuntimeOption::EvalAuthoritativeMode) { raise_error("Setting static properties through reflection is not " "allowed in RepoAuthoritative mode"); } auto const sd = cls.get(); auto const class_ = Class::lookup(sd); if (!class_) raise_error("Non-existent class %s", sd->data()); VMRegAnchor _; CoeffectsAutoGuard _2; auto const lookup = class_->getSPropIgnoreLateInit( force ? class_ : arGetContextClass(vmfp()), prop.get() ); if (!lookup.val) { raise_error("Class %s does not have a property named %s", sd->data(), prop.get()->data()); } if (!lookup.accessible) { raise_error("Invalid access to class %s's property %s", sd->data(), prop.get()->data()); } if (lookup.constant) { throw_cannot_modify_static_const_prop(sd->data(), prop.get()->data()); } auto const& sprop = class_->staticProperties()[lookup.slot]; auto const& tc = sprop.typeConstraint; // TODO(T61738946): We can remove the temporary here once we no longer coerce // class_meth types. auto tmp = value; if (RuntimeOption::EvalCheckPropTypeHints > 0 && tc.isCheckable()) { tc.verifyStaticProperty(tmp.asTypedValue(), class_, sprop.cls, prop.get()); } tvAsVariant(lookup.val) = tmp; } namespace { const StaticString s_classname("classname"); Array implTypeStructure(const Variant& cls_or_obj, const Variant& cns_name, bool no_throw) { SuppressClassConversionWarning suppressor; auto const cns_sd = cns_name.getStringDataOrNull(); if (!cns_sd) { auto name = cls_or_obj.toString(); auto const typeAlias = TypeAlias::load(name.get()); if (!typeAlias) { raise_error("Non-existent type alias %s", name.get()->data()); } auto const& preresolved = typeAlias->resolvedTypeStructure(); if (!preresolved.isNull()) { assertx(!preresolved.empty()); assertx(preresolved.isDict()); return preresolved; } auto const& typeStructure = typeAlias->typeStructure(); assertx(!typeStructure.empty()); assertx(typeStructure.isDict()); Array resolved; try { bool persistent = true; resolved = TypeStructure::resolve(name, typeStructure, persistent); } catch (Exception& e) { raise_error("resolving type alias %s failed. " "Have you declared all classes in the type alias", name.get()->data()); } assertx(!resolved.empty()); assertx(resolved.isDict()); return resolved; } auto const cls = get_cls(cls_or_obj); if (!cls) { raise_error("Class undefined: %s", cls_or_obj.toString().get()->data()); } auto const cls_sd = cls->name(); if (!cls->hasTypeConstant(cns_sd, true)) { raise_error("Non-existent type constant %s::%s", cls_sd->data(), cns_sd->data()); } auto ad = jit::loadClsTypeCnsHelper(cls, cns_sd, no_throw); return Array::attach(ad); } } // namespace /* * cls_or_obj: the name of a class or an instance of the class; * cns_name: the name of the type constant of the class; * * If the type constant exists and is not abstract, this function * returns the shape representing the type associated with the type * constant. */ Array HHVM_FUNCTION(type_structure, const Variant& cls_or_obj, const Variant& cns_name) { return implTypeStructure(cls_or_obj, cns_name, false); } Array HHVM_FUNCTION(type_structure_no_throw, const Variant& cls_or_obj, const Variant& cns_name) { return implTypeStructure(cls_or_obj, cns_name, true); } String HHVM_FUNCTION(type_structure_classname, const Variant& cls_or_obj, const Variant& cns_name) { auto const ts = implTypeStructure(cls_or_obj, cns_name, false); auto const classname = ts->get(s_classname.get(), true); assertx(isStringType(type(classname))); assertx(val(classname).pstr->isStatic()); return String::attach(val(classname).pstr); } [[noreturn]] void Reflection::ThrowReflectionExceptionObject(const Variant& message) { Object inst{s_ReflectionExceptionClass}; tvDecRefGen( g_context->invokeFunc(s_ReflectionExceptionClass->getCtor(), make_vec_array(message), inst.get()) ); throw_object(inst); } ///////////////////////////////////////////////////////////////////////////// // class ReflectionFuncHandle const StaticString s_ReflectionFuncHandle("ReflectionFuncHandle"); static TypedValue HHVM_METHOD(ReflectionFunctionAbstract, getFileName) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); if (func->isBuiltin()) { return make_tv<KindOfBoolean>(false); } auto file = func->unit()->filepath(); if (!file) file = staticEmptyString(); if (file->data()[0] != '/') { auto path = SourceRootInfo::RelativeToPhpRoot(StrNR(file)); return make_tv<KindOfString>(path.detach()); } assertx(!file->isRefCounted()); return make_tv<KindOfPersistentString>(file); } static Variant HHVM_METHOD(ReflectionFunctionAbstract, getStartLine) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); if (func->isBuiltin()) { return false; } return func->line1(); } static Variant HHVM_METHOD(ReflectionFunctionAbstract, getEndLine) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); if (func->isBuiltin()) { return false; } return func->line2(); } static TypedValue HHVM_METHOD(ReflectionFunctionAbstract, getDocComment) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto const comment = func->docComment(); if (comment == nullptr || comment->empty()) { return make_tv<KindOfBoolean>(false); } else { assertx(!comment->isRefCounted()); return make_tv<KindOfPersistentString>(const_cast<StringData*>(comment)); } } static String HHVM_METHOD(ReflectionFunctionAbstract, getName) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto ret = const_cast<StringData*>(func->name()); return String(ret); } static bool HHVM_METHOD(ReflectionFunctionAbstract, isHack) { return true; } static bool HHVM_METHOD(ReflectionFunctionAbstract, isInternal) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->isBuiltin(); } static bool HHVM_METHOD(ReflectionFunctionAbstract, isGenerator) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->isGenerator(); } static bool HHVM_METHOD(ReflectionFunctionAbstract, isAsync) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->isAsync(); } static bool HHVM_METHOD(ReflectionFunctionAbstract, isVariadic) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->hasVariadicCaptureParam(); } static int64_t HHVM_METHOD(ReflectionFunctionAbstract, getNumberOfParameters) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->numParams(); } ALWAYS_INLINE static Array get_function_param_info(const Func* func) { const Func::ParamInfoVec& params = func->params(); VecInit ai(func->numParams()); for (int i = 0; i < func->numParams(); ++i) { Array param = Array::CreateDict(); const Func::ParamInfo& fpi = params[i]; param.set(s_index, make_tv<KindOfInt64>(i)); param.set(s_name, make_tv<KindOfPersistentString>(func->localNames()[i])); auto const nonExtendedConstraint = fpi.typeConstraint.hasConstraint() && !fpi.typeConstraint.isExtended(); auto const type = nonExtendedConstraint ? fpi.typeConstraint.typeName() : staticEmptyString(); param.set(s_type, make_tv<KindOfPersistentString>(type)); const StringData* typeHint = fpi.userType ? fpi.userType : staticEmptyString(); param.set(s_type_hint, make_tv<KindOfPersistentString>(typeHint)); // callable typehint considered builtin; stdclass typehint is not if ( fpi.typeConstraint.isCallable() || (fpi.typeConstraint.underlyingDataType() && fpi.typeConstraint.underlyingDataType() != KindOfObject ) ) { param.set(s_type_hint_builtin, make_tv<KindOfBoolean>(true)); } else { param.set(s_type_hint_builtin, make_tv<KindOfBoolean>(false)); } param.set(s_function, make_tv<KindOfPersistentString>(func->name())); if (func->preClass()) { param.set( s_class, make_tv<KindOfPersistentString>( func->implCls() ? func->implCls()->name() : func->preClass()->name() ) ); } if (!nonExtendedConstraint || fpi.typeConstraint.isNullable()) { param.set(s_nullable, make_tv<KindOfBoolean>(true)); param.set(s_type_hint_nullable, make_tv<KindOfBoolean>(true)); } else { param.set(s_type_hint_nullable, make_tv<KindOfBoolean>(false)); } if (fpi.phpCode) { Variant v = default_arg_from_php_code(fpi, func, i); param.set(s_default, v); param.set(s_defaultText, make_tv<KindOfPersistentString>(fpi.phpCode)); } if (func->isInOut(i)) { param.set(s_inout, make_tv<KindOfBoolean>(true)); } if (func->isReadonly(i)) { param.set(s_readonly, make_tv<KindOfBoolean>(true)); } if (fpi.isVariadic()) { param.set(s_is_variadic, make_tv<KindOfBoolean>(true)); } { DictInit userAttrs(fpi.userAttributes.size()); for (auto const& attr : fpi.userAttributes) { userAttrs.set(StrNR{attr.first}, attr.second); } param.set(s_attributes, make_array_like_tv(userAttrs.create())); } ai.append(VarNR(param).tv()); } auto arr = ai.toArray(); bool isOptional = true; for (int i = func->numParams() - 1; i >= 0; i--) { auto& param = asArrRef(arr.lval(i)); isOptional = isOptional && (param.exists(s_default) || param.exists(s_is_variadic)); param.set(s_is_optional, isOptional); } return arr; } // helper for getParameters static Array HHVM_METHOD(ReflectionFunctionAbstract, getParamInfo) { // FIXME: each parameter info should be HNI with a handle to the // Func::ParamInfo auto const func = ReflectionFuncHandle::GetFuncFor(this_); return get_function_param_info(func); } // helper for getReturnTypeText static String HHVM_METHOD(ReflectionFunctionAbstract, getReturnTypeHint) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto retTypeSD = func->returnUserType(); if (retTypeSD && retTypeSD->size()) { auto ret = const_cast<StringData*>(retTypeSD); return String(ret); } return String(); } static Array HHVM_METHOD(ReflectionFunctionAbstract, getRetTypeInfo) { DictInit retTypeInfo{3}; auto name = HHVM_MN(ReflectionFunctionAbstract, getReturnTypeHint)(this_); if (name && !name.empty()) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto retType = func->returnTypeConstraint(); if (retType.isNullable()) { retTypeInfo.set(s_type_hint_nullable, make_tv<KindOfBoolean>(true)); } else { retTypeInfo.set(s_type_hint_nullable, make_tv<KindOfBoolean>(false)); } if ( retType.isCallable() || // callable type hint is considered builtin (retType.underlyingDataType() && retType.underlyingDataType() != KindOfObject ) ) { retTypeInfo.set(s_type_hint_builtin, make_tv<KindOfBoolean>(true)); } else { retTypeInfo.set(s_type_hint_builtin, make_tv<KindOfBoolean>(false)); } } else { name = staticEmptyString(); retTypeInfo.set(s_type_hint_nullable, make_tv<KindOfBoolean>(false)); retTypeInfo.set(s_type_hint_builtin, make_tv<KindOfBoolean>(false)); } retTypeInfo.set(s_type_hint, name); return retTypeInfo.toArray(); } namespace { const Array reified_generics_info_to_array(const ReifiedGenericsInfo& info) { VecInit arr(info.m_typeParamInfo.size()); for (auto tparam : info.m_typeParamInfo) { DictInit tparamArr(3); tparamArr.set(s_is_reified, make_tv<KindOfBoolean>(tparam.m_isReified)); tparamArr.set(s_is_soft, make_tv<KindOfBoolean>(tparam.m_isSoft)); tparamArr.set(s_is_warn, make_tv<KindOfBoolean>(tparam.m_isWarn)); arr.append(tparamArr.toArray()); } return arr.toArray(); } } // namespace static Array HHVM_METHOD(ReflectionFunctionAbstract, getReifiedTypeParamInfo) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return reified_generics_info_to_array(func->getReifiedGenericsInfo()); } const StaticString s_pure("pure"); const StaticString s_defaults("defaults"); static Array HHVM_METHOD(ReflectionFunctionAbstract, getCoeffects) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); std::vector<LowStringPtr> result; for (auto const& name : func->staticCoeffectNames()) { if (name->equal(s_pure.get())) continue; result.push_back(name); } if (func->staticCoeffectNames().empty()) { result.push_back(makeStaticString(s_defaults.get())); } if (func->hasCoeffectRules()) { for (auto const& rule : func->getCoeffectRules()) { auto const name = rule.toString(func); if (name) result.push_back(makeStaticString(*name)); } } VecInit arr(result.size()); for (auto& name : result) arr.append(make_tv<KindOfPersistentString>(name)); return arr.toArray(); } static Variant HHVM_METHOD(ReflectionFunctionAbstract, getModule) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto const name = func->unit()->moduleName(); if (!name) return init_null_variant; return String::attach(const_cast<StringData*>(name)); } static bool HHVM_METHOD(ReflectionFunctionAbstract, returnsReadonly) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrReadonlyReturn; } ALWAYS_INLINE static Array get_function_user_attributes(const Func* func) { auto userAttrs = func->userAttributes(); DictInit ai(userAttrs.size()); for (auto it = userAttrs.begin(); it != userAttrs.end(); ++it) { ai.set(StrNR(it->first).asString(), it->second); } return ai.toArray(); } static Array HHVM_METHOD(ReflectionFunctionAbstract, getAttributesNamespaced) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return get_function_user_attributes(func); } // ------------------------- class ReflectionMethod // helper for __construct static bool HHVM_METHOD(ReflectionMethod, __init, const Variant& cls_or_object, const String& meth_name) { auto const cls = get_cls(cls_or_object); if (!cls || meth_name.isNull()) { // caller raises exception return false; } auto const func = get_method_func(cls, meth_name); if (!func) { // caller raises exception return false; } assertx(func->isMethod()); ReflectionFuncHandle::Get(this_)->setFunc(func); return true; } static bool HHVM_METHOD(ReflectionMethod, isFinal) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrFinal; } static bool HHVM_METHOD(ReflectionMethod, isAbstract) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrAbstract; } static bool HHVM_METHOD(ReflectionMethod, isPublic) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrPublic; } static bool HHVM_METHOD(ReflectionMethod, isProtected) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrProtected; } static bool HHVM_METHOD(ReflectionMethod, isPrivate) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrPrivate; } static bool HHVM_METHOD(ReflectionMethod, isStatic) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrStatic; } static bool HHVM_METHOD(ReflectionMethod, isStaticInPrologue) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->isStaticInPrologue(); } static bool HHVM_METHOD(ReflectionMethod, isConstructor) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return isConstructor(func); } static bool HHVM_METHOD(ReflectionMethod, isReadonly) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return func->attrs() & AttrReadonlyThis; } static int HHVM_METHOD(ReflectionMethod, getModifiers) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); return get_modifiers(func->attrs(), false, false); } // private helper for getPrototype static String HHVM_METHOD(ReflectionMethod, getPrototypeClassname) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); const Class *prototypeCls = nullptr; if (func->baseCls() != nullptr && func->baseCls() != func->implCls()) { prototypeCls = func->baseCls(); const Class *result = get_prototype_class_from_interfaces( prototypeCls, func); if (result) { prototypeCls = result; } } else if (func->isMethod()) { // lookup the prototype in the interfaces prototypeCls = get_prototype_class_from_interfaces(func->implCls(), func); } if (prototypeCls) { auto ret = const_cast<StringData*>(prototypeCls->name()); return String(ret); } return String(); } // private helper for getDeclaringClass static String HHVM_METHOD(ReflectionMethod, getDeclaringClassname) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto ret = const_cast<StringData*>(func->implCls()->name()); return String(ret); } // ------------------------- class ReflectionFile const StaticString s_ReflectionFileHandle("ReflectionFileHandle"); // helper for __construct static String HHVM_METHOD(ReflectionFile, __init, const String& name) { if (name.isNull()) { Reflection::ThrowReflectionExceptionObject( "Tried to construct ReflectionFile but the name was null" ); } const Unit* unit = lookupUnit( File::TranslatePath(name).get(), "", nullptr, Native::s_noNativeFuncs, false ); if (!unit) { Reflection::ThrowReflectionExceptionObject(folly::sformat( "File '{}' does not exist", name )); } ReflectionFileHandle::Get(this_)->setUnit(unit); return String::attach(const_cast<StringData*>(unit->filepath())); } static Array HHVM_METHOD(ReflectionFile, getAttributesNamespaced) { auto const unit = ReflectionFileHandle::GetUnitFor(this_); assertx(unit); auto fileAttrs = unit->fileAttributes(); DictInit ai(fileAttrs.size()); for (auto it = fileAttrs.begin(); it != fileAttrs.end(); ++it) { ai.set(StrNR(it->first), tvAsCVarRef(&it->second)); } return ai.toArray(); } // ------------------------- class ReflectionFunction // helper for __construct static bool HHVM_METHOD(ReflectionFunction, __initName, const String& name) { if (name.isNull()) { return false; } const Func* func = Func::load(name.get()); if (!func) { return false; } ReflectionFuncHandle::Get(this_)->setFunc(func); return true; } // helper for __construct static bool HHVM_METHOD(ReflectionFunction, __initClosure, const Object& closure) { auto const cls = get_cls(closure); assertx(cls); if (!cls) { return false; } const Func* func = cls->lookupMethod(s___invoke.get()); if (!func) { // caller raises exception return false; } assertx(func->isClosureBody()); assertx(func->implCls()->isScopedClosure()); ReflectionFuncHandle::Get(this_)->setFunc(func); return true; } const StaticString s_ExpectedClosureInstance("Expected closure instance"); // helper for getClosureScopeClass static Variant HHVM_METHOD(ReflectionFunction, getClosureScopeClassname, const Object& closure) { if (!closure->instanceof(c_Closure::classof())) { SystemLib::throwExceptionObject(s_ExpectedClosureInstance); } if (auto scope = c_Closure::fromObject(closure.get())->getScope()) { return String(const_cast<StringData*>(scope->name())); } return init_null_variant; } static Variant HHVM_METHOD(ReflectionFunction, getClosureThisObject, const Object& closure) { if (!closure->instanceof(c_Closure::classof())) { SystemLib::throwExceptionObject(s_ExpectedClosureInstance); } auto const clos = c_Closure::fromObject(closure.get()); if (clos->hasThis()) { return Object{clos->getThis()}; } return init_null_variant; } ///////////////////////////////////////////////////////////////////////////// // class ReflectionClass const StaticString s_ReflectionClassHandle("ReflectionClassHandle"); // helper for __construct static String HHVM_METHOD(ReflectionClass, __init, const String& name) { return ReflectionClassHandle::Get(this_)->init(name); } static String HHVM_METHOD(ReflectionClass, getName) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->nameStr(); } static String HHVM_METHOD(ReflectionClass, getParentName) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->parentStr(); } static bool HHVM_METHOD(ReflectionClass, isHack) { return true; } static bool HHVM_METHOD(ReflectionClass, isInternal) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->attrs() & AttrBuiltin; } static bool HHVM_METHOD(ReflectionClass, isInstantiable) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return !(cls->attrs() & (AttrAbstract | AttrInterface | AttrTrait | AttrEnum | AttrEnumClass)) && (cls->getCtor()->attrs() & AttrPublic); } static bool HHVM_METHOD(ReflectionClass, isFinal) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->attrs() & AttrFinal; } static bool HHVM_METHOD(ReflectionClass, isAbstract) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->attrs() & AttrAbstract; } static bool HHVM_METHOD(ReflectionClass, isInterface) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->attrs() & AttrInterface; } static bool HHVM_METHOD(ReflectionClass, isTrait) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->attrs() & AttrTrait; } static bool HHVM_METHOD(ReflectionClass, isEnum) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->attrs() & (AttrEnum|AttrEnumClass); } static String HHVM_METHOD(ReflectionClass, getEnumUnderlyingType) { auto const cls = ReflectionClassHandle::GetClassFor(this_); if (!(cls->attrs() & (AttrEnum|AttrEnumClass))) { Reflection::ThrowReflectionExceptionObject( "Trying to read the Enum-type of a non-Enum"); } // use the unresolved preclass type to yield the userland types return StrNR(cls->preClass()->enumBaseTy().typeName()); } static int HHVM_METHOD(ReflectionClass, getModifiers) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return get_modifiers(cls->attrs(), true, false); } static TypedValue HHVM_METHOD(ReflectionClass, getFileName) { auto const cls = ReflectionClassHandle::GetClassFor(this_); if (cls->attrs() & AttrBuiltin) { return make_tv<KindOfBoolean>(false); } auto file = cls->preClass()->unit()->filepath(); if (!file) file = staticEmptyString(); if (file->data()[0] != '/') { auto path = SourceRootInfo::RelativeToPhpRoot(StrNR(file)); return make_tv<KindOfString>(path.detach()); } assertx(!file->isRefCounted()); return make_tv<KindOfPersistentString>(file); } static Variant HHVM_METHOD(ReflectionClass, getStartLine) { auto const cls = ReflectionClassHandle::GetClassFor(this_); if (cls->isBuiltin()) { return false; } return cls->preClass()->line1(); } static Variant HHVM_METHOD(ReflectionClass, getEndLine) { auto const cls = ReflectionClassHandle::GetClassFor(this_); if (cls->isBuiltin()) { return false; } return cls->preClass()->line2(); } static TypedValue HHVM_METHOD(ReflectionClass, getDocComment) { auto const cls = ReflectionClassHandle::GetClassFor(this_); auto const pcls = cls->preClass(); auto const comment = pcls->docComment(); if (comment == nullptr || comment->empty()) { return make_tv<KindOfBoolean>(false); } else { assertx(!comment->isRefCounted()); return make_tv<KindOfPersistentString>(const_cast<StringData*>(comment)); } } static Array HHVM_METHOD(ReflectionClass, getRequirementNames) { auto const cls = ReflectionClassHandle::GetClassFor(this_); if (!(cls->attrs() & (AttrTrait | AttrInterface))) { // requirements are applied to abstract/concrete classes when they use // a trait / implement an interface return empty_vec_array(); } auto const& requirements = cls->allRequirements(); auto numReqs = requirements.size(); if (numReqs == 0) { return empty_vec_array(); } VecInit pai(numReqs); for (int i = 0; i < numReqs; ++i) { auto const& req = requirements[i]; pai.append(Variant{const_cast<StringData*>(req->name())}); } return pai.toArray(); } static Array HHVM_METHOD(ReflectionClass, getInterfaceNames) { auto const cls = ReflectionClassHandle::GetClassFor(this_); auto st = req::make<c_Set>(); auto const& allIfaces = cls->allInterfaces(); st->reserve(allIfaces.size()); for (auto const& interface: cls->declInterfaces()) { st->add(const_cast<StringData*>(interface->name())); } if (allIfaces.size() > cls->declInterfaces().size()) { for (int i = 0; i < allIfaces.size(); ++i) { auto const& interface = allIfaces[i]; st->add(const_cast<StringData*>(interface->name())); } } return st->toVArray(); } static Array HHVM_METHOD(ReflectionClass, getTraitNames) { auto const cls = ReflectionClassHandle::GetClassFor(this_); auto const& traits = cls->preClass()->usedTraits(); VecInit ai(traits.size()); for (const StringData* traitName : traits) { ai.append(Variant{const_cast<StringData*>(traitName)}); } return ai.toArray(); } static Array get_trait_alias_info(const Class* cls) { auto const& aliases = cls->traitAliases(); if (aliases.size()) { DictInit ai(aliases.size()); for (auto const& namePair : aliases) { ai.set(StrNR(namePair.first), VarNR(namePair.second).tv()); } return ai.toArray(); } else { // Even if we have alias rules, if we're in repo mode, they will be applied // during the trait flattening step, and we won't populate traitAliases() // on the Class. auto const& rules = cls->preClass()->traitAliasRules(); DictInit ai(rules.size()); for (auto const& rule : rules) { auto namePair = rule.asNamePair(); ai.set(StrNR(namePair.first), VarNR(namePair.second).tv()); } return ai.toArray(); } } static Array HHVM_METHOD(ReflectionClass, getTraitAliases) { return get_trait_alias_info(ReflectionClassHandle::GetClassFor(this_)); } static bool HHVM_METHOD(ReflectionClass, hasMethod, const String& name) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return (get_method_func(cls, name) != nullptr); } namespace { const Class* get_class_from_name(const String& name) { auto const cls = Class::load(name.get()); if (!cls) { auto message = folly::sformat( "class {} could not be loaded", name.toCppString() ); Reflection::ThrowReflectionExceptionObject(message); } return cls; } } // helper for getMethods: returns a keyset static Array HHVM_STATIC_METHOD( ReflectionClass, getMethodOrder, const String& clsname, int64_t filter) { auto const cls = get_class_from_name(clsname); Attr mask = attrs_from_modifiers(filter, false); // At each step, we fetch from the PreClass is important because the // order in which getMethods returns matters req::StringIFastSet visitedMethods; req::StringIFastSet visitedInterfaces; auto st = Array::CreateKeyset(); auto add = [&] (const Func* m) { if (m->isGenerated()) return; if (!visitedMethods.insert(m->nameStr()).second) return; if (m->attrs() & mask) { st.append(m->nameStr().asString()); } }; std::function<void(const Class*)> collect; std::function<void(const Class*)> collectInterface; collect = [&] (const Class* clas) { if (!clas) return; auto const methods = clas->preClass()->methods(); auto const numMethods = clas->preClass()->numMethods(); auto numDeclMethods = clas->preClass()->numDeclMethods(); if (numDeclMethods == -1) numDeclMethods = numMethods; // Add declared methods. for (Slot i = 0; i < numDeclMethods; ++i) { add(methods[i]); } // Recurse; we need to order the parent's methods before our trait methods. collect(clas->parent()); for (Slot i = numDeclMethods; i < numMethods; ++i) { // For repo mode, where trait methods are flattened at compile-time. add(methods[i]); } for (Slot i = clas->traitsBeginIdx(); i < clas->traitsEndIdx(); ++i) { // For non-repo mode, where they are added at Class-creation time. add(clas->getMethod(i)); } }; collectInterface = [&] (const Class* iface) { if (!iface) return; if (!visitedInterfaces.insert(iface->nameStr()).second) return; size_t const numMethods = iface->preClass()->numMethods(); Func* const* methods = iface->preClass()->methods(); for (Slot i = 0; i < numMethods; ++i) { add(methods[i]); } for (auto const& parentIface: iface->declInterfaces()) { collectInterface(parentIface.get()); } auto const& allIfaces = iface->allInterfaces(); if (allIfaces.size() > iface->declInterfaces().size()) { for (int i = 0; i < allIfaces.size(); ++i) { collectInterface(allIfaces[i].get()); } } }; collect(const_cast<Class*>(cls)); // concrete classes should already have all of their methods present if (((AttrPublic | AttrAbstract | AttrStatic) & mask) && cls->attrs() & (AttrInterface | AttrAbstract | AttrTrait)) { for (auto const& interface: cls->declInterfaces()) { collectInterface(interface.get()); } auto const& allIfaces = cls->allInterfaces(); if (allIfaces.size() > cls->declInterfaces().size()) { for (int i = 0; i < allIfaces.size(); ++i) { auto const& interface = allIfaces[i]; collectInterface(interface.get()); } } } return st; } static bool HHVM_METHOD(ReflectionClass, hasConstant, const String& name) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return cls->hasConstant(name.get()); } static TypedValue HHVM_METHOD(ReflectionClass, getConstant, const String& name) { auto const cls = ReflectionClassHandle::GetClassFor(this_); auto value = cls->clsCnsGet(name.get()); if (value.m_type == KindOfUninit) return make_tv<KindOfBoolean>(false); tvIncRefGen(value); return value; } static void addClassConstantNames(const Class* cls, const req::ptr<c_Set>& st, size_t limit) { assertx(cls && st && (st->size() < limit)); auto numConsts = cls->numConstants(); const Class::Const* consts = cls->constants(); for (size_t i = 0; i < numConsts; i++) { if (consts[i].cls == cls && !consts[i].isAbstractAndUninit() && consts[i].kind() == ConstModifiers::Kind::Value) { st->add(const_cast<StringData*>(consts[i].name.get())); } } auto const& allTraits = cls->usedTraitClasses(); auto const numTraits = allTraits.size(); for (int i = 0; i < numTraits && (st->size() < limit); ++i) { addClassConstantNames(allTraits[i].get(), st, limit); } if ((st->size() < limit) && cls->parent()) { addClassConstantNames(cls->parent(), st, limit); } auto const& allIfaces = cls->allInterfaces(); auto const numIfaces = allIfaces.size(); for (int i = 0; i < numIfaces && (st->size() < limit); ++i) { addClassConstantNames(allIfaces[i].get(), st, limit); } } // helper for getConstants static Array HHVM_STATIC_METHOD( ReflectionClass, getOrderedConstants, const String& clsname) { auto const cls = get_class_from_name(clsname); size_t numConsts = cls->numConstants(); if (!numConsts) { return empty_dict_array(); } auto st = req::make<c_Set>(); st->reserve(numConsts); addClassConstantNames(cls, st, numConsts); assertx(st->size() <= numConsts); DictInit ai(numConsts); IterateV(st->arrayData(), [&](TypedValue k) { auto constName = val(k).pstr; auto value = cls->clsCnsGet(constName); assertx(type(value) != KindOfUninit); ai.set(constName, value); }); return ai.toArray(); } namespace { // helper for getOrdered*Constants template <typename Fn> static Array orderedConstantsHelper(const Class* cls, Fn filterFn) { auto const numConsts = cls->numConstants(); if (!numConsts) { return empty_dict_array(); } auto st = KeysetInit{numConsts}; auto const consts = cls->constants(); for (size_t i = 0; i < numConsts; i++) { auto const& konst = consts[i]; if (filterFn(konst)) { st.add(make_tv<KindOfPersistentString>(konst.name.get())); } } auto ret = st.create()->toDict(false /*copy*/); assertx(ret->size() <= numConsts); return Array::attach(std::move(ret)); } } static Array HHVM_STATIC_METHOD( ReflectionClass, getOrderedAbstractConstants, const String& clsname ) { return orderedConstantsHelper( get_class_from_name(clsname), [](Class::Const c) -> bool { return c.isAbstractAndUninit() && c.kind() == ConstModifiers::Kind::Value; } ); } static Array HHVM_STATIC_METHOD( ReflectionClass, getOrderedTypeConstants, const String& clsname ) { return orderedConstantsHelper( get_class_from_name(clsname), [](Class::Const c) -> bool { return c.kind() == ConstModifiers::Kind::Type; } ); } static Array HHVM_METHOD(ReflectionClass, getAttributesNamespaced) { auto const cls = ReflectionClassHandle::GetClassFor(this_); // UserAttributes are stored exclusively on the PreClass. auto const pcls = cls->preClass(); auto userAttrs = pcls->userAttributes(); DictInit ai(userAttrs.size()); for (auto const& attr : userAttrs) { ai.set(StrNR(attr.first), attr.second); } return ai.toArray(); } static Array HHVM_METHOD(ReflectionClass, getAttributesRecursiveNamespaced) { auto const cls = ReflectionClassHandle::GetClassFor(this_); Array ret = Array::CreateDict(); // no reasonable idea about sizing // UserAttributes are stored in the PreClass, so we must walk the parent // chain to get all of them; attribute specifications from child classes // win over parents. // const pointer to Class => pointer to a (const) Class Class* currentCls = const_cast<Class*>(cls); do { auto const pcls = currentCls->preClass(); for (auto it = pcls->userAttributes().begin(); it != pcls->userAttributes().end(); ++it) { if (!ret.exists(StrNR(it->first))) { ret.set(StrNR(it->first), it->second); } } } while ((currentCls = currentCls->parent())); return ret; } static Array HHVM_METHOD(ReflectionClass, getReifiedTypeParamInfo) { auto const cls = ReflectionClassHandle::GetClassFor(this_); return reified_generics_info_to_array(cls->getReifiedGenericsInfo()); } static Array HHVM_STATIC_METHOD( ReflectionClass, getClassPropertyInfo, const String& clsname) { /* * FIXME: This implementation is pretty horrible and should be rewritten * when ReflectionProperty is ported. */ auto const cls = get_class_from_name(clsname); auto const properties = cls->declProperties(); cls->initialize(); auto const& propInitVec = cls->getPropData() ? *cls->getPropData() : cls->declPropInit(); auto ret = Array::CreateDict(); for (auto const& declProp : properties) { auto slot = declProp.serializationIdx; auto index = cls->propSlotToIndex(slot); auto const& prop = properties[slot]; auto const default_val = propInitVec[index].val.tv(); if (((prop.attrs & AttrPrivate) == AttrPrivate) && (prop.cls != cls)) { continue; } auto info = Array::CreateDict(); set_instance_prop_info(info, &prop, default_val); ret.set(StrNR(prop.name), VarNR(info).tv()); } // static properties auto const sProperties = cls->staticProperties(); for (auto const& sProp : sProperties) { auto slot = sProp.serializationIdx; auto const& prop = sProperties[slot]; if (((prop.attrs & AttrPrivate) == AttrPrivate) && (prop.cls != cls)) { continue; } auto info = Array::CreateDict(); set_static_prop_info(info, &prop); ret.set(StrNR(prop.name), VarNR(info).tv()); } return ret; } static Array HHVM_METHOD(ReflectionClass, getDynamicPropertyInfos, const Object& obj) { auto const cls = ReflectionClassHandle::GetClassFor(this_); auto obj_data = obj.get(); assertx(obj_data->getVMClass() == cls); if (!obj_data->hasDynProps()) { return empty_dict_array(); } auto const dynPropArray = obj_data->dynPropArray(); DictInit ret{dynPropArray->size()}; IterateKV(dynPropArray.get(), [&](TypedValue k, TypedValue) { if (RuntimeOption::EvalNoticeOnReadDynamicProp) { auto const key = tvCastToString(k); obj_data->raiseReadDynamicProp(key.get()); } auto info = Array::CreateDict(); set_dyn_prop_info(info, k, cls->name()); ret.setValidKey(k, VarNR(info).tv()); }); return ret.toArray(); } static String HHVM_METHOD(ReflectionClass, getConstructorName) { auto const cls = ReflectionClassHandle::GetClassFor(this_); auto ctor = cls->getDeclaredCtor(); if (!ctor) { return String(); } auto ret = const_cast<StringData*>(ctor->name()); return String(ret); } void ReflectionClassHandle::wakeup(const Variant& content, ObjectData* obj) { if (!content.isString()) { throw Exception("Native data of ReflectionClass should be a class name"); } String clsName = content.toString(); String result = init(clsName); if (result.empty()) { auto msg = folly::format("Class {} does not exist", clsName).str(); Reflection::ThrowReflectionExceptionObject(String(msg)); } // It is possible that $name does not get serialized. If a class derives // from ReflectionClass and the return value of its __sleep() function does // not contain 'name', $name gets ignored. So, we restore $name here. obj->setProp(nullptr, s_name.get(), result.asTypedValue()); } static Variant reflection_extension_name_get(const Object& this_) { assertx(Reflection::s_ReflectionExtensionClass); auto const name = this_->getProp( Reflection::s_ReflectionExtensionClass, s___name.get() ); return tvCastToString(name.tv()); } static Native::PropAccessor reflection_extension_Accessors[] = { {"name", reflection_extension_name_get, nullptr, nullptr, nullptr}, // name is read only {nullptr, nullptr, nullptr, nullptr, nullptr} }; static Native::PropAccessorMap reflection_extension_accessorsMap ((Native::PropAccessor*)reflection_extension_Accessors); struct reflection_extension_PropHandler : Native::MapPropHandler<reflection_extension_PropHandler> { static constexpr Native::PropAccessorMap& map = reflection_extension_accessorsMap; }; ///////////////////////////////////////////////////////////////////////////// // class ReflectionTypeConstant const StaticString s_ReflectionConstHandle("ReflectionConstHandle"); // helper for __construct static bool HHVM_METHOD(ReflectionTypeConstant, __init, const Variant& cls_or_obj, const String& const_name) { auto const cls = get_cls(cls_or_obj); if (!cls || const_name.isNull()) { // caller raises exception return false; } size_t numConsts = cls->numConstants(); const Class::Const* consts = cls->constants(); for (size_t i = 0; i < numConsts; i++) { if (const_name.same(consts[i].name) && consts[i].kind() == ConstModifiers::Kind::Type) { auto handle = ReflectionConstHandle::Get(this_); handle->setConst(&consts[i]); handle->setClass(cls); return true; } } // caller raises exception return false; } static String HHVM_METHOD(ReflectionTypeConstant, getName) { auto const cns = ReflectionConstHandle::GetConstFor(this_); auto ret = const_cast<StringData*>(cns->name.get()); return String(ret); } static bool HHVM_METHOD(ReflectionTypeConstant, isAbstract) { auto const cns = ReflectionConstHandle::GetConstFor(this_); if (RO::EvalTypeconstAbstractDefaultReflectionIsAbstract) { return cns->isAbstract(); } else { return cns->isAbstractAndUninit(); } } // helper for getAssignedTypeText static String HHVM_METHOD(ReflectionTypeConstant, getAssignedTypeHint) { auto const cns = ReflectionConstHandle::GetConstFor(this_); if (isStringType(cns->val.m_type)) { return String(cns->val.m_data.pstr); } if (isArrayLikeType(cns->val.m_type)) { auto const cls = cns->cls; // go to the preclass to find the unresolved TypeStructure to get // the original assigned type text auto const preCls = cls->preClass(); auto typeCns = preCls->lookupConstant(cns->name); assertx(typeCns->kind() == ConstModifiers::Kind::Type); assertx(!typeCns->isAbstractAndUninit()); assertx(isArrayLikeType(typeCns->val().m_type)); return TypeStructure::toString(Array::attach(typeCns->val().m_data.parr), TypeStructure::TSDisplayType::TSDisplayTypeReflection); } return String(); } // private helper for getDeclaringClass static String HHVM_METHOD(ReflectionTypeConstant, getDeclaringClassname) { auto const cns = ReflectionConstHandle::GetConstFor(this_); auto cls = cns->cls; auto ret = const_cast<StringData*>(cls->name()); return String(ret); } // private helper for getClass static String HHVM_METHOD(ReflectionTypeConstant, getClassname) { auto const cls = ReflectionConstHandle::GetClassFor(this_); auto ret = const_cast<StringData*>(cls->name()); return String(ret); } ///////////////////////////////////////////////////////////////////////////// // class ReflectionProperty const StaticString s_ReflectionPropHandle("ReflectionPropHandle"); static void HHVM_METHOD(ReflectionProperty, __construct, const Variant& cls_or_obj, const String& prop_name) { auto const cls = get_cls(cls_or_obj); if (!cls) { Reflection::ThrowReflectionExceptionObject(folly::sformat( "Class {} does not exist", cls_or_obj.toString().toCppString() )); } if (prop_name.isNull()) { Reflection::ThrowReflectionExceptionObject(folly::sformat( "Property {}:: does not exist", cls->name()->toCppString() )); } auto data = Native::data<ReflectionPropHandle>(this_); // is there a declared instance property? auto lookup = cls->getDeclPropSlot(cls, prop_name.get()); auto propIdx = lookup.slot; if (propIdx != kInvalidSlot) { auto const prop = &cls->declProperties()[propIdx]; data->setInstanceProp(prop); this_->setProp(nullptr, s_class.get(), make_tv<KindOfPersistentString>(prop->cls->name())); this_->setProp(nullptr, s_name.get(), make_tv<KindOfPersistentString>(prop->name)); return; } // is there a declared static property? lookup = cls->findSProp(cls, prop_name.get()); propIdx = lookup.slot; if (propIdx != kInvalidSlot) { auto const prop = &cls->staticProperties()[propIdx]; data->setStaticProp(prop); this_->setProp(nullptr, s_class.get(), make_tv<KindOfPersistentString>(prop->cls->name())); this_->setProp(nullptr, s_name.get(), make_tv<KindOfPersistentString>(prop->name)); return; } // is there a dynamic property? if (cls_or_obj.is(KindOfObject)) { auto obj = cls_or_obj.asCObjRef().get(); assertx(cls == obj->getVMClass()); if (obj->getAttribute(ObjectData::HasDynPropArr) && obj->dynPropArray().exists( obj->dynPropArray().convertKey<IntishCast::Cast>(prop_name)) ){ if (RuntimeOption::EvalNoticeOnReadDynamicProp) { obj->raiseReadDynamicProp(prop_name.get()); } data->setDynamicProp(); this_->setProp(nullptr, s_class.get(), make_tv<KindOfPersistentString>(cls->name())); this_->setProp(nullptr, s_name.get(), prop_name.asTypedValue()); return; } } Reflection::ThrowReflectionExceptionObject(folly::sformat( "Property {}::{} does not exist", cls->name()->toCppString(), prop_name.toCppString() )); } namespace { [[noreturn]] void reflection_property_internal_error() { raise_fatal_error("Internal error: Failed to retrieve the reflection object"); } } static bool HHVM_METHOD(ReflectionProperty, isPublic) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: return data->getProp()->attrs & AttrPublic; case ReflectionPropHandle::Type::Static: return data->getSProp()->attrs & AttrPublic; case ReflectionPropHandle::Type::Dynamic: return true; default: reflection_property_internal_error(); } } static bool HHVM_METHOD(ReflectionProperty, isProtected) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: return data->getProp()->attrs & AttrProtected; case ReflectionPropHandle::Type::Static: return data->getSProp()->attrs & AttrProtected; case ReflectionPropHandle::Type::Dynamic: return false; default: reflection_property_internal_error(); } } static bool HHVM_METHOD(ReflectionProperty, isPrivate) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: return data->getProp()->attrs & AttrPrivate; case ReflectionPropHandle::Type::Static: return data->getSProp()->attrs & AttrPrivate; case ReflectionPropHandle::Type::Dynamic: return false; default: reflection_property_internal_error(); } } static bool HHVM_METHOD(ReflectionProperty, isStatic) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Static: return true; case ReflectionPropHandle::Type::Instance: case ReflectionPropHandle::Type::Dynamic: return false; default: reflection_property_internal_error(); } } static bool HHVM_METHOD(ReflectionProperty, isDefault) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: case ReflectionPropHandle::Type::Static: return true; case ReflectionPropHandle::Type::Dynamic: return false; default: reflection_property_internal_error(); } } static int HHVM_METHOD(ReflectionProperty, getModifiers) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: return get_modifiers(data->getProp()->attrs, false, true); case ReflectionPropHandle::Type::Static: return get_modifiers(data->getSProp()->attrs, false, true); case ReflectionPropHandle::Type::Dynamic: return get_modifiers(AttrPublic, false, true); default: reflection_property_internal_error(); } } static TypedValue HHVM_METHOD(ReflectionProperty, getDocComment) { auto const data = Native::data<ReflectionPropHandle>(this_); const StringData *comment = nullptr; switch (data->getType()) { case ReflectionPropHandle::Type::Instance: comment = data->getProp()->preProp->docComment(); break; case ReflectionPropHandle::Type::Static: comment = data->getSProp()->preProp->docComment(); break; case ReflectionPropHandle::Type::Dynamic: break; default: reflection_property_internal_error(); } if (comment == nullptr || comment->empty()) { return tvReturn(false); } else { Variant vComment{comment, Variant::PersistentStrInit{}}; return tvReturn(std::move(vComment)); } } static String HHVM_METHOD(ReflectionProperty, getTypeText) { auto const data = Native::data<ReflectionPropHandle>(this_); const StringData *type = nullptr; switch (data->getType()) { case ReflectionPropHandle::Type::Instance: type = data->getProp()->preProp->userType(); break; case ReflectionPropHandle::Type::Static: type = data->getSProp()->preProp->userType(); break; case ReflectionPropHandle::Type::Dynamic: break; default: reflection_property_internal_error(); } if (type == nullptr || type->empty()) { return empty_string(); } else { return StrNR(type); } } static TypedValue HHVM_METHOD(ReflectionProperty, getDefaultValue) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: { auto const prop = data->getProp(); // We can't get propIdx from prop->idx (that's not what that is) or by // doing prop - prop->cls->declProperties().begin() (the prop can be in // the prop vector of a child class but it will always point to the class // it was declared in); so if we don't want to store propIdx we have to // look it up by name. auto cls = prop->cls; auto lookup = cls->getDeclPropSlot(cls, prop->name); auto propSlot = lookup.slot; assertx(propSlot != kInvalidSlot); auto propIndex = cls->propSlotToIndex(propSlot); cls->initialize(); auto const& propInitVec = cls->getPropData() ? *cls->getPropData() : cls->declPropInit(); auto val = VarNR{propInitVec[propIndex].val.tv()}; return tvReturn(val); } case ReflectionPropHandle::Type::Static: { auto const prop = data->getSProp(); prop->cls->initialize(); return tvReturn(tvAsCVarRef(&prop->val)); } case ReflectionPropHandle::Type::Dynamic: return make_tv<KindOfNull>(); default: reflection_property_internal_error(); } } static Array HHVM_METHOD(ReflectionProperty, getAttributesNamespaced) { auto const data = Native::data<ReflectionPropHandle>(this_); auto attrs = Array::CreateDict(); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: { auto const prop = data->getProp()->preProp; for (auto attr : prop->userAttributes()) { attrs.set(StrNR(attr.first), attr.second); } return attrs; } case ReflectionPropHandle::Type::Static: { auto const prop = data->getSProp()->preProp; for (auto attr : prop->userAttributes()) { attrs.set(StrNR(attr.first), attr.second); } return attrs; } case ReflectionPropHandle::Type::Dynamic: return attrs; default: reflection_property_internal_error(); } } static bool HHVM_METHOD(ReflectionProperty, isReadonly) { auto const data = Native::data<ReflectionPropHandle>(this_); switch (data->getType()) { case ReflectionPropHandle::Type::Instance: return data->getProp()->attrs & AttrIsReadonly; case ReflectionPropHandle::Type::Static: return data->getSProp()->attrs & AttrIsReadonly; case ReflectionPropHandle::Type::Dynamic: return false; default: reflection_property_internal_error(); } } ///////////////////////////////////////////////////////////////////////////// // class ReflectionTypeAlias const StaticString s_ReflectionTypeAliasHandle("ReflectionTypeAliasHandle"); // helper for __construct: // caller throws exception when return value is false static String HHVM_METHOD(ReflectionTypeAlias, __init, const String& name) { auto const typeAlias = TypeAlias::load(name.get()); if (!typeAlias) { return empty_string(); } ReflectionTypeAliasHandle::Get(this_)->setTypeAlias(typeAlias); return String::attach(const_cast<StringData*>(typeAlias->name())); } static Array HHVM_METHOD(ReflectionTypeAlias, getTypeStructure) { auto const req = ReflectionTypeAliasHandle::GetTypeAliasFor(this_); assertx(req); auto const typeStructure = req->typeStructure(); assertx(!typeStructure.empty()); assertx(typeStructure.isDict()); return typeStructure; } static String HHVM_METHOD(ReflectionTypeAlias, getAssignedTypeText) { auto const req = ReflectionTypeAliasHandle::GetTypeAliasFor(this_); assertx(req); auto const typeStructure = req->typeStructure(); assertx(!typeStructure.empty()); assertx(typeStructure.isDict()); return TypeStructure::toString(typeStructure, TypeStructure::TSDisplayType::TSDisplayTypeReflection); } static Array HHVM_METHOD(ReflectionTypeAlias, getAttributesNamespaced) { auto const req = ReflectionTypeAliasHandle::GetTypeAliasFor(this_); assertx(req); auto const userAttrs = req->userAttrs(); DictInit ai(userAttrs.size()); for (auto& attr : userAttrs) { ai.set(StrNR(attr.first), tvAsCVarRef(&attr.second)); } return ai.toArray(); } static String HHVM_METHOD(ReflectionTypeAlias, getFileName) { auto const req = ReflectionTypeAliasHandle::GetTypeAliasFor(this_); assertx(req); auto file = req->unit()->filepath(); if (!file) file = staticEmptyString(); if (file->data()[0] != '/') { return SourceRootInfo::RelativeToPhpRoot(StrNR(file)); } return String::attach(const_cast<StringData*>(file)); } /////////////////////////////////////////////////////////////////////////////// struct ReflectionExtension final : Extension { ReflectionExtension() : Extension("reflection", "$Id$") { } void moduleInit() override { HHVM_FE(hphp_create_object); HHVM_FE(hphp_create_object_without_constructor); HHVM_FE(hphp_get_extension_info); HHVM_FE(hphp_get_property); HHVM_FE(hphp_get_static_property); HHVM_FE(hphp_invoke); HHVM_FE(hphp_invoke_method); HHVM_FE(hphp_set_property); HHVM_FE(hphp_set_static_property); HHVM_FALIAS(HH\\type_structure, type_structure); HHVM_FALIAS(HH\\type_structure_no_throw, type_structure_no_throw); HHVM_FALIAS(HH\\type_structure_classname, type_structure_classname); HHVM_ME(ReflectionFunctionAbstract, getName); HHVM_ME(ReflectionFunctionAbstract, isHack); HHVM_ME(ReflectionFunctionAbstract, isInternal); HHVM_ME(ReflectionFunctionAbstract, isGenerator); HHVM_ME(ReflectionFunctionAbstract, isAsync); HHVM_ME(ReflectionFunctionAbstract, isVariadic); HHVM_ME(ReflectionFunctionAbstract, getFileName); HHVM_ME(ReflectionFunctionAbstract, getStartLine); HHVM_ME(ReflectionFunctionAbstract, getEndLine); HHVM_ME(ReflectionFunctionAbstract, getDocComment); HHVM_ME(ReflectionFunctionAbstract, getReturnTypeHint); HHVM_ME(ReflectionFunctionAbstract, getNumberOfParameters); HHVM_ME(ReflectionFunctionAbstract, getParamInfo); HHVM_ME(ReflectionFunctionAbstract, getAttributesNamespaced); HHVM_ME(ReflectionFunctionAbstract, getRetTypeInfo); HHVM_ME(ReflectionFunctionAbstract, getReifiedTypeParamInfo); HHVM_ME(ReflectionFunctionAbstract, getCoeffects); HHVM_ME(ReflectionFunctionAbstract, getModule); HHVM_ME(ReflectionFunctionAbstract, returnsReadonly); HHVM_ME(ReflectionMethod, __init); HHVM_ME(ReflectionMethod, isFinal); HHVM_ME(ReflectionMethod, isAbstract); HHVM_ME(ReflectionMethod, isPublic); HHVM_ME(ReflectionMethod, isProtected); HHVM_ME(ReflectionMethod, isPrivate); HHVM_ME(ReflectionMethod, isStatic); HHVM_ME(ReflectionMethod, isStaticInPrologue); HHVM_ME(ReflectionMethod, isConstructor); HHVM_ME(ReflectionMethod, isReadonly); HHVM_ME(ReflectionMethod, getModifiers); HHVM_ME(ReflectionMethod, getPrototypeClassname); HHVM_ME(ReflectionMethod, getDeclaringClassname); HHVM_ME(ReflectionFile, __init); HHVM_ME(ReflectionFile, getAttributesNamespaced); HHVM_ME(ReflectionFunction, __initName); HHVM_ME(ReflectionFunction, __initClosure); HHVM_ME(ReflectionFunction, getClosureScopeClassname); HHVM_ME(ReflectionFunction, getClosureThisObject); HHVM_ME(ReflectionTypeConstant, __init); HHVM_ME(ReflectionTypeConstant, getName); HHVM_ME(ReflectionTypeConstant, isAbstract); HHVM_ME(ReflectionTypeConstant, getAssignedTypeHint); HHVM_ME(ReflectionTypeConstant, getDeclaringClassname); HHVM_ME(ReflectionTypeConstant, getClassname); HHVM_ME(ReflectionProperty, __construct); HHVM_ME(ReflectionProperty, isPublic); HHVM_ME(ReflectionProperty, isProtected); HHVM_ME(ReflectionProperty, isPrivate); HHVM_ME(ReflectionProperty, isStatic); HHVM_ME(ReflectionProperty, isDefault); HHVM_ME(ReflectionProperty, isReadonly); HHVM_ME(ReflectionProperty, getModifiers); HHVM_ME(ReflectionProperty, getDocComment); HHVM_ME(ReflectionProperty, getTypeText); HHVM_ME(ReflectionProperty, getDefaultValue); HHVM_ME(ReflectionProperty, getAttributesNamespaced); HHVM_ME(ReflectionTypeAlias, __init); HHVM_ME(ReflectionTypeAlias, getTypeStructure); HHVM_ME(ReflectionTypeAlias, getAttributesNamespaced); HHVM_ME(ReflectionTypeAlias, getAssignedTypeText); HHVM_ME(ReflectionTypeAlias, getFileName); HHVM_ME(ReflectionClass, __init); HHVM_ME(ReflectionClass, getName); HHVM_ME(ReflectionClass, getParentName); HHVM_ME(ReflectionClass, isHack); HHVM_ME(ReflectionClass, isInternal); HHVM_ME(ReflectionClass, isInstantiable); HHVM_ME(ReflectionClass, isInterface); HHVM_ME(ReflectionClass, isTrait); HHVM_ME(ReflectionClass, isEnum); HHVM_ME(ReflectionClass, getEnumUnderlyingType); HHVM_ME(ReflectionClass, isAbstract); HHVM_ME(ReflectionClass, isFinal); HHVM_ME(ReflectionClass, getModifiers); HHVM_ME(ReflectionClass, getFileName); HHVM_ME(ReflectionClass, getStartLine); HHVM_ME(ReflectionClass, getEndLine); HHVM_ME(ReflectionClass, getDocComment); HHVM_ME(ReflectionClass, getInterfaceNames); HHVM_ME(ReflectionClass, getRequirementNames); HHVM_ME(ReflectionClass, getTraitNames); HHVM_ME(ReflectionClass, getTraitAliases); HHVM_ME(ReflectionClass, hasMethod); HHVM_STATIC_ME(ReflectionClass, getMethodOrder); HHVM_ME(ReflectionClass, hasConstant); HHVM_ME(ReflectionClass, getConstant); HHVM_STATIC_ME(ReflectionClass, getOrderedConstants); HHVM_STATIC_ME(ReflectionClass, getOrderedAbstractConstants); HHVM_STATIC_ME(ReflectionClass, getOrderedTypeConstants); HHVM_ME(ReflectionClass, getAttributesNamespaced); HHVM_ME(ReflectionClass, getAttributesRecursiveNamespaced); HHVM_ME(ReflectionClass, getReifiedTypeParamInfo); HHVM_STATIC_ME(ReflectionClass, getClassPropertyInfo); HHVM_ME(ReflectionClass, getDynamicPropertyInfos); HHVM_ME(ReflectionClass, getConstructorName); Native::registerNativeDataInfo<ReflectionFuncHandle>( s_ReflectionFuncHandle.get()); Native::registerNativeDataInfo<ReflectionClassHandle>( s_ReflectionClassHandle.get()); Native::registerNativeDataInfo<ReflectionConstHandle>( s_ReflectionConstHandle.get()); Native::registerNativeDataInfo<ReflectionPropHandle>( s_ReflectionPropHandle.get()); Native::registerNativeDataInfo<ReflectionFileHandle>( s_ReflectionFileHandle.get()); Native::registerNativeDataInfo<ReflectionTypeAliasHandle>( s_ReflectionTypeAliasHandle.get(), Native::NO_SWEEP); Native::registerNativePropHandler <reflection_extension_PropHandler>(s_reflectionextension); loadSystemlib(); loadSystemlib("reflection-classes"); loadSystemlib("reflection-internals-functions"); loadSystemlib("reflection_hni"); Reflection::s_ReflectionExceptionClass = Class::lookup(s_reflectionexception.get()); assertx(Reflection::s_ReflectionExceptionClass); Reflection::s_ReflectionExtensionClass = Class::lookup(s_reflectionextension.get()); assertx(Reflection::s_ReflectionExtensionClass); } } s_reflection_extension; /////////////////////////////////////////////////////////////////////////////// namespace DebuggerReflection { namespace { void set_debugger_source_info(Array &ret, const StringData* file, int line1, int line2) { if (!file) file = staticEmptyString(); if (file->data()[0] != '/') { ret.set(s_file, SourceRootInfo::RelativeToPhpRoot(StrNR(file))); } else { assertx(!file->isRefCounted()); ret.set(s_file, make_tv<KindOfPersistentString>(file)); } ret.set(s_line1, make_tv<KindOfInt64>(line1)); ret.set(s_line2, make_tv<KindOfInt64>(line2)); } } static void set_debugger_return_type_constraint(Array &ret, const StringData* retType) { assertx(ret.isDict()); if (retType && retType->size()) { assertx(!retType->isRefCounted()); ret.set(s_return_type, make_tv<KindOfPersistentString>(retType)); } else { ret.set(s_return_type, make_tv<KindOfBoolean>(false)); } } static void set_debugger_reflection_method_prototype_info(Array& ret, const Func *func) { assertx(ret.isDict()); const Class *prototypeCls = nullptr; if (func->baseCls() != nullptr && func->baseCls() != func->implCls()) { prototypeCls = func->baseCls(); const Class *result = get_prototype_class_from_interfaces( prototypeCls, func); if (result) prototypeCls = result; } else if (func->isMethod()) { // lookup the prototype in the interfaces prototypeCls = get_prototype_class_from_interfaces(func->implCls(), func); } if (prototypeCls) { Array prototype = Array::CreateDict(); prototype.set(s_class, make_tv<KindOfPersistentString>(prototypeCls->name())); prototype.set(s_name, make_tv<KindOfPersistentString>(func->name())); ret.set(s_prototype, prototype); } } static void set_debugger_reflection_function_info(Array& ret, const Func* func) { assertx(ret.isDict()); // return type if (func->isBuiltin()) { ret.set(s_internal, make_tv<KindOfBoolean>(true)); } set_debugger_return_type_constraint(ret, func->returnUserType()); // doc comments set_doc_comment(ret, func->docComment(), func->isBuiltin()); // parameters ret.set(s_params, get_function_param_info(func)); // user attributes ret.set(s_attributes, get_function_user_attributes(func)); ret.set(s_is_async, func->isAsync()); ret.set(s_is_closure, func->isClosureBody()); ret.set(s_is_generator, func->isGenerator()); } static void set_debugger_reflection_method_info(Array& ret, const Func* func, const Class* cls) { assertx(ret.isDict()); ret.set(s_name, make_tv<KindOfPersistentString>(func->name())); set_attrs(ret, get_modifiers(func->attrs(), false, false)); if (isConstructor(func)) { ret.set(s_constructor, make_tv<KindOfBoolean>(true)); } // If Func* is from a PreClass, it doesn't know about base classes etc. // Swap it out for the full version if possible. auto resolved_func = func->implCls() ? func : cls->lookupMethod(func->name()); if (!resolved_func) { resolved_func = func; } ret.set(s_class, make_tv<KindOfPersistentString>(resolved_func->implCls()->name())); set_debugger_reflection_function_info(ret, resolved_func); set_debugger_source_info(ret, func->unit()->filepath(), func->line1(), func->line2()); set_debugger_reflection_method_prototype_info(ret, resolved_func); } Array get_function_info(const String& name) { if (name.get() == nullptr) return null_array; const Func* func = Func::load(name.get()); if (!func) return null_array; auto ret = Array::CreateDict(); ret.set(s_name, make_tv<KindOfPersistentString>(func->name())); // setting parameters and static variables set_debugger_reflection_function_info(ret, func); set_debugger_source_info(ret, func->unit()->filepath(), func->line1(), func->line2()); return ret; } Array get_class_info(const String& name) { auto cls = get_cls(name); if (!cls) return null_array; auto ret = Array::CreateDict(); ret.set(s_name, make_tv<KindOfPersistentString>(cls->name())); ret.set(s_extension, empty_string_tv()); ret.set(s_parent, make_tv<KindOfPersistentString>(cls->preClass()->parent())); // interfaces { auto const& allIfaces = cls->allInterfaces(); DictInit arr(allIfaces.size()); for (auto const& interface: cls->declInterfaces()) { arr.set(interface->nameStr(), make_tv<KindOfInt64>(1)); } if (allIfaces.size() > cls->declInterfaces().size()) { for (int i = 0; i < allIfaces.size(); ++i) { auto const& interface = allIfaces[i]; arr.set(interface->nameStr(), make_tv<KindOfInt64>(1)); } } ret.set(s_interfaces, make_array_like_tv(arr.create())); } // traits { auto const& traits = cls->preClass()->usedTraits(); DictInit arr(traits.size()); for (auto const& traitName : traits) { arr.set(StrNR(traitName), make_tv<KindOfInt64>(1)); } ret.set(s_traits, make_array_like_tv(arr.create())); } // trait aliases { ret.set(s_trait_aliases, VarNR(get_trait_alias_info(cls)).tv()); } // attributes { if (cls->attrs() & AttrBuiltin) { ret.set(s_internal, make_tv<KindOfBoolean>(true)); } if (cls->attrs() & AttrFinal) { ret.set(s_final, make_tv<KindOfBoolean>(true)); } if (cls->attrs() & AttrAbstract) { ret.set(s_abstract, make_tv<KindOfBoolean>(true)); } if (cls->attrs() & AttrInterface) { ret.set(s_interface, make_tv<KindOfBoolean>(true)); } if (cls->attrs() & AttrTrait) { ret.set(s_trait, make_tv<KindOfBoolean>(true)); } ret.set(s_modifiers, make_tv<KindOfInt64>( get_modifiers(cls->attrs(), true, false)) ); if (cls->getCtor()->attrs() & AttrPublic && !(cls->attrs() & AttrAbstract) && !(cls->attrs() & AttrInterface) && !(cls->attrs() & AttrTrait)) { ret.set(s_instantiable, make_tv<KindOfBoolean>(true)); } } // methods { auto arr = Array::CreateDict(); // Fetch from PreClass as: // - the order is important // - we want type profiling info // and neither of these are in the Class... Func* const* methods = cls->preClass()->methods(); size_t const numMethods = cls->preClass()->numMethods(); for (Slot i = 0; i < numMethods; ++i) { const Func* m = methods[i]; if (m->isGenerated()) continue; auto lowerName = HHVM_FN(strtolower)(m->nameStr()); auto info = Array::CreateDict(); set_debugger_reflection_method_info(info, m, cls); arr.set(lowerName, VarNR(info).tv()); } for (Slot i = cls->traitsBeginIdx(); i < cls->traitsEndIdx(); ++i) { const Func* m = cls->getMethod(i); if (m->isGenerated()) continue; auto lowerName = HHVM_FN(strtolower)(m->nameStr()); auto info = Array::CreateDict(); set_debugger_reflection_method_info(info, m, cls); arr.set(lowerName, VarNR(info).tv()); } ret.set(s_methods, VarNR(arr).tv()); } // properties { auto arr = Array::CreateDict(); auto arrPriv = Array::CreateDict(); auto arrIdx = Array::CreateDict(); auto arrPrivIdx = Array::CreateDict(); auto const properties = cls->declProperties(); auto const& propInitVec = cls->declPropInit(); auto const nProps = cls->numDeclProperties(); for (Slot slot = 0; slot < nProps; ++slot) { auto index = cls->propSlotToIndex(slot); auto const& prop = properties[slot]; auto const default_val = propInitVec[index].val.tv(); auto info = Array::CreateDict(); if ((prop.attrs & AttrPrivate) == AttrPrivate) { if (prop.cls == cls) { set_instance_prop_info(info, &prop, default_val); arrPriv.set(StrNR(prop.name), VarNR(info).tv()); arrPrivIdx.set(StrNR(prop.name), prop.serializationIdx); } continue; } set_instance_prop_info(info, &prop, default_val); arr.set(StrNR(prop.name), VarNR(info).tv()); arrIdx.set(StrNR(prop.name), prop.serializationIdx); } for (auto const& prop : cls->staticProperties()) { auto info = Array::CreateDict(); if ((prop.attrs & AttrPrivate) == AttrPrivate) { if (prop.cls == cls) { set_static_prop_info(info, &prop); arrPriv.set(StrNR(prop.name), VarNR(info).tv()); arrPrivIdx.set(StrNR(prop.name), prop.serializationIdx); } continue; } set_static_prop_info(info, &prop); arr.set(StrNR(prop.name), VarNR(info).tv()); arrIdx.set(StrNR(prop.name), prop.serializationIdx); } ret.set(s_properties, VarNR(arr).tv()); ret.set(s_private_properties, VarNR(arrPriv).tv()); ret.set(s_properties_index, VarNR(arrIdx).tv()); ret.set(s_private_properties_index, VarNR(arrPrivIdx).tv()); } // constants { size_t numConsts = cls->numConstants(); const Class::Const* consts = cls->constants(); DictInit arr(numConsts); for (size_t i = 0; i < numConsts; i++) { // Note: hphpc doesn't include inherited constants in // get_class_constants(), so mimic that behavior if (consts[i].cls == cls) { TypedValue value = cls->clsCnsGet(consts[i].name); assertx(value.m_type != KindOfUninit); arr.set(StrNR(consts[i].name), value); } } ret.set(s_constants, make_array_like_tv(arr.create())); } { // source info const PreClass* pcls = cls->preClass(); set_debugger_source_info(ret, pcls->unit()->filepath(), pcls->line1(), pcls->line2()); set_doc_comment(ret, pcls->docComment(), pcls->isBuiltin()); } // user attributes { const PreClass* pcls = cls->preClass(); auto const& attrs = pcls->userAttributes(); DictInit arr{attrs.size()}; for (auto const& attr : attrs) { arr.set(StrNR(attr.first), attr.second); } ret.set(s_attributes, make_array_like_tv(arr.create())); } return ret; } } /////////////////////////////////////////////////////////////////////////////// }