2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/autoload-handler.h"
20 #include "hphp/runtime/base/array-init.h"
21 #include "hphp/runtime/base/builtin-functions.h"
22 #include "hphp/runtime/base/req-containers.h"
23 #include "hphp/runtime/ext/string/ext_string.h"
24 #include "hphp/runtime/base/type-string.h"
25 #include "hphp/runtime/base/tv-refcount.h"
26 #include "hphp/runtime/base/container-functions.h"
27 #include "hphp/runtime/base/unit-cache.h"
28 #include "hphp/runtime/vm/unit.h"
29 #include "hphp/runtime/vm/unit-util.h"
30 #include "hphp/runtime/vm/vm-regs.h"
34 //////////////////////////////////////////////////////////////////////
38 //////////////////////////////////////////////////////////////////////
42 s_function("function"),
43 s_constant("constant"),
46 s_autoload("__autoload"),
47 s_exception("exception"),
49 s_previous("previous");
51 using CufIterPtr
= req::unique_ptr
<CufIter
>;
53 //////////////////////////////////////////////////////////////////////
55 Variant
invoke_for_autoload(const String
& function
, const Variant
& params
) {
56 Func
* func
= Unit::loadFunc(function
.get());
57 if (func
&& (isContainer(params
) || params
.isNull())) {
58 return Variant::attach(g_context
->invokeFunc(func
, params
));
60 raise_warning("call_user_func to non-existent function %s",
62 return Variant(false);
66 * Wraps calling an (autoload) PHP function from a CufIter.
68 Variant
vm_call_user_func_cufiter(const CufIter
& cufIter
,
69 const Array
& params
) {
70 ObjectData
* obj
= nullptr;
71 HPHP::Class
* cls
= nullptr;
72 StringData
* invName
= cufIter
.name();
73 const HPHP::Func
* f
= cufIter
.func();
75 if (uintptr_t(cufIter
.ctx()) & 1) {
76 cls
= (Class
*)(uintptr_t(cufIter
.ctx()) & ~1);
78 obj
= (ObjectData
*)cufIter
.ctx();
81 assertx(!obj
|| !cls
);
83 invName
->incRefCount();
85 return Variant::attach(
86 g_context
->invokeFunc(f
, params
, obj
, cls
, nullptr, invName
,
87 ExecutionContext::InvokeCuf
, false,
93 * Helper method from converting between a PHP function and a CufIter.
95 bool vm_decode_function_cufiter(const Variant
& function
,
96 CufIterPtr
& cufIter
) {
97 ObjectData
* obj
= nullptr;
99 StringData
* invName
= nullptr;
101 // Don't warn here, let the caller decide what to do if the func is nullptr.
102 const HPHP::Func
* func
= vm_decode_function(function
, GetCallerFrame(), false,
103 obj
, cls
, invName
, dynamic
,
104 DecodeFlags::NoWarn
);
105 if (func
== nullptr) {
109 cufIter
= req::make_unique
<CufIter
>();
110 cufIter
->setFunc(func
);
111 cufIter
->setName(invName
);
113 cufIter
->setCtx(obj
);
116 cufIter
->setCtx(cls
);
118 cufIter
->setDynamic(dynamic
);
122 //////////////////////////////////////////////////////////////////////
126 //////////////////////////////////////////////////////////////////////
128 IMPLEMENT_REQUEST_LOCAL(AutoloadHandler
, AutoloadHandler::s_instance
);
130 void AutoloadHandler::requestInit() {
131 assertx(m_map
.get() == nullptr);
132 assertx(m_map_root
.get() == nullptr);
133 assertx(m_loading
.get() == nullptr);
134 m_spl_stack_inited
= false;
135 new (&m_handlers
) req::deque
<HandlerBundle
>();
136 m_handlers_valid
= true;
139 void AutoloadHandler::requestShutdown() {
143 m_handlers_valid
= false;
144 // m_spl_stack_inited will be re-initialized by the next requestInit
145 // m_handlers will be re-initialized by the next requestInit
148 bool AutoloadHandler::setMap(const Array
& map
, const String
& root
) {
150 this->m_map_root
= root
;
155 struct FuncExistsChecker
{
156 const StringData
* m_name
;
157 mutable NamedEntity
* m_ne
;
158 explicit FuncExistsChecker(const StringData
* name
)
159 : m_name(name
), m_ne(nullptr) {}
160 bool operator()() const {
162 m_ne
= NamedEntity::get(m_name
, false);
167 auto f
= m_ne
->getCachedFunc();
168 return (f
!= nullptr) &&
169 (f
->builtinFuncPtr() != Native::unimplementedWrapper
);
172 struct ClassExistsChecker
{
173 const String
& m_name
;
174 mutable NamedEntity
* m_ne
;
175 explicit ClassExistsChecker(const String
& name
)
176 : m_name(name
), m_ne(nullptr) {}
177 bool operator()() const {
179 m_ne
= NamedEntity::get(m_name
.get(), false);
184 return m_ne
->getCachedClass() != nullptr;
187 struct ConstExistsChecker
{
188 const StringData
* m_name
;
189 explicit ConstExistsChecker(const StringData
* name
)
191 bool operator()() const {
192 return Unit::lookupCns(m_name
) != nullptr;
195 struct TypeExistsChecker
{
196 const String
& m_name
;
197 mutable NamedEntity
* m_ne
;
198 explicit TypeExistsChecker(const String
& name
)
199 : m_name(name
), m_ne(nullptr) {}
200 bool operator()() const {
202 m_ne
= NamedEntity::get(m_name
.get(), false);
207 return m_ne
->getCachedTypeAlias() != nullptr;
210 struct ClassOrTypeExistsChecker
{
211 const String
& m_name
;
212 mutable NamedEntity
* m_ne
;
213 explicit ClassOrTypeExistsChecker(const String
& name
)
214 : m_name(name
), m_ne(nullptr) {}
215 bool operator()() const {
217 m_ne
= NamedEntity::get(m_name
.get(), false);
222 return m_ne
->getCachedClass() != nullptr ||
223 m_ne
->getCachedTypeAlias() != nullptr;
233 AutoloadHandler::Result
234 AutoloadHandler::loadFromMapImpl(const String
& clsName
,
237 const T
&checkExists
,
239 assertx(!m_map
.isNull());
240 // Always normalize name before autoloading
241 const String
& name
= normalizeNS(clsName
);
242 auto const type_map
= m_map
.get()->get(kind
).unboxed();
243 if (!isArrayType(type_map
.type())) return Failure
;
244 String canonicalName
= toLower
? HHVM_FN(strtolower
)(name
) : name
;
245 auto const file
= type_map
.val().parr
->get(canonicalName
).unboxed();
247 if (isStringType(file
.type())) {
248 String fName
{file
.val().pstr
};
249 if (fName
.get()->data()[0] != '/') {
250 if (!m_map_root
.empty()) {
251 fName
= m_map_root
+ fName
;
257 auto const ec
= g_context
.getNoCheck();
258 auto const unit
= lookupUnit(fName
.get(), "", &initial
);
262 ec
->invokeFunc(unit
->getMain(nullptr), init_null_variant
,
263 nullptr, nullptr, nullptr, nullptr,
264 ExecutionContext::InvokePseudoMain
)
269 } catch (ExitException
&) {
271 } catch (ResourceExceededException
&) {
273 } catch (ExtendedException
& ee
) {
274 auto fileAndLine
= ee
.getFileAndLine();
275 err
= (fileAndLine
.first
.empty())
277 : folly::format("{} in {} on line {}",
278 ee
.getMessage(), fileAndLine
.first
,
279 fileAndLine
.second
).str();
280 } catch (Exception
& e
) {
281 err
= e
.getMessage();
282 } catch (Object
& e
) {
285 err
= String("Unknown Exception");
288 if (ok
&& checkExists()) {
295 AutoloadHandler::Result
296 AutoloadHandler::loadFromMap(const String
& clsName
,
299 const T
&checkExists
) {
301 Variant err
{Variant::NullInit()};
302 Result res
= loadFromMapImpl(clsName
, kind
, toLower
, checkExists
, err
);
303 if (res
== Success
) return Success
;
304 auto const func
= m_map
.get()->get(s_failure
);
305 if (isNullType(func
.unboxed().type())) return Failure
;
306 res
= invokeFailureCallback(tvAsCVarRef(func
.tv_ptr()), kind
, clsName
, err
);
307 if (checkExists()) return Success
;
308 if (res
== RetryAutoloading
) {
315 AutoloadHandler::Result
316 AutoloadHandler::invokeFailureCallback(const Variant
& func
, const String
& kind
,
317 const String
& name
, const Variant
& err
) {
318 // can throw, otherwise
319 // - true means the map was updated. try again
320 // - false means we should stop applying autoloaders (only affects classes)
321 // - anything else means keep going
322 Variant action
= vm_call_user_func(func
,
323 make_packed_array(kind
, name
, err
));
324 auto const actionCell
= action
.asCell();
325 if (actionCell
->m_type
== KindOfBoolean
) {
326 return actionCell
->m_data
.num
? RetryAutoloading
: StopAutoloading
;
328 return ContinueAutoloading
;
331 bool AutoloadHandler::autoloadFunc(StringData
* name
) {
332 return !m_map
.isNull() &&
333 loadFromMap(String
{stripInOutSuffix(name
)},
336 FuncExistsChecker(name
)) != Failure
;
339 bool AutoloadHandler::autoloadConstant(StringData
* name
) {
340 return !m_map
.isNull() &&
341 loadFromMap(String
{name
},
344 ConstExistsChecker(name
)) != Failure
;
347 bool AutoloadHandler::autoloadType(const String
& name
) {
348 return !m_map
.isNull() &&
349 loadFromMap(name
, s_type
, true, TypeExistsChecker(name
)) != Failure
;
354 * https://github.com/php/php-src/blob/PHP-5.6/Zend/zend_execute_API.c#L960
356 bool is_valid_class_name(folly::StringPiece className
) {
359 "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\177"
360 "\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220"
361 "\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241"
362 "\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262"
363 "\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303"
364 "\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324"
365 "\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345"
366 "\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366"
367 "\367\370\371\372\373\374\375\376\377\\"
368 ) == className
.size();
371 bool AutoloadHandler::autoloadClass(const String
& clsName
,
372 bool forceSplStack
/* = false */) {
373 if (clsName
.empty()) return false;
374 const String
& className
= normalizeNS(clsName
);
375 // Verify class name before passing it to __autoload()
376 if (!is_valid_class_name(className
.slice())) {
379 if (!m_map
.isNull()) {
380 ClassExistsChecker
ce(className
);
381 Result res
= loadFromMap(className
, s_class
, true, ce
);
382 if (res
== Success
|| ce()) return true;
383 if (res
== StopAutoloading
) return false;
385 return autoloadClassPHP5Impl(className
, forceSplStack
);
388 bool AutoloadHandler::autoloadClassPHP5Impl(const String
& className
,
389 bool forceSplStack
) {
390 // If we end up in a recursive autoload loop where we try to load the
391 // same class twice, just fail the load to match PHP5 as many frameworks
392 // rely on it unless we are forcing a restart (due to spl_autoload_call)
393 // in which case autoload is allowed to be reentrant.
394 if (!forceSplStack
) {
395 if (m_loading
.exists(className
)) { return false; }
396 m_loading
.add(className
, className
);
398 // We can still overflow the stack if there is a loop when using
399 // spl_autoload_call directly, but this behavior matches PHP5.
400 m_loading
.append(className
);
403 // Make sure state is cleaned up from this load; autoloading of arbitrary
404 // code below can throw
406 DEBUG_ONLY
auto const l_className
= m_loading
.pop().toString();
407 assertx(l_className
== className
);
410 Array params
= PackedArrayInit(1).append(className
).toArray();
411 if (!m_spl_stack_inited
&& !forceSplStack
) {
412 if (function_exists(s_autoload
)) {
413 invoke_for_autoload(s_autoload
, params
);
418 if (!m_spl_stack_inited
|| m_handlers
.empty()) {
421 Object autoloadException
;
422 for (const HandlerBundle
& hb
: m_handlers
) {
424 vm_call_user_func_cufiter(*hb
.m_cufIter
, params
);
425 } catch (Object
& ex
) {
426 assertx(ex
.instanceof(SystemLib::s_ThrowableClass
));
427 if (autoloadException
.isNull()) {
428 autoloadException
= ex
;
431 auto const ctx
= cur
->instanceof(SystemLib::s_ExceptionClass
)
434 Variant next
= cur
->o_get(s_previous
, false, ctx
);
435 while (next
.isObject()) {
436 cur
= next
.toObject();
437 next
= cur
->o_get(s_previous
, false, ctx
);
439 cur
->o_set(s_previous
, autoloadException
, ctx
);
440 autoloadException
= ex
;
443 if (Unit::lookupClass(className
.get()) != nullptr) {
447 if (!autoloadException
.isNull()) {
448 throw_object(autoloadException
);
454 AutoloadHandler::Result
455 AutoloadHandler::loadFromMapPartial(const String
& className
,
458 const T
&checkExists
,
460 Result res
= loadFromMapImpl(className
, kind
, toLower
, checkExists
, err
);
461 if (res
== Success
) {
464 assertx(res
== Failure
);
466 auto const func
= m_map
.get()->get(s_failure
);
467 if (!isNullType(func
.unboxed().type())) {
468 res
= invokeFailureCallback(tvAsCVarRef(func
.tv_ptr()),
469 kind
, className
, err
);
470 assertx(res
!= Failure
);
479 bool AutoloadHandler::autoloadClassOrType(const String
& clsName
) {
480 if (clsName
.empty()) return false;
481 const String
& className
= normalizeNS(clsName
);
482 if (!m_map
.isNull()) {
483 ClassOrTypeExistsChecker
cte(className
);
484 bool tryClass
= true, tryType
= true;
485 Result classRes
= RetryAutoloading
, typeRes
= RetryAutoloading
;
487 Variant classErr
{Variant::NullInit()};
489 // Try consulting the 'class' map first, but don't call the failure
490 // callback unless there was an uncaught exception or a fatal error
491 // during the include operation.
492 classRes
= loadFromMapPartial(className
, s_class
, true, cte
, classErr
);
493 if (classRes
== Success
) return true;
495 Variant typeErr
{Variant::NullInit()};
497 // Next, try consulting the 'type' map. Again, don't call the failure
498 // callback unless there was an uncaught exception or fatal error.
499 typeRes
= loadFromMapPartial(className
, s_type
, true, cte
, typeErr
);
500 if (typeRes
== Success
) return true;
502 auto const func
= Variant::wrap(m_map
.get()->get(s_failure
).tv());
503 // If we reach this point, then for each map either nothing was found
504 // or the file we included didn't define a class or type alias with the
505 // specified name, and the failure callback (if one exists) did not throw
506 // or raise a fatal error.
507 if (!func
.isNull()) {
508 // First, call the failure callback for 'class' if we didn't do so
510 if (classRes
== Failure
) {
512 classRes
= invokeFailureCallback(func
, s_class
, className
, classErr
);
513 // The failure callback may have defined a class or type alias for
514 // us, in which case we're done.
515 if (cte()) return true;
517 // Next, call the failure callback for 'type' if we didn't do so above
518 if (typeRes
== Failure
) {
520 typeRes
= invokeFailureCallback(func
, s_type
, className
, typeErr
);
521 // The failure callback may have defined a class or type alias for
522 // us, in which case we're done.
523 if (cte()) return true;
525 assertx(classRes
!= Failure
&& typeRes
!= Failure
);
526 tryClass
= (classRes
== RetryAutoloading
);
527 tryType
= (typeRes
== RetryAutoloading
);
528 // If the failure callback requested a retry for 'class' or 'type'
529 // or both, jump back to the top to try again.
530 if (tryClass
|| tryType
) {
533 if (classRes
== StopAutoloading
) {
534 // If the failure callback requested that we stop autoloading for
535 // 'class', then return false here so we don't fall through to the
536 // PHP5 autoload impl below.
540 // Break out of the while loop so that we can fall through to the
541 // to the call the the PHP5 autoload impl below.
545 return autoloadClassPHP5Impl(className
, false);
548 Array
AutoloadHandler::getHandlers() {
549 if (!m_spl_stack_inited
) {
553 PackedArrayInit
handlers(m_handlers
.size());
555 for (const HandlerBundle
& hb
: m_handlers
) {
556 CufIter
* cufIter
= hb
.m_cufIter
.get();
557 ObjectData
* obj
= nullptr;
558 HPHP::Class
* cls
= nullptr;
559 const HPHP::Func
* f
= cufIter
->func();
561 if (hb
.m_handler
.isObject()) {
562 handlers
.append(hb
.m_handler
);
563 } else if (cufIter
->ctx()) {
564 PackedArrayInit
callable(2);
565 if (uintptr_t(cufIter
->ctx()) & 1) {
566 cls
= (Class
*)(uintptr_t(cufIter
->ctx()) & ~1);
567 callable
.append(String(cls
->nameStr()));
569 obj
= (ObjectData
*)cufIter
->ctx();
570 callable
.append(Variant
{obj
});
572 callable
.append(String(f
->nameStr()));
573 handlers
.append(callable
.toArray());
575 handlers
.append(String(f
->nameStr()));
579 return handlers
.toArray();
582 bool AutoloadHandler::CompareBundles::operator()(
583 const HandlerBundle
& hb
) {
584 auto const& lhs
= *m_cufIter
;
585 auto const& rhs
= *hb
.m_cufIter
;
587 if (lhs
.ctx() != rhs
.ctx()) {
588 // We only consider ObjectData* for equality (not a Class*) so if either is
589 // an object these are not considered equal.
590 if (!(uintptr_t(lhs
.ctx()) & 1) || !(uintptr_t(rhs
.ctx()) & 1)) {
595 return lhs
.func() == rhs
.func();
598 bool AutoloadHandler::addHandler(const Variant
& handler
, bool prepend
) {
599 CufIterPtr cufIter
= nullptr;
600 if (!vm_decode_function_cufiter(handler
, cufIter
)) {
604 m_spl_stack_inited
= true;
606 // Zend doesn't modify the order of the list if the handler is already
608 auto const& compareBundles
= CompareBundles(cufIter
.get());
609 if (std::find_if(m_handlers
.begin(), m_handlers
.end(), compareBundles
) !=
615 m_handlers
.emplace_back(handler
, cufIter
);
617 m_handlers
.emplace_front(handler
, cufIter
);
623 bool AutoloadHandler::isRunning() {
624 return !m_loading
.empty();
627 void AutoloadHandler::removeHandler(const Variant
& handler
) {
628 CufIterPtr cufIter
= nullptr;
629 if (!vm_decode_function_cufiter(handler
, cufIter
)) {
633 // Use find_if instead of remove_if since we know there can only be one match
635 auto const& compareBundles
= CompareBundles(cufIter
.get());
636 auto it
= std::find_if(m_handlers
.begin(), m_handlers
.end(), compareBundles
);
637 if (it
!= m_handlers
.end()) {
638 m_handlers
.erase(it
);
642 void AutoloadHandler::removeAllHandlers() {
643 m_spl_stack_inited
= false;
648 //////////////////////////////////////////////////////////////////////