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 if (!psi_parser_init(&parser
, NULL
, 0)) {
50 if (!(predef
= psi_parser_open_string(&parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
51 psi_parser_dtor(&parser
);
55 if (!psi_parser_parse(&parser
, predef
)) {
56 psi_parser_input_free(&predef
);
57 psi_parser_dtor(&parser
);
60 psi_parser_input_free(&predef
);
62 zend_hash_init(&psi_cpp_defaults
, 0, NULL
, NULL
, 1);
63 zend_hash_copy(&psi_cpp_defaults
, &parser
.preproc
->defs
, NULL
);
65 psi_parser_dtor(&parser
);
70 PHP_MSHUTDOWN_FUNCTION(psi_cpp
)
72 struct psi_cpp_macro_decl
*macro
;
74 ZEND_HASH_FOREACH_PTR(&psi_cpp_defaults
, macro
)
76 psi_cpp_macro_decl_free(¯o
);
78 ZEND_HASH_FOREACH_END();
80 zend_hash_destroy(&psi_cpp_defaults
);
85 static void free_cpp_def(zval
*p
)
87 if (Z_TYPE_P(p
) == IS_PTR
) {
88 struct psi_cpp_macro_decl
*macro
= Z_PTR_P(p
);
90 if (!zend_hash_exists(&psi_cpp_defaults
, macro
->token
->text
)) {
91 psi_cpp_macro_decl_free(¯o
);
96 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
98 struct psi_cpp
*cpp
= calloc(1, sizeof(*cpp
));
101 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
102 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
103 zend_hash_copy(&cpp
->defs
, &psi_cpp_defaults
, NULL
);
109 static int dump_def(zval
*p
)
111 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
115 dprintf(2, "PSI: CPP decl -> #define ");
116 psi_cpp_macro_decl_dump(2, decl
);
119 return ZEND_HASH_APPLY_KEEP
;
123 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
126 struct psi_cpp
*cpp
= *cpp_ptr
;
129 zend_hash_apply(&cpp
->defs
, dump_def
);
132 zend_hash_destroy(&cpp
->defs
);
133 zend_hash_destroy(&cpp
->once
);
138 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
140 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
142 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
144 psi_cpp_tokiter_reset(cpp
);
145 while (psi_cpp_tokiter_valid(cpp
)) {
146 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
148 /* strip comments and attributes */
149 if (token
->type
== PSI_T_COMMENT
150 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
151 psi_cpp_tokiter_del_cur(cpp
, true);
155 /* line continuations */
156 if (token
->type
== PSI_T_EOL
) {
158 psi_cpp_tokiter_del_prev(cpp
, true);
159 psi_cpp_tokiter_del_cur(cpp
, true);
163 } else if (token
->type
== PSI_T_BSLASH
) {
169 /* this whole turf is needed to distinct between:
170 * #define foo (1,2,3)
174 if (token
->type
== PSI_T_WHITESPACE
) {
179 psi_cpp_tokiter_del_cur(cpp
, true);
183 switch (token
->type
) {
209 /* mask special token for parser */
210 struct psi_token
*no_ws
= psi_token_copy(token
);
212 no_ws
->type
= PSI_T_NO_WHITESPACE
;
213 zend_string_release(no_ws
->text
);
214 no_ws
->text
= zend_string_init_interned("\xA0", 1, 1);
215 psi_cpp_tokiter_add(cpp
, no_ws
);
221 name
= define
= hash
= eol
= false;
226 psi_cpp_tokiter_add_cur(cpp
);
227 psi_cpp_tokiter_next(cpp
);
233 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
235 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
236 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
238 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
240 psi_cpp_tokiter_reset(cpp
);
241 while (psi_cpp_tokiter_valid(cpp
)) {
242 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
244 if (current
->type
== PSI_T_HASH
) {
249 } else if (current
->type
== PSI_T_EOL
) {
251 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
257 psi_cpp_tokiter_del_cur(cpp
, true);
264 switch (current
->type
) {
267 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
269 do_expansion
= false;
279 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
281 do_expansion
= false;
291 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
299 do_expansion
= !skip_all
;
301 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
310 fprintf(stderr
, "PSI: CPP skip ");
311 psi_token_dump(2, current
);
313 psi_cpp_tokiter_del_cur(cpp
, true);
318 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
319 bool expanded
= false;
321 while (psi_cpp_tokiter_expand(cpp
)) {
330 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
333 size_t processed
= 0;
334 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
337 psi_plist_pop(parser_tokens
, NULL
);
338 psi_plist_clean(parser_tokens
);
342 psi_plist_free(parser_tokens
);
346 /* leave EOLs in the input stream, else we might end up
347 * with a hash not preceded with a new line after include */
348 psi_cpp_tokiter_del_cur(cpp
, false);
351 #if PSI_CPP_DEBUG > 1
352 psi_cpp_tokiter_dump(2, cpp
);
358 psi_cpp_tokiter_add_cur(cpp
);
359 psi_cpp_tokiter_next(cpp
);
362 psi_plist_free(parser_tokens
);
367 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
370 struct psi_cpp temp
= *cpp
;
372 cpp
->tokens
.iter
= *tokens
;
373 cpp
->tokens
.next
= NULL
;
375 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
379 if (cpp
->tokens
.next
) {
380 free(cpp
->tokens
.iter
);
381 cpp
->tokens
.iter
= cpp
->tokens
.next
;
382 cpp
->tokens
.next
= NULL
;
385 *tokens
= cpp
->tokens
.iter
;
387 if (temp
.tokens
.iter
) {
388 cpp
->tokens
.iter
= temp
.tokens
.iter
;
389 cpp
->tokens
.next
= temp
.tokens
.next
;
390 cpp
->index
= temp
.index
;
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
);
407 fprintf(stderr
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
409 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
410 fprintf(stderr
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
412 psi_token_dump(2, tok
);
418 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
420 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
422 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
423 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
424 "'%s' redefined", decl
->token
->text
->val
);
425 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
426 "'%s' previously defined", old
->token
->text
->val
);
430 fprintf(stderr
, "PSI: CPP MACRO num_exp -> %s ", decl
->token
->text
->val
);
432 fprintf(stderr
, "PSI: CPP MACRO decl -> %s ", decl
->token
->text
->val
);
434 psi_cpp_macro_decl_dump(2, decl
);
435 fprintf(stderr
, "\n");
437 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
440 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
442 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
445 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
447 struct psi_validate_scope scope
= {0};
449 scope
.defs
= &cpp
->defs
;
450 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
453 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, &cpp
->defs
)) {
459 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
461 struct psi_parser_input
*include
;
463 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
465 include
= psi_parser_open_file(cpp
->parser
, path
, false);
467 struct psi_plist
*tokens
;
469 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
471 tokens
= psi_parser_scan(cpp
->parser
, include
);
473 *parsed
= psi_cpp_process(cpp
, &tokens
);
476 size_t num_tokens
= psi_plist_count(tokens
);
479 psi_cpp_tokiter_add_range(cpp
, num_tokens
, psi_plist_eles(tokens
));
482 psi_plist_free(tokens
);
485 psi_parser_input_free(&include
);
487 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
493 static inline void include_path(const struct psi_token
*file
, char **path
)
495 if (file
->text
->val
[0] == '/') {
496 *path
= file
->text
->val
;
501 strncpy(*path
, file
->file
->val
, PATH_MAX
);
503 dir
= dirname(*path
);
506 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
508 memmove(*path
, dir
, len
);
510 memcpy(&(*path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
514 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
518 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
519 /* first try as is, full or relative path */
520 char temp
[PATH_MAX
], *path
= temp
;
522 include_path(file
, &path
);
524 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
527 if (try_include(cpp
, path
, &parsed
)) {
533 /* look through search paths */
534 if (file
->text
->val
[0] != '/') {
539 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
540 if ((sep
= strchr(cpp
->search
, ':'))) {
541 cpp
->search
= sep
+ 1;
543 /* point to end of string */
544 cpp
->search
+= strlen(cpp
->search
);
548 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
549 cpp
->search
= PSI_G(search_path
);
555 sep
= strchr(cpp
->search
, ':');
556 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
558 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
559 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
562 if (try_include(cpp
, path
, &parsed
)) {
568 cpp
->search
= sep
+ 1;