[Fix] is_callable should respect __call and __callStatic
[hiphop-php.git] / src / runtime / eval / ast / method_statement.cpp
blob0f2cd1536fb315a73aea94a3c083d550365cd643
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- 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 <runtime/eval/ast/expression.h>
17 #include <runtime/eval/ast/method_statement.h>
18 #include <runtime/eval/ast/statement_list_statement.h>
19 #include <runtime/eval/runtime/variable_environment.h>
20 #include <runtime/eval/ast/class_statement.h>
21 #include <runtime/eval/runtime/eval_state.h>
22 #include <runtime/eval/ast/function_call_expression.h>
24 namespace HPHP {
25 namespace Eval {
26 using namespace std;
27 ///////////////////////////////////////////////////////////////////////////////
29 MethodStatement::MethodStatement(STATEMENT_ARGS, const string &name,
30 const ClassStatement *cls, int modifiers,
31 const string &doc)
32 : FunctionStatement(STATEMENT_PASS, name, doc), m_class(cls),
33 m_modifiers(modifiers),
34 m_fullName(StringData::GetStaticString(
35 string(cls->name().c_str()) + "::" + name)) {
36 if ((m_modifiers & (ClassStatement::Public | ClassStatement::Protected |
37 ClassStatement::Private)) == 0) {
38 m_modifiers |= ClassStatement::Public;
40 m_callInfo.m_invoker = (void*)MethInvoker;
41 m_callInfo.m_invokerFewArgs = (void*)MethInvokerFewArgs;
42 if (m_modifiers & ClassStatement::Static) {
43 m_callInfo.m_flags |= CallInfo::StaticMethod;
44 } else {
45 m_callInfo.m_flags |= CallInfo::Method;
47 if (m_modifiers & ClassStatement::Protected) {
48 m_callInfo.m_flags |= CallInfo::Protected;
49 } else if (m_modifiers & ClassStatement::Private) {
50 m_callInfo.m_flags |= CallInfo::Private;
54 void MethodStatement::setPublic() {
55 m_modifiers = ClassStatement::Public;
58 String MethodStatement::fullName() const {
59 return m_fullName;
62 void MethodStatement::eval(VariableEnvironment &env) const {
63 if (env.isGotoing()) return;
64 ENTER_STMT;
65 // register with reflection, invoke, etc.
68 LVariableTable *MethodStatement::getStaticVars(VariableEnvironment &env)
69 const {
70 return &RequestEvalState::getMethodStatics(this, env.currentClass());
73 Variant MethodStatement::invokeInstance(CObjRef obj, CArrRef params,
74 bool check /* = true */) const {
75 if (getModifiers() & ClassStatement::Static) {
76 return invokeStatic(obj->o_getClassName(), params, check);
78 if (check) attemptAccess(FrameInjection::GetClassName(false));
79 // The debug frame should have been pushed at ObjectMethodExpression
80 DECLARE_THREAD_INFO_NOINIT
81 MethScopeVariableEnvironment env(this);
82 env.setCurrentObject(obj);
83 String clsName(m_class->name());
84 EvalFrameInjection fi(clsName, m_fullName->data(), env,
85 loc()->file, obj.get(), FrameInjection::ObjectMethod);
86 if (m_ref) {
87 return strongBind(invokeImpl(env, params));
89 return invokeImpl(env, params);
92 Variant MethodStatement::invokeInstanceFewArgs(CObjRef obj, int count,
93 INVOKE_FEW_ARGS_IMPL_ARGS, bool check) const {
94 if (getModifiers() & ClassStatement::Static) {
95 return invokeStaticFewArgs(obj->o_getClassName(), count,
96 INVOKE_FEW_ARGS_PASS_ARGS, check);
98 if (check) attemptAccess(FrameInjection::GetClassName(false));
99 // The debug frame should have been pushed at ObjectMethodExpression
100 DECLARE_THREAD_INFO_NOINIT
101 MethScopeVariableEnvironment env(this);
102 env.setCurrentObject(obj);
103 String clsName(m_class->name());
104 EvalFrameInjection fi(clsName, m_fullName->data(), env,
105 loc()->file, obj.get(), FrameInjection::ObjectMethod);
106 if (m_ref) {
107 return strongBind(invokeImplFewArgs(env, count, INVOKE_FEW_ARGS_PASS_ARGS));
109 return invokeImplFewArgs(env, count, INVOKE_FEW_ARGS_PASS_ARGS);
112 Variant MethodStatement::
113 invokeInstanceDirect(CObjRef obj, VariableEnvironment &env,
114 const FunctionCallExpression *caller,
115 bool check /* = true */) const {
116 if (getModifiers() & ClassStatement::Static) {
117 return invokeStaticDirect(obj->o_getClassName(), env,
118 caller, false, check);
120 if (check) attemptAccess(FrameInjection::GetClassName(false));
121 DECLARE_THREAD_INFO_NOINIT
122 MethScopeVariableEnvironment fenv(this);
123 directBind(env, caller, fenv);
124 fenv.setCurrentObject(obj);
125 String clsName(m_class->name());
126 EvalFrameInjection fi(clsName, m_fullName->data(), fenv,
127 loc()->file, obj.get(), FrameInjection::ObjectMethod);
128 if (m_ref) {
129 return strongBind(evalBody(fenv));
131 return evalBody(fenv);
134 Variant MethodStatement::invokeStatic(const char* cls, CArrRef params,
135 bool check /* = true */) const {
136 if (check) attemptAccess(FrameInjection::GetClassName(false));
137 DECLARE_THREAD_INFO_NOINIT
138 MethScopeVariableEnvironment env(this);
139 env.setCurrentClass(cls);
140 String clsName(m_class->name());
141 EvalFrameInjection fi(clsName, m_fullName->data(), env, loc()->file,
142 NULL, FrameInjection::StaticMethod);
143 if (m_ref) {
144 return strongBind(invokeImpl(env, params));
146 return invokeImpl(env, params);
149 Variant MethodStatement::invokeStaticFewArgs(const char* cls, int count,
150 INVOKE_FEW_ARGS_IMPL_ARGS, bool check) const {
151 if (check) attemptAccess(FrameInjection::GetClassName(false));
152 DECLARE_THREAD_INFO_NOINIT
153 MethScopeVariableEnvironment env(this);
154 env.setCurrentClass(cls);
155 String clsName(m_class->name());
156 EvalFrameInjection fi(clsName, m_fullName->data(), env, loc()->file,
157 NULL, FrameInjection::StaticMethod);
158 if (m_ref) {
159 return strongBind(invokeImplFewArgs(env, count, INVOKE_FEW_ARGS_PASS_ARGS));
161 return invokeImplFewArgs(env, count, INVOKE_FEW_ARGS_PASS_ARGS);
164 Variant MethodStatement::
165 invokeStaticDirect(CStrRef cls, VariableEnvironment &env,
166 const FunctionCallExpression *caller, bool sp,
167 bool check /* = true */)
168 const {
169 if (check) attemptAccess(FrameInjection::GetClassName(false));
170 MethScopeVariableEnvironment fenv(this);
171 directBind(env, caller, fenv);
172 fenv.setCurrentClass(cls.data());
173 EvalFrameInjection::EvalStaticClassNameHelper helper(cls, sp);
174 DECLARE_THREAD_INFO_NOINIT
175 String clsName(m_class->name());
176 EvalFrameInjection fi(clsName, m_fullName->data(), fenv,
177 loc()->file, NULL, FrameInjection::StaticMethod);
178 if (m_ref) {
179 return strongBind(evalBody(fenv));
181 return evalBody(fenv);
184 Variant MethodStatement::evalBody(VariableEnvironment &env) const {
185 if (isAbstract()) {
186 raise_error("Cannot call abstract method %s()", m_fullName->data());
188 if (m_ref) {
189 return strongBind(FunctionStatement::evalBody(env));
190 } else {
191 return FunctionStatement::evalBody(env);
195 void MethodStatement::getInfo(ClassInfo::MethodInfo &info) const {
196 FunctionStatement::getInfo(info);
197 int attr = info.attribute == ClassInfo::IsNothing ? 0 : info.attribute;
198 if (m_modifiers & ClassStatement::Abstract) attr |= ClassInfo::IsAbstract;
199 if (m_modifiers & ClassStatement::Final) attr |= ClassInfo::IsFinal;
200 if (m_modifiers & ClassStatement::Protected) attr |= ClassInfo::IsProtected;
201 if (m_modifiers & ClassStatement::Private) attr |= ClassInfo::IsPrivate;
202 if (m_modifiers & ClassStatement::Static) attr |= ClassInfo::IsStatic;
203 if (!(attr & ClassInfo::IsProtected || attr & ClassInfo::IsPrivate)) {
204 attr |= ClassInfo::IsPublic;
206 info.attribute = (ClassInfo::Attribute)attr;
209 void MethodStatement::attemptAccess(const char *context) const {
210 if (g_context->getDebuggerBypassCheck()) {
211 return;
213 int mods = getModifiers();
214 const ClassStatement *cs = getClass();
215 ClassStatement::Modifier level = ClassStatement::Public;
216 if (mods & ClassStatement::Private) level = ClassStatement::Private;
217 else if (mods & ClassStatement::Protected) level = ClassStatement::Protected;
218 bool access = true;
219 if (level == ClassStatement::Protected) {
220 while (!cs->hasAccess(context, level)) {
221 while ((cs = cs->parentStatement())) {
222 if (cs->findMethod(m_name->data())) {
223 break;
226 if (!cs) {
227 access = false;
228 break;
231 } else {
232 access = cs->hasAccess(context, level);
235 // special case when handling parent's private constructors that are allowed
236 if (!access) {
237 if (level == ClassStatement::Private &&
238 (strcasecmp(m_name->data(), "__construct") == 0 ||
239 strcasecmp(m_name->data(), getClass()->name().c_str()) == 0)) {
240 access = cs->hasAccess(context, ClassStatement::Protected);
244 if (!access) {
245 const char *mod = "protected";
246 if (level == ClassStatement::Private) mod = "private";
247 throw FatalErrorException(0, "Attempt to call %s %s::%s()%s%s",
248 mod, getClass()->name().c_str(), m_name->data(),
249 context[0] ? " from " : "",
250 context[0] ? context : "");
254 bool MethodStatement::isAbstract() const {
255 return getModifiers() & ClassStatement::Abstract ||
256 m_class->getModifiers() & ClassStatement::Interface;
259 Variant MethodStatement::MethInvoker(MethodCallPackage &mcp, CArrRef params) {
260 const MethodStatement *ms = (const MethodStatement*)mcp.extra;
261 bool check = strcasecmp(ms->m_name->data(), "__invoke") != 0;
262 bool isStatic = ms->getModifiers() & ClassStatement::Static;
263 if (isStatic || !mcp.obj) {
264 String cn;
265 if (UNLIKELY(!isStatic && mcp.isObj && mcp.obj == NULL)) {
266 // this is needed for continuations where
267 // we are passed the dummy object
268 cn = ms->getClass()->name();
269 } else {
270 cn = mcp.getClassName();
272 if (ms->refReturn()) {
273 return strongBind(ms->invokeStatic(cn.c_str(), params, check));
274 } else {
275 return ms->invokeStatic(cn.c_str(), params, check);
277 } else {
278 if (ms->refReturn()) {
279 return strongBind(ms->invokeInstance(mcp.rootObj, params, check));
280 } else {
281 return ms->invokeInstance(mcp.rootObj, params, check);
286 Variant MethodStatement::MethInvokerFewArgs(MethodCallPackage &mcp,
287 int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
288 const MethodStatement *ms = (const MethodStatement*)mcp.extra;
289 bool check = strcasecmp(ms->m_name->data(), "__invoke") != 0;
290 bool isStatic = ms->getModifiers() & ClassStatement::Static;
291 if (isStatic || !mcp.obj) {
292 String cn;
293 if (UNLIKELY(!isStatic && mcp.isObj && mcp.obj == NULL)) {
294 // this is needed for continuations where
295 // we are passed the dummy object
296 cn = ms->getClass()->name();
297 } else {
298 cn = mcp.getClassName();
300 if (ms->refReturn()) {
301 return strongBind(ms->invokeStaticFewArgs(cn.c_str(), count,
302 INVOKE_FEW_ARGS_PASS_ARGS, check));
303 } else {
304 return ms->invokeStaticFewArgs(cn.c_str(), count,
305 INVOKE_FEW_ARGS_PASS_ARGS, check);
307 } else {
308 if (ms->refReturn()) {
309 return strongBind(ms->invokeInstanceFewArgs(mcp.rootObj, count,
310 INVOKE_FEW_ARGS_PASS_ARGS, check));
311 } else {
312 return ms->invokeInstanceFewArgs(mcp.rootObj, count,
313 INVOKE_FEW_ARGS_PASS_ARGS, check);
318 void MethodStatement::dump(std::ostream &out) const {
319 ClassStatement::dumpModifiers(out, m_modifiers, false);
320 FunctionStatement::dump(out);
323 ///////////////////////////////////////////////////////////////////////////////