Refactor property code to use MemberLookupContext
[hiphop-php.git] / hphp / runtime / base / builtin-functions.cpp
blob29159e0e7bcd6459ed9b69751f165618718a4fb9
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/builtin-functions.h"
19 #include "hphp/runtime/base/backtrace.h"
20 #include "hphp/runtime/base/code-coverage.h"
21 #include "hphp/runtime/base/container-functions.h"
22 #include "hphp/runtime/base/execution-context.h"
23 #include "hphp/runtime/base/file-util.h"
24 #include "hphp/runtime/base/opaque-resource.h"
25 #include "hphp/runtime/base/request-injection-data.h"
26 #include "hphp/runtime/base/runtime-option.h"
27 #include "hphp/runtime/base/unit-cache.h"
28 #include "hphp/runtime/base/variable-serializer.h"
29 #include "hphp/runtime/base/variable-unserializer.h"
31 #include "hphp/runtime/debugger/debugger.h"
33 #include "hphp/runtime/ext/std/ext_std_closure.h"
34 #include "hphp/runtime/ext/std/ext_std_function.h"
35 #include "hphp/runtime/ext/string/ext_string.h"
37 #include "hphp/runtime/vm/event-hook.h"
38 #include "hphp/runtime/vm/func.h"
39 #include "hphp/runtime/vm/jit/translator-inline.h"
40 #include "hphp/runtime/vm/jit/translator.h"
41 #include "hphp/runtime/vm/method-lookup.h"
42 #include "hphp/runtime/vm/reified-generics.h"
43 #include "hphp/runtime/vm/type-constraint.h"
44 #include "hphp/runtime/vm/unit-util.h"
45 #include "hphp/runtime/vm/unit.h"
47 #include "hphp/system/systemlib.h"
49 #include "hphp/util/logger.h"
50 #include "hphp/util/process.h"
51 #include "hphp/util/string-vsnprintf.h"
52 #include "hphp/util/text-util.h"
54 #include <folly/Format.h>
56 #include <algorithm>
58 namespace HPHP {
60 using std::string;
62 ///////////////////////////////////////////////////////////////////////////////
63 // static strings
65 const StaticString
66 s_Array("Array"),
67 s_offsetExists("offsetExists"),
68 s___invoke("__invoke"),
69 s_self("self"),
70 s_parent("parent"),
71 s_static("static");
73 const StaticString s_cmpWithCollection(
74 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
75 "a collection with an integer, double, string, array, or object"
77 const StaticString s_cmpWithVec(
78 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
79 "a vec with a non-vec"
81 const StaticString s_cmpWithDict(
82 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
83 "dicts"
85 const StaticString s_cmpWithKeyset(
86 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
87 "keysets"
89 const StaticString s_cmpWithClsMeth(
90 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
91 "a clsmeth"
93 const StaticString s_cmpWithRClsMeth(
94 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
95 "a reified class meth"
97 const StaticString s_cmpWithRFunc(
98 "Cannot use relational comparison operators (<, <=, >, >=, <=>) with "
99 "reified functions"
101 const StaticString s_cmpWithOpaqueResource(
102 "Cannot use relational comparison operators (<, <=, >, >=, <=>) with "
103 "opaque values"
105 const StaticString s_cmpWithNonArr(
106 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
107 "a PHP array with a non-array"
109 const StaticString s_cmpWithFunc(
110 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
111 "funcs"
114 ///////////////////////////////////////////////////////////////////////////////
116 bool array_is_valid_callback(const Array& arr) {
117 if (!arr.isVec() && !arr.isDict()) return false;
118 if (arr.size() != 2 || !arr.exists(int64_t(0)) || !arr.exists(int64_t(1))) {
119 return false;
121 auto const elem0 = arr.lookup(0);
122 if (!isStringType(elem0.type()) && !isObjectType(elem0.type()) &&
123 !isClassType(elem0.type()) && !isLazyClassType(elem0.type())) {
124 return false;
126 auto const elem1 = arr.lookup(1);
127 if (!isStringType(elem1.type()) && !isFuncType(elem1.type())) {
128 return false;
130 return true;
133 const StaticString
134 s__invoke("__invoke"),
135 s_Closure__invoke("Closure::__invoke"),
136 s_colon2("::");
138 bool is_callable(const Variant& v) {
139 CallCtx ctx;
140 vm_decode_function(v, ctx, DecodeFlags::LookupOnly);
141 return ctx.func != nullptr && !ctx.func->isAbstract();
144 bool is_callable(const Variant& v, bool syntax_only, Variant* name) {
145 bool ret = true;
146 if (LIKELY(!syntax_only)) {
147 ret = is_callable(v);
148 if (LIKELY(!name)) return ret;
151 auto const tv_func = v.asTypedValue();
152 if (isFuncType(tv_func->m_type)) {
153 auto func_name = tv_func->m_data.pfunc->fullName();
154 if (name) *name = Variant{func_name, Variant::PersistentStrInit{}};
155 return true;
158 if (isClsMethType(tv_func->m_type)) {
159 auto const clsmeth = tv_func->m_data.pclsmeth;
160 if (name) {
161 *name = concat3(
162 clsmeth->getCls()->nameStr(), "::", clsmeth->getFunc()->nameStr());
164 return true;
167 if (isStringType(tv_func->m_type)) {
168 if (name) *name = v;
169 return ret;
172 if (isVecType(tv_func->m_type) ||
173 isDictType(tv_func->m_type)) {
174 auto const arr = Array(tv_func->m_data.parr);
175 auto const clsname = arr.lookup(int64_t(0));
176 auto const mthname = arr.lookup(int64_t(1));
178 if (arr.size() != 2 || !clsname.is_init() ||
179 (!isStringType(mthname.type()) && !isFuncType(mthname.type()))) {
180 if (name) *name = s_Array;
181 return false;
184 StringData* clsString = nullptr;
185 if (isObjectType(clsname.type())) {
186 clsString = clsname.val().pobj->getClassName().get();
187 } else if (isStringType(clsname.type())) {
188 clsString = clsname.val().pstr;
189 } else if (isLazyClassType(clsname.type())) {
190 clsString = const_cast<StringData*>(clsname.val().plazyclass.name());
191 } else if (isClassType(clsname.type())) {
192 clsString = const_cast<StringData*>(clsname.val().pclass->name());
193 } else {
194 if (name) *name = s_Array;
195 return false;
198 if (isFuncType(mthname.type())) {
199 if (name) {
200 *name = Variant{mthname.val().pfunc->fullName(),
201 Variant::PersistentStrInit{}};
203 return true;
206 if (name) {
207 *name = concat3(String{clsString},
208 s_colon2,
209 String{mthname.val().pstr});
211 return ret;
214 if (tv_func->m_type == KindOfObject) {
215 ObjectData *d = tv_func->m_data.pobj;
216 const Func* invoke = d->getVMClass()->lookupMethod(s__invoke.get());
217 if (name) {
218 if (d->instanceof(c_Closure::classof())) {
219 // Hack to stop the mangled name from showing up
220 *name = s_Closure__invoke;
221 } else {
222 *name = d->getClassName().asString() + "::__invoke";
225 return invoke != nullptr;
228 return false;
231 namespace {
232 Class* vm_decode_class_from_name(
233 const String& clsName,
234 const String& funcName,
235 bool nameContainsClass,
236 ActRec* ar,
237 bool& forwarding,
238 Class* ctx,
239 DecodeFlags flags) {
240 Class* cls = nullptr;
241 if (clsName.get()->isame(s_self.get())) {
242 if (ctx) {
243 cls = ctx;
245 if (!nameContainsClass) {
246 forwarding = true;
248 } else if (clsName.get()->isame(s_parent.get())) {
249 if (ctx && ctx->parent()) {
250 cls = ctx->parent();
252 if (!nameContainsClass) {
253 forwarding = true;
255 } else if (clsName.get()->isame(s_static.get())) {
256 if (ar && ar->func()->cls()) {
257 if (ar->hasThis()) {
258 cls = ar->getThis()->getVMClass();
259 } else {
260 cls = ar->getClass();
262 if (flags != DecodeFlags::NoWarn && cls) {
263 if (RuntimeOption::EvalWarnOnSkipFrameLookup) {
264 raise_warning(
265 "vm_decode_function() used to decode a LSB class "
266 "method on %s",
267 cls->name()->data()
272 } else {
273 if (flags == DecodeFlags::Warn && nameContainsClass) {
274 String nameClass = funcName.substr(0, funcName.find("::"));
275 if (nameClass.get()->isame(s_self.get()) ||
276 nameClass.get()->isame(s_static.get())) {
277 raise_warning("behavior of call_user_func(array('%s', '%s')) "
278 "is undefined", clsName.data(), funcName.data());
281 cls = Class::load(clsName.get());
283 return cls;
286 const Func* vm_decode_func_from_name(
287 const String& funcName,
288 ActRec* ar,
289 bool forwarding,
290 ObjectData*& this_,
291 Class*& cls,
292 Class* ctx,
293 Class* cc,
294 DecodeFlags flags) {
295 CallType lookupType = this_ ? CallType::ObjMethod : CallType::ClsMethod;
296 auto const moduleName =
297 ar ? ar->func()->unit()->moduleName() : (const StringData*)nullptr;
298 auto const callCtx = MemberLookupContext(ctx, moduleName);
299 auto f = lookupMethodCtx(cc, funcName.get(), callCtx, lookupType,
300 MethodLookupErrorOptions::NoErrorOnModule);
301 if (f && (f->attrs() & AttrStatic)) {
302 // If we found a method and its static, null out this_
303 this_ = nullptr;
304 } else {
305 if (!this_ && ar && ar->func()->cls() && ar->hasThis()) {
306 // If we did not find a static method AND this_ is null AND there is a
307 // frame ar, check if the current instance from ar is compatible
308 auto const obj = ar->getThis();
309 if (obj->instanceof(cls)) {
310 this_ = obj;
311 cls = obj->getVMClass();
313 if (flags != DecodeFlags::NoWarn && this_) {
314 if (RuntimeOption::EvalWarnOnSkipFrameLookup) {
315 raise_warning(
316 "vm_decode_function() used to decode a method on $this, an "
317 "instance of %s, from the caller, %s",
318 cls->name()->data(),
319 ar->func()->fullName()->data()
324 if (!f) {
325 if (lookupType == CallType::ClsMethod) {
326 this_ = nullptr;
328 // Bail out if we couldn't find the method
329 if (flags == DecodeFlags::Warn) {
330 raise_invalid_argument_warning("function: method '%s' not found",
331 funcName.data());
333 return nullptr;
337 if (!this_ && !f->isStaticInPrologue()) {
338 if (flags != DecodeFlags::LookupOnly) throw_missing_this(f);
341 assertx(f && f->preClass());
342 // If this_ is non-NULL, then this_ is the current instance and cls is
343 // the class of the current instance.
344 assertx(!this_ || this_->getVMClass() == cls);
345 // If we are doing a forwarding call and this_ is null, set cls
346 // appropriately to propagate the current late bound class.
347 if (!this_ && forwarding && ar && ar->func()->cls()) {
348 auto const fwdCls = ar->hasThis() ?
349 ar->getThis()->getVMClass() : ar->getClass();
351 // Only forward the current late bound class if it is the same or
352 // a descendent of cls
353 if (fwdCls->classof(cls)) {
354 cls = fwdCls;
357 if (flags != DecodeFlags::NoWarn && fwdCls) {
358 if (RuntimeOption::EvalWarnOnSkipFrameLookup) {
359 raise_warning(
360 "vm_decode_function() forwarded the calling context, %s",
361 fwdCls->name()->data()
367 if (flags != DecodeFlags::NoWarn && !f->isPublic()) {
368 if (RuntimeOption::EvalWarnOnSkipFrameLookup) {
369 raise_warning(
370 "vm_decode_function() used to decode a %s method: %s",
371 f->attrs() & AttrPrivate ? "private" : "protected",
372 f->fullName()->data()
376 return f;
380 bool checkMethCallerTarget(const Func* meth, const Class* ctx, bool error) {
381 if (meth->isStaticInPrologue()) {
382 if (!error) return false;
383 SystemLib::throwInvalidArgumentExceptionObject(folly::sformat(
384 "meth_caller(): method {} is static",
385 meth->fullName()->data()
389 if (meth->isPublic() || ctx == meth->cls()) return true;
390 if (ctx && (meth->attrs() & AttrProtected) && ctx->classof(meth->cls())) {
391 return true;
394 // Executing this meth_caller() can still be an error but we won't know until
395 // we know precisely what instance it's being invoked on.
396 // XXX: Do we want this behavior?
397 if (meth->hasPrivateAncestor() && meth->cls()->classof(ctx)) {
398 auto const cmeth = ctx->lookupMethod(meth->name());
399 if (cmeth && cmeth->cls() == ctx && (cmeth->attrs() & AttrPrivate)) {
400 return true;
404 if (error) {
405 SystemLib::throwInvalidArgumentExceptionObject(folly::sformat(
406 "meth_caller(): method {} cannot be called from this context",
407 meth->fullName()->data()
410 return false;
413 void checkMethCaller(const Func* func, const Class* ctx) {
414 auto const cls = Class::load(func->methCallerClsName());
415 if (!cls) {
416 SystemLib::throwInvalidArgumentExceptionObject(folly::sformat(
417 "meth_caller(): class {} not found", func->methCallerClsName()->data()
421 if (isTrait(cls)) {
422 SystemLib::throwInvalidArgumentExceptionObject(folly::sformat(
423 "meth_caller(): class {} is a trait", func->methCallerClsName()->data()
427 auto const meth = [&] () -> const Func* {
428 if (auto const m = cls->lookupMethod(func->methCallerMethName())) return m;
429 for (auto const i : cls->allInterfaces().range()) {
430 if (auto const m = i->lookupMethod(func->methCallerMethName())) return m;
432 return nullptr;
433 }();
434 if (!meth) {
435 SystemLib::throwInvalidArgumentExceptionObject(folly::sformat(
436 "meth_caller(): method {}::{} not found",
437 func->methCallerClsName()->data(),
438 func->methCallerMethName()->data()
442 checkMethCallerTarget(meth, ctx, true);
445 const HPHP::Func*
446 vm_decode_function(const_variant_ref function,
447 ActRec* ar,
448 ObjectData*& this_,
449 HPHP::Class*& cls,
450 bool& dynamic,
451 DecodeFlags flags /* = DecodeFlags::Warn */,
452 bool genericsAlreadyGiven /* = false */) {
453 bool forwarding = false;
454 dynamic = true;
456 if (function.isFunc()) {
457 dynamic = false;
458 this_ = nullptr;
459 cls = nullptr;
460 return function.toFuncVal();
463 if (function.isClsMeth()) {
464 dynamic = false;
465 this_ = nullptr;
466 auto const clsmeth = function.toClsMethVal();
467 cls = clsmeth->getCls();
468 return clsmeth->getFunc();
471 if (function.isString() || function.isArray()) {
472 HPHP::Class* ctx = nullptr;
473 if (ar) ctx = arGetContextClass(ar);
474 // Decode the 'function' parameter into this_, cls, name, pos, and
475 // nameContainsClass.
476 this_ = nullptr;
477 cls = nullptr;
478 String name;
479 int pos = String::npos;
480 bool nameContainsClass = false;
481 if (function.isString()) {
482 // If 'function' is a string we simply assign it to name and
483 // leave this_ and cls set to NULL.
484 name = function.toString();
485 pos = name.find("::");
486 nameContainsClass =
487 (pos != 0 && pos != String::npos && pos + 2 < name.size());
488 } else {
489 // If 'function' is an array with exactly two indices 0 and 1, we
490 // assign the value at index 1 to name and we use the value at index
491 // 0 to populate cls (if the value is a string) or this_ and cls (if
492 // the value is an object).
493 assertx(function.isArray());
494 Array arr = function.toArray();
495 if (!array_is_valid_callback(arr)) {
496 if (flags == DecodeFlags::Warn) {
497 raise_invalid_argument_warning("function: not a valid callback array");
499 return nullptr;
502 Variant elem0 = arr[0];
503 Variant elem1 = arr[1];
504 if (elem1.isFunc()) {
505 if (elem0.isObject()) {
506 this_ = elem0.getObjectData();
507 cls = this_->getVMClass();
508 } else if (elem0.isClass()) {
509 cls = elem0.toClassVal();
510 } else if (elem0.isLazyClass()) {
511 cls = Class::load(elem0.toLazyClassVal().name());
512 } else {
513 raise_error("calling an ill-formed array without resolved "
514 "object/class pointer");
515 return nullptr;
517 return elem1.toFuncVal();
520 assertx(elem1.isString());
521 name = elem1.toString();
522 pos = name.find("::");
523 nameContainsClass =
524 (pos != 0 && pos != String::npos && pos + 2 < name.size());
525 if (elem0.isString() || elem0.isLazyClass()) {
526 auto const clsName = elem0.isString() ?
527 elem0.toString() : StrNR{elem0.toLazyClassVal().name()};
528 cls = vm_decode_class_from_name(
529 clsName, name, nameContainsClass, ar, forwarding, ctx,
530 flags);
531 if (!cls) {
532 if (flags == DecodeFlags::Warn) {
533 raise_invalid_argument_warning("function: class not found");
535 return nullptr;
537 } else if (elem0.isClass()) {
538 cls = elem0.toClassVal();
539 } else {
540 assertx(elem0.isObject());
541 this_ = elem0.getObjectData();
542 cls = this_->getVMClass();
546 HPHP::Class* cc = cls;
547 if (nameContainsClass) {
548 String c = name.substr(0, pos);
549 name = name.substr(pos + 2);
550 if (c.get()->isame(s_self.get())) {
551 if (cls) {
552 cc = cls;
553 } else if (ctx) {
554 cc = ctx;
556 if (!this_) {
557 forwarding = true;
559 } else if (c.get()->isame(s_parent.get())) {
560 if (cls) {
561 cc = cls->parent();
562 } else if (ctx && ctx->parent()) {
563 cc = ctx->parent();
565 if (!this_) {
566 forwarding = true;
568 } else if (c.get()->isame(s_static.get())) {
569 if (ar && ar->func()->cls()) {
570 if (ar->hasThis()) {
571 cc = ar->getThis()->getVMClass();
572 } else {
573 cc = ar->getClass();
576 if (flags != DecodeFlags::NoWarn && cc) {
577 if (RuntimeOption::EvalWarnOnSkipFrameLookup) {
578 raise_warning(
579 "vm_decode_function() used to decode a LSB class "
580 "method on %s",
581 cc->name()->data()
585 } else {
586 cc = Class::load(c.get());
588 if (!cc) {
589 if (flags == DecodeFlags::Warn) {
590 raise_invalid_argument_warning("function: class not found");
592 return nullptr;
594 if (cls) {
595 if (!cls->classof(cc)) {
596 if (flags == DecodeFlags::Warn) {
597 raise_warning("call_user_func expects parameter 1 to be a valid "
598 "callback, class '%s' is not a subclass of '%s'",
599 cls->preClass()->name()->data(),
600 cc->preClass()->name()->data());
602 return nullptr;
605 // If there is not a current instance, cc trumps cls.
606 if (!this_) {
607 cls = cc;
611 if (!cls) {
612 HPHP::Func* f = HPHP::Func::load(name.get());
613 if (!f) {
614 if (flags == DecodeFlags::Warn) {
615 raise_invalid_argument_warning("function: method '%s' not found",
616 name.data());
618 return nullptr;
620 assertx(f && f->preClass() == nullptr);
621 if (f->hasReifiedGenerics() && !genericsAlreadyGiven &&
622 !f->getReifiedGenericsInfo().allGenericsSoft()) {
623 raise_invalid_argument_warning(
624 "You may not call the reified function '%s' "
625 "without reified arguments",
626 f->fullName()->data());
627 return nullptr;
629 return f;
631 assertx(cls);
632 return vm_decode_func_from_name(
633 name, ar, forwarding, this_, cls, ctx, cc, flags);
635 if (function.isObject()) {
636 this_ = function.asCObjRef().get();
637 cls = nullptr;
638 dynamic = false;
639 const HPHP::Func *f = this_->getVMClass()->lookupMethod(s___invoke.get());
640 if (f != nullptr && f->isStaticInPrologue()) {
641 // If __invoke is static, invoke it as such
642 cls = this_->getVMClass();
643 this_ = nullptr;
645 if (flags == DecodeFlags::Warn && f == nullptr) {
646 raise_warning("call_user_func() expects parameter 1 to be a valid "
647 "callback, object of class %s given (no __invoke "
648 "method found)", this_->getVMClass()->name()->data());
650 return f;
652 if (flags == DecodeFlags::Warn) {
653 raise_invalid_argument_warning("function: not string, closure, or array");
655 return nullptr;
658 Variant vm_call_user_func(const_variant_ref function, const Variant& params,
659 RuntimeCoeffects providedCoeffects
660 /* = RuntimeCoeffects::fixme() */,
661 bool checkRef /* = false */,
662 bool allowDynCallNoPointer /* = false */) {
663 CallCtx ctx;
664 vm_decode_function(function, ctx);
665 if (ctx.func == nullptr || (!isContainer(params) && !params.isNull())) {
666 return uninit_null();
668 return Variant::attach(
669 g_context->invokeFunc(ctx.func, params, ctx.this_, ctx.cls,
670 providedCoeffects, ctx.dynamic,
671 checkRef, allowDynCallNoPointer)
675 Variant
676 invoke(const String& function, const Variant& params,
677 bool allowDynCallNoPointer /* = false */) {
678 Func* func = Func::load(function.get());
679 if (func && (isContainer(params) || params.isNull())) {
680 auto ret = Variant::attach(
681 g_context->invokeFunc(func, params, nullptr, nullptr,
682 RuntimeCoeffects::fixme(), true, false,
683 allowDynCallNoPointer)
686 return ret;
688 throw ExtendedException("(1) call the function without enough arguments OR "
689 "(2) Unable to find function \"%s\" OR "
690 "(3) function was not in invoke table OR "
691 "(4) function was renamed to something else.",
692 function.c_str());
695 Variant invoke_static_method(const String& s, const String& method,
696 const Variant& params, bool fatal /* = true */) {
697 HPHP::Class* class_ = Class::lookup(s.get());
698 if (class_ == nullptr) {
699 o_invoke_failed(s.data(), method.data(), fatal);
700 return uninit_null();
702 const HPHP::Func* f = class_->lookupMethod(method.get());
703 if (f == nullptr || !f->isStaticInPrologue() ||
704 (!isContainer(params) && !params.isNull())) {
705 o_invoke_failed(s.data(), method.data(), fatal);
706 return uninit_null();
708 auto ret = Variant::attach(
709 g_context->invokeFunc(f, params, nullptr, class_, RuntimeCoeffects::fixme())
711 return ret;
714 Variant o_invoke_failed(const char *cls, const char *meth,
715 bool fatal /* = true */) {
716 if (fatal) {
717 string msg = "Unknown method ";
718 msg += cls;
719 msg += "::";
720 msg += meth;
721 raise_fatal_error(msg.c_str());
722 } else {
723 raise_warning("call_user_func to non-existent method %s::%s", cls, meth);
724 return false;
728 void throw_has_this_need_static(const Func* f) {
729 auto const msg = folly::sformat(
730 "Static method {}() cannot be called on instance",
731 f->fullName()->data()
733 SystemLib::throwBadMethodCallExceptionObject(msg);
736 void throw_missing_this(const Func* f) {
737 auto const msg = folly::sformat(
738 "Non-static method {}() cannot be called statically",
739 f->fullName()->data()
741 SystemLib::throwBadMethodCallExceptionObject(msg);
744 void NEVER_INLINE throw_invalid_property_name(const String& name) {
745 if (!name.size()) {
746 raise_error("Cannot access empty property");
748 raise_error("Cannot access property started with '\\0'");
751 void throw_instance_method_fatal(const char *name) {
752 raise_error("Non-static method %s() cannot be called statically", name);
755 void throw_call_reified_func_without_generics(const Func* f) {
756 auto const msg = folly::sformat(
757 "Cannot call the reified function '{}' without the reified generics",
758 f->fullName()->data()
760 SystemLib::throwBadMethodCallExceptionObject(msg);
763 void throw_implicit_context_exception(std::string s) {
764 SystemLib::throwInvalidOperationExceptionObject(s);
767 void raise_implicit_context_warning(std::string s) {
768 raise_warning(s);
771 void throw_iterator_not_valid() {
772 SystemLib::throwInvalidOperationExceptionObject(
773 "Iterator is not valid");
776 void throw_collection_property_exception() {
777 SystemLib::throwInvalidOperationExceptionObject(
778 "Cannot access a property on a collection");
781 void throw_invalid_collection_parameter() {
782 SystemLib::throwInvalidArgumentExceptionObject(
783 "Parameter must be an array or an instance of Traversable");
786 void throw_invalid_operation_exception(StringData* str) {
787 SystemLib::throwInvalidOperationExceptionObject(Variant{str});
790 void throw_division_by_zero_exception() {
791 SystemLib::throwDivisionByZeroExceptionObject();
794 void throw_collection_compare_exception() {
795 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithCollection);
798 void throw_vec_compare_exception() {
799 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithVec);
802 void throw_dict_compare_exception() {
803 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithDict);
806 void throw_rfunc_compare_exception() {
807 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithRFunc);
810 void throw_opaque_resource_compare_exception() {
811 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithOpaqueResource);
814 void throw_keyset_compare_exception() {
815 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithKeyset);
818 void throw_clsmeth_compare_exception() {
819 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithClsMeth);
822 void throw_rclsmeth_compare_exception() {
823 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithRClsMeth);
826 void throw_func_compare_exception() {
827 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithFunc);
830 void throw_param_is_not_container() {
831 static const string msg("Parameter must be an array or collection");
832 SystemLib::throwInvalidArgumentExceptionObject(msg);
835 void throw_invalid_inout_base() {
836 SystemLib::throwInvalidArgumentExceptionObject(
837 "Parameters marked inout must be contained in locals, vecs, dicts, "
838 "keysets, and arrays"
842 void throw_cannot_modify_immutable_object(const char* className) {
843 auto msg = folly::sformat(
844 "Cannot modify immutable object of type {}",
845 className
847 SystemLib::throwInvalidOperationExceptionObject(msg);
850 void throw_cannot_modify_const_object(const char* className) {
851 auto msg = folly::sformat(
852 "Cannot modify const object of type {}",
853 className
855 SystemLib::throwInvalidOperationExceptionObject(msg);
858 void throw_object_forbids_dynamic_props(const char* className) {
859 auto msg = folly::sformat(
860 "Class {} does not allow use of dynamic (non-declared) properties",
861 className
863 SystemLib::throwInvalidOperationExceptionObject(msg);
866 void throw_cannot_modify_const_prop(const char* className,
867 const char* propName)
869 auto msg = folly::sformat(
870 "Cannot modify const property {} of class {} after construction",
871 propName, className
873 SystemLib::throwInvalidOperationExceptionObject(msg);
876 void throw_cannot_modify_static_const_prop(const char* className,
877 const char* propName)
879 auto msg = folly::sformat(
880 "Cannot modify static const property {} of class {}.",
881 propName, className
883 SystemLib::throwInvalidOperationExceptionObject(msg);
886 void throw_local_must_be_value_type(const char* locName)
888 auto const msg = folly::sformat("Local {} must be a value type.", locName);
889 SystemLib::throwReadonlyViolationExceptionObject(msg);
892 namespace {
893 [[noreturn]]
894 void throw_readonly_violation(const char* className, const char* propName,
895 const char* msg) {
896 std::string fmtMsg;
897 string_printf(fmtMsg, msg, propName, className);
898 SystemLib::throwReadonlyViolationExceptionObject(fmtMsg);
902 void throw_must_be_readonly(const char* className, const char* propName) {
903 throw_readonly_violation(className, propName, Strings::MUST_BE_READONLY);
906 void throw_must_be_mutable(const char* className, const char* propName) {
907 throw_readonly_violation(className, propName, Strings::MUST_BE_MUTABLE);
910 void throw_must_be_enclosed_in_readonly(const char* className, const char* propName) {
911 throw_readonly_violation(className, propName, Strings::MUST_BE_ENCLOSED_IN_READONLY);
914 void throw_must_be_value_type(const char* className, const char* propName) {
915 throw_readonly_violation(className, propName, Strings::MUST_BE_VALUE_TYPE);
918 void throw_cannot_modify_readonly_collection() {
919 SystemLib::throwReadonlyViolationExceptionObject(
920 Strings::READONLY_COLLECTIONS_CANNOT_BE_MODIFIED
924 bool readonlyLocalShouldThrow(TypedValue tv, ReadonlyOp op) {
925 if (op == ReadonlyOp::CheckROCOW || op == ReadonlyOp::CheckMutROCOW) {
926 vmMInstrState().roProp = true;
927 if (type(tv) == KindOfObject) return true;
929 return false;
932 void checkReadonly(const TypedValue* tv,
933 const Class* cls,
934 const StringData* name,
935 bool readonly,
936 ReadonlyOp op,
937 bool writeMode) {
938 if ((op == ReadonlyOp::CheckMutROCOW && readonly) || op == ReadonlyOp::CheckROCOW) {
939 vmMInstrState().roProp = true;
941 if (readonly) {
942 if (op == ReadonlyOp::CheckMutROCOW || op == ReadonlyOp::CheckROCOW) {
943 if (type(tv) == KindOfObject) {
944 throw_must_be_value_type(cls->name()->data(), name->data());
946 } else if (op == ReadonlyOp::Mutable) {
947 if (writeMode) {
948 throw_must_be_mutable(cls->name()->data(), name->data());
949 } else {
950 throw_must_be_enclosed_in_readonly(cls->name()->data(), name->data());
953 } else if (op == ReadonlyOp::Readonly || op == ReadonlyOp::CheckROCOW) {
954 throw_must_be_readonly(cls->name()->data(), name->data());
958 NEVER_INLINE
959 void throw_late_init_prop(const Class* cls,
960 const StringData* propName,
961 bool isSProp) {
962 SystemLib::throwInvalidOperationExceptionObject(
963 folly::sformat(
964 "Accessing <<__LateInit>> {} '{}::{}' before initialization",
965 isSProp ? "static property" : "property",
966 cls->name(),
967 propName
972 NEVER_INLINE
973 void throw_parameter_wrong_type(TypedValue tv,
974 const Func* callee,
975 unsigned int arg_num,
976 const StringData* expected_type) {
977 auto const msg = param_type_error_message(
978 callee->name()->data(), arg_num, expected_type->data(), tv);
979 if (RuntimeOption::PHP7_EngineExceptions) {
980 SystemLib::throwTypeErrorObject(msg);
982 SystemLib::throwRuntimeExceptionObject(msg);
985 void check_collection_cast_to_array() {
986 if (RuntimeOption::WarnOnCollectionToArray) {
987 raise_warning("Casting a collection to an array is an expensive operation "
988 "and should be avoided where possible. To convert a "
989 "collection to an array without raising a warning, use the "
990 "toArray() method.");
994 Object create_object_only(const String& s) {
995 return Object::attach(g_context->createObjectOnly(s.get()));
998 Object init_object(const String& s, const Array& params, ObjectData* o) {
999 return Object{g_context->initObject(s.get(), params, o)};
1002 Object
1003 create_object(const String& s, const Array& params, bool init /* = true */) {
1004 return Object::attach(g_context->createObject(s.get(), params, init));
1007 void throw_object(const Object& e) {
1008 throw req::root<Object>(e);
1011 #if ((__GNUC__ != 4) || (__GNUC_MINOR__ != 8))
1012 void throw_object(Object&& e) {
1013 throw req::root<Object>(std::move(e));
1015 #endif
1018 * This function is used when another thread is segfaulting---we just
1019 * want to wait forever to give it a chance to write a stacktrace file
1020 * (and maybe a core file).
1022 void pause_forever() {
1023 for (;;) sleep(300);
1026 bool is_constructor_name(const char* fn) {
1027 auto len = strlen(fn);
1028 const char construct[] = "__construct";
1029 auto clen = sizeof(construct) - 1;
1031 if (len >= clen && !strcasecmp(fn + len - clen, construct)) {
1032 if (len == clen || (len > clen + 2 &&
1033 fn[len - clen - 1] == ':' &&
1034 fn[len - clen - 2] == ':')) {
1035 return true;
1038 return false;
1041 void throw_missing_arguments_nr(const char *fn, int expected, int got) {
1042 SystemLib::throwRuntimeExceptionObject(folly::sformat(
1043 "{}() expects exactly {} parameter{}, {} given",
1045 expected,
1046 expected == 1 ? "" : "s",
1051 void raise_bad_type_warning(const char *fmt, ...) {
1052 va_list ap;
1053 va_start(ap, fmt);
1054 std::string msg;
1055 string_vsnprintf(msg, fmt, ap);
1056 va_end(ap);
1058 raise_warning("Invalid operand type was used: %s", msg.c_str());
1061 void raise_expected_array_warning(const char* fn /*=nullptr*/) {
1062 if (!fn) {
1063 fn = fromLeaf([&](const BTFrame& frm) {
1064 return frm.func()->name()->data();
1065 }, backtrace_detail::true_pred, "(unknown)");
1067 raise_bad_type_warning("%s expects array(s)", fn);
1070 void raise_expected_array_or_collection_warning(const char* fn /*=nullptr*/) {
1071 if (!fn) {
1072 fn = fromLeaf([&](const BTFrame& frm) {
1073 return frm.func()->name()->data();
1074 }, backtrace_detail::true_pred, "(unknown)");
1076 raise_bad_type_warning("%s expects array(s) or collection(s)", fn);
1079 void raise_invalid_argument_warning(const char *fmt, ...) {
1080 va_list ap;
1081 va_start(ap, fmt);
1082 string msg;
1083 string_vsnprintf(msg, fmt, ap);
1084 va_end(ap);
1085 raise_warning("Invalid argument: %s", msg.c_str());
1088 Variant throw_fatal_unset_static_property(const char *s, const char *prop) {
1089 raise_error("Attempt to unset static property %s::$%s", s, prop);
1090 return uninit_null();
1093 Variant unserialize_ex(const char* str, int len,
1094 VariableUnserializer::Type type,
1095 const Array& options /* = null_array */,
1096 bool pure /* = false */) {
1097 if (str == nullptr || len <= 0) {
1098 return false;
1101 VariableUnserializer vu(str, len, type,
1102 /* allowUnknownSerializableClass = */ true,
1103 options);
1104 if (pure) vu.setPure();
1105 Variant v;
1106 try {
1107 v = vu.unserialize();
1108 } catch (FatalErrorException& e) {
1109 throw;
1110 } catch (InvalidAllowedClassesException& e) {
1111 raise_warning(
1112 "unserialize(): allowed_classes option should be array or boolean"
1114 return false;
1115 } catch (Exception& e) {
1116 raise_notice("Unable to unserialize: [%.*s]. %s.",
1117 std::min(len, 1000), str, e.getMessage().c_str());
1118 return false;
1120 return v;
1123 Variant unserialize_ex(const String& str,
1124 VariableUnserializer::Type type,
1125 const Array& options /* = null_array */,
1126 bool pure /* = false */) {
1127 return unserialize_ex(str.data(), str.size(), type, options, pure);
1130 String concat3(const String& s1, const String& s2, const String& s3) {
1131 auto r1 = s1.slice();
1132 auto r2 = s2.slice();
1133 auto r3 = s3.slice();
1134 auto len = r1.size() + r2.size() + r3.size();
1135 auto str = String::attach(StringData::Make(len));
1136 auto const r = str.mutableData();
1137 memcpy(r, r1.data(), r1.size());
1138 memcpy(r + r1.size(), r2.data(), r2.size());
1139 memcpy(r + r1.size() + r2.size(), r3.data(), r3.size());
1140 str.setSize(len);
1141 return str;
1144 String concat4(const String& s1, const String& s2, const String& s3,
1145 const String& s4) {
1146 auto r1 = s1.slice();
1147 auto r2 = s2.slice();
1148 auto r3 = s3.slice();
1149 auto r4 = s4.slice();
1150 auto len = r1.size() + r2.size() + r3.size() + r4.size();
1151 auto str = String::attach(StringData::Make(len));
1152 auto const r = str.mutableData();
1153 memcpy(r, r1.data(), r1.size());
1154 memcpy(r + r1.size(), r2.data(), r2.size());
1155 memcpy(r + r1.size() + r2.size(), r3.data(), r3.size());
1156 memcpy(r + r1.size() + r2.size() + r3.size(), r4.data(), r4.size());
1157 str.setSize(len);
1158 return str;
1161 static bool invoke_file_impl(Variant& res, const String& path, bool once,
1162 const char *currentDir,
1163 bool callByHPHPInvoke) {
1164 bool initial;
1165 auto const u = lookupUnit(path.get(), currentDir, &initial,
1166 Native::s_noNativeFuncs, false);
1167 if (u == nullptr) return false;
1168 if (!once || initial) {
1169 *res.asTypedValue() = g_context->invokeUnit(u, callByHPHPInvoke);
1171 return true;
1174 static NEVER_INLINE Variant throw_missing_file(const char* file) {
1175 throw PhpFileDoesNotExistException(file);
1178 static Variant invoke_file(const String& s,
1179 bool once,
1180 const char *currentDir,
1181 bool callByHPHPInvoke) {
1182 Variant r;
1183 if (invoke_file_impl(r, s, once, currentDir, callByHPHPInvoke)) {
1184 return r;
1186 return throw_missing_file(s.c_str());
1189 Variant include_impl_invoke(const String& file, bool once,
1190 const char *currentDir, bool callByHPHPInvoke) {
1191 if (FileUtil::isAbsolutePath(file.toCppString())) {
1192 if (RuntimeOption::SandboxMode || !RuntimeOption::AlwaysUseRelativePath) {
1193 try {
1194 return invoke_file(file, once, currentDir, callByHPHPInvoke);
1195 } catch(PhpFileDoesNotExistException& e) {}
1198 try {
1199 String rel_path(FileUtil::relativePath(RuntimeOption::SourceRoot,
1200 string(file.data())));
1202 // Don't try/catch - We want the exception to be passed along
1203 return invoke_file(rel_path, once, currentDir, callByHPHPInvoke);
1204 } catch(PhpFileDoesNotExistException& e) {
1205 throw PhpFileDoesNotExistException(file.c_str());
1207 } else {
1208 // Don't try/catch - We want the exception to be passed along
1209 return invoke_file(file, once, currentDir, callByHPHPInvoke);
1214 * Used by include_impl. resolve_include() needs some way of checking the
1215 * existence of a file path, which for hphpc means attempting to invoke it.
1216 * This struct carries some context information needed for the invocation, as
1217 * well as a place for the return value of invoking the file.
1219 struct IncludeImplInvokeContext {
1220 bool once;
1221 const char* currentDir;
1223 Variant returnValue;
1226 static bool include_impl_invoke_context(const String& file, void* ctx) {
1227 struct IncludeImplInvokeContext* context = (IncludeImplInvokeContext*)ctx;
1228 bool invoked_file = false;
1229 try {
1230 context->returnValue = include_impl_invoke(file, context->once,
1231 context->currentDir);
1232 invoked_file = true;
1233 } catch (PhpFileDoesNotExistException& e) {
1234 context->returnValue = false;
1236 return invoked_file;
1240 * tryFile is a pointer to a function that resolve_include() will use to
1241 * determine if a path references a real file. ctx is a pointer to some context
1242 * information that will be passed through to tryFile. (It's a hacky closure)
1244 String resolve_include(const String& file, const char* currentDir,
1245 bool (*tryFile)(const String& file, void*), void* ctx) {
1246 const char* c_file = file.data();
1248 auto const getCwd = [] () -> String {
1249 if (LIKELY(!g_context.isNull())) return g_context->getCwd();
1250 return String(Process::CurrentWorkingDirectory, CopyString);
1253 if (!File::IsPlainFilePath(file)) {
1254 // URIs don't have an include path
1255 if (tryFile(file, ctx)) {
1256 return file;
1259 } else if (FileUtil::isAbsolutePath(file.toCppString())) {
1260 String can_path = FileUtil::canonicalize(file);
1262 if (tryFile(can_path, ctx)) {
1263 return can_path;
1266 } else if ((c_file[0] == '.' && (c_file[1] == '/' || (
1267 c_file[1] == '.' && c_file[2] == '/')))) {
1269 String path(String(getCwd() + "/" + file));
1270 String can_path = FileUtil::canonicalize(path);
1272 if (tryFile(can_path, ctx)) {
1273 return can_path;
1276 } else {
1277 if (!RequestInfo::s_requestInfo.isNull()) {
1278 auto const& includePaths = RID().getIncludePaths();
1280 for (auto const& includePath : includePaths) {
1281 String path("");
1282 auto const is_stream_wrapper =
1283 includePath.find("://") != std::string::npos;
1285 if (!is_stream_wrapper && !FileUtil::isAbsolutePath(includePath)) {
1286 path += (getCwd() + "/");
1289 path += includePath;
1291 if (path[path.size() - 1] != '/') {
1292 path += "/";
1295 path += file;
1297 String can_path;
1298 if (!is_stream_wrapper) {
1299 can_path = FileUtil::canonicalize(path);
1300 } else {
1301 can_path = String(path.c_str());
1304 if (tryFile(can_path, ctx)) {
1305 return can_path;
1310 if (FileUtil::isAbsolutePath(currentDir)) {
1311 String path(currentDir);
1312 path += "/";
1313 path += file;
1314 String can_path = FileUtil::canonicalize(path);
1316 if (tryFile(can_path, ctx)) {
1317 return can_path;
1319 } else {
1320 String path(getCwd() + "/" + currentDir + file);
1321 String can_path = FileUtil::canonicalize(path);
1323 if (tryFile(can_path, ctx)) {
1324 return can_path;
1329 return String();
1332 static Variant include_impl(const String& file, bool once,
1333 const char *currentDir, bool required,
1334 bool raiseNotice) {
1335 struct IncludeImplInvokeContext ctx = {once, currentDir};
1336 String can_path = resolve_include(file, currentDir,
1337 include_impl_invoke_context, (void*)&ctx);
1339 if (can_path.isNull()) {
1340 // Failure
1341 if (raiseNotice) {
1342 raise_notice("Tried to invoke %s but file not found.", file.data());
1344 if (required) {
1345 String ms = "Required file that does not exist: ";
1346 ms += file;
1347 raise_fatal_error(ms.data());
1349 return false;
1352 return ctx.returnValue;
1355 Variant require(const String& file,
1356 bool once,
1357 const char* currentDir,
1358 bool raiseNotice) {
1359 return include_impl(file, once, currentDir, true, raiseNotice);
1362 bool function_exists(const String& function_name) {
1363 auto f = Func::lookup(function_name.get());
1364 return (f != nullptr) &&
1365 (f->arFuncPtr() != Native::unimplementedWrapper);
1368 ///////////////////////////////////////////////////////////////////////////////
1369 // debugger and code coverage instrumentation
1371 void throw_exception(const Object& e) {
1372 if (!e.instanceof(SystemLib::s_ThrowableClass)) {
1373 raise_error("Exceptions must implement the Throwable interface.");
1375 DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(e.get()));
1376 throw req::root<Object>(e);
1379 ///////////////////////////////////////////////////////////////////////////////