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 *******************************************************************************/
26 #include "php_psi_stdinc.h"
33 #define PSI_CPP_SEARCH
34 #define PSI_CPP_PREDEF
35 #include "php_psi_cpp.h"
39 HashTable psi_cpp_defaults
;
41 PHP_MINIT_FUNCTION(psi_cpp
)
43 struct psi_parser parser
;
44 struct psi_parser_input
*predef
;
46 PSI_G(search_path
) = pemalloc(strlen(PSI_G(directory
)) + strlen(psi_cpp_search
) + 1 + 1, 1);
47 sprintf(PSI_G(search_path
), "%s:%s", PSI_G(directory
), psi_cpp_search
);
49 if (!psi_parser_init(&parser
, psi_error_wrapper
, PSI_SILENT
)) {
53 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
54 psi_parser_dtor(&parser
);
58 if (!psi_parser_parse(&parser
, predef
)) {
59 psi_parser_input_free(&predef
);
60 psi_parser_dtor(&parser
);
63 psi_parser_input_free(&predef
);
65 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
66 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
68 psi_parser_dtor(&parser
);
73 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
75 struct psi_cpp_macro_decl
*macro
;
77 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
79 psi_cpp_macro_decl_free(¯o
);
81 ZEND_HASH_FOREACH_END();
83 zend_hash_destroy(&psi_cpp_defaults
);
88 static void free_cpp_def(zval
*p
)
90 if (Z_TYPE_P(p
) == IS_PTR
) {
91 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
93 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
94 psi_cpp_macro_decl_free(¯o
);
99 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
101 struct psi_cpp
*cpp
= pecalloc(1, sizeof(*cpp
), 1);
104 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
105 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
106 zend_hash_copy(&cpp
->defs
, &psi_cpp_defaults
, NULL
);
111 static char *include_flavor
[] = {
117 #if PSI_CPP_DEBUG > 1
118 static int dump_def(zval
*p
)
120 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
123 dprintf(2, "PSI: CPP decl -> #define ");
124 psi_cpp_macro_decl_dump(2, decl
);
127 return ZEND_HASH_APPLY_KEEP
;
131 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
134 struct psi_cpp
*cpp
= *cpp_ptr
;
136 #if PSI_CPP_DEBUG > 1
137 zend_hash_apply(&cpp
->defs
, dump_def
);
140 zend_hash_destroy(&cpp
->defs
);
141 zend_hash_destroy(&cpp
->once
);
146 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
148 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
150 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
152 psi_cpp_tokiter_reset(cpp
);
153 while (psi_cpp_tokiter_valid(cpp
)) {
154 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
156 /* strip comments and attributes */
157 if (token
->type
== PSI_T_COMMENT
158 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
159 psi_cpp_tokiter_del_cur(cpp
, true);
163 /* line continuations */
164 if (token
->type
== PSI_T_EOL
) {
166 psi_cpp_tokiter_del_prev(cpp
, true);
167 psi_cpp_tokiter_del_cur(cpp
, true);
171 } else if (token
->type
== PSI_T_BSLASH
) {
177 /* this whole turf is needed to distinct between:
178 * #define foo (1,2,3)
182 if (token
->type
== PSI_T_WHITESPACE
) {
187 psi_cpp_tokiter_del_cur(cpp
, true);
191 switch (token
->type
) {
217 /* mask special token for parser */
218 struct psi_token
*no_ws
= psi_token_copy(token
);
220 no_ws
->type
= PSI_T_NO_WHITESPACE
;
221 zend_string_release(no_ws
->text
);
222 no_ws
->text
= zend_string_init_interned("\xA0", 1, 1);
223 psi_cpp_tokiter_add(cpp
, no_ws
);
229 name
= define
= hash
= eol
= false;
234 psi_cpp_tokiter_add_cur(cpp
);
235 psi_cpp_tokiter_next(cpp
);
241 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
243 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
244 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
246 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
248 psi_cpp_tokiter_reset(cpp
);
249 while (psi_cpp_tokiter_valid(cpp
)) {
250 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
252 if (current
->type
== PSI_T_HASH
) {
257 } else if (current
->type
== PSI_T_EOL
) {
259 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
265 psi_cpp_tokiter_del_cur(cpp
, true);
272 switch (current
->type
) {
275 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
277 do_expansion
= false;
287 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
289 do_expansion
= false;
299 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
307 do_expansion
= !skip_all
;
309 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
318 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP skip ");
319 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, current
);
321 psi_cpp_tokiter_del_cur(cpp
, true);
326 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
327 bool expanded
= false;
329 while (psi_cpp_tokiter_expand(cpp
)) {
338 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
341 size_t processed
= 0;
342 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
345 psi_plist_pop(parser_tokens
, NULL
);
346 psi_plist_clean(parser_tokens
);
350 psi_plist_free(parser_tokens
);
354 /* leave EOLs in the input stream, else we might end up
355 * with a hash not preceded with a new line after include */
356 psi_cpp_tokiter_del_cur(cpp
, false);
359 #if PSI_CPP_DEBUG > 1
360 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
366 psi_cpp_tokiter_add_cur(cpp
);
367 psi_cpp_tokiter_next(cpp
);
370 psi_plist_free(parser_tokens
);
375 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
378 struct psi_cpp temp
= *cpp
; cpp
->level
= temp
.level
;
380 cpp
->tokens
.iter
= *tokens
;
381 cpp
->tokens
.next
= NULL
;
383 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
387 if (cpp
->tokens
.next
) {
388 free(cpp
->tokens
.iter
);
389 cpp
->tokens
.iter
= cpp
->tokens
.next
;
390 cpp
->tokens
.next
= NULL
;
393 *tokens
= cpp
->tokens
.iter
;
395 if (temp
.tokens
.iter
) {
396 cpp
->tokens
.iter
= temp
.tokens
.iter
;
397 cpp
->tokens
.next
= temp
.tokens
.next
;
399 cpp
->index
= temp
.index
;
400 cpp
->skip
= temp
.skip
;
401 cpp
->level
= temp
.level
;
402 cpp
->seen
= temp
.seen
;
407 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
411 if (tok
->type
== PSI_T_NAME
) {
412 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
)
413 || psi_builtin_exists(tok
->text
);
419 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
421 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
423 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
426 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
432 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
434 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
436 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
437 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
438 "'%s' redefined", decl
->token
->text
->val
);
439 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
440 "'%s' previously defined", old
->token
->text
->val
);
444 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
446 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
448 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
449 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
451 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
454 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
456 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
459 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
461 struct psi_validate_scope scope
= {0};
464 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
467 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
473 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
477 struct psi_plist
*tokens
;
478 struct psi_parser_input
*include
;
480 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
484 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
485 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
490 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
491 include_flavor
[flags
], path
);
493 include
= psi_parser_open_file(cpp
->parser
, path
, false);
498 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
500 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
502 tokens
= psi_parser_scan(cpp
->parser
, include
);
503 psi_parser_input_free(&include
);
509 parsed
= psi_cpp_process(cpp
, &tokens
);
511 psi_plist_free(tokens
);
515 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
523 # define eaccess access
525 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
533 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
534 /* first try as is, full or relative path */
535 if (file
->text
->val
[0] == '/') {
536 path
= file
->text
->val
;
541 strncpy(path
, file
->file
->val
, PATH_MAX
);
546 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
548 memmove(path
, dir
, len
);
550 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
553 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
554 include_flavor
[flags
], path
);
555 if (0 == eaccess(path
, R_OK
)) {
560 /* look through search paths */
561 if (file
->text
->val
[0] != '/') {
565 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
566 if ((sep
= strchr(cpp
->search
, ':'))) {
567 cpp
->search
= sep
+ 1;
569 /* point to end of string */
570 cpp
->search
+= strlen(cpp
->search
);
574 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
575 cpp
->search
= PSI_G(search_path
);
581 sep
= strchr(cpp
->search
, ':');
582 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
584 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
585 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
586 include_flavor
[flags
], path
);
587 if (0 == eaccess(path
, R_OK
)) {
593 cpp
->search
= sep
+ 1;