2 +----------------------------------------------------------------------+
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"
48 using namespace HPHP::MethodLookup
;
51 ///////////////////////////////////////////////////////////////////////////////
55 s_offsetExists("offsetExists"),
56 s___autoload("__autoload"),
58 s___callStatic("__callStatic"),
59 s___invoke("__invoke"),
60 s_exception("exception"),
61 s_previous("previous"),
66 s_function("function"),
67 s_constant("constant"),
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))) {
80 Variant elem0
= arr
.rvalAt(int64_t(0));
81 if (!elem0
.isString() && !elem0
.isObject()) {
84 Variant elem1
= arr
.rvalAt(int64_t(1));
85 if (!elem1
.isString()) {
92 vm_decode_function(CVarRef function
,
98 bool warn
/* = true */) {
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.
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("::");
116 (pos
!= 0 && pos
!= String::npos
&& pos
+ 2 < name
.size());
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
)) {
126 throw_invalid_argument("function: not a valid callback array");
130 Variant elem1
= arr
.rvalAt(int64_t(1));
131 name
= elem1
.toString();
132 pos
= name
.find("::");
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())) {
142 if (!nameContainsClass
) {
145 } else if (sclass
->isame(s_parent
.get())) {
146 if (ctx
&& ctx
->parent()) {
149 if (!nameContainsClass
) {
152 } else if (sclass
->isame(s_static
.get())) {
155 cls
= ar
->getThis()->getVMClass();
156 } else if (ar
->hasClass()) {
157 cls
= ar
->getClass();
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());
174 throw_invalid_argument("function: class not found");
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())) {
198 } else if (c
->isame(s_parent
.get())) {
201 } else if (ctx
&& ctx
->parent()) {
207 } else if (c
->isame(s_static
.get())) {
210 cc
= ar
->getThis()->getVMClass();
211 } else if (ar
->hasClass()) {
216 cc
= Unit::loadClass(c
.get());
220 throw_invalid_argument("function: class not found");
225 if (!cls
->classof(cc
)) {
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());
235 // If there is not a current instance, cc trumps cls.
241 HPHP::Func
* f
= HPHP::Unit::loadFunc(name
.get());
244 throw_invalid_argument("function: method '%s' not found",
249 assert(f
&& f
->preClass() == nullptr);
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_
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
)) {
266 cls
= obj
->getVMClass();
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
));
282 // We found __call or __callStatic!
283 // Stash the original name into invName.
284 invName
= name
.get();
285 invName
->incRefCount();
287 // Bail out if we couldn't find the method or __call
289 throw_invalid_argument("function: method '%s' not found",
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;
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
)) {
318 if (function
.isObject()) {
319 this_
= function
.asCObjRef().get();
321 const HPHP::Func
*f
= this_
->getVMClass()->lookupMethod(s___invoke
.get());
323 ((f
->attrs() & AttrStatic
) && !f
->isClosureBody())) {
324 // If __invoke is static, invoke it as such
325 cls
= this_
->getVMClass();
331 throw_invalid_argument("function: not string, closure, or array");
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
,
345 return uninit_null();
348 g_vmContext
->invokeFunc((TypedValue
*)&ret
, f
, params
, obj
, cls
,
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());
358 g_vmContext
->invokeFunc(ret
.asTypedValue(), func
, params
);
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();
383 g_vmContext
->invokeFunc((TypedValue
*)&ret
, f
, params
, nullptr, class_
);
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(),
394 return invoke_failed(func
.toString().c_str(), params
, fatal
);
398 Variant
invoke_failed(const char *func
, CArrRef params
,
399 bool fatal
/* = true */) {
401 throw InvalidFunctionCallException(func
);
403 raise_warning("call_user_func to non-existent function %s", func
);
408 Variant
o_invoke_failed(const char *cls
, const char *meth
,
409 bool fatal
/* = true */) {
411 string msg
= "Unknown method ";
415 throw FatalErrorException(msg
.c_str());
417 raise_warning("call_user_func to non-existent method %s::%s", cls
, meth
);
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
) {
436 throw EmptyObjectPropertyException();
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"));
454 void throw_collection_modified() {
455 Object
e(SystemLib::AllocInvalidOperationExceptionObject(
456 "Collection was modified during iteration"));
460 void throw_collection_property_exception() {
461 Object
e(SystemLib::AllocInvalidOperationExceptionObject(
462 "Cannot access a property on a collection"));
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
));
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() {
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
) &&
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();
542 void throw_missing_arguments_nr(const char *fn
, int expected
, int got
,
543 int level
/* = 0 */) {
544 if (level
== 2 || RuntimeOption::ThrowMissingArguments
) {
546 raise_error(Strings::MISSING_ARGUMENT
, fn
, got
);
548 raise_error(Strings::MISSING_ARGUMENTS
, fn
, expected
, got
);
552 raise_warning(Strings::MISSING_ARGUMENT
, fn
, got
);
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
);
573 if (cmax
>= 0 && count
> cmax
) {
574 throw_toomany_arguments_nr(fn
, cmax
, level
);
580 void throw_bad_type_exception(const char *fmt
, ...) {
584 Util::string_vsnprintf(msg
, fmt
, 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();
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
, ...) {
607 Util::string_vsnprintf(msg
, fmt
, 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
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());
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
) {
666 if (methodName
== nullptr) {
667 msg
= "Call to a member function on a non-object";
669 Util::string_printf(msg
,
670 "Call to a member function %s() on a non-object",
674 if (RuntimeOption::ThrowExceptionOnBadMethodCall
) {
675 Object
e(SystemLib::AllocBadMethodCallExceptionObject(String(msg
)));
679 throw FatalErrorException(msg
.c_str());
682 String
f_serialize(CVarRef value
) {
683 switch (value
.getType()) {
688 return value
.getBoolean() ? "b:1;" : "b:0;";
692 sb
.append(value
.getInt64());
696 case KindOfStaticString
:
698 StringData
*str
= value
.getStringData();
701 sb
.append(str
->size());
703 sb
.append(str
->data(), str
->size());
708 ArrayData
*arr
= value
.getArrayData();
709 if (arr
->empty()) return "a:0:{}";
715 VariableSerializer
vs(VariableSerializer::Type::Serialize
);
716 return vs
.serialize(value
, true);
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) {
732 VariableUnserializer
vu(str
, len
, type
, false, class_whitelist
);
735 v
= vu
.unserialize();
736 } catch (FatalErrorException
&e
) {
738 } catch (Exception
&e
) {
739 raise_notice("Unable to unserialize: [%s]. %s.", str
,
740 e
.getMessage().c_str());
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
);
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
);
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
) {
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
);
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
) {
816 if (invoke_file_impl(r
, s
, once
, currentDir
)) {
819 return throw_missing_file(s
.c_str());
822 bool invoke_file_impl(Variant
&res
, CStrRef path
, bool once
,
823 const char *currentDir
) {
825 HPHP::Eval::PhpFile
* efile
=
826 g_vmContext
->lookupPhpFile(path
.get(), currentDir
, &initial
);
827 HPHP::Unit
* u
= nullptr;
828 if (efile
) u
= efile
->unit();
832 if (!once
|| initial
) {
833 g_vmContext
->invokeUnit((TypedValue
*)(&res
), u
);
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
{
846 const char* currentDir
;
851 static bool include_impl_invoke_context(CStrRef file
, void* ctx
) {
852 struct IncludeImplInvokeContext
* context
= (IncludeImplInvokeContext
*)ctx
;
853 bool invoked_file
= false;
855 context
->returnValue
= include_impl_invoke(file
, context
->once
,
856 context
->currentDir
);
858 } catch (PhpFileDoesNotExistException
& e
) {
859 context
->returnValue
= false;
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
)) {
879 } else if (c_file
[0] == '/') {
880 String
can_path(Util::canonicalize(file
.c_str(), file
.size()),
883 if (tryFile(can_path
, ctx
)) {
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()),
894 if (tryFile(can_path
, ctx
)) {
899 Array includePaths
= g_context
->getIncludePathArray();
900 unsigned int path_count
= includePaths
.size();
902 for (int i
= 0; i
< (int)path_count
; i
++) {
904 String includePath
= includePaths
[i
].toString();
906 if (includePath
[0] != '/') {
907 path
+= (g_context
->getCwd() + "/");
912 if (path
[path
.size() - 1] != '/') {
917 String
can_path(Util::canonicalize(path
.c_str(), path
.size()),
920 if (tryFile(can_path
, ctx
)) {
925 if (currentDir
[0] == '/') {
926 String
path(currentDir
);
929 String
can_path(Util::canonicalize(path
.c_str(), path
.size()),
932 if (tryFile(can_path
, ctx
)) {
936 String
path(g_context
->getCwd() + "/" + currentDir
+ file
);
937 String
can_path(Util::canonicalize(path
.c_str(), path
.size()),
940 if (tryFile(can_path
, ctx
)) {
949 static Variant
include_impl(CStrRef file
, bool once
,
950 const char *currentDir
, bool required
,
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()) {
959 raise_notice("Tried to invoke %s but file not found.", file
->data());
962 String ms
= "Required file that does not exist: ";
964 throw FatalErrorException(ms
.data());
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 ///////////////////////////////////////////////////////////////////////////////
987 IMPLEMENT_REQUEST_LOCAL(AutoloadHandler
, AutoloadHandler::s_instance
);
989 void AutoloadHandler::requestInit() {
996 void AutoloadHandler::requestShutdown() {
1002 bool AutoloadHandler::setMap(CArrRef map
, CStrRef root
) {
1004 this->m_map_root
= root
;
1008 class ClassExistsChecker
{
1010 ClassExistsChecker() {}
1011 bool operator()(CStrRef name
) const {
1012 return Unit::lookupClass(name
.get()) != nullptr;
1016 class ConstantExistsChecker
{
1018 bool operator()(CStrRef name
) const {
1019 return Unit::lookupCns(name
.get()) != nullptr;
1024 AutoloadHandler::Result
AutoloadHandler::loadFromMap(CStrRef name
,
1027 const T
&checkExists
) {
1028 assert(!m_map
.isNull());
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
);
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
;
1044 Transl::VMRegAnchor _
;
1046 VMExecutionContext
* ec
= g_vmContext
;
1047 Unit
* u
= ec
->evalInclude(fName
.get(), nullptr, &initial
);
1051 ec
->invokeFunc(&retval
, u
->getMain(), null_array
,
1052 nullptr, nullptr, nullptr, nullptr,
1053 ExecutionContext::InvokePseudoMain
);
1054 tvRefcountedDecRef(&retval
);
1060 if (ok
&& checkExists(name
)) {
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,
1093 return Unit::GetNamedEntity(name
.get())->getCachedTypedef() != nullptr;
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;
1111 if (res
!= Failure
) return res
== Success
;
1114 Array
params(ArrayInit(1, ArrayInit::vectorInit
).set(className
).create());
1115 bool l_running
= m_running
;
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
;
1123 m_running
= l_running
;
1126 if (m_handlers
.isNull() || m_handlers
->empty()) {
1127 m_running
= l_running
;
1130 Object autoloadException
;
1131 for (ArrayIter
iter(m_handlers
); iter
; ++iter
) {
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
;
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) {
1153 m_running
= l_running
;
1154 if (!autoloadException
.isNull()) {
1155 throw autoloadException
;
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();
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);
1173 // This adds the handler at the beginning
1174 m_handlers
= CREATE_MAP1(name
, handler
) + m_handlers
;
1179 bool AutoloadHandler::isRunning() {
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() {
1193 String
AutoloadHandler::getSignature(CVarRef handler
) {
1195 if (!f_is_callable(handler
, false, ref(name
))) {
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
);
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()));
1231 ///////////////////////////////////////////////////////////////////////////////