/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/vm/native.h" #include "hphp/runtime/vm/runtime.h" namespace HPHP { namespace Native { ////////////////////////////////////////////////////////////////////////////// BuiltinFunctionMap s_builtinFunctions; ClassConstantMapMap s_class_constant_map; /** * Native function caller * Remaps an array of TypedValue* (such as from an ActRec*) * into a call to a native (C++) function using type hinting * from HPHP::Func */ // Return a function pointer type for calling a builtin with a given // return value and args. template struct NativeFunction { typedef Ret (*type)(Args...); }; // Recursively pack all parameters up to call a native builtin. template struct NativeFuncCaller; template struct NativeFuncCaller { template static Ret call(const Func* func, TypedValue* tvs, Args... args) { typedef NativeFuncCaller NextArgT; DataType type = func->params()[CurArg].builtinType(); if (type == KindOfDouble) { // pass TV.m_data.dbl by value with C++ calling convention for doubles return NextArgT::call(func, tvs - 1, args..., tvs->m_data.dbl); } if (type == KindOfInt64 || type == KindOfBoolean) { // pass TV.m_data.num by value return NextArgT::call(func, tvs - 1, args..., tvs->m_data.num); } if (IS_STRING_TYPE(type) || type == KindOfArray || type == KindOfObject || type == KindOfResource) { // pass ptr to TV.m_data for String&, Array&, or Object& return NextArgT::call(func, tvs - 1, args..., &tvs->m_data); } // final case is for passing full value as Variant& return NextArgT::call(func, tvs - 1, args..., tvs); } }; template struct NativeFuncCaller { template static Ret call(const Func* f, TypedValue*, Args... args) { typedef typename NativeFunction::type FuncType; return reinterpret_cast(f->nativeFuncPtr())(args...); } }; #define NFC_CALL(numargs, rettype, func, ...) \ switch (numargs) { \ case 0: return NativeFuncCaller::call(func, __VA_ARGS__); \ case 1: return NativeFuncCaller::call(func, __VA_ARGS__); \ case 2: return NativeFuncCaller::call(func, __VA_ARGS__); \ case 3: return NativeFuncCaller::call(func, __VA_ARGS__); \ case 4: return NativeFuncCaller::call(func, __VA_ARGS__); \ case 5: return NativeFuncCaller::call(func, __VA_ARGS__); \ default: assert(false); \ } // Caller is expected to always pass a String or Object for ctx (or nullptr) template static Ret makeNativeCall(const Func* f, TypedValue* args, size_t numArgs, TypedValue *ctx = nullptr) { static_assert(kMaxBuiltinArgs == 5, "makeNativeCall needs updates for kMaxBuiltinArgs"); if (ctx == nullptr) { NFC_CALL(numArgs, Ret, f, args); } else if (ctx->m_type == KindOfClass) { NFC_CALL(numArgs, Ret, f, args, ctx->m_data.pcls); } else { assert(ctx->m_type == KindOfObject); NFC_CALL(numArgs, Ret, f, args, &ctx->m_data); } not_reached(); } template static int64_t makeNativeRefCall(const Func* f, Ret* ret, TypedValue* args, size_t numArgs, TypedValue* ctx) { static_assert(kMaxBuiltinArgs == 5, "makeNativeRefCall needs updates for kMaxBuiltinArgs"); if (ctx == nullptr) { NFC_CALL(numArgs, int64_t, f, args, ret); } else if (ctx->m_type == KindOfClass) { NFC_CALL(numArgs, int64_t, f, args, ret, ctx->m_data.pcls); } else { assert(ctx->m_type == KindOfObject); NFC_CALL(numArgs, int64_t, f, args, ret, &ctx->m_data); } not_reached(); } #undef NFC_CALL ////////////////////////////////////////////////////////////////////////////// bool coerceFCallArgs(TypedValue* args, int32_t numArgs, int32_t numNonDefault, const Func* func) { assert(numArgs == func->numParams()); bool zendParamMode = func->info() && func->info()->attribute & ClassInfo::ZendParamMode; for (int32_t i = 0; (i < numNonDefault) && (i < numArgs); i++) { const Func::ParamInfo& pi = func->params()[i]; #define CASE(kind) \ case KindOf##kind: \ if (zendParamMode) { \ if (!tvCoerceParamTo##kind##InPlace(&args[-i])) { \ raise_param_type_warning( \ func->name()->data(), \ i+1, \ KindOf##kind, \ args[-i].m_type \ ); \ return false; \ } \ } else { \ tvCastTo##kind##InPlace(&args[-i]); \ } \ break; /* end of case */ switch (pi.builtinType()) { CASE(Boolean) CASE(Int64) CASE(Double) CASE(String) CASE(Array) CASE(Object) CASE(Resource) case KindOfUnknown: break; default: not_reached(); } #undef CASE } return true; } void callFunc(const Func* func, TypedValue *ctx, TypedValue* args, int32_t numArgs, TypedValue &ret) { ret.m_type = func->returnType(); switch (func->returnType()) { case KindOfBoolean: ret.m_data.num = makeNativeCall(func, args, numArgs, ctx); break; case KindOfNull: /* void return type */ case KindOfInt64: ret.m_data.num = makeNativeCall(func, args, numArgs, ctx); break; case KindOfString: case KindOfStaticString: case KindOfArray: case KindOfObject: case KindOfResource: makeNativeRefCall(func, &ret.m_data, args, numArgs, ctx); if (ret.m_data.num == 0) { ret.m_type = KindOfNull; } break; case KindOfUnknown: makeNativeRefCall(func, &ret, args, numArgs, ctx); if (ret.m_type == KindOfUninit) { ret.m_type = KindOfNull; } break; default: not_reached(); } } static inline int32_t minNumArgs(ActRec *ar) { int32_t num = 0; auto func = ar->m_func; auto numArgs = func->numParams(); const Func::ParamInfoVec& paramInfo = func->params(); while ((num < numArgs) && (paramInfo[num].funcletOff() == InvalidAbsoluteOffset)) { ++num; } return num; } static const StringData* getInvokeName(ActRec *ar) { if (ar->hasInvName()) { return ar->getInvName(); } auto func = ar->m_func; auto cls = func->cls(); if (!cls) { return func->name(); } String clsname(cls->name()); String funcname(func->name()); return makeStaticString(clsname + "::" + funcname); } static inline bool nativeWrapperCheckArgs(ActRec* ar) { auto func = ar->m_func; auto numArgs = func->numParams(); auto numNonDefault = ar->numArgs(); if (numNonDefault < numArgs) { const Func::ParamInfoVec& paramInfo = func->params(); if (InvalidAbsoluteOffset == paramInfo[numNonDefault].funcletOff()) { // There's at least one non-default param which wasn't passed throw_wrong_arguments_nr(getInvokeName(ar)->data(), numNonDefault, minNumArgs(ar), numArgs, 1); return false; } } else if (numNonDefault > numArgs) { // Too many arguments passed, raise a warning ourselves this time throw_wrong_arguments_nr(getInvokeName(ar)->data(), numNonDefault, minNumArgs(ar), numArgs, 1); return false; } // Looks good return true; } TypedValue* functionWrapper(ActRec* ar) { auto func = ar->m_func; auto numArgs = func->numParams(); auto numNonDefault = ar->numArgs(); TypedValue* args = ((TypedValue*)ar) - 1; TypedValue rv; rv.m_type = KindOfNull; if (LIKELY(numNonDefault == numArgs) || LIKELY(nativeWrapperCheckArgs(ar))) { coerceFCallArgs(args, numArgs, numNonDefault, func); callFunc(func, nullptr, args, numArgs, rv); } frame_free_locals_no_this_inl(ar, func->numLocals()); assert(rv.m_type != KindOfUninit); tvCopy(rv, ar->m_r); return &ar->m_r; } TypedValue* methodWrapper(ActRec* ar) { auto func = ar->m_func; auto numArgs = func->numParams(); auto numNonDefault = ar->numArgs(); bool isStatic = func->isStatic(); TypedValue* args = ((TypedValue*)ar) - 1; TypedValue rv; rv.m_type = KindOfNull; if (LIKELY(numNonDefault == numArgs) || LIKELY(nativeWrapperCheckArgs(ar))) { coerceFCallArgs(args, numArgs, numNonDefault, func); // Prepend a context arg for methods // KindOfClass when it's being called statically Foo::bar() // KindOfObject when it's being called on an instance $foo->bar() TypedValue ctx; if (ar->hasThis()) { if (isStatic) { throw_instance_method_fatal(getInvokeName(ar)->data()); } ctx.m_type = KindOfObject; ctx.m_data.pobj = ar->getThis(); } else { if (!isStatic) { throw_instance_method_fatal(getInvokeName(ar)->data()); } ctx.m_type = KindOfClass; ctx.m_data.pcls = const_cast(ar->getClass()); } callFunc(func, &ctx, args, numArgs, rv); } if (isStatic) { frame_free_locals_no_this_inl(ar, func->numLocals()); } else { frame_free_locals_inl(ar, func->numLocals()); } assert(rv.m_type != KindOfUninit); tvCopy(rv, ar->m_r); return &ar->m_r; } TypedValue* unimplementedWrapper(ActRec* ar) { auto func = ar->m_func; auto cls = func->cls(); if (cls) { raise_error("Call to unimplemented native method %s::%s()", cls->name()->data(), func->name()->data()); if (func->isStatic()) { frame_free_locals_no_this_inl(ar, func->numParams()); } else { frame_free_locals_inl(ar, func->numParams()); } } else { raise_error("Call to unimplemented native function %s()", func->name()->data()); frame_free_locals_no_this_inl(ar, func->numParams()); } ar->m_r.m_type = KindOfNull; return &ar->m_r; } ////////////////////////////////////////////////////////////////////////////// }} // namespace HPHP::Native