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 libname
= zend_string_copy(lib
->text
);
65 cpp
->parser
->file
.libnames
= psi_plist_add(cpp
->parser
->file
.libnames
,
70 PHP_MINIT_FUNCTION(psi_cpp
);
71 PHP_MINIT_FUNCTION(psi_cpp
)
73 struct psi_parser parser
;
74 struct psi_parser_input
*predef
;
76 PSI_G(search_path
) = pemalloc(strlen(PSI_G(directory
)) + strlen(psi_cpp_search
) + 1 + 1, 1);
77 sprintf(PSI_G(search_path
), "%s:%s", PSI_G(directory
), psi_cpp_search
);
79 if (!psi_parser_init(&parser
, psi_error_wrapper
, PSI_SILENT
)) {
83 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
84 psi_parser_dtor(&parser
);
88 if (!psi_parser_parse(&parser
, predef
)) {
89 psi_parser_input_free(&predef
);
90 psi_parser_dtor(&parser
);
93 psi_parser_input_free(&predef
);
95 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
96 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
98 psi_parser_dtor(&parser
);
100 #define PSI_CPP_PRAGMA(name) \
101 zend_hash_str_add_ptr(&psi_cpp_pragmas, #name, strlen(#name), psi_cpp_pragma_ ## name)
102 zend_hash_init(&psi_cpp_pragmas
, 0, NULL
, NULL
, 1);
103 PSI_CPP_PRAGMA(once
);
109 PHP_MSHUTDOWN_FUNCTION(psi_cpp
);
110 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
112 struct psi_cpp_macro_decl
*macro
;
114 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
116 psi_cpp_macro_decl_free(¯o
);
118 ZEND_HASH_FOREACH_END();
120 zend_hash_destroy(&psi_cpp_defaults
);
125 static void free_cpp_def(zval
*p
)
127 if (Z_TYPE_P(p
) == IS_PTR
) {
128 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
130 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
131 psi_cpp_macro_decl_free(¯o
);
136 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
138 struct psi_cpp
*cpp
= pecalloc(1, sizeof(*cpp
), 1);
141 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
142 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
143 zend_hash_copy(&cpp
->defs
, &psi_cpp_defaults
, NULL
);
144 zend_hash_init(&cpp
->expanding
, 0, NULL
, NULL
, 1);
149 static char *include_flavor
[] = {
155 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
158 struct psi_cpp
*cpp
= *cpp_ptr
;
161 zend_hash_destroy(&cpp
->defs
);
162 zend_hash_destroy(&cpp
->once
);
163 zend_hash_destroy(&cpp
->expanding
);
168 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
170 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
172 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
174 psi_cpp_tokiter_reset(cpp
);
175 while (psi_cpp_tokiter_valid(cpp
)) {
176 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
178 /* strip comments and attributes */
179 if (token
->type
== PSI_T_COMMENT
180 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
181 psi_cpp_tokiter_del_cur(cpp
, true);
185 /* line continuations */
186 if (token
->type
== PSI_T_EOL
) {
188 psi_cpp_tokiter_del_prev(cpp
, true);
189 psi_cpp_tokiter_del_cur(cpp
, true);
193 } else if (token
->type
== PSI_T_BSLASH
) {
199 /* this whole turf is needed to distinct between:
200 * #define foo (1,2,3)
204 if (token
->type
== PSI_T_WHITESPACE
) {
209 psi_cpp_tokiter_del_cur(cpp
, true);
213 switch (token
->type
) {
239 /* mask special token for parser */
240 struct psi_token
*no_ws
= psi_token_copy(token
);
242 no_ws
->type
= PSI_T_NO_WHITESPACE
;
243 zend_string_release(no_ws
->text
);
244 no_ws
->text
= psi_string_init_interned("\xA0", 1, 1);
245 psi_cpp_tokiter_add(cpp
, no_ws
);
251 name
= define
= hash
= eol
= false;
256 psi_cpp_tokiter_add_cur(cpp
);
257 psi_cpp_tokiter_next(cpp
);
263 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
265 bool is_eol
= true, do_expansion
= true, skip_paren
= false, skip_all
= false;
267 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
269 psi_cpp_tokiter_reset(cpp
);
270 while (psi_cpp_tokiter_valid(cpp
)) {
271 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
273 if (current
->type
== PSI_T_HASH
) {
278 } else if (current
->type
== PSI_T_EOL
) {
280 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
286 psi_cpp_tokiter_del_cur(cpp
, true);
293 switch (current
->type
) {
296 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
298 do_expansion
= false;
308 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
310 do_expansion
= false;
320 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
328 do_expansion
= !skip_all
;
330 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
339 PSI_DEBUG_LOCK(cpp
->parser
,
340 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP skip ");
341 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, current
);
344 psi_cpp_tokiter_del_cur(cpp
, true);
349 if (do_expansion
&& psi_cpp_defined(cpp
, current
)) {
350 if (psi_cpp_tokiter_expand(cpp
)) {
355 psi_cpp_tokiter_add_cur(cpp
);
357 if (cpp
->do_cpp
&& is_eol
) {
358 size_t processed
= 0;
362 parsed
= psi_parser_process(cpp
->parser
, cpp
->tokens
.exec
, &processed
);
363 psi_plist_clean(cpp
->tokens
.exec
);
366 psi_plist_free(cpp
->tokens
.exec
);
370 #if PSI_CPP_DEBUG > 1
371 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
375 psi_cpp_tokiter_next(cpp
);
378 psi_plist_free(cpp
->tokens
.exec
);
379 cpp
->tokens
.exec
= NULL
;
384 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
,
385 struct psi_token
*expanding
)
388 struct psi_cpp temp
= *cpp
;
390 cpp
->tokens
.iter
= *tokens
;
391 cpp
->tokens
.next
= NULL
;
392 cpp
->tokens
.exec
= NULL
;
395 zend_hash_add_empty_element(&cpp
->expanding
, expanding
->text
);
397 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
401 zend_hash_del(&cpp
->expanding
, expanding
->text
);
404 *tokens
= cpp
->tokens
.next
;
405 psi_plist_free(cpp
->tokens
.iter
);
406 if (cpp
->tokens
.exec
) {
407 assert(!psi_plist_count(cpp
->tokens
.exec
));
408 psi_plist_free(cpp
->tokens
.exec
);
411 cpp
->tokens
= temp
.tokens
;
412 cpp
->index
= temp
.index
;
413 cpp
->skip
= temp
.skip
;
414 cpp
->level
= temp
.level
;
415 cpp
->seen
= temp
.seen
;
416 cpp
->do_cpp
= temp
.do_cpp
;
421 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
423 bool defined
= false;
425 if (tok
->type
== PSI_T_NAME
) {
426 if (psi_builtin_exists(tok
->text
)) {
428 } else if (!zend_hash_exists(&cpp
->expanding
, tok
->text
)) {
429 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
);
432 PSI_DEBUG_LOCK(cpp
->parser
,
433 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
435 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
437 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
442 PSI_DEBUG_PRINT(cpp
->parser
, " expanding=");
443 ZEND_HASH_FOREACH_STR_KEY(&cpp
->expanding
, key
)
445 PSI_DEBUG_PRINT(cpp
->parser
, "%s,", key
->val
);
447 ZEND_HASH_FOREACH_END();
448 PSI_DEBUG_PRINT(cpp
->parser
, "\t");
450 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
458 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
460 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
462 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
463 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
464 "'%s' redefined", decl
->token
->text
->val
);
465 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
466 "'%s' previously defined", old
->token
->text
->val
);
469 PSI_DEBUG_LOCK(cpp
->parser
,
471 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
473 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
475 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
476 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
479 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
482 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
484 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
487 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
489 struct psi_validate_scope scope
= {0};
492 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
495 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
501 bool psi_cpp_pragma(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
503 psi_cpp_pragma_func fn
;
505 fn
= zend_hash_find_ptr(&psi_cpp_pragmas
, decl
->token
->text
);
510 return fn(cpp
, decl
);
513 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
517 struct psi_plist
*tokens
;
518 struct psi_parser_input
*include
;
520 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
524 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
525 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
530 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
531 include_flavor
[flags
], path
);
533 include
= psi_parser_open_file(cpp
->parser
, path
, false);
538 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
540 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
542 tokens
= psi_parser_scan(cpp
->parser
, include
);
543 psi_parser_input_free(&include
);
549 ++cpp
->include_level
;
550 parsed
= psi_cpp_process(cpp
, &tokens
, NULL
);
551 --cpp
->include_level
;
554 psi_plist_free(tokens
);
558 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
566 # define eaccess access
568 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
576 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
577 /* first try as is, full or relative path */
578 if (file
->text
->val
[0] == '/') {
579 path
= file
->text
->val
;
584 strncpy(path
, file
->file
->val
, PATH_MAX
);
589 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
591 memmove(path
, dir
, len
);
593 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
596 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
597 include_flavor
[flags
], path
);
598 if (0 == eaccess(path
, R_OK
)) {
603 /* look through search paths */
604 if (file
->text
->val
[0] != '/') {
608 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
609 if ((sep
= strchr(cpp
->search
, ':'))) {
610 cpp
->search
= sep
+ 1;
612 /* point to end of string */
613 cpp
->search
+= strlen(cpp
->search
);
617 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
618 cpp
->search
= PSI_G(search_path
);
624 sep
= strchr(cpp
->search
, ':');
625 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
627 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
628 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
629 include_flavor
[flags
], path
);
630 if (0 == eaccess(path
, R_OK
)) {
636 cpp
->search
= sep
+ 1;