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