Skip to content

Commit cf4b6be

Browse files
committed
Refactor this, throw SimdJsonValueError
1 parent 2bb0470 commit cf4b6be

6 files changed

Lines changed: 184 additions & 73 deletions

File tree

php_simdjson.cpp

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern "C" {
2929
* Both the declaration and the definition of PHP_SIMDJSON_API variables, functions must be within an 'extern "C"' block for Windows
3030
*/
3131
PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce;
32+
PHP_SIMDJSON_API zend_class_entry *simdjson_value_error_ce;
3233

3334
} /* end extern "C" */
3435

@@ -81,7 +82,7 @@ ZEND_END_ARG_INFO()
8182
static simdjson_php_parser *simdjson_get_parser() {
8283
simdjson_php_parser *parser = SIMDJSON_G(parser);
8384
if (parser == NULL) {
84-
parser = cplus_simdjson_create_parser();
85+
parser = php_simdjson_create_parser();
8586
SIMDJSON_G(parser) = parser;
8687
ZEND_ASSERT(parser != NULL);
8788
}
@@ -111,7 +112,7 @@ PHP_FUNCTION (simdjson_is_valid) {
111112
if (!simdjson_validate_depth(depth)) {
112113
RETURN_NULL();
113114
}
114-
bool is_json = cplus_simdjson_is_valid(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth);
115+
bool is_json = php_simdjson_is_valid(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), depth);
115116
ZVAL_BOOL(return_value, is_json);
116117
}
117118

@@ -125,9 +126,9 @@ PHP_FUNCTION (simdjson_decode) {
125126
if (!simdjson_validate_depth(depth)) {
126127
RETURN_NULL();
127128
}
128-
simdjson_php_error_code error = cplus_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, assoc, depth);
129+
simdjson_php_error_code error = php_simdjson_parse(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), return_value, assoc, depth);
129130
if (error) {
130-
cplus_simdjson_throw_jsonexception(error);
131+
php_simdjson_throw_jsonexception(error);
131132
}
132133
}
133134

@@ -143,25 +144,29 @@ PHP_FUNCTION (simdjson_key_value) {
143144
if (!simdjson_validate_depth(depth)) {
144145
RETURN_NULL();
145146
}
146-
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);
147+
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);
147148
if (error) {
148-
cplus_simdjson_throw_jsonexception(error);
149+
php_simdjson_throw_jsonexception(error);
149150
}
150151
}
151152

152153
PHP_FUNCTION (simdjson_key_count) {
153154
zend_string *json = NULL;
154155
zend_string *key = NULL;
155156
zend_long depth = SIMDJSON_PARSE_DEFAULT_DEPTH;
156-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &json, &key, &depth) == FAILURE) {
157+
bool throw_if_uncountable = false;
158+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|lb", &json, &key, &depth, &throw_if_uncountable) == FAILURE) {
157159
return;
158160
}
159161
if (!simdjson_validate_depth(depth)) {
160162
RETURN_NULL();
161163
}
162-
simdjson_php_error_code error = cplus_simdjson_key_count(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), return_value, depth);
164+
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);
163165
if (error) {
164-
cplus_simdjson_throw_jsonexception(error);
166+
if (error == SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE && !throw_if_uncountable) {
167+
RETURN_LONG(0);
168+
}
169+
php_simdjson_throw_jsonexception(error);
165170
}
166171
}
167172

@@ -175,13 +180,16 @@ PHP_FUNCTION (simdjson_key_exists) {
175180
if (!simdjson_validate_depth(depth)) {
176181
return;
177182
}
178-
u_short stats = cplus_simdjson_key_exists(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), depth);
179-
if (SIMDJSON_PARSE_FAIL == stats) {
180-
RETURN_NULL();
181-
} else if (SIMDJSON_PARSE_KEY_EXISTS == stats) {
182-
RETURN_TRUE;
183-
} else if (SIMDJSON_PARSE_KEY_NOEXISTS == stats) {
184-
RETURN_FALSE;
183+
simdjson_php_error_code error = php_simdjson_key_exists(simdjson_get_parser(), ZSTR_VAL(json), ZSTR_LEN(json), ZSTR_VAL(key), depth);
184+
switch (error) {
185+
case simdjson::SUCCESS:
186+
RETURN_TRUE;
187+
case simdjson::NO_SUCH_FIELD:
188+
case simdjson::INDEX_OUT_OF_BOUNDS:
189+
case simdjson::INCORRECT_TYPE:
190+
RETURN_FALSE;
191+
default:
192+
php_simdjson_throw_jsonexception(error);
185193
}
186194
}
187195

