1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 #include "protobuf.h"
32
33 #include <php.h>
34 #include <Zend/zend_interfaces.h>
35
36 #include "arena.h"
37 #include "array.h"
38 #include "bundled_php.h"
39 #include "convert.h"
40 #include "def.h"
41 #include "map.h"
42 #include "message.h"
43 #include "names.h"
44
45 // -----------------------------------------------------------------------------
46 // Module "globals"
47 // -----------------------------------------------------------------------------
48
49 // Despite the name, module "globals" are really thread-locals:
50 // * PROTOBUF_G(var) accesses the thread-local variable for 'var'. Either:
51 // * PROTOBUF_G(var) -> protobuf_globals.var (Non-ZTS / non-thread-safe)
52 // * PROTOBUF_G(var) -> <Zend magic> (ZTS / thread-safe builds)
53
54 #define PROTOBUF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(protobuf, v)
55
56 ZEND_BEGIN_MODULE_GLOBALS(protobuf)
57 // Set by the user to make the descriptor pool persist between requests.
58 zend_bool keep_descriptor_pool_after_request;
59
60 // Currently we make the generated pool a "global", which means that if a user
61 // does explicitly create threads within their request, the other threads will
62 // get different results from DescriptorPool::getGeneratedPool(). We require
63 // that all descriptors are loaded from the main thread.
64 zval generated_pool;
65
66 // A upb_symtab that we are saving for the next request so that we don't have
67 // to rebuild it from scratch. When keep_descriptor_pool_after_request==true,
68 // we steal the upb_symtab from the global DescriptorPool object just before
69 // destroying it.
70 upb_symtab *saved_symtab;
71
72 // Object cache (see interface in protobuf.h).
73 HashTable object_cache;
74
75 // Name cache (see interface in protobuf.h).
76 HashTable name_msg_cache;
77 HashTable name_enum_cache;
78 ZEND_END_MODULE_GLOBALS(protobuf)
79
ZEND_DECLARE_MODULE_GLOBALS(protobuf)80 ZEND_DECLARE_MODULE_GLOBALS(protobuf)
81
82 const zval *get_generated_pool() {
83 return &PROTOBUF_G(generated_pool);
84 }
85
86 // This is a PHP extension (not a Zend extension). What follows is a summary of
87 // a PHP extension's lifetime and when various handlers are called.
88 //
89 // * PHP_GINIT_FUNCTION(protobuf) / PHP_GSHUTDOWN_FUNCTION(protobuf)
90 // are the constructor/destructor for the globals. The sequence over the
91 // course of a process lifetime is:
92 //
93 // # Process startup
94 // GINIT(<Main Thread Globals>)
95 // MINIT
96 //
97 // foreach request:
98 // RINIT
99 // # Request is processed here.
100 // RSHUTDOWN
101 //
102 // foreach thread:
103 // GINIT(<This Thread Globals>)
104 // # Code for the thread runs here.
105 // GSHUTDOWN(<This Thread Globals>)
106 //
107 // # Process Shutdown
108 // #
109 // # These should be running per the docs, but I have not been able to
110 // # actually get the process-wide shutdown functions to run.
111 // #
112 // # MSHUTDOWN
113 // # GSHUTDOWN(<Main Thread Globals>)
114 //
115 // * Threads can be created either explicitly by the user, inside a request,
116 // or implicitly by the runtime, to process multiple requests concurrently.
117 // If the latter is being used, then the "foreach thread" block above
118 // actually looks like this:
119 //
120 // foreach thread:
121 // GINIT(<This Thread Globals>)
122 // # A non-main thread will only receive requests when using a threaded
123 // # MPM with Apache
124 // foreach request:
125 // RINIT
126 // # Request is processed here.
127 // RSHUTDOWN
128 // GSHUTDOWN(<This Thread Globals>)
129 //
130 // That said, it appears that few people use threads with PHP:
131 // * The pthread package documented at
132 // https://www.php.net/manual/en/class.thread.php nas not been released
133 // since 2016, and the current release fails to compile against any PHP
134 // newer than 7.0.33.
135 // * The GitHub master branch supports 7.2+, but this has not been released
136 // to PECL.
137 // * Its owner has disavowed it as "broken by design" and "in an untenable
138 // position for the future": https://github.com/krakjoe/pthreads/issues/929
139 // * The only way to use PHP with requests in different threads is to use the
140 // Apache 2 mod_php with the "worker" MPM. But this is explicitly
141 // discouraged by the documentation: https://serverfault.com/a/231660
142
PHP_GSHUTDOWN_FUNCTION(protobuf)143 static PHP_GSHUTDOWN_FUNCTION(protobuf) {
144 if (protobuf_globals->saved_symtab) {
145 upb_symtab_free(protobuf_globals->saved_symtab);
146 }
147 }
148
PHP_GINIT_FUNCTION(protobuf)149 static PHP_GINIT_FUNCTION(protobuf) {
150 ZVAL_NULL(&protobuf_globals->generated_pool);
151 protobuf_globals->saved_symtab = NULL;
152 }
153
154 /**
155 * PHP_RINIT_FUNCTION(protobuf)
156 *
157 * This function is run at the beginning of processing each request.
158 */
PHP_RINIT_FUNCTION(protobuf)159 static PHP_RINIT_FUNCTION(protobuf) {
160 // Create the global generated pool.
161 // Reuse the symtab (if any) left to us by the last request.
162 upb_symtab *symtab = PROTOBUF_G(saved_symtab);
163 DescriptorPool_CreateWithSymbolTable(&PROTOBUF_G(generated_pool), symtab);
164
165 // Set up autoloader for bundled sources.
166 zend_eval_string("spl_autoload_register('protobuf_internal_loadbundled');",
167 NULL, "autoload_register.php");
168
169 zend_hash_init(&PROTOBUF_G(object_cache), 64, NULL, NULL, 0);
170 zend_hash_init(&PROTOBUF_G(name_msg_cache), 64, NULL, NULL, 0);
171 zend_hash_init(&PROTOBUF_G(name_enum_cache), 64, NULL, NULL, 0);
172
173 return SUCCESS;
174 }
175
176 /**
177 * PHP_RSHUTDOWN_FUNCTION(protobuf)
178 *
179 * This function is run at the end of processing each request.
180 */
PHP_RSHUTDOWN_FUNCTION(protobuf)181 static PHP_RSHUTDOWN_FUNCTION(protobuf) {
182 // Preserve the symtab if requested.
183 if (PROTOBUF_G(keep_descriptor_pool_after_request)) {
184 zval *zv = &PROTOBUF_G(generated_pool);
185 PROTOBUF_G(saved_symtab) = DescriptorPool_Steal(zv);
186 }
187
188 zval_dtor(&PROTOBUF_G(generated_pool));
189 zend_hash_destroy(&PROTOBUF_G(object_cache));
190 zend_hash_destroy(&PROTOBUF_G(name_msg_cache));
191 zend_hash_destroy(&PROTOBUF_G(name_enum_cache));
192
193 return SUCCESS;
194 }
195
196 // -----------------------------------------------------------------------------
197 // Bundled PHP sources
198 // -----------------------------------------------------------------------------
199
200 // We bundle PHP sources for well-known types into the C extension. There is no
201 // need to implement these in C.
202
PHP_FUNCTION(protobuf_internal_loadbundled)203 static PHP_FUNCTION(protobuf_internal_loadbundled) {
204 char *name = NULL;
205 zend_long size;
206 BundledPhp_File *file;
207
208 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &size) != SUCCESS) {
209 return;
210 }
211
212 for (file = bundled_files; file->filename; file++) {
213 if (strcmp(file->filename, name) == 0) {
214 zend_eval_string((char*)file->contents, NULL, (char*)file->filename);
215 return;
216 }
217 }
218 }
219
220 ZEND_BEGIN_ARG_INFO_EX(arginfo_load_bundled_source, 0, 0, 1)
221 ZEND_ARG_INFO(0, class_name)
ZEND_END_ARG_INFO()222 ZEND_END_ARG_INFO()
223
224 // -----------------------------------------------------------------------------
225 // Object Cache.
226 // -----------------------------------------------------------------------------
227
228 void ObjCache_Add(const void *upb_obj, zend_object *php_obj) {
229 zend_ulong k = (zend_ulong)upb_obj;
230 zend_hash_index_add_ptr(&PROTOBUF_G(object_cache), k, php_obj);
231 }
232
ObjCache_Delete(const void * upb_obj)233 void ObjCache_Delete(const void *upb_obj) {
234 if (upb_obj) {
235 zend_ulong k = (zend_ulong)upb_obj;
236 int ret = zend_hash_index_del(&PROTOBUF_G(object_cache), k);
237 PBPHP_ASSERT(ret == SUCCESS);
238 }
239 }
240
ObjCache_Get(const void * upb_obj,zval * val)241 bool ObjCache_Get(const void *upb_obj, zval *val) {
242 zend_ulong k = (zend_ulong)upb_obj;
243 zend_object *obj = zend_hash_index_find_ptr(&PROTOBUF_G(object_cache), k);
244
245 if (obj) {
246 GC_ADDREF(obj);
247 ZVAL_OBJ(val, obj);
248 return true;
249 } else {
250 ZVAL_NULL(val);
251 return false;
252 }
253 }
254
255 // -----------------------------------------------------------------------------
256 // Name Cache.
257 // -----------------------------------------------------------------------------
258
NameMap_AddMessage(const upb_msgdef * m)259 void NameMap_AddMessage(const upb_msgdef *m) {
260 char *k = GetPhpClassname(upb_msgdef_file(m), upb_msgdef_fullname(m));
261 zend_hash_str_add_ptr(&PROTOBUF_G(name_msg_cache), k, strlen(k), (void*)m);
262 free(k);
263 }
264
NameMap_AddEnum(const upb_enumdef * e)265 void NameMap_AddEnum(const upb_enumdef *e) {
266 char *k = GetPhpClassname(upb_enumdef_file(e), upb_enumdef_fullname(e));
267 zend_hash_str_add_ptr(&PROTOBUF_G(name_enum_cache), k, strlen(k), (void*)e);
268 free(k);
269 }
270
NameMap_GetMessage(zend_class_entry * ce)271 const upb_msgdef *NameMap_GetMessage(zend_class_entry *ce) {
272 const upb_msgdef *ret =
273 zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
274
275 if (!ret && ce->create_object) {
276 #if PHP_VERSION_ID < 80000
277 zval tmp;
278 zval zv;
279 ZVAL_OBJ(&tmp, ce->create_object(ce));
280 zend_call_method_with_0_params(&tmp, ce, NULL, "__construct", &zv);
281 zval_ptr_dtor(&tmp);
282 #else
283 zval zv;
284 zend_object *tmp = ce->create_object(ce);
285 zend_call_method_with_0_params(tmp, ce, NULL, "__construct", &zv);
286 OBJ_RELEASE(tmp);
287 #endif
288 zval_ptr_dtor(&zv);
289 ret = zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
290 }
291
292 return ret;
293 }
294
NameMap_GetEnum(zend_class_entry * ce)295 const upb_enumdef *NameMap_GetEnum(zend_class_entry *ce) {
296 const upb_enumdef *ret =
297 zend_hash_find_ptr(&PROTOBUF_G(name_enum_cache), ce->name);
298 return ret;
299 }
300
301 // -----------------------------------------------------------------------------
302 // Module init.
303 // -----------------------------------------------------------------------------
304
305 zend_function_entry protobuf_functions[] = {
306 PHP_FE(protobuf_internal_loadbundled, arginfo_load_bundled_source)
307 ZEND_FE_END
308 };
309
310 static const zend_module_dep protobuf_deps[] = {
311 ZEND_MOD_OPTIONAL("date")
312 ZEND_MOD_END
313 };
314
315 PHP_INI_BEGIN()
316 STD_PHP_INI_ENTRY("protobuf.keep_descriptor_pool_after_request", "0",
317 PHP_INI_SYSTEM, OnUpdateBool,
318 keep_descriptor_pool_after_request, zend_protobuf_globals,
319 protobuf_globals)
PHP_INI_END()320 PHP_INI_END()
321
322 static PHP_MINIT_FUNCTION(protobuf) {
323 REGISTER_INI_ENTRIES();
324 Arena_ModuleInit();
325 Array_ModuleInit();
326 Convert_ModuleInit();
327 Def_ModuleInit();
328 Map_ModuleInit();
329 Message_ModuleInit();
330 return SUCCESS;
331 }
332
PHP_MSHUTDOWN_FUNCTION(protobuf)333 static PHP_MSHUTDOWN_FUNCTION(protobuf) {
334 return SUCCESS;
335 }
336
337 zend_module_entry protobuf_module_entry = {
338 STANDARD_MODULE_HEADER_EX,
339 NULL,
340 protobuf_deps,
341 "protobuf", // extension name
342 protobuf_functions, // function list
343 PHP_MINIT(protobuf), // process startup
344 PHP_MSHUTDOWN(protobuf), // process shutdown
345 PHP_RINIT(protobuf), // request shutdown
346 PHP_RSHUTDOWN(protobuf), // request shutdown
347 NULL, // extension info
348 "3.13.0", // extension version
349 PHP_MODULE_GLOBALS(protobuf), // globals descriptor
350 PHP_GINIT(protobuf), // globals ctor
351 PHP_GSHUTDOWN(protobuf), // globals dtor
352 NULL, // post deactivate
353 STANDARD_MODULE_PROPERTIES_EX
354 };
355
356 ZEND_GET_MODULE(protobuf)
357