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 static bool psi_cpp_pragma_blacklist_decl(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
72 struct psi_token
*name
;
74 if (!psi_plist_get(decl
->tokens
, 0, &name
)
75 || !name
|| name
->type
!= PSI_T_QUOTED_STRING
) {
79 psi_blacklist_add_decl(name
->text
->val
, name
->text
->len
);
83 static bool psi_cpp_pragma_blacklist_var(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
85 struct psi_token
*name
;
87 if (!psi_plist_get(decl
->tokens
, 0, &name
)
88 || !name
|| name
->type
!= PSI_T_QUOTED_STRING
) {
92 psi_blacklist_add_var(name
->text
->val
, name
->text
->len
);
96 PHP_MINIT_FUNCTION(psi_cpp
);
97 PHP_MINIT_FUNCTION(psi_cpp
)
99 struct psi_parser parser
;
100 struct psi_parser_input
*predef
;
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
);
105 if (!psi_parser_init(&parser
, psi_error_wrapper
, PSI_SILENT
)) {
109 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
110 psi_parser_dtor(&parser
);
114 if (!psi_parser_parse(&parser
, predef
)) {
115 psi_parser_input_free(&predef
);
116 psi_parser_dtor(&parser
);
119 psi_parser_input_free(&predef
);
121 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
122 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
124 psi_parser_dtor(&parser
);
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
);
131 PSI_CPP_PRAGMA(blacklist_decl
);
132 PSI_CPP_PRAGMA(blacklist_var
);
137 PHP_MSHUTDOWN_FUNCTION(psi_cpp
);
138 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
140 struct psi_cpp_macro_decl
*macro
;
142 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
144 psi_cpp_macro_decl_free(¯o
);
146 ZEND_HASH_FOREACH_END();
148 zend_hash_destroy(&psi_cpp_defaults
);
153 static void free_cpp_def(zval
*p
)
155 if (Z_TYPE_P(p
) == IS_PTR
) {
156 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
158 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
159 psi_cpp_macro_decl_free(¯o
);
164 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
166 struct psi_cpp
*cpp
= pecalloc(1, sizeof(*cpp
), 1);
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);
177 static char *include_flavor
[] = {
183 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
186 struct psi_cpp
*cpp
= *cpp_ptr
;
189 zend_hash_destroy(&cpp
->defs
);
190 zend_hash_destroy(&cpp
->once
);
191 zend_hash_destroy(&cpp
->expanding
);
196 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
198 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
200 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
202 psi_cpp_tokiter_reset(cpp
);
203 while (psi_cpp_tokiter_valid(cpp
)) {
204 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
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);
213 /* line continuations */
214 if (token
->type
== PSI_T_EOL
) {
216 psi_cpp_tokiter_del_prev(cpp
, true);
217 psi_cpp_tokiter_del_cur(cpp
, true);
221 } else if (token
->type
== PSI_T_BSLASH
) {
227 /* this whole turf is needed to distinct between:
228 * #define foo (1,2,3)
232 if (token
->type
== PSI_T_WHITESPACE
) {
237 psi_cpp_tokiter_del_cur(cpp
, true);
241 switch (token
->type
) {
267 /* mask special token for parser */
268 struct psi_token
*no_ws
= psi_token_copy(token
);
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
);
279 name
= define
= hash
= eol
= false;
284 psi_cpp_tokiter_add_cur(cpp
);
285 psi_cpp_tokiter_next(cpp
);
291 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
293 bool is_eol
= true, do_expansion
= true, skip_paren
= false, skip_all
= false;
295 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
297 psi_cpp_tokiter_reset(cpp
);
298 while (psi_cpp_tokiter_valid(cpp
)) {
299 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
301 if (current
->type
== PSI_T_HASH
) {
306 } else if (current
->type
== PSI_T_EOL
) {
308 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
314 psi_cpp_tokiter_del_cur(cpp
, true);
321 switch (current
->type
) {
324 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
326 do_expansion
= false;
336 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
338 do_expansion
= false;
348 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
356 do_expansion
= !skip_all
;
358 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
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
);
372 psi_cpp_tokiter_del_cur(cpp
, true);
377 if (do_expansion
&& psi_cpp_defined(cpp
, current
)) {
378 if (psi_cpp_tokiter_expand(cpp
)) {
383 psi_cpp_tokiter_add_cur(cpp
);
385 if (cpp
->do_cpp
&& is_eol
) {
386 size_t processed
= 0;
390 parsed
= psi_parser_process(cpp
->parser
, cpp
->tokens
.exec
, &processed
);
391 psi_plist_clean(cpp
->tokens
.exec
);
394 psi_plist_free(cpp
->tokens
.exec
);
398 #if PSI_CPP_DEBUG > 1
399 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
403 psi_cpp_tokiter_next(cpp
);
406 psi_plist_free(cpp
->tokens
.exec
);
407 cpp
->tokens
.exec
= NULL
;
412 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
,
413 struct psi_token
*expanding
)
416 struct psi_cpp temp
= *cpp
;
418 cpp
->tokens
.iter
= *tokens
;
419 cpp
->tokens
.next
= NULL
;
420 cpp
->tokens
.exec
= NULL
;
423 zend_hash_add_empty_element(&cpp
->expanding
, expanding
->text
);
425 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
429 zend_hash_del(&cpp
->expanding
, expanding
->text
);
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
);
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
;
449 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
451 bool defined
= false;
453 if (tok
->type
== PSI_T_NAME
) {
454 if (psi_builtin_exists(tok
->text
)) {
456 } else if (!zend_hash_exists(&cpp
->expanding
, tok
->text
)) {
457 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
);
460 PSI_DEBUG_LOCK(cpp
->parser
,
461 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
463 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
465 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
470 PSI_DEBUG_PRINT(cpp
->parser
, " expanding=");
471 ZEND_HASH_FOREACH_STR_KEY(&cpp
->expanding
, key
)
473 PSI_DEBUG_PRINT(cpp
->parser
, "%s,", key
->val
);
475 ZEND_HASH_FOREACH_END();
476 PSI_DEBUG_PRINT(cpp
->parser
, "\t");
478 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
486 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
488 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
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
);
497 PSI_DEBUG_LOCK(cpp
->parser
,
499 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
501 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
503 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
504 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
507 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
510 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
512 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
515 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
517 struct psi_validate_scope scope
= {0};
518 unsigned flags
= cpp
->parser
->flags
;
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
;
526 cpp
->parser
->flags
= flags
;
527 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
533 bool psi_cpp_pragma(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
535 psi_cpp_pragma_func fn
;
537 fn
= zend_hash_find_ptr(&psi_cpp_pragmas
, decl
->token
->text
);
542 return fn(cpp
, decl
);
545 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
549 struct psi_plist
*tokens
;
550 struct psi_parser_input
*include
;
552 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
556 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
557 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
562 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
563 include_flavor
[flags
], path
);
565 include
= psi_parser_open_file(cpp
->parser
, path
, false);
570 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
572 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
574 tokens
= psi_parser_scan(cpp
->parser
, include
);
575 psi_parser_input_free(&include
);
581 ++cpp
->include_level
;
582 parsed
= psi_cpp_process(cpp
, &tokens
, NULL
);
583 --cpp
->include_level
;
586 psi_plist_free(tokens
);
590 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
598 # define eaccess access
600 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
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
;
616 strncpy(path
, file
->file
->val
, PATH_MAX
);
621 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
623 memmove(path
, dir
, len
);
625 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
628 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
629 include_flavor
[flags
], path
);
630 if (0 == eaccess(path
, R_OK
)) {
635 /* look through search paths */
636 if (file
->text
->val
[0] != '/') {
640 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
641 if ((sep
= strchr(cpp
->search
, ':'))) {
642 cpp
->search
= sep
+ 1;
644 /* point to end of string */
645 cpp
->search
+= strlen(cpp
->search
);
649 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
650 cpp
->search
= PSI_G(search_path
);
656 sep
= strchr(cpp
->search
, ':');
657 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
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
)) {
668 cpp
->search
= sep
+ 1;