• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   // Set by the user to make the descriptor pool persist between requests.
60   zend_class_entry* constructing_class;
61 
62   // A upb_DefPool that we are saving for the next request so that we don't have
63   // to rebuild it from scratch. When keep_descriptor_pool_after_request==true,
64   // we steal the upb_DefPool from the global DescriptorPool object just before
65   // destroying it.
66   upb_DefPool *global_symtab;
67 
68   // Object cache (see interface in protobuf.h).
69   HashTable object_cache;
70 
71   // Name cache (see interface in protobuf.h).
72   HashTable name_msg_cache;
73   HashTable name_enum_cache;
74 
75   // An array of descriptor objects constructed during this request. These are
76   // logically referenced by the corresponding class entry, but since we can't
77   // actually write a class entry destructor, we reference them here, to be
78   // destroyed on request shutdown.
79   HashTable descriptors;
ZEND_END_MODULE_GLOBALS(protobuf)80 ZEND_END_MODULE_GLOBALS(protobuf)
81 
82 void free_protobuf_globals(zend_protobuf_globals *globals) {
83   zend_hash_destroy(&globals->name_msg_cache);
84   zend_hash_destroy(&globals->name_enum_cache);
85   upb_DefPool_Free(globals->global_symtab);
86   globals->global_symtab = NULL;
87 }
88 
ZEND_DECLARE_MODULE_GLOBALS(protobuf)89 ZEND_DECLARE_MODULE_GLOBALS(protobuf)
90 
91 upb_DefPool *get_global_symtab() {
92   return PROTOBUF_G(global_symtab);
93 }
94 
95 // This is a PHP extension (not a Zend extension). What follows is a summary of
96 // a PHP extension's lifetime and when various handlers are called.
97 //
98 //  * PHP_GINIT_FUNCTION(protobuf) / PHP_GSHUTDOWN_FUNCTION(protobuf)
99 //    are the constructor/destructor for the globals. The sequence over the
100 //    course of a process lifetime is:
101 //
102 //    # Process startup
103 //    GINIT(<Main Thread Globals>)
104 //    MINIT
105 //
106 //    foreach request:
107 //      RINIT
108 //        # Request is processed here.
109 //      RSHUTDOWN
110 //
111 //    foreach thread:
112 //      GINIT(<This Thread Globals>)
113 //        # Code for the thread runs here.
114 //      GSHUTDOWN(<This Thread Globals>)
115 //
116 //    # Process Shutdown
117 //    #
118 //    # These should be running per the docs, but I have not been able to
119 //    # actually get the process-wide shutdown functions to run.
120 //    #
121 //    # MSHUTDOWN
122 //    # GSHUTDOWN(<Main Thread Globals>)
123 //
124 //  * Threads can be created either explicitly by the user, inside a request,
125 //    or implicitly by the runtime, to process multiple requests concurrently.
126 //    If the latter is being used, then the "foreach thread" block above
127 //    actually looks like this:
128 //
129 //    foreach thread:
130 //      GINIT(<This Thread Globals>)
131 //      # A non-main thread will only receive requests when using a threaded
132 //      # MPM with Apache
133 //      foreach request:
134 //        RINIT
135 //          # Request is processed here.
136 //        RSHUTDOWN
137 //      GSHUTDOWN(<This Thread Globals>)
138 //
139 // That said, it appears that few people use threads with PHP:
140 //   * The pthread package documented at
141 //     https://www.php.net/manual/en/class.thread.php nas not been released
142 //     since 2016, and the current release fails to compile against any PHP
143 //     newer than 7.0.33.
144 //     * The GitHub master branch supports 7.2+, but this has not been released
145 //       to PECL.
146 //     * Its owner has disavowed it as "broken by design" and "in an untenable
147 //       position for the future": https://github.com/krakjoe/pthreads/issues/929
148 //   * The only way to use PHP with requests in different threads is to use the
149 //     Apache 2 mod_php with the "worker" MPM. But this is explicitly
150 //     discouraged by the documentation: https://serverfault.com/a/231660
151 
PHP_GSHUTDOWN_FUNCTION(protobuf)152 static PHP_GSHUTDOWN_FUNCTION(protobuf) {
153   if (protobuf_globals->global_symtab) {
154     free_protobuf_globals(protobuf_globals);
155   }
156 }
157 
PHP_GINIT_FUNCTION(protobuf)158 static PHP_GINIT_FUNCTION(protobuf) {
159   protobuf_globals->global_symtab = NULL;
160 }
161 
162 /**
163  * PHP_RINIT_FUNCTION(protobuf)
164  *
165  * This function is run at the beginning of processing each request.
166  */
PHP_RINIT_FUNCTION(protobuf)167 static PHP_RINIT_FUNCTION(protobuf) {
168   // Create the global generated pool.
169   // Reuse the symtab (if any) left to us by the last request.
170   upb_DefPool *symtab = PROTOBUF_G(global_symtab);
171   if (!symtab) {
172     PROTOBUF_G(global_symtab) = symtab = upb_DefPool_New();
173     zend_hash_init(&PROTOBUF_G(name_msg_cache), 64, NULL, NULL, 0);
174     zend_hash_init(&PROTOBUF_G(name_enum_cache), 64, NULL, NULL, 0);
175   }
176 
177   zend_hash_init(&PROTOBUF_G(object_cache), 64, NULL, NULL, 0);
178   zend_hash_init(&PROTOBUF_G(descriptors), 64, NULL, ZVAL_PTR_DTOR, 0);
179   PROTOBUF_G(constructing_class) = NULL;
180 
181   return SUCCESS;
182 }
183 
184 /**
185  * PHP_RSHUTDOWN_FUNCTION(protobuf)
186  *
187  * This function is run at the end of processing each request.
188  */
PHP_RSHUTDOWN_FUNCTION(protobuf)189 static PHP_RSHUTDOWN_FUNCTION(protobuf) {
190   // Preserve the symtab if requested.
191   if (!PROTOBUF_G(keep_descriptor_pool_after_request)) {
192     free_protobuf_globals(ZEND_MODULE_GLOBALS_BULK(protobuf));
193   }
194 
195   zend_hash_destroy(&PROTOBUF_G(object_cache));
196   zend_hash_destroy(&PROTOBUF_G(descriptors));
197 
198   return SUCCESS;
199 }
200 
201 // -----------------------------------------------------------------------------
202 // Object Cache.
203 // -----------------------------------------------------------------------------
204 
Descriptors_Add(zend_object * desc)205 void Descriptors_Add(zend_object *desc) {
206   // The hash table will own a ref (it will destroy it when the table is
207   // destroyed), but for some reason the insert operation does not add a ref, so
208   // we do that here with ZVAL_OBJ_COPY().
209   zval zv;
210   ZVAL_OBJ_COPY(&zv, desc);
211   zend_hash_next_index_insert(&PROTOBUF_G(descriptors), &zv);
212 }
213 
ObjCache_Add(const void * upb_obj,zend_object * php_obj)214 void ObjCache_Add(const void *upb_obj, zend_object *php_obj) {
215   zend_ulong k = (zend_ulong)upb_obj;
216   zend_hash_index_add_ptr(&PROTOBUF_G(object_cache), k, php_obj);
217 }
218 
ObjCache_Delete(const void * upb_obj)219 void ObjCache_Delete(const void *upb_obj) {
220   if (upb_obj) {
221     zend_ulong k = (zend_ulong)upb_obj;
222     int ret = zend_hash_index_del(&PROTOBUF_G(object_cache), k);
223     PBPHP_ASSERT(ret == SUCCESS);
224   }
225 }
226 
ObjCache_Get(const void * upb_obj,zval * val)227 bool ObjCache_Get(const void *upb_obj, zval *val) {
228   zend_ulong k = (zend_ulong)upb_obj;
229   zend_object *obj = zend_hash_index_find_ptr(&PROTOBUF_G(object_cache), k);
230 
231   if (obj) {
232     ZVAL_OBJ_COPY(val, obj);
233     return true;
234   } else {
235     ZVAL_NULL(val);
236     return false;
237   }
238 }
239 
240 // -----------------------------------------------------------------------------
241 // Name Cache.
242 // -----------------------------------------------------------------------------
243 
NameMap_AddMessage(const upb_MessageDef * m)244 void NameMap_AddMessage(const upb_MessageDef *m) {
245   for (int i = 0; i < 2; ++i) {
246     char *k = GetPhpClassname(upb_MessageDef_File(m), upb_MessageDef_FullName(m), (bool)i);
247     zend_hash_str_add_ptr(&PROTOBUF_G(name_msg_cache), k, strlen(k), (void*)m);
248     if (!IsPreviouslyUnreservedClassName(k)) {
249       free(k);
250       return;
251     }
252     free(k);
253   }
254 }
255 
NameMap_AddEnum(const upb_EnumDef * e)256 void NameMap_AddEnum(const upb_EnumDef *e) {
257   char *k = GetPhpClassname(upb_EnumDef_File(e), upb_EnumDef_FullName(e), false);
258   zend_hash_str_add_ptr(&PROTOBUF_G(name_enum_cache), k, strlen(k), (void*)e);
259   free(k);
260 }
261 
NameMap_GetMessage(zend_class_entry * ce)262 const upb_MessageDef *NameMap_GetMessage(zend_class_entry *ce) {
263   const upb_MessageDef *ret =
264       zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
265 
266   if (!ret && ce->create_object && ce != PROTOBUF_G(constructing_class)) {
267 #if PHP_VERSION_ID < 80000
268     zval tmp;
269     zval zv;
270     ZVAL_OBJ(&tmp, ce->create_object(ce));
271     zend_call_method_with_0_params(&tmp, ce, NULL, "__construct", &zv);
272     zval_ptr_dtor(&tmp);
273 #else
274     zval zv;
275     zend_object *tmp = ce->create_object(ce);
276     zend_call_method_with_0_params(tmp, ce, NULL, "__construct", &zv);
277     OBJ_RELEASE(tmp);
278 #endif
279     zval_ptr_dtor(&zv);
280     ret = zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
281   }
282 
283   return ret;
284 }
285 
NameMap_GetEnum(zend_class_entry * ce)286 const upb_EnumDef *NameMap_GetEnum(zend_class_entry *ce) {
287   const upb_EnumDef *ret =
288       zend_hash_find_ptr(&PROTOBUF_G(name_enum_cache), ce->name);
289   return ret;
290 }
291 
NameMap_EnterConstructor(zend_class_entry * ce)292 void NameMap_EnterConstructor(zend_class_entry* ce) {
293   assert(!PROTOBUF_G(constructing_class));
294   PROTOBUF_G(constructing_class) = ce;
295 }
296 
NameMap_ExitConstructor(zend_class_entry * ce)297 void NameMap_ExitConstructor(zend_class_entry* ce) {
298   assert(PROTOBUF_G(constructing_class) == ce);
299   PROTOBUF_G(constructing_class) = NULL;
300 }
301 
302 // -----------------------------------------------------------------------------
303 // Module init.
304 // -----------------------------------------------------------------------------
305 
306 zend_function_entry protobuf_functions[] = {
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_ALL, 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   UNREGISTER_INI_ENTRIES();
335   return SUCCESS;
336 }
337 
338 zend_module_entry protobuf_module_entry = {
339   STANDARD_MODULE_HEADER_EX,
340   NULL,
341   protobuf_deps,
342   "protobuf",               // extension name
343   protobuf_functions,       // function list
344   PHP_MINIT(protobuf),      // process startup
345   PHP_MSHUTDOWN(protobuf),  // process shutdown
346   PHP_RINIT(protobuf),      // request shutdown
347   PHP_RSHUTDOWN(protobuf),  // request shutdown
348   NULL,                     // extension info
349   PHP_PROTOBUF_VERSION,     // extension version
350   PHP_MODULE_GLOBALS(protobuf),  // globals descriptor
351   PHP_GINIT(protobuf),      // globals ctor
352   PHP_GSHUTDOWN(protobuf),  // globals dtor
353   NULL,                     // post deactivate
354   STANDARD_MODULE_PROPERTIES_EX
355 };
356 
357 ZEND_GET_MODULE(protobuf)
358