package php.runtime.invoke;
import php.runtime.Information;
import php.runtime.Memory;
import php.runtime.annotation.Reflection;
import php.runtime.common.Messages;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.FatalException;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.invoke.cache.FunctionCallCache;
import php.runtime.invoke.cache.MethodCallCache;
import php.runtime.lang.IObject;
import php.runtime.memory.ArrayMemory;
import php.runtime.memory.ObjectMemory;
import php.runtime.memory.StringMemory;
import php.runtime.reflection.*;
final public class InvokeHelper {
private InvokeHelper() { }
public static void checkAccess(Environment env, TraceInfo trace, MethodEntity method) {
int access = method.canAccess(env);
if (access == 0)
return;
ClassEntity contextCls = env.getLastClassOnStack();
String context = contextCls == null ? "" : contextCls.getName();
switch (access){
case 1: throw new FatalException(
Messages.ERR_CALL_TO_PROTECTED_METHOD.fetch(
method.getClazz().getName() + "::" + method.getName(), context
),
trace
);
case 2: throw new FatalException(
Messages.ERR_CALL_TO_PRIVATE_METHOD.fetch(
method.getClazz().getName() + "::" + method.getName(), context
),
trace
);
}
}
public static void checkAccess(Environment env, TraceInfo trace, PropertyEntity property) {
switch (property.canAccess(env)){
case 1: throw new FatalException(
Messages.ERR_ACCESS_TO_PROTECTED_PROPERTY.fetch(
property.getClazz().getName(), property.getName()
),
trace
);
case 2: throw new FatalException(
Messages.ERR_ACCESS_TO_PRIVATE_PROPERTY.fetch(
property.getClazz().getName(), property.getName()
),
trace
);
}
}
public static Memory[] makeArguments(Environment env, Memory[] args,
ParameterEntity[] parameters,
String originClassName, String originMethodName,
TraceInfo trace) {
return InvokeArgumentHelper.makeArguments(env, args, parameters, originClassName, originMethodName, trace);
}
/**
* Method is invoked via bytecode
* @throws Throwable
*/
public static Memory callAny(Memory method, Memory[] args, Environment env, TraceInfo trace)
throws Throwable {
method = method.toValue();
if (method.isObject()) {
return ObjectInvokeHelper.invokeMethod(method, null, null, env, trace, args);
} else if (method.isArray()){
Memory one = null, two = null;
for(Memory el : (ArrayMemory)method){
if (one == null)
one = el;
else if (two == null)
two = el;
else
break;
}
if (one == null || two == null) {
env.error(trace, Messages.ERR_CALL_TO_UNDEFINED_FUNCTION.fetch(method.toString()));
return Memory.NULL;
}
String methodName = two.toString();
if (one.isObject())
return ObjectInvokeHelper.invokeMethod(one, methodName, methodName.toLowerCase(), env, trace, args);
else {
String className = one.toString();
ClassEntity magic = env.fetchMagicClass(className);
if (magic != null)
className = magic.getName();
return InvokeHelper.callStaticDynamic(
env,
trace,
className, className.toLowerCase(),
methodName, methodName.toLowerCase(),
args,
null, 0
);
}
} else {
String methodName = method.toString();
int p;
if ((p = methodName.indexOf("::")) > -1) {
String className = methodName.substring(0, p);
methodName = methodName.substring(p + 2, methodName.length());
return InvokeHelper.callStaticDynamic(
env, trace,
className, className.toLowerCase(),
methodName, methodName.toLowerCase(),
args,
null, 0
);
} else {
return InvokeHelper.call(env, trace, methodName.toLowerCase(), methodName, args, null, 0);
}
}
}
public static Memory call(Environment env, TraceInfo trace, FunctionEntity function, Memory[] args)
throws Throwable {
Memory[] passed = makeArguments(env, args, function.getParameters(), function.getName(), null, trace);
Memory result = function.getImmutableResult();
if (result != null) return result;
if (trace != null && function.isUsesStackTrace())
env.pushCall(trace, null, args, function.getName(), null, null);
try {
result = function.invoke(env, trace, passed);
} finally {
if (trace != null && function.isUsesStackTrace())
env.popCall();
}
return result;
}
public static Memory call(Environment env, TraceInfo trace, String sign, String originName,
Memory[] args, FunctionCallCache callCache, int cacheIndex) throws Throwable {
FunctionEntity function = null;
if (callCache != null)
function = callCache.get(env, cacheIndex);
if (function == null) {
function = env.fetchFunction(originName, sign);
if (function != null && callCache != null) {
callCache.put(env, cacheIndex, function);
}
}
if (function == null) {
if (!sign.isEmpty() && sign.charAt(0) != Information.NAMESPACE_SEP_CHAR) { // for global style invoke
int p = sign.lastIndexOf(Information.NAMESPACE_SEP_CHAR);
if (p > -1)
function = env.fetchFunction(originName.substring(p + 1), sign.substring(p + 1));
}
if (function == null) {
env.error(trace, Messages.ERR_CALL_TO_UNDEFINED_FUNCTION.fetch(originName));
return Memory.NULL;
}
if (callCache != null) {
callCache.put(env, cacheIndex, function);
}
}
return call(env, trace, function, args);
}
public static Memory callStaticDynamic(Environment env, TraceInfo trace,
String originClassName, String className,
String originMethodName, String methodName,
Memory[] args, MethodCallCache callCache, int cacheIndex) throws Throwable {
return callStatic(
env, trace,
className, methodName,
originClassName, originMethodName,
args,
callCache, cacheIndex
);
}
public static Memory callStatic(Environment env, TraceInfo trace,
String className, String methodName, String originClassName, String originMethodName,
Memory[] args, MethodCallCache callCache, int cacheIndex)
throws Throwable {
if (callCache != null) {
MethodEntity entity = callCache.get(env, cacheIndex);
if (entity != null) {
return callStatic(env, trace, entity, originClassName, args, false);
}
}
ClassEntity classEntity = env.fetchClass(originClassName, className, true);
MethodEntity method = classEntity == null ? null : classEntity.findMethod(methodName);
Memory[] passed = null;
boolean isMagic = false;
if (method == null){
IObject maybeObject = env.getLateObject();
if (maybeObject != null && maybeObject.getReflection().isInstanceOf(classEntity))
return ObjectInvokeHelper.invokeMethod(
new ObjectMemory(maybeObject), originMethodName, methodName, env, trace, args
);
if (classEntity != null && classEntity.methodMagicCallStatic != null){
method = classEntity.methodMagicCallStatic;
isMagic = true;
passed = new Memory[]{
new StringMemory(originMethodName),
ArrayMemory.of(args)
};
} else {
if (classEntity == null) {
env.error(trace, Messages.ERR_CLASS_NOT_FOUND.fetch(originClassName));
return Memory.NULL;
}
}
}
if (method == null){
env.error(trace, Messages.ERR_CALL_TO_UNDEFINED_METHOD.fetch(originClassName + "::" + originMethodName));
return Memory.NULL;
}
if (!method.isStatic()) {
IObject maybeObject = env.getLateObject();
if (maybeObject != null
&& maybeObject.getReflection().isInstanceOf(classEntity))
return ObjectInvokeHelper.invokeMethod(maybeObject, method, env, trace, args, true);
env.error(trace,
ErrorType.E_STRICT,
Messages.ERR_NON_STATIC_METHOD_CALLED_DYNAMICALLY,
originClassName, originMethodName
);
}
if (callCache != null && !isMagic) {
callCache.put(env, cacheIndex, method);
}
checkAccess(env, trace, method);
if (passed == null)
passed = makeArguments(env, args, method.getParameters(), originClassName, originMethodName, trace);
Memory result = method.getImmutableResult();
if (result != null) return result;
try {
if (trace != null)
env.pushCall(trace, null, args, originMethodName, method.getClazz().getName(), originClassName);
return method.invokeStatic(env, trace, passed);
} finally {
if (trace != null)
env.popCall();
}
}
public static Memory callStatic(Environment env, TraceInfo trace,
MethodEntity method,
Memory[] args)
throws Throwable {
return callStatic(env, trace, method, null, args, true);
}
public static Memory callStatic(Environment env, TraceInfo trace,
MethodEntity method, @Reflection.Nullable String staticClass,
Memory[] args, boolean checkAccess)
throws Throwable {
if (checkAccess)
checkAccess(env, trace, method);
Memory result = method.getImmutableResult();
if (result != null)
return result;
String originClassName = method.getClazz().getName();
String originMethodName = method.getName();
Memory[] passed = makeArguments(env, args, method.getParameters(), originClassName, originMethodName, trace);
try {
if (trace != null && method.isUsesStackTrace())
env.pushCall(trace, null, passed, originMethodName, originClassName, staticClass == null ? originClassName : staticClass);
return method.invokeStatic(env, passed);
} finally {
if (trace != null && method.isUsesStackTrace())
env.popCall();
}
}
public static void checkReturnReference(Memory memory, Environment env, TraceInfo trace){
if (memory.isImmutable()){
env.warning(trace, Messages.ERR_RETURN_NOT_REFERENCE.fetch());
}
}
public static void checkYieldReference(Memory memory, Environment env, TraceInfo trace){
if (memory.isImmutable()){
env.error(trace, ErrorType.E_NOTICE, Messages.ERR_YIELD_NOT_REFERENCE.fetch());
}
}
}