Files
Rafal Somla 6cdca18cb2 bug#34332205 - follow up #2
Turns out that verifying type specs that include "!" requires more changes to the code. For now disabling checks if type spec contains "!".
2022-07-14 12:17:40 +02:00

387 lines
10 KiB
C++

/*
+----------------------------------------------------------------------+
| PHP Version 7 |
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| 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. |
+----------------------------------------------------------------------+
| Authors: Darek Slusarczyk <marines@php.net> |
+----------------------------------------------------------------------+
*/
#include "php_api.h"
#include "mysqlnd_api.h"
#include "functions.h"
#include "strings.h"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
namespace mysqlx {
namespace util {
namespace {
struct Arg_list
{
Arg_list(const zend_execute_data* execute_data);
std::size_t args_count() const;
std::size_t required_args_count() const;
bool is_variadic() const;
const zend_execute_data* const execute_data;
};
// ------------
Arg_list::Arg_list(const zend_execute_data* execute_data)
: execute_data(execute_data)
{
}
std::size_t Arg_list::args_count() const
{
return execute_data->func->common.num_args;
}
std::size_t Arg_list::required_args_count() const
{
return execute_data->func->common.required_num_args;
}
bool Arg_list::is_variadic() const
{
return (execute_data->func->common.fn_flags & ZEND_ACC_VARIADIC) == ZEND_ACC_VARIADIC;
}
// ------------------------
struct Type_spec
{
enum class Variadic {
None,
Zero_or_more,
One_or_more
};
Type_spec(
const util::string& args,
Variadic variadic);
bool empty() const;
bool is_variadic() const;
const util::string args;
const Variadic variadic;
};
// ------------
Type_spec::Type_spec(
const util::string& args,
Variadic variadic)
: args(args)
, variadic(variadic)
{
}
bool Type_spec::empty() const
{
return args.empty() && !is_variadic();
}
bool Type_spec::is_variadic() const
{
return variadic != Variadic::None;
}
// ------------------------
class Verify_call_parameters
{
public:
Verify_call_parameters(
bool is_method,
zend_execute_data* execute_data,
const char* type_spec);
void run();
private:
bool extract_type_spec_fragments(
util::string* required_args,
util::string* optional_args);
Type_spec create_type_spec(const util::string& raw_args);
Type_spec::Variadic resolve_variadic(const util::string& args);
void validate_type_spec(const Type_spec& type_spec);
void verify_variadic(
const Type_spec& type_spec_required_args,
const Type_spec& type_spec_optional_args);
void verify_required_args_count(
const Type_spec& type_spec_required_args);
void verify_optional_args_count(
const Type_spec& type_spec_optional_args);
std::size_t calc_arglist_optional_args_count();
std::size_t calc_min_args_count(
const Type_spec& type_spec,
bool required);
std::invalid_argument verify_error(const util::string& reason);
private:
const bool is_method;
const Arg_list arg_list;
const util::string type_spec;
};
// ------------
Verify_call_parameters::Verify_call_parameters(
bool is_method,
zend_execute_data* execute_data,
const char* type_spec)
: is_method(is_method)
, arg_list(execute_data)
, type_spec(type_spec)
{
}
void Verify_call_parameters::run()
{
util::string required_args;
util::string optional_args;
/*
Skip verification if extract_type_spec_fragments() detects a type
specification that we can not handle (yet).
FIXME: We should be able to handle all type specifications.
*/
if (!extract_type_spec_fragments(&required_args, &optional_args))
return;
Type_spec type_spec_required_args{ create_type_spec(required_args) };
validate_type_spec(type_spec_required_args);
Type_spec type_spec_optional_args{ create_type_spec(optional_args) };
validate_type_spec(type_spec_optional_args);
verify_variadic(type_spec_required_args, type_spec_optional_args);
verify_required_args_count(type_spec_required_args);
verify_optional_args_count(type_spec_optional_args);
}
/*
Returns false if type specification can not be handled by our checker. For
now this is the case when type specification contains nullable parameters.
FIXME: Correctly handle nullable parameters (bug#34384368)
*/
bool Verify_call_parameters::extract_type_spec_fragments(
util::string* required_args,
util::string* optional_args)
{
bool separator_found{ false };
const char Optional_args_separator{ '|' };
const char Nullable_arg_specifier{ '!' };
for( size_t i { 0 } ; i < type_spec.size(); ++i ) {
if( type_spec[i] == Nullable_arg_specifier)
return false;
if( type_spec[i] == Optional_args_separator ) {
if( separator_found ) {
throw verify_error("only one optional args block is allowed");
}
*required_args = type_spec.substr( 0, i );
*optional_args = type_spec.substr( i + 1, type_spec.size() - i + 1 );
separator_found = true;
}
}
if( !separator_found ) {
*required_args = type_spec;
}
return true;
}
Type_spec Verify_call_parameters::create_type_spec(const util::string& raw_args)
{
util::string args(raw_args);
const Type_spec::Variadic variadic{ resolve_variadic(args) };
if (variadic != Type_spec::Variadic::None) {
args.pop_back();
}
return Type_spec(args, variadic);
}
Type_spec::Variadic Verify_call_parameters::resolve_variadic(const util::string& args)
{
const util::string variadic_types{ "*+" };
const std::size_t idx{ args.find_first_of(variadic_types) };
if (idx == util::string::npos) return Type_spec::Variadic::None;
if (idx + 1 != args.length()) {
throw verify_error("variadic specificator is always last char or it is invalid");
}
switch (type_spec[idx]) {
case '*':
return Type_spec::Variadic::Zero_or_more;
case '+':
return Type_spec::Variadic::One_or_more;
default:
assert(!"unexpected char!");
return Type_spec::Variadic::None;
}
}
void Verify_call_parameters::validate_type_spec(const Type_spec& type_specification)
{
/*
type_spec sscanf like typelist (though no %)
l long -> long *
d double -> double *
b boolean -> zend_bool *
a array -> zval **
h HastTable -> HastTable*
o object -> zval **
O object -> zval **, zend_class_entry *
Object must be derived from given class
s string -> char **, int *
You receive string and length
r resource -> zval **
z zval -> zval **
Z zval-ref -> zval ***
| right part is optional
/ next param gets separated if not reference
! Next param returns NULL if param type IS_NULL
*/
const util::string allowed_types{ "ldbahoOsz!" };
const std::size_t disallowed_type_idx{ type_specification.args.find_first_not_of(allowed_types) };
if (disallowed_type_idx != util::string::npos) {
throw verify_error("unknown type in type_specification");
}
}
void Verify_call_parameters::verify_variadic(
const Type_spec& type_spec_required_args,
const Type_spec& type_spec_optional_args)
{
if (type_spec_required_args.is_variadic() && !type_spec_optional_args.empty()) {
throw verify_error("variadic specificator is always last char in whole type_spec");
}
}
void Verify_call_parameters::verify_required_args_count(
const Type_spec& type_spec_required_args)
{
const std::size_t arglist_required_args_count{
arg_list.required_args_count()
};
const std::size_t type_spec_required_args_count{
calc_min_args_count(type_spec_required_args, true)
};
if (arglist_required_args_count != type_spec_required_args_count) {
throw verify_error("required number of args in arglist and type_spec are different");
}
}
void Verify_call_parameters::verify_optional_args_count(
const Type_spec& type_spec_optional_args)
{
const std::size_t arglist_optional_args_count{
calc_arglist_optional_args_count()
};
const std::size_t type_spec_optional_args_count{
calc_min_args_count(type_spec_optional_args, false)
};
if (arglist_optional_args_count != type_spec_optional_args_count) {
throw verify_error("optional number of args in arglist and type_spec are different");
}
}
std::size_t Verify_call_parameters::calc_arglist_optional_args_count()
{
const std::size_t arglist_args_count{ arg_list.args_count() };
const std::size_t arglist_required_args_count{ arg_list.required_args_count() };
if (arglist_required_args_count <= arglist_args_count) {
return arglist_args_count - arglist_required_args_count;
}
if (!arg_list.is_variadic()) {
throw verify_error("arglist args count less than required args count, and no variadic arg");
}
if ((arglist_args_count + 1) == arglist_required_args_count) {
return 0;
}
assert((arglist_args_count + 1) < arglist_required_args_count);
throw verify_error("arglist args count less than required args count, despite variadic arg");
}
std::size_t Verify_call_parameters::calc_min_args_count(
const Type_spec& type_specification,
bool required)
{
std::size_t args_count{ type_specification.args.size() };
if (is_method && required) {
if (args_count == 0) {
throw verify_error("method call needs at least one argument - object");
}
// decrement due to obligatory specifier for object ('O' or 'o')
--args_count;
}
if (type_specification.variadic == Type_spec::Variadic::One_or_more) {
++args_count;
}
return args_count;
}
std::invalid_argument Verify_call_parameters::verify_error(const util::string& reason)
{
std::ostringstream os;
os << "verification of call params failed: " << reason;
return std::invalid_argument(os.str());
}
} // anonymous namespace
void verify_call_parameters(
bool is_method,
zend_execute_data* execute_data,
const char* type_spec)
{
Verify_call_parameters verify_parameters(is_method, execute_data, type_spec);
verify_parameters.run();
}
} // namespace util
} // namespace mysqlx