static memory for let_calloc
[m6w6/ext-psi] / src / cpp.c
1 /*******************************************************************************
2 Copyright (c) 2017, Michael Wallner <mike@php.net>.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 * Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 *******************************************************************************/
25
26 #include "php_psi_stdinc.h"
27
28 #include "cpp.h"
29 #include "parser.h"
30
31 #define PSI_CPP_SEARCH
32 #define PSI_CPP_PREDEF
33 #include "php_psi_cpp.h"
34
35 #include "php_psi.h"
36
37 static void free_cpp_def(zval *p)
38 {
39 if (Z_TYPE_P(p) == IS_PTR) {
40 psi_cpp_macro_decl_free((void *) &Z_PTR_P(p));
41 }
42 }
43
44 struct psi_cpp *psi_cpp_init(struct psi_parser *P)
45 {
46 struct psi_cpp *cpp = calloc(1, sizeof(*cpp));
47
48 cpp->parser = P;
49 zend_hash_init(&cpp->defs, 0, NULL, free_cpp_def, 1);
50 zend_hash_init(&cpp->once, 0, NULL, NULL, 1);
51
52 return cpp;
53 }
54
55 bool psi_cpp_load_defaults(struct psi_cpp *cpp)
56 {
57 struct psi_parser_input *predef;
58
59 if ((predef = psi_parser_open_string(cpp->parser, psi_cpp_predef, sizeof(psi_cpp_predef) - 1))) {
60 bool parsed = psi_parser_parse(cpp->parser, predef);
61 free(predef);
62 return parsed;
63 }
64
65 return false;
66 }
67
68 static int dump_def(zval *p)
69 {
70 struct psi_cpp_macro_decl *decl = Z_PTR_P(p);
71
72 if (decl) {
73 dprintf(2, "#define ");
74 psi_cpp_macro_decl_dump(2, decl);
75 dprintf(2, "\n");
76 }
77 return ZEND_HASH_APPLY_KEEP;
78 }
79
80 void psi_cpp_free(struct psi_cpp **cpp_ptr)
81 {
82 if (*cpp_ptr) {
83 struct psi_cpp *cpp = *cpp_ptr;
84
85 *cpp_ptr = NULL;
86 if (cpp->parser->flags & PSI_DEBUG) {
87 fprintf(stderr, "PSI: CPP decls:\n");
88 zend_hash_apply(&cpp->defs, dump_def);
89 }
90 zend_hash_destroy(&cpp->defs);
91 zend_hash_destroy(&cpp->once);
92 free(cpp);
93 }
94 }
95
96 static bool psi_cpp_stage1(struct psi_cpp *cpp)
97 {
98 bool name = false, define = false, hash = false, eol = true, esc = false, ws = false;
99
100 psi_cpp_tokiter_reset(cpp);
101 while (psi_cpp_tokiter_valid(cpp)) {
102 struct psi_token *token = psi_cpp_tokiter_current(cpp);
103
104 /* strip comments and attributes */
105 if (token->type == PSI_T_COMMENT
106 || token->type == PSI_T_CPP_ATTRIBUTE) {
107 psi_cpp_tokiter_del_cur(cpp, true);
108 continue;
109 }
110
111 /* line continuations */
112 if (token->type == PSI_T_EOL) {
113 if (esc) {
114 psi_cpp_tokiter_del_range(cpp, psi_cpp_tokiter_index(cpp) - 1, 2, true);
115 psi_cpp_tokiter_prev(cpp);
116 esc = false;
117 continue;
118 }
119 } else if (token->type == PSI_T_BSLASH) {
120 esc = !esc;
121 } else {
122 esc = false;
123 }
124
125 /* this whole turf is needed to distinct between:
126 * #define foo (1,2,3)
127 * #define foo(a,b,c)
128 */
129
130 if (token->type == PSI_T_WHITESPACE) {
131 if (name) {
132 name = false;
133 }
134 ws = true;
135 psi_cpp_tokiter_del_cur(cpp, true);
136 continue;
137 }
138
139 switch (token->type) {
140 case PSI_T_EOL:
141 eol = true;
142 break;
143 case PSI_T_HASH:
144 if (eol) {
145 hash = true;
146 eol = false;
147 }
148 break;
149 case PSI_T_DEFINE:
150 if (hash) {
151 define = true;
152 hash = false;
153 }
154 break;
155 case PSI_T_NAME:
156 if (define) {
157 name = true;
158 define = false;
159 }
160 break;
161 case PSI_T_LPAREN:
162 if (name) {
163 name = false;
164 if (!ws) {
165 /* mask special token for parser */
166 struct psi_token *no_ws = psi_token_copy(token);
167
168 no_ws->type = PSI_T_NO_WHITESPACE;
169 no_ws->text[0] = '\xA0';
170 psi_cpp_tokiter_ins_cur(cpp, no_ws);
171 continue;
172 }
173 }
174 /* no break */
175 default:
176 name = define = hash = eol = false;
177 break;
178 }
179
180 ws = false;
181 psi_cpp_tokiter_next(cpp);
182 }
183
184 return true;
185 }
186
187 static bool psi_cpp_stage2(struct psi_cpp *cpp)
188 {
189 struct psi_plist *parser_tokens = psi_plist_init((psi_plist_dtor) psi_token_free);
190
191 do {
192 bool is_eol = true, do_cpp = false, do_expansion = true, skip_paren = false, skip_all = false;
193
194 psi_cpp_tokiter_reset(cpp);
195
196 while (psi_cpp_tokiter_valid(cpp)) {
197 struct psi_token *current = psi_cpp_tokiter_current(cpp);
198
199 if (current->type == PSI_T_HASH) {
200 if (is_eol) {
201 do_cpp = true;
202 is_eol = false;
203 }
204 } else if (current->type == PSI_T_EOL) {
205 #if PSI_CPP_DEBUG
206 fprintf(stderr, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
207 #endif
208 is_eol = true;
209 skip_all = false;
210 do_expansion = true;
211 if (!do_cpp) {
212 psi_cpp_tokiter_del_cur(cpp, true);
213 continue;
214 }
215 } else {
216 is_eol = false;
217
218 if (do_cpp) {
219 switch (current->type) {
220 case PSI_T_DEFINE:
221 #if PSI_CPP_DEBUG
222 fprintf(stderr, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
223 #endif
224 do_expansion = false;
225 skip_all = true;
226 break;
227 case PSI_T_DEFINED:
228 skip_paren = true;
229 /* no break */
230 case PSI_T_IFDEF:
231 case PSI_T_IFNDEF:
232 case PSI_T_UNDEF:
233 #if PSI_CPP_DEBUG
234 fprintf(stderr, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
235 #endif
236 do_expansion = false;
237 break;
238 case PSI_T_LPAREN:
239
240 if (!skip_all) {
241 if (skip_paren) {
242 skip_paren = false;
243 } else {
244 do_expansion = true;
245 #if PSI_CPP_DEBUG
246 fprintf(stderr, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
247 #endif
248 }
249 }
250 break;
251 case PSI_T_NAME:
252 break;
253 default:
254 do_expansion = !skip_all;
255 #if PSI_CPP_DEBUG
256 fprintf(stderr, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion?"true":"false");
257 #endif
258 }
259 }
260 }
261
262 if (cpp->skip) {
263 /* FIXME: del_range */
264 if (!do_cpp) {
265 #if PSI_CPP_DEBUG
266 fprintf(stderr, "PSI: CPP skip ");
267 psi_token_dump(2, current);
268 #endif
269 psi_cpp_tokiter_del_cur(cpp, true);
270 continue;
271 }
272 }
273
274 if (do_expansion && current->type == PSI_T_NAME && psi_cpp_tokiter_defined(cpp)) {
275 bool expanded = false;
276
277 while (psi_cpp_tokiter_expand(cpp)) {
278 expanded = true;
279 }
280 if (expanded) {
281 continue;
282 }
283 }
284
285 if (do_cpp) {
286 parser_tokens = psi_plist_add(parser_tokens, &current);
287
288 if (is_eol) {
289 size_t processed = 0;
290 bool parsed = psi_parser_process(cpp->parser, parser_tokens, &processed);
291
292 /* EOL */
293 psi_plist_pop(parser_tokens, NULL);
294 psi_plist_clean(parser_tokens);
295 do_cpp = false;
296
297 if (!parsed) {
298 psi_plist_free(parser_tokens);
299 return false;
300 }
301 } else {
302 /* leave EOLs in the input stream, else we might end up
303 * with a hash not preceded with a new line after include */
304 psi_cpp_tokiter_del_cur(cpp, false);
305 }
306
307 #if PSI_CPP_DEBUG > 1
308 psi_cpp_tokiter_dump(2, cpp);
309 #endif
310
311 continue;
312 }
313
314 psi_cpp_tokiter_next(cpp);
315 }
316 } while (cpp->expanded);
317
318 psi_plist_free(parser_tokens);
319
320 return true;
321 }
322
323 bool psi_cpp_process(struct psi_cpp *cpp, struct psi_plist **tokens)
324 {
325 bool parsed = false;
326 struct psi_cpp temp = *cpp;
327
328 cpp->tokens = *tokens;
329 if (psi_cpp_stage1(cpp) && psi_cpp_stage2(cpp)) {
330 parsed = true;
331 }
332 *tokens = cpp->tokens;
333
334 if (temp.tokens) {
335 cpp->tokens = temp.tokens;
336 cpp->index = temp.index;
337 }
338
339 return parsed;
340 }
341
342 bool psi_cpp_defined(struct psi_cpp *cpp, struct psi_token *tok)
343 {
344 bool defined;
345
346 if (tok->type == PSI_T_NAME) {
347 defined = zend_hash_str_exists(&cpp->defs, tok->text, tok->size);
348 } else {
349 defined = false;
350 }
351
352 #if PSI_CPP_DEBUG
353 fprintf(stderr, "PSI: CPP defined -> %s ", defined ? "true" : "false");
354 psi_token_dump(2, tok);
355 #endif
356
357 return defined;
358 }
359
360 void psi_cpp_define(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
361 {
362 struct psi_cpp_macro_decl *old = zend_hash_str_find_ptr(&cpp->defs, decl->token->text, decl->token->size);
363
364 if (old && !psi_cpp_macro_decl_equal(old, decl)) {
365 cpp->parser->error(PSI_DATA(cpp->parser), decl->token, PSI_WARNING,
366 "'%s' redefined", decl->token->text);
367 cpp->parser->error(PSI_DATA(cpp->parser), old->token, PSI_WARNING,
368 "'%s' previously defined", old->token->text);
369 }
370 zend_hash_str_update_ptr(&cpp->defs, decl->token->text, decl->token->size, decl);
371 }
372
373 bool psi_cpp_undef(struct psi_cpp *cpp, struct psi_token *tok)
374 {
375 return SUCCESS == zend_hash_str_del(&cpp->defs, tok->text, tok->size);
376 }
377
378 bool psi_cpp_if(struct psi_cpp *cpp, struct psi_cpp_exp *exp)
379 {
380 if (!psi_num_exp_validate(PSI_DATA(cpp->parser), exp->data.num, NULL, NULL, NULL, NULL, NULL)) {
381 return false;
382 }
383 if (!psi_long_num_exp(exp->data.num, NULL, &cpp->defs)) {
384 return false;
385 }
386 return true;
387 }
388
389 static inline bool try_include(struct psi_cpp *cpp, const char *path, bool *parsed)
390 {
391 struct psi_parser_input *include;
392
393 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP include trying %s\n", path);
394
395 include = psi_parser_open_file(cpp->parser, path, false);
396 if (include) {
397 struct psi_plist *tokens;
398
399 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP include scanning %s\n", path);
400
401 tokens = psi_parser_scan(cpp->parser, include);
402 if (tokens) {
403 *parsed = psi_cpp_process(cpp, &tokens);
404
405 if (*parsed) {
406 ++cpp->expanded;
407 psi_cpp_tokiter_ins_range(cpp, cpp->index,
408 psi_plist_count(tokens), psi_plist_eles(tokens));
409 free(tokens);
410 } else {
411 psi_plist_free(tokens);
412 }
413 }
414 free(include);
415
416 zend_hash_str_add_empty_element(&cpp->once, path, strlen(path));
417 return true;
418 }
419 return false;
420 }
421
422 bool psi_cpp_include(struct psi_cpp *cpp, const char *file, unsigned flags)
423 {
424 bool parsed = false;
425 int f_len = strlen(file);
426
427 if (!(flags & PSI_CPP_INCLUDE_NEXT) || *file == '/') {
428 /* first try as is, full or relative path */
429 if ((flags & PSI_CPP_INCLUDE_ONCE) && zend_hash_str_exists(&cpp->once, file, f_len)) {
430 return true;
431 }
432 if (try_include(cpp, file, &parsed)) {
433 /* found */
434 return parsed;
435 }
436 }
437
438 /* look through search paths */
439 if (*file != '/') {
440 char path[PATH_MAX];
441 const char *sep;
442 int p_len;
443
444 if ((flags & PSI_CPP_INCLUDE_NEXT) && cpp->search) {
445 if ((sep = strchr(cpp->search, ':'))) {
446 cpp->search = sep + 1;
447 } else {
448 /* point to end of string */
449 cpp->search += strlen(cpp->search);
450 }
451 }
452
453 if (!(flags & PSI_CPP_INCLUDE_NEXT) || !cpp->search) {
454 cpp->search = PSI_G(search_path);
455 }
456
457 do {
458 int d_len;
459
460 sep = strchr(cpp->search, ':');
461 d_len = sep ? sep - cpp->search : strlen(cpp->search);
462
463 if (PATH_MAX > (p_len = snprintf(path, PATH_MAX, "%.*s/%.*s", d_len, cpp->search, f_len, file))) {
464 if ((flags & PSI_CPP_INCLUDE_ONCE) && zend_hash_str_exists(&cpp->once, path, p_len)) {
465 return true;
466 }
467 if (try_include(cpp, path, &parsed)) {
468 break;
469 }
470 }
471
472 if (sep) {
473 cpp->search = sep + 1;
474 }
475 } while (sep);
476 }
477
478 return parsed;
479 }