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 "convert.h"
39 #include "def.h"
40 #include "map.h"
41 #include "message.h"
42 #include "names.h"
43
44 // -----------------------------------------------------------------------------
45 // Module "globals"
46 // -----------------------------------------------------------------------------
47
48 // Despite the name, module "globals" are really thread-locals:
49 // * PROTOBUF_G(var) accesses the thread-local variable for 'var'. Either:
50 // * PROTOBUF_G(var) -> protobuf_globals.var (Non-ZTS / non-thread-safe)
51 // * PROTOBUF_G(var) -> <Zend magic> (ZTS / thread-safe builds)
52
53 #define PROTOBUF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(protobuf, v)
54
55 ZEND_BEGIN_MODULE_GLOBALS(protobuf)
56 // Set by the user to make the descriptor pool persist between requests.
57 zend_bool keep_descriptor_pool_after_request;
58
59 // Currently we make the generated pool a "global", which means that if a user
60 // does explicitly create threads within their request, the other threads will
61 // get different results from DescriptorPool::getGeneratedPool(). We require
62 // that all descriptors are loaded from the main thread.
63 zval generated_pool;
64
65 // A upb_symtab that we are saving for the next request so that we don't have
66 // to rebuild it from scratch. When keep_descriptor_pool_after_request==true,
67 // we steal the upb_symtab from the global DescriptorPool object just before
68 // destroying it.
69 upb_symtab *saved_symtab;
70
71 // Object cache (see interface in protobuf.h).
72 HashTable object_cache;
73
74 // Name cache (see interface in protobuf.h).
75 HashTable name_msg_cache;
76 HashTable name_enum_cache;
77 ZEND_END_MODULE_GLOBALS(protobuf)
78
ZEND_DECLARE_MODULE_GLOBALS(protobuf)79 ZEND_DECLARE_MODULE_GLOBALS(protobuf)
80
81 const zval *get_generated_pool() {
82 return &PROTOBUF_G(generated_pool);
83 }
84
85 // This is a PHP extension (not a Zend extension). What follows is a summary of
86 // a PHP extension's lifetime and when various handlers are called.
87 //
88 // * PHP_GINIT_FUNCTION(protobuf) / PHP_GSHUTDOWN_FUNCTION(protobuf)
89 // are the constructor/destructor for the globals. The sequence over the
90 // course of a process lifetime is:
91 //
92 // # Process startup
93 // GINIT(<Main Thread Globals>)
94 // MINIT
95 //
96 // foreach request:
97 // RINIT
98 // # Request is processed here.
99 // RSHUTDOWN
100 //
101 // foreach thread:
102 // GINIT(<This Thread Globals>)
103 // # Code for the thread runs here.
104 // GSHUTDOWN(<This Thread Globals>)
105 //
106 // # Process Shutdown
107 // #
108 // # These should be running per the docs, but I have not been able to
109 // # actually get the process-wide shutdown functions to run.
110 // #
111 // # MSHUTDOWN
112 // # GSHUTDOWN(<Main Thread Globals>)
113 //
114 // * Threads can be created either explicitly by the user, inside a request,
115 // or implicitly by the runtime, to process multiple requests concurrently.
116 // If the latter is being used, then the "foreach thread" block above
117 // actually looks like this:
118 //
119 // foreach thread:
120 // GINIT(<This Thread Globals>)
121 // # A non-main thread will only receive requests when using a threaded
122 // # MPM with Apache
123 // foreach request:
124 // RINIT
125 // # Request is processed here.
126 // RSHUTDOWN
127 // GSHUTDOWN(<This Thread Globals>)
128 //
129 // That said, it appears that few people use threads with PHP:
130 // * The pthread package documented at
131 // https://www.php.net/manual/en/class.thread.php nas not been released
132 // since 2016, and the current release fails to compile against any PHP
133 // newer than 7.0.33.
134 // * The GitHub master branch supports 7.2+, but this has not been released
135 // to PECL.
136 // * Its owner has disavowed it as "broken by design" and "in an untenable
137 // position for the future": https://github.com/krakjoe/pthreads/issues/929
138 // * The only way to use PHP with requests in different threads is to use the
139 // Apache 2 mod_php with the "worker" MPM. But this is explicitly
140 // discouraged by the documentation: https://serverfault.com/a/231660
141
PHP_GSHUTDOWN_FUNCTION(protobuf)142 static PHP_GSHUTDOWN_FUNCTION(protobuf) {
143 if (protobuf_globals->saved_symtab) {
144 upb_symtab_free(protobuf_globals->saved_symtab);
145 }
146 }
147
PHP_GINIT_FUNCTION(protobuf)148 static PHP_GINIT_FUNCTION(protobuf) {
149 ZVAL_NULL(&protobuf_globals->generated_pool);
150 protobuf_globals->saved_symtab = NULL;
151 }
152
153 /**
154 * PHP_RINIT_FUNCTION(protobuf)
155 *
156 * This function is run at the beginning of processing each request.
157 */
PHP_RINIT_FUNCTION(protobuf)158 static PHP_RINIT_FUNCTION(protobuf) {
159 // Create the global generated pool.
160 // Reuse the symtab (if any) left to us by the last request.
161 upb_symtab *symtab = PROTOBUF_G(saved_symtab);
162 DescriptorPool_CreateWithSymbolTable(&PROTOBUF_G(generated_pool), symtab);
163
164 zend_hash_init(&PROTOBUF_G(object_cache), 64, NULL, NULL, 0);
165 zend_hash_init(&PROTOBUF_G(name_msg_cache), 64, NULL, NULL, 0);
166 zend_hash_init(&PROTOBUF_G(name_enum_cache), 64, NULL, NULL, 0);
167
168 return SUCCESS;
169 }
170
171 /**
172 * PHP_RSHUTDOWN_FUNCTION(protobuf)
173 *
174 * This function is run at the end of processing each request.
175 */
PHP_RSHUTDOWN_FUNCTION(protobuf)176 static PHP_RSHUTDOWN_FUNCTION(protobuf) {
177 // Preserve the symtab if requested.
178 if (PROTOBUF_G(keep_descriptor_pool_after_request)) {
179 zval *zv = &PROTOBUF_G(generated_pool);
180 PROTOBUF_G(saved_symtab) = DescriptorPool_Steal(zv);
181 }
182
183 zval_dtor(&PROTOBUF_G(generated_pool));
184 zend_hash_destroy(&PROTOBUF_G(object_cache));
185 zend_hash_destroy(&PROTOBUF_G(name_msg_cache));
186 zend_hash_destroy(&PROTOBUF_G(name_enum_cache));
187
188 return SUCCESS;
189 }
190
191 // -----------------------------------------------------------------------------
192 // Object Cache.
193 // -----------------------------------------------------------------------------
194
ObjCache_Add(const void * upb_obj,zend_object * php_obj)195 void ObjCache_Add(const void *upb_obj, zend_object *php_obj) {
196 zend_ulong k = (zend_ulong)upb_obj;
197 zend_hash_index_add_ptr(&PROTOBUF_G(object_cache), k, php_obj);
198 }
199
ObjCache_Delete(const void * upb_obj)200 void ObjCache_Delete(const void *upb_obj) {
201 if (upb_obj) {
202 zend_ulong k = (zend_ulong)upb_obj;
203 int ret = zend_hash_index_del(&PROTOBUF_G(object_cache), k);
204 PBPHP_ASSERT(ret == SUCCESS);
205 }
206 }
207
ObjCache_Get(const void * upb_obj,zval * val)208 bool ObjCache_Get(const void *upb_obj, zval *val) {
209 zend_ulong k = (zend_ulong)upb_obj;
210 zend_object *obj = zend_hash_index_find_ptr(&PROTOBUF_G(object_cache), k);
211
212 if (obj) {
213 GC_ADDREF(obj);
214 ZVAL_OBJ(val, obj);
215 return true;
216 } else {
217 ZVAL_NULL(val);
218 return false;
219 }
220 }
221
222 // -----------------------------------------------------------------------------
223 // Name Cache.
224 // -----------------------------------------------------------------------------
225
NameMap_AddMessage(const upb_msgdef * m)226 void NameMap_AddMessage(const upb_msgdef *m) {
227 char *k = GetPhpClassname(upb_msgdef_file(m), upb_msgdef_fullname(m));
228 zend_hash_str_add_ptr(&PROTOBUF_G(name_msg_cache), k, strlen(k), (void*)m);
229 free(k);
230 }
231
NameMap_AddEnum(const upb_enumdef * e)232 void NameMap_AddEnum(const upb_enumdef *e) {
233 char *k = GetPhpClassname(upb_enumdef_file(e), upb_enumdef_fullname(e));
234 zend_hash_str_add_ptr(&PROTOBUF_G(name_enum_cache), k, strlen(k), (void*)e);
235 free(k);
236 }
237
NameMap_GetMessage(zend_class_entry * ce)238 const upb_msgdef *NameMap_GetMessage(zend_class_entry *ce) {
239 const upb_msgdef *ret =
240 zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
241
242 if (!ret && ce->create_object) {
243 #if PHP_VERSION_ID < 80000
244 zval tmp;
245 zval zv;
246 ZVAL_OBJ(&tmp, ce->create_object(ce));
247 zend_call_method_with_0_params(&tmp, ce, NULL, "__construct", &zv);
248 zval_ptr_dtor(&tmp);
249 #else
250 zval zv;
251 zend_object *tmp = ce->create_object(ce);
252 zend_call_method_with_0_params(tmp, ce, NULL, "__construct", &zv);
253 OBJ_RELEASE(tmp);
254 #endif
255 zval_ptr_dtor(&zv);
256 ret = zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
257 }
258
259 return ret;
260 }
261
NameMap_GetEnum(zend_class_entry * ce)262 const upb_enumdef *NameMap_GetEnum(zend_class_entry *ce) {
263 const upb_enumdef *ret =
264 zend_hash_find_ptr(&PROTOBUF_G(name_enum_cache), ce->name);
265 return ret;
266 }
267
268 // -----------------------------------------------------------------------------
269 // Module init.
270 // -----------------------------------------------------------------------------
271
272 zend_function_entry protobuf_functions[] = {
273 ZEND_FE_END
274 };
275
276 static const zend_module_dep protobuf_deps[] = {
277 ZEND_MOD_OPTIONAL("date")
278 ZEND_MOD_END
279 };
280
281 PHP_INI_BEGIN()
282 STD_PHP_INI_ENTRY("protobuf.keep_descriptor_pool_after_request", "0",
283 PHP_INI_SYSTEM, OnUpdateBool,
284 keep_descriptor_pool_after_request, zend_protobuf_globals,
285 protobuf_globals)
PHP_INI_END()286 PHP_INI_END()
287
288 static PHP_MINIT_FUNCTION(protobuf) {
289 REGISTER_INI_ENTRIES();
290 Arena_ModuleInit();
291 Array_ModuleInit();
292 Convert_ModuleInit();
293 Def_ModuleInit();
294 Map_ModuleInit();
295 Message_ModuleInit();
296 return SUCCESS;
297 }
298
PHP_MSHUTDOWN_FUNCTION(protobuf)299 static PHP_MSHUTDOWN_FUNCTION(protobuf) {
300 return SUCCESS;
301 }
302
303 zend_module_entry protobuf_module_entry = {
304 STANDARD_MODULE_HEADER_EX,
305 NULL,
306 protobuf_deps,
307 "protobuf", // extension name
308 protobuf_functions, // function list
309 PHP_MINIT(protobuf), // process startup
310 PHP_MSHUTDOWN(protobuf), // process shutdown
311 PHP_RINIT(protobuf), // request shutdown
312 PHP_RSHUTDOWN(protobuf), // request shutdown
313 NULL, // extension info
314 PHP_PROTOBUF_VERSION, // extension version
315 PHP_MODULE_GLOBALS(protobuf), // globals descriptor
316 PHP_GINIT(protobuf), // globals ctor
317 PHP_GSHUTDOWN(protobuf), // globals dtor
318 NULL, // post deactivate
319 STANDARD_MODULE_PROPERTIES_EX
320 };
321
322 ZEND_GET_MODULE(protobuf)
323