travis: update
[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 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #else
29 # include "php_config.h"
30 #endif
31
32 #include "php_psi.h"
33
34 #include <libgen.h>
35
36 #include "cpp.h"
37 #include "parser.h"
38 #include "debug.h"
39
40 #define PSI_CPP_SEARCH
41 #define PSI_CPP_PREDEF
42 #include "php_psi_predef.h"
43
44 static HashTable psi_cpp_defaults;
45 static HashTable psi_cpp_pragmas;
46
47 typedef bool (*psi_cpp_pragma_func)(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl);
48
49 static bool psi_cpp_pragma_once(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
50 {
51 return NULL != zend_hash_add_empty_element(&cpp->once, decl->token->file);
52 }
53
54 static bool psi_cpp_pragma_lib(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
55 {
56 struct psi_token *lib = NULL;
57 zend_string *libname;
58
59 if (!psi_plist_get(decl->tokens, 0, &lib)
60 || !lib || lib->type != PSI_T_QUOTED_STRING) {
61 return false;
62 }
63
64 libname = zend_string_copy(lib->text);
65 cpp->parser->file.libnames = psi_plist_add(cpp->parser->file.libnames,
66 &libname);
67 return true;
68 }
69
70 static bool psi_cpp_pragma_blacklist_decl(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
71 {
72 struct psi_token *name;
73
74 if (!psi_plist_get(decl->tokens, 0, &name)
75 || !name || name->type != PSI_T_QUOTED_STRING) {
76 return false;
77 }
78
79 psi_blacklist_add_decl(name->text->val, name->text->len);
80 return true;
81 }
82
83 static bool psi_cpp_pragma_blacklist_var(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
84 {
85 struct psi_token *name;
86
87 if (!psi_plist_get(decl->tokens, 0, &name)
88 || !name || name->type != PSI_T_QUOTED_STRING) {
89 return false;
90 }
91
92 psi_blacklist_add_var(name->text->val, name->text->len);
93 return true;
94 }
95
96 PHP_MINIT_FUNCTION(psi_cpp);
97 PHP_MINIT_FUNCTION(psi_cpp)
98 {
99 struct psi_parser parser;
100 struct psi_parser_input *predef;
101
102 PSI_G(search_path) = pemalloc(strlen(PSI_G(directory)) + strlen(psi_cpp_search) + 1 + 1, 1);
103 sprintf(PSI_G(search_path), "%s:%s", PSI_G(directory), psi_cpp_search);
104
105 if (!psi_parser_init(&parser, psi_error_wrapper, PSI_SILENT)) {
106 return FAILURE;
107 }
108
109 if (!(predef = psi_parser_open_string(&parser, psi_cpp_predef, sizeof(psi_cpp_predef) - 1))) {
110 psi_parser_dtor(&parser);
111 return FAILURE;
112 }
113
114 if (!psi_parser_parse(&parser, predef)) {
115 psi_parser_input_free(&predef);
116 psi_parser_dtor(&parser);
117 return FAILURE;
118 }
119 psi_parser_input_free(&predef);
120
121 zend_hash_init(&psi_cpp_defaults, 0, NULL, NULL, 1);
122 zend_hash_copy(&psi_cpp_defaults, &parser.preproc->defs, NULL);
123
124 psi_parser_dtor(&parser);
125
126 #define PSI_CPP_PRAGMA(name) \
127 zend_hash_str_add_ptr(&psi_cpp_pragmas, #name, strlen(#name), psi_cpp_pragma_ ## name)
128 zend_hash_init(&psi_cpp_pragmas, 0, NULL, NULL, 1);
129 PSI_CPP_PRAGMA(once);
130 PSI_CPP_PRAGMA(lib);
131 PSI_CPP_PRAGMA(blacklist_decl);
132 PSI_CPP_PRAGMA(blacklist_var);
133
134 return SUCCESS;
135 }
136
137 PHP_MSHUTDOWN_FUNCTION(psi_cpp);
138 PHP_MSHUTDOWN_FUNCTION(psi_cpp)
139 {
140 struct psi_cpp_macro_decl *macro;
141
142 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults, macro)
143 {
144 psi_cpp_macro_decl_free(&macro);
145 }
146 ZEND_HASH_FOREACH_END();
147
148 zend_hash_destroy(&psi_cpp_defaults);
149
150 return SUCCESS;
151 }
152
153 static void free_cpp_def(zval *p)
154 {
155 if (Z_TYPE_P(p) == IS_PTR) {
156 struct psi_cpp_macro_decl *macro = Z_PTR_P(p);
157
158 if (!zend_hash_exists(&psi_cpp_defaults, macro->token->text)) {
159 psi_cpp_macro_decl_free(&macro);
160 }
161 }
162 }
163
164 struct psi_cpp *psi_cpp_init(struct psi_parser *P)
165 {
166 struct psi_cpp *cpp = pecalloc(1, sizeof(*cpp), 1);
167
168 cpp->parser = P;
169 zend_hash_init(&cpp->once, 0, NULL, NULL, 1);
170 zend_hash_init(&cpp->defs, 0, NULL, free_cpp_def, 1);
171 zend_hash_copy(&cpp->defs, &psi_cpp_defaults, NULL);
172 zend_hash_init(&cpp->expanding, 0, NULL, NULL, 1);
173
174 return cpp;
175 }
176
177 static char *include_flavor[] = {
178 "include",
179 "include next",
180 "include once"
181 };
182
183 void psi_cpp_free(struct psi_cpp **cpp_ptr)
184 {
185 if (*cpp_ptr) {
186 struct psi_cpp *cpp = *cpp_ptr;
187
188 *cpp_ptr = NULL;
189 zend_hash_destroy(&cpp->defs);
190 zend_hash_destroy(&cpp->once);
191 zend_hash_destroy(&cpp->expanding);
192 free(cpp);
193 }
194 }
195
196 static bool psi_cpp_stage1(struct psi_cpp *cpp)
197 {
198 bool name = false, define = false, hash = false, eol = true, esc = false, ws = false;
199
200 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP %s\n", "stage1");
201
202 psi_cpp_tokiter_reset(cpp);
203 while (psi_cpp_tokiter_valid(cpp)) {
204 struct psi_token *token = psi_cpp_tokiter_current(cpp);
205
206 /* strip comments and attributes */
207 if (token->type == PSI_T_COMMENT
208 || token->type == PSI_T_CPP_ATTRIBUTE) {
209 psi_cpp_tokiter_del_cur(cpp, true);
210 continue;
211 }
212
213 /* line continuations */
214 if (token->type == PSI_T_EOL) {
215 if (esc) {
216 psi_cpp_tokiter_del_prev(cpp, true);
217 psi_cpp_tokiter_del_cur(cpp, true);
218 esc = false;
219 continue;
220 }
221 } else if (token->type == PSI_T_BSLASH) {
222 esc = !esc;
223 } else {
224 esc = false;
225 }
226
227 /* this whole turf is needed to distinct between:
228 * #define foo (1,2,3)
229 * #define foo(a,b,c)
230 */
231
232 if (token->type == PSI_T_WHITESPACE) {
233 if (name) {
234 name = false;
235 }
236 ws = true;
237 psi_cpp_tokiter_del_cur(cpp, true);
238 continue;
239 }
240
241 switch (token->type) {
242 case PSI_T_EOL:
243 eol = true;
244 break;
245 case PSI_T_HASH:
246 if (eol) {
247 hash = true;
248 eol = false;
249 }
250 break;
251 case PSI_T_DEFINE:
252 if (hash) {
253 define = true;
254 hash = false;
255 }
256 break;
257 case PSI_T_NAME:
258 if (define) {
259 name = true;
260 define = false;
261 }
262 break;
263 case PSI_T_LPAREN:
264 if (name) {
265 name = false;
266 if (!ws) {
267 /* mask special token for parser */
268 struct psi_token *no_ws = psi_token_copy(token);
269
270 no_ws->type = PSI_T_NO_WHITESPACE;
271 zend_string_release(no_ws->text);
272 no_ws->text = psi_string_init_interned("\xA0", 1, 1);
273 psi_cpp_tokiter_add(cpp, no_ws);
274 continue;
275 }
276 }
277 /* no break */
278 default:
279 name = define = hash = eol = false;
280 break;
281 }
282
283 ws = false;
284 psi_cpp_tokiter_add_cur(cpp);
285 psi_cpp_tokiter_next(cpp);
286 }
287
288 return true;
289 }
290
291 static bool psi_cpp_stage2(struct psi_cpp *cpp)
292 {
293 bool is_eol = true, do_expansion = true, skip_paren = false, skip_all = false;
294
295 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP %s\n", "stage2");
296
297 psi_cpp_tokiter_reset(cpp);
298 while (psi_cpp_tokiter_valid(cpp)) {
299 struct psi_token *current = psi_cpp_tokiter_current(cpp);
300
301 if (current->type == PSI_T_HASH) {
302 if (is_eol) {
303 cpp->do_cpp = true;
304 is_eol = false;
305 }
306 } else if (current->type == PSI_T_EOL) {
307 #if PSI_CPP_DEBUG
308 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
309 #endif
310 is_eol = true;
311 skip_all = false;
312 do_expansion = true;
313 if (!cpp->do_cpp) {
314 psi_cpp_tokiter_del_cur(cpp, true);
315 continue;
316 }
317 } else {
318 is_eol = false;
319
320 if (cpp->do_cpp) {
321 switch (current->type) {
322 case PSI_T_DEFINE:
323 #if PSI_CPP_DEBUG
324 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
325 #endif
326 do_expansion = false;
327 skip_all = true;
328 break;
329 case PSI_T_DEFINED:
330 skip_paren = true;
331 /* no break */
332 case PSI_T_IFDEF:
333 case PSI_T_IFNDEF:
334 case PSI_T_UNDEF:
335 #if PSI_CPP_DEBUG
336 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
337 #endif
338 do_expansion = false;
339 break;
340 case PSI_T_LPAREN:
341
342 if (!skip_all) {
343 if (skip_paren) {
344 skip_paren = false;
345 } else {
346 do_expansion = true;
347 #if PSI_CPP_DEBUG
348 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
349 #endif
350 }
351 }
352 break;
353 case PSI_T_NAME:
354 break;
355 default:
356 do_expansion = !skip_all;
357 #if PSI_CPP_DEBUG
358 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion?"true":"false");
359 #endif
360 }
361 }
362 }
363
364 if (cpp->skip) {
365 if (!cpp->do_cpp) {
366 #if PSI_CPP_DEBUG
367 PSI_DEBUG_LOCK(cpp->parser,
368 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP skip ");
369 PSI_DEBUG_DUMP(cpp->parser, psi_token_dump, current);
370 );
371 #endif
372 psi_cpp_tokiter_del_cur(cpp, true);
373 continue;
374 }
375 }
376
377 if (do_expansion && psi_cpp_defined(cpp, current)) {
378 if (psi_cpp_tokiter_expand(cpp)) {
379 continue;
380 }
381 }
382
383 psi_cpp_tokiter_add_cur(cpp);
384
385 if (cpp->do_cpp && is_eol) {
386 size_t processed = 0;
387 bool parsed;
388
389 cpp->do_cpp = false;
390 parsed = psi_parser_process(cpp->parser, cpp->tokens.exec, &processed);
391 psi_plist_clean(cpp->tokens.exec);
392
393 if (!parsed) {
394 psi_plist_free(cpp->tokens.exec);
395 return false;
396 }
397
398 #if PSI_CPP_DEBUG > 1
399 PSI_DEBUG_DUMP(cpp->parser, psi_cpp_tokiter_dump, cpp);
400 #endif
401 }
402
403 psi_cpp_tokiter_next(cpp);
404 }
405
406 psi_plist_free(cpp->tokens.exec);
407 cpp->tokens.exec = NULL;
408
409 return true;
410 }
411
412 bool psi_cpp_process(struct psi_cpp *cpp, struct psi_plist **tokens,
413 struct psi_token *expanding)
414 {
415 bool parsed = false;
416 struct psi_cpp temp = *cpp;
417
418 cpp->tokens.iter = *tokens;
419 cpp->tokens.next = NULL;
420 cpp->tokens.exec = NULL;
421
422 if (expanding) {
423 zend_hash_add_empty_element(&cpp->expanding, expanding->text);
424 }
425 if (psi_cpp_stage1(cpp) && psi_cpp_stage2(cpp)) {
426 parsed = true;
427 }
428 if (expanding) {
429 zend_hash_del(&cpp->expanding, expanding->text);
430 }
431
432 *tokens = cpp->tokens.next;
433 psi_plist_free(cpp->tokens.iter);
434 if (cpp->tokens.exec) {
435 assert(!psi_plist_count(cpp->tokens.exec));
436 psi_plist_free(cpp->tokens.exec);
437 }
438
439 cpp->tokens = temp.tokens;
440 cpp->index = temp.index;
441 cpp->skip = temp.skip;
442 cpp->level = temp.level;
443 cpp->seen = temp.seen;
444 cpp->do_cpp = temp.do_cpp;
445
446 return parsed;
447 }
448
449 bool psi_cpp_defined(struct psi_cpp *cpp, struct psi_token *tok)
450 {
451 bool defined = false;
452
453 if (tok->type == PSI_T_NAME) {
454 if (psi_builtin_exists(tok->text)) {
455 defined = true;
456 } else if (!zend_hash_exists(&cpp->expanding, tok->text)) {
457 defined = zend_hash_exists(&cpp->defs, tok->text);
458 }
459 #if PSI_CPP_DEBUG
460 PSI_DEBUG_LOCK(cpp->parser,
461 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP defined -> %s ", defined ? "true" : "false");
462 if (defined) {
463 struct psi_cpp_macro_decl *macro = zend_hash_find_ptr(&cpp->defs, tok->text);
464 if (macro) {
465 PSI_DEBUG_PRINT(cpp->parser, " @ %s:%u ", macro->token->file->val, macro->token->line);
466 }
467 } else {
468 zend_string *key;
469
470 PSI_DEBUG_PRINT(cpp->parser, " expanding=");
471 ZEND_HASH_FOREACH_STR_KEY(&cpp->expanding, key)
472 {
473 PSI_DEBUG_PRINT(cpp->parser, "%s,", key->val);
474 }
475 ZEND_HASH_FOREACH_END();
476 PSI_DEBUG_PRINT(cpp->parser, "\t");
477 }
478 PSI_DEBUG_DUMP(cpp->parser, psi_token_dump, tok);
479 );
480 #endif
481 }
482
483 return defined;
484 }
485
486 void psi_cpp_define(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
487 {
488 struct psi_cpp_macro_decl *old = zend_hash_find_ptr(&cpp->defs, decl->token->text);
489
490 if (old && !psi_cpp_macro_decl_equal(old, decl)) {
491 cpp->parser->error(PSI_DATA(cpp->parser), decl->token, PSI_WARNING,
492 "'%s' redefined", decl->token->text->val);
493 cpp->parser->error(PSI_DATA(cpp->parser), old->token, PSI_WARNING,
494 "'%s' previously defined", old->token->text->val);
495 }
496 #if PSI_CPP_DEBUG
497 PSI_DEBUG_LOCK(cpp->parser,
498 if (decl->exp) {
499 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP MACRO num_exp -> ");
500 } else {
501 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP MACRO decl -> ");
502 }
503 PSI_DEBUG_DUMP(cpp->parser, psi_cpp_macro_decl_dump, decl);
504 PSI_DEBUG_PRINT(cpp->parser, "\n");
505 );
506 #endif
507 zend_hash_update_ptr(&cpp->defs, decl->token->text, decl);
508 }
509
510 bool psi_cpp_undef(struct psi_cpp *cpp, struct psi_token *tok)
511 {
512 return SUCCESS == zend_hash_del(&cpp->defs, tok->text);
513 }
514
515 bool psi_cpp_if(struct psi_cpp *cpp, struct psi_cpp_exp *exp)
516 {
517 struct psi_validate_scope scope = {0};
518 unsigned flags = cpp->parser->flags;
519
520 scope.cpp = cpp;
521 cpp->parser->flags |= PSI_SILENT;
522 if (!psi_num_exp_validate(PSI_DATA(cpp->parser), exp->data.num, &scope)) {
523 cpp->parser->flags = flags;
524 return false;
525 }
526 cpp->parser->flags = flags;
527 if (!psi_num_exp_get_long(exp->data.num, NULL, cpp)) {
528 return false;
529 }
530 return true;
531 }
532
533 bool psi_cpp_pragma(struct psi_cpp *cpp, struct psi_cpp_macro_decl *decl)
534 {
535 psi_cpp_pragma_func fn;
536
537 fn = zend_hash_find_ptr(&psi_cpp_pragmas, decl->token->text);
538 if (!fn) {
539 return false;
540 }
541
542 return fn(cpp, decl);
543 }
544
545 bool psi_cpp_include(struct psi_cpp *cpp, const struct psi_token *file, unsigned flags)
546 {
547 bool parsed = false;
548 char path[PATH_MAX];
549 struct psi_plist *tokens;
550 struct psi_parser_input *include;
551
552 if (!psi_cpp_has_include(cpp, file, flags, path)) {
553 return false;
554 }
555
556 if (flags & PSI_CPP_INCLUDE_ONCE) {
557 if (zend_hash_str_exists(&cpp->once, path, strlen(path))) {
558 return true;
559 }
560 }
561
562 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP %s opening %s\n",
563 include_flavor[flags], path);
564
565 include = psi_parser_open_file(cpp->parser, path, false);
566 if (!include) {
567 return false;
568 }
569
570 zend_hash_str_add_empty_element(&cpp->once, path, strlen(path));
571
572 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP include scanning %s\n", path);
573
574 tokens = psi_parser_scan(cpp->parser, include);
575 psi_parser_input_free(&include);
576
577 if (!tokens) {
578 return false;
579 }
580
581 ++cpp->include_level;
582 parsed = psi_cpp_process(cpp, &tokens, NULL);
583 --cpp->include_level;
584
585 if (!parsed) {
586 psi_plist_free(tokens);
587 return false;
588 }
589
590 psi_cpp_tokiter_add_range(cpp, psi_plist_count(tokens), psi_plist_eles(tokens));
591 free(tokens);
592
593 ++cpp->expanded;
594 return true;
595 }
596
597 #ifndef HAVE_EACCESS
598 # define eaccess access
599 #endif
600 bool psi_cpp_has_include(struct psi_cpp *cpp, const struct psi_token *file, unsigned flags, char *path)
601 {
602 char temp[PATH_MAX];
603
604 if (!path) {
605 path = temp;
606 }
607
608 if (file->type == PSI_T_QUOTED_STRING && (!(flags & PSI_CPP_INCLUDE_NEXT) || file->text->val[0] == '/')) {
609 /* first try as is, full or relative path */
610 if (file->text->val[0] == '/') {
611 path = file->text->val;
612 } else {
613 char *dir;
614 size_t len;
615
616 strncpy(path, file->file->val, PATH_MAX);
617
618 dir = dirname(path);
619 len = strlen(dir);
620
621 assert(len + file->text->len + 1 < PATH_MAX);
622
623 memmove(path, dir, len);
624 path[len] = '/';
625 memcpy(&(path)[len + 1], file->text->val, file->text->len + 1);
626 }
627
628 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP %s trying %s\n",
629 include_flavor[flags], path);
630 if (0 == eaccess(path, R_OK)) {
631 return true;
632 }
633 }
634
635 /* look through search paths */
636 if (file->text->val[0] != '/') {
637 const char *sep;
638 int p_len;
639
640 if ((flags & PSI_CPP_INCLUDE_NEXT) && cpp->search) {
641 if ((sep = strchr(cpp->search, ':'))) {
642 cpp->search = sep + 1;
643 } else {
644 /* point to end of string */
645 cpp->search += strlen(cpp->search);
646 }
647 }
648
649 if (!(flags & PSI_CPP_INCLUDE_NEXT)) {
650 cpp->search = PSI_G(search_path);
651 }
652
653 do {
654 int d_len;
655
656 sep = strchr(cpp->search, ':');
657 d_len = sep ? sep - cpp->search : strlen(cpp->search);
658
659 if (PATH_MAX > (p_len = snprintf(path, PATH_MAX, "%.*s/%.*s", d_len, cpp->search, (int) file->text->len, file->text->val))) {
660 PSI_DEBUG_PRINT(cpp->parser, "PSI: CPP %s trying %s\n",
661 include_flavor[flags], path);
662 if (0 == eaccess(path, R_OK)) {
663 return true;
664 }
665 }
666
667 if (sep) {
668 cpp->search = sep + 1;
669 }
670 } while (sep);
671 }
672
673 return false;
674 }