@@ -212,6 +220,11 @@ ZEND_TSRMLS_CACHE_UPDATE();
212220
#define SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(errcode, val) REGISTER_LONG_CONSTANT("SIMDJSON_ERR_" #errcode, (val), CONST_PERSISTENT)
213221
PHP_MINIT_FUNCTION (simdjson) {
214222
simdjson_exception_ce = register_class_SimdJsonException(spl_ce_RuntimeException);
223+
#if PHP_VERSION_ID >= 80000
224+
simdjson_value_error_ce = register_class_SimdJsonValueError(zend_ce_value_error);
225+
#else
226+
simdjson_value_error_ce = register_class_SimdJsonValueError(zend_ce_error);
227+
#endif
215228
SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(CAPACITY); ///< This parser can't support a document that big
216229
// SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(MEMALLOC); ///< Error allocating memory, most likely out of memory
217230
SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(TAPE_ERROR); ///< Something went wrong, this is a generic error
@@ -242,7 +255,8 @@ PHP_MINIT_FUNCTION (simdjson) {
242255
SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(SCALAR_DOCUMENT_AS_VALUE); ///< A scalar document is treated as a value.
243256
SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(OUT_OF_BOUNDS); ///< Attempted to access location outside of document.
244257
SIMDJSON_REGISTER_ERROR_CODE_CONSTANT(TRAILING_CONTENT); ///< Unexpected trailing content in the JSON input
245-
SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(INVALID_PROPERTY, INVALID_PHP_PROPERTY); ///< Invalid property
258+
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
259+
SIMDJSON_REGISTER_CUSTOM_ERROR_CODE_CONSTANT(INVALID_PROPERTY, SIMDJSON_PHP_ERR_INVALID_PHP_PROPERTY); ///< Invalid property name
246260

247261
return SUCCESS;
248262
}
@@ -268,7 +282,7 @@ PHP_RINIT_FUNCTION (simdjson) {
268282
PHP_RSHUTDOWN_FUNCTION (simdjson) {
269283
simdjson_php_parser *parser = SIMDJSON_G(parser);
270284
if (parser != NULL) {
271-
cplus_simdjson_free_parser(parser);
285+
php_simdjson_free_parser(parser);
272286
SIMDJSON_G(parser) = NULL;
273287
}
274288
return SUCCESS;

php_simdjson.h

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,45 @@
1414
#ifndef PHP_SIMDJSON_H
1515
#define PHP_SIMDJSON_H
1616

17+
/*
18+
* Error constant implementation notes:
19+
*
20+
* - 0 always means success, and non-0 is a failure condition.
21+
* - Prefix these with SIMDJSON_PHP_ERR_ to distinguish them from other values.
22+
* - The error codes (value or labels) belonging to the C simdjson project may change in the future.
23+
*
24+
* Maybe these should be exposed as extern const once there's a project that needs the other values.
25+
* For now, they're also exposed as `REGISTER_LONG_CONSTANT("SIMDJSON_ERR_" #errcode, (val), CONST_PERSISTENT)`
26+
*/
27+
#define SIMDJSON_PHP_ERR_SUCCESS 0
28+
#define SIMDJSON_PHP_ERR_INVALID_PHP_PROPERTY 255
29+
#define SIMDJSON_PHP_ERR_KEY_COUNT_NOT_COUNTABLE 254
30+
1731
/*
1832
* Put all of the publicly visible functionality and macros into the same header file
1933
* (On windows, the include paths used by the c compiler may be different)
2034
*/
2135
#include "Zend/zend.h"
2236
#include "Zend/zend_portability.h"
2337

24-
// 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),
25-
// so that pecls written in C can use this functionality without separate C++ files to load bindings.h.
26-
// BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers.
27-
// When I add ZEND_API to declarations and possibly definitions, I get linker errors.
38+
/*
39+
* All code in this header file should be changed to go within BEGIN_EXTERN_C/END_EXTERN_C macros
40+
* (both header definitions, and C++ declarations, including function implementations),
41+
* so that pecls written in C can use this functionality without separate C++ files to load bindings.h.
42+
*
43+
* This header file deliberately does not depend on other header files in this project,
44+
* to make including this header file easier for other PECLs (avoid include path issues)
45+
*
46+
* BEGIN_EXTERN_C is needed for symbols to be mangled using C rules instead of C++ rules in all includers.
47+
* (This macro can be used from both C and C++ source files)
48+
*/
2849
BEGIN_EXTERN_C()
2950

3051
extern zend_module_entry simdjson_module_entry;
3152
#define phpext_simdjson_ptr &simdjson_module_entry
3253

3354
#define PHP_SIMDJSON_VERSION "2.2.0dev"
3455
#define SIMDJSON_SUPPORT_URL "https://github.com/crazyxman/simdjson_php"
35-
#define SIMDJSON_PARSE_FAIL 0
36-
#define SIMDJSON_PARSE_SUCCESS 1
37-
#define SIMDJSON_PARSE_KEY_EXISTS 2
38-
#define SIMDJSON_PARSE_KEY_NOEXISTS 3
3956

4057
#define SIMDJSON_PARSE_DEFAULT_DEPTH 512
4158

@@ -51,6 +68,8 @@ ZEND_BEGIN_MODULE_GLOBALS(simdjson)
5168
* php::simdjson::parser pointer, constructed on first use with request-scope lifetime.
5269
* Note that in ZTS builds, the thread for each request will deliberately have different instances for each concurrently running request.
5370
* (The simdjson library is not thread safe)
71+
*
72+
* This is similar to php-src's ext/session session data storage.
5473
*/
5574
struct simdjson_php_parser *parser;
5675
ZEND_END_MODULE_GLOBALS(simdjson)
@@ -67,9 +86,7 @@ ZEND_TSRMLS_CACHE_EXTERN()
6786
#endif
6887
#endif
6988

70-
/*
71-
* This module defines utilities and helper functions used elsewhere in simdjson.
72-
*/
89+
/* Only the functions and variables defined with PHP_SIMDJSON_API can be loaded by other PECLs */
7390
#ifdef PHP_WIN32
7491
# define PHP_SIMDJSON_API __declspec(dllexport)
7592
#elif defined(__GNUC__) && __GNUC__ >= 4
@@ -78,27 +95,90 @@ ZEND_TSRMLS_CACHE_EXTERN()
7895
# define PHP_SIMDJSON_API /* nothing special */
7996
#endif
8097

98+
/** Defines 'class SimdJsonException' */
8199
extern PHP_SIMDJSON_API zend_class_entry *simdjson_exception_ce;
100+
extern PHP_SIMDJSON_API zend_class_entry *simdjson_value_error_ce;
82101

83-
// NOTE: Namespaces and references(&) are C++ only functionality.
84-
// To expose this functionality to other C PECLs,
85-
// switch to a forward class declaration of a class that only wraps simdjson::dom::parser
102+
/**
103+
* NOTE: Namespaces and references(&) and classes (instead of structs) are C++ only functionality.
104+
*
105+
* To expose this functionality to other C PECLs,
106+
* switch to a forward class declaration of a class that only wraps simdjson::dom::parser
107+
*/
86108
typedef uint8_t simdjson_php_error_code;
87109

88110
/* NOTE: Callers should check if len is greater than 4GB - simdjson will always return a non zero error code for those */
89111

90-
/* FIXME add cplus_simdjson_get_default_singleton_parser api */
91-
/* FIXME add cplus_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool assoc) */
92-
93-
PHP_SIMDJSON_API const char* cplus_simdjson_error_msg(simdjson_php_error_code);
94-
PHP_SIMDJSON_API void cplus_simdjson_throw_jsonexception(simdjson_php_error_code);
95-
PHP_SIMDJSON_API struct simdjson_php_parser* cplus_simdjson_create_parser(void);
96-
PHP_SIMDJSON_API void cplus_simdjson_free_parser(struct simdjson_php_parser* parser);
97-
PHP_SIMDJSON_API bool cplus_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth);
98-
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);
99-
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);
100-
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);
101-
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);
112+
/* FIXME add php_simdjson_get_default_singleton_parser api */
113+
/* FIXME add php_simdjson_decode_with_default_singleton_parser(return_value, json, len, bool assoc) */
114+
115+
/**
116+
* Returns the error message corresponding to a given error code returned by a call to simdjson_php.
117+
*/
118+
PHP_SIMDJSON_API const char* php_simdjson_error_msg(simdjson_php_error_code code);
119+
/**
120+
* Throw a SimdJsonException with the provided error code and the corresponding error message.
121+
*/
122+
PHP_SIMDJSON_API void php_simdjson_throw_jsonexception(simdjson_php_error_code code);
123+
/**
124+
* Create a brand new parser instance.
125+
*
126+
* The caller must free it with php_simdjson_free_parser once it is no longer used.
127+
*
128+
* Callers may use this instead of the shared singleton parser when memory usage is a concern
129+
* (e.g. the PECLs are likely to be used load a string that's megabytes long in a long-lived php process)
130+
*/
131+
PHP_SIMDJSON_API struct simdjson_php_parser* php_simdjson_create_parser(void);
132+
/**
133+
* Release a parser **constructed by php_simdjson_create_parser** and all associated buffers.
134+
*/
135+
PHP_SIMDJSON_API void php_simdjson_free_parser(struct simdjson_php_parser* parser);
136+
/**
137+
* Returns true if the given json string is valid
138+
*/
139+
PHP_SIMDJSON_API bool php_simdjson_is_valid(struct simdjson_php_parser* parser, const char *json, size_t len, size_t depth);
140+
/**
141+
* Parses the given string into a return code.
142+
*
143+
* If the returned error code is 0, then return_value contains the parsed value.
144+
* If the returned error code is non-0, then return_value will not be initialized.
145+
*/
146+
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);
147+
/**
148+
* Parses the part of the given string at the json pointer 'key' into a PHP value at return_value
149+
*
150+
* If the returned error code is 0, then return_value contains the parsed value (or null).
151+
* If the returned error code is non-0, then return_value will not be initialized.
152+
*
153+
* - SIMDJSON_ERR_NO_SUCH_FIELD is returned if the json pointer 'key' is not found
154+
* - Other errors are returned for invalid json, etc.
155+
*
156+
* @see https://www.rfc-editor.org/rfc/rfc6901.html
157+
*/
158+
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);
159+
/**
160+
* Checks if the json pointer 'key' exists in the given json string.
161+
*
162+
* - 0 if the key exists
163+
* - NO_SUCH_FIELD if a field does not exist in an object
164+
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
165+
* - INCORRECT_TYPE if a non-integer is used to access an array
166+
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
167+
*
168+
* @see https://www.rfc-editor.org/rfc/rfc6901.html
169+
*/
170+
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);
171+
/**
172+
* Count the keys of the given array/object at json pointer 'key' exists in the given json string.
173+
*
174+
* If the returned error code is 0, then the zval in return_value is overwritten with the key count using ZVAL_LONG.
175+
* - For arrays, this is the array size
176+
* - For objects, this is the object size
177+
* - For other values, this is 0 (when fail_if_uncountable is false)
178+
*
179+
* @see https://www.rfc-editor.org/rfc/rfc6901.html
180+
*/
181+
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);
102182

