• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "protobuf.h"
9 
10 #include <Zend/zend_interfaces.h>
11 #include <php.h>
12 
13 #include "arena.h"
14 #include "array.h"
15 #include "convert.h"
16 #include "def.h"
17 #include "map.h"
18 #include "message.h"
19 #include "names.h"
20 
21 // -----------------------------------------------------------------------------
22 // Module "globals"
23 // -----------------------------------------------------------------------------
24 
25 // Despite the name, module "globals" are really thread-locals:
26 //  * PROTOBUF_G(var) accesses the thread-local variable for 'var'. Either:
27 //    * PROTOBUF_G(var) -> protobuf_globals.var (Non-ZTS / non-thread-safe)
28 //    * PROTOBUF_G(var) -> <Zend magic>         (ZTS / thread-safe builds)
29 
30 #define PROTOBUF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(protobuf, v)
31 
32 // clang-format off
33 ZEND_BEGIN_MODULE_GLOBALS(protobuf)
34   // Set by the user to make the descriptor pool persist between requests.
35   zend_bool keep_descriptor_pool_after_request;
36 
37   // Set by the user to make the descriptor pool persist between requests.
38   zend_class_entry* constructing_class;
39 
40   // A upb_DefPool that we are saving for the next request so that we don't have
41   // to rebuild it from scratch. When keep_descriptor_pool_after_request==true,
42   // we steal the upb_DefPool from the global DescriptorPool object just before
43   // destroying it.
44   upb_DefPool* global_symtab;
45 
46   // Object cache (see interface in protobuf.h).
47   HashTable object_cache;
48 
49   // Name cache (see interface in protobuf.h).
50   HashTable name_msg_cache;
51   HashTable name_enum_cache;
52 
53   // An array of descriptor objects constructed during this request. These are
54   // logically referenced by the corresponding class entry, but since we can't
55   // actually write a class entry destructor, we reference them here, to be
56   // destroyed on request shutdown.
57   HashTable descriptors;
ZEND_END_MODULE_GLOBALS(protobuf)58 ZEND_END_MODULE_GLOBALS(protobuf)
59 // clang-format on
60 
61 void free_protobuf_globals(zend_protobuf_globals* globals) {
62   zend_hash_destroy(&globals->name_msg_cache);
63   zend_hash_destroy(&globals->name_enum_cache);
64   upb_DefPool_Free(globals->global_symtab);
65   globals->global_symtab = NULL;
66 }
67 
ZEND_DECLARE_MODULE_GLOBALS(protobuf)68 ZEND_DECLARE_MODULE_GLOBALS(protobuf)
69 
70 upb_DefPool* get_global_symtab() { return PROTOBUF_G(global_symtab); }
71 
72 // This is a PHP extension (not a Zend extension). What follows is a summary of
73 // a PHP extension's lifetime and when various handlers are called.
74 //
75 //  * PHP_GINIT_FUNCTION(protobuf) / PHP_GSHUTDOWN_FUNCTION(protobuf)
76 //    are the constructor/destructor for the globals. The sequence over the
77 //    course of a process lifetime is:
78 //
79 //    # Process startup
80 //    GINIT(<Main Thread Globals>)
81 //    MINIT
82 //
83 //    foreach request:
84 //      RINIT
85 //        # Request is processed here.
86 //      RSHUTDOWN
87 //
88 //    foreach thread:
89 //      GINIT(<This Thread Globals>)
90 //        # Code for the thread runs here.
91 //      GSHUTDOWN(<This Thread Globals>)
92 //
93 //    # Process Shutdown
94 //    #
95 //    # These should be running per the docs, but I have not been able to
96 //    # actually get the process-wide shutdown functions to run.
97 //    #
98 //    # MSHUTDOWN
99 //    # GSHUTDOWN(<Main Thread Globals>)
100 //
101 //  * Threads can be created either explicitly by the user, inside a request,
102 //    or implicitly by the runtime, to process multiple requests concurrently.
103 //    If the latter is being used, then the "foreach thread" block above
104 //    actually looks like this:
105 //
106 //    foreach thread:
107 //      GINIT(<This Thread Globals>)
108 //      # A non-main thread will only receive requests when using a threaded
109 //      # MPM with Apache
110 //      foreach request:
111 //        RINIT
112 //          # Request is processed here.
113 //        RSHUTDOWN
114 //      GSHUTDOWN(<This Thread Globals>)
115 //
116 // That said, it appears that few people use threads with PHP:
117 //   * The pthread package documented at
118 //     https://www.php.net/manual/en/class.thread.php nas not been released
119 //     since 2016, and the current release fails to compile against any PHP
120 //     newer than 7.0.33.
121 //     * The GitHub master branch supports 7.2+, but this has not been released
122 //       to PECL.
123 //     * Its owner has disavowed it as "broken by design" and "in an untenable
124 //       position for the future":
125 //       https://github.com/krakjoe/pthreads/issues/929
126 //   * The only way to use PHP with requests in different threads is to use the
127 //     Apache 2 mod_php with the "worker" MPM. But this is explicitly
128 //     discouraged by the documentation: https://serverfault.com/a/231660
129 
PHP_GSHUTDOWN_FUNCTION(protobuf)130 static PHP_GSHUTDOWN_FUNCTION(protobuf) {
131   if (protobuf_globals->global_symtab) {
132     free_protobuf_globals(protobuf_globals);
133   }
134 }
135 
PHP_GINIT_FUNCTION(protobuf)136 static PHP_GINIT_FUNCTION(protobuf) { protobuf_globals->global_symtab = NULL; }
137 
138 /**
139  * PHP_RINIT_FUNCTION(protobuf)
140  *
141  * This function is run at the beginning of processing each request.
142  */
PHP_RINIT_FUNCTION(protobuf)143 static PHP_RINIT_FUNCTION(protobuf) {
144   // Create the global generated pool.
145   // Reuse the symtab (if any) left to us by the last request.
146   if (!PROTOBUF_G(global_symtab)) {
147     zend_bool persistent = PROTOBUF_G(keep_descriptor_pool_after_request);
148     PROTOBUF_G(global_symtab) = upb_DefPool_New();
149     zend_hash_init(&PROTOBUF_G(name_msg_cache), 64, NULL, NULL, persistent);
150     zend_hash_init(&PROTOBUF_G(name_enum_cache), 64, NULL, NULL, persistent);
151   }
152 
153   zend_hash_init(&PROTOBUF_G(object_cache), 64, NULL, NULL, 0);
154   zend_hash_init(&PROTOBUF_G(descriptors), 64, NULL, ZVAL_PTR_DTOR, 0);
155   PROTOBUF_G(constructing_class) = NULL;
156 
157   return SUCCESS;
158 }
159 
160 /**
161  * PHP_RSHUTDOWN_FUNCTION(protobuf)
162  *
163  * This function is run at the end of processing each request.
164  */
PHP_RSHUTDOWN_FUNCTION(protobuf)165 static PHP_RSHUTDOWN_FUNCTION(protobuf) {
166   // Preserve the symtab if requested.
167   if (!PROTOBUF_G(keep_descriptor_pool_after_request)) {
168     free_protobuf_globals(ZEND_MODULE_GLOBALS_BULK(protobuf));
169   }
170 
171   zend_hash_destroy(&PROTOBUF_G(object_cache));
172   zend_hash_destroy(&PROTOBUF_G(descriptors));
173 
174   return SUCCESS;
175 }
176 
177 // -----------------------------------------------------------------------------
178 // Object Cache.
179 // -----------------------------------------------------------------------------
180 
Descriptors_Add(zend_object * desc)181 void Descriptors_Add(zend_object* desc) {
182   // The hash table will own a ref (it will destroy it when the table is
183   // destroyed), but for some reason the insert operation does not add a ref, so
184   // we do that here with ZVAL_OBJ_COPY().
185   zval zv;
186   ZVAL_OBJ_COPY(&zv, desc);
187   zend_hash_next_index_insert(&PROTOBUF_G(descriptors), &zv);
188 }
189 
ObjCache_Add(const void * upb_obj,zend_object * php_obj)190 void ObjCache_Add(const void* upb_obj, zend_object* php_obj) {
191   zend_ulong k = (zend_ulong)upb_obj;
192   zend_hash_index_add_ptr(&PROTOBUF_G(object_cache), k, php_obj);
193 }
194 
ObjCache_Delete(const void * upb_obj)195 void ObjCache_Delete(const void* upb_obj) {
196   if (upb_obj) {
197     zend_ulong k = (zend_ulong)upb_obj;
198     int ret = zend_hash_index_del(&PROTOBUF_G(object_cache), k);
199     PBPHP_ASSERT(ret == SUCCESS);
200   }
201 }
202 
ObjCache_Get(const void * upb_obj,zval * val)203 bool ObjCache_Get(const void* upb_obj, zval* val) {
204   zend_ulong k = (zend_ulong)upb_obj;
205   zend_object* obj = zend_hash_index_find_ptr(&PROTOBUF_G(object_cache), k);
206 
207   if (obj) {
208     ZVAL_OBJ_COPY(val, obj);
209     return true;
210   } else {
211     ZVAL_NULL(val);
212     return false;
213   }
214 }
215 
216 // -----------------------------------------------------------------------------
217 // Name Cache.
218 // -----------------------------------------------------------------------------
219 
NameMap_AddMessage(const upb_MessageDef * m)220 void NameMap_AddMessage(const upb_MessageDef* m) {
221   for (int i = 0; i < 2; ++i) {
222     char* k = GetPhpClassname(upb_MessageDef_File(m),
223                               upb_MessageDef_FullName(m), (bool)i);
224     zend_hash_str_add_ptr(&PROTOBUF_G(name_msg_cache), k, strlen(k), (void*)m);
225     if (!IsPreviouslyUnreservedClassName(k)) {
226       free(k);
227       return;
228     }
229     free(k);
230   }
231 }
232 
NameMap_AddEnum(const upb_EnumDef * e)233 void NameMap_AddEnum(const upb_EnumDef* e) {
234   char* k =
235       GetPhpClassname(upb_EnumDef_File(e), upb_EnumDef_FullName(e), false);
236   zend_hash_str_add_ptr(&PROTOBUF_G(name_enum_cache), k, strlen(k), (void*)e);
237   free(k);
238 }
239 
NameMap_GetMessage(zend_class_entry * ce)240 const upb_MessageDef* NameMap_GetMessage(zend_class_entry* ce) {
241   const upb_MessageDef* ret =
242       zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
243 
244   if (!ret && ce->create_object && ce != PROTOBUF_G(constructing_class)) {
245     zval zv;
246     zend_object* tmp = ce->create_object(ce);
247     zend_call_method_with_0_params(tmp, ce, NULL, "__construct", &zv);
248     OBJ_RELEASE(tmp);
249     zval_ptr_dtor(&zv);
250     ret = zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
251   }
252 
253   return ret;
254 }
255 
NameMap_GetEnum(zend_class_entry * ce)256 const upb_EnumDef* NameMap_GetEnum(zend_class_entry* ce) {
257   const upb_EnumDef* ret =
258       zend_hash_find_ptr(&PROTOBUF_G(name_enum_cache), ce->name);
259   return ret;
260 }
261 
NameMap_EnterConstructor(zend_class_entry * ce)262 void NameMap_EnterConstructor(zend_class_entry* ce) {
263   assert(!PROTOBUF_G(constructing_class));
264   PROTOBUF_G(constructing_class) = ce;
265 }
266 
NameMap_ExitConstructor(zend_class_entry * ce)267 void NameMap_ExitConstructor(zend_class_entry* ce) {
268   assert(PROTOBUF_G(constructing_class) == ce);
269   PROTOBUF_G(constructing_class) = NULL;
270 }
271 
272 // -----------------------------------------------------------------------------
273 // Module init.
274 // -----------------------------------------------------------------------------
275 
276 zend_function_entry protobuf_functions[] = {ZEND_FE_END};
277 
278 static const zend_module_dep protobuf_deps[] = {ZEND_MOD_OPTIONAL("date")
279                                                     ZEND_MOD_END};
280 
281 PHP_INI_BEGIN()
282 STD_PHP_INI_ENTRY("protobuf.keep_descriptor_pool_after_request", "0",
283                   PHP_INI_ALL, OnUpdateBool, keep_descriptor_pool_after_request,
284                   zend_protobuf_globals, protobuf_globals)
PHP_INI_END()285 PHP_INI_END()
286 
287 static PHP_MINIT_FUNCTION(protobuf) {
288   REGISTER_INI_ENTRIES();
289   Arena_ModuleInit();
290   Array_ModuleInit();
291   Convert_ModuleInit();
292   Def_ModuleInit();
293   Map_ModuleInit();
294   Message_ModuleInit();
295   return SUCCESS;
296 }
297 
PHP_MSHUTDOWN_FUNCTION(protobuf)298 static PHP_MSHUTDOWN_FUNCTION(protobuf) {
299   UNREGISTER_INI_ENTRIES();
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 startup
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 ZEND_GET_MODULE(protobuf)
322