/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | | Copyright (c) 1998-2010 Zend Technologies Ltd. (http://www.zend.com) | +----------------------------------------------------------------------+ | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt. | | If you did not receive a copy of the Zend license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@zend.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/base/builtin_functions.h" #include "hphp/runtime/base/type_conversions.h" #include "hphp/runtime/base/code_coverage.h" #include "hphp/runtime/base/externals.h" #include "hphp/runtime/base/variable_serializer.h" #include "hphp/runtime/base/variable_unserializer.h" #include "hphp/runtime/base/runtime_option.h" #include "hphp/runtime/base/execution_context.h" #include "hphp/runtime/base/strings.h" #include "hphp/runtime/base/file_repository.h" #include "hphp/runtime/debugger/debugger.h" #include "hphp/runtime/ext/ext_process.h" #include "hphp/runtime/ext/ext_class.h" #include "hphp/runtime/ext/ext_function.h" #include "hphp/runtime/ext/ext_file.h" #include "hphp/runtime/ext/ext_collections.h" #include "hphp/util/logger.h" #include "hphp/util/util.h" #include "hphp/util/process.h" #include "hphp/runtime/vm/repo.h" #include "hphp/runtime/vm/jit/translator.h" #include "hphp/runtime/vm/jit/translator-inline.h" #include "hphp/runtime/vm/unit.h" #include "hphp/runtime/vm/event_hook.h" #include "hphp/system/systemlib.h" #include using namespace HPHP::MethodLookup; namespace HPHP { /////////////////////////////////////////////////////////////////////////////// // static strings const StaticString s_offsetExists("offsetExists"); const StaticString s___autoload("__autoload"); const StaticString s___call("__call"); const StaticString s___callStatic("__callStatic"); const StaticString s_exception("exception"); const StaticString s_previous("previous"); const StaticString s_self("self"); const StaticString s_parent("parent"); const StaticString s_static("static"); const StaticString s_class("class"); const StaticString s_function("function"); const StaticString s_constant("constant"); const StaticString s_type("type"); const StaticString s_failure("failure"); const StaticString s_Traversable("Traversable"); const StaticString s_KeyedTraversable("KeyedTraversable"); const StaticString s_Indexish("Indexish"); /////////////////////////////////////////////////////////////////////////////// bool array_is_valid_callback(CArrRef arr) { if (arr.size() != 2 || !arr.exists(int64_t(0)) || !arr.exists(int64_t(1))) { return false; } Variant elem0 = arr.rvalAt(int64_t(0)); if (!elem0.isString() && !elem0.isObject()) { return false; } Variant elem1 = arr.rvalAt(int64_t(1)); if (!elem1.isString()) { return false; } return true; } const HPHP::Func* vm_decode_function(CVarRef function, ActRec* ar, bool forwarding, ObjectData*& this_, HPHP::Class*& cls, StringData*& invName, bool warn /* = true */) { invName = nullptr; if (function.isString() || function.isArray()) { HPHP::Class* ctx = nullptr; if (ar) ctx = arGetContextClass(ar); // Decode the 'function' parameter into this_, cls, name, pos, and // nameContainsClass. this_ = nullptr; cls = nullptr; String name; int pos = String::npos; bool nameContainsClass = false; if (function.isString()) { // If 'function' is a string we simply assign it to name and // leave this_ and cls set to NULL. name = function.toString(); pos = name.find("::"); nameContainsClass = (pos != 0 && pos != String::npos && pos + 2 < name.size()); } else { // If 'function' is an array with exactly two indices 0 and 1, we // assign the value at index 1 to name and we use the value at index // 0 to populate cls (if the value is a string) or this_ and cls (if // the value is an object). assert(function.isArray()); Array arr = function.toArray(); if (!array_is_valid_callback(arr)) { if (warn) { throw_invalid_argument("function: not a valid callback array"); } return nullptr; } Variant elem1 = arr.rvalAt(int64_t(1)); name = elem1.toString(); pos = name.find("::"); nameContainsClass = (pos != 0 && pos != String::npos && pos + 2 < name.size()); Variant elem0 = arr.rvalAt(int64_t(0)); if (elem0.isString()) { String sclass = elem0.toString(); if (sclass->isame(s_self.get())) { if (ctx) { cls = ctx; } if (!nameContainsClass) { forwarding = true; } } else if (sclass->isame(s_parent.get())) { if (ctx && ctx->parent()) { cls = ctx->parent(); } if (!nameContainsClass) { forwarding = true; } } else if (sclass->isame(s_static.get())) { if (ar) { if (ar->hasThis()) { cls = ar->getThis()->getVMClass(); } else if (ar->hasClass()) { cls = ar->getClass(); } } } else { if (warn && nameContainsClass) { String nameClass = name.substr(0, pos); if (nameClass->isame(s_self.get()) || nameClass->isame(s_parent.get()) || nameClass->isame(s_static.get())) { raise_warning("behavior of call_user_func(array('%s', '%s')) " "is undefined", sclass->data(), name->data()); } } cls = Unit::loadClass(sclass.get()); } if (!cls) { if (warn) { throw_invalid_argument("function: class not found"); } return nullptr; } } else { assert(elem0.isObject()); this_ = elem0.getObjectData(); cls = this_->getVMClass(); } } HPHP::Class* cc = cls; if (nameContainsClass) { String c = name.substr(0, pos); name = name.substr(pos + 2); if (c->isame(s_self.get())) { if (cls) { cc = cls; } else if (ctx) { cc = ctx; } if (!this_) { forwarding = true; } } else if (c->isame(s_parent.get())) { if (cls) { cc = cls->parent(); } else if (ctx && ctx->parent()) { cc = ctx->parent(); } if (!this_) { forwarding = true; } } else if (c->isame(s_static.get())) { if (ar) { if (ar->hasThis()) { cc = ar->getThis()->getVMClass(); } else if (ar->hasClass()) { cc = ar->getClass(); } } } else { cc = Unit::loadClass(c.get()); } if (!cc) { if (warn) { throw_invalid_argument("function: class not found"); } return nullptr; } if (cls) { if (!cls->classof(cc)) { if (warn) { raise_warning("call_user_func expects parameter 1 to be a valid " "callback, class '%s' is not a subclass of '%s'", cls->preClass()->name()->data(), cc->preClass()->name()->data()); } return nullptr; } } // If there is not a current instance, cc trumps cls. if (!this_) { cls = cc; } } if (!cls) { HPHP::Func* f = HPHP::Unit::loadFunc(name.get()); if (!f) { if (warn) { throw_invalid_argument("function: method '%s' not found", name->data()); } return nullptr; } assert(f && f->preClass() == nullptr); return f; } assert(cls); CallType lookupType = this_ ? CallType::ObjMethod : CallType::ClsMethod; const HPHP::Func* f = g_vmContext->lookupMethodCtx(cc, name.get(), ctx, lookupType); if (f && (f->attrs() & AttrStatic)) { // If we found a method and its static, null out this_ this_ = nullptr; } else { if (!this_ && ar) { // If we did not find a static method AND this_ is null AND there is a // frame ar, check if the current instance from ar is compatible ObjectData* obj = ar->hasThis() ? ar->getThis() : nullptr; if (obj && obj->instanceof(cls)) { this_ = obj; cls = obj->getVMClass(); } } if (!f) { if (this_) { // If this_ is non-null AND we could not find a method, try // looking up __call in cls's method table f = cls->lookupMethod(s___call.get()); assert(!f || !(f->attrs() & AttrStatic)); } if (!f && lookupType == CallType::ClsMethod) { f = cls->lookupMethod(s___callStatic.get()); assert(!f || (f->attrs() & AttrStatic)); this_ = nullptr; } if (f) { // We found __call or __callStatic! // Stash the original name into invName. invName = name.get(); invName->incRefCount(); } else { // Bail out if we couldn't find the method or __call if (warn) { throw_invalid_argument("function: method '%s' not found", name->data()); } return nullptr; } } } assert(f && f->preClass()); // If this_ is non-NULL, then this_ is the current instance and cls is // the class of the current instance. assert(!this_ || this_->getVMClass() == cls); // If we are doing a forwarding call and this_ is null, set cls // appropriately to propagate the current late bound class. if (!this_ && forwarding && ar) { HPHP::Class* fwdCls = nullptr; ObjectData* obj = ar->hasThis() ? ar->getThis() : nullptr; if (obj) { fwdCls = obj->getVMClass(); } else if (ar->hasClass()) { fwdCls = ar->getClass(); } // Only forward the current late bound class if it is the same or // a descendent of cls if (fwdCls && fwdCls->classof(cls)) { cls = fwdCls; } } return f; } if (function.isObject()) { static StringData* invokeStr = StringData::GetStaticString("__invoke"); this_ = function.asCObjRef().get(); cls = nullptr; const HPHP::Func *f = this_->getVMClass()->lookupMethod(invokeStr); if (f != nullptr && ((f->attrs() & AttrStatic) && !f->isClosureBody())) { // If __invoke is static, invoke it as such cls = this_->getVMClass(); this_ = nullptr; } return f; } if (warn) { throw_invalid_argument("function: not string, closure, or array"); } return nullptr; } Variant vm_call_user_func(CVarRef function, CArrRef params, bool forwarding /* = false */) { ObjectData* obj = nullptr; HPHP::Class* cls = nullptr; HPHP::Transl::CallerFrame cf; StringData* invName = nullptr; const HPHP::Func* f = vm_decode_function(function, cf(), forwarding, obj, cls, invName); if (f == nullptr) { return uninit_null(); } Variant ret; g_vmContext->invokeFunc((TypedValue*)&ret, f, params, obj, cls, nullptr, invName); return ret; } Variant invoke(CStrRef function, CArrRef params, strhash_t hash /* = -1 */, bool tryInterp /* = true */, bool fatal /* = true */) { Func* func = Unit::loadFunc(function.get()); if (func) { Variant ret; g_vmContext->invokeFunc(ret.asTypedValue(), func, params); return ret; } return invoke_failed(function.c_str(), params, fatal); } Variant invoke(const char *function, CArrRef params, strhash_t hash /* = -1*/, bool tryInterp /* = true */, bool fatal /* = true */) { String funcName(function, CopyString); return invoke(funcName, params, hash, tryInterp, fatal); } Variant invoke_static_method(CStrRef s, CStrRef method, CArrRef params, bool fatal /* = true */) { HPHP::Class* class_ = Unit::lookupClass(s.get()); if (class_ == nullptr) { o_invoke_failed(s.data(), method.data(), fatal); return uninit_null(); } const HPHP::Func* f = class_->lookupMethod(method.get()); if (f == nullptr || !(f->attrs() & AttrStatic)) { o_invoke_failed(s.data(), method.data(), fatal); return uninit_null(); } Variant ret; g_vmContext->invokeFunc((TypedValue*)&ret, f, params, nullptr, class_); return ret; } Variant invoke_failed(CVarRef func, CArrRef params, bool fatal /* = true */) { if (func.isObject()) { return o_invoke_failed( func.objectForCall()->o_getClassName().c_str(), "__invoke", fatal); } else { return invoke_failed(func.toString().c_str(), params, fatal); } } Variant invoke_failed(const char *func, CArrRef params, bool fatal /* = true */) { if (fatal) { throw InvalidFunctionCallException(func); } else { raise_warning("call_user_func to non-existent function %s", func); return false; } } Variant o_invoke_failed(const char *cls, const char *meth, bool fatal /* = true */) { if (fatal) { string msg = "Unknown method "; msg += cls; msg += "::"; msg += meth; throw FatalErrorException(msg.c_str()); } else { raise_warning("call_user_func to non-existent method %s::%s", cls, meth); return false; } } void NEVER_INLINE raise_null_object_prop() { raise_notice("Trying to get property of non-object"); } void NEVER_INLINE throw_null_get_object_prop() { raise_error("Trying to get property of non-object"); } void NEVER_INLINE throw_null_object_prop() { raise_error("Trying to set property of non-object"); } void NEVER_INLINE throw_invalid_property_name(CStrRef name) { if (!name.size()) { throw EmptyObjectPropertyException(); } else { throw NullStartObjectPropertyException(); } } void throw_instance_method_fatal(const char *name) { if (!strstr(name, "::__destruct")) { raise_error("Non-static method %s() cannot be called statically", name); } } void throw_iterator_not_valid() { Object e(SystemLib::AllocInvalidOperationExceptionObject( "Iterator is not valid")); throw e; } void throw_collection_modified() { Object e(SystemLib::AllocInvalidOperationExceptionObject( "Collection was modified during iteration")); throw e; } void throw_collection_property_exception() { Object e(SystemLib::AllocInvalidOperationExceptionObject( "Cannot access a property on a collection")); throw e; } void throw_collection_compare_exception() { static const string msg( "Cannot use relational comparison operators (<, <=, >, >=) to compare " "a collection with an integer, double, string, array, or object"); if (RuntimeOption::StrictCollections) { Object e(SystemLib::AllocRuntimeExceptionObject(msg)); throw e; } raise_warning(msg); } // TODO void check_collection_compare(ObjectData* obj) { if (obj && obj->isCollection()) throw_collection_compare_exception(); } void check_collection_compare(ObjectData* obj1, ObjectData* obj2) { if (obj1 && obj2 && (obj1->isCollection() || obj2->isCollection())) { throw_collection_compare_exception(); } } void check_collection_cast_to_array() { if (RuntimeOption::WarnOnCollectionToArray) { raise_warning("Casting a collection to an array is an expensive operation " "and should be avoided where possible. To convert a " "collection to an array without raising a warning, use the " "toArray() method."); } } Object create_object_only(CStrRef s) { return g_vmContext->createObjectOnly(s.get()); } Object create_object(CStrRef s, CArrRef params, bool init /* = true */) { return g_vmContext->createObject(s.get(), params, init); } /* * This function is used when another thread is segfaulting---we just * want to wait forever to give it a chance to write a stacktrace file * (and maybe a core file). */ void pause_forever() { for (;;) sleep(300); } ssize_t check_request_surprise(ThreadInfo *info) { RequestInjectionData &p = info->m_reqInjectionData; bool do_timedout, do_memExceeded, do_signaled; ssize_t flags = p.fetchAndClearFlags(); do_timedout = (flags & RequestInjectionData::TimedOutFlag) && !p.getDebugger(); do_memExceeded = (flags & RequestInjectionData::MemExceededFlag); do_signaled = (flags & RequestInjectionData::SignaledFlag); // Start with any pending exception that might be on the thread. Exception* pendingException = info->m_pendingException; info->m_pendingException = nullptr; if (do_timedout && !pendingException) { pendingException = generate_request_timeout_exception(); } if (do_memExceeded && !pendingException) { pendingException = generate_memory_exceeded_exception(); } if (do_signaled) f_pcntl_signal_dispatch(); if (pendingException) { pendingException->throwException(); } return flags; } void throw_missing_arguments_nr(const char *fn, int expected, int got, int level /* = 0 */) { if (level == 2 || RuntimeOption::ThrowMissingArguments) { if (expected == 1) { raise_error(Strings::MISSING_ARGUMENT, fn, got); } else { raise_error(Strings::MISSING_ARGUMENTS, fn, expected, got); } } else { if (expected == 1) { raise_warning(Strings::MISSING_ARGUMENT, fn, got); } else { raise_warning(Strings::MISSING_ARGUMENTS, fn, expected, got); } } } void throw_toomany_arguments_nr(const char *fn, int num, int level /* = 0 */) { if (level == 2 || RuntimeOption::ThrowTooManyArguments) { raise_error("Too many arguments for %s(), expected %d", fn, num); } else if (level == 1 || RuntimeOption::WarnTooManyArguments) { raise_warning("Too many arguments for %s(), expected %d", fn, num); } } void throw_wrong_arguments_nr(const char *fn, int count, int cmin, int cmax, int level /* = 0 */) { if (cmin >= 0 && count < cmin) { throw_missing_arguments_nr(fn, cmin, count, level); return; } if (cmax >= 0 && count > cmax) { throw_toomany_arguments_nr(fn, cmax, level); return; } assert(false); } void throw_bad_type_exception(const char *fmt, ...) { va_list ap; va_start(ap, fmt); string msg; Util::string_vsnprintf(msg, fmt, ap); va_end(ap); if (RuntimeOption::ThrowBadTypeExceptions) { throw InvalidOperandException(msg.c_str()); } raise_warning("Invalid operand type was used: %s", msg.c_str()); } void throw_bad_array_exception() { const char* fn = "(unknown)"; ActRec *ar = g_vmContext->getStackFrame(); if (ar) { fn = ar->m_func->name()->data(); } throw_bad_type_exception("%s expects array(s)", fn); } void throw_invalid_argument(const char *fmt, ...) { va_list ap; va_start(ap, fmt); string msg; Util::string_vsnprintf(msg, fmt, ap); va_end(ap); if (RuntimeOption::ThrowInvalidArguments) { throw InvalidArgumentException(msg.c_str()); } raise_warning("Invalid argument: %s", msg.c_str()); } Variant throw_fatal_unset_static_property(const char *s, const char *prop) { raise_error("Attempt to unset static property %s::$%s", s, prop); return uninit_null(); } void throw_infinite_recursion_exception() { if (!RuntimeOption::NoInfiniteRecursionDetection) { // Reset profiler otherwise it might recurse further causing segfault DECLARE_THREAD_INFO info->m_profiler = nullptr; throw UncatchableException("infinite recursion detected"); } } Exception* generate_request_timeout_exception() { Exception* ret = nullptr; ThreadInfo *info = ThreadInfo::s_threadInfo.getNoCheck(); RequestInjectionData &data = info->m_reqInjectionData; if (data.timeoutSeconds > 0) { // This extra checking is needed, because there may be a race condition // a TimeoutThread sets flag "true" right after an old request finishes and // right before a new requets resets "started". In this case, we flag // "timedout" back to "false". if (time(0) - data.started >= data.timeoutSeconds) { std::string exceptionMsg = "entire web request took longer than "; exceptionMsg += boost::lexical_cast(data.timeoutSeconds); exceptionMsg += " seconds and timed out"; ArrayHolder exceptionStack; if (RuntimeOption::InjectedStackTrace) { exceptionStack = g_vmContext->debugBacktrace(false, true, true).get(); } ret = new FatalErrorException(exceptionMsg, exceptionStack.get()); } } return ret; } Exception* generate_memory_exceeded_exception() { ArrayHolder exceptionStack; if (RuntimeOption::InjectedStackTrace) { exceptionStack = g_vmContext->debugBacktrace(false, true, true).get(); } return new FatalErrorException( "request has exceeded memory limit", exceptionStack.get()); } void throw_call_non_object() { throw_call_non_object(nullptr); } void throw_call_non_object(const char *methodName) { std::string msg; if (methodName == nullptr) { msg = "Call to a member function on a non-object"; } else { Util::string_printf(msg, "Call to a member function %s() on a non-object", methodName); } if (RuntimeOption::ThrowExceptionOnBadMethodCall) { Object e(SystemLib::AllocBadMethodCallExceptionObject(String(msg))); throw e; } throw FatalErrorException(msg.c_str()); } String f_serialize(CVarRef value) { switch (value.getType()) { case KindOfUninit: case KindOfNull: return "N;"; case KindOfBoolean: return value.getBoolean() ? "b:1;" : "b:0;"; case KindOfInt64: { StringBuffer sb; sb.append("i:"); sb.append(value.getInt64()); sb.append(';'); return sb.detach(); } case KindOfStaticString: case KindOfString: { StringData *str = value.getStringData(); StringBuffer sb; sb.append("s:"); sb.append(str->size()); sb.append(":\""); sb.append(str->data(), str->size()); sb.append("\";"); return sb.detach(); } case KindOfArray: { ArrayData *arr = value.getArrayData(); if (arr->empty()) return "a:0:{}"; // fall-through } case KindOfObject: case KindOfDouble: { VariableSerializer vs(VariableSerializer::Type::Serialize); return vs.serialize(value, true); } default: assert(false); break; } return ""; } Variant unserialize_ex(const char* str, int len, VariableUnserializer::Type type, CArrRef class_whitelist /* = null_array */) { if (str == nullptr || len <= 0) { return false; } VariableUnserializer vu(str, len, type, false, class_whitelist); Variant v; try { v = vu.unserialize(); } catch (FatalErrorException &e) { throw; } catch (Exception &e) { raise_notice("Unable to unserialize: [%s]. %s.", str, e.getMessage().c_str()); return false; } return v; } Variant unserialize_ex(CStrRef str, VariableUnserializer::Type type, CArrRef class_whitelist /* = null_array */) { return unserialize_ex(str.data(), str.size(), type, class_whitelist); } String concat3(CStrRef s1, CStrRef s2, CStrRef s3) { StringSlice r1 = s1.slice(); StringSlice r2 = s2.slice(); StringSlice r3 = s3.slice(); int len = r1.len + r2.len + r3.len; StringData* str = NEW(StringData)(len); MutableSlice r = str->mutableSlice(); memcpy(r.ptr, r1.ptr, r1.len); memcpy(r.ptr + r1.len, r2.ptr, r2.len); memcpy(r.ptr + r1.len + r2.len, r3.ptr, r3.len); str->setSize(len); return str; } String concat4(CStrRef s1, CStrRef s2, CStrRef s3, CStrRef s4) { StringSlice r1 = s1.slice(); StringSlice r2 = s2.slice(); StringSlice r3 = s3.slice(); StringSlice r4 = s4.slice(); int len = r1.len + r2.len + r3.len + r4.len; StringData* str = NEW(StringData)(len); MutableSlice r = str->mutableSlice(); memcpy(r.ptr, r1.ptr, r1.len); memcpy(r.ptr + r1.len, r2.ptr, r2.len); memcpy(r.ptr + r1.len + r2.len, r3.ptr, r3.len); memcpy(r.ptr + r1.len + r2.len + r3.len, r4.ptr, r4.len); str->setSize(len); return str; } bool interface_supports_array(const StringData* s) { return (s->isame(s_Traversable.get()) || s->isame(s_KeyedTraversable.get()) || s->isame(s_Indexish.get())); } bool interface_supports_array(const std::string& n) { const char* s = n.c_str(); return ((n.size() == 11 && !strcasecmp(s, "Traversable")) || (n.size() == 16 && !strcasecmp(s, "KeyedTraversable")) || (n.size() == 8 && !strcasecmp(s, "Indexish"))); } Variant include_impl_invoke(CStrRef file, bool once, const char *currentDir) { if (file[0] == '/') { if (RuntimeOption::SandboxMode || !RuntimeOption::AlwaysUseRelativePath) { try { return invoke_file(file, once, currentDir); } catch(PhpFileDoesNotExistException &e) {} } String rel_path(Util::relativePath(RuntimeOption::SourceRoot, string(file.data()))); // Don't try/catch - We want the exception to be passed along return invoke_file(rel_path, once, currentDir); } else { // Don't try/catch - We want the exception to be passed along return invoke_file(file, once, currentDir); } } Variant invoke_file(CStrRef s, bool once, const char *currentDir) { Variant r; if (invoke_file_impl(r, s, once, currentDir)) { return r; } return throw_missing_file(s.c_str()); } bool invoke_file_impl(Variant &res, CStrRef path, bool once, const char *currentDir) { bool initial; HPHP::Eval::PhpFile* efile = g_vmContext->lookupPhpFile(path.get(), currentDir, &initial); HPHP::Unit* u = nullptr; if (efile) u = efile->unit(); if (u == nullptr) { return false; } if (!once || initial) { g_vmContext->invokeUnit((TypedValue*)(&res), u); } return true; } /** * Used by include_impl. resolve_include() needs some way of checking the * existence of a file path, which for hphpc means attempting to invoke it. * This struct carries some context information needed for the invocation, as * well as a place for the return value of invoking the file. */ struct IncludeImplInvokeContext { bool once; const char* currentDir; Variant returnValue; }; static bool include_impl_invoke_context(CStrRef file, void* ctx) { struct IncludeImplInvokeContext* context = (IncludeImplInvokeContext*)ctx; bool invoked_file = false; try { context->returnValue = include_impl_invoke(file, context->once, context->currentDir); invoked_file = true; } catch (PhpFileDoesNotExistException& e) { context->returnValue = false; } return invoked_file; } /** * tryFile is a pointer to a function that resolve_include() will use to * determine if a path references a real file. ctx is a pointer to some context * information that will be passed through to tryFile. (It's a hacky closure) */ String resolve_include(CStrRef file, const char* currentDir, bool (*tryFile)(CStrRef file, void*), void* ctx) { const char* c_file = file->data(); if (c_file[0] == '/') { String can_path(Util::canonicalize(file.c_str(), file.size()), AttachString); if (tryFile(can_path, ctx)) { return can_path; } } else if ((c_file[0] == '.' && (c_file[1] == '/' || ( c_file[1] == '.' && c_file[2] == '/')))) { String path(String(g_context->getCwd() + "/" + file)); String can_path(Util::canonicalize(path.c_str(), path.size()), AttachString); if (tryFile(can_path, ctx)) { return can_path; } } else { Array includePaths = g_context->getIncludePathArray(); unsigned int path_count = includePaths.size(); for (int i = 0; i < (int)path_count; i++) { String path(""); String includePath = includePaths[i].toString(); if (includePath[0] != '/') { path += (g_context->getCwd() + "/"); } path += includePath; if (path[path.size() - 1] != '/') { path += "/"; } path += file; String can_path(Util::canonicalize(path.c_str(), path.size()), AttachString); if (tryFile(can_path, ctx)) { return can_path; } } if (currentDir[0] == '/') { String path(currentDir); path += "/"; path += file; String can_path(Util::canonicalize(path.c_str(), path.size()), AttachString); if (tryFile(can_path, ctx)) { return can_path; } } else { String path(g_context->getCwd() + "/" + currentDir + file); String can_path(Util::canonicalize(path.c_str(), path.size()), AttachString); if (tryFile(can_path, ctx)) { return can_path; } } } return String(); } static Variant include_impl(CStrRef file, bool once, const char *currentDir, bool required, bool raiseNotice) { struct IncludeImplInvokeContext ctx = {once, currentDir}; String can_path = resolve_include(file, currentDir, include_impl_invoke_context, (void*)&ctx); if (can_path.isNull()) { // Failure if (raiseNotice) { raise_notice("Tried to invoke %s but file not found.", file->data()); } if (required) { String ms = "Required file that does not exist: "; ms += file; throw FatalErrorException(ms.data()); } return false; } return ctx.returnValue; } Variant include(CStrRef file, bool once /* = false */, const char *currentDir /* = NULL */, bool raiseNotice /*= true*/) { return include_impl(file, once, currentDir, false, raiseNotice); } Variant require(CStrRef file, bool once /* = false */, const char *currentDir /* = NULL */, bool raiseNotice /*= true*/) { return include_impl(file, once, currentDir, true, raiseNotice); } /////////////////////////////////////////////////////////////////////////////// // class Limits IMPLEMENT_REQUEST_LOCAL(AutoloadHandler, AutoloadHandler::s_instance); void AutoloadHandler::requestInit() { m_running = false; m_handlers.reset(); m_map.reset(); m_map_root.reset(); } void AutoloadHandler::requestShutdown() { m_handlers.reset(); m_map.reset(); m_map_root.reset(); } bool AutoloadHandler::setMap(CArrRef map, CStrRef root) { this->m_map = map; this->m_map_root = root; return true; } class ClassExistsChecker { public: ClassExistsChecker() {} bool operator()(CStrRef name) const { return Unit::lookupClass(name.get()) != nullptr; } }; class ConstantExistsChecker { public: bool operator()(CStrRef name) const { return Unit::lookupCns(name.get()) != nullptr; } }; template AutoloadHandler::Result AutoloadHandler::loadFromMap(CStrRef name, CStrRef kind, bool toLower, const T &checkExists) { assert(!m_map.isNull()); while (true) { CVarRef &type_map = m_map.get()->get(kind); auto const typeMapCell = type_map.asCell(); if (typeMapCell->m_type != KindOfArray) return Failure; String canonicalName = toLower ? StringUtil::ToLower(name) : name; CVarRef &file = typeMapCell->m_data.parr->get(canonicalName); bool ok = false; if (file.isString()) { String fName = file.toCStrRef().get(); if (fName.get()->data()[0] != '/') { if (!m_map_root.empty()) { fName = m_map_root + fName; } } try { Transl::VMRegAnchor _; bool initial; VMExecutionContext* ec = g_vmContext; Unit* u = ec->evalInclude(fName.get(), nullptr, &initial); if (u) { if (initial) { TypedValue retval; ec->invokeFunc(&retval, u->getMain(), null_array, nullptr, nullptr, nullptr, nullptr, ExecutionContext::InvokePseudoMain); tvRefcountedDecRef(&retval); } ok = true; } } catch (...) {} } if (ok && checkExists(name)) { return Success; } CVarRef &func = m_map.get()->get(s_failure); if (!isset(func)) return Failure; // can throw, otherwise // - true means the map was updated. try again // - false means we should stop applying autoloaders (only affects classes) // - anything else means keep going Variant action = vm_call_user_func(func, CREATE_VECTOR2(kind, name)); auto const actionCell = action.asCell(); if (actionCell->m_type == KindOfBoolean) { if (actionCell->m_data.num) continue; return StopAutoloading; } return ContinueAutoloading; } } bool AutoloadHandler::autoloadFunc(StringData* name) { return !m_map.isNull() && loadFromMap(name, s_function, true, function_exists) != Failure; } bool AutoloadHandler::autoloadConstant(StringData* name) { return !m_map.isNull() && loadFromMap(name, s_constant, false, ConstantExistsChecker()) != Failure; } bool AutoloadHandler::autoloadType(CStrRef name) { return !m_map.isNull() && loadFromMap(name, s_type, true, [] (CStrRef name) { return Unit::GetNamedEntity(name.get())->getCachedTypedef() != nullptr; } ) != Failure; } /** * invokeHandler returns true if any autoload handlers were executed, * false otherwise. When this function returns true, it is the caller's * responsibility to check if the given class or interface exists. */ bool AutoloadHandler::invokeHandler(CStrRef className, bool forceSplStack /* = false */) { if (!m_map.isNull()) { ClassExistsChecker ce; Result res = loadFromMap(className, s_class, true, ce); if (res == ContinueAutoloading) { if (ce(className)) return true; } else { if (res != Failure) return res == Success; } } Array params(ArrayInit(1, ArrayInit::vectorInit).set(className).create()); bool l_running = m_running; m_running = true; if (m_handlers.isNull() && !forceSplStack) { if (function_exists(s___autoload)) { invoke(s___autoload, params, -1, true, false); m_running = l_running; return true; } m_running = l_running; return false; } if (m_handlers.isNull() || m_handlers->empty()) { m_running = l_running; return false; } Object autoloadException; for (ArrayIter iter(m_handlers); iter; ++iter) { try { vm_call_user_func(iter.second(), params); } catch (Object& ex) { assert(ex.instanceof(SystemLib::s_ExceptionClass)); if (autoloadException.isNull()) { autoloadException = ex; } else { Object cur = ex; Variant next = cur->o_get(s_previous, false, s_exception); while (next.isObject()) { cur = next.toObject(); next = cur->o_get(s_previous, false, s_exception); } cur->o_set(s_previous, autoloadException, s_exception); autoloadException = ex; } } if (Unit::lookupClass(className.get()) != nullptr) { break; } } m_running = l_running; if (!autoloadException.isNull()) { throw autoloadException; } return true; } bool AutoloadHandler::addHandler(CVarRef handler, bool prepend) { String name = getSignature(handler); if (name.isNull()) return false; if (m_handlers.isNull()) { m_handlers = Array::Create(); } if (!prepend) { // The following ensures that the handler is added at the end m_handlers.remove(name, true); m_handlers.add(name, handler, true); } else { // This adds the handler at the beginning m_handlers = CREATE_MAP1(name, handler) + m_handlers; } return true; } bool AutoloadHandler::isRunning() { return m_running; } void AutoloadHandler::removeHandler(CVarRef handler) { String name = getSignature(handler); if (name.isNull()) return; m_handlers.remove(name, true); } void AutoloadHandler::removeAllHandlers() { m_handlers.reset(); } String AutoloadHandler::getSignature(CVarRef handler) { Variant name; if (!f_is_callable(handler, false, ref(name))) { return null_string; } String lName = StringUtil::ToLower(name.toString()); if (handler.isArray()) { Variant first = handler.getArrayData()->get(int64_t(0)); if (first.isObject()) { // Add the object address as part of the signature int64_t data = (int64_t)first.getObjectData(); lName += String((const char *)&data, sizeof(data), CopyString); } } else if (handler.isObject()) { // "lName" will just be "classname::__invoke", // add object address to differentiate the signature int64_t data = (int64_t)handler.getObjectData(); lName += String((const char*)&data, sizeof(data), CopyString); } return lName; } bool function_exists(CStrRef function_name) { return HPHP::Unit::lookupFunc(function_name.get()) != nullptr; } /////////////////////////////////////////////////////////////////////////////// // debugger and code coverage instrumentation void throw_exception(CObjRef e) { if (!e.instanceof(SystemLib::s_ExceptionClass)) { raise_error("Exceptions must be valid objects derived from the " "Exception base class"); } DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(e.get())); throw e; } /////////////////////////////////////////////////////////////////////////////// }