1 /*-------------------------------------------------------------------------
2 *
3 * plpgsql_check.c
4 *
5 * enhanced checks for plpgsql functions
6 *
7 * by Pavel Stehule 2013-2021
8 *
9 *-------------------------------------------------------------------------
10 *
11 * Notes:
12 *
13 * 1) Secondary hash table for function signature is necessary due holding is_checked
14 * attribute - this protection against unwanted repeated check.
15 *
16 * 2) Reusing some plpgsql_xxx functions requires full run-time environment. It is
17 * emulated by fake expression context and fake fceinfo (these are created when
18 * active checking is used) - see: setup_fake_fcinfo, setup_cstate.
19 *
20 * 3) The environment is referenced by stored execution plans. The actual plan should
21 * not be linked with fake environment. All expressions created in checking time
22 * should be relased by release_exprs(cstate.exprs) function.
23 *
24 */
25
26 #include "plpgsql_check.h"
27 #include "plpgsql_check_builtins.h"
28
29 #include "storage/lwlock.h"
30 #include "storage/shmem.h"
31 #include "utils/guc.h"
32 #include "utils/memutils.h"
33
34 #ifdef PG_MODULE_MAGIC
35 PG_MODULE_MAGIC;
36 #endif
37
38 PLpgSQL_plugin **plpgsql_check_plugin_var_ptr;
39
40
41 static PLpgSQL_plugin plugin_funcs = { plpgsql_check_profiler_func_init,
42 plpgsql_check_on_func_beg,
43 plpgsql_check_profiler_func_end,
44 plpgsql_check_profiler_stmt_beg,
45 plpgsql_check_profiler_stmt_end,
46 NULL,
47 NULL};
48
49
50 static const struct config_enum_entry plpgsql_check_mode_options[] = {
51 {"disabled", PLPGSQL_CHECK_MODE_DISABLED, false},
52 {"by_function", PLPGSQL_CHECK_MODE_BY_FUNCTION, false},
53 {"fresh_start", PLPGSQL_CHECK_MODE_FRESH_START, false},
54 {"every_start", PLPGSQL_CHECK_MODE_EVERY_START, false},
55 {NULL, 0, false}
56 };
57
58 static const struct config_enum_entry tracer_verbosity_options[] = {
59 {"terse", PGERROR_TERSE, false},
60 {"default", PGERROR_DEFAULT, false},
61 {"verbose", PGERROR_VERBOSE, false},
62 {NULL, 0, false}
63 };
64
65 static const struct config_enum_entry tracer_level_options[] = {
66 {"debug5", DEBUG5, false},
67 {"debug4", DEBUG4, false},
68 {"debug3", DEBUG3, false},
69 {"debug2", DEBUG2, false},
70 {"debug1", DEBUG1, false},
71 {"debug", DEBUG2, true},
72 {"info", INFO, false},
73 {"notice", NOTICE, false},
74 {"log", LOG, false},
75 {NULL, 0, false}
76 };
77
78 void _PG_init(void);
79 void _PG_fini(void);
80
81 shmem_startup_hook_type prev_shmem_startup_hook = NULL;
82
83 bool plpgsql_check_regress_test_mode;
84
85
86 /*
87 * Links to function in plpgsql module
88 */
89 plpgsql_check__build_datatype_t plpgsql_check__build_datatype_p;
90 plpgsql_check__compile_t plpgsql_check__compile_p;
91 plpgsql_check__parser_setup_t plpgsql_check__parser_setup_p;
92 plpgsql_check__stmt_typename_t plpgsql_check__stmt_typename_p;
93 plpgsql_check__exec_get_datum_type_t plpgsql_check__exec_get_datum_type_p;
94 plpgsql_check__recognize_err_condition_t plpgsql_check__recognize_err_condition_p;
95 plpgsql_check__ns_lookup_t plpgsql_check__ns_lookup_p;
96
97
98 /*
99 * load_external_function retursn PGFunctions - we need generic function, so
100 * it is not 100% correct, but in used context it is not a problem.
101 */
102 #define LOAD_EXTERNAL_FUNCTION(file, funcname) ((void *) (load_external_function(file, funcname, true, NULL)))
103
104 /*
105 * Module initialization
106 *
107 * join to PLpgSQL executor
108 *
109 */
110 void
_PG_init(void)111 _PG_init(void)
112 {
113
114 /* Be sure we do initialization only once (should be redundant now) */
115 static bool inited = false;
116
117 if (inited)
118 return;
119
120 pg_bindtextdomain(TEXTDOMAIN);
121
122 AssertVariableIsOfType(&plpgsql_build_datatype, plpgsql_check__build_datatype_t);
123 plpgsql_check__build_datatype_p = (plpgsql_check__build_datatype_t)
124 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_build_datatype");
125
126 AssertVariableIsOfType(&plpgsql_compile, plpgsql_check__compile_t);
127 plpgsql_check__compile_p = (plpgsql_check__compile_t)
128 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_compile");
129
130 AssertVariableIsOfType(&plpgsql_parser_setup, plpgsql_check__parser_setup_t);
131 plpgsql_check__parser_setup_p = (plpgsql_check__parser_setup_t)
132 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_parser_setup");
133
134 AssertVariableIsOfType(&plpgsql_stmt_typename, plpgsql_check__stmt_typename_t);
135 plpgsql_check__stmt_typename_p = (plpgsql_check__stmt_typename_t)
136 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_stmt_typename");
137
138 AssertVariableIsOfType(&plpgsql_exec_get_datum_type, plpgsql_check__exec_get_datum_type_t);
139 plpgsql_check__exec_get_datum_type_p = (plpgsql_check__exec_get_datum_type_t)
140 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_exec_get_datum_type");
141
142 AssertVariableIsOfType(&plpgsql_recognize_err_condition, plpgsql_check__recognize_err_condition_t);
143 plpgsql_check__recognize_err_condition_p = (plpgsql_check__recognize_err_condition_t)
144 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_recognize_err_condition");
145
146 AssertVariableIsOfType(&plpgsql_ns_lookup, plpgsql_check__ns_lookup_t);
147 plpgsql_check__ns_lookup_p = (plpgsql_check__ns_lookup_t)
148 LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_ns_lookup");
149
150 plpgsql_check_plugin_var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable( "PLpgSQL_plugin");
151 *plpgsql_check_plugin_var_ptr = &plugin_funcs;
152
153 DefineCustomBoolVariable("plpgsql_check.regress_test_mode",
154 "reduces volatile output",
155 NULL,
156 &plpgsql_check_regress_test_mode,
157 false,
158 PGC_USERSET, 0,
159 NULL, NULL, NULL);
160
161 DefineCustomEnumVariable("plpgsql_check.mode",
162 "choose a mode for enhanced checking",
163 NULL,
164 &plpgsql_check_mode,
165 PLPGSQL_CHECK_MODE_BY_FUNCTION,
166 plpgsql_check_mode_options,
167 PGC_USERSET, 0,
168 NULL, NULL, NULL);
169
170 DefineCustomBoolVariable("plpgsql_check.show_nonperformance_extra_warnings",
171 "when is true, then extra warning (except performance warnings) are showed",
172 NULL,
173 &plpgsql_check_extra_warnings,
174 false,
175 PGC_USERSET, 0,
176 NULL, NULL, NULL);
177
178 DefineCustomBoolVariable("plpgsql_check.show_nonperformance_warnings",
179 "when is true, then warning (except performance warnings) are showed",
180 NULL,
181 &plpgsql_check_other_warnings,
182 false,
183 PGC_USERSET, 0,
184 NULL, NULL, NULL);
185
186 DefineCustomBoolVariable("plpgsql_check.show_performance_warnings",
187 "when is true, then performance warnings are showed",
188 NULL,
189 &plpgsql_check_performance_warnings,
190 false,
191 PGC_USERSET, 0,
192 NULL, NULL, NULL);
193
194 DefineCustomBoolVariable("plpgsql_check.fatal_errors",
195 "when is true, then plpgsql check stops execution on detected error",
196 NULL,
197 &plpgsql_check_fatal_errors,
198 true,
199 PGC_USERSET, 0,
200 NULL, NULL, NULL);
201
202 DefineCustomBoolVariable("plpgsql_check.profiler",
203 "when is true, then function execution profile is updated",
204 NULL,
205 &plpgsql_check_profiler,
206 false,
207 PGC_USERSET, 0,
208 NULL, NULL, NULL);
209
210 DefineCustomBoolVariable("plpgsql_check.enable_tracer",
211 "when is true, then tracer's functionality is enabled",
212 NULL,
213 &plpgsql_check_enable_tracer,
214 false,
215 PGC_SUSET, 0,
216 NULL, NULL, NULL);
217
218 DefineCustomBoolVariable("plpgsql_check.tracer",
219 "when is true, then function is traced",
220 NULL,
221 &plpgsql_check_tracer,
222 false,
223 PGC_USERSET, 0,
224 NULL, NULL, NULL);
225
226 DefineCustomBoolVariable("plpgsql_check.trace_assert",
227 "when is true, then statement ASSERT is traced",
228 NULL,
229 &plpgsql_check_trace_assert,
230 false,
231 PGC_USERSET, 0,
232 NULL, NULL, NULL);
233
234 DefineCustomBoolVariable("plpgsql_check.tracer_test_mode",
235 "when is true, then output of tracer is in regress test possible format",
236 NULL,
237 &plpgsql_check_tracer_test_mode,
238 false,
239 PGC_USERSET, 0,
240 NULL, NULL, NULL);
241
242 DefineCustomEnumVariable("plpgsql_check.tracer_verbosity",
243 "sets the verbosity of tracer",
244 NULL,
245 (int *) &plpgsql_check_tracer_verbosity,
246 PGERROR_DEFAULT,
247 tracer_verbosity_options,
248 PGC_USERSET, 0,
249 NULL, NULL, NULL);
250
251 DefineCustomEnumVariable("plpgsql_check.trace_assert_verbosity",
252 "sets the verbosity of trace ASSERT statement",
253 NULL,
254 (int *) &plpgsql_check_trace_assert_verbosity,
255 PGERROR_DEFAULT,
256 tracer_verbosity_options,
257 PGC_USERSET, 0,
258 NULL, NULL, NULL);
259
260 DefineCustomEnumVariable("plpgsql_check.tracer_errlevel",
261 "sets an error level of tracer's messages",
262 NULL,
263 (int *) &plpgsql_check_tracer_errlevel,
264 NOTICE,
265 tracer_level_options,
266 PGC_USERSET, 0,
267 NULL, NULL, NULL);
268
269 DefineCustomIntVariable("plpgsql_check.tracer_variable_max_length",
270 "Maximum output length of content of variables in bytes",
271 NULL,
272 &plpgsql_check_tracer_variable_max_length,
273 1024,
274 10, 2048,
275 PGC_USERSET, 0,
276 NULL, NULL, NULL);
277
278 EmitWarningsOnPlaceholders("plpgsql_check");
279
280 plpgsql_check_HashTableInit();
281 plpgsql_check_profiler_init_hash_tables();
282
283 /* Use shared memory when we can register more for self */
284 if (process_shared_preload_libraries_in_progress)
285 {
286
287 DefineCustomIntVariable("plpgsql_check.profiler_max_shared_chunks",
288 "maximum numbers of statements chunks in shared memory",
289 NULL,
290 &plpgsql_check_profiler_max_shared_chunks,
291 15000, 50, 100000,
292 PGC_POSTMASTER, 0,
293 NULL, NULL, NULL);
294
295 RequestAddinShmemSpace(plpgsql_check_shmem_size());
296
297 RequestNamedLWLockTranche("plpgsql_check profiler", 1);
298 RequestNamedLWLockTranche("plpgsql_check fstats", 1);
299
300 /*
301 * Install hooks.
302 */
303 prev_shmem_startup_hook = shmem_startup_hook;
304 shmem_startup_hook = plpgsql_check_profiler_shmem_startup;
305 }
306
307 inited = true;
308 }
309
310 /*
311 * Module unload callback
312 */
313 void
_PG_fini(void)314 _PG_fini(void)
315 {
316 shmem_startup_hook = prev_shmem_startup_hook;
317
318 /* Be more correct, and clean rendezvous variable */
319 *plpgsql_check_plugin_var_ptr = NULL;
320 }
321