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"
37 #define PSI_CPP_SEARCH
38 #define PSI_CPP_PREDEF
39 #include "php_psi_predef.h"
43 HashTable psi_cpp_defaults
;
45 PHP_MINIT_FUNCTION(psi_cpp
);
46 PHP_MINIT_FUNCTION(psi_cpp
)
48 struct psi_parser parser
;
49 struct psi_parser_input
*predef
;
51 PSI_G(search_path
) = pemalloc(strlen(PSI_G(directory
)) + strlen(psi_cpp_search
) + 1 + 1, 1);
52 sprintf(PSI_G(search_path
), "%s:%s", PSI_G(directory
), psi_cpp_search
);
54 if (!psi_parser_init(&parser
, psi_error_wrapper
, PSI_SILENT
)) {
58 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
59 psi_parser_dtor(&parser
);
63 if (!psi_parser_parse(&parser
, predef
)) {
64 psi_parser_input_free(&predef
);
65 psi_parser_dtor(&parser
);
68 psi_parser_input_free(&predef
);
70 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
71 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
73 psi_parser_dtor(&parser
);
78 PHP_MSHUTDOWN_FUNCTION(psi_cpp
);
79 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
81 struct psi_cpp_macro_decl
*macro
;
83 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
85 psi_cpp_macro_decl_free(¯o
);
87 ZEND_HASH_FOREACH_END();
89 zend_hash_destroy(&psi_cpp_defaults
);
94 static void free_cpp_def(zval
*p
)
96 if (Z_TYPE_P(p
) == IS_PTR
) {
97 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
99 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
100 psi_cpp_macro_decl_free(¯o
);
105 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
107 struct psi_cpp
*cpp
= pecalloc(1, sizeof(*cpp
), 1);
110 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
111 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
112 zend_hash_copy(&cpp
->defs
, &psi_cpp_defaults
, NULL
);
117 static char *include_flavor
[] = {
123 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
126 struct psi_cpp
*cpp
= *cpp_ptr
;
129 zend_hash_destroy(&cpp
->defs
);
130 zend_hash_destroy(&cpp
->once
);
135 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
137 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
139 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
141 psi_cpp_tokiter_reset(cpp
);
142 while (psi_cpp_tokiter_valid(cpp
)) {
143 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
145 /* strip comments and attributes */
146 if (token
->type
== PSI_T_COMMENT
147 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
148 psi_cpp_tokiter_del_cur(cpp
, true);
152 /* line continuations */
153 if (token
->type
== PSI_T_EOL
) {
155 psi_cpp_tokiter_del_prev(cpp
, true);
156 psi_cpp_tokiter_del_cur(cpp
, true);
160 } else if (token
->type
== PSI_T_BSLASH
) {
166 /* this whole turf is needed to distinct between:
167 * #define foo (1,2,3)
171 if (token
->type
== PSI_T_WHITESPACE
) {
176 psi_cpp_tokiter_del_cur(cpp
, true);
180 switch (token
->type
) {
206 /* mask special token for parser */
207 struct psi_token
*no_ws
= psi_token_copy(token
);
209 no_ws
->type
= PSI_T_NO_WHITESPACE
;
210 zend_string_release(no_ws
->text
);
211 no_ws
->text
= psi_string_init_interned("\xA0", 1, 1);
212 psi_cpp_tokiter_add(cpp
, no_ws
);
218 name
= define
= hash
= eol
= false;
223 psi_cpp_tokiter_add_cur(cpp
);
224 psi_cpp_tokiter_next(cpp
);
230 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
232 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
233 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
235 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
237 psi_cpp_tokiter_reset(cpp
);
238 while (psi_cpp_tokiter_valid(cpp
)) {
239 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
241 if (current
->type
== PSI_T_HASH
) {
246 } else if (current
->type
== PSI_T_EOL
) {
248 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
254 psi_cpp_tokiter_del_cur(cpp
, true);
261 switch (current
->type
) {
264 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
266 do_expansion
= false;
276 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
278 do_expansion
= false;
288 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
296 do_expansion
= !skip_all
;
298 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
307 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP skip ");
308 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, current
);
310 psi_cpp_tokiter_del_cur(cpp
, true);
315 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
316 bool expanded
= false;
318 while (psi_cpp_tokiter_expand(cpp
)) {
327 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
330 size_t processed
= 0;
331 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
334 psi_plist_pop(parser_tokens
, NULL
);
335 psi_plist_clean(parser_tokens
);
339 psi_plist_free(parser_tokens
);
343 /* leave EOLs in the input stream, else we might end up
344 * with a hash not preceded with a new line after include */
345 psi_cpp_tokiter_del_cur(cpp
, false);
348 #if PSI_CPP_DEBUG > 1
349 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
355 psi_cpp_tokiter_add_cur(cpp
);
356 psi_cpp_tokiter_next(cpp
);
359 psi_plist_free(parser_tokens
);
364 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
367 struct psi_cpp temp
= *cpp
; cpp
->level
= temp
.level
;
369 cpp
->tokens
.iter
= *tokens
;
370 cpp
->tokens
.next
= NULL
;
372 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
376 if (cpp
->tokens
.next
) {
377 free(cpp
->tokens
.iter
);
378 cpp
->tokens
.iter
= cpp
->tokens
.next
;
379 cpp
->tokens
.next
= NULL
;
382 *tokens
= cpp
->tokens
.iter
;
384 if (temp
.tokens
.iter
) {
385 cpp
->tokens
.iter
= temp
.tokens
.iter
;
386 cpp
->tokens
.next
= temp
.tokens
.next
;
388 cpp
->index
= temp
.index
;
389 cpp
->skip
= temp
.skip
;
390 cpp
->level
= temp
.level
;
391 cpp
->seen
= temp
.seen
;
396 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
400 if (tok
->type
== PSI_T_NAME
) {
401 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
)
402 || psi_builtin_exists(tok
->text
);
408 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
410 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
412 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
415 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
421 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
423 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
425 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
426 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
427 "'%s' redefined", decl
->token
->text
->val
);
428 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
429 "'%s' previously defined", old
->token
->text
->val
);
433 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
435 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
437 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
438 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
440 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
443 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
445 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
448 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
450 struct psi_validate_scope scope
= {0};
453 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
456 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
462 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
466 struct psi_plist
*tokens
;
467 struct psi_parser_input
*include
;
469 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
473 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
474 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
479 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
480 include_flavor
[flags
], path
);
482 include
= psi_parser_open_file(cpp
->parser
, path
, false);
487 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
489 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
491 tokens
= psi_parser_scan(cpp
->parser
, include
);
492 psi_parser_input_free(&include
);
498 parsed
= psi_cpp_process(cpp
, &tokens
);
500 psi_plist_free(tokens
);
504 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
512 # define eaccess access
514 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
522 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
523 /* first try as is, full or relative path */
524 if (file
->text
->val
[0] == '/') {
525 path
= file
->text
->val
;
530 strncpy(path
, file
->file
->val
, PATH_MAX
);
535 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
537 memmove(path
, dir
, len
);
539 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
542 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
543 include_flavor
[flags
], path
);
544 if (0 == eaccess(path
, R_OK
)) {
549 /* look through search paths */
550 if (file
->text
->val
[0] != '/') {
554 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
555 if ((sep
= strchr(cpp
->search
, ':'))) {
556 cpp
->search
= sep
+ 1;
558 /* point to end of string */
559 cpp
->search
+= strlen(cpp
->search
);
563 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
564 cpp
->search
= PSI_G(search_path
);
570 sep
= strchr(cpp
->search
, ':');
571 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
573 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
574 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
575 include_flavor
[flags
], path
);
576 if (0 == eaccess(path
, R_OK
)) {
582 cpp
->search
= sep
+ 1;