1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) 1997-2020 Derick Rethans                               |
4    +----------------------------------------------------------------------+
5    | This source file is subject to the 2-Clause BSD license which is     |
6    | available through the LICENSE file, or online at                     |
7    | http://opensource.org/licenses/bsd-license.php                       |
8    +----------------------------------------------------------------------+
9  */
10 #include <stdlib.h>
11 #include <math.h>
12 
13 #include "php_xdebug.h"
14 #include "code_coverage_private.h"
15 
16 #include "lib/hash.h"
17 #include "lib/log.h"
18 #include "lib/str.h"
19 
ZEND_EXTERN_MODULE_GLOBALS(xdebug)20 ZEND_EXTERN_MODULE_GLOBALS(xdebug)
21 
22 xdebug_branch_info *xdebug_branch_info_create(unsigned int size)
23 {
24 	xdebug_branch_info *tmp;
25 
26 	tmp = calloc(1, sizeof(xdebug_branch_info));
27 	tmp->size = size;
28 	tmp->branches = calloc(size, sizeof(xdebug_branch));
29 	tmp->entry_points = xdebug_set_create(size);
30 	tmp->starts       = xdebug_set_create(size);
31 	tmp->ends         = xdebug_set_create(size);
32 
33 	tmp->path_info.paths_count = 0;
34 	tmp->path_info.paths_size  = 0;
35 	tmp->path_info.paths = NULL;
36 
37 	return tmp;
38 }
39 
xdebug_branch_info_free(xdebug_branch_info * branch_info)40 void xdebug_branch_info_free(xdebug_branch_info *branch_info)
41 {
42 	unsigned int i;
43 
44 	for (i = 0; i < branch_info->path_info.paths_count; i++) {
45 		free(branch_info->path_info.paths[i]->elements);
46 		free(branch_info->path_info.paths[i]);
47 	}
48 	free(branch_info->path_info.paths);
49 	xdebug_hash_destroy(branch_info->path_info.path_hash);
50 	free(branch_info->branches);
51 	xdebug_set_free(branch_info->entry_points);
52 	xdebug_set_free(branch_info->starts);
53 	xdebug_set_free(branch_info->ends);
54 	free(branch_info);
55 }
56 
xdebug_branch_info_update(xdebug_branch_info * branch_info,unsigned int pos,unsigned int lineno,unsigned int outidx,unsigned int jump_pos)57 void xdebug_branch_info_update(xdebug_branch_info *branch_info, unsigned int pos, unsigned int lineno, unsigned int outidx, unsigned int jump_pos)
58 {
59 	xdebug_set_add(branch_info->ends, pos);
60 	if (outidx < XDEBUG_BRANCH_MAX_OUTS) {
61 		branch_info->branches[pos].outs[outidx] = jump_pos;
62 		if (outidx + 1 > branch_info->branches[pos].outs_count) {
63 			branch_info->branches[pos].outs_count = outidx + 1;
64 		}
65 	}
66 	branch_info->branches[pos].start_lineno = lineno;
67 }
68 
only_leave_first_catch(zend_op_array * opa,xdebug_branch_info * branch_info,int position)69 static void only_leave_first_catch(zend_op_array *opa, xdebug_branch_info *branch_info, int position)
70 {
71 	unsigned int exit_jmp;
72 #if PHP_VERSION_ID >= 70300 && ZEND_USE_ABS_JMP_ADDR
73 	zend_op *base_address = &(opa->opcodes[0]);
74 #endif
75 
76 	if (opa->opcodes[position].opcode == ZEND_FETCH_CLASS) {
77 		position++;
78 	}
79 
80 	if (opa->opcodes[position].opcode != ZEND_CATCH) {
81 		return;
82 	}
83 
84 	xdebug_set_remove(branch_info->entry_points, position);
85 
86 #if PHP_VERSION_ID >= 70300
87 	if (opa->opcodes[position].extended_value & ZEND_LAST_CATCH) {
88 		return;
89 	}
90 	exit_jmp = XDEBUG_ZNODE_JMP_LINE(opa->opcodes[position].op2, position, base_address);
91 #else
92 	if (opa->opcodes[position].result.num) {
93 		return;
94 	}
95 	exit_jmp = position + ((signed int) opa->opcodes[position].extended_value / sizeof(zend_op));
96 #endif
97 
98 	if (opa->opcodes[exit_jmp].opcode == ZEND_FETCH_CLASS) {
99 		exit_jmp++;
100 	}
101 	if (opa->opcodes[exit_jmp].opcode == ZEND_CATCH) {
102 		only_leave_first_catch(opa, branch_info, exit_jmp);
103 	}
104 }
105 
xdebug_branch_post_process(zend_op_array * opa,xdebug_branch_info * branch_info)106 void xdebug_branch_post_process(zend_op_array *opa, xdebug_branch_info *branch_info)
107 {
108 	unsigned int i;
109 	int          in_branch = 0, last_start = -1;
110 #if PHP_VERSION_ID >= 70300 && ZEND_USE_ABS_JMP_ADDR
111 	zend_op *base_address = &(opa->opcodes[0]);
112 #endif
113 
114 	/* Figure out which CATCHes are chained, and hence which ones should be
115 	 * considered entry points */
116 	for (i = 0; i < branch_info->entry_points->size; i++) {
117 		if (xdebug_set_in(branch_info->entry_points, i) && opa->opcodes[i].opcode == ZEND_CATCH) {
118 #if PHP_VERSION_ID >= 70300
119 # if ZEND_USE_ABS_JMP_ADDR
120 			if (opa->opcodes[i].op2.jmp_addr != NULL) {
121 # else
122 			if (opa->opcodes[i].op2.jmp_offset != 0) {
123 # endif
124 				only_leave_first_catch(opa, branch_info, XDEBUG_ZNODE_JMP_LINE(opa->opcodes[i].op2, i, base_address));
125 			}
126 #else
127 			only_leave_first_catch(opa, branch_info, i + ((signed int) opa->opcodes[i].extended_value / sizeof(zend_op)));
128 #endif
129 		}
130 	}
131 
132 	for (i = 0; i < branch_info->starts->size; i++) {
133 		if (xdebug_set_in(branch_info->starts, i)) {
134 			if (in_branch) {
135 				branch_info->branches[last_start].outs_count = 1;
136 				branch_info->branches[last_start].outs[0] = i;
137 				branch_info->branches[last_start].end_op = i-1;
138 				branch_info->branches[last_start].end_lineno = branch_info->branches[i].start_lineno;
139 			}
140 			last_start = i;
141 			in_branch = 1;
142 		}
143 		if (xdebug_set_in(branch_info->ends, i)) {
144 			size_t j;
145 
146 			for (j = 0; j < branch_info->branches[i].outs_count; j++) {
147 				branch_info->branches[last_start].outs[j] = branch_info->branches[i].outs[j];
148 			}
149 			branch_info->branches[last_start].outs_count = branch_info->branches[i].outs_count;
150 			branch_info->branches[last_start].end_op = i;
151 			branch_info->branches[last_start].end_lineno = branch_info->branches[i].start_lineno;
152 			in_branch = 0;
153 		}
154 	}
155 }
156 
157 static void xdebug_path_add(xdebug_path *path, unsigned int nr)
158 {
159 	if (!path) {
160 		return;
161 	}
162 	if (path->elements_count == path->elements_size) {
163 		path->elements_size += 32;
164 		path->elements = realloc(path->elements, sizeof(unsigned int) * path->elements_size);
165 	}
166 	path->elements[path->elements_count] = nr;
167 	path->elements_count++;
168 }
169 
170 static void xdebug_path_info_add_path(xdebug_path_info *path_info, xdebug_path *path)
171 {
172 	if (path_info->paths_count == path_info->paths_size) {
173 		path_info->paths_size += 32;
174 		path_info->paths = realloc(path_info->paths, sizeof(xdebug_path*) * path_info->paths_size);
175 	}
176 	path_info->paths[path_info->paths_count] = path;
177 	path_info->paths_count++;
178 }
179 
180 static void xdebug_path_info_make_sure_level_exists(xdebug_path_info *path_info, unsigned int level)
181 {
182 	unsigned int i = 0, orig_size;
183 
184 	orig_size = path_info->paths_size;
185 
186 	if (level >= path_info->paths_size) {
187 		path_info->paths_size = level + 32;
188 		path_info->paths = realloc(path_info->paths, sizeof(xdebug_path*) * path_info->paths_size);
189 
190 		for (i = orig_size; i < XG_COV(branches).size; i++) {
191 			XG_COV(branches).last_branch_nr[i] = -1;
192 		}
193 
194 		for (i = orig_size; i < path_info->paths_size; i++) {
195 			path_info->paths[i] = NULL;
196 		}
197 	}
198 }
199 
200 void xdebug_path_info_add_path_for_level(xdebug_path_info *path_info, xdebug_path *path, unsigned int level)
201 {
202 	xdebug_path_info_make_sure_level_exists(path_info, level);
203 	path_info->paths[level] = path;
204 }
205 
206 xdebug_path *xdebug_path_info_get_path_for_level(xdebug_path_info *path_info, unsigned int level)
207 {
208 	xdebug_path_info_make_sure_level_exists(path_info, level);
209 	return path_info->paths[level];
210 }
211 
212 xdebug_path *xdebug_path_new(xdebug_path *old_path)
213 {
214 	xdebug_path *tmp;
215 	tmp = calloc(1, sizeof(xdebug_path));
216 
217 	if (old_path) {
218 		unsigned i;
219 
220 		for (i = 0; i < old_path->elements_count; i++) {
221 			xdebug_path_add(tmp, old_path->elements[i]);
222 		}
223 	}
224 	return tmp;
225 }
226 
227 void xdebug_path_free(xdebug_path *path)
228 {
229 	if (path->elements) {
230 		free(path->elements);
231 	}
232 	free(path);
233 }
234 
235 static unsigned int xdebug_branch_find_last_element(xdebug_path *path)
236 {
237 	return path->elements[path->elements_count-1];
238 }
239 
240 static int xdebug_path_exists(xdebug_path *path, unsigned int elem1, unsigned int elem2)
241 {
242 	unsigned int i;
243 
244 	for (i = 0; i < path->elements_count - 1; i++) {
245 		if (path->elements[i] == elem1 && path->elements[i + 1] == elem2) {
246 			return 1;
247 		}
248 	}
249 	return 0;
250 }
251 
252 static void xdebug_branch_find_path(unsigned int nr, xdebug_branch_info *branch_info, xdebug_path *prev_path)
253 {
254 	unsigned int last;
255 	xdebug_path *new_path;
256 	int found = 0;
257 	size_t i = 0;
258 
259 	if (branch_info->path_info.paths_count > 4095) {
260 		return;
261 	}
262 
263 	new_path = xdebug_path_new(prev_path);
264 	xdebug_path_add(new_path, nr);
265 
266 	last = xdebug_branch_find_last_element(new_path);
267 
268 	for (i = 0; i < branch_info->branches[nr].outs_count; i++) {
269 		int out = branch_info->branches[nr].outs[i];
270 		if (out != 0 && out != XDEBUG_JMP_EXIT && !xdebug_path_exists(new_path, last, out)) {
271 			xdebug_branch_find_path(out, branch_info, new_path);
272 			found = 1;
273 		}
274 	}
275 
276 	if (found) {
277 		xdebug_path_free(new_path);
278 		return;
279 	}
280 
281 	xdebug_path_info_add_path(&(branch_info->path_info), new_path);
282 }
283 
284 xdebug_path_info *xdebug_path_info_ctor(void)
285 {
286 	xdebug_path_info *tmp;
287 
288 	tmp = xdmalloc(sizeof(xdebug_path_info));
289 	tmp->paths_count = 0;
290 	tmp->paths_size = 0;
291 	tmp->paths = NULL;
292 	tmp->path_hash = NULL;
293 
294 	return tmp;
295 }
296 
297 void xdebug_path_info_dtor(xdebug_path_info *path_info)
298 {
299 	unsigned int i;
300 
301 	for (i = 0; i < path_info->paths_count; i++) {
302 		xdebug_path_free(path_info->paths[i]);
303 	}
304 	xdfree(path_info->paths);
305 	path_info->paths = NULL;
306 	if (path_info->path_hash) {
307 		xdebug_hash_destroy(path_info->path_hash);
308 		path_info->path_hash = NULL;
309 	}
310 
311 	xdfree(path_info);
312 }
313 
314 void xdebug_create_key_for_path(xdebug_path *path, xdebug_str *str)
315 {
316 	unsigned int i;
317 	char temp_nr[16];
318 
319 	for (i = 0; i < path->elements_count; i++) {
320 		snprintf(temp_nr, 15, "%u:", path->elements[i]);
321 		xdebug_str_add(str, temp_nr, 0);
322 	}
323 }
324 
325 void xdebug_branch_find_paths(xdebug_branch_info *branch_info)
326 {
327 	unsigned int i;
328 
329 	for (i = 0; i < branch_info->entry_points->size; i++) {
330 		if (xdebug_set_in(branch_info->entry_points, i)) {
331 			xdebug_branch_find_path(i, branch_info, NULL);
332 		}
333 	}
334 
335 	branch_info->path_info.path_hash = xdebug_hash_alloc(128, NULL);
336 
337 	for (i = 0; i < branch_info->path_info.paths_count; i++) {
338 		xdebug_str str = XDEBUG_STR_INITIALIZER;
339 		xdebug_create_key_for_path(branch_info->path_info.paths[i], &str);
340 		xdebug_hash_add(branch_info->path_info.path_hash, str.d, str.l, branch_info->path_info.paths[i]);
341 		xdfree(str.d);
342 	}
343 }
344 
345 void xdebug_branch_info_mark_reached(zend_string *filename, char *function_name, zend_op_array *op_array, long opcode_nr)
346 {
347 	xdebug_coverage_file *file;
348 	xdebug_coverage_function *function;
349 	xdebug_branch_info *branch_info;
350 
351 	if (XG_COV(previous_mark_filename) && zend_string_equals(XG_COV(previous_mark_filename), filename)) {
352 		file = XG_COV(previous_mark_file);
353 	} else {
354 		if (!xdebug_hash_find(XG_COV(code_coverage_info), ZSTR_VAL(filename), ZSTR_LEN(filename), (void *) &file)) {
355 			return;
356 		}
357 		if (XG_COV(previous_mark_filename)) {
358 			zend_string_release(XG_COV(previous_mark_filename));
359 		}
360 		XG_COV(previous_mark_filename) = zend_string_copy(file->name);
361 		XG_COV(previous_mark_file) = file;
362 	}
363 
364 	/* If there is no branch info, we don't have to do more */
365 	if (!file->has_branch_info) {
366 		return;
367 	}
368 
369 	/* Check if the function already exists in the hash */
370 	if (!xdebug_hash_find(file->functions, function_name, strlen(function_name), (void *) &function)) {
371 		return;
372 	}
373 
374 	branch_info = function->branch_info;
375 
376 	if (opcode_nr != 0 && xdebug_set_in(branch_info->entry_points, opcode_nr)) {
377 		xdebug_code_coverage_end_of_function(op_array, filename, function_name);
378 		xdebug_code_coverage_start_of_function(op_array, function_name);
379 	}
380 
381 	if (xdebug_set_in(branch_info->starts, opcode_nr)) {
382 		char *key;
383 		void *dummy;
384 		function_stack_entry *tail_fse = XDEBUG_VECTOR_TAIL(XG_BASE(stack));
385 
386 		/* Mark out for previous branch, if one is set */
387 		if (XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))] != -1) {
388 			size_t i = 0;
389 
390 			for (i = 0; i < branch_info->branches[XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))]].outs_count; i++) {
391 				if (branch_info->branches[XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))]].outs[i] == opcode_nr) {
392 					branch_info->branches[XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))]].outs_hit[i] = 1;
393 				}
394 			}
395 		}
396 
397 		key = xdebug_sprintf("%d:%d:%d", opcode_nr, XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))], tail_fse->function_nr);
398 
399 		if (!xdebug_hash_find(XG_COV(visited_branches), key, strlen(key), (void*) &dummy)) {
400 			xdebug_path_add(XG_COV(paths_stack)->paths[XDEBUG_VECTOR_COUNT(XG_BASE(stack))], opcode_nr);
401 			xdebug_hash_add(XG_COV(visited_branches), key, strlen(key), NULL);
402 		}
403 		xdfree(key);
404 
405 		branch_info->branches[opcode_nr].hit = 1;
406 
407 		XG_COV(branches).last_branch_nr[XDEBUG_VECTOR_COUNT(XG_BASE(stack))] = opcode_nr;
408 	}
409 }
410 
411 void xdebug_branch_info_mark_end_of_function_reached(zend_string *filename, char *function_name, char *key, int key_len)
412 {
413 	xdebug_coverage_file *file;
414 	xdebug_coverage_function *function;
415 	xdebug_branch_info *branch_info;
416 	xdebug_path *path;
417 
418 	if (XG_COV(previous_mark_filename) && zend_string_equals(XG_COV(previous_mark_filename), filename) == 0) {
419 		file = XG_COV(previous_mark_file);
420 	} else {
421 		if (!xdebug_hash_find(XG_COV(code_coverage_info), ZSTR_VAL(filename), ZSTR_LEN(filename), (void *) &file)) {
422 			return;
423 		}
424 		zend_string_release(XG_COV(previous_mark_filename));
425 		XG_COV(previous_mark_filename) = zend_string_copy(file->name);
426 		XG_COV(previous_mark_file) = file;
427 	}
428 
429 	/* If there is no branch info, we don't have to do more */
430 	if (!file->has_branch_info) {
431 		return;
432 	}
433 
434 	/* Check if the function already exists in the hash */
435 	if (!xdebug_hash_find(file->functions, function_name, strlen(function_name), (void *) &function)) {
436 		return;
437 	}
438 
439 	branch_info = function->branch_info;
440 
441 	if (!xdebug_hash_find(branch_info->path_info.path_hash, key, key_len, (void *) &path)) {
442 		return;
443 	}
444 	path->hit = 1;
445 }
446 
447 void xdebug_branch_info_add_branches_and_paths(zend_string *filename, char *function_name, xdebug_branch_info *branch_info)
448 {
449 	xdebug_coverage_file *file;
450 	xdebug_coverage_function *function;
451 
452 	if (XG_COV(previous_filename) && zend_string_equals(XG_COV(previous_filename), filename) == 0) {
453 		file = XG_COV(previous_file);
454 	} else {
455 		/* Check if the file already exists in the hash */
456 		if (!xdebug_hash_find(XG_COV(code_coverage_info), ZSTR_VAL(filename), ZSTR_LEN(filename), (void *) &file)) {
457 			/* The file does not exist, so we add it to the hash */
458 			file = xdebug_coverage_file_ctor(filename);
459 
460 			xdebug_hash_add(XG_COV(code_coverage_info), ZSTR_VAL(filename), ZSTR_LEN(filename), file);
461 		}
462 		zend_string_release(XG_COV(previous_filename));
463 		XG_COV(previous_filename) = zend_string_copy(file->name);
464 		XG_COV(previous_file) = file;
465 	}
466 
467 	/* Check if the function already exists in the hash */
468 	if (!xdebug_hash_find(file->functions, function_name, strlen(function_name), (void *) &function)) {
469 		/* The file does not exist, so we add it to the hash */
470 		function = xdebug_coverage_function_ctor(function_name);
471 
472 		xdebug_hash_add(file->functions, function_name, strlen(function_name), function);
473 	}
474 
475 	if (branch_info) {
476 		file->has_branch_info = 1;
477 	}
478 	function->branch_info = branch_info;
479 }
480