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 HashTable psi_cpp_defaults
;
46 PHP_MINIT_FUNCTION(psi_cpp
);
47 PHP_MINIT_FUNCTION(psi_cpp
)
49 struct psi_parser parser
;
50 struct psi_parser_input
*predef
;
52 PSI_G(search_path
) = pemalloc(strlen(PSI_G(directory
)) + strlen(psi_cpp_search
) + 1 + 1, 1);
53 sprintf(PSI_G(search_path
), "%s:%s", PSI_G(directory
), psi_cpp_search
);
55 if (!psi_parser_init(&parser
, psi_error_wrapper
, PSI_SILENT
)) {
59 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
60 psi_parser_dtor(&parser
);
64 if (!psi_parser_parse(&parser
, predef
)) {
65 psi_parser_input_free(&predef
);
66 psi_parser_dtor(&parser
);
69 psi_parser_input_free(&predef
);
71 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
72 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
74 psi_parser_dtor(&parser
);
79 PHP_MSHUTDOWN_FUNCTION(psi_cpp
);
80 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
82 struct psi_cpp_macro_decl
*macro
;
84 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
86 psi_cpp_macro_decl_free(¯o
);
88 ZEND_HASH_FOREACH_END();
90 zend_hash_destroy(&psi_cpp_defaults
);
95 static void free_cpp_def(zval
*p
)
97 if (Z_TYPE_P(p
) == IS_PTR
) {
98 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
100 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
101 psi_cpp_macro_decl_free(¯o
);
106 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
108 struct psi_cpp
*cpp
= pecalloc(1, sizeof(*cpp
), 1);
111 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
112 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
113 zend_hash_copy(&cpp
->defs
, &psi_cpp_defaults
, NULL
);
118 static char *include_flavor
[] = {
124 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
127 struct psi_cpp
*cpp
= *cpp_ptr
;
130 zend_hash_destroy(&cpp
->defs
);
131 zend_hash_destroy(&cpp
->once
);
136 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
138 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
140 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
142 psi_cpp_tokiter_reset(cpp
);
143 while (psi_cpp_tokiter_valid(cpp
)) {
144 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
146 /* strip comments and attributes */
147 if (token
->type
== PSI_T_COMMENT
148 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
149 psi_cpp_tokiter_del_cur(cpp
, true);
153 /* line continuations */
154 if (token
->type
== PSI_T_EOL
) {
156 psi_cpp_tokiter_del_prev(cpp
, true);
157 psi_cpp_tokiter_del_cur(cpp
, true);
161 } else if (token
->type
== PSI_T_BSLASH
) {
167 /* this whole turf is needed to distinct between:
168 * #define foo (1,2,3)
172 if (token
->type
== PSI_T_WHITESPACE
) {
177 psi_cpp_tokiter_del_cur(cpp
, true);
181 switch (token
->type
) {
207 /* mask special token for parser */
208 struct psi_token
*no_ws
= psi_token_copy(token
);
210 no_ws
->type
= PSI_T_NO_WHITESPACE
;
211 zend_string_release(no_ws
->text
);
212 no_ws
->text
= psi_string_init_interned("\xA0", 1, 1);
213 psi_cpp_tokiter_add(cpp
, no_ws
);
219 name
= define
= hash
= eol
= false;
224 psi_cpp_tokiter_add_cur(cpp
);
225 psi_cpp_tokiter_next(cpp
);
231 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
233 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
234 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
236 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
238 psi_cpp_tokiter_reset(cpp
);
239 while (psi_cpp_tokiter_valid(cpp
)) {
240 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
242 if (current
->type
== PSI_T_HASH
) {
247 } else if (current
->type
== PSI_T_EOL
) {
249 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
255 psi_cpp_tokiter_del_cur(cpp
, true);
262 switch (current
->type
) {
265 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
267 do_expansion
= false;
277 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
279 do_expansion
= false;
289 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
297 do_expansion
= !skip_all
;
299 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
308 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP skip ");
309 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, current
);
311 psi_cpp_tokiter_del_cur(cpp
, true);
316 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
317 bool expanded
= false;
319 while (psi_cpp_tokiter_expand(cpp
)) {
328 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
331 size_t processed
= 0;
332 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
335 psi_plist_pop(parser_tokens
, NULL
);
336 psi_plist_clean(parser_tokens
);
340 psi_plist_free(parser_tokens
);
344 /* leave EOLs in the input stream, else we might end up
345 * with a hash not preceded with a new line after include */
346 psi_cpp_tokiter_del_cur(cpp
, false);
349 #if PSI_CPP_DEBUG > 1
350 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
356 psi_cpp_tokiter_add_cur(cpp
);
357 psi_cpp_tokiter_next(cpp
);
360 psi_plist_free(parser_tokens
);
365 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
368 struct psi_cpp temp
= *cpp
; cpp
->level
= temp
.level
;
370 cpp
->tokens
.iter
= *tokens
;
371 cpp
->tokens
.next
= NULL
;
373 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
377 if (cpp
->tokens
.next
) {
378 free(cpp
->tokens
.iter
);
379 cpp
->tokens
.iter
= cpp
->tokens
.next
;
380 cpp
->tokens
.next
= NULL
;
383 *tokens
= cpp
->tokens
.iter
;
385 if (temp
.tokens
.iter
) {
386 cpp
->tokens
.iter
= temp
.tokens
.iter
;
387 cpp
->tokens
.next
= temp
.tokens
.next
;
389 cpp
->index
= temp
.index
;
390 cpp
->skip
= temp
.skip
;
391 cpp
->level
= temp
.level
;
392 cpp
->seen
= temp
.seen
;
397 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
401 if (tok
->type
== PSI_T_NAME
) {
402 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
)
403 || psi_builtin_exists(tok
->text
);
409 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
411 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
413 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
416 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
422 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
424 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
426 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
427 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
428 "'%s' redefined", decl
->token
->text
->val
);
429 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
430 "'%s' previously defined", old
->token
->text
->val
);
434 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
436 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
438 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
439 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
441 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
444 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
446 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
449 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
451 struct psi_validate_scope scope
= {0};
454 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
457 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
463 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
467 struct psi_plist
*tokens
;
468 struct psi_parser_input
*include
;
470 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
474 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
475 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
480 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
481 include_flavor
[flags
], path
);
483 include
= psi_parser_open_file(cpp
->parser
, path
, false);
488 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
490 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
492 tokens
= psi_parser_scan(cpp
->parser
, include
);
493 psi_parser_input_free(&include
);
499 parsed
= psi_cpp_process(cpp
, &tokens
);
501 psi_plist_free(tokens
);
505 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
513 # define eaccess access
515 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
523 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
524 /* first try as is, full or relative path */
525 if (file
->text
->val
[0] == '/') {
526 path
= file
->text
->val
;
531 strncpy(path
, file
->file
->val
, PATH_MAX
);
536 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
538 memmove(path
, dir
, len
);
540 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
543 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
544 include_flavor
[flags
], path
);
545 if (0 == eaccess(path
, R_OK
)) {
550 /* look through search paths */
551 if (file
->text
->val
[0] != '/') {
555 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
556 if ((sep
= strchr(cpp
->search
, ':'))) {
557 cpp
->search
= sep
+ 1;
559 /* point to end of string */
560 cpp
->search
+= strlen(cpp
->search
);
564 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
565 cpp
->search
= PSI_G(search_path
);
571 sep
= strchr(cpp
->search
, ':');
572 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
574 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
575 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
576 include_flavor
[flags
], path
);
577 if (0 == eaccess(path
, R_OK
)) {
583 cpp
->search
= sep
+ 1;