// ********************************************************************** // // Copyright (c) 2003-present ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** #include #include #include #include #include #include using namespace std; using namespace IcePHP; using namespace Slice::PHP; extern "C" { ZEND_FUNCTION(IcePHP_Operation_call); } namespace IcePHP { class ParamInfo : public IceUtil::Shared { public: TypeInfoPtr type; bool optional; int tag; int pos; }; typedef IceUtil::Handle ParamInfoPtr; typedef list ParamInfoList; // // Receives an out parameter or return value. // class ResultCallback : public UnmarshalCallback { public: ResultCallback(); ~ResultCallback(); virtual void unmarshaled(zval*, zval*, void* TSRMLS_DC); void unset(TSRMLS_D); zval* zv; }; typedef IceUtil::Handle ResultCallbackPtr; typedef vector ResultCallbackList; // // Encapsulates attributes of an operation. // class OperationI : public Operation { public: OperationI(const char*, Ice::OperationMode, Ice::OperationMode, Ice::FormatType, zval*, zval*, zval*, zval* TSRMLS_DC); ~OperationI(); virtual zend_function* function(); string name; // On-the-wire name. Ice::OperationMode mode; Ice::OperationMode sendMode; Ice::FormatType format; ParamInfoList inParams; ParamInfoList optionalInParams; ParamInfoList outParams; ParamInfoList optionalOutParams; ParamInfoPtr returnType; ExceptionInfoList exceptions; bool sendsClasses; bool returnsClasses; int numParams; private: zend_internal_function* _zendFunction; static void convertParams(zval*, ParamInfoList&, bool& TSRMLS_DC); static ParamInfoPtr convertParam(zval*, int TSRMLS_DC); static void getArgInfo(zend_arg_info&, const ParamInfoPtr&, bool); }; typedef IceUtil::Handle OperationIPtr; // // The base class for client-side invocations. // class Invocation : public virtual IceUtil::Shared { public: Invocation(const Ice::ObjectPrx&, const CommunicatorInfoPtr& TSRMLS_DC); virtual void invoke(INTERNAL_FUNCTION_PARAMETERS) = 0; protected: Ice::ObjectPrx _prx; CommunicatorInfoPtr _communicator; #ifdef ZTS TSRMLS_D; #endif }; typedef IceUtil::Handle InvocationPtr; // // TypedInvocation uses the information in the given operation to validate, marshal, and unmarshal // parameters and exceptions. // class TypedInvocation : public virtual Invocation { public: TypedInvocation(const Ice::ObjectPrx&, const CommunicatorInfoPtr&, const OperationIPtr& TSRMLS_DC); protected: OperationIPtr _op; bool prepareRequest(int, zval**, Ice::OutputStream*, pair& TSRMLS_DC); void unmarshalResults(int, zval**, zval*, const pair& TSRMLS_DC); zval* unmarshalException(const pair& TSRMLS_DC); bool validateException(const ExceptionInfoPtr& TSRMLS_DC) const; void checkTwowayOnly(const Ice::ObjectPrx&) const; }; // // A synchronous typed invocation. // class SyncTypedInvocation : public virtual TypedInvocation { public: SyncTypedInvocation(const Ice::ObjectPrx&, const CommunicatorInfoPtr&, const OperationIPtr& TSRMLS_DC); virtual void invoke(INTERNAL_FUNCTION_PARAMETERS); }; class UserExceptionFactory : public Ice::UserExceptionFactory { public: UserExceptionFactory(const CommunicatorInfoPtr& communicator TSRMLS_DC) : _communicator(communicator) { #ifdef ZTS this->TSRMLS_C = TSRMLS_C; #endif } virtual void createAndThrow(const string& id) { ExceptionInfoPtr info = getExceptionInfo(id TSRMLS_CC); if(info) { throw ExceptionReader(_communicator, info TSRMLS_CC); } } private: const CommunicatorInfoPtr _communicator; #if ZTS TSRMLS_D; #endif }; } // // ResultCallback implementation. // IcePHP::ResultCallback::ResultCallback() : zv(0) { } IcePHP::ResultCallback::~ResultCallback() { if(zv) { zval_ptr_dtor(&zv); } } void IcePHP::ResultCallback::unmarshaled(zval* val, zval*, void* TSRMLS_DC) { // // Keep a reference to the unmarshaled value. // zv = val; Z_ADDREF_P(zv); } void IcePHP::ResultCallback::unset(TSRMLS_D) { MAKE_STD_ZVAL(zv); assignUnset(zv TSRMLS_CC); } // // OperationI implementation. // IcePHP::OperationI::OperationI(const char* n, Ice::OperationMode m, Ice::OperationMode sm, Ice::FormatType f, zval* in, zval* out, zval* ret, zval* ex TSRMLS_DC) : name(n), mode(m), sendMode(sm), format(f), _zendFunction(0) { // // inParams // sendsClasses = false; if(in) { convertParams(in, inParams, sendsClasses TSRMLS_CC); } // // outParams // returnsClasses = false; if(out) { convertParams(out, outParams, returnsClasses TSRMLS_CC); } // // returnType // if(ret) { returnType = convertParam(ret, 0 TSRMLS_CC); if(!returnsClasses) { returnsClasses = returnType->type->usesClasses(); } } numParams = static_cast(inParams.size() + outParams.size()); class SortFn { public: static bool compare(const ParamInfoPtr& lhs, const ParamInfoPtr& rhs) { return lhs->tag < rhs->tag; } static bool isRequired(const ParamInfoPtr& i) { return !i->optional; } }; // // The inParams list represents the parameters in the order of declaration. // We also need a sorted list of optional parameters. // ParamInfoList l = inParams; copy(l.begin(), remove_if(l.begin(), l.end(), SortFn::isRequired), back_inserter(optionalInParams)); optionalInParams.sort(SortFn::compare); // // The outParams list represents the parameters in the order of declaration. // We also need a sorted list of optional parameters. If the return value is // optional, we must include it in this list. // l = outParams; copy(l.begin(), remove_if(l.begin(), l.end(), SortFn::isRequired), back_inserter(optionalOutParams)); if(returnType && returnType->optional) { optionalOutParams.push_back(returnType); } optionalOutParams.sort(SortFn::compare); // // exceptions // if(ex) { HashTable* arr = Z_ARRVAL_P(ex); HashPosition pos; zend_hash_internal_pointer_reset_ex(arr, &pos); void* data; while(zend_hash_get_current_data_ex(arr, &data, &pos) != FAILURE) { zval** val = reinterpret_cast(data); ExceptionInfoPtr i = Wrapper::value(*val TSRMLS_CC); exceptions.push_back(i); zend_hash_move_forward_ex(arr, &pos); } } } IcePHP::OperationI::~OperationI() { if(_zendFunction) { delete []_zendFunction->arg_info; efree(const_cast(_zendFunction->function_name)); efree(_zendFunction); } } zend_function* IcePHP::OperationI::function() { if(!_zendFunction) { // // Create an array that indicates how arguments are passed to the operation. // zend_arg_info* argInfo = new zend_arg_info[numParams]; int i = 0; ParamInfoList::const_iterator p; for(p = inParams.begin(); p != inParams.end(); ++p, ++i) { getArgInfo(argInfo[i], *p, false); #if PHP_VERSION_ID < 50400 argInfo[i].required_num_args = static_cast(numParams); #endif } for(p = outParams.begin(); p != outParams.end(); ++p, ++i) { getArgInfo(argInfo[i], *p, true); #if PHP_VERSION_ID < 50400 argInfo[i].required_num_args = static_cast(numParams); #endif } string fixed = fixIdent(name); _zendFunction = static_cast(emalloc(sizeof(zend_internal_function))); _zendFunction->type = ZEND_INTERNAL_FUNCTION; _zendFunction->function_name = estrndup(STRCAST(fixed.c_str()), static_cast(fixed.length())); _zendFunction->scope = proxyClassEntry; _zendFunction->fn_flags = ZEND_ACC_PUBLIC; _zendFunction->prototype = 0; _zendFunction->num_args = static_cast(numParams); _zendFunction->arg_info = argInfo; _zendFunction->required_num_args = _zendFunction->num_args; #if PHP_VERSION_ID < 50400 _zendFunction->pass_rest_by_reference = 0; _zendFunction->return_reference = 0; #endif _zendFunction->handler = ZEND_FN(IcePHP_Operation_call); } return reinterpret_cast(_zendFunction); } void IcePHP::OperationI::convertParams(zval* p, ParamInfoList& params, bool& usesClasses TSRMLS_DC) { assert(Z_TYPE_P(p) == IS_ARRAY); HashTable* arr = Z_ARRVAL_P(p); HashPosition pos; zend_hash_internal_pointer_reset_ex(arr, &pos); void* data; int i = 0; while(zend_hash_get_current_data_ex(arr, &data, &pos) != FAILURE) { zval** val = reinterpret_cast(data); ParamInfoPtr param = convertParam(*val, i TSRMLS_CC); params.push_back(param); if(!param->optional && !usesClasses) { usesClasses = param->type->usesClasses(); } zend_hash_move_forward_ex(arr, &pos); ++i; } } ParamInfoPtr IcePHP::OperationI::convertParam(zval* p, int pos TSRMLS_DC) { assert(Z_TYPE_P(p) == IS_ARRAY); HashTable* arr = Z_ARRVAL_P(p); ParamInfoPtr param = new ParamInfo; zval** m; zend_hash_index_find(arr, 0, reinterpret_cast(&m)); param->type = Wrapper::value(*m TSRMLS_CC); param->optional = zend_hash_num_elements(arr) > 1; if(param->optional) { zend_hash_index_find(arr, 1, reinterpret_cast(&m)); assert(Z_TYPE_PP(m) == IS_LONG); param->tag = Z_LVAL_PP(m); } param->pos = pos; return param; } void IcePHP::OperationI::getArgInfo(zend_arg_info& arg, const ParamInfoPtr& info, bool out) { arg.name = 0; arg.class_name = 0; arg.allow_null = 1; if(!info->optional) { const bool isArray = SequenceInfoPtr::dynamicCast(info->type) || DictionaryInfoPtr::dynamicCast(info->type); #if PHP_VERSION_ID < 50400 arg.array_type_hint = isArray ? 1 : 0; arg.return_reference = 0; #else arg.type_hint = isArray ? IS_ARRAY : 0; #endif } else { #if PHP_VERSION_ID < 50400 arg.array_type_hint = 0; arg.return_reference = 0; #else arg.type_hint = 0; #endif } arg.pass_by_reference = out ? 1 : 0; } // // Invocation // IcePHP::Invocation::Invocation(const Ice::ObjectPrx& prx, const CommunicatorInfoPtr& communicator TSRMLS_DC) : _prx(prx), _communicator(communicator) { #ifdef ZTS this->TSRMLS_C = TSRMLS_C; #endif } // // TypedInvocation // IcePHP::TypedInvocation::TypedInvocation(const Ice::ObjectPrx& prx, const CommunicatorInfoPtr& communicator, const OperationIPtr& op TSRMLS_DC) : Invocation(prx, communicator TSRMLS_CC), _op(op) { } bool IcePHP::TypedInvocation::prepareRequest(int argc, zval** args, Ice::OutputStream* os, pair& params TSRMLS_DC) { // // Verify that the expected number of arguments are supplied. The context argument is optional. // if(argc != _op->numParams && argc != _op->numParams + 1) { runtimeError("incorrect number of parameters (%d)" TSRMLS_CC, argc); return false; } // // The operation's configuration (zend_function) forces out parameters // to be passed by reference. // for(int i = static_cast(_op->inParams.size()); i < _op->numParams; ++i) { assert(PZVAL_IS_REF(args[i])); } if(!_op->inParams.empty()) { try { // // Marshal the in parameters. // os->startEncapsulation(_prx->ice_getEncodingVersion(), _op->format); ObjectMap objectMap; ParamInfoList::iterator p; // // Validate the supplied arguments. // for(p = _op->inParams.begin(); p != _op->inParams.end(); ++p) { ParamInfoPtr info = *p; zval* arg = args[info->pos]; if((!info->optional || !isUnset(arg TSRMLS_CC)) && !info->type->validate(arg, false TSRMLS_CC)) { invalidArgument("invalid value for argument %d in operation `%s'" TSRMLS_CC, info->pos + 1, _op->name.c_str()); return false; } } // // Marshal the required parameters. // for(p = _op->inParams.begin(); p != _op->inParams.end(); ++p) { ParamInfoPtr info = *p; if(!info->optional) { zval* arg = args[info->pos]; info->type->marshal(arg, os, &objectMap, false TSRMLS_CC); } } // // Marshal the optional parameters. // for(p = _op->optionalInParams.begin(); p != _op->optionalInParams.end(); ++p) { ParamInfoPtr info = *p; zval* arg = args[info->pos]; if(!isUnset(arg TSRMLS_CC) && os->writeOptional(info->tag, info->type->optionalFormat())) { info->type->marshal(arg, os, &objectMap, true TSRMLS_CC); } } if(_op->sendsClasses) { os->writePendingValues(); } os->endEncapsulation(); params = os->finished(); } catch(const AbortMarshaling&) { return false; } catch(const Ice::Exception& ex) { throwException(ex TSRMLS_CC); return false; } } return true; } void IcePHP::TypedInvocation::unmarshalResults(int argc, zval** args, zval* ret, const pair& bytes TSRMLS_DC) { Ice::InputStream is(_communicator->getCommunicator(), bytes); // // Store a pointer to a local StreamUtil object as the stream's closure. // This is necessary to support object unmarshaling (see ObjectReader). // StreamUtil util; assert(!is.getClosure()); is.setClosure(&util); is.startEncapsulation(); ParamInfoList::iterator p; // // These callbacks collect references to the unmarshaled values. We copy them into // the argument list *after* any pending objects have been unmarshaled. // ResultCallbackList outParamCallbacks; ResultCallbackPtr retCallback; outParamCallbacks.resize(_op->outParams.size()); // // Unmarshal the required out parameters. // for(p = _op->outParams.begin(); p != _op->outParams.end(); ++p) { ParamInfoPtr info = *p; if(!info->optional) { ResultCallbackPtr cb = new ResultCallback; outParamCallbacks[info->pos] = cb; info->type->unmarshal(&is, cb, _communicator, 0, 0, false TSRMLS_CC); } } // // Unmarshal the required return value, if any. // if(_op->returnType && !_op->returnType->optional) { retCallback = new ResultCallback; _op->returnType->type->unmarshal(&is, retCallback, _communicator, 0, 0, false TSRMLS_CC); } // // Unmarshal the optional results. This includes an optional return value. // for(p = _op->optionalOutParams.begin(); p != _op->optionalOutParams.end(); ++p) { ParamInfoPtr info = *p; ResultCallbackPtr cb = new ResultCallback; if(_op->returnType && info->tag == _op->returnType->tag) { retCallback = cb; } else { outParamCallbacks[info->pos] = cb; } if(is.readOptional(info->tag, info->type->optionalFormat())) { info->type->unmarshal(&is, cb, _communicator, 0, 0, true TSRMLS_CC); } else { cb->unset(TSRMLS_C); } } if(_op->returnsClasses) { is.readPendingValues(); } is.endEncapsulation(); util.updateSlicedData(TSRMLS_C); int i = static_cast(_op->inParams.size()); for(ResultCallbackList::iterator q = outParamCallbacks.begin(); q != outParamCallbacks.end(); ++q, ++i) { // // We must explicitly destroy the existing contents of all zvals passed // as out parameters, otherwise leaks occur. // zval* val = (*q)->zv; zval_dtor(args[i]); args[i]->value = val->value; Z_TYPE_P(args[i]) = Z_TYPE_P(val); zval_copy_ctor(args[i]); } if(_op->returnType) { ret->value = retCallback->zv->value; Z_TYPE_P(ret) = Z_TYPE_P(retCallback->zv); zval_copy_ctor(ret); } } zval* IcePHP::TypedInvocation::unmarshalException(const pair& bytes TSRMLS_DC) { Ice::InputStream is(_communicator->getCommunicator(), bytes); // // Store a pointer to a local StreamUtil object as the stream's closure. // This is necessary to support object unmarshaling (see ObjectReader). // StreamUtil util; assert(!is.getClosure()); is.setClosure(&util); is.startEncapsulation(); try { Ice::UserExceptionFactoryPtr factory = new UserExceptionFactory(_communicator TSRMLS_CC); is.throwException(factory); } catch(const ExceptionReader& r) { is.endEncapsulation(); zval* ex = r.getException(); ExceptionInfoPtr info = r.getInfo(); if(validateException(info TSRMLS_CC)) { util.updateSlicedData(TSRMLS_C); Ice::SlicedDataPtr slicedData = r.getSlicedData(); if(slicedData) { StreamUtil::setSlicedDataMember(ex, slicedData TSRMLS_CC); } return ex; } else { zval_ptr_dtor(&ex); Ice::UnknownUserException uue(__FILE__, __LINE__, "operation raised undeclared exception `" + info->id + "'"); return convertException(uue TSRMLS_CC); } } // // Getting here should be impossible: we can get here only if the // sender has marshaled a sequence of type IDs, none of which we // have a factory for. This means that sender and receiver disagree // about the Slice definitions they use. // Ice::UnknownUserException uue(__FILE__, __LINE__, "unknown exception"); return convertException(uue TSRMLS_CC); } bool IcePHP::TypedInvocation::validateException(const ExceptionInfoPtr& info TSRMLS_DC) const { for(ExceptionInfoList::const_iterator p = _op->exceptions.begin(); p != _op->exceptions.end(); ++p) { if(info->isA((*p)->id)) { return true; } } return false; } void IcePHP::TypedInvocation::checkTwowayOnly(const Ice::ObjectPrx& proxy) const { if((_op->returnType || !_op->outParams.empty()) && !proxy->ice_isTwoway()) { Ice::TwowayOnlyException ex(__FILE__, __LINE__); ex.operation = _op->name; throw ex; } } // // SyncTypedInvocation // IcePHP::SyncTypedInvocation::SyncTypedInvocation(const Ice::ObjectPrx& prx, const CommunicatorInfoPtr& communicator, const OperationIPtr& op TSRMLS_DC) : Invocation(prx, communicator TSRMLS_CC), TypedInvocation(prx, communicator, op TSRMLS_CC) { } void IcePHP::SyncTypedInvocation::invoke(INTERNAL_FUNCTION_PARAMETERS) { // // Retrieve the arguments. // zval*** args = static_cast(emalloc(ZEND_NUM_ARGS() * sizeof(zval**))); AutoEfree autoArgs(args); // Call efree on return if(zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) { runtimeError("unable to get arguments" TSRMLS_CC); return; } Ice::OutputStream os(_prx->ice_getCommunicator()); pair params; if(!prepareRequest(ZEND_NUM_ARGS(), *args, &os, params TSRMLS_CC)) { return; } bool hasCtx = false; Ice::Context ctx; if(ZEND_NUM_ARGS() == _op->numParams + 1) { if(!extractStringMap(*args[ZEND_NUM_ARGS() - 1], ctx TSRMLS_CC)) { return; } hasCtx = true; } try { checkTwowayOnly(_prx); // // Invoke the operation. // vector result; bool status; { if(hasCtx) { status = _prx->ice_invoke(_op->name, _op->sendMode, params, result, ctx); } else { status = _prx->ice_invoke(_op->name, _op->sendMode, params, result); } } // // Process the reply. // if(_prx->ice_isTwoway()) { if(!status) { // // Unmarshal a user exception. // pair rb(0, 0); if(!result.empty()) { rb.first = &result[0]; rb.second = &result[0] + result.size(); } zval* ex = unmarshalException(rb TSRMLS_CC); if(ex) { zend_throw_exception_object(ex TSRMLS_CC); } } else if(!_op->outParams.empty() || _op->returnType) { // // Unmarshal the results. // pair rb(0, 0); if(!result.empty()) { rb.first = &result[0]; rb.second = &result[0] + result.size(); } unmarshalResults(ZEND_NUM_ARGS(), *args, return_value, rb TSRMLS_CC); } } } catch(const AbortMarshaling&) { } catch(const Ice::Exception& ex) { throwException(ex TSRMLS_CC); } } ZEND_FUNCTION(IcePHP_defineOperation) { zval* cls; char* name; int nameLen; long mode; long sendMode; long format; zval* inParams; zval* outParams; zval* returnType; zval* exceptions; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, const_cast("osllla!a!a!a!"), &cls, &name, &nameLen, &mode, &sendMode, &format, &inParams, &outParams, &returnType, &exceptions) == FAILURE) { return; } TypeInfoPtr type = Wrapper::value(cls TSRMLS_CC); ProxyInfoPtr c = ProxyInfoPtr::dynamicCast(type); assert(c); OperationIPtr op = new OperationI(name, static_cast(mode), static_cast(sendMode), static_cast(format), inParams, outParams, returnType, exceptions TSRMLS_CC); c->addOperation(name, op); } ZEND_FUNCTION(IcePHP_Operation_call) { Ice::ObjectPrx proxy; ProxyInfoPtr info; CommunicatorInfoPtr comm; #ifndef NDEBUG bool b = #endif fetchProxy(getThis(), proxy, info, comm TSRMLS_CC); assert(b); assert(proxy); assert(info); OperationPtr op = info->getOperation(get_active_function_name(TSRMLS_C)); assert(op); // handleGetMethod should have already verified the operation's existence. OperationIPtr opi = OperationIPtr::dynamicCast(op); assert(opi); InvocationPtr inv = new SyncTypedInvocation(proxy, comm, opi TSRMLS_CC); inv->invoke(INTERNAL_FUNCTION_PARAM_PASSTHRU); }