1 
2 /*
3   +------------------------------------------------------------------------+
4   | Phalcon Framework                                                      |
5   +------------------------------------------------------------------------+
6   | Copyright (c) 2011-2016 Phalcon Team (http://www.phalconphp.com)       |
7   +------------------------------------------------------------------------+
8   | This source file is subject to the New BSD License that is bundled     |
9   | with this package in the file docs/LICENSE.txt.                        |
10   |                                                                        |
11   | If you did not receive a copy of the license and are unable to         |
12   | obtain it through the world-wide-web, please send an email             |
13   | to license@phalconphp.com so we can send you a copy immediately.       |
14   +------------------------------------------------------------------------+
15   | Authors: Andres Gutierrez <andres@phalconphp.com>                      |
16   |          Eduar Carvajal <eduar@phalconphp.com>                         |
17   +------------------------------------------------------------------------+
18 */
19 
20 const phannot_token_names phannot_tokens[] =
21 {
22 	{ "INTEGER",        PHANNOT_T_INTEGER },
23 	{ "DOUBLE",         PHANNOT_T_DOUBLE },
24 	{ "STRING",         PHANNOT_T_STRING },
25 	{ "IDENTIFIER",     PHANNOT_T_IDENTIFIER },
26 	{ "@",              PHANNOT_T_AT },
27 	{ ",",              PHANNOT_T_COMMA },
28 	{ "=",              PHANNOT_T_EQUALS },
29 	{ ":",              PHANNOT_T_COLON },
30 	{ "(",              PHANNOT_T_PARENTHESES_OPEN },
31 	{ ")",              PHANNOT_T_PARENTHESES_CLOSE },
32 	{ "{",              PHANNOT_T_BRACKET_OPEN },
33 	{ "}",              PHANNOT_T_BRACKET_CLOSE },
34  	{ "[",              PHANNOT_T_SBRACKET_OPEN },
35 	{ "]",              PHANNOT_T_SBRACKET_CLOSE },
36 	{ "ARBITRARY TEXT", PHANNOT_T_ARBITRARY_TEXT },
37 	{ NULL, 0 }
38 };
39 
40 /**
41  * Wrapper to alloc memory within the parser
42  */
phannot_wrapper_alloc(size_t bytes)43 static void *phannot_wrapper_alloc(size_t bytes){
44 	return emalloc(bytes);
45 }
46 
47 /**
48  * Wrapper to free memory within the parser
49  */
phannot_wrapper_free(void * pointer)50 static void phannot_wrapper_free(void *pointer){
51 	efree(pointer);
52 }
53 
54 /**
55  * Creates a parser_token to be passed to the parser
56  */
phannot_parse_with_token(void * phannot_parser,int opcode,int parsercode,phannot_scanner_token * token,phannot_parser_status * parser_status)57 static void phannot_parse_with_token(void* phannot_parser, int opcode, int parsercode, phannot_scanner_token *token, phannot_parser_status *parser_status){
58 
59 	phannot_parser_token *pToken;
60 
61 	pToken = emalloc(sizeof(phannot_parser_token));
62 	pToken->opcode = opcode;
63 	pToken->token = token->value;
64 	pToken->token_len = token->len;
65 	pToken->free_flag = 1;
66 
67 	phannot_(phannot_parser, parsercode, pToken, parser_status);
68 
69 	token->value = NULL;
70 	token->len = 0;
71 }
72 
73 /**
74  * Creates an error message when it's triggered by the scanner
75  */
phannot_scanner_error_msg(phannot_parser_status * parser_status,char ** error_msg TSRMLS_DC)76 static void phannot_scanner_error_msg(phannot_parser_status *parser_status, char **error_msg TSRMLS_DC){
77 
78 	phannot_scanner_state *state = parser_status->scanner_state;
79 
80 	if (state->start) {
81 		if (state->start_length > 16) {
82 			spprintf(error_msg, 0, "Scanning error before '%.16s...' in %s on line %d", state->start, state->active_file, state->active_line);
83 		} else {
84 			spprintf(error_msg, 0, "Scanning error before '%s' in %s on line %d", state->start, state->active_file, state->active_line);
85 		}
86 	} else {
87 		spprintf(error_msg, 0, "Scanning error near to EOF in %s", state->active_file);
88 	}
89 }
90 
91 /**
92  * Receives the comment tokenizes and parses it
93  */
phannot_parse_annotations(zval * result,zval * comment,zval * file_path,zval * line TSRMLS_DC)94 int phannot_parse_annotations(zval *result, zval *comment, zval *file_path, zval *line TSRMLS_DC) {
95 
96 	char *comment_str;
97 	int comment_len;
98 	char *file_path_str;
99 	int line_num;
100 
101 	char *error_msg = NULL;
102 
103 	ZVAL_NULL(result);
104 
105 	if (Z_TYPE_P(comment) == IS_STRING) {
106 		comment_str = Z_STRVAL_P(comment);
107 		comment_len = Z_STRLEN_P(comment);
108 	} else {
109 		comment_str = "";
110 		comment_len = 0;
111 	}
112 
113 	if (Z_TYPE_P(file_path) == IS_STRING) {
114 		file_path_str = Z_STRVAL_P(file_path);
115 	} else {
116 		file_path_str = "eval";
117 	}
118 
119 	if (Z_TYPE_P(line) == IS_LONG) {
120 		line_num = Z_LVAL_P(line);
121 	} else {
122 		line_num = 0;
123 	}
124 
125 	if (phannot_internal_parse_annotations(&result, comment_str, comment_len, file_path_str, line_num, &error_msg TSRMLS_CC) == FAILURE) {
126 		if (likely(error_msg != NULL)) {
127 			zephir_throw_exception_string(phalcon_annotations_exception_ce, error_msg, strlen(error_msg) TSRMLS_CC);
128 			efree(error_msg);
129 		} else {
130 			zephir_throw_exception_string(phalcon_annotations_exception_ce, SL("There was an error parsing annotation") TSRMLS_CC);
131 		}
132 
133 		return FAILURE;
134 	}
135 
136 	return SUCCESS;
137 }
138 
139 /**
140  * Remove comment separators from a docblock
141  */
phannot_remove_comment_separators(char ** ret,int * ret_len,const char * comment,int length,int * start_lines)142 static void phannot_remove_comment_separators(char **ret, int *ret_len, const char *comment, int length, int *start_lines)
143 {
144 	char ch;
145 	int start_mode = 1, j, i, open_parentheses;
146 	smart_str processed_str = {0};
147 
148 	(*start_lines) = 0;
149 
150 	for (i = 0; i < length; i++) {
151 
152 		ch = comment[i];
153 
154 		if (start_mode) {
155 			if (ch == ' ' || ch == '*' || ch == '/' || ch == '\t' || ch == 11) {
156 				continue;
157 			}
158 			start_mode = 0;
159 		}
160 
161 		if (ch == '@') {
162 
163 			smart_str_appendc(&processed_str, ch);
164 			i++;
165 
166 			open_parentheses = 0;
167 			for (j = i; j < length; j++) {
168 
169 				ch = comment[j];
170 
171 				if (start_mode) {
172 					if (ch == ' ' || ch == '*' || ch == '/' || ch == '\t' || ch == 11) {
173 						continue;
174 					}
175 					start_mode = 0;
176 				}
177 
178 				if (open_parentheses == 0) {
179 
180 					if (isalnum(ch) || '_' == ch || '\\' == ch) {
181 						smart_str_appendc(&processed_str, ch);
182 						continue;
183 					}
184 
185 					if (ch == '(') {
186 						smart_str_appendc(&processed_str, ch);
187 						open_parentheses++;
188 						continue;
189 					}
190 
191 				} else {
192 
193 					smart_str_appendc(&processed_str, ch);
194 
195 					if (ch == '(') {
196 						open_parentheses++;
197 					} else {
198 						if (ch == ')') {
199 							open_parentheses--;
200 						} else {
201 							if (ch == '\n') {
202 								(*start_lines)++;
203 								start_mode = 1;
204 							}
205 						}
206 					}
207 
208 					if (open_parentheses > 0) {
209 						continue;
210 					}
211 				}
212 
213 				i = j;
214 				smart_str_appendc(&processed_str, ' ');
215 				break;
216 			}
217 		}
218 
219 		if (ch == '\n') {
220 			(*start_lines)++;
221 			start_mode = 1;
222 		}
223 	}
224 
225 	smart_str_0(&processed_str);
226 
227 #if PHP_VERSION_ID < 70000
228 	if (processed_str.len) {
229 		*ret     = processed_str.c;
230 		*ret_len = processed_str.len;
231 	} else {
232 		*ret     = NULL;
233 		*ret_len = 0;
234 	}
235 #else
236 	if (processed_str.s) {
237 		*ret     = estrndup(ZSTR_VAL(processed_str.s), ZSTR_LEN(processed_str.s));
238 		*ret_len = ZSTR_LEN(processed_str.s);
239 		smart_str_free(&processed_str);
240 	} else {
241 		*ret     = NULL;
242 		*ret_len = 0;
243 	}
244 #endif
245 }
246 
247 /**
248  * Parses a comment returning an intermediate array representation
249  */
phannot_internal_parse_annotations(zval ** result,const char * comment,int comment_len,const char * file_path,int line,char ** error_msg TSRMLS_DC)250 int phannot_internal_parse_annotations(zval **result, const char *comment, int comment_len, const char *file_path, int line, char **error_msg TSRMLS_DC)
251 {
252 	phannot_scanner_state *state;
253 	phannot_scanner_token token;
254 	int start_lines;
255 	int scanner_status, status = SUCCESS;
256 	phannot_parser_status *parser_status = NULL;
257 	void* phannot_parser;
258 	char *processed_comment;
259 	int processed_comment_len;
260 
261 	*error_msg = NULL;
262 
263 	/**
264 	 * Check if the comment has content
265 	 */
266 	if (UNEXPECTED(!comment)) {
267 		ZVAL_BOOL(*result, 0);
268 		spprintf(error_msg, 0, "Empty annotation");
269 		return FAILURE;
270 	}
271 
272 	if (comment_len < 2) {
273 		ZVAL_BOOL(*result, 0);
274 		return SUCCESS;
275 	}
276 
277 	/**
278 	 * Remove comment separators
279 	 */
280 	phannot_remove_comment_separators(&processed_comment, &processed_comment_len, comment, comment_len, &start_lines);
281 
282 	if (processed_comment_len < 2) {
283 		ZVAL_BOOL(*result, 0);
284 		if (processed_comment) {
285 			efree(processed_comment);
286 		}
287 
288 		return SUCCESS;
289 	}
290 
291 	/**
292 	 * Start the reentrant parser
293 	 */
294 	phannot_parser = phannot_Alloc(phannot_wrapper_alloc);
295 	if (unlikely(!phannot_parser)) {
296 		ZVAL_BOOL(*result, 0);
297 		return FAILURE;
298 	}
299 
300 	parser_status = emalloc(sizeof(phannot_parser_status) + sizeof(phannot_scanner_state));
301 	state         = (phannot_scanner_state*)((char*)parser_status + sizeof(phannot_parser_status));
302 
303 	parser_status->status = PHANNOT_PARSING_OK;
304 	parser_status->scanner_state = state;
305 #if PHP_VERSION_ID < 70000
306 	parser_status->ret = NULL;
307 #endif
308 	parser_status->token = &token;
309 	parser_status->syntax_error = NULL;
310 
311 	/**
312 	 * Initialize the scanner state
313 	 */
314 	state->active_token = 0;
315 	state->start = processed_comment;
316 	state->start_length = 0;
317 	state->mode = PHANNOT_MODE_RAW;
318 	state->active_file = file_path;
319 
320 	token.value = NULL;
321 	token.len = 0;
322 
323 	/**
324 	 * Possible start line
325 	 */
326 	if (line) {
327 		state->active_line = line - start_lines;
328 	} else {
329 		state->active_line = 1;
330 	}
331 
332 	state->end = state->start;
333 
334 	while(0 <= (scanner_status = phannot_get_token(state, &token))) {
335 
336 		state->active_token = token.opcode;
337 
338 		state->start_length = processed_comment + processed_comment_len - state->start;
339 
340 		switch (token.opcode) {
341 
342 			case PHANNOT_T_IGNORE:
343 				break;
344 
345 			case PHANNOT_T_AT:
346 				phannot_(phannot_parser, PHANNOT_AT, NULL, parser_status);
347 				break;
348 			case PHANNOT_T_COMMA:
349 				phannot_(phannot_parser, PHANNOT_COMMA, NULL, parser_status);
350 				break;
351 			case PHANNOT_T_EQUALS:
352 				phannot_(phannot_parser, PHANNOT_EQUALS, NULL, parser_status);
353 				break;
354 			case PHANNOT_T_COLON:
355 				phannot_(phannot_parser, PHANNOT_COLON, NULL, parser_status);
356 				break;
357 
358 			case PHANNOT_T_PARENTHESES_OPEN:
359 				phannot_(phannot_parser, PHANNOT_PARENTHESES_OPEN, NULL, parser_status);
360 				break;
361 			case PHANNOT_T_PARENTHESES_CLOSE:
362 				phannot_(phannot_parser, PHANNOT_PARENTHESES_CLOSE, NULL, parser_status);
363 				break;
364 
365 			case PHANNOT_T_BRACKET_OPEN:
366 				phannot_(phannot_parser, PHANNOT_BRACKET_OPEN, NULL, parser_status);
367 				break;
368 			case PHANNOT_T_BRACKET_CLOSE:
369 				phannot_(phannot_parser, PHANNOT_BRACKET_CLOSE, NULL, parser_status);
370 				break;
371 
372 			case PHANNOT_T_SBRACKET_OPEN:
373 				phannot_(phannot_parser, PHANNOT_SBRACKET_OPEN, NULL, parser_status);
374 				break;
375 			case PHANNOT_T_SBRACKET_CLOSE:
376 				phannot_(phannot_parser, PHANNOT_SBRACKET_CLOSE, NULL, parser_status);
377 				break;
378 
379 			case PHANNOT_T_NULL:
380 				phannot_(phannot_parser, PHANNOT_NULL, NULL, parser_status);
381 				break;
382 			case PHANNOT_T_TRUE:
383 				phannot_(phannot_parser, PHANNOT_TRUE, NULL, parser_status);
384 				break;
385 			case PHANNOT_T_FALSE:
386 				phannot_(phannot_parser, PHANNOT_FALSE, NULL, parser_status);
387 				break;
388 
389 			case PHANNOT_T_INTEGER:
390 				phannot_parse_with_token(phannot_parser, PHANNOT_T_INTEGER, PHANNOT_INTEGER, &token, parser_status);
391 				break;
392 			case PHANNOT_T_DOUBLE:
393 				phannot_parse_with_token(phannot_parser, PHANNOT_T_DOUBLE, PHANNOT_DOUBLE, &token, parser_status);
394 				break;
395 			case PHANNOT_T_STRING:
396 				phannot_parse_with_token(phannot_parser, PHANNOT_T_STRING, PHANNOT_STRING, &token, parser_status);
397 				break;
398 			case PHANNOT_T_IDENTIFIER:
399 				phannot_parse_with_token(phannot_parser, PHANNOT_T_IDENTIFIER, PHANNOT_IDENTIFIER, &token, parser_status);
400 				break;
401 			/*case PHANNOT_T_ARBITRARY_TEXT:
402 				phannot_parse_with_token(phannot_parser, PHANNOT_T_ARBITRARY_TEXT, PHANNOT_ARBITRARY_TEXT, &token, parser_status);
403 				break;*/
404 
405 			default:
406 				parser_status->status = PHANNOT_PARSING_FAILED;
407 				if (!*error_msg) {
408 					spprintf(error_msg, 0, "Scanner: unknown opcode %d on in %s line %d", token.opcode, state->active_file, state->active_line);
409 				}
410 				break;
411 		}
412 
413 		if (parser_status->status != PHANNOT_PARSING_OK) {
414 			status = FAILURE;
415 			break;
416 		}
417 
418 		state->end = state->start;
419 	}
420 
421 	if (status != FAILURE) {
422 		switch (scanner_status) {
423 
424 			case PHANNOT_SCANNER_RETCODE_ERR:
425 			case PHANNOT_SCANNER_RETCODE_IMPOSSIBLE:
426 				if (!*error_msg) {
427 					phannot_scanner_error_msg(parser_status, error_msg TSRMLS_CC);
428 				}
429 				status = FAILURE;
430 				break;
431 
432 			default:
433 				phannot_(phannot_parser, 0, NULL, parser_status);
434 		}
435 	}
436 
437 	state->active_token = 0;
438 	state->start = NULL;
439 
440 	if (parser_status->status != PHANNOT_PARSING_OK) {
441 		status = FAILURE;
442 		if (parser_status->syntax_error) {
443 			if (!*error_msg) {
444 				*error_msg = parser_status->syntax_error;
445 			} else {
446 				efree(parser_status->syntax_error);
447 			}
448 		}
449 	}
450 
451 	phannot_Free(phannot_parser, phannot_wrapper_free);
452 
453 	if (status != FAILURE) {
454 		if (parser_status->status == PHANNOT_PARSING_OK) {
455 #if PHP_VERSION_ID < 70000
456 			if (parser_status->ret) {
457 				ZVAL_ZVAL(*result, parser_status->ret, 0, 0);
458 				ZVAL_NULL(parser_status->ret);
459 				zval_ptr_dtor(&parser_status->ret);
460 			} else {
461 				array_init(*result);
462 			}
463 #else
464 			ZVAL_ZVAL(*result, &parser_status->ret, 1, 1);
465 #endif
466 		}
467 	}
468 
469 	efree(processed_comment);
470 	efree(parser_status);
471 
472 	return status;
473 }
474