From 3332656fc6fc1465787922ca24745f28333a2b74 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Thu, 13 Oct 2022 22:14:36 -0400 Subject: [PATCH 1/9] Add PHP_SIMDJSON_API headers, mark php 7.1+ test --- php_simdjson.cpp | 9 ++++++--- php_simdjson.h | 11 +++++++++++ src/bindings.cpp | 16 ++++++++-------- src/bindings.h | 23 +++++++++++++---------- tests/compat/pass001.1.phpt | 6 +++++- tests/compat/pass001.phpt | 6 +++++- 6 files changed, 48 insertions(+), 23 deletions(-) diff --git a/php_simdjson.cpp b/php_simdjson.cpp index 3df38e5..40ef141 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -24,10 +24,13 @@ extern "C" { #include "php_simdjson.h" #include "simdjson_arginfo.h" -} -// Both the declaration and the definition of ZEND_API variables, functions must be within an 'extern "C"' block for Windows? -zend_class_entry *simdjson_exception_ce; +/** + * Both the declaration and the definition of PHP_SIMDJSON_API variables, functions must be within an 'extern "C"' block for Windows + */ +PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce; + +} #include "src/bindings.h" #include "src/simdjson.h" diff --git a/php_simdjson.h b/php_simdjson.h index 548c666..55193cf 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -54,4 +54,15 @@ ZEND_TSRMLS_CACHE_EXTERN() #endif #endif +/* + * This module defines utilities and helper functions used elsewhere in simdjson. + */ +#ifdef PHP_WIN32 +# define PHP_SIMDJSON_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_SIMDJSON_API __attribute__ ((visibility("default"))) +#else +# define PHP_SIMDJSON_API +#endif + #endif diff --git a/src/bindings.cpp b/src/bindings.cpp index a257492..f968ea4 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -29,7 +29,7 @@ extern "C" { #define SIMDJSON_DEPTH_CHECK_THRESHOLD 100000 -void cplus_simdjson_throw_jsonexception(simdjson_php_error_code error) +PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code error) { zend_throw_exception(simdjson_exception_ce, simdjson::error_message((simdjson::error_code) error), (zend_long) error); } @@ -300,15 +300,15 @@ static zval create_object(simdjson::dom::element element) /* {{{ */ { /* }}} */ -simdjson_php_parser* cplus_simdjson_create_parser(void) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_parser* cplus_simdjson_create_parser(void) /* {{{ */ { return new simdjson_php_parser(); } -void cplus_simdjson_free_parser(simdjson_php_parser* parser) /* {{{ */ { +PHP_SIMDJSON_API void cplus_simdjson_free_parser(simdjson_php_parser* parser) /* {{{ */ { delete parser; } -bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth) /* {{{ */ { simdjson::dom::element doc; /* The depth is passed in to ensure this behaves the same way for the same arguments */ auto error = build_parsed_json_cust(parser, doc, json, len, true, depth); @@ -320,7 +320,7 @@ bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size /* }}} */ -simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, unsigned char assoc, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, unsigned char assoc, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::error_code error = build_parsed_json_cust(parser, doc, json, len, true, depth); if (error) { @@ -335,7 +335,7 @@ simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const return simdjson::SUCCESS; } /* }}} */ -simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, unsigned char assoc, +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, unsigned char assoc, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::dom::element element; @@ -351,7 +351,7 @@ simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, co /* }}} */ -u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth) /* {{{ */ { simdjson::dom::element doc; auto error = build_parsed_json_cust(parser, doc, json, len, true, depth); if (error) { @@ -366,7 +366,7 @@ u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, /* }}} */ -simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::dom::element element; diff --git a/src/bindings.h b/src/bindings.h index a366877..ca70493 100644 --- a/src/bindings.h +++ b/src/bindings.h @@ -16,11 +16,14 @@ #include "Zend/zend.h" #include "Zend/zend_portability.h" +#include "php_simdjson.h" -// TODO: All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros (both header definitions, and C++ declarations, including function implementations), +// All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros (both header definitions, and C++ declarations, including function implementations), // so that pecls written in C can use this functionality without separate C++ files to load bindings.h. // BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers. // When I add ZEND_API to declarations and possibly definitions, I get linker errors. +BEGIN_EXTERN_C() + extern zend_class_entry *simdjson_exception_ce; // NOTE: Namespaces and references(&) are C++ only functionality. @@ -29,15 +32,15 @@ extern zend_class_entry *simdjson_exception_ce; class simdjson_php_parser; typedef uint8_t simdjson_php_error_code; -void cplus_simdjson_throw_jsonexception(simdjson_php_error_code); -simdjson_php_parser* cplus_simdjson_create_parser(void); -void cplus_simdjson_free_parser(simdjson_php_parser* parser); -bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth); -simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, unsigned char assoc, size_t depth); -simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, unsigned char assoc, size_t depth); -u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth); -simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth); +PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code); +PHP_SIMDJSON_API simdjson_php_parser* cplus_simdjson_create_parser(void); +PHP_SIMDJSON_API void cplus_simdjson_free_parser(simdjson_php_parser* parser); +PHP_SIMDJSON_API bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, unsigned char assoc, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, unsigned char assoc, size_t depth); +PHP_SIMDJSON_API u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth); -// END_EXTERN_C() +END_EXTERN_C() #endif diff --git a/tests/compat/pass001.1.phpt b/tests/compat/pass001.1.phpt index f515082..d69b2dd 100644 --- a/tests/compat/pass001.1.phpt +++ b/tests/compat/pass001.1.phpt @@ -1,7 +1,11 @@ --TEST-- JSON (http://www.crockford.com/JSON/JSON_checker/test/pass1.json) --SKIPIF-- - + --INI-- serialize_precision=-1 --FILE-- diff --git a/tests/compat/pass001.phpt b/tests/compat/pass001.phpt index d51b3f7..9f2a7a6 100644 --- a/tests/compat/pass001.phpt +++ b/tests/compat/pass001.phpt @@ -1,7 +1,11 @@ --TEST-- JSON (http://www.crockford.com/JSON/JSON_checker/test/pass1.json) --SKIPIF-- - + --INI-- serialize_precision=-1 --FILE-- From b518217cf63e7a0b320b0bdc88656c967d64238e Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Thu, 13 Oct 2022 23:17:57 -0400 Subject: [PATCH 2/9] WIP Make the header files load in C An example snippet of manually patching php-memcached to use simdjson instead: ``` + if (0) { php_memc_user_data_t *memc_user_data = memcached_get_user_data(memc); php_json_decode(return_value, ZSTR_VAL(payload), ZSTR_LEN(payload), (memc_user_data->serializer == SERIALIZER_JSON_ARRAY), PHP_JSON_PARSER_DEFAULT_DEPTH); + } else { + php_memc_user_data_t *memc_user_data = memcached_get_user_data(memc); + struct simdjson_php_parser *parser = cplus_simdjson_create_parser(); + cplus_simdjson_parse(parser, ZSTR_VAL(payload), ZSTR_LEN(payload), return_value, (memc_user_data->serializer == SERIALIZER_JSON_ARRAY), PHP_JSON_PARSER_DEFAULT_DEPTH); + cplus_simdjson_free_parser(parser); } ``` --- config.m4 | 4 +- config.w32 | 4 +- package.xml | 41 ++++-- php_simdjson.cpp | 8 +- php_simdjson.h | 51 ++++++- src/bindings.h | 46 ------ src/{bindings.cpp => simdjson_bindings.cpp} | 138 ++++++++++-------- ...ndings_defs.h => simdjson_bindings_defs.h} | 6 +- tests/decode_invalid_property.phpt | 2 +- 9 files changed, 167 insertions(+), 133 deletions(-) delete mode 100644 src/bindings.h rename src/{bindings.cpp => simdjson_bindings.cpp} (75%) rename src/{bindings_defs.h => simdjson_bindings_defs.h} (93%) diff --git a/config.m4 b/config.m4 index ff3ed06..b5d9d99 100644 --- a/config.m4 +++ b/config.m4 @@ -33,11 +33,11 @@ if test "$PHP_SIMDJSON" != "no"; then dnl Disable development checks of C simdjson library in php debug builds (can manually override) PHP_NEW_EXTENSION(simdjson, [ php_simdjson.cpp \ - src/bindings.cpp \ + src/simdjson_bindings.cpp \ src/simdjson.cpp], $ext_shared,, "-std=c++17 -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 -DSIMDJSON_EXCEPTIONS=0 -DSIMDJSON_DEVELOPMENT_CHECKS=0", cxx) - PHP_INSTALL_HEADERS([ext/simdjson], [php_simdjson.h, src/bindings.h src/bindings_defs.h]) + PHP_INSTALL_HEADERS([ext/simdjson], [php_simdjson.h src/simdjson_bindings_defs.h]) PHP_ADD_MAKEFILE_FRAGMENT PHP_ADD_BUILD_DIR(src, 1) fi diff --git a/config.w32 b/config.w32 index cb50f4e..08025ea 100644 --- a/config.w32 +++ b/config.w32 @@ -9,8 +9,8 @@ if (PHP_SIMDJSON == "yes") { 'php_simdjson.cpp', 'yes', '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 /std:c++latest'); - ADD_SOURCES(configure_module_dirname + '/src', 'simdjson.cpp bindings.cpp', 'simdjson'); + ADD_SOURCES(configure_module_dirname + '/src', 'simdjson.cpp simdjson_bindings.cpp', 'simdjson'); ADD_FLAG('CFLAGS_SIMDJSON', '/I' + configure_module_dirname); - PHP_INSTALL_HEADERS('ext/simdjson', 'php_simdjson.h src/bindings.h src/bindings_defs.h'); + PHP_INSTALL_HEADERS('ext/simdjson', 'php_simdjson.h src/simdjson_bindings_defs.h'); } // vim:ft=javascript diff --git a/package.xml b/package.xml index 8f2e891..adc5782 100644 --- a/package.xml +++ b/package.xml @@ -20,8 +20,8 @@ --> 2022-10-14 - 2.1.0 - 2.1.0 + 2.2.0dev + 2.2.0dev stable @@ -29,14 +29,8 @@ Apache 2.0 -* Allow out of range 64-bit values in JSON integer syntax and allow floating point values outside of the max/min finite floating point values (i.e. parsing to +/- infinity). - - This allows simdjson_decode() to be used as a replacement for json_decode() in more use cases. -* Return the correct value in simdjson_key_count() for JSON pointers to arrays/objects exceeding size 0xFFFFFF. - Previously, this would be limited to returning at most 0xFFFFFF(16777215). -* Throw 'SimdJsonException extends RuntimeException' instead of RuntimeException. -* Set the error code from simdjson as SimdJsonException->getCode() -* Expose error_code constants from simdjson as `SIMDJSON_ERR_$ERRCODENAME` +* Expose simdjson C APIs marked with PHP_SIMDJSON_API in php_simdjson.h to other C projects. +* Ensure that that header file works when required by C projects and that publicly exposed functions are available. @@ -51,9 +45,8 @@ - - - + + @@ -125,6 +118,28 @@ simdjson + + 2022-10-14 + + 2.1.0 + 2.1.0 + + + stable + stable + + Apache 2.0 + +* Allow out of range 64-bit values in JSON integer syntax and allow floating point values outside of the max/min finite floating point values (i.e. parsing to +/- infinity). + + This allows simdjson_decode() to be used as a replacement for json_decode() in more use cases. +* Return the correct value in simdjson_key_count() for JSON pointers to arrays/objects exceeding size 0xFFFFFF. + Previously, this would be limited to returning at most 0xFFFFFF(16777215). +* Throw 'SimdJsonException extends RuntimeException' instead of RuntimeException. +* Set the error code from simdjson as SimdJsonException->getCode() +* Expose error_code constants from simdjson as `SIMDJSON_ERR_$ERRCODENAME` + + 2022-10-01 diff --git a/php_simdjson.cpp b/php_simdjson.cpp index 40ef141..f04e41d 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -30,9 +30,11 @@ extern "C" { */ PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce; -} +} /* end extern "C" */ -#include "src/bindings.h" +/* C++ header file for simdjson_php helper methods/classes */ +#include "src/simdjson_bindings_defs.h" +/* Single header file from fork of simdjson C project (to imitate php's handling of infinity/overflowing integers in json_decode) */ #include "src/simdjson.h" ZEND_DECLARE_MODULE_GLOBALS(simdjson); @@ -240,7 +242,7 @@ PHP_MINIT_FUNCTION (simdjson) { SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(SCALAR_DOCUMENT_AS_VALUE); ///< A scalar document is treated as a value. SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(OUT_OF_BOUNDS); ///< Attempted to access location outside of document. SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(TRAILING_CONTENT); ///< Unexpected trailing content in the JSON input - SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(INVALID_PROPERTY, 255); ///< Invalid property + SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(INVALID_PROPERTY, INVALID_PHP_PROPERTY); ///< Invalid property return SUCCESS; } diff --git a/php_simdjson.h b/php_simdjson.h index 55193cf..ab8decd 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -14,10 +14,23 @@ #ifndef PHP_SIMDJSON_H #define PHP_SIMDJSON_H +/* + * Put all of the publicly visible functionality and macros into the same header file + * (On windows, the include paths used by the c compiler may be different) + */ +#include "Zend/zend.h" +#include "Zend/zend_portability.h" + +// All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros (both header definitions, and C++ declarations, including function implementations), +// so that pecls written in C can use this functionality without separate C++ files to load bindings.h. +// BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers. +// When I add ZEND_API to declarations and possibly definitions, I get linker errors. +BEGIN_EXTERN_C() + extern zend_module_entry simdjson_module_entry; #define phpext_simdjson_ptr &simdjson_module_entry -#define PHP_SIMDJSON_VERSION "2.1.0" +#define PHP_SIMDJSON_VERSION "2.2.0dev" #define SIMDJSON_SUPPORT_URL "https://github.com/crazyxman/simdjson_php" #define SIMDJSON_PARSE_FAIL 0 #define SIMDJSON_PARSE_SUCCESS 1 @@ -29,9 +42,9 @@ extern zend_module_entry simdjson_module_entry; /* * NOTE: Namespaces and references(&) are C++ only functionality. * To expose this functionality to other C PECLs, - * switch to a forward class declaration of a class that only wraps simdjson::dom::parser + * switch to a forward struct declaration of a struct that only wraps simdjson::dom::parser */ -class simdjson_php_parser; +struct simdjson_php_parser; ZEND_BEGIN_MODULE_GLOBALS(simdjson) /* @@ -39,7 +52,7 @@ ZEND_BEGIN_MODULE_GLOBALS(simdjson) * Note that in ZTS builds, the thread for each request will deliberately have different instances for each concurrently running request. * (The simdjson library is not thread safe) */ - simdjson_php_parser *parser; + struct simdjson_php_parser *parser; ZEND_END_MODULE_GLOBALS(simdjson) PHP_MINIT_FUNCTION(simdjson); @@ -58,11 +71,35 @@ ZEND_TSRMLS_CACHE_EXTERN() * This module defines utilities and helper functions used elsewhere in simdjson. */ #ifdef PHP_WIN32 -# define PHP_SIMDJSON_API __declspec(dllexport) +# define PHP_SIMDJSON_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 -# define PHP_SIMDJSON_API __attribute__ ((visibility("default"))) +# define PHP_SIMDJSON_API __attribute__ ((visibility("default"))) #else -# define PHP_SIMDJSON_API +# define PHP_SIMDJSON_API /* nothing special */ #endif +extern zend_class_entry *simdjson_exception_ce; + +// NOTE: Namespaces and references(&) are C++ only functionality. +// To expose this functionality to other C PECLs, +// switch to a forward class declaration of a class that only wraps simdjson::dom::parser +typedef uint8_t simdjson_php_error_code; + +/* NOTE: Callers should check if len is greater than 4GB - simdjson will always return a non zero error code for those */ + +/* FIXME add cplus_simdjson_get_default_singleton_parser api */ +/* FIXME add cplus_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool assoc) */ + +PHP_SIMDJSON_API const char* cplus_simdjson_error_msg(simdjson_php_error_code); +PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code); +PHP_SIMDJSON_API struct simdjson_php_parser* cplus_simdjson_create_parser(void); +PHP_SIMDJSON_API void cplus_simdjson_free_parser(struct simdjson_php_parser* parser); +PHP_SIMDJSON_API bool cplus_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(struct simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, size_t depth); +PHP_SIMDJSON_API u_short cplus_simdjson_key_exists(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth); + +END_EXTERN_C() + #endif diff --git a/src/bindings.h b/src/bindings.h deleted file mode 100644 index ca70493..0000000 --- a/src/bindings.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | simdjson_php | - +----------------------------------------------------------------------+ - | This source file is subject to version 2.0 of the Apache 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.apache.org/licenses/LICENSE-2.0.html | - +----------------------------------------------------------------------+ - | Author: Jinxi Wang <1054636713@qq.com> | - +----------------------------------------------------------------------+ -*/ - -#ifndef SIMDJSON_PHP_BINDINGS_H -#define SIMDJSON_PHP_BINDINGS_H - -#include "Zend/zend.h" -#include "Zend/zend_portability.h" -#include "php_simdjson.h" - -// All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros (both header definitions, and C++ declarations, including function implementations), -// so that pecls written in C can use this functionality without separate C++ files to load bindings.h. -// BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers. -// When I add ZEND_API to declarations and possibly definitions, I get linker errors. -BEGIN_EXTERN_C() - -extern zend_class_entry *simdjson_exception_ce; - -// NOTE: Namespaces and references(&) are C++ only functionality. -// To expose this functionality to other C PECLs, -// switch to a forward class declaration of a class that only wraps simdjson::dom::parser -class simdjson_php_parser; -typedef uint8_t simdjson_php_error_code; - -PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code); -PHP_SIMDJSON_API simdjson_php_parser* cplus_simdjson_create_parser(void); -PHP_SIMDJSON_API void cplus_simdjson_free_parser(simdjson_php_parser* parser); -PHP_SIMDJSON_API bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth); -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, unsigned char assoc, size_t depth); -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, unsigned char assoc, size_t depth); -PHP_SIMDJSON_API u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth); -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth); - -END_EXTERN_C() - -#endif diff --git a/src/bindings.cpp b/src/simdjson_bindings.cpp similarity index 75% rename from src/bindings.cpp rename to src/simdjson_bindings.cpp index f968ea4..f5fc6d7 100644 --- a/src/bindings.cpp +++ b/src/simdjson_bindings.cpp @@ -20,8 +20,7 @@ extern "C" { } #include "simdjson.h" -#include "bindings.h" -#include "bindings_defs.h" +#include "simdjson_bindings_defs.h" #if PHP_VERSION_ID < 70300 #define zend_string_release_ex(s, persistent) zend_string_release((s)) @@ -29,9 +28,18 @@ extern "C" { #define SIMDJSON_DEPTH_CHECK_THRESHOLD 100000 +PHP_SIMDJSON_API const char* cplus_simdjson_error_msg(simdjson_php_error_code error) +{ + if (UNEXPECTED(error == INVALID_PHP_PROPERTY)) { + return "Invalid property name"; + } else { + return simdjson::error_message((simdjson::error_code) error); + } +} + PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code error) { - zend_throw_exception(simdjson_exception_ce, simdjson::error_message((simdjson::error_code) error), (zend_long) error); + zend_throw_exception(simdjson_exception_ce, cplus_simdjson_error_msg(error), (zend_long) error); } static inline simdjson::simdjson_result @@ -128,44 +136,48 @@ static zend_always_inline void simdjson_set_zval_to_int64(zval *zv, const int64_ ZVAL_LONG(zv, value); } -static zval create_array(simdjson::dom::element element) /* {{{ */ { - zval v; - +static simdjson_php_error_code create_array(simdjson::dom::element element, zval *return_value) /* {{{ */ { switch (element.type()) { //ASCII sort case simdjson::dom::element_type::STRING : - simdjson_set_zval_to_string(&v, element.get_c_str().value_unsafe(), element.get_string_length().value_unsafe()); + simdjson_set_zval_to_string(return_value, element.get_c_str().value_unsafe(), element.get_string_length().value_unsafe()); break; case simdjson::dom::element_type::INT64 : - simdjson_set_zval_to_int64(&v, element.get_int64().value_unsafe()); + simdjson_set_zval_to_int64(return_value, element.get_int64().value_unsafe()); break; /* UINT64 is used for positive values exceeding INT64_MAX */ - case simdjson::dom::element_type::UINT64 : ZVAL_DOUBLE(&v, (double)element.get_uint64().value_unsafe()); + case simdjson::dom::element_type::UINT64 : ZVAL_DOUBLE(return_value, (double)element.get_uint64().value_unsafe()); break; - case simdjson::dom::element_type::DOUBLE : ZVAL_DOUBLE(&v, element.get_double().value_unsafe()); + case simdjson::dom::element_type::DOUBLE : ZVAL_DOUBLE(return_value, element.get_double().value_unsafe()); break; case simdjson::dom::element_type::BOOL : - ZVAL_BOOL(&v, element.get_bool().value_unsafe()); + ZVAL_BOOL(return_value, element.get_bool().value_unsafe()); break; case simdjson::dom::element_type::NULL_VALUE : - ZVAL_NULL(&v); + ZVAL_NULL(return_value); break; case simdjson::dom::element_type::ARRAY : { const auto json_array = element.get_array().value_unsafe(); #if PHP_VERSION_ID >= 70300 if (json_array.size() == 0) { /* Reuse the immutable empty array to save memory */ - ZVAL_EMPTY_ARRAY(&v); + ZVAL_EMPTY_ARRAY(return_value); break; } #endif zend_array *arr; - array_init(&v); - arr = Z_ARR(v); + array_init(return_value); + arr = Z_ARR_P(return_value); for (simdjson::dom::element child : json_array) { - zval value = create_array(child); - zend_hash_next_index_insert(arr, &value); + zval array_element; + simdjson_php_error_code error = create_array(child, &array_element); + if (UNEXPECTED(error)) { + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return error; + } + zend_hash_next_index_insert(arr, &array_element); } break; @@ -175,75 +187,85 @@ static zval create_array(simdjson::dom::element element) /* {{{ */ { #if PHP_VERSION_ID >= 70300 if (json_object.size() == 0) { /* Reuse the immutable empty array to save memory */ - ZVAL_EMPTY_ARRAY(&v); + ZVAL_EMPTY_ARRAY(return_value); break; } #endif zend_array *arr; - array_init(&v); - arr = Z_ARR(v); + array_init(return_value); + arr = Z_ARR_P(return_value); for (simdjson::dom::key_value_pair field : json_object) { - zval value = create_array(field.value); + zval array_element; + simdjson_php_error_code error = create_array(field.value, &array_element); + if (UNEXPECTED(error)) { + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return error; + } /* TODO consider using zend_string_init_existing_interned in php 8.1+ to save memory and time freeing strings. */ - simdjson_add_key_to_symtable(arr, field.key.data(), field.key.size(), &value); + simdjson_add_key_to_symtable(arr, field.key.data(), field.key.size(), &array_element); } break; } EMPTY_SWITCH_DEFAULT_CASE(); } - return v; + return simdjson::SUCCESS; } /* }}} */ -static zval create_object(simdjson::dom::element element) /* {{{ */ { - zval v; - +static simdjson_php_error_code create_object(simdjson::dom::element element, zval *return_value) /* {{{ */ { switch (element.type()) { //ASCII sort case simdjson::dom::element_type::STRING : - simdjson_set_zval_to_string(&v, element.get_c_str().value_unsafe(), element.get_string_length().value_unsafe()); + simdjson_set_zval_to_string(return_value, element.get_c_str().value_unsafe(), element.get_string_length().value_unsafe()); break; case simdjson::dom::element_type::INT64 : - simdjson_set_zval_to_int64(&v, element.get_int64().value_unsafe()); + simdjson_set_zval_to_int64(return_value, element.get_int64().value_unsafe()); break; /* UINT64 is used for positive values exceeding INT64_MAX */ - case simdjson::dom::element_type::UINT64 : ZVAL_DOUBLE(&v, (double)element.get_uint64().value_unsafe()); + case simdjson::dom::element_type::UINT64 : ZVAL_DOUBLE(return_value, (double)element.get_uint64().value_unsafe()); break; - case simdjson::dom::element_type::DOUBLE : ZVAL_DOUBLE(&v, element.get_double().value_unsafe()); + case simdjson::dom::element_type::DOUBLE : ZVAL_DOUBLE(return_value, element.get_double().value_unsafe()); break; case simdjson::dom::element_type::BOOL : - ZVAL_BOOL(&v, element.get_bool().value_unsafe()); + ZVAL_BOOL(return_value, element.get_bool().value_unsafe()); break; case simdjson::dom::element_type::NULL_VALUE : - ZVAL_NULL(&v); + ZVAL_NULL(return_value); break; case simdjson::dom::element_type::ARRAY : { const auto json_array = element.get_array().value_unsafe(); #if PHP_VERSION_ID >= 70300 if (json_array.size() == 0) { /* Reuse the immutable empty array to save memory */ - ZVAL_EMPTY_ARRAY(&v); - break; + ZVAL_EMPTY_ARRAY(return_value); + return simdjson::SUCCESS; } #endif zend_array *arr; - array_init(&v); - arr = Z_ARR(v); + array_init(return_value); + arr = Z_ARR_P(return_value); for (simdjson::dom::element child : json_array) { - zval value = create_object(child); + zval value; + simdjson_php_error_code error = create_object(child, &value); + if (UNEXPECTED(error)) { + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return error; + } zend_hash_next_index_insert(arr, &value); } break; } case simdjson::dom::element_type::OBJECT : { const auto json_object = element.get_object().value_unsafe(); - object_init(&v); + object_init(return_value); #if PHP_VERSION_ID >= 80000 - zend_object *obj = Z_OBJ(v); + zend_object *obj = Z_OBJ_P(return_value); #endif for (simdjson::dom::key_value_pair field : json_object) { @@ -251,13 +273,18 @@ static zval create_object(simdjson::dom::element element) /* {{{ */ { size_t size = field.key.size(); /* PHP 7.1 allowed using the empty string as a property of an object */ if (UNEXPECTED(data[0] == '\0') && (PHP_VERSION_ID < 70100 || UNEXPECTED(size > 0))) { - if (!EG(exception)) { - /* Use a number that won't be in the simdjson bindings */ - zend_throw_exception(simdjson_exception_ce, "Invalid property name", 255); - } - return v; + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + /* Use a number that won't be in the simdjson bindings */ + return INVALID_PHP_PROPERTY; + } + zval value; + simdjson_php_error_code error = create_object(field.value, &value); + if (error) { + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return error; } - zval value = create_object(field.value); /* Add the key to the object */ #if PHP_VERSION_ID >= 80000 @@ -276,13 +303,13 @@ static zval create_object(simdjson::dom::element element) /* {{{ */ { zval zkey; zend_string *key = size == 1 ? ZSTR_CHAR((unsigned char)data[0]) : ZSTR_EMPTY_ALLOC(); ZVAL_INTERNED_STR(&zkey, key); - zend_std_write_property(&v, &zkey, &value, NULL); + zend_std_write_property(return_value, &zkey, &value, NULL); } else # endif { zval zkey; ZVAL_STRINGL(&zkey, data, size); - zend_std_write_property(&v, &zkey, &value, NULL); + zend_std_write_property(return_value, &zkey, &value, NULL); zval_ptr_dtor_nogc(&zkey); } #endif @@ -294,10 +321,9 @@ static zval create_object(simdjson::dom::element element) /* {{{ */ { } EMPTY_SWITCH_DEFAULT_CASE(); } - return v; + return simdjson::SUCCESS; } - /* }}} */ PHP_SIMDJSON_API simdjson_php_parser* cplus_simdjson_create_parser(void) /* {{{ */ { @@ -320,7 +346,7 @@ PHP_SIMDJSON_API bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, unsigned char assoc, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::error_code error = build_parsed_json_cust(parser, doc, json, len, true, depth); if (error) { @@ -328,25 +354,23 @@ PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parse } if (assoc) { - *return_value = create_array(doc); + return create_array(doc, return_value); } else { - *return_value = create_object(doc); + return create_object(doc, return_value); } - return simdjson::SUCCESS; } /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, unsigned char assoc, +PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::dom::element element; SIMDJSON_TRY(build_parsed_json_cust(parser, doc, json, len, true, depth)); SIMDJSON_TRY(get_key_with_optional_prefix(doc, key).get(element)); if (assoc) { - *return_value = create_array(element); + return create_array(element, return_value); } else { - *return_value = create_object(element); + return create_object(element, return_value); } - return 0; } /* }}} */ diff --git a/src/bindings_defs.h b/src/simdjson_bindings_defs.h similarity index 93% rename from src/bindings_defs.h rename to src/simdjson_bindings_defs.h index 2532698..eac3fc7 100644 --- a/src/bindings_defs.h +++ b/src/simdjson_bindings_defs.h @@ -15,11 +15,13 @@ #include "simdjson.h" +#define INVALID_PHP_PROPERTY 255 + // NOTE: Namespaces are C++ only functionality. // To expose this functionality to other C PECLs, // bindings.h exposes a forward class declaration of a class that only wraps simdjson::dom::parser -class simdjson_php_parser { - public: +struct simdjson_php_parser { +public: simdjson::dom::parser parser; }; diff --git a/tests/decode_invalid_property.phpt b/tests/decode_invalid_property.phpt index 194ecc6..0a13f29 100644 --- a/tests/decode_invalid_property.phpt +++ b/tests/decode_invalid_property.phpt @@ -57,7 +57,7 @@ array(2) { string(4) "test" } string(21) "Invalid property name" -object(stdClass)#2 (1) { +object(stdClass)#1 (1) { [""]=> bool(true) } From 2bb0470523d5b6e3d3b0b03abe075a22fb9724c9 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Fri, 14 Oct 2022 00:05:46 -0400 Subject: [PATCH 3/9] Add missing PHP_SIMDJSON_API macro --- php_simdjson.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php_simdjson.h b/php_simdjson.h index ab8decd..ccd733c 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -78,7 +78,7 @@ ZEND_TSRMLS_CACHE_EXTERN() # define PHP_SIMDJSON_API /* nothing special */ #endif -extern zend_class_entry *simdjson_exception_ce; +extern PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce; // NOTE: Namespaces and references(&) are C++ only functionality. // To expose this functionality to other C PECLs, From cf4b6bee2b84f00e493707f8b4c4fc1ad2811f3c Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sat, 15 Oct 2022 09:43:01 -0400 Subject: [PATCH 4/9] Refactor this, throw SimdJsonValueError --- php_simdjson.cpp | 50 ++++++++----- php_simdjson.h | 132 ++++++++++++++++++++++++++++------- simdjson.stub.php | 9 +++ simdjson_arginfo.h | 17 ++++- src/simdjson_bindings.cpp | 47 ++++++------- src/simdjson_bindings_defs.h | 2 - 6 files changed, 184 insertions(+), 73 deletions(-) diff --git a/php_simdjson.cpp b/php_simdjson.cpp index f04e41d..ac64854 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -29,6 +29,7 @@ extern "C" { * Both the declaration and the definition of PHP_SIMDJSON_API variables, functions must be within an 'extern "C"' block for Windows */ PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce; +PHP_SIMDJSON_API zend_class_entry *simdjson_value_error_ce; } /* end extern "C" */ @@ -81,7 +82,7 @@ ZEND_END_ARG_INFO() static simdjson_php_parser *simdjson_get_parser() { simdjson_php_parser *parser = SIMDJSON_G(parser); if (parser == NULL) { - parser = cplus_simdjson_create_parser(); + parser = php_simdjson_create_parser(); SIMDJSON_G(parser) = parser; ZEND_ASSERT(parser != NULL); } @@ -111,7 +112,7 @@ PHP_FUNCTION (simdjson_is_valid) { if (!simdjson_validate_depth(depth)) { RETURN_NULL(); } - bool is_json = cplus_simdjson_is_valid(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth); + bool is_json = php_simdjson_is_valid(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth); ZVAL_BOOL(return_value, is_json); } @@ -125,9 +126,9 @@ PHP_FUNCTION (simdjson_decode) { if (!simdjson_validate_depth(depth)) { RETURN_NULL(); } - simdjson_php_error_code error = cplus_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, assoc, depth); + simdjson_php_error_code error = php_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, assoc, depth); if (error) { - cplus_simdjson_throw_jsonexception(error); + php_simdjson_throw_jsonexception(error); } } @@ -143,9 +144,9 @@ PHP_FUNCTION (simdjson_key_value) { if (!simdjson_validate_depth(depth)) { RETURN_NULL(); } - simdjson_php_error_code error = cplus_simdjson_key_value(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, assoc, depth); + simdjson_php_error_code error = php_simdjson_key_value(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, assoc, depth); if (error) { - cplus_simdjson_throw_jsonexception(error); + php_simdjson_throw_jsonexception(error); } } @@ -153,15 +154,19 @@ PHP_FUNCTION (simdjson_key_count) { zend_string *json = NULL; zend_string *key = NULL; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &json, &key, &depth) == FAILURE) { + bool throw_if_uncountable = false; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|lb", &json, &key, &depth, &throw_if_uncountable) == FAILURE) { return; } if (!simdjson_validate_depth(depth)) { RETURN_NULL(); } - simdjson_php_error_code error = cplus_simdjson_key_count(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, depth); + simdjson_php_error_code error = php_simdjson_key_count(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, depth, throw_if_uncountable); if (error) { - cplus_simdjson_throw_jsonexception(error); + if (error == SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE && !throw_if_uncountable) { + RETURN_LONG(0); + } + php_simdjson_throw_jsonexception(error); } } @@ -175,13 +180,16 @@ PHP_FUNCTION (simdjson_key_exists) { if (!simdjson_validate_depth(depth)) { return; } - u_short stats = cplus_simdjson_key_exists(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), depth); - if (SIMDJSON_PARSE_FAIL == stats) { - RETURN_NULL(); - } else if (SIMDJSON_PARSE_KEY_EXISTS == stats) { - RETURN_TRUE; - } else if (SIMDJSON_PARSE_KEY_NOEXISTS == stats) { - RETURN_FALSE; + simdjson_php_error_code error = php_simdjson_key_exists(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), depth); + switch (error) { + case simdjson::SUCCESS: + RETURN_TRUE; + case simdjson::NO_SUCH_FIELD: + case simdjson::INDEX_OUT_OF_BOUNDS: + case simdjson::INCORRECT_TYPE: + RETURN_FALSE; + default: + php_simdjson_throw_jsonexception(error); } } @@ -212,6 +220,11 @@ ZEND_TSRMLS_CACHE_UPDATE(); #define SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(errcode, val) REGISTER_LONG_CONSTANT("SIMDJSON_ERR_" #errcode, (val), CONST_PERSISTENT) PHP_MINIT_FUNCTION (simdjson) { simdjson_exception_ce = register_class_SimdJsonException(spl_ce_RuntimeException); +#if PHP_VERSION_ID >= 80000 + simdjson_value_error_ce = register_class_SimdJsonValueError(zend_ce_value_error); +#else + simdjson_value_error_ce = register_class_SimdJsonValueError(zend_ce_error); +#endif SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(CAPACITY); ///< This parser can't support a document that big // SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(MEMALLOC); ///< Error allocating memory, most likely out of memory SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(TAPE_ERROR); ///< Something went wrong, this is a generic error @@ -242,7 +255,8 @@ PHP_MINIT_FUNCTION (simdjson) { SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(SCALAR_DOCUMENT_AS_VALUE); ///< A scalar document is treated as a value. SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(OUT_OF_BOUNDS); ///< Attempted to access location outside of document. SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(TRAILING_CONTENT); ///< Unexpected trailing content in the JSON input - SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(INVALID_PROPERTY, INVALID_PHP_PROPERTY); ///< Invalid property + SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(KEY_COUNT_NOT_COUNTABLE, SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE); ///< JSON pointer refers to a value that cannot be counted + SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(INVALID_PROPERTY, SIMDJSON_PHP_ERR_INVALID_PHP_PROPERTY); ///< Invalid property name return SUCCESS; } @@ -268,7 +282,7 @@ PHP_RINIT_FUNCTION (simdjson) { PHP_RSHUTDOWN_FUNCTION (simdjson) { simdjson_php_parser *parser = SIMDJSON_G(parser); if (parser != NULL) { - cplus_simdjson_free_parser(parser); + php_simdjson_free_parser(parser); SIMDJSON_G(parser) = NULL; } return SUCCESS; diff --git a/php_simdjson.h b/php_simdjson.h index ccd733c..5f199aa 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -14,6 +14,20 @@ #ifndef PHP_SIMDJSON_H #define PHP_SIMDJSON_H +/* + * Error constant implementation notes: + * + * - 0 always means success, and non-0 is a failure condition. + * - Prefix these with SIMDJSON_PHP_ERR_ to distinguish them from other values. + * - The error codes (value or labels) belonging to the C simdjson project may change in the future. + * + * Maybe these should be exposed as extern const once there's a project that needs the other values. + * For now, they're also exposed as `REGISTER_LONG_CONSTANT("SIMDJSON_ERR_" #errcode, (val), CONST_PERSISTENT)` + */ +#define SIMDJSON_PHP_ERR_SUCCESS 0 +#define SIMDJSON_PHP_ERR_INVALID_PHP_PROPERTY 255 +#define SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE 254 + /* * Put all of the publicly visible functionality and macros into the same header file * (On windows, the include paths used by the c compiler may be different) @@ -21,10 +35,17 @@ #include "Zend/zend.h" #include "Zend/zend_portability.h" -// All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros (both header definitions, and C++ declarations, including function implementations), -// so that pecls written in C can use this functionality without separate C++ files to load bindings.h. -// BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers. -// When I add ZEND_API to declarations and possibly definitions, I get linker errors. +/* + * All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros + * (both header definitions, and C++ declarations, including function implementations), + * so that pecls written in C can use this functionality without separate C++ files to load bindings.h. + * + * This header file deliberately does not depend on other header files in this project, + * to make including this header file easier for other PECLs (avoid include path issues) + * + * BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers. + * (This macro can be used from both C and C++ source files) + */ BEGIN_EXTERN_C() extern zend_module_entry simdjson_module_entry; @@ -32,10 +53,6 @@ extern zend_module_entry simdjson_module_entry; #define PHP_SIMDJSON_VERSION "2.2.0dev" #define SIMDJSON_SUPPORT_URL "https://github.com/crazyxman/simdjson_php" -#define SIMDJSON_PARSE_FAIL 0 -#define SIMDJSON_PARSE_SUCCESS 1 -#define SIMDJSON_PARSE_KEY_EXISTS 2 -#define SIMDJSON_PARSE_KEY_NOEXISTS 3 #define SIMDJSON_PARSE_DEFAULT_DEPTH 512 @@ -51,6 +68,8 @@ ZEND_BEGIN_MODULE_GLOBALS(simdjson) * php::simdjson::parser pointer, constructed on first use with request-scope lifetime. * Note that in ZTS builds, the thread for each request will deliberately have different instances for each concurrently running request. * (The simdjson library is not thread safe) + * + * This is similar to php-src's ext/session session data storage. */ struct simdjson_php_parser *parser; ZEND_END_MODULE_GLOBALS(simdjson) @@ -67,9 +86,7 @@ ZEND_TSRMLS_CACHE_EXTERN() #endif #endif -/* - * This module defines utilities and helper functions used elsewhere in simdjson. - */ +/* Only the functions and variables defined with PHP_SIMDJSON_API can be loaded by other PECLs */ #ifdef PHP_WIN32 # define PHP_SIMDJSON_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 @@ -78,27 +95,90 @@ ZEND_TSRMLS_CACHE_EXTERN() # define PHP_SIMDJSON_API /* nothing special */ #endif +/** Defines 'class SimdJsonException' */ extern PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce; +extern PHP_SIMDJSON_API zend_class_entry *simdjson_value_error_ce; -// NOTE: Namespaces and references(&) are C++ only functionality. -// To expose this functionality to other C PECLs, -// switch to a forward class declaration of a class that only wraps simdjson::dom::parser +/** + * NOTE: Namespaces and references(&) and classes (instead of structs) are C++ only functionality. + * + * To expose this functionality to other C PECLs, + * switch to a forward class declaration of a class that only wraps simdjson::dom::parser + */ typedef uint8_t simdjson_php_error_code; /* NOTE: Callers should check if len is greater than 4GB - simdjson will always return a non zero error code for those */ -/* FIXME add cplus_simdjson_get_default_singleton_parser api */ -/* FIXME add cplus_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool assoc) */ - -PHP_SIMDJSON_API const char* cplus_simdjson_error_msg(simdjson_php_error_code); -PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code); -PHP_SIMDJSON_API struct simdjson_php_parser* cplus_simdjson_create_parser(void); -PHP_SIMDJSON_API void cplus_simdjson_free_parser(struct simdjson_php_parser* parser); -PHP_SIMDJSON_API bool cplus_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth); -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(struct simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth); -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, size_t depth); -PHP_SIMDJSON_API u_short cplus_simdjson_key_exists(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth); -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth); +/* FIXME add php_simdjson_get_default_singleton_parser api */ +/* FIXME add php_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool assoc) */ + +/** + * Returns the error message corresponding to a given error code returned by a call to simdjson_php. + */ +PHP_SIMDJSON_API const char* php_simdjson_error_msg(simdjson_php_error_code code); +/** + * Throw a SimdJsonException with the provided error code and the corresponding error message. + */ +PHP_SIMDJSON_API void php_simdjson_throw_jsonexception(simdjson_php_error_code code); +/** + * Create a brand new parser instance. + * + * The caller must free it with php_simdjson_free_parser once it is no longer used. + * + * Callers may use this instead of the shared singleton parser when memory usage is a concern + * (e.g. the PECLs are likely to be used load a string that's megabytes long in a long-lived php process) + */ +PHP_SIMDJSON_API struct simdjson_php_parser* php_simdjson_create_parser(void); +/** + * Release a parser **constructed by php_simdjson_create_parser** and all associated buffers. + */ +PHP_SIMDJSON_API void php_simdjson_free_parser(struct simdjson_php_parser* parser); +/** + * Returns true if the given json string is valid + */ +PHP_SIMDJSON_API bool php_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth); +/** + * Parses the given string into a return code. + * + * If the returned error code is 0, then return_value contains the parsed value. + * If the returned error code is non-0, then return_value will not be initialized. + */ +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(struct simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth); +/** + * Parses the part of the given string at the json pointer 'key' into a PHP value at return_value + * + * If the returned error code is 0, then return_value contains the parsed value (or null). + * If the returned error code is non-0, then return_value will not be initialized. + * + * - SIMDJSON_ERR_NO_SUCH_FIELD is returned if the json pointer 'key' is not found + * - Other errors are returned for invalid json, etc. + * + * @see https://www.rfc-editor.org/rfc/rfc6901.html + */ +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_value(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, size_t depth); +/** + * Checks if the json pointer 'key' exists in the given json string. + * + * - 0 if the key exists + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + * + * @see https://www.rfc-editor.org/rfc/rfc6901.html + */ +PHP_SIMDJSON_API uint8_t php_simdjson_key_exists(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth); +/** + * Count the keys of the given array/object at json pointer 'key' exists in the given json string. + * + * If the returned error code is 0, then the zval in return_value is overwritten with the key count using ZVAL_LONG. + * - For arrays, this is the array size + * - For objects, this is the object size + * - For other values, this is 0 (when fail_if_uncountable is false) + * + * @see https://www.rfc-editor.org/rfc/rfc6901.html + */ +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_count(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth, bool fail_if_uncountable); END_EXTERN_C() diff --git a/simdjson.stub.php b/simdjson.stub.php index 5b535ec..e5e1e72 100644 --- a/simdjson.stub.php +++ b/simdjson.stub.php @@ -9,3 +9,12 @@ class SimdJsonException extends RuntimeException { } + +/** + * Thrown for invalid depths, with similar behavior to php 8.0. + * + * NOTE: https://www.php.net/valueerror was added in php 8.0. + * In older php versions, this extends Error instead. + */ +class SimdJsonValueError extends ValueError { +} diff --git a/simdjson_arginfo.h b/simdjson_arginfo.h index 0b47c57..2b277b8 100644 --- a/simdjson_arginfo.h +++ b/simdjson_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e7f05ec4984c79ce2696174896f000a7082de6ed */ + * Stub hash: 7d4b37b2b4c79e2802efe01a53bb11cdfbc62d7c */ @@ -8,6 +8,11 @@ static const zend_function_entry class_SimdJsonException_methods[] = { ZEND_FE_END }; + +static const zend_function_entry class_SimdJsonValueError_methods[] = { + ZEND_FE_END +}; + static zend_class_entry *register_class_SimdJsonException(zend_class_entry *class_entry_RuntimeException) { zend_class_entry ce, *class_entry; @@ -17,3 +22,13 @@ static zend_class_entry *register_class_SimdJsonException(zend_class_entry *clas return class_entry; } + +static zend_class_entry *register_class_SimdJsonValueError(zend_class_entry *class_entry_ValueError) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "SimdJsonValueError", class_SimdJsonValueError_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ValueError); + + return class_entry; +} diff --git a/src/simdjson_bindings.cpp b/src/simdjson_bindings.cpp index f5fc6d7..b4e1281 100644 --- a/src/simdjson_bindings.cpp +++ b/src/simdjson_bindings.cpp @@ -28,18 +28,21 @@ extern "C" { #define SIMDJSON_DEPTH_CHECK_THRESHOLD 100000 -PHP_SIMDJSON_API const char* cplus_simdjson_error_msg(simdjson_php_error_code error) +PHP_SIMDJSON_API const char* php_simdjson_error_msg(simdjson_php_error_code error) { - if (UNEXPECTED(error == INVALID_PHP_PROPERTY)) { - return "Invalid property name"; - } else { - return simdjson::error_message((simdjson::error_code) error); + switch (error) { + case SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE: + return "JSON pointer refers to a value that cannot be counted"; + case SIMDJSON_PHP_ERR_INVALID_PHP_PROPERTY: + return "Invalid property name"; + default: + return simdjson::error_message((simdjson::error_code) error); } } -PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code error) +PHP_SIMDJSON_API void php_simdjson_throw_jsonexception(simdjson_php_error_code error) { - zend_throw_exception(simdjson_exception_ce, cplus_simdjson_error_msg(error), (zend_long) error); + zend_throw_exception(simdjson_exception_ce, php_simdjson_error_msg(error), (zend_long) error); } static inline simdjson::simdjson_result @@ -276,7 +279,7 @@ static simdjson_php_error_code create_object(simdjson::dom::element element, zva zval_ptr_dtor(return_value); ZVAL_NULL(return_value); /* Use a number that won't be in the simdjson bindings */ - return INVALID_PHP_PROPERTY; + return SIMDJSON_PHP_ERR_INVALID_PHP_PROPERTY; } zval value; simdjson_php_error_code error = create_object(field.value, &value); @@ -326,15 +329,15 @@ static simdjson_php_error_code create_object(simdjson::dom::element element, zva /* }}} */ -PHP_SIMDJSON_API simdjson_php_parser* cplus_simdjson_create_parser(void) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_parser* php_simdjson_create_parser(void) /* {{{ */ { return new simdjson_php_parser(); } -PHP_SIMDJSON_API void cplus_simdjson_free_parser(simdjson_php_parser* parser) /* {{{ */ { +PHP_SIMDJSON_API void php_simdjson_free_parser(simdjson_php_parser* parser) /* {{{ */ { delete parser; } -PHP_SIMDJSON_API bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API bool php_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth) /* {{{ */ { simdjson::dom::element doc; /* The depth is passed in to ensure this behaves the same way for the same arguments */ auto error = build_parsed_json_cust(parser, doc, json, len, true, depth); @@ -346,7 +349,7 @@ PHP_SIMDJSON_API bool cplus_simdjson_is_valid(simdjson_php_parser* parser, const /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::error_code error = build_parsed_json_cust(parser, doc, json, len, true, depth); if (error) { @@ -360,7 +363,7 @@ PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_parse(simdjson_php_parse } } /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::dom::element element; @@ -375,22 +378,15 @@ PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_value(simdjson_php_p /* }}} */ -PHP_SIMDJSON_API u_short cplus_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_exists(simdjson_php_parser* parser, const char *json, size_t len, const char *key, size_t depth) /* {{{ */ { simdjson::dom::element doc; - auto error = build_parsed_json_cust(parser, doc, json, len, true, depth); - if (error) { - return SIMDJSON_PARSE_KEY_NOEXISTS; - } - error = get_key_with_optional_prefix(doc, key).error(); - if (error) { - return SIMDJSON_PARSE_KEY_NOEXISTS; - } - return SIMDJSON_PARSE_KEY_EXISTS; + SIMDJSON_TRY(build_parsed_json_cust(parser, doc, json, len, true, depth)); + return get_key_with_optional_prefix(doc, key).error(); } /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_count(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, size_t depth, bool fail_if_uncountable) /* {{{ */ { simdjson::dom::element doc; simdjson::dom::element element; @@ -430,8 +426,7 @@ PHP_SIMDJSON_API simdjson_php_error_code cplus_simdjson_key_count(simdjson_php_p break; } default: - key_count = 0; - break; + return SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE; } ZVAL_LONG(return_value, key_count); return simdjson::SUCCESS; diff --git a/src/simdjson_bindings_defs.h b/src/simdjson_bindings_defs.h index eac3fc7..5ddf4eb 100644 --- a/src/simdjson_bindings_defs.h +++ b/src/simdjson_bindings_defs.h @@ -15,8 +15,6 @@ #include "simdjson.h" -#define INVALID_PHP_PROPERTY 255 - // NOTE: Namespaces are C++ only functionality. // To expose this functionality to other C PECLs, // bindings.h exposes a forward class declaration of a class that only wraps simdjson::dom::parser From c34c575d46d590db28e8afab565bdddb4a555fcd Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sat, 15 Oct 2022 09:58:09 -0400 Subject: [PATCH 5/9] Change handling of depth errors, key_exists exceptions And throw exceptions in simdjson_key_count instead of returning null --- php_simdjson.cpp | 55 +++++++++++++++++++++++-------------- php_simdjson.h | 8 +++++- simdjson.stub.php | 6 +++- simdjson_arginfo.h | 2 +- tests/decode_max_depth.phpt | 42 ++++++++++++++++------------ 5 files changed, 73 insertions(+), 40 deletions(-) diff --git a/php_simdjson.cpp b/php_simdjson.cpp index ac64854..cae6114 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -38,6 +38,11 @@ PHP_SIMDJSON_API zend_class_entry *simdjson_value_error_ce; /* Single header file from fork of simdjson C project (to imitate php's handling of infinity/overflowing integers in json_decode) */ #include "src/simdjson.h" +/* Define RETURN_THROWS macro in older php versions */ +#ifndef RETURN_THROWS +#define RETURN_THROWS() do { ZEND_ASSERT(EG(exception)); (void) return_value; return; } while (0) +#endif + ZEND_DECLARE_MODULE_GLOBALS(simdjson); #if PHP_VERSION_ID >= 70200 @@ -48,7 +53,7 @@ ZEND_DECLARE_MODULE_GLOBALS(simdjson); ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, NULL, allow_null) #endif -SIMDJSON_ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(simdjson_is_valid_arginfo, 0, 1, _IS_BOOL, 1) +SIMDJSON_ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(simdjson_is_valid_arginfo, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, depth, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -92,12 +97,19 @@ static simdjson_php_parser *simdjson_get_parser() { // The simdjson parser accepts strings with at most 32-bit lengths, for now. #define SIMDJSON_MAX_DEPTH ((zend_long)((SIZE_MAX / 8) < (UINT32_MAX / 2) ? (SIZE_MAX / 8) : (UINT32_MAX / 2))) -static bool simdjson_validate_depth(zend_long depth) { +static ZEND_COLD void simdjson_throw_depth_must_be_positive(const char *function_name, const int arg_num) { + zend_throw_error(simdjson_value_error_ce, "%s(): Argument #%d ($depth) must be greater than zero", function_name, arg_num); +} +static ZEND_COLD void simdjson_throw_depth_too_large(const char *function_name, const int arg_num) { + zend_throw_error(simdjson_value_error_ce, "%s(): Argument #%d ($depth) exceeds maximum allowed value of " ZEND_LONG_FMT, function_name, arg_num, SIMDJSON_MAX_DEPTH); +} + +static zend_always_inline bool simdjson_validate_depth(zend_long depth, const char *function_name, const int arg_num) { if (UNEXPECTED(depth <= 0)) { - php_error_docref(NULL, E_WARNING, "Depth must be greater than zero"); + simdjson_throw_depth_must_be_positive(function_name, arg_num); return false; } else if (UNEXPECTED(depth > SIMDJSON_MAX_DEPTH)) { - php_error_docref(NULL, E_WARNING, "Depth exceeds maximum allowed value of " ZEND_LONG_FMT, SIMDJSON_MAX_DEPTH); + simdjson_throw_depth_too_large(function_name, arg_num); return false; } return true; @@ -107,10 +119,10 @@ PHP_FUNCTION (simdjson_is_valid) { zend_string *json = NULL; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &json, &depth) == FAILURE) { - return; + RETURN_THROWS(); } - if (!simdjson_validate_depth(depth)) { - RETURN_NULL(); + if (!simdjson_validate_depth(depth, "simdjson_is_valid", 2)) { + RETURN_THROWS(); } bool is_json = php_simdjson_is_valid(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth); ZVAL_BOOL(return_value, is_json); @@ -121,32 +133,33 @@ PHP_FUNCTION (simdjson_decode) { zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; zend_string *json = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|bl", &json, &assoc, &depth) == FAILURE) { - return; + RETURN_THROWS(); } - if (!simdjson_validate_depth(depth)) { - RETURN_NULL(); + if (!simdjson_validate_depth(depth, "simdjson_decode", 2)) { + RETURN_THROWS(); } simdjson_php_error_code error = php_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, assoc, depth); if (error) { php_simdjson_throw_jsonexception(error); + RETURN_THROWS(); } } PHP_FUNCTION (simdjson_key_value) { - zend_string *json = NULL; zend_string *key = NULL; zend_bool assoc = 0; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|bl", &json, &key, &assoc, &depth) == FAILURE) { - return; + RETURN_THROWS(); } - if (!simdjson_validate_depth(depth)) { - RETURN_NULL(); + if (!simdjson_validate_depth(depth, "simdjson_key_value", 4)) { + RETURN_THROWS(); } simdjson_php_error_code error = php_simdjson_key_value(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, assoc, depth); if (error) { php_simdjson_throw_jsonexception(error); + RETURN_THROWS(); } } @@ -156,10 +169,10 @@ PHP_FUNCTION (simdjson_key_count) { zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; bool throw_if_uncountable = false; if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|lb", &json, &key, &depth, &throw_if_uncountable) == FAILURE) { - return; + RETURN_THROWS(); } - if (!simdjson_validate_depth(depth)) { - RETURN_NULL(); + if (!simdjson_validate_depth(depth, "simdjson_key_count", 4)) { + RETURN_THROWS(); } simdjson_php_error_code error = php_simdjson_key_count(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, depth, throw_if_uncountable); if (error) { @@ -167,6 +180,7 @@ PHP_FUNCTION (simdjson_key_count) { RETURN_LONG(0); } php_simdjson_throw_jsonexception(error); + RETURN_THROWS(); } } @@ -175,10 +189,10 @@ PHP_FUNCTION (simdjson_key_exists) { zend_string *key = NULL; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &json, &key, &depth) == FAILURE) { - return; + RETURN_THROWS(); } - if (!simdjson_validate_depth(depth)) { - return; + if (!simdjson_validate_depth(depth, "simdjson_key_exists", 3)) { + RETURN_THROWS(); } simdjson_php_error_code error = php_simdjson_key_exists(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), depth); switch (error) { @@ -190,6 +204,7 @@ PHP_FUNCTION (simdjson_key_exists) { RETURN_FALSE; default: php_simdjson_throw_jsonexception(error); + RETURN_THROWS(); } } diff --git a/php_simdjson.h b/php_simdjson.h index 5f199aa..537a11a 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -51,7 +51,13 @@ BEGIN_EXTERN_C() extern zend_module_entry simdjson_module_entry; #define phpext_simdjson_ptr &simdjson_module_entry -#define PHP_SIMDJSON_VERSION "2.2.0dev" +#define PHP_SIMDJSON_VERSION "3.0.0dev" +/** + * PHP_SIMDJSON_VERSION_ID has the same format as PHP_VERSION_ID: Major version * 10000 + Minor version * 100 + Patch version. + * This is meant for use by PECL extensions that depend on simdjson. + */ +#define PHP_SIMDJSON_VERSION_ID 30000 + #define SIMDJSON_SUPPORT_URL "https://github.com/crazyxman/simdjson_php" #define SIMDJSON_PARSE_DEFAULT_DEPTH 512 diff --git a/simdjson.stub.php b/simdjson.stub.php index e5e1e72..3549f78 100644 --- a/simdjson.stub.php +++ b/simdjson.stub.php @@ -11,10 +11,14 @@ class SimdJsonException extends RuntimeException { } /** - * Thrown for invalid depths, with similar behavior to php 8.0. + * Thrown for error conditions on fields such as $depth that are not expected to be + * from user-provided JSON, with similar behavior to php 8.0. * * NOTE: https://www.php.net/valueerror was added in php 8.0. * In older php versions, this extends Error instead. + * + * When support for php 8.0 is dropped completely, + * a major release of simdjson will likely switch to a standard ValueError. */ class SimdJsonValueError extends ValueError { } diff --git a/simdjson_arginfo.h b/simdjson_arginfo.h index 2b277b8..684094c 100644 --- a/simdjson_arginfo.h +++ b/simdjson_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7d4b37b2b4c79e2802efe01a53bb11cdfbc62d7c */ + * Stub hash: b8ea2fddf5afdd9d45d2541d63169e659208d20e */ diff --git a/tests/decode_max_depth.phpt b/tests/decode_max_depth.phpt index e8e3b69..6018272 100644 --- a/tests/decode_max_depth.phpt +++ b/tests/decode_max_depth.phpt @@ -6,29 +6,37 @@ declare(strict_types=1); ini_set('error_reporting', (string)E_ALL); ini_set('display_errors', 'stderr'); -foreach ([1024, PHP_INT_MAX >> 1] as $depth) { - $value = \simdjson_decode('[]', true, $depth); - var_dump($value); - $value = \simdjson_key_count('{"a":"b"}', 'a', $depth); - var_dump($value); - $value = \simdjson_key_value('{"a":{}}', 'a', true, $depth); - var_dump($value); - $value = \simdjson_is_valid('{}', $depth); - var_dump($value); +function dump(Closure $c) { + try { + var_dump($c()); + } catch (SimdJsonValueError $e) { + printf("Caught %s: %s\n", get_class($e), $e->getMessage()); + } +} + +foreach ([0, 1024, PHP_INT_MAX >> 1] as $depth) { + dump(function () use ($depth) { return simdjson_decode('[]', true, $depth); }); + dump(function () use ($depth) { return simdjson_key_count('{"a":"b"}', 'a', $depth); }); + dump(function () use ($depth) { return simdjson_key_value('{"a":{}}', 'a', true, $depth); }); + dump(function () use ($depth) { return simdjson_key_exists('{"a":{}}', 'a', $depth); }); + dump(function () use ($depth) { return simdjson_is_valid('{}', $depth); }); } ?> --EXPECTF-- +Caught SimdJsonValueError: simdjson_decode(): Argument #2 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_key_count(): Argument #4 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_key_value(): Argument #4 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_key_exists(): Argument #3 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) must be greater than zero array(0) { } int(0) array(0) { } bool(true) -Warning: simdjson_decode(): Depth exceeds maximum allowed value of %d in %s on line 7 -NULL -Warning: simdjson_key_count(): Depth exceeds maximum allowed value of %d in %s on line 9 -NULL -Warning: simdjson_key_value(): Depth exceeds maximum allowed value of %d in %s on line 11 -NULL -Warning: simdjson_is_valid(): Depth exceeds maximum allowed value of %d in %s on line 13 -NULL +bool(true) +Caught SimdJsonValueError: simdjson_decode(): Argument #2 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_key_count(): Argument #4 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_key_value(): Argument #4 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_key_exists(): Argument #3 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) exceeds maximum allowed value of %d From b214c55dbb16e53e2c396a4ebc0c08eb9f5a0e44 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sat, 15 Oct 2022 10:15:45 -0400 Subject: [PATCH 6/9] Fix test failures, remove redundant test --- package.xml | 14 ++++++++++---- php_simdjson.cpp | 5 +++-- tests/compat/json_decode_error.phpt | 14 -------------- tests/decode_max_depth.phpt | 21 ++++++++++++--------- tests/depth.phpt | 9 +++------ tests/dump.inc | 8 ++++++++ tests/is_valid_args.phpt | 2 +- tests/is_valid_edge_cases.phpt | 20 ++++++++++---------- tests/key_count_args.phpt | 5 +++-- tests/key_exists_args.phpt | 2 +- 10 files changed, 51 insertions(+), 49 deletions(-) delete mode 100644 tests/compat/json_decode_error.phpt create mode 100644 tests/dump.inc diff --git a/package.xml b/package.xml index adc5782..cca5d26 100644 --- a/package.xml +++ b/package.xml @@ -20,8 +20,8 @@ --> 2022-10-14 - 2.2.0dev - 2.2.0dev + 3.0.0dev + 3.0.0dev stable @@ -29,8 +29,14 @@ Apache 2.0 +* Add SimdJsonValueError. In php 8.0+, it extends ValueError, and it extends Error in older php versions. + This provides an API similar to the JSON module, which started throwing ValueError for invalid depths in php 8.0. +* Throw SimdJsonValueError instead of emitting notices if $depth is too small or too large in all simdjson PHP functions. + simdjson_is_valid(), simdjson_key_count() and simdjson_key_exists() now have non-null return types. +* Throw a SimdJsonException in simdjson_key_exists on error conditions such as invalid json, to be consistent with other simdjson PHP functions. +* Add an optional boolean `$throw_if_uncountable = false` to simdjson_key_count. + When this is overridden to be true, simdjson_key_count will throw a SimdJsonException if the JSON pointer refers to a value that exists but is neither an array nor an object instead of returning 0. * Expose simdjson C APIs marked with PHP_SIMDJSON_API in php_simdjson.h to other C projects. -* Ensure that that header file works when required by C projects and that publicly exposed functions are available. @@ -64,6 +70,7 @@ + @@ -94,7 +101,6 @@ - diff --git a/php_simdjson.cpp b/php_simdjson.cpp index cae6114..759f01e 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -71,16 +71,17 @@ ZEND_BEGIN_ARG_INFO_EX(simdjson_key_value_arginfo, 0, 0, 2) ZEND_ARG_TYPE_INFO(0, depth, IS_LONG, 0) ZEND_END_ARG_INFO() -SIMDJSON_ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(simdjson_key_exists_arginfo, 0, 2, _IS_BOOL, 1) +SIMDJSON_ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(simdjson_key_exists_arginfo, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, depth, IS_LONG, 0) ZEND_END_ARG_INFO() -SIMDJSON_ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(simdjson_key_count_arginfo, 0, 2, IS_LONG, 1) +SIMDJSON_ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(simdjson_key_count_arginfo, 0, 2, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, depth, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, throw_if_uncountable, _IS_BOOL, 0) ZEND_END_ARG_INFO() #define SIMDJSON_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(simdjson, v) diff --git a/tests/compat/json_decode_error.phpt b/tests/compat/json_decode_error.phpt deleted file mode 100644 index 07fe08e..0000000 --- a/tests/compat/json_decode_error.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -Test simdjson_decode() function : error conditions ---FILE-- -getMessage()); -} -?> ---EXPECTF-- -Warning: simdjson_decode(): Depth must be greater than zero in %s on line 3 -NULL diff --git a/tests/decode_max_depth.phpt b/tests/decode_max_depth.phpt index 6018272..2b11936 100644 --- a/tests/decode_max_depth.phpt +++ b/tests/decode_max_depth.phpt @@ -5,16 +5,9 @@ simdjson_decode reject max depth declare(strict_types=1); ini_set('error_reporting', (string)E_ALL); ini_set('display_errors', 'stderr'); +require_once __DIR__ . '/dump.inc'; -function dump(Closure $c) { - try { - var_dump($c()); - } catch (SimdJsonValueError $e) { - printf("Caught %s: %s\n", get_class($e), $e->getMessage()); - } -} - -foreach ([0, 1024, PHP_INT_MAX >> 1] as $depth) { +foreach ([0, PHP_INT_MIN, 1024, PHP_INT_MAX >> 1, PHP_INT_MAX] as $depth) { dump(function () use ($depth) { return simdjson_decode('[]', true, $depth); }); dump(function () use ($depth) { return simdjson_key_count('{"a":"b"}', 'a', $depth); }); dump(function () use ($depth) { return simdjson_key_value('{"a":{}}', 'a', true, $depth); }); @@ -28,6 +21,11 @@ Caught SimdJsonValueError: simdjson_key_count(): Argument #4 ($depth) must be gr Caught SimdJsonValueError: simdjson_key_value(): Argument #4 ($depth) must be greater than zero Caught SimdJsonValueError: simdjson_key_exists(): Argument #3 ($depth) must be greater than zero Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_decode(): Argument #2 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_key_count(): Argument #4 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_key_value(): Argument #4 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_key_exists(): Argument #3 ($depth) must be greater than zero +Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) must be greater than zero array(0) { } int(0) @@ -40,3 +38,8 @@ Caught SimdJsonValueError: simdjson_key_count(): Argument #4 ($depth) exceeds ma Caught SimdJsonValueError: simdjson_key_value(): Argument #4 ($depth) exceeds maximum allowed value of %d Caught SimdJsonValueError: simdjson_key_exists(): Argument #3 ($depth) exceeds maximum allowed value of %d Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_decode(): Argument #2 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_key_count(): Argument #4 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_key_value(): Argument #4 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_key_exists(): Argument #3 ($depth) exceeds maximum allowed value of %d +Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) exceeds maximum allowed value of %d diff --git a/tests/depth.phpt b/tests/depth.phpt index 26de5b1..70b8405 100644 --- a/tests/depth.phpt +++ b/tests/depth.phpt @@ -5,8 +5,9 @@ display_errors=stderr error_reporting=E_ALL --FILE-- --EXPECTF-- -Warning: simdjson_decode(): Depth must be greater than zero in %s on line 2 -NULL -Warning: simdjson_key_value(): Depth must be greater than zero in %s on line 3 -NULL int(0) array(0) { } diff --git a/tests/dump.inc b/tests/dump.inc new file mode 100644 index 0000000..1b8a29c --- /dev/null +++ b/tests/dump.inc @@ -0,0 +1,8 @@ +getMessage()); + } +} diff --git a/tests/is_valid_args.phpt b/tests/is_valid_args.phpt index a39c262..629fd21 100644 --- a/tests/is_valid_args.phpt +++ b/tests/is_valid_args.phpt @@ -13,5 +13,5 @@ Function [ function simdjson_is_valid ] { Parameter #0 [ string $json ] Parameter #1 [ int%S $depth%S ] } - - Return [ %Sbool%S ] + - Return [ bool%S ] } \ No newline at end of file diff --git a/tests/is_valid_edge_cases.phpt b/tests/is_valid_edge_cases.phpt index f6bd6c1..456413f 100644 --- a/tests/is_valid_edge_cases.phpt +++ b/tests/is_valid_edge_cases.phpt @@ -5,20 +5,20 @@ error_reporting=E_ALL display_errors=stderr --FILE-- --EXPECTF-- -Warning: simdjson_is_valid(): Depth must be greater than zero in %sis_valid_edge_cases.php on line 2 -NULL +Caught SimdJsonValueError: simdjson_is_valid(): Argument #2 ($depth) must be greater than zero bool(true) bool(true) bool(false) diff --git a/tests/key_count_args.phpt b/tests/key_count_args.phpt index 060b163..9d1a9ca 100644 --- a/tests/key_count_args.phpt +++ b/tests/key_count_args.phpt @@ -9,10 +9,11 @@ echo $reflection; --EXPECTF-- Function [ function simdjson_key_count ] { - - Parameters [3] { + - Parameters [4] { Parameter #0 [ string $json ] Parameter #1 [ string $key ] Parameter #2 [ int%S $depth%S ] + Parameter #3 [ bool%S $throw_if_uncountable%S ] } - - Return [ %Sint%S ] + - Return [ int%S ] } \ No newline at end of file diff --git a/tests/key_exists_args.phpt b/tests/key_exists_args.phpt index f97d1a3..2bcce8a 100644 --- a/tests/key_exists_args.phpt +++ b/tests/key_exists_args.phpt @@ -14,5 +14,5 @@ Function [ function simdjson_key_exists ] { Parameter #1 [ string $key ] Parameter #2 [ int%S $depth%S ] } - - Return [ %Sbool%S ] + - Return [ bool%S ] } \ No newline at end of file From a90756f7a52a9a5b3fde09205f1ba00c5c7d5c33 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sat, 15 Oct 2022 11:06:04 -0400 Subject: [PATCH 7/9] Rename $assoc to $associative. Update API documentation. Fill out the api documentation more in the README --- README.md | 69 +++++++++++++++++++++++++++++++++++++++--------- package.xml | 2 +- php_simdjson.cpp | 16 +++++------ 3 files changed, 65 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index bf65efb..6f1d59c 100644 --- a/README.md +++ b/README.md @@ -108,47 +108,75 @@ var_dump($res) //int(5) diff --git a/php_simdjson.cpp b/php_simdjson.cpp index 759f01e..ba7c504 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -60,14 +60,14 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(simdjson_decode_arginfo, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, assoc, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, associative, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, depth, IS_LONG, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(simdjson_key_value_arginfo, 0, 0, 2) ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, assoc, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, associative, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, depth, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -130,16 +130,16 @@ PHP_FUNCTION (simdjson_is_valid) { } PHP_FUNCTION (simdjson_decode) { - zend_bool assoc = 0; + zend_bool associative = 0; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; zend_string *json = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|bl", &json, &assoc, &depth) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|bl", &json, &associative, &depth) == FAILURE) { RETURN_THROWS(); } if (!simdjson_validate_depth(depth, "simdjson_decode", 2)) { RETURN_THROWS(); } - simdjson_php_error_code error = php_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, assoc, depth); + simdjson_php_error_code error = php_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, associative, depth); if (error) { php_simdjson_throw_jsonexception(error); RETURN_THROWS(); @@ -149,15 +149,15 @@ PHP_FUNCTION (simdjson_decode) { PHP_FUNCTION (simdjson_key_value) { zend_string *json = NULL; zend_string *key = NULL; - zend_bool assoc = 0; + zend_bool associative = 0; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|bl", &json, &key, &assoc, &depth) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|bl", &json, &key, &associative, &depth) == FAILURE) { RETURN_THROWS(); } if (!simdjson_validate_depth(depth, "simdjson_key_value", 4)) { RETURN_THROWS(); } - simdjson_php_error_code error = php_simdjson_key_value(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, assoc, depth); + simdjson_php_error_code error = php_simdjson_key_value(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, associative, depth); if (error) { php_simdjson_throw_jsonexception(error); RETURN_THROWS(); From d94c391a29ff3beafee48734763200099cd83b94 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sat, 15 Oct 2022 11:18:32 -0400 Subject: [PATCH 8/9] Update documentation --- php_simdjson.cpp | 4 ++++ php_simdjson.h | 15 ++++++++++++--- src/simdjson_bindings.cpp | 8 ++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/php_simdjson.cpp b/php_simdjson.cpp index ba7c504..864cf66 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -129,6 +129,10 @@ PHP_FUNCTION (simdjson_is_valid) { ZVAL_BOOL(return_value, is_json); } +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse_default(const char *json, size_t len, zval *return_value, bool associative, size_t depth) { + return php_simdjson_parse(simdjson_get_parser(), json, len, return_value, associative, depth); +} + PHP_FUNCTION (simdjson_decode) { zend_bool associative = 0; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; diff --git a/php_simdjson.h b/php_simdjson.h index 537a11a..686c199 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -116,7 +116,7 @@ typedef uint8_t simdjson_php_error_code; /* NOTE: Callers should check if len is greater than 4GB - simdjson will always return a non zero error code for those */ /* FIXME add php_simdjson_get_default_singleton_parser api */ -/* FIXME add php_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool assoc) */ +/* FIXME add php_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool associative) */ /** * Returns the error message corresponding to a given error code returned by a call to simdjson_php. @@ -133,6 +133,8 @@ PHP_SIMDJSON_API void php_simdjson_throw_jsonexception(simdjson_php_error_code c * * Callers may use this instead of the shared singleton parser when memory usage is a concern * (e.g. the PECLs are likely to be used load a string that's megabytes long in a long-lived php process) + * + * Callers should free this parser before or during the request shutdown phase. */ PHP_SIMDJSON_API struct simdjson_php_parser* php_simdjson_create_parser(void); /** @@ -143,13 +145,20 @@ PHP_SIMDJSON_API void php_simdjson_free_parser(struct simdjson_php_parser* parse * Returns true if the given json string is valid */ PHP_SIMDJSON_API bool php_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth); +/** + * Parses the given string into a return code, using the default singleton parser. + * + * This must be called after simdjson's request initialization phase and before simdjson's request shutdown phase. + * (e.g. PECLs should not use this during module or request initialization/shutdown) + */ +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse_default(const char *json, size_t len, zval *return_value, bool associative, size_t depth); /** * Parses the given string into a return code. * * If the returned error code is 0, then return_value contains the parsed value. * If the returned error code is non-0, then return_value will not be initialized. */ -PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(struct simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(struct simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool associative, size_t depth); /** * Parses the part of the given string at the json pointer 'key' into a PHP value at return_value * @@ -161,7 +170,7 @@ PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(struct simdjson_php_ * * @see https://www.rfc-editor.org/rfc/rfc6901.html */ -PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_value(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_value(struct simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool associative, size_t depth); /** * Checks if the json pointer 'key' exists in the given json string. * diff --git a/src/simdjson_bindings.cpp b/src/simdjson_bindings.cpp index b4e1281..e9810c0 100644 --- a/src/simdjson_bindings.cpp +++ b/src/simdjson_bindings.cpp @@ -349,27 +349,27 @@ PHP_SIMDJSON_API bool php_simdjson_is_valid(simdjson_php_parser* parser, const c /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool assoc, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse(simdjson_php_parser* parser, const char *json, size_t len, zval *return_value, bool associative, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::error_code error = build_parsed_json_cust(parser, doc, json, len, true, depth); if (error) { return error; } - if (assoc) { + if (associative) { return create_array(doc, return_value); } else { return create_object(doc, return_value); } } /* }}} */ -PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool assoc, +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_key_value(simdjson_php_parser* parser, const char *json, size_t len, const char *key, zval *return_value, bool associative, size_t depth) /* {{{ */ { simdjson::dom::element doc; simdjson::dom::element element; SIMDJSON_TRY(build_parsed_json_cust(parser, doc, json, len, true, depth)); SIMDJSON_TRY(get_key_with_optional_prefix(doc, key).get(element)); - if (assoc) { + if (associative) { return create_array(element, return_value); } else { return create_object(element, return_value); From 222ad9a52732ce751704f35835084afe0efd6f3e Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sat, 15 Oct 2022 11:28:05 -0400 Subject: [PATCH 9/9] Add remaining API methods for other PECLS --- php_simdjson.cpp | 12 ++++++++++-- php_simdjson.h | 33 ++++++++++++++++++++++++--------- src/simdjson_bindings.cpp | 8 ++------ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/php_simdjson.cpp b/php_simdjson.cpp index 864cf66..dc9482b 100644 --- a/php_simdjson.cpp +++ b/php_simdjson.cpp @@ -95,6 +95,10 @@ static simdjson_php_parser *simdjson_get_parser() { return parser; } +PHP_SIMDJSON_API struct simdjson_php_parser *php_simdjson_get_default_singleton_parser(void) { + return simdjson_get_parser(); +} + // The simdjson parser accepts strings with at most 32-bit lengths, for now. #define SIMDJSON_MAX_DEPTH ((zend_long)((SIZE_MAX / 8) < (UINT32_MAX / 2) ? (SIZE_MAX / 8) : (UINT32_MAX / 2))) @@ -125,14 +129,18 @@ PHP_FUNCTION (simdjson_is_valid) { if (!simdjson_validate_depth(depth, "simdjson_is_valid", 2)) { RETURN_THROWS(); } - bool is_json = php_simdjson_is_valid(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth); - ZVAL_BOOL(return_value, is_json); + simdjson_php_error_code error = php_simdjson_validate(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth); + ZVAL_BOOL(return_value, !error); } PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse_default(const char *json, size_t len, zval *return_value, bool associative, size_t depth) { return php_simdjson_parse(simdjson_get_parser(), json, len, return_value, associative, depth); } +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_validate_default(const char *json, size_t len, size_t depth) { + return php_simdjson_validate(simdjson_get_parser(), json, len, depth); +} + PHP_FUNCTION (simdjson_decode) { zend_bool associative = 0; zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH; diff --git a/php_simdjson.h b/php_simdjson.h index 686c199..0bb1d14 100644 --- a/php_simdjson.h +++ b/php_simdjson.h @@ -115,6 +115,28 @@ typedef uint8_t simdjson_php_error_code; /* NOTE: Callers should check if len is greater than 4GB - simdjson will always return a non zero error code for those */ +/** + * Parses the given string into a return code, using the default singleton parser. + * + * This must be called after simdjson's request initialization phase and before simdjson's request shutdown phase. + * (e.g. PECLs should not use this during module or request initialization/shutdown) + */ +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse_default(const char *json, size_t len, zval *return_value, bool associative, size_t depth); + +/** + * Checks if the given JSON is valid, using the default singleton parser. + * Returns 0 if it is valid. + */ +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_validate_default(const char *json, size_t len, size_t depth); + +/** + * Returns or creates the singleton parser used internally by simdjson (e.g. for the `php_simdjson_*_default()` methods). + * (Thread-local in ZTS builds of PHP) + * + * Callers must NOT free this. + */ +PHP_SIMDJSON_API struct simdjson_php_parser *php_simdjson_get_default_singleton_parser(void); + /* FIXME add php_simdjson_get_default_singleton_parser api */ /* FIXME add php_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool associative) */ @@ -142,16 +164,9 @@ PHP_SIMDJSON_API struct simdjson_php_parser* php_simdjson_create_parser(void); */ PHP_SIMDJSON_API void php_simdjson_free_parser(struct simdjson_php_parser* parser); /** - * Returns true if the given json string is valid + * Returns 0 if the given json string is valid */ -PHP_SIMDJSON_API bool php_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth); -/** - * Parses the given string into a return code, using the default singleton parser. - * - * This must be called after simdjson's request initialization phase and before simdjson's request shutdown phase. - * (e.g. PECLs should not use this during module or request initialization/shutdown) - */ -PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_parse_default(const char *json, size_t len, zval *return_value, bool associative, size_t depth); +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_validate(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth); /** * Parses the given string into a return code. * diff --git a/src/simdjson_bindings.cpp b/src/simdjson_bindings.cpp index e9810c0..36af5bc 100644 --- a/src/simdjson_bindings.cpp +++ b/src/simdjson_bindings.cpp @@ -337,14 +337,10 @@ PHP_SIMDJSON_API void php_simdjson_free_parser(simdjson_php_parser* parser) /* { delete parser; } -PHP_SIMDJSON_API bool php_simdjson_is_valid(simdjson_php_parser* parser, const char *json, size_t len, size_t depth) /* {{{ */ { +PHP_SIMDJSON_API simdjson_php_error_code php_simdjson_validate(simdjson_php_parser* parser, const char *json, size_t len, size_t depth) /* {{{ */ { simdjson::dom::element doc; /* The depth is passed in to ensure this behaves the same way for the same arguments */ - auto error = build_parsed_json_cust(parser, doc, json, len, true, depth); - if (error) { - return false; - } - return true; + return build_parsed_json_cust(parser, doc, json, len, true, depth); } /* }}} */