Rename _ to - in runtime/base, part 2
[hiphop-php.git] / hphp / runtime / base / builtin_functions.cpp
blob0372eed2b26ec357f3bad317be16e106c468e17f
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1998-2010 Zend Technologies Ltd. (http://www.zend.com) |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 2.00 of the Zend license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.zend.com/license/2_00.txt. |
12 | If you did not receive a copy of the Zend license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@zend.com so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/base/builtin_functions.h"
20 #include "hphp/runtime/base/type_conversions.h"
21 #include "hphp/runtime/base/code_coverage.h"
22 #include "hphp/runtime/base/externals.h"
23 #include "hphp/runtime/base/variable-serializer.h"
24 #include "hphp/runtime/base/variable-unserializer.h"
25 #include "hphp/runtime/base/runtime_option.h"
26 #include "hphp/runtime/base/execution_context.h"
27 #include "hphp/runtime/base/strings.h"
28 #include "hphp/runtime/base/file_repository.h"
29 #include "hphp/runtime/debugger/debugger.h"
30 #include "hphp/runtime/ext/ext_process.h"
31 #include "hphp/runtime/ext/ext_class.h"
32 #include "hphp/runtime/ext/ext_function.h"
33 #include "hphp/runtime/ext/ext_file.h"
34 #include "hphp/runtime/ext/ext_collections.h"
35 #include "hphp/util/logger.h"
36 #include "hphp/util/util.h"
37 #include "hphp/util/process.h"
38 #include "hphp/runtime/vm/repo.h"
39 #include "hphp/runtime/vm/jit/translator.h"
40 #include "hphp/runtime/vm/jit/translator-inline.h"
41 #include "hphp/runtime/vm/unit.h"
42 #include "hphp/runtime/vm/event-hook.h"
43 #include "hphp/system/systemlib.h"
44 #include "folly/Format.h"
46 #include <limits>
48 using namespace HPHP::MethodLookup;
50 namespace HPHP {
51 ///////////////////////////////////////////////////////////////////////////////
52 // static strings
54 const StaticString
55 s_offsetExists("offsetExists"),
56 s___autoload("__autoload"),
57 s___call("__call"),
58 s___callStatic("__callStatic"),
59 s___invoke("__invoke"),
60 s_exception("exception"),
61 s_previous("previous"),
62 s_self("self"),
63 s_parent("parent"),
64 s_static("static"),
65 s_class("class"),
66 s_function("function"),
67 s_constant("constant"),
68 s_type("type"),
69 s_failure("failure"),
70 s_Traversable("Traversable"),
71 s_KeyedTraversable("KeyedTraversable"),
72 s_Indexish("Indexish");
74 ///////////////////////////////////////////////////////////////////////////////
76 bool array_is_valid_callback(CArrRef arr) {
77 if (arr.size() != 2 || !arr.exists(int64_t(0)) || !arr.exists(int64_t(1))) {
78 return false;
80 Variant elem0 = arr.rvalAt(int64_t(0));
81 if (!elem0.isString() && !elem0.isObject()) {
82 return false;
84 Variant elem1 = arr.rvalAt(int64_t(1));
85 if (!elem1.isString()) {
86 return false;
88 return true;
91 const HPHP::Func*
92 vm_decode_function(CVarRef function,
93 ActRec* ar,
94 bool forwarding,
95 ObjectData*& this_,
96 HPHP::Class*& cls,
97 StringData*& invName,
98 bool warn /* = true */) {
99 invName = nullptr;
100 if (function.isString() || function.isArray()) {
101 HPHP::Class* ctx = nullptr;
102 if (ar) ctx = arGetContextClass(ar);
103 // Decode the 'function' parameter into this_, cls, name, pos, and
104 // nameContainsClass.
105 this_ = nullptr;
106 cls = nullptr;
107 String name;
108 int pos = String::npos;
109 bool nameContainsClass = false;
110 if (function.isString()) {
111 // If 'function' is a string we simply assign it to name and
112 // leave this_ and cls set to NULL.
113 name = function.toString();
114 pos = name.find("::");
115 nameContainsClass =
116 (pos != 0 && pos != String::npos && pos + 2 < name.size());
117 } else {
118 // If 'function' is an array with exactly two indices 0 and 1, we
119 // assign the value at index 1 to name and we use the value at index
120 // 0 to populate cls (if the value is a string) or this_ and cls (if
121 // the value is an object).
122 assert(function.isArray());
123 Array arr = function.toArray();
124 if (!array_is_valid_callback(arr)) {
125 if (warn) {
126 throw_invalid_argument("function: not a valid callback array");
128 return nullptr;
130 Variant elem1 = arr.rvalAt(int64_t(1));
131 name = elem1.toString();
132 pos = name.find("::");
133 nameContainsClass =
134 (pos != 0 && pos != String::npos && pos + 2 < name.size());
135 Variant elem0 = arr.rvalAt(int64_t(0));
136 if (elem0.isString()) {
137 String sclass = elem0.toString();
138 if (sclass->isame(s_self.get())) {
139 if (ctx) {
140 cls = ctx;
142 if (!nameContainsClass) {
143 forwarding = true;
145 } else if (sclass->isame(s_parent.get())) {
146 if (ctx && ctx->parent()) {
147 cls = ctx->parent();
149 if (!nameContainsClass) {
150 forwarding = true;
152 } else if (sclass->isame(s_static.get())) {
153 if (ar) {
154 if (ar->hasThis()) {
155 cls = ar->getThis()->getVMClass();
156 } else if (ar->hasClass()) {
157 cls = ar->getClass();
160 } else {
161 if (warn && nameContainsClass) {
162 String nameClass = name.substr(0, pos);
163 if (nameClass->isame(s_self.get()) ||
164 nameClass->isame(s_parent.get()) ||
165 nameClass->isame(s_static.get())) {
166 raise_warning("behavior of call_user_func(array('%s', '%s')) "
167 "is undefined", sclass->data(), name->data());
170 cls = Unit::loadClass(sclass.get());
172 if (!cls) {
173 if (warn) {
174 throw_invalid_argument("function: class not found");
176 return nullptr;
178 } else {
179 assert(elem0.isObject());
180 this_ = elem0.getObjectData();
181 cls = this_->getVMClass();
185 HPHP::Class* cc = cls;
186 if (nameContainsClass) {
187 String c = name.substr(0, pos);
188 name = name.substr(pos + 2);
189 if (c->isame(s_self.get())) {
190 if (cls) {
191 cc = cls;
192 } else if (ctx) {
193 cc = ctx;
195 if (!this_) {
196 forwarding = true;
198 } else if (c->isame(s_parent.get())) {
199 if (cls) {
200 cc = cls->parent();
201 } else if (ctx && ctx->parent()) {
202 cc = ctx->parent();
204 if (!this_) {
205 forwarding = true;
207 } else if (c->isame(s_static.get())) {
208 if (ar) {
209 if (ar->hasThis()) {
210 cc = ar->getThis()->getVMClass();
211 } else if (ar->hasClass()) {
212 cc = ar->getClass();
215 } else {
216 cc = Unit::loadClass(c.get());
218 if (!cc) {
219 if (warn) {
220 throw_invalid_argument("function: class not found");
222 return nullptr;
224 if (cls) {
225 if (!cls->classof(cc)) {
226 if (warn) {
227 raise_warning("call_user_func expects parameter 1 to be a valid "
228 "callback, class '%s' is not a subclass of '%s'",
229 cls->preClass()->name()->data(),
230 cc->preClass()->name()->data());
232 return nullptr;
235 // If there is not a current instance, cc trumps cls.
236 if (!this_) {
237 cls = cc;
240 if (!cls) {
241 HPHP::Func* f = HPHP::Unit::loadFunc(name.get());
242 if (!f) {
243 if (warn) {
244 throw_invalid_argument("function: method '%s' not found",
245 name->data());
247 return nullptr;
249 assert(f && f->preClass() == nullptr);
250 return f;
252 assert(cls);
253 CallType lookupType = this_ ? CallType::ObjMethod : CallType::ClsMethod;
254 const HPHP::Func* f =
255 g_vmContext->lookupMethodCtx(cc, name.get(), ctx, lookupType);
256 if (f && (f->attrs() & AttrStatic)) {
257 // If we found a method and its static, null out this_
258 this_ = nullptr;
259 } else {
260 if (!this_ && ar) {
261 // If we did not find a static method AND this_ is null AND there is a
262 // frame ar, check if the current instance from ar is compatible
263 ObjectData* obj = ar->hasThis() ? ar->getThis() : nullptr;
264 if (obj && obj->instanceof(cls)) {
265 this_ = obj;
266 cls = obj->getVMClass();
269 if (!f) {
270 if (this_) {
271 // If this_ is non-null AND we could not find a method, try
272 // looking up __call in cls's method table
273 f = cls->lookupMethod(s___call.get());
274 assert(!f || !(f->attrs() & AttrStatic));
276 if (!f && lookupType == CallType::ClsMethod) {
277 f = cls->lookupMethod(s___callStatic.get());
278 assert(!f || (f->attrs() & AttrStatic));
279 this_ = nullptr;
281 if (f) {
282 // We found __call or __callStatic!
283 // Stash the original name into invName.
284 invName = name.get();
285 invName->incRefCount();
286 } else {
287 // Bail out if we couldn't find the method or __call
288 if (warn) {
289 throw_invalid_argument("function: method '%s' not found",
290 name->data());
292 return nullptr;
296 assert(f && f->preClass());
297 // If this_ is non-NULL, then this_ is the current instance and cls is
298 // the class of the current instance.
299 assert(!this_ || this_->getVMClass() == cls);
300 // If we are doing a forwarding call and this_ is null, set cls
301 // appropriately to propagate the current late bound class.
302 if (!this_ && forwarding && ar) {
303 HPHP::Class* fwdCls = nullptr;
304 ObjectData* obj = ar->hasThis() ? ar->getThis() : nullptr;
305 if (obj) {
306 fwdCls = obj->getVMClass();
307 } else if (ar->hasClass()) {
308 fwdCls = ar->getClass();
310 // Only forward the current late bound class if it is the same or
311 // a descendent of cls
312 if (fwdCls && fwdCls->classof(cls)) {
313 cls = fwdCls;
316 return f;
318 if (function.isObject()) {
319 this_ = function.asCObjRef().get();
320 cls = nullptr;
321 const HPHP::Func *f = this_->getVMClass()->lookupMethod(s___invoke.get());
322 if (f != nullptr &&
323 ((f->attrs() & AttrStatic) && !f->isClosureBody())) {
324 // If __invoke is static, invoke it as such
325 cls = this_->getVMClass();
326 this_ = nullptr;
328 return f;
330 if (warn) {
331 throw_invalid_argument("function: not string, closure, or array");
333 return nullptr;
336 Variant vm_call_user_func(CVarRef function, CArrRef params,
337 bool forwarding /* = false */) {
338 ObjectData* obj = nullptr;
339 HPHP::Class* cls = nullptr;
340 HPHP::Transl::CallerFrame cf;
341 StringData* invName = nullptr;
342 const HPHP::Func* f = vm_decode_function(function, cf(), forwarding,
343 obj, cls, invName);
344 if (f == nullptr) {
345 return uninit_null();
347 Variant ret;
348 g_vmContext->invokeFunc((TypedValue*)&ret, f, params, obj, cls,
349 nullptr, invName);
350 return ret;
353 Variant invoke(CStrRef function, CArrRef params, strhash_t hash /* = -1 */,
354 bool tryInterp /* = true */, bool fatal /* = true */) {
355 Func* func = Unit::loadFunc(function.get());
356 if (func) {
357 Variant ret;
358 g_vmContext->invokeFunc(ret.asTypedValue(), func, params);
359 return ret;
361 return invoke_failed(function.c_str(), params, fatal);
364 Variant invoke(const char *function, CArrRef params, strhash_t hash /* = -1*/,
365 bool tryInterp /* = true */, bool fatal /* = true */) {
366 String funcName(function, CopyString);
367 return invoke(funcName, params, hash, tryInterp, fatal);
370 Variant invoke_static_method(CStrRef s, CStrRef method, CArrRef params,
371 bool fatal /* = true */) {
372 HPHP::Class* class_ = Unit::lookupClass(s.get());
373 if (class_ == nullptr) {
374 o_invoke_failed(s.data(), method.data(), fatal);
375 return uninit_null();
377 const HPHP::Func* f = class_->lookupMethod(method.get());
378 if (f == nullptr || !(f->attrs() & AttrStatic)) {
379 o_invoke_failed(s.data(), method.data(), fatal);
380 return uninit_null();
382 Variant ret;
383 g_vmContext->invokeFunc((TypedValue*)&ret, f, params, nullptr, class_);
384 return ret;
387 Variant invoke_failed(CVarRef func, CArrRef params,
388 bool fatal /* = true */) {
389 if (func.isObject()) {
390 return o_invoke_failed(
391 func.objectForCall()->o_getClassName().c_str(),
392 "__invoke", fatal);
393 } else {
394 return invoke_failed(func.toString().c_str(), params, fatal);
398 Variant invoke_failed(const char *func, CArrRef params,
399 bool fatal /* = true */) {
400 if (fatal) {
401 throw InvalidFunctionCallException(func);
402 } else {
403 raise_warning("call_user_func to non-existent function %s", func);
404 return false;
408 Variant o_invoke_failed(const char *cls, const char *meth,
409 bool fatal /* = true */) {
410 if (fatal) {
411 string msg = "Unknown method ";
412 msg += cls;
413 msg += "::";
414 msg += meth;
415 throw FatalErrorException(msg.c_str());
416 } else {
417 raise_warning("call_user_func to non-existent method %s::%s", cls, meth);
418 return false;
422 void NEVER_INLINE raise_null_object_prop() {
423 raise_notice("Trying to get property of non-object");
426 void NEVER_INLINE throw_null_get_object_prop() {
427 raise_error("Trying to get property of non-object");
430 void NEVER_INLINE throw_null_object_prop() {
431 raise_error("Trying to set property of non-object");
434 void NEVER_INLINE throw_invalid_property_name(CStrRef name) {
435 if (!name.size()) {
436 throw EmptyObjectPropertyException();
437 } else {
438 throw NullStartObjectPropertyException();
442 void throw_instance_method_fatal(const char *name) {
443 if (!strstr(name, "::__destruct")) {
444 raise_error("Non-static method %s() cannot be called statically", name);
448 void throw_iterator_not_valid() {
449 Object e(SystemLib::AllocInvalidOperationExceptionObject(
450 "Iterator is not valid"));
451 throw e;
454 void throw_collection_modified() {
455 Object e(SystemLib::AllocInvalidOperationExceptionObject(
456 "Collection was modified during iteration"));
457 throw e;
460 void throw_collection_property_exception() {
461 Object e(SystemLib::AllocInvalidOperationExceptionObject(
462 "Cannot access a property on a collection"));
463 throw e;
466 void throw_collection_compare_exception() {
467 static const string msg(
468 "Cannot use relational comparison operators (<, <=, >, >=) to compare "
469 "a collection with an integer, double, string, array, or object");
470 if (RuntimeOption::StrictCollections) {
471 Object e(SystemLib::AllocRuntimeExceptionObject(msg));
472 throw e;
474 raise_warning(msg);
477 // TODO
478 void check_collection_compare(ObjectData* obj) {
479 if (obj && obj->isCollection()) throw_collection_compare_exception();
482 void check_collection_compare(ObjectData* obj1, ObjectData* obj2) {
483 if (obj1 && obj2 && (obj1->isCollection() || obj2->isCollection())) {
484 throw_collection_compare_exception();
488 void check_collection_cast_to_array() {
489 if (RuntimeOption::WarnOnCollectionToArray) {
490 raise_warning("Casting a collection to an array is an expensive operation "
491 "and should be avoided where possible. To convert a "
492 "collection to an array without raising a warning, use the "
493 "toArray() method.");
497 Object create_object_only(CStrRef s) {
498 return g_vmContext->createObjectOnly(s.get());
501 Object create_object(CStrRef s, CArrRef params, bool init /* = true */) {
502 return g_vmContext->createObject(s.get(), params, init);
506 * This function is used when another thread is segfaulting---we just
507 * want to wait forever to give it a chance to write a stacktrace file
508 * (and maybe a core file).
510 void pause_forever() {
511 for (;;) sleep(300);
514 ssize_t check_request_surprise(ThreadInfo *info) {
515 RequestInjectionData &p = info->m_reqInjectionData;
516 bool do_timedout, do_memExceeded, do_signaled;
518 ssize_t flags = p.fetchAndClearFlags();
519 do_timedout = (flags & RequestInjectionData::TimedOutFlag) &&
520 !p.getDebugger();
521 do_memExceeded = (flags & RequestInjectionData::MemExceededFlag);
522 do_signaled = (flags & RequestInjectionData::SignaledFlag);
524 // Start with any pending exception that might be on the thread.
525 Exception* pendingException = info->m_pendingException;
526 info->m_pendingException = nullptr;
528 if (do_timedout && !pendingException) {
529 pendingException = generate_request_timeout_exception();
531 if (do_memExceeded && !pendingException) {
532 pendingException = generate_memory_exceeded_exception();
534 if (do_signaled) f_pcntl_signal_dispatch();
536 if (pendingException) {
537 pendingException->throwException();
539 return flags;
542 void throw_missing_arguments_nr(const char *fn, int expected, int got,
543 int level /* = 0 */) {
544 if (level == 2 || RuntimeOption::ThrowMissingArguments) {
545 if (expected == 1) {
546 raise_error(Strings::MISSING_ARGUMENT, fn, got);
547 } else {
548 raise_error(Strings::MISSING_ARGUMENTS, fn, expected, got);
550 } else {
551 if (expected == 1) {
552 raise_warning(Strings::MISSING_ARGUMENT, fn, got);
553 } else {
554 raise_warning(Strings::MISSING_ARGUMENTS, fn, expected, got);
559 void throw_toomany_arguments_nr(const char *fn, int num, int level /* = 0 */) {
560 if (level == 2 || RuntimeOption::ThrowTooManyArguments) {
561 raise_error("Too many arguments for %s(), expected %d", fn, num);
562 } else if (level == 1 || RuntimeOption::WarnTooManyArguments) {
563 raise_warning("Too many arguments for %s(), expected %d", fn, num);
567 void throw_wrong_arguments_nr(const char *fn, int count, int cmin, int cmax,
568 int level /* = 0 */) {
569 if (cmin >= 0 && count < cmin) {
570 throw_missing_arguments_nr(fn, cmin, count, level);
571 return;
573 if (cmax >= 0 && count > cmax) {
574 throw_toomany_arguments_nr(fn, cmax, level);
575 return;
577 assert(false);
580 void throw_bad_type_exception(const char *fmt, ...) {
581 va_list ap;
582 va_start(ap, fmt);
583 string msg;
584 Util::string_vsnprintf(msg, fmt, ap);
585 va_end(ap);
587 if (RuntimeOption::ThrowBadTypeExceptions) {
588 throw InvalidOperandException(msg.c_str());
591 raise_warning("Invalid operand type was used: %s", msg.c_str());
594 void throw_bad_array_exception() {
595 const char* fn = "(unknown)";
596 ActRec *ar = g_vmContext->getStackFrame();
597 if (ar) {
598 fn = ar->m_func->name()->data();
600 throw_bad_type_exception("%s expects array(s)", fn);
603 void throw_invalid_argument(const char *fmt, ...) {
604 va_list ap;
605 va_start(ap, fmt);
606 string msg;
607 Util::string_vsnprintf(msg, fmt, ap);
608 va_end(ap);
610 if (RuntimeOption::ThrowInvalidArguments) {
611 throw InvalidArgumentException(msg.c_str());
614 raise_warning("Invalid argument: %s", msg.c_str());
617 Variant throw_fatal_unset_static_property(const char *s, const char *prop) {
618 raise_error("Attempt to unset static property %s::$%s", s, prop);
619 return uninit_null();
622 void throw_infinite_recursion_exception() {
623 if (!RuntimeOption::NoInfiniteRecursionDetection) {
624 // Reset profiler otherwise it might recurse further causing segfault
625 DECLARE_THREAD_INFO
626 info->m_profiler = nullptr;
627 throw UncatchableException("infinite recursion detected");
630 Exception* generate_request_timeout_exception() {
631 Exception* ret = nullptr;
632 ThreadInfo *info = ThreadInfo::s_threadInfo.getNoCheck();
633 RequestInjectionData &data = info->m_reqInjectionData;
635 bool cli = RuntimeOption::ClientExecutionMode();
636 std::string exceptionMsg = cli ?
637 "Maximum execution time of " :
638 "entire web request took longer than ";
639 exceptionMsg += folly::to<std::string>(data.getTimeout());
640 exceptionMsg += cli ? " seconds exceeded" : " seconds and timed out";
641 ArrayHolder exceptionStack;
642 if (RuntimeOption::InjectedStackTrace) {
643 exceptionStack = g_vmContext->debugBacktrace(false, true, true).get();
645 ret = new FatalErrorException(exceptionMsg, exceptionStack.get());
647 return ret;
650 Exception* generate_memory_exceeded_exception() {
651 ArrayHolder exceptionStack;
652 if (RuntimeOption::InjectedStackTrace) {
653 exceptionStack = g_vmContext->debugBacktrace(false, true, true).get();
655 return new FatalErrorException(
656 "request has exceeded memory limit", exceptionStack.get());
659 void throw_call_non_object() {
660 throw_call_non_object(nullptr);
663 void throw_call_non_object(const char *methodName) {
664 std::string msg;
666 if (methodName == nullptr) {
667 msg = "Call to a member function on a non-object";
668 } else {
669 Util::string_printf(msg,
670 "Call to a member function %s() on a non-object",
671 methodName);
674 if (RuntimeOption::ThrowExceptionOnBadMethodCall) {
675 Object e(SystemLib::AllocBadMethodCallExceptionObject(String(msg)));
676 throw e;
679 throw FatalErrorException(msg.c_str());
682 String f_serialize(CVarRef value) {
683 switch (value.getType()) {
684 case KindOfUninit:
685 case KindOfNull:
686 return "N;";
687 case KindOfBoolean:
688 return value.getBoolean() ? "b:1;" : "b:0;";
689 case KindOfInt64: {
690 StringBuffer sb;
691 sb.append("i:");
692 sb.append(value.getInt64());
693 sb.append(';');
694 return sb.detach();
696 case KindOfStaticString:
697 case KindOfString: {
698 StringData *str = value.getStringData();
699 StringBuffer sb;
700 sb.append("s:");
701 sb.append(str->size());
702 sb.append(":\"");
703 sb.append(str->data(), str->size());
704 sb.append("\";");
705 return sb.detach();
707 case KindOfArray: {
708 ArrayData *arr = value.getArrayData();
709 if (arr->empty()) return "a:0:{}";
710 // fall-through
712 case KindOfObject:
713 case KindOfResource:
714 case KindOfDouble: {
715 VariableSerializer vs(VariableSerializer::Type::Serialize);
716 return vs.serialize(value, true);
718 default:
719 assert(false);
720 break;
722 return "";
725 Variant unserialize_ex(const char* str, int len,
726 VariableUnserializer::Type type,
727 CArrRef class_whitelist /* = null_array */) {
728 if (str == nullptr || len <= 0) {
729 return false;
732 VariableUnserializer vu(str, len, type, false, class_whitelist);
733 Variant v;
734 try {
735 v = vu.unserialize();
736 } catch (FatalErrorException &e) {
737 throw;
738 } catch (Exception &e) {
739 raise_notice("Unable to unserialize: [%s]. %s.", str,
740 e.getMessage().c_str());
741 return false;
743 return v;
746 Variant unserialize_ex(CStrRef str,
747 VariableUnserializer::Type type,
748 CArrRef class_whitelist /* = null_array */) {
749 return unserialize_ex(str.data(), str.size(), type, class_whitelist);
752 String concat3(CStrRef s1, CStrRef s2, CStrRef s3) {
753 StringSlice r1 = s1.slice();
754 StringSlice r2 = s2.slice();
755 StringSlice r3 = s3.slice();
756 int len = r1.len + r2.len + r3.len;
757 StringData* str = NEW(StringData)(len);
758 MutableSlice r = str->mutableSlice();
759 memcpy(r.ptr, r1.ptr, r1.len);
760 memcpy(r.ptr + r1.len, r2.ptr, r2.len);
761 memcpy(r.ptr + r1.len + r2.len, r3.ptr, r3.len);
762 str->setSize(len);
763 return str;
766 String concat4(CStrRef s1, CStrRef s2, CStrRef s3, CStrRef s4) {
767 StringSlice r1 = s1.slice();
768 StringSlice r2 = s2.slice();
769 StringSlice r3 = s3.slice();
770 StringSlice r4 = s4.slice();
771 int len = r1.len + r2.len + r3.len + r4.len;
772 StringData* str = NEW(StringData)(len);
773 MutableSlice r = str->mutableSlice();
774 memcpy(r.ptr, r1.ptr, r1.len);
775 memcpy(r.ptr + r1.len, r2.ptr, r2.len);
776 memcpy(r.ptr + r1.len + r2.len, r3.ptr, r3.len);
777 memcpy(r.ptr + r1.len + r2.len + r3.len, r4.ptr, r4.len);
778 str->setSize(len);
779 return str;
782 bool interface_supports_array(const StringData* s) {
783 return (s->isame(s_Traversable.get()) ||
784 s->isame(s_KeyedTraversable.get()) ||
785 s->isame(s_Indexish.get()));
788 bool interface_supports_array(const std::string& n) {
789 const char* s = n.c_str();
790 return ((n.size() == 11 && !strcasecmp(s, "Traversable")) ||
791 (n.size() == 16 && !strcasecmp(s, "KeyedTraversable")) ||
792 (n.size() == 8 && !strcasecmp(s, "Indexish")));
795 Variant include_impl_invoke(CStrRef file, bool once, const char *currentDir) {
796 if (file[0] == '/') {
797 if (RuntimeOption::SandboxMode || !RuntimeOption::AlwaysUseRelativePath) {
798 try {
799 return invoke_file(file, once, currentDir);
800 } catch(PhpFileDoesNotExistException &e) {}
803 String rel_path(Util::relativePath(RuntimeOption::SourceRoot,
804 string(file.data())));
806 // Don't try/catch - We want the exception to be passed along
807 return invoke_file(rel_path, once, currentDir);
808 } else {
809 // Don't try/catch - We want the exception to be passed along
810 return invoke_file(file, once, currentDir);
814 Variant invoke_file(CStrRef s, bool once, const char *currentDir) {
815 Variant r;
816 if (invoke_file_impl(r, s, once, currentDir)) {
817 return r;
819 return throw_missing_file(s.c_str());
822 bool invoke_file_impl(Variant &res, CStrRef path, bool once,
823 const char *currentDir) {
824 bool initial;
825 HPHP::Eval::PhpFile* efile =
826 g_vmContext->lookupPhpFile(path.get(), currentDir, &initial);
827 HPHP::Unit* u = nullptr;
828 if (efile) u = efile->unit();
829 if (u == nullptr) {
830 return false;
832 if (!once || initial) {
833 g_vmContext->invokeUnit((TypedValue*)(&res), u);
835 return true;
839 * Used by include_impl. resolve_include() needs some way of checking the
840 * existence of a file path, which for hphpc means attempting to invoke it.
841 * This struct carries some context information needed for the invocation, as
842 * well as a place for the return value of invoking the file.
844 struct IncludeImplInvokeContext {
845 bool once;
846 const char* currentDir;
848 Variant returnValue;
851 static bool include_impl_invoke_context(CStrRef file, void* ctx) {
852 struct IncludeImplInvokeContext* context = (IncludeImplInvokeContext*)ctx;
853 bool invoked_file = false;
854 try {
855 context->returnValue = include_impl_invoke(file, context->once,
856 context->currentDir);
857 invoked_file = true;
858 } catch (PhpFileDoesNotExistException& e) {
859 context->returnValue = false;
861 return invoked_file;
865 * tryFile is a pointer to a function that resolve_include() will use to
866 * determine if a path references a real file. ctx is a pointer to some context
867 * information that will be passed through to tryFile. (It's a hacky closure)
869 String resolve_include(CStrRef file, const char* currentDir,
870 bool (*tryFile)(CStrRef file, void*), void* ctx) {
871 const char* c_file = file->data();
873 if (!File::IsPlainFilePath(file)) {
874 // URIs don't have an include path
875 if (tryFile(file, ctx)) {
876 return file;
879 } else if (c_file[0] == '/') {
880 String can_path(Util::canonicalize(file.c_str(), file.size()),
881 AttachString);
883 if (tryFile(can_path, ctx)) {
884 return can_path;
887 } else if ((c_file[0] == '.' && (c_file[1] == '/' || (
888 c_file[1] == '.' && c_file[2] == '/')))) {
890 String path(String(g_context->getCwd() + "/" + file));
891 String can_path(Util::canonicalize(path.c_str(), path.size()),
892 AttachString);
894 if (tryFile(can_path, ctx)) {
895 return can_path;
898 } else {
899 Array includePaths = g_context->getIncludePathArray();
900 unsigned int path_count = includePaths.size();
902 for (int i = 0; i < (int)path_count; i++) {
903 String path("");
904 String includePath = includePaths[i].toString();
906 if (includePath[0] != '/') {
907 path += (g_context->getCwd() + "/");
910 path += includePath;
912 if (path[path.size() - 1] != '/') {
913 path += "/";
916 path += file;
917 String can_path(Util::canonicalize(path.c_str(), path.size()),
918 AttachString);
920 if (tryFile(can_path, ctx)) {
921 return can_path;
925 if (currentDir[0] == '/') {
926 String path(currentDir);
927 path += "/";
928 path += file;
929 String can_path(Util::canonicalize(path.c_str(), path.size()),
930 AttachString);
932 if (tryFile(can_path, ctx)) {
933 return can_path;
935 } else {
936 String path(g_context->getCwd() + "/" + currentDir + file);
937 String can_path(Util::canonicalize(path.c_str(), path.size()),
938 AttachString);
940 if (tryFile(can_path, ctx)) {
941 return can_path;
946 return String();
949 static Variant include_impl(CStrRef file, bool once,
950 const char *currentDir, bool required,
951 bool raiseNotice) {
952 struct IncludeImplInvokeContext ctx = {once, currentDir};
953 String can_path = resolve_include(file, currentDir,
954 include_impl_invoke_context, (void*)&ctx);
956 if (can_path.isNull()) {
957 // Failure
958 if (raiseNotice) {
959 raise_notice("Tried to invoke %s but file not found.", file->data());
961 if (required) {
962 String ms = "Required file that does not exist: ";
963 ms += file;
964 throw FatalErrorException(ms.data());
966 return false;
969 return ctx.returnValue;
972 Variant include(CStrRef file, bool once /* = false */,
973 const char *currentDir /* = NULL */,
974 bool raiseNotice /*= true*/) {
975 return include_impl(file, once, currentDir, false, raiseNotice);
978 Variant require(CStrRef file, bool once /* = false */,
979 const char *currentDir /* = NULL */,
980 bool raiseNotice /*= true*/) {
981 return include_impl(file, once, currentDir, true, raiseNotice);
984 ///////////////////////////////////////////////////////////////////////////////
985 // class Limits
987 IMPLEMENT_REQUEST_LOCAL(AutoloadHandler, AutoloadHandler::s_instance);
989 void AutoloadHandler::requestInit() {
990 m_running = false;
991 m_handlers.reset();
992 m_map.reset();
993 m_map_root.reset();
996 void AutoloadHandler::requestShutdown() {
997 m_handlers.reset();
998 m_map.reset();
999 m_map_root.reset();
1002 bool AutoloadHandler::setMap(CArrRef map, CStrRef root) {
1003 this->m_map = map;
1004 this->m_map_root = root;
1005 return true;
1008 class ClassExistsChecker {
1009 public:
1010 ClassExistsChecker() {}
1011 bool operator()(CStrRef name) const {
1012 return Unit::lookupClass(name.get()) != nullptr;
1016 class ConstantExistsChecker {
1017 public:
1018 bool operator()(CStrRef name) const {
1019 return Unit::lookupCns(name.get()) != nullptr;
1023 template <class T>
1024 AutoloadHandler::Result AutoloadHandler::loadFromMap(CStrRef name,
1025 CStrRef kind,
1026 bool toLower,
1027 const T &checkExists) {
1028 assert(!m_map.isNull());
1029 while (true) {
1030 CVarRef &type_map = m_map.get()->get(kind);
1031 auto const typeMapCell = type_map.asCell();
1032 if (typeMapCell->m_type != KindOfArray) return Failure;
1033 String canonicalName = toLower ? StringUtil::ToLower(name) : name;
1034 CVarRef &file = typeMapCell->m_data.parr->get(canonicalName);
1035 bool ok = false;
1036 if (file.isString()) {
1037 String fName = file.toCStrRef().get();
1038 if (fName.get()->data()[0] != '/') {
1039 if (!m_map_root.empty()) {
1040 fName = m_map_root + fName;
1043 try {
1044 Transl::VMRegAnchor _;
1045 bool initial;
1046 VMExecutionContext* ec = g_vmContext;
1047 Unit* u = ec->evalInclude(fName.get(), nullptr, &initial);
1048 if (u) {
1049 if (initial) {
1050 TypedValue retval;
1051 ec->invokeFunc(&retval, u->getMain(), null_array,
1052 nullptr, nullptr, nullptr, nullptr,
1053 ExecutionContext::InvokePseudoMain);
1054 tvRefcountedDecRef(&retval);
1056 ok = true;
1058 } catch (...) {}
1060 if (ok && checkExists(name)) {
1061 return Success;
1063 CVarRef &func = m_map.get()->get(s_failure);
1064 if (!isset(func)) return Failure;
1065 // can throw, otherwise
1066 // - true means the map was updated. try again
1067 // - false means we should stop applying autoloaders (only affects classes)
1068 // - anything else means keep going
1069 Variant action = vm_call_user_func(func, CREATE_VECTOR2(kind, name));
1070 auto const actionCell = action.asCell();
1071 if (actionCell->m_type == KindOfBoolean) {
1072 if (actionCell->m_data.num) continue;
1073 return StopAutoloading;
1075 return ContinueAutoloading;
1079 bool AutoloadHandler::autoloadFunc(StringData* name) {
1080 return !m_map.isNull() &&
1081 loadFromMap(name, s_function, true, function_exists) != Failure;
1084 bool AutoloadHandler::autoloadConstant(StringData* name) {
1085 return !m_map.isNull() &&
1086 loadFromMap(name, s_constant, false, ConstantExistsChecker()) != Failure;
1089 bool AutoloadHandler::autoloadType(CStrRef name) {
1090 return !m_map.isNull() &&
1091 loadFromMap(name, s_type, true,
1092 [] (CStrRef name) {
1093 return Unit::GetNamedEntity(name.get())->getCachedTypedef() != nullptr;
1095 ) != Failure;
1099 * invokeHandler returns true if any autoload handlers were executed,
1100 * false otherwise. When this function returns true, it is the caller's
1101 * responsibility to check if the given class or interface exists.
1103 bool AutoloadHandler::invokeHandler(CStrRef className,
1104 bool forceSplStack /* = false */) {
1105 if (!m_map.isNull()) {
1106 ClassExistsChecker ce;
1107 Result res = loadFromMap(className, s_class, true, ce);
1108 if (res == ContinueAutoloading) {
1109 if (ce(className)) return true;
1110 } else {
1111 if (res != Failure) return res == Success;
1114 Array params(ArrayInit(1, ArrayInit::vectorInit).set(className).create());
1115 bool l_running = m_running;
1116 m_running = true;
1117 if (m_handlers.isNull() && !forceSplStack) {
1118 if (function_exists(s___autoload)) {
1119 invoke(s___autoload, params, -1, true, false);
1120 m_running = l_running;
1121 return true;
1123 m_running = l_running;
1124 return false;
1126 if (m_handlers.isNull() || m_handlers->empty()) {
1127 m_running = l_running;
1128 return false;
1130 Object autoloadException;
1131 for (ArrayIter iter(m_handlers); iter; ++iter) {
1132 try {
1133 vm_call_user_func(iter.second(), params);
1134 } catch (Object& ex) {
1135 assert(ex.instanceof(SystemLib::s_ExceptionClass));
1136 if (autoloadException.isNull()) {
1137 autoloadException = ex;
1138 } else {
1139 Object cur = ex;
1140 Variant next = cur->o_get(s_previous, false, s_exception);
1141 while (next.isObject()) {
1142 cur = next.toObject();
1143 next = cur->o_get(s_previous, false, s_exception);
1145 cur->o_set(s_previous, autoloadException, s_exception);
1146 autoloadException = ex;
1149 if (Unit::lookupClass(className.get()) != nullptr) {
1150 break;
1153 m_running = l_running;
1154 if (!autoloadException.isNull()) {
1155 throw autoloadException;
1157 return true;
1160 bool AutoloadHandler::addHandler(CVarRef handler, bool prepend) {
1161 String name = getSignature(handler);
1162 if (name.isNull()) return false;
1164 if (m_handlers.isNull()) {
1165 m_handlers = Array::Create();
1168 if (!prepend) {
1169 // The following ensures that the handler is added at the end
1170 m_handlers.remove(name, true);
1171 m_handlers.add(name, handler, true);
1172 } else {
1173 // This adds the handler at the beginning
1174 m_handlers = CREATE_MAP1(name, handler) + m_handlers;
1176 return true;
1179 bool AutoloadHandler::isRunning() {
1180 return m_running;
1183 void AutoloadHandler::removeHandler(CVarRef handler) {
1184 String name = getSignature(handler);
1185 if (name.isNull()) return;
1186 m_handlers.remove(name, true);
1189 void AutoloadHandler::removeAllHandlers() {
1190 m_handlers.reset();
1193 String AutoloadHandler::getSignature(CVarRef handler) {
1194 Variant name;
1195 if (!f_is_callable(handler, false, ref(name))) {
1196 return null_string;
1198 String lName = StringUtil::ToLower(name.toString());
1199 if (handler.isArray()) {
1200 Variant first = handler.getArrayData()->get(int64_t(0));
1201 if (first.isObject()) {
1202 // Add the object address as part of the signature
1203 int64_t data = (int64_t)first.getObjectData();
1204 lName += String((const char *)&data, sizeof(data), CopyString);
1206 } else if (handler.isObject()) {
1207 // "lName" will just be "classname::__invoke",
1208 // add object address to differentiate the signature
1209 int64_t data = (int64_t)handler.getObjectData();
1210 lName += String((const char*)&data, sizeof(data), CopyString);
1212 return lName;
1215 bool function_exists(CStrRef function_name) {
1216 return HPHP::Unit::lookupFunc(function_name.get()) != nullptr;
1219 ///////////////////////////////////////////////////////////////////////////////
1220 // debugger and code coverage instrumentation
1222 void throw_exception(CObjRef e) {
1223 if (!e.instanceof(SystemLib::s_ExceptionClass)) {
1224 raise_error("Exceptions must be valid objects derived from the "
1225 "Exception base class");
1227 DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(e.get()));
1228 throw e;
1231 ///////////////////////////////////////////////////////////////////////////////