1300d57e9513e5b5a43410386ce5733df2bf47d8
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 static void free_cpp_def(zval
*p
)
41 if (Z_TYPE_P(p
) == IS_PTR
) {
42 psi_cpp_macro_decl_free((void *) &Z_PTR_P(p
));
46 struct psi_cpp
*psi_cpp_init(struct psi_parser
*P
)
48 struct psi_cpp
*cpp
= calloc(1, sizeof(*cpp
));
51 zend_hash_init(&cpp
->defs
, 0, NULL
, free_cpp_def
, 1);
52 zend_hash_init(&cpp
->once
, 0, NULL
, NULL
, 1);
57 bool psi_cpp_load_defaults(struct psi_cpp
*cpp
)
59 struct psi_parser_input
*predef
;
61 if ((predef
= psi_parser_open_string(cpp
->parser
, psi_cpp_predef
, sizeof(psi_cpp_predef
) - 1))) {
62 bool parsed
= psi_parser_parse(cpp
->parser
, predef
);
63 psi_parser_input_free(&predef
);
71 static int dump_def(zval
*p
)
73 struct psi_cpp_macro_decl
*decl
= Z_PTR_P(p
);
77 dprintf(2, "PSI: CPP decl -> #define ");
78 psi_cpp_macro_decl_dump(2, decl
);
81 return ZEND_HASH_APPLY_KEEP
;
85 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
88 struct psi_cpp
*cpp
= *cpp_ptr
;
91 zend_hash_apply(&cpp
->defs
, dump_def
);
94 zend_hash_destroy(&cpp
->defs
);
95 zend_hash_destroy(&cpp
->once
);
100 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
102 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
104 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
106 psi_cpp_tokiter_reset(cpp
);
107 while (psi_cpp_tokiter_valid(cpp
)) {
108 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
110 /* strip comments and attributes */
111 if (token
->type
== PSI_T_COMMENT
112 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
113 psi_cpp_tokiter_del_cur(cpp
, true);
117 /* line continuations */
118 if (token
->type
== PSI_T_EOL
) {
120 psi_cpp_tokiter_del_prev(cpp
, true);
121 psi_cpp_tokiter_del_cur(cpp
, true);
125 } else if (token
->type
== PSI_T_BSLASH
) {
131 /* this whole turf is needed to distinct between:
132 * #define foo (1,2,3)
136 if (token
->type
== PSI_T_WHITESPACE
) {
141 psi_cpp_tokiter_del_cur(cpp
, true);
145 switch (token
->type
) {
171 /* mask special token for parser */
172 struct psi_token
*no_ws
= psi_token_copy(token
);
174 no_ws
->type
= PSI_T_NO_WHITESPACE
;
175 zend_string_release(no_ws
->text
);
176 no_ws
->text
= zend_string_init_interned("\xA0", 1, 1);
177 psi_cpp_tokiter_add(cpp
, no_ws
);
183 name
= define
= hash
= eol
= false;
188 psi_cpp_tokiter_add_cur(cpp
);
189 psi_cpp_tokiter_next(cpp
);
195 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
197 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
198 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
200 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
202 psi_cpp_tokiter_reset(cpp
);
203 while (psi_cpp_tokiter_valid(cpp
)) {
204 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
206 if (current
->type
== PSI_T_HASH
) {
211 } else if (current
->type
== PSI_T_EOL
) {
213 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
219 psi_cpp_tokiter_del_cur(cpp
, true);
226 switch (current
->type
) {
229 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
231 do_expansion
= false;
241 fprintf(stderr
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
243 do_expansion
= false;
253 fprintf(stderr
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
261 do_expansion
= !skip_all
;
263 fprintf(stderr
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
272 fprintf(stderr
, "PSI: CPP skip ");
273 psi_token_dump(2, current
);
275 psi_cpp_tokiter_del_cur(cpp
, true);
280 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
281 bool expanded
= false;
283 while (psi_cpp_tokiter_expand(cpp
)) {
292 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
295 size_t processed
= 0;
296 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
299 psi_plist_pop(parser_tokens
, NULL
);
300 psi_plist_clean(parser_tokens
);
304 psi_plist_free(parser_tokens
);
308 /* leave EOLs in the input stream, else we might end up
309 * with a hash not preceded with a new line after include */
310 psi_cpp_tokiter_del_cur(cpp
, false);
313 #if PSI_CPP_DEBUG > 1
314 psi_cpp_tokiter_dump(2, cpp
);
320 psi_cpp_tokiter_add_cur(cpp
);
321 psi_cpp_tokiter_next(cpp
);
324 psi_plist_free(parser_tokens
);
329 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
332 struct psi_cpp temp
= *cpp
;
334 cpp
->tokens
.iter
= *tokens
;
335 cpp
->tokens
.next
= NULL
;
337 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
341 if (cpp
->tokens
.next
) {
342 free(cpp
->tokens
.iter
);
343 cpp
->tokens
.iter
= cpp
->tokens
.next
;
344 cpp
->tokens
.next
= NULL
;
347 *tokens
= cpp
->tokens
.iter
;
349 if (temp
.tokens
.iter
) {
350 cpp
->tokens
.iter
= temp
.tokens
.iter
;
351 cpp
->tokens
.next
= temp
.tokens
.next
;
352 cpp
->index
= temp
.index
;
358 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
362 if (tok
->type
== PSI_T_NAME
) {
363 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
);
369 fprintf(stderr
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
371 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
372 fprintf(stderr
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
374 psi_token_dump(2, tok
);
380 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
382 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
384 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
385 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
386 "'%s' redefined", decl
->token
->text
->val
);
387 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
388 "'%s' previously defined", old
->token
->text
->val
);
392 fprintf(stderr
, "PSI: CPP MACRO num_exp -> %s ", decl
->token
->text
->val
);
394 fprintf(stderr
, "PSI: CPP MACRO decl -> %s ", decl
->token
->text
->val
);
396 psi_cpp_macro_decl_dump(2, decl
);
397 fprintf(stderr
, "\n");
399 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
402 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
404 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
407 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
409 struct psi_validate_scope scope
= {0};
411 scope
.defs
= &cpp
->defs
;
412 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
415 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, &cpp
->defs
)) {
421 static inline bool try_include(struct psi_cpp
*cpp
, const char *path
, bool *parsed
)
423 struct psi_parser_input
*include
;
425 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include trying %s\n", path
);
427 include
= psi_parser_open_file(cpp
->parser
, path
, false);
429 struct psi_plist
*tokens
;
431 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
433 tokens
= psi_parser_scan(cpp
->parser
, include
);
435 *parsed
= psi_cpp_process(cpp
, &tokens
);
438 size_t num_tokens
= psi_plist_count(tokens
);
441 psi_cpp_tokiter_add_range(cpp
, num_tokens
, psi_plist_eles(tokens
));
444 psi_plist_free(tokens
);
447 psi_parser_input_free(&include
);
449 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
455 static inline void include_path(const struct psi_token
*file
, char **path
)
457 if (file
->text
->val
[0] == '/') {
458 *path
= file
->text
->val
;
463 strncpy(*path
, file
->file
->val
, PATH_MAX
);
465 dir
= dirname(*path
);
468 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
470 memmove(*path
, dir
, len
);
472 memcpy(&(*path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
476 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
480 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
481 /* first try as is, full or relative path */
482 char temp
[PATH_MAX
], *path
= temp
;
484 include_path(file
, &path
);
486 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
489 if (try_include(cpp
, path
, &parsed
)) {
495 /* look through search paths */
496 if (file
->text
->val
[0] != '/') {
501 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
502 if ((sep
= strchr(cpp
->search
, ':'))) {
503 cpp
->search
= sep
+ 1;
505 /* point to end of string */
506 cpp
->search
+= strlen(cpp
->search
);
510 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
511 cpp
->search
= PSI_G(search_path
);
517 sep
= strchr(cpp
->search
, ':');
518 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
520 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
521 if ((flags
& PSI_CPP_INCLUDE_ONCE
) && zend_hash_str_exists(&cpp
->once
, path
, p_len
)) {
524 if (try_include(cpp
, path
, &parsed
)) {
530 cpp
->search
= sep
+ 1;