1 /*******************************************************************************
2 Copyright (c) 2017, Michael Wallner <mike@php.net>.
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
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.
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 *******************************************************************************/
29 # include "php_config.h"
40 #define PSI_CPP_SEARCH
41 #define PSI_CPP_PREDEF
42 #include "php_psi_predef.h"
44 static HashTable psi_cpp_defaults
;
45 static HashTable psi_cpp_pragmas
;
47 typedef bool (*psi_cpp_pragma_func
)(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
);
49 static bool psi_cpp_pragma_once(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
51 return NULL
!= zend_hash_add_empty_element(&cpp
->once
, decl
->token
->file
);
54 static bool psi_cpp_pragma_lib(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
56 struct psi_token
*lib
= NULL
;
59 if (!psi_plist_get(decl
->tokens
, 0, &lib
)
60 || !lib
|| lib
->type
!= PSI_T_QUOTED_STRING
) {
64 cpp
->parser
->file
.libnames
= psi_plist_add(cpp
->parser
->file
.libnames
,
69 PHP_MINIT_FUNCTION(psi_cpp
);
70 PHP_MINIT_FUNCTION(psi_cpp
)
72 struct psi_parser parser
;
73 struct psi_parser_input
*predef
;
75 PSI_G(search_path
) = pemalloc(strlen(PSI_G(directory
)) + strlen(psi_cpp_search
) + 1 + 1, 1);
76 sprintf(PSI_G(search_path
), "%s:%s", PSI_G(directory
), psi_cpp_search
);
78 if (!psi_parser_init(&parser
, psi_error_wrapper
, PSI_SILENT
)) {
82 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
83 psi_parser_dtor(&parser
);
87 if (!psi_parser_parse(&parser
, predef
)) {
88 psi_parser_input_free(&predef
);
89 psi_parser_dtor(&parser
);
92 psi_parser_input_free(&predef
);
94 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
95 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
97 psi_parser_dtor(&parser
);
99 #define PSI_CPP_PRAGMA(name) \
100 zend_hash_str_add_ptr(&psi_cpp_pragmas, #name, strlen(#name), psi_cpp_pragma_ ## name)
101 zend_hash_init(&psi_cpp_pragmas
, 0, NULL
, NULL
, 1);
102 PSI_CPP_PRAGMA(once
);
108 PHP_MSHUTDOWN_FUNCTION(psi_cpp
);
109 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
111 struct psi_cpp_macro_decl
*macro
;
113 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
115 psi_cpp_macro_decl_free(¯o
);
117 ZEND_HASH_FOREACH_END();
119 zend_hash_destroy(&psi_cpp_defaults
);
124 static void free_cpp_def(zval
*p
)
126 if (Z_TYPE_P(p
) == IS_PTR
) {
127 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
129 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
130 psi_cpp_macro_decl_free(¯o
);
135 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
137 struct psi_cpp
*cpp
= pecalloc(1, sizeof(*cpp
), 1);
140 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
141 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
142 zend_hash_copy(&cpp
->defs
, &psi_cpp_defaults
, NULL
);
143 zend_hash_init(&cpp
->expanding
, 0, NULL
, NULL
, 1);
148 static char *include_flavor
[] = {
154 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
157 struct psi_cpp
*cpp
= *cpp_ptr
;
160 zend_hash_destroy(&cpp
->defs
);
161 zend_hash_destroy(&cpp
->once
);
162 zend_hash_destroy(&cpp
->expanding
);
167 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
169 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
171 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
173 psi_cpp_tokiter_reset(cpp
);
174 while (psi_cpp_tokiter_valid(cpp
)) {
175 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
177 /* strip comments and attributes */
178 if (token
->type
== PSI_T_COMMENT
179 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
180 psi_cpp_tokiter_del_cur(cpp
, true);
184 /* line continuations */
185 if (token
->type
== PSI_T_EOL
) {
187 psi_cpp_tokiter_del_prev(cpp
, true);
188 psi_cpp_tokiter_del_cur(cpp
, true);
192 } else if (token
->type
== PSI_T_BSLASH
) {
198 /* this whole turf is needed to distinct between:
199 * #define foo (1,2,3)
203 if (token
->type
== PSI_T_WHITESPACE
) {
208 psi_cpp_tokiter_del_cur(cpp
, true);
212 switch (token
->type
) {
238 /* mask special token for parser */
239 struct psi_token
*no_ws
= psi_token_copy(token
);
241 no_ws
->type
= PSI_T_NO_WHITESPACE
;
242 zend_string_release(no_ws
->text
);
243 no_ws
->text
= psi_string_init_interned("\xA0", 1, 1);
244 psi_cpp_tokiter_add(cpp
, no_ws
);
250 name
= define
= hash
= eol
= false;
255 psi_cpp_tokiter_add_cur(cpp
);
256 psi_cpp_tokiter_next(cpp
);
262 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
264 bool is_eol
= true, do_expansion
= true, skip_paren
= false, skip_all
= false;
266 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
268 psi_cpp_tokiter_reset(cpp
);
269 while (psi_cpp_tokiter_valid(cpp
)) {
270 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
272 if (current
->type
== PSI_T_HASH
) {
277 } else if (current
->type
== PSI_T_EOL
) {
279 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
285 psi_cpp_tokiter_del_cur(cpp
, true);
292 switch (current
->type
) {
295 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
297 do_expansion
= false;
307 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
309 do_expansion
= false;
319 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
327 do_expansion
= !skip_all
;
329 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
338 PSI_DEBUG_LOCK(cpp
->parser
,
339 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP skip ");
340 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, current
);
343 psi_cpp_tokiter_del_cur(cpp
, true);
348 if (do_expansion
&& psi_cpp_defined(cpp
, current
)) {
349 if (psi_cpp_tokiter_expand(cpp
)) {
354 psi_cpp_tokiter_add_cur(cpp
);
356 if (cpp
->do_cpp
&& is_eol
) {
357 size_t processed
= 0;
361 parsed
= psi_parser_process(cpp
->parser
, cpp
->tokens
.exec
, &processed
);
362 psi_plist_clean(cpp
->tokens
.exec
);
365 psi_plist_free(cpp
->tokens
.exec
);
369 #if PSI_CPP_DEBUG > 1
370 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
374 psi_cpp_tokiter_next(cpp
);
377 psi_plist_free(cpp
->tokens
.exec
);
378 cpp
->tokens
.exec
= NULL
;
383 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
,
384 struct psi_token
*expanding
)
387 struct psi_cpp temp
= *cpp
;
389 cpp
->tokens
.iter
= *tokens
;
390 cpp
->tokens
.next
= NULL
;
391 cpp
->tokens
.exec
= NULL
;
394 zend_hash_add_empty_element(&cpp
->expanding
, expanding
->text
);
396 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
400 zend_hash_del(&cpp
->expanding
, expanding
->text
);
403 *tokens
= cpp
->tokens
.next
;
404 psi_plist_free(cpp
->tokens
.iter
);
405 if (cpp
->tokens
.exec
) {
406 assert(!psi_plist_count(cpp
->tokens
.exec
));
407 psi_plist_free(cpp
->tokens
.exec
);
410 cpp
->tokens
= temp
.tokens
;
411 cpp
->index
= temp
.index
;
412 cpp
->skip
= temp
.skip
;
413 cpp
->level
= temp
.level
;
414 cpp
->seen
= temp
.seen
;
415 cpp
->do_cpp
= temp
.do_cpp
;
420 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
422 bool defined
= false;
424 if (tok
->type
== PSI_T_NAME
) {
425 if (psi_builtin_exists(tok
->text
)) {
427 } else if (!zend_hash_exists(&cpp
->expanding
, tok
->text
)) {
428 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
);
431 PSI_DEBUG_LOCK(cpp
->parser
,
432 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
434 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
436 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
441 PSI_DEBUG_PRINT(cpp
->parser
, " expanding=");
442 ZEND_HASH_FOREACH_STR_KEY(&cpp
->expanding
, key
)
444 PSI_DEBUG_PRINT(cpp
->parser
, "%s,", key
->val
);
446 ZEND_HASH_FOREACH_END();
447 PSI_DEBUG_PRINT(cpp
->parser
, "\t");
449 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
457 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
459 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
461 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
462 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
463 "'%s' redefined", decl
->token
->text
->val
);
464 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
465 "'%s' previously defined", old
->token
->text
->val
);
468 PSI_DEBUG_LOCK(cpp
->parser
,
470 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
472 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
474 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
475 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
478 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
481 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
483 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
486 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
488 struct psi_validate_scope scope
= {0};
491 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
494 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
500 bool psi_cpp_pragma(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
502 psi_cpp_pragma_func
*fn
;
504 fn
= zend_hash_find_ptr(&psi_cpp_pragmas
, decl
->token
->text
);
509 return fn(cpp
, decl
);
512 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
516 struct psi_plist
*tokens
;
517 struct psi_parser_input
*include
;
519 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
523 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
524 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
529 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
530 include_flavor
[flags
], path
);
532 include
= psi_parser_open_file(cpp
->parser
, path
, false);
537 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
539 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
541 tokens
= psi_parser_scan(cpp
->parser
, include
);
542 psi_parser_input_free(&include
);
548 ++cpp
->include_level
;
549 parsed
= psi_cpp_process(cpp
, &tokens
, NULL
);
550 --cpp
->include_level
;
553 psi_plist_free(tokens
);
557 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
565 # define eaccess access
567 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
575 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
576 /* first try as is, full or relative path */
577 if (file
->text
->val
[0] == '/') {
578 path
= file
->text
->val
;
583 strncpy(path
, file
->file
->val
, PATH_MAX
);
588 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
590 memmove(path
, dir
, len
);
592 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
595 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
596 include_flavor
[flags
], path
);
597 if (0 == eaccess(path
, R_OK
)) {
602 /* look through search paths */
603 if (file
->text
->val
[0] != '/') {
607 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
608 if ((sep
= strchr(cpp
->search
, ':'))) {
609 cpp
->search
= sep
+ 1;
611 /* point to end of string */
612 cpp
->search
+= strlen(cpp
->search
);
616 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
617 cpp
->search
= PSI_G(search_path
);
623 sep
= strchr(cpp
->search
, ':');
624 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
626 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
627 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
628 include_flavor
[flags
], path
);
629 if (0 == eaccess(path
, R_OK
)) {
635 cpp
->search
= sep
+ 1;