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