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 void psi_cpp_free(struct psi_cpp
**cpp_ptr
)
120 struct psi_cpp
*cpp
= *cpp_ptr
;
123 zend_hash_destroy(&cpp
->defs
);
124 zend_hash_destroy(&cpp
->once
);
129 static bool psi_cpp_stage1(struct psi_cpp
*cpp
)
131 bool name
= false, define
= false, hash
= false, eol
= true, esc
= false, ws
= false;
133 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage1");
135 psi_cpp_tokiter_reset(cpp
);
136 while (psi_cpp_tokiter_valid(cpp
)) {
137 struct psi_token
*token
= psi_cpp_tokiter_current(cpp
);
139 /* strip comments and attributes */
140 if (token
->type
== PSI_T_COMMENT
141 || token
->type
== PSI_T_CPP_ATTRIBUTE
) {
142 psi_cpp_tokiter_del_cur(cpp
, true);
146 /* line continuations */
147 if (token
->type
== PSI_T_EOL
) {
149 psi_cpp_tokiter_del_prev(cpp
, true);
150 psi_cpp_tokiter_del_cur(cpp
, true);
154 } else if (token
->type
== PSI_T_BSLASH
) {
160 /* this whole turf is needed to distinct between:
161 * #define foo (1,2,3)
165 if (token
->type
== PSI_T_WHITESPACE
) {
170 psi_cpp_tokiter_del_cur(cpp
, true);
174 switch (token
->type
) {
200 /* mask special token for parser */
201 struct psi_token
*no_ws
= psi_token_copy(token
);
203 no_ws
->type
= PSI_T_NO_WHITESPACE
;
204 zend_string_release(no_ws
->text
);
205 no_ws
->text
= zend_string_init_interned("\xA0", 1, 1);
206 psi_cpp_tokiter_add(cpp
, no_ws
);
212 name
= define
= hash
= eol
= false;
217 psi_cpp_tokiter_add_cur(cpp
);
218 psi_cpp_tokiter_next(cpp
);
224 static bool psi_cpp_stage2(struct psi_cpp
*cpp
)
226 struct psi_plist
*parser_tokens
= psi_plist_init((psi_plist_dtor
) psi_token_free
);
227 bool is_eol
= true, do_cpp
= false, do_expansion
= true, skip_paren
= false, skip_all
= false;
229 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s\n", "stage2");
231 psi_cpp_tokiter_reset(cpp
);
232 while (psi_cpp_tokiter_valid(cpp
)) {
233 struct psi_token
*current
= psi_cpp_tokiter_current(cpp
);
235 if (current
->type
== PSI_T_HASH
) {
240 } else if (current
->type
== PSI_T_EOL
) {
242 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_EOL\n");
248 psi_cpp_tokiter_del_cur(cpp
, true);
255 switch (current
->type
) {
258 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_DEFINE, skip_all\n");
260 do_expansion
= false;
270 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=false, PSI_T_{IF{,N},UN}DEF\n");
272 do_expansion
= false;
282 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=true, PSI_T_LPAREN, !skip_all, !skip_paren\n");
290 do_expansion
= !skip_all
;
292 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP do_expansion=%s, <- !skip_all\n", do_expansion
?"true":"false");
301 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP skip ");
302 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, current
);
304 psi_cpp_tokiter_del_cur(cpp
, true);
309 if (do_expansion
&& current
->type
== PSI_T_NAME
&& psi_cpp_tokiter_defined(cpp
)) {
310 bool expanded
= false;
312 while (psi_cpp_tokiter_expand(cpp
)) {
321 parser_tokens
= psi_plist_add(parser_tokens
, ¤t
);
324 size_t processed
= 0;
325 bool parsed
= psi_parser_process(cpp
->parser
, parser_tokens
, &processed
);
328 psi_plist_pop(parser_tokens
, NULL
);
329 psi_plist_clean(parser_tokens
);
333 psi_plist_free(parser_tokens
);
337 /* leave EOLs in the input stream, else we might end up
338 * with a hash not preceded with a new line after include */
339 psi_cpp_tokiter_del_cur(cpp
, false);
342 #if PSI_CPP_DEBUG > 1
343 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_tokiter_dump
, cpp
);
349 psi_cpp_tokiter_add_cur(cpp
);
350 psi_cpp_tokiter_next(cpp
);
353 psi_plist_free(parser_tokens
);
358 bool psi_cpp_process(struct psi_cpp
*cpp
, struct psi_plist
**tokens
)
361 struct psi_cpp temp
= *cpp
; cpp
->level
= temp
.level
;
363 cpp
->tokens
.iter
= *tokens
;
364 cpp
->tokens
.next
= NULL
;
366 if (psi_cpp_stage1(cpp
) && psi_cpp_stage2(cpp
)) {
370 if (cpp
->tokens
.next
) {
371 free(cpp
->tokens
.iter
);
372 cpp
->tokens
.iter
= cpp
->tokens
.next
;
373 cpp
->tokens
.next
= NULL
;
376 *tokens
= cpp
->tokens
.iter
;
378 if (temp
.tokens
.iter
) {
379 cpp
->tokens
.iter
= temp
.tokens
.iter
;
380 cpp
->tokens
.next
= temp
.tokens
.next
;
382 cpp
->index
= temp
.index
;
383 cpp
->skip
= temp
.skip
;
384 cpp
->level
= temp
.level
;
385 cpp
->seen
= temp
.seen
;
390 bool psi_cpp_defined(struct psi_cpp
*cpp
, struct psi_token
*tok
)
394 if (tok
->type
== PSI_T_NAME
) {
395 defined
= zend_hash_exists(&cpp
->defs
, tok
->text
)
396 || psi_builtin_exists(tok
->text
);
402 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP defined -> %s ", defined
? "true" : "false");
404 struct psi_cpp_macro_decl
*macro
= zend_hash_find_ptr(&cpp
->defs
, tok
->text
);
406 PSI_DEBUG_PRINT(cpp
->parser
, " @ %s:%u ", macro
->token
->file
->val
, macro
->token
->line
);
409 PSI_DEBUG_DUMP(cpp
->parser
, psi_token_dump
, tok
);
415 void psi_cpp_define(struct psi_cpp
*cpp
, struct psi_cpp_macro_decl
*decl
)
417 struct psi_cpp_macro_decl
*old
= zend_hash_find_ptr(&cpp
->defs
, decl
->token
->text
);
419 if (old
&& !psi_cpp_macro_decl_equal(old
, decl
)) {
420 cpp
->parser
->error(PSI_DATA(cpp
->parser
), decl
->token
, PSI_WARNING
,
421 "'%s' redefined", decl
->token
->text
->val
);
422 cpp
->parser
->error(PSI_DATA(cpp
->parser
), old
->token
, PSI_WARNING
,
423 "'%s' previously defined", old
->token
->text
->val
);
427 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO num_exp -> ");
429 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP MACRO decl -> ");
431 PSI_DEBUG_DUMP(cpp
->parser
, psi_cpp_macro_decl_dump
, decl
);
432 PSI_DEBUG_PRINT(cpp
->parser
, "\n");
434 zend_hash_update_ptr(&cpp
->defs
, decl
->token
->text
, decl
);
437 bool psi_cpp_undef(struct psi_cpp
*cpp
, struct psi_token
*tok
)
439 return SUCCESS
== zend_hash_del(&cpp
->defs
, tok
->text
);
442 bool psi_cpp_if(struct psi_cpp
*cpp
, struct psi_cpp_exp
*exp
)
444 struct psi_validate_scope scope
= {0};
447 if (!psi_num_exp_validate(PSI_DATA(cpp
->parser
), exp
->data
.num
, &scope
)) {
450 if (!psi_num_exp_get_long(exp
->data
.num
, NULL
, cpp
)) {
456 bool psi_cpp_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
)
460 struct psi_plist
*tokens
;
461 struct psi_parser_input
*include
;
463 if (!psi_cpp_has_include(cpp
, file
, flags
, path
)) {
467 if (flags
& PSI_CPP_INCLUDE_ONCE
) {
468 if (zend_hash_str_exists(&cpp
->once
, path
, strlen(path
))) {
473 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s opening %s\n",
474 include_flavor
[flags
], path
);
476 include
= psi_parser_open_file(cpp
->parser
, path
, false);
481 zend_hash_str_add_empty_element(&cpp
->once
, path
, strlen(path
));
483 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP include scanning %s\n", path
);
485 tokens
= psi_parser_scan(cpp
->parser
, include
);
486 psi_parser_input_free(&include
);
492 parsed
= psi_cpp_process(cpp
, &tokens
);
494 psi_plist_free(tokens
);
498 psi_cpp_tokiter_add_range(cpp
, psi_plist_count(tokens
), psi_plist_eles(tokens
));
506 # define eaccess access
508 bool psi_cpp_has_include(struct psi_cpp
*cpp
, const struct psi_token
*file
, unsigned flags
, char *path
)
516 if (file
->type
== PSI_T_QUOTED_STRING
&& (!(flags
& PSI_CPP_INCLUDE_NEXT
) || file
->text
->val
[0] == '/')) {
517 /* first try as is, full or relative path */
518 if (file
->text
->val
[0] == '/') {
519 path
= file
->text
->val
;
524 strncpy(path
, file
->file
->val
, PATH_MAX
);
529 assert(len
+ file
->text
->len
+ 1 < PATH_MAX
);
531 memmove(path
, dir
, len
);
533 memcpy(&(path
)[len
+ 1], file
->text
->val
, file
->text
->len
+ 1);
536 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
537 include_flavor
[flags
], path
);
538 if (0 == eaccess(path
, R_OK
)) {
543 /* look through search paths */
544 if (file
->text
->val
[0] != '/') {
548 if ((flags
& PSI_CPP_INCLUDE_NEXT
) && cpp
->search
) {
549 if ((sep
= strchr(cpp
->search
, ':'))) {
550 cpp
->search
= sep
+ 1;
552 /* point to end of string */
553 cpp
->search
+= strlen(cpp
->search
);
557 if (!(flags
& PSI_CPP_INCLUDE_NEXT
)) {
558 cpp
->search
= PSI_G(search_path
);
564 sep
= strchr(cpp
->search
, ':');
565 d_len
= sep
? sep
- cpp
->search
: strlen(cpp
->search
);
567 if (PATH_MAX
> (p_len
= snprintf(path
, PATH_MAX
, "%.*s/%.*s", d_len
, cpp
->search
, (int) file
->text
->len
, file
->text
->val
))) {
568 PSI_DEBUG_PRINT(cpp
->parser
, "PSI: CPP %s trying %s\n",
569 include_flavor
[flags
], path
);
570 if (0 == eaccess(path
, R_OK
)) {
576 cpp
->search
= sep
+ 1;