d145ce88e3
Re-implement them in terms of TypedValue and Cell helpers.
1261 linhas
39 KiB
C++
1261 linhas
39 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| 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/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/lib/systemlib.h"
|
|
|
|
#include <limits>
|
|
|
|
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_ ? ObjMethod : 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 == 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<std::string>(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());
|
|
}
|
|
|
|
void throw_unexpected_argument_type(int argNum, const char *fnName,
|
|
const char *expected, CVarRef val) {
|
|
const char *otype = nullptr;
|
|
switch (val.getType()) {
|
|
case KindOfUninit:
|
|
case KindOfNull: otype = "null"; break;
|
|
case KindOfBoolean: otype = "bool"; break;
|
|
case KindOfInt64: otype = "int"; break;
|
|
case KindOfDouble: otype = "double"; break;
|
|
case KindOfStaticString:
|
|
case KindOfString: otype = "string"; break;
|
|
case KindOfArray: otype = "array"; break;
|
|
case KindOfObject:
|
|
otype = val.getObjectData()->o_getClassName().c_str();
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
raise_recoverable_error
|
|
("Argument %d passed to %s must be an instance of %s, %s given",
|
|
argNum, fnName, expected, otype);
|
|
}
|
|
|
|
Object f_clone(CVarRef v) {
|
|
if (v.isObject()) {
|
|
Object clone = Object(v.toObject()->clone());
|
|
clone->t___clone();
|
|
return clone;
|
|
}
|
|
raise_error("Cannot clone non-object");
|
|
return Object();
|
|
}
|
|
|
|
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::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];
|
|
|
|
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 <class T>
|
|
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);
|
|
Variant::TypedValueAccessor tva = type_map.getTypedAccessor();
|
|
if (Variant::GetAccessorType(tva) != KindOfArray) return Failure;
|
|
String canonicalName = toLower ? StringUtil::ToLower(name) : name;
|
|
CVarRef &file = Variant::GetArrayData(tva)->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));
|
|
tva = action.getTypedAccessor();
|
|
if (Variant::GetAccessorType(tva) == KindOfBoolean) {
|
|
if (Variant::GetBoolean(tva)) 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())->getCachedNameDef();
|
|
}
|
|
) != 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);
|
|
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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|