Add support for HHBC ops with 5 immediates
[hiphop-php.git] / hphp / runtime / base / autoload-handler.cpp
blob25e6c38300fb32ee7bfa8cde4a83b08e762c36a4
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
18 #include <algorithm>
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"
32 namespace HPHP {
34 //////////////////////////////////////////////////////////////////////
36 namespace {
38 //////////////////////////////////////////////////////////////////////
40 const StaticString
41 s_class("class"),
42 s_function("function"),
43 s_constant("constant"),
44 s_type("type"),
45 s_failure("failure"),
46 s_autoload("__autoload"),
47 s_exception("exception"),
48 s_error("Error"),
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",
61 function.c_str());
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();
74 if (cufIter.ctx()) {
75 if (uintptr_t(cufIter.ctx()) & 1) {
76 cls = (Class*)(uintptr_t(cufIter.ctx()) & ~1);
77 } else {
78 obj = (ObjectData*)cufIter.ctx();
81 assertx(!obj || !cls);
82 if (invName) {
83 invName->incRefCount();
85 return Variant::attach(
86 g_context->invokeFunc(f, params, obj, cls, nullptr, invName,
87 ExecutionContext::InvokeCuf, false,
88 cufIter.dynamic())
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;
98 Class* cls = nullptr;
99 StringData* invName = nullptr;
100 bool dynamic;
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) {
106 return false;
109 cufIter = req::make_unique<CufIter>();
110 cufIter->setFunc(func);
111 cufIter->setName(invName);
112 if (obj) {
113 cufIter->setCtx(obj);
114 obj->incRefCount();
115 } else {
116 cufIter->setCtx(cls);
118 cufIter->setDynamic(dynamic);
119 return true;
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() {
140 m_map.reset();
141 m_map_root.reset();
142 m_loading.reset();
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) {
149 this->m_map = map;
150 this->m_map_root = root;
151 return true;
154 namespace {
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 {
161 if (!m_ne) {
162 m_ne = NamedEntity::get(m_name, false);
163 if (!m_ne) {
164 return 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 {
178 if (!m_ne) {
179 m_ne = NamedEntity::get(m_name.get(), false);
180 if (!m_ne) {
181 return false;
184 return m_ne->getCachedClass() != nullptr;
187 struct ConstExistsChecker {
188 const StringData* m_name;
189 explicit ConstExistsChecker(const StringData* name)
190 : m_name(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 {
201 if (!m_ne) {
202 m_ne = NamedEntity::get(m_name.get(), false);
203 if (!m_ne) {
204 return 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 {
216 if (!m_ne) {
217 m_ne = NamedEntity::get(m_name.get(), false);
218 if (!m_ne) {
219 return false;
222 return m_ne->getCachedClass() != nullptr ||
223 m_ne->getCachedTypeAlias() != nullptr;
228 const StaticString
229 s_file("file"),
230 s_line("line");
232 template <class T>
233 AutoloadHandler::Result
234 AutoloadHandler::loadFromMapImpl(const String& clsName,
235 const String& kind,
236 bool toLower,
237 const T &checkExists,
238 Variant& err) {
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();
246 bool ok = false;
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;
254 try {
255 VMRegAnchor _;
256 bool initial;
257 auto const ec = g_context.getNoCheck();
258 auto const unit = lookupUnit(fName.get(), "", &initial);
259 if (unit) {
260 if (initial) {
261 tvDecRefGen(
262 ec->invokeFunc(unit->getMain(nullptr), init_null_variant,
263 nullptr, nullptr, nullptr, nullptr,
264 ExecutionContext::InvokePseudoMain)
267 ok = true;
269 } catch (ExitException&) {
270 throw;
271 } catch (ResourceExceededException&) {
272 throw;
273 } catch (ExtendedException& ee) {
274 auto fileAndLine = ee.getFileAndLine();
275 err = (fileAndLine.first.empty())
276 ? ee.getMessage()
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) {
283 err = e;
284 } catch (...) {
285 err = String("Unknown Exception");
288 if (ok && checkExists()) {
289 return Success;
291 return Failure;
294 template <class T>
295 AutoloadHandler::Result
296 AutoloadHandler::loadFromMap(const String& clsName,
297 const String& kind,
298 bool toLower,
299 const T &checkExists) {
300 while (true) {
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) {
309 continue;
311 return res;
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)},
334 s_function,
335 true,
336 FuncExistsChecker(name)) != Failure;
339 bool AutoloadHandler::autoloadConstant(StringData* name) {
340 return !m_map.isNull() &&
341 loadFromMap(String{name},
342 s_constant,
343 false,
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;
353 * Taken from php-src
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) {
357 return strspn(
358 className.data(),
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())) {
377 return false;
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);
397 } else {
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
405 SCOPE_EXIT {
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);
414 return true;
416 return false;
418 if (!m_spl_stack_inited || m_handlers.empty()) {
419 return false;
421 Object autoloadException;
422 for (const HandlerBundle& hb : m_handlers) {
423 try {
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;
429 } else {
430 Object cur = ex;
431 auto const ctx = cur->instanceof(SystemLib::s_ExceptionClass)
432 ? s_exception
433 : s_error;
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) {
444 break;
447 if (!autoloadException.isNull()) {
448 throw_object(autoloadException);
450 return true;
453 template <class T>
454 AutoloadHandler::Result
455 AutoloadHandler::loadFromMapPartial(const String& className,
456 const String& kind,
457 bool toLower,
458 const T &checkExists,
459 Variant& err) {
460 Result res = loadFromMapImpl(className, kind, toLower, checkExists, err);
461 if (res == Success) {
462 return Success;
464 assertx(res == Failure);
465 if (!err.isNull()) {
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);
471 if (checkExists()) {
472 return Success;
476 return res;
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;
486 while (true) {
487 Variant classErr{Variant::NullInit()};
488 if (tryClass) {
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()};
496 if (tryType) {
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
509 // above
510 if (classRes == Failure) {
511 assertx(tryClass);
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) {
519 assertx(tryType);
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) {
531 continue;
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.
537 return false;
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.
542 break;
545 return autoloadClassPHP5Impl(className, false);
548 Array AutoloadHandler::getHandlers() {
549 if (!m_spl_stack_inited) {
550 return Array();
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()));
568 } else {
569 obj = (ObjectData*)cufIter->ctx();
570 callable.append(Variant{obj});
572 callable.append(String(f->nameStr()));
573 handlers.append(callable.toArray());
574 } else {
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)) {
591 return false;
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)) {
601 return false;
604 m_spl_stack_inited = true;
606 // Zend doesn't modify the order of the list if the handler is already
607 // registered.
608 auto const& compareBundles = CompareBundles(cufIter.get());
609 if (std::find_if(m_handlers.begin(), m_handlers.end(), compareBundles) !=
610 m_handlers.end()) {
611 return true;
614 if (!prepend) {
615 m_handlers.emplace_back(handler, cufIter);
616 } else {
617 m_handlers.emplace_front(handler, cufIter);
620 return true;
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)) {
630 return;
633 // Use find_if instead of remove_if since we know there can only be one match
634 // in the vector.
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;
644 m_handlers.clear();
648 //////////////////////////////////////////////////////////////////////