103183
END_EXTERN_C()
104184

simdjson.stub.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@
99

1010
class SimdJsonException extends RuntimeException {
1111
}
12+
13+
/**
14+
* Thrown for invalid depths, with similar behavior to php 8.0.
15+
*
16+
* NOTE: https://www.php.net/valueerror was added in php 8.0.
17+
* In older php versions, this extends Error instead.
18+
*/
19+
class SimdJsonValueError extends ValueError {
20+
}

simdjson_arginfo.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: e7f05ec4984c79ce2696174896f000a7082de6ed */
2+
* Stub hash: 7d4b37b2b4c79e2802efe01a53bb11cdfbc62d7c */
33

44

55

@@ -8,6 +8,11 @@ static const zend_function_entry class_SimdJsonException_methods[] = {
88
ZEND_FE_END
99
};
1010

11+
12+
static const zend_function_entry class_SimdJsonValueError_methods[] = {
13+
ZEND_FE_END
14+
};
15+
1116
static zend_class_entry *register_class_SimdJsonException(zend_class_entry *class_entry_RuntimeException)
1217
{
1318
zend_class_entry ce, *class_entry;
@@ -17,3 +22,13 @@ static zend_class_entry *register_class_SimdJsonException(zend_class_entry *clas
1722

1823
return class_entry;
1924
}
25+
26+
static zend_class_entry *register_class_SimdJsonValueError(zend_class_entry *class_entry_ValueError)
27+
{
28+
zend_class_entry ce, *class_entry;
29+
30+
INIT_CLASS_ENTRY(ce, "SimdJsonValueError", class_SimdJsonValueError_methods);
31+
class_entry = zend_register_internal_class_ex(&ce, class_entry_ValueError);
32+
33+
return class_entry;
34+
}

0 commit comments

Comments
 (0)