Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / builtin-functions.cpp
blob3f6e2113555ff8b07e963a279c767333328c7a5a
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/externals.h"
24 #include "hphp/runtime/base/file-util.h"
25 #include "hphp/runtime/base/request-injection-data.h"
26 #include "hphp/runtime/base/runtime-option.h"
27 #include "hphp/runtime/base/strings.h"
28 #include "hphp/runtime/base/unit-cache.h"
29 #include "hphp/runtime/base/variable-serializer.h"
30 #include "hphp/runtime/base/variable-unserializer.h"
32 #include "hphp/runtime/debugger/debugger.h"
34 #include "hphp/runtime/ext/std/ext_std_closure.h"
35 #include "hphp/runtime/ext/std/ext_std_function.h"
36 #include "hphp/runtime/ext/string/ext_string.h"
38 #include "hphp/runtime/vm/event-hook.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/repo.h"
43 #include "hphp/runtime/vm/unit-util.h"
44 #include "hphp/runtime/vm/unit.h"
46 #include "hphp/system/systemlib.h"
48 #include "hphp/util/logger.h"
49 #include "hphp/util/process.h"
50 #include "hphp/util/string-vsnprintf.h"
51 #include "hphp/util/text-util.h"
53 #include <folly/Format.h>
55 #include <boost/format.hpp>
57 #include <algorithm>
59 namespace HPHP {
61 using std::string;
63 ///////////////////////////////////////////////////////////////////////////////
64 // static strings
66 const StaticString
67 s_offsetExists("offsetExists"),
68 s___call("__call"),
69 s___callStatic("__callStatic"),
70 s___invoke("__invoke"),
71 s_self("self"),
72 s_parent("parent"),
73 s_static("static");
75 const StaticString s_cmpWithCollection(
76 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
77 "a collection with an integer, double, string, array, or object"
79 const StaticString s_cmpWithVec(
80 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
81 "a vec with a non-vec"
83 const StaticString s_cmpWithDict(
84 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
85 "dicts"
87 const StaticString s_cmpWithKeyset(
88 "Cannot use relational comparison operators (<, <=, >, >=, <=>) to compare "
89 "keysets"
92 ///////////////////////////////////////////////////////////////////////////////
94 bool array_is_valid_callback(const Array& arr) {
95 if (!arr.isPHPArray() && !arr.isVecArray()) return false;
96 if (arr.size() != 2 || !arr.exists(int64_t(0)) || !arr.exists(int64_t(1))) {
97 return false;
99 auto const elem0 = arr.rvalAt(0).unboxed();
100 if (!isStringType(elem0.type()) && !isObjectType(elem0.type())) {
101 return false;
103 auto const elem1 = arr.rvalAt(1).unboxed();
104 if (!isStringType(elem1.type())) {
105 return false;
107 return true;
110 const StaticString
111 s__invoke("__invoke"),
112 s_Closure__invoke("Closure::__invoke"),
113 s_colon2("::");
115 bool is_callable(const Variant& v) {
116 ObjectData* obj = nullptr;
117 HPHP::Class* cls = nullptr;
118 StringData* invName = nullptr;
119 bool dynamic;
120 const HPHP::Func* f = vm_decode_function(v, GetCallerFrame(), false, obj, cls,
121 invName, dynamic,
122 DecodeFlags::LookupOnly);
123 if (invName != nullptr) {
124 decRefStr(invName);
126 return f != nullptr && !f->isAbstract();
129 bool is_callable(const Variant& v, bool syntax_only, RefData* name) {
130 bool ret = true;
131 if (LIKELY(!syntax_only)) {
132 ret = is_callable(v);
133 if (LIKELY(!name)) return ret;
136 auto const tv_func = v.asCell();
137 if (isStringType(tv_func->m_type)) {
138 if (name) *name->var() = v;
139 return ret;
142 if (isArrayType(tv_func->m_type) || isVecType(tv_func->m_type)) {
143 auto const arr = Array(tv_func->m_data.parr);
144 auto const clsname = arr.rvalAt(int64_t(0)).unboxed();
145 auto const mthname = arr.rvalAt(int64_t(1)).unboxed();
147 if (arr.size() != 2 ||
148 clsname.is_dummy() ||
149 !isStringType(mthname.type())) {
150 if (name) *name->var() = array_string;
151 return false;
154 StringData* clsString = nullptr;
155 if (isObjectType(clsname.type())) {
156 clsString = clsname.val().pobj->getClassName().get();
157 } else if (isStringType(clsname.type())) {
158 clsString = clsname.val().pstr;
159 } else {
160 if (name) *name->var() = array_string;
161 return false;
164 if (name) {
165 *name->var() = concat3(String{clsString},
166 s_colon2,
167 String{mthname.val().pstr});
169 return ret;
172 if (tv_func->m_type == KindOfObject) {
173 ObjectData *d = tv_func->m_data.pobj;
174 const Func* invoke = d->getVMClass()->lookupMethod(s__invoke.get());
175 if (name) {
176 if (d->instanceof(c_Closure::classof())) {
177 // Hack to stop the mangled name from showing up
178 *name->var() = s_Closure__invoke;
179 } else {
180 *name->var() = d->getClassName().asString() + "::__invoke";
183 return invoke != nullptr;
186 return false;
189 const HPHP::Func*
190 vm_decode_function(const Variant& function,
191 ActRec* ar,
192 bool forwarding,
193 ObjectData*& this_,
194 HPHP::Class*& cls,
195 StringData*& invName,
196 bool& dynamic,
197 DecodeFlags flags /* = DecodeFlags::Warn */) {
198 invName = nullptr;
199 dynamic = true;
200 if (function.isString() || function.isArray()) {
201 HPHP::Class* ctx = nullptr;
202 if (ar) ctx = arGetContextClass(ar);
203 // Decode the 'function' parameter into this_, cls, name, pos, and
204 // nameContainsClass.
205 this_ = nullptr;
206 cls = nullptr;
207 String name;
208 int pos = String::npos;
209 bool nameContainsClass = false;
210 if (function.isString()) {
211 // If 'function' is a string we simply assign it to name and
212 // leave this_ and cls set to NULL.
213 name = function.toString();
214 pos = name.find("::");
215 nameContainsClass =
216 (pos != 0 && pos != String::npos && pos + 2 < name.size());
217 } else {
218 // If 'function' is an array with exactly two indices 0 and 1, we
219 // assign the value at index 1 to name and we use the value at index
220 // 0 to populate cls (if the value is a string) or this_ and cls (if
221 // the value is an object).
222 assert(function.isArray());
223 Array arr = function.toArray();
224 if (!array_is_valid_callback(arr)) {
225 if (flags == DecodeFlags::Warn) {
226 throw_invalid_argument("function: not a valid callback array");
228 return nullptr;
230 Variant elem1 = arr[1];
231 name = elem1.toString();
232 pos = name.find("::");
233 nameContainsClass =
234 (pos != 0 && pos != String::npos && pos + 2 < name.size());
235 Variant elem0 = arr[0];
236 if (elem0.isString()) {
237 String sclass = elem0.toString();
238 if (sclass.get()->isame(s_self.get())) {
239 if (ctx) {
240 cls = ctx;
242 if (!nameContainsClass) {
243 forwarding = true;
245 } else if (sclass.get()->isame(s_parent.get())) {
246 if (ctx && ctx->parent()) {
247 cls = ctx->parent();
249 if (!nameContainsClass) {
250 forwarding = true;
252 } else if (sclass.get()->isame(s_static.get())) {
253 if (ar && ar->func()->cls()) {
254 if (ar->hasThis()) {
255 cls = ar->getThis()->getVMClass();
256 } else {
257 cls = ar->getClass();
260 } else {
261 if (flags == DecodeFlags::Warn && nameContainsClass) {
262 String nameClass = name.substr(0, pos);
263 if (nameClass.get()->isame(s_self.get()) ||
264 nameClass.get()->isame(s_static.get())) {
265 raise_warning("behavior of call_user_func(array('%s', '%s')) "
266 "is undefined", sclass.data(), name.data());
269 cls = Unit::loadClass(sclass.get());
271 if (!cls) {
272 if (flags == DecodeFlags::Warn) {
273 throw_invalid_argument("function: class not found");
275 return nullptr;
277 } else {
278 assert(elem0.isObject());
279 this_ = elem0.getObjectData();
280 cls = this_->getVMClass();
284 HPHP::Class* cc = cls;
285 if (nameContainsClass) {
286 String c = name.substr(0, pos);
287 name = name.substr(pos + 2);
288 if (c.get()->isame(s_self.get())) {
289 if (cls) {
290 cc = cls;
291 } else if (ctx) {
292 cc = ctx;
294 if (!this_) {
295 forwarding = true;
297 } else if (c.get()->isame(s_parent.get())) {
298 if (cls) {
299 cc = cls->parent();
300 } else if (ctx && ctx->parent()) {
301 cc = ctx->parent();
303 if (!this_) {
304 forwarding = true;
306 } else if (c.get()->isame(s_static.get())) {
307 if (ar && ar->func()->cls()) {
308 if (ar->hasThis()) {
309 cc = ar->getThis()->getVMClass();
310 } else {
311 cc = ar->getClass();
314 } else {
315 cc = Unit::loadClass(c.get());
317 if (!cc) {
318 if (flags == DecodeFlags::Warn) {
319 throw_invalid_argument("function: class not found");
321 return nullptr;
323 if (cls) {
324 if (!cls->classof(cc)) {
325 if (flags == DecodeFlags::Warn) {
326 raise_warning("call_user_func expects parameter 1 to be a valid "
327 "callback, class '%s' is not a subclass of '%s'",
328 cls->preClass()->name()->data(),
329 cc->preClass()->name()->data());
331 return nullptr;
334 // If there is not a current instance, cc trumps cls.
335 if (!this_) {
336 cls = cc;
340 if (!cls) {
341 HPHP::Func* f = HPHP::Unit::loadFunc(name.get());
342 if (!f) {
343 if (flags == DecodeFlags::Warn) {
344 throw_invalid_argument("function: method '%s' not found",
345 name.data());
347 return nullptr;
349 assert(f && f->preClass() == nullptr);
350 return f;
352 assert(cls);
353 CallType lookupType = this_ ? CallType::ObjMethod : CallType::ClsMethod;
354 auto f = lookupMethodCtx(cc, name.get(), ctx, lookupType);
355 if (f && (f->attrs() & AttrStatic)) {
356 // If we found a method and its static, null out this_
357 this_ = nullptr;
358 } else {
359 if (!this_ && ar && ar->func()->cls() && ar->hasThis()) {
360 // If we did not find a static method AND this_ is null AND there is a
361 // frame ar, check if the current instance from ar is compatible
362 auto const obj = ar->getThis();
363 if (obj->instanceof(cls)) {
364 this_ = obj;
365 cls = obj->getVMClass();
368 if (!f) {
369 if (this_) {
370 // If this_ is non-null AND we could not find a method, try
371 // looking up __call in cls's method table
372 f = cls->lookupMethod(s___call.get());
373 assert(!f || !(f->attrs() & AttrStatic));
375 if (!f && lookupType == CallType::ClsMethod) {
376 f = cls->lookupMethod(s___callStatic.get());
377 assert(!f || (f->attrs() & AttrStatic));
378 this_ = nullptr;
380 if (f && (cc == cls || cc->lookupMethod(f->name()))) {
381 // We found __call or __callStatic!
382 // Stash the original name into invName.
383 invName = name.get();
384 invName->incRefCount();
385 } else {
386 // Bail out if we couldn't find the method or __call
387 if (flags == DecodeFlags::Warn) {
388 throw_invalid_argument("function: method '%s' not found",
389 name.data());
391 return nullptr;
396 if (!this_ && !f->isStaticInPrologue()) {
397 if (flags == DecodeFlags::Warn) raise_missing_this(f);
398 if (flags != DecodeFlags::LookupOnly && f->attrs() & AttrRequiresThis) {
399 return nullptr;
403 assert(f && f->preClass());
404 // If this_ is non-NULL, then this_ is the current instance and cls is
405 // the class of the current instance.
406 assert(!this_ || this_->getVMClass() == cls);
407 // If we are doing a forwarding call and this_ is null, set cls
408 // appropriately to propagate the current late bound class.
409 if (!this_ && forwarding && ar && ar->func()->cls()) {
410 auto const fwdCls = ar->hasThis() ?
411 ar->getThis()->getVMClass() : ar->getClass();
413 // Only forward the current late bound class if it is the same or
414 // a descendent of cls
415 if (fwdCls->classof(cls)) {
416 cls = fwdCls;
420 return f;
422 if (function.isObject()) {
423 this_ = function.asCObjRef().get();
424 cls = nullptr;
425 dynamic = false;
426 const HPHP::Func *f = this_->getVMClass()->lookupMethod(s___invoke.get());
427 if (f != nullptr && f->isStaticInPrologue()) {
428 // If __invoke is static, invoke it as such
429 cls = this_->getVMClass();
430 this_ = nullptr;
432 if (flags == DecodeFlags::Warn && f == nullptr) {
433 raise_warning("call_user_func() expects parameter 1 to be a valid "
434 "callback, object of class %s given (no __invoke "
435 "method found)", this_->getVMClass()->name()->data());
437 return f;
439 if (flags == DecodeFlags::Warn) {
440 throw_invalid_argument("function: not string, closure, or array");
442 return nullptr;
445 Variant vm_call_user_func(const Variant& function, const Variant& params,
446 bool forwarding /* = false */,
447 bool checkRef /* = false */) {
448 ObjectData* obj = nullptr;
449 Class* cls = nullptr;
450 CallerFrame cf;
451 StringData* invName = nullptr;
452 bool dynamic;
453 const Func* f = vm_decode_function(function, cf(), forwarding,
454 obj, cls, invName, dynamic);
455 if (f == nullptr || (!isContainer(params) && !params.isNull())) {
456 return uninit_null();
458 auto ret = Variant::attach(
459 g_context->invokeFunc(f, params, obj, cls,
460 nullptr, invName, ExecutionContext::InvokeCuf,
461 false, dynamic, checkRef)
463 if (UNLIKELY(ret.getRawType() == KindOfRef)) {
464 tvUnbox(*ret.asTypedValue());
466 return ret;
469 static Variant invoke_failed(const char *func,
470 bool fatal /* = true */) {
471 if (fatal) {
472 throw ExtendedException("(1) call the function without enough arguments OR "
473 "(2) Unable to find function \"%s\" OR "
474 "(3) function was not in invoke table OR "
475 "(4) function was renamed to something else.",
476 func);
478 raise_warning("call_user_func to non-existent function %s", func);
479 return false;
482 static Variant
483 invoke(const String& function, const Variant& params, strhash_t /*hash*/,
484 bool /*tryInterp*/, bool fatal, bool useWeakTypes = false) {
485 Func* func = Unit::loadFunc(function.get());
486 if (func && (isContainer(params) || params.isNull())) {
487 auto ret = Variant::attach(
488 g_context->invokeFunc(func, params, nullptr, nullptr,
489 nullptr, nullptr, ExecutionContext::InvokeNormal,
490 useWeakTypes)
492 if (UNLIKELY(ret.getRawType() == KindOfRef)) {
493 tvUnbox(*ret.asTypedValue());
495 return ret;
497 return invoke_failed(function.c_str(), fatal);
500 // Declared in externals.h. If you're considering calling this
501 // function for some new code, please reconsider.
502 Variant invoke(const char *function, const Variant& params, strhash_t hash /* = -1 */,
503 bool tryInterp /* = true */, bool fatal /* = true */,
504 bool useWeakTypes /* = false */) {
505 String funcName(function, CopyString);
506 return invoke(funcName, params, hash, tryInterp, fatal, useWeakTypes);
509 Variant invoke_static_method(const String& s, const String& method,
510 const Variant& params, bool fatal /* = true */) {
511 HPHP::Class* class_ = Unit::lookupClass(s.get());
512 if (class_ == nullptr) {
513 o_invoke_failed(s.data(), method.data(), fatal);
514 return uninit_null();
516 const HPHP::Func* f = class_->lookupMethod(method.get());
517 if (f == nullptr || !(f->attrs() & AttrStatic) ||
518 (!isContainer(params) && !params.isNull())) {
519 o_invoke_failed(s.data(), method.data(), fatal);
520 return uninit_null();
522 auto ret = Variant::attach(
523 g_context->invokeFunc(f, params, nullptr, class_)
525 if (UNLIKELY(ret.getRawType() == KindOfRef)) {
526 tvUnbox(*ret.asTypedValue());
528 return ret;
531 Variant o_invoke_failed(const char *cls, const char *meth,
532 bool fatal /* = true */) {
533 if (fatal) {
534 string msg = "Unknown method ";
535 msg += cls;
536 msg += "::";
537 msg += meth;
538 raise_fatal_error(msg.c_str());
539 } else {
540 raise_warning("call_user_func to non-existent method %s::%s", cls, meth);
541 return false;
545 template<class EF, class NF>
546 void missing_this_check_helper(const Func* f, EF ef, NF nf) {
547 if (f->attrs() & AttrRequiresThis) {
548 ef();
549 return;
552 if (RuntimeOption::EvalRaiseMissingThis && !f->isStatic()) {
553 nf();
554 return;
558 void raise_missing_this(const Func* f) {
559 missing_this_check_helper(
561 [&] {
562 raise_error("Non-static method %s() cannot be called statically",
563 f->fullName()->data());
565 [&] {
566 auto constexpr msg =
567 "Non-static method %s() should not be called statically";
569 if (RuntimeOption::PHP7_DeprecationWarnings) {
570 raise_deprecated(msg, f->fullName()->data());
571 } else {
572 raise_strict_warning(msg, f->fullName()->data());
577 bool needs_missing_this_check(const Func* f) {
578 bool ret = false;
579 missing_this_check_helper(
581 [&] { ret = true; },
582 [&] { ret = true; }
584 return ret;
587 void NEVER_INLINE raise_null_object_prop() {
588 raise_notice("Trying to get property of non-object");
591 void NEVER_INLINE throw_null_get_object_prop() {
592 raise_error("Trying to get property of non-object");
595 void NEVER_INLINE throw_invalid_property_name(const String& name) {
596 if (!name.size()) {
597 raise_error("Cannot access empty property");
599 raise_error("Cannot access property started with '\\0'");
602 void throw_instance_method_fatal(const char *name) {
603 if (!strstr(name, "::__destruct")) {
604 raise_error("Non-static method %s() cannot be called statically", name);
608 void throw_iterator_not_valid() {
609 SystemLib::throwInvalidOperationExceptionObject(
610 "Iterator is not valid");
613 void throw_collection_property_exception() {
614 SystemLib::throwInvalidOperationExceptionObject(
615 "Cannot access a property on a collection");
618 void throw_invalid_collection_parameter() {
619 SystemLib::throwInvalidArgumentExceptionObject(
620 "Parameter must be an array or an instance of Traversable");
623 void throw_invalid_operation_exception(StringData* str) {
624 SystemLib::throwInvalidOperationExceptionObject(Variant{str});
627 void throw_arithmetic_error(StringData* str) {
628 SystemLib::throwArithmeticErrorObject(Variant{str});
631 void throw_division_by_zero_error(StringData *str) {
632 SystemLib::throwDivisionByZeroErrorObject(Variant{str});
635 void throw_collection_compare_exception() {
636 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithCollection);
639 void throw_vec_compare_exception() {
640 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithVec);
643 void throw_dict_compare_exception() {
644 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithDict);
647 void throw_keyset_compare_exception() {
648 SystemLib::throwInvalidOperationExceptionObject(s_cmpWithKeyset);
651 void throw_param_is_not_container() {
652 static const string msg("Parameter must be an array or collection");
653 SystemLib::throwInvalidArgumentExceptionObject(msg);
656 void throw_invalid_inout_base() {
657 SystemLib::throwInvalidArgumentExceptionObject(
658 "Parameters marked inout must be contained in locals, vecs, dicts, "
659 "keysets, and arrays"
663 void throw_cannot_modify_immutable_object(const char* className) {
664 auto msg = folly::sformat(
665 "Cannot modify immutable object of type {}",
666 className
668 SystemLib::throwInvalidOperationExceptionObject(msg);
671 void throw_object_forbids_dynamic_props(const char* className) {
672 auto msg = folly::sformat(
673 "Class {} does not allow use of dynamic (non-declared) properties",
674 className
676 SystemLib::throwInvalidOperationExceptionObject(msg);
679 void throw_cannot_modify_immutable_prop(const char* className,
680 const char* propName)
682 auto msg = folly::sformat(
683 "Cannot modify immutable property {} of class {} after construction",
684 propName, className
686 SystemLib::throwInvalidOperationExceptionObject(msg);
689 void throw_cannot_bind_immutable_prop(const char* className,
690 const char* propName)
692 auto msg = folly::sformat(
693 "Cannot bind immutable property {} of class {}",
694 propName, className
696 SystemLib::throwInvalidOperationExceptionObject(msg);
699 void check_collection_compare(const ObjectData* obj) {
700 if (obj && obj->isCollection()) throw_collection_compare_exception();
703 void check_collection_compare(const ObjectData* obj1, const ObjectData* obj2) {
704 if (obj1 && obj2 && (obj1->isCollection() || obj2->isCollection())) {
705 throw_collection_compare_exception();
709 void check_collection_cast_to_array() {
710 if (RuntimeOption::WarnOnCollectionToArray) {
711 raise_warning("Casting a collection to an array is an expensive operation "
712 "and should be avoided where possible. To convert a "
713 "collection to an array without raising a warning, use the "
714 "toArray() method.");
718 Object create_object_only(const String& s) {
719 return Object::attach(g_context->createObjectOnly(s.get()));
722 Object init_object(const String& s, const Array& params, ObjectData* o) {
723 return Object{g_context->initObject(s.get(), params, o)};
726 Object
727 create_object(const String& s, const Array& params, bool init /* = true */) {
728 return Object::attach(g_context->createObject(s.get(), params, init));
731 void throw_object(const Object& e) {
732 throw req::root<Object>(e);
735 #if ((__GNUC__ != 4) || (__GNUC_MINOR__ != 8))
736 void throw_object(Object&& e) {
737 throw req::root<Object>(std::move(e));
739 #endif
742 * This function is used when another thread is segfaulting---we just
743 * want to wait forever to give it a chance to write a stacktrace file
744 * (and maybe a core file).
746 void pause_forever() {
747 for (;;) sleep(300);
750 bool is_constructor_name(const char* fn) {
751 auto len = strlen(fn);
752 const char construct[] = "__construct";
753 auto clen = sizeof(construct) - 1;
755 if (len >= clen && !strcasecmp(fn + len - clen, construct)) {
756 if (len == clen || (len > clen + 2 &&
757 fn[len - clen - 1] == ':' &&
758 fn[len - clen - 2] == ':')) {
759 return true;
762 return false;
765 void throw_wrong_argument_count_nr(const char *fn, int expected, int got,
766 const char *expectDesc,
767 int level /* = 0 */,
768 TypedValue *rv /* = nullptr */) {
769 if (rv != nullptr) {
770 rv->m_data.num = 0LL;
771 rv->m_type = KindOfNull;
773 std::string msg;
774 if (expected == 1) {
775 msg = (boost::format(Strings::MISSING_ARGUMENT) %
776 fn % expectDesc % got).str();
777 } else {
778 msg = (boost::format(Strings::MISSING_ARGUMENTS) %
779 fn % expectDesc % expected % got).str();
782 if (level == 2) {
783 raise_error(msg);
784 } else {
785 if (is_constructor_name(fn)) {
786 SystemLib::throwExceptionObject(msg);
788 raise_warning(msg);
792 void throw_missing_arguments_nr(const char *fn, int expected, int got,
793 int level /* = 0 */,
794 TypedValue *rv /* = nullptr */) {
795 throw_wrong_argument_count_nr(fn, expected, got, "exactly", level, rv);
798 void throw_toomany_arguments_nr(const char *fn, int expected, int got,
799 int level /* = 0 */,
800 TypedValue *rv /* = nullptr */) {
801 throw_wrong_argument_count_nr(fn, expected, got, "exactly", level, rv);
804 void throw_wrong_arguments_nr(const char *fn, int count, int cmin, int cmax,
805 int level /* = 0 */,
806 TypedValue *rv /* = nullptr */) {
807 if (cmin >= 0 && count < cmin) {
808 if (cmin != cmax) {
809 throw_wrong_argument_count_nr(fn, cmin, count, "at least", level, rv);
810 } else {
811 throw_wrong_argument_count_nr(fn, cmin, count, "exactly", level, rv);
813 return;
815 if (cmax >= 0 && count > cmax) {
816 if (cmin != cmax) {
817 throw_wrong_argument_count_nr(fn, cmax, count, "at most", level, rv);
818 } else {
819 throw_wrong_argument_count_nr(fn, cmax, count, "exactly", level, rv);
821 return;
823 if (rv != nullptr) {
824 rv->m_data.num = 0LL;
825 rv->m_type = KindOfNull;
827 assert(false);
830 void throw_bad_type_exception(const char *fmt, ...) {
831 va_list ap;
832 va_start(ap, fmt);
833 std::string msg;
834 string_vsnprintf(msg, fmt, ap);
835 va_end(ap);
837 raise_warning("Invalid operand type was used: %s", msg.c_str());
840 void throw_expected_array_exception(const char* fn /*=nullptr*/) {
841 if (!fn) {
842 if (auto ar = g_context->getStackFrame()) {
843 fn = ar->m_func->name()->data();
844 } else {
845 fn = "(unknown)";
848 throw_bad_type_exception("%s expects array(s)", fn);
851 void throw_expected_array_or_collection_exception(const char* fn /*=nullptr*/) {
852 if (!fn) {
853 if (auto ar = g_context->getStackFrame()) {
854 fn = ar->m_func->name()->data();
855 } else {
856 fn = "(unknown)";
859 throw_bad_type_exception("%s expects array(s) or collection(s)", fn);
862 void throw_invalid_argument(const char *fmt, ...) {
863 va_list ap;
864 va_start(ap, fmt);
865 string msg;
866 string_vsnprintf(msg, fmt, ap);
867 va_end(ap);
868 raise_warning("Invalid argument: %s", msg.c_str());
871 Variant throw_fatal_unset_static_property(const char *s, const char *prop) {
872 raise_error("Attempt to unset static property %s::$%s", s, prop);
873 return uninit_null();
876 Variant unserialize_ex(const char* str, int len,
877 VariableUnserializer::Type type,
878 const Array& options /* = null_array */) {
879 if (str == nullptr || len <= 0) {
880 return false;
883 VariableUnserializer vu(str, len, type, true, options);
884 Variant v;
885 try {
886 v = vu.unserialize();
887 } catch (FatalErrorException &e) {
888 throw;
889 } catch (InvalidAllowedClassesException &e) {
890 raise_warning(
891 "unserialize(): allowed_classes option should be array or boolean"
893 return false;
894 } catch (Exception &e) {
895 raise_notice("Unable to unserialize: [%.1000s]. %s.", str,
896 e.getMessage().c_str());
897 return false;
899 return v;
902 Variant unserialize_ex(const String& str,
903 VariableUnserializer::Type type,
904 const Array& options /* = null_array */) {
905 return unserialize_ex(str.data(), str.size(), type, options);
908 String concat3(const String& s1, const String& s2, const String& s3) {
909 auto r1 = s1.slice();
910 auto r2 = s2.slice();
911 auto r3 = s3.slice();
912 auto len = r1.size() + r2.size() + r3.size();
913 auto str = String::attach(StringData::Make(len));
914 auto const r = str.mutableData();
915 memcpy(r, r1.data(), r1.size());
916 memcpy(r + r1.size(), r2.data(), r2.size());
917 memcpy(r + r1.size() + r2.size(), r3.data(), r3.size());
918 str.setSize(len);
919 return str;
922 String concat4(const String& s1, const String& s2, const String& s3,
923 const String& s4) {
924 auto r1 = s1.slice();
925 auto r2 = s2.slice();
926 auto r3 = s3.slice();
927 auto r4 = s4.slice();
928 auto len = r1.size() + r2.size() + r3.size() + r4.size();
929 auto str = String::attach(StringData::Make(len));
930 auto const r = str.mutableData();
931 memcpy(r, r1.data(), r1.size());
932 memcpy(r + r1.size(), r2.data(), r2.size());
933 memcpy(r + r1.size() + r2.size(), r3.data(), r3.size());
934 memcpy(r + r1.size() + r2.size() + r3.size(), r4.data(), r4.size());
935 str.setSize(len);
936 return str;
939 static bool invoke_file_impl(Variant& res, const String& path, bool once,
940 const char *currentDir) {
941 bool initial;
942 auto const u = lookupUnit(path.get(), currentDir, &initial);
943 if (u == nullptr) return false;
944 if (!once || initial) {
945 *res.asTypedValue() = g_context->invokeUnit(u);
947 return true;
950 static NEVER_INLINE Variant throw_missing_file(const char* file) {
951 throw PhpFileDoesNotExistException(file);
954 static Variant invoke_file(const String& s,
955 bool once,
956 const char *currentDir) {
957 Variant r;
958 if (invoke_file_impl(r, s, once, currentDir)) {
959 return r;
961 return throw_missing_file(s.c_str());
964 Variant include_impl_invoke(const String& file, bool once,
965 const char *currentDir) {
966 if (FileUtil::isAbsolutePath(file.toCppString())) {
967 if (RuntimeOption::SandboxMode || !RuntimeOption::AlwaysUseRelativePath) {
968 try {
969 return invoke_file(file, once, currentDir);
970 } catch(PhpFileDoesNotExistException &e) {}
973 try {
974 String rel_path(FileUtil::relativePath(RuntimeOption::SourceRoot,
975 string(file.data())));
977 // Don't try/catch - We want the exception to be passed along
978 return invoke_file(rel_path, once, currentDir);
979 } catch(PhpFileDoesNotExistException &e) {
980 throw PhpFileDoesNotExistException(file.c_str());
982 } else {
983 // Don't try/catch - We want the exception to be passed along
984 return invoke_file(file, once, currentDir);
989 * Used by include_impl. resolve_include() needs some way of checking the
990 * existence of a file path, which for hphpc means attempting to invoke it.
991 * This struct carries some context information needed for the invocation, as
992 * well as a place for the return value of invoking the file.
994 struct IncludeImplInvokeContext {
995 bool once;
996 const char* currentDir;
998 Variant returnValue;
1001 static bool include_impl_invoke_context(const String& file, void* ctx) {
1002 struct IncludeImplInvokeContext* context = (IncludeImplInvokeContext*)ctx;
1003 bool invoked_file = false;
1004 try {
1005 context->returnValue = include_impl_invoke(file, context->once,
1006 context->currentDir);
1007 invoked_file = true;
1008 } catch (PhpFileDoesNotExistException& e) {
1009 context->returnValue = false;
1011 return invoked_file;
1015 * tryFile is a pointer to a function that resolve_include() will use to
1016 * determine if a path references a real file. ctx is a pointer to some context
1017 * information that will be passed through to tryFile. (It's a hacky closure)
1019 String resolve_include(const String& file, const char* currentDir,
1020 bool (*tryFile)(const String& file, void*), void* ctx) {
1021 const char* c_file = file.data();
1023 if (!File::IsPlainFilePath(file)) {
1024 // URIs don't have an include path
1025 if (tryFile(file, ctx)) {
1026 return file;
1029 } else if (FileUtil::isAbsolutePath(file.toCppString())) {
1030 String can_path = FileUtil::canonicalize(file);
1032 if (tryFile(can_path, ctx)) {
1033 return can_path;
1036 } else if ((c_file[0] == '.' && (c_file[1] == '/' || (
1037 c_file[1] == '.' && c_file[2] == '/')))) {
1039 String path(String(g_context->getCwd() + "/" + file));
1040 String can_path = FileUtil::canonicalize(path);
1042 if (tryFile(can_path, ctx)) {
1043 return can_path;
1046 } else {
1047 auto const& includePaths = RID().getIncludePaths();
1049 for (auto const& includePath : includePaths) {
1050 String path("");
1051 auto const is_stream_wrapper =
1052 includePath.find("://") != std::string::npos;
1054 if (!is_stream_wrapper && !FileUtil::isAbsolutePath(includePath)) {
1055 path += (g_context->getCwd() + "/");
1058 path += includePath;
1060 if (path[path.size() - 1] != '/') {
1061 path += "/";
1064 path += file;
1066 String can_path;
1067 if (!is_stream_wrapper) {
1068 can_path = FileUtil::canonicalize(path);
1069 } else {
1070 can_path = String(path.c_str());
1073 if (tryFile(can_path, ctx)) {
1074 return can_path;
1078 if (FileUtil::isAbsolutePath(currentDir)) {
1079 String path(currentDir);
1080 path += "/";
1081 path += file;
1082 String can_path = FileUtil::canonicalize(path);
1084 if (tryFile(can_path, ctx)) {
1085 return can_path;
1087 } else {
1088 String path(g_context->getCwd() + "/" + currentDir + file);
1089 String can_path = FileUtil::canonicalize(path);
1091 if (tryFile(can_path, ctx)) {
1092 return can_path;
1097 return String();
1100 static Variant include_impl(const String& file, bool once,
1101 const char *currentDir, bool required,
1102 bool raiseNotice) {
1103 struct IncludeImplInvokeContext ctx = {once, currentDir};
1104 String can_path = resolve_include(file, currentDir,
1105 include_impl_invoke_context, (void*)&ctx);
1107 if (can_path.isNull()) {
1108 // Failure
1109 if (raiseNotice) {
1110 raise_notice("Tried to invoke %s but file not found.", file.data());
1112 if (required) {
1113 String ms = "Required file that does not exist: ";
1114 ms += file;
1115 raise_fatal_error(ms.data());
1117 return false;
1120 return ctx.returnValue;
1123 Variant require(const String& file,
1124 bool once,
1125 const char* currentDir,
1126 bool raiseNotice) {
1127 return include_impl(file, once, currentDir, true, raiseNotice);
1130 bool function_exists(const String& function_name) {
1131 auto f = Unit::lookupFunc(function_name.get());
1132 return (f != nullptr) &&
1133 (f->builtinFuncPtr() != Native::unimplementedWrapper);
1136 ///////////////////////////////////////////////////////////////////////////////
1137 // debugger and code coverage instrumentation
1139 void throw_exception(const Object& e) {
1140 if (!e.instanceof(SystemLib::s_ThrowableClass)) {
1141 raise_error("Exceptions must implement the Throwable interface.");
1143 DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(e.get()));
1144 throw req::root<Object>(e);
1147 ///////////////////////////////////////////////////////////////////////////////