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
, NULL
, 0)) {
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
);
112 static int dump_def(zval
*p
)
114 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
118 dprintf(2, "PSI: CPP decl -> #define ");
119 psi_cpp_macro_decl_dump(2, decl
);
122 return ZEND_HASH_APPLY_KEEP
;
126 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
129 struct psi_cpp
*cpp
= *cpp_ptr
;
132 zend_hash_apply(&cpp
->defs
, dump_def
);
135 zend_hash_destroy(&cpp
->defs
);
136 zend_hash_destroy(&cpp
->once
);
141 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
143 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
145 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
147 psi_cpp_tokiter_reset(cpp
);
148 while (psi_cpp_tokiter_valid(cpp
)) {
149 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
151 /* strip comments and attributes */
152 if (token
->type
== PSI_T_COMMENT
153 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
154 psi_cpp_tokiter_del_cur(cpp
, true);
158 /* line continuations */
159 if (token
->type
== PSI_T_EOL
) {
161 psi_cpp_tokiter_del_prev(cpp
, true);
162 psi_cpp_tokiter_del_cur(cpp
, true);
166 } else if (token
->type
== PSI_T_BSLASH
) {
172 /* this whole turf is needed to distinct between:
173 * #define foo (1,2,3)
177 if (token
->type
== PSI_T_WHITESPACE
) {
182 psi_cpp_tokiter_del_cur(cpp
, true);
186 switch (token
->type
) {
212 /* mask special token for parser */
213 struct psi_token
*no_ws
= psi_token_copy(token
);
215 no_ws
->type
= PSI_T_NO_WHITESPACE
;
216 zend_string_release(no_ws
->text
);
217 no_ws
->text
= zend_string_init_interned("\xA0", 1, 1);
218 psi_cpp_tokiter_add(cpp
, no_ws
);
224 name
= define
= hash
= eol
= false;
229 psi_cpp_tokiter_add_cur(cpp
);
230 psi_cpp_tokiter_next(cpp
);
236 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
238 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
239 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
241 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
243 psi_cpp_tokiter_reset(cpp
);
244 while (psi_cpp_tokiter_valid(cpp
)) {
245 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
247 if (current
->type
== PSI_T_HASH
) {
252 } else if (current
->type
== PSI_T_EOL
) {
254 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
260 psi_cpp_tokiter_del_cur(cpp
, true);
267 switch (current
->type
) {
270 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
272 do_expansion
= false;
282 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
284 do_expansion
= false;
294 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
302 do_expansion
= !skip_all
;
304 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
313 fprintf(stderr
, "PSI: CPP skip ");
314 psi_token_dump(2, current
);
316 psi_cpp_tokiter_del_cur(cpp
, true);
321 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
322 bool expanded
= false;
324 while (psi_cpp_tokiter_expand(cpp
)) {
333 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
336 size_t processed
= 0;
337 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
340 psi_plist_pop(parser_tokens
, NULL
);
341 psi_plist_clean(parser_tokens
);
345 psi_plist_free(parser_tokens
);
349 /* leave EOLs in the input stream, else we might end up
350 * with a hash not preceded with a new line after include */
351 psi_cpp_tokiter_del_cur(cpp
, false);
354 #if PSI_CPP_DEBUG > 1
355 psi_cpp_tokiter_dump(2, cpp
);
361 psi_cpp_tokiter_add_cur(cpp
);
362 psi_cpp_tokiter_next(cpp
);
365 psi_plist_free(parser_tokens
);
370 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
373 struct psi_cpp temp
= *cpp
;
375 cpp
->tokens
.iter
= *tokens
;
376 cpp
->tokens
.next
= NULL
;
378 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
382 if (cpp
->tokens
.next
) {
383 free(cpp
->tokens
.iter
);
384 cpp
->tokens
.iter
= cpp
->tokens
.next
;
385 cpp
->tokens
.next
= NULL
;
388 *tokens
= cpp
->tokens
.iter
;
390 if (temp
.tokens
.iter
) {
391 cpp
->tokens
.iter
= temp
.tokens
.iter
;
392 cpp
->tokens
.next
= temp
.tokens
.next
;
393 cpp
->index
= temp
.index
;
399 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
403 if (tok
->type
== PSI_T_NAME
) {
404 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
);
410 fprintf(stderr
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
412 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
413 fprintf(stderr
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
415 psi_token_dump(2, 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 fprintf(stderr
, "PSI: CPP MACRO num_exp -> %s ", decl
->token
->text
->val
);
435 fprintf(stderr
, "PSI: CPP MACRO decl -> %s ", decl
->token
->text
->val
);
437 psi_cpp_macro_decl_dump(2, decl
);
438 fprintf(stderr
, "\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};
452 scope
.defs
= &cpp
->defs
;
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
->defs
)) {
462 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
464 struct psi_parser_input
*include
;
466 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
468 include
= psi_parser_open_file(cpp
->parser
, path
, false);
470 struct psi_plist
*tokens
;
472 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
474 tokens
= psi_parser_scan(cpp
->parser
, include
);
476 *parsed
= psi_cpp_process(cpp
, &tokens
);
479 size_t num_tokens
= psi_plist_count(tokens
);
482 psi_cpp_tokiter_add_range(cpp
, num_tokens
, psi_plist_eles(tokens
));
485 psi_plist_free(tokens
);
488 psi_parser_input_free(&include
);
490 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
496 static inline void include_path(const struct psi_token
*file
, char **path
)
498 if (file
->text
->val
[0] == '/') {
499 *path
= file
->text
->val
;
504 strncpy(*path
, file
->file
->val
, PATH_MAX
);
506 dir
= dirname(*path
);
509 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
511 memmove(*path
, dir
, len
);
513 memcpy(&(*path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
517 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
521 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
522 /* first try as is, full or relative path */
523 char temp
[PATH_MAX
], *path
= temp
;
525 include_path(file
, &path
);
527 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
530 if (try_include(cpp
, path
, &parsed
)) {
536 /* look through search paths */
537 if (file
->text
->val
[0] != '/') {
542 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
543 if ((sep
= strchr(cpp
->search
, ':'))) {
544 cpp
->search
= sep
+ 1;
546 /* point to end of string */
547 cpp
->search
+= strlen(cpp
->search
);
551 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
552 cpp
->search
= PSI_G(search_path
);
558 sep
= strchr(cpp
->search
, ':');
559 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
561 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
562 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
565 if (try_include(cpp
, path
, &parsed
)) {
571 cpp
->search
= sep
+ 1;