Skip to content

Commit

Permalink
Merge branch '0.29.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
scoder committed Mar 2, 2019
2 parents f00cd89 + d634196 commit b9eaba7
Showing 1 changed file with 56 additions and 49 deletions.
105 changes: 56 additions & 49 deletions Cython/Coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def file_tracer(self, filename):
# is not from the main .pyx file but a file with a different
# name than the .c file (which prevents us from finding the
# .c file)
_, code = self._parse_lines(c_file, filename)
_, code = self._read_source_lines(c_file, filename)
if code is None:
return None # no source found

Expand All @@ -104,7 +104,7 @@ def file_reporter(self, filename):
c_file, _ = self._find_source_files(filename)
if not c_file:
return None # unknown file
rel_file_path, code = self._parse_lines(c_file, filename)
rel_file_path, code = self._read_source_lines(c_file, filename)
if code is None:
return None # no source found
return CythonModuleReporter(c_file, filename, rel_file_path, code)
Expand Down Expand Up @@ -170,14 +170,14 @@ def _find_c_source_files(self, dir_path, source_file):
for filename in os.listdir(dir_path):
ext = splitext(filename)[1].lower()
if ext in C_FILE_EXTENSIONS:
self._parse_lines(os.path.join(dir_path, filename), source_file)
self._read_source_lines(os.path.join(dir_path, filename), source_file)
if source_file in self._c_files_map:
return
# not found? then try one package up
if is_package_dir(dir_path):
self._find_c_source_files(os.path.dirname(dir_path), source_file)

def _parse_lines(self, c_file, sourcefile):
def _read_source_lines(self, c_file, sourcefile):
"""
Parse a Cython generated C/C++ source file and find the executable lines.
Each executable line starts with a comment header that states source file
Expand All @@ -188,51 +188,7 @@ def _parse_lines(self, c_file, sourcefile):
if c_file in self._parsed_c_files:
code_lines = self._parsed_c_files[c_file]
else:
match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match
match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match
match_comment_end = re.compile(r' *[*]/$').match
match_trace_line = re.compile(r' *__Pyx_TraceLine\(([0-9]+),').match
not_executable = re.compile(
r'\s*c(?:type)?def\s+'
r'(?:(?:public|external)\s+)?'
r'(?:struct|union|enum|class)'
r'(\s+[^:]+|)\s*:'
).match

code_lines = defaultdict(dict)
executable_lines = defaultdict(set)
current_filename = None
with open(c_file) as lines:
lines = iter(lines)
for line in lines:
match = match_source_path_line(line)
if not match:
if '__Pyx_TraceLine(' in line and current_filename is not None:
trace_line = match_trace_line(line)
if trace_line:
executable_lines[current_filename].add(int(trace_line.group(1)))
continue
filename, lineno = match.groups()
current_filename = filename
lineno = int(lineno)
for comment_line in lines:
match = match_current_code_line(comment_line)
if match:
code_line = match.group(1).rstrip()
if not_executable(code_line):
break
code_lines[filename][lineno] = code_line
break
elif match_comment_end(comment_line):
# unexpected comment format - false positive?
break

# Remove lines that generated code but are not traceable.
for filename, lines in code_lines.items():
dead_lines = set(lines).difference(executable_lines.get(filename, ()))
for lineno in dead_lines:
del lines[lineno]

code_lines = self._parse_cfile_lines(c_file)
self._parsed_c_files[c_file] = code_lines

if self._c_files_map is None:
Expand All @@ -246,6 +202,57 @@ def _parse_lines(self, c_file, sourcefile):
return (None,) * 2 # e.g. shared library file
return self._c_files_map[sourcefile][1:]

def _parse_cfile_lines(self, c_file):
"""
Parse a C file and extract all source file lines that generated executable code.
"""
match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match
match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match
match_comment_end = re.compile(r' *[*]/$').match
match_trace_line = re.compile(r' *__Pyx_TraceLine\(([0-9]+),').match
not_executable = re.compile(
r'\s*c(?:type)?def\s+'
r'(?:(?:public|external)\s+)?'
r'(?:struct|union|enum|class)'
r'(\s+[^:]+|)\s*:'
).match

code_lines = defaultdict(dict)
executable_lines = defaultdict(set)
current_filename = None

with open(c_file) as lines:
lines = iter(lines)
for line in lines:
match = match_source_path_line(line)
if not match:
if '__Pyx_TraceLine(' in line and current_filename is not None:
trace_line = match_trace_line(line)
if trace_line:
executable_lines[current_filename].add(int(trace_line.group(1)))
continue
filename, lineno = match.groups()
current_filename = filename
lineno = int(lineno)
for comment_line in lines:
match = match_current_code_line(comment_line)
if match:
code_line = match.group(1).rstrip()
if not_executable(code_line):
break
code_lines[filename][lineno] = code_line
break
elif match_comment_end(comment_line):
# unexpected comment format - false positive?
break

# Remove lines that generated code but are not traceable.
for filename, lines in code_lines.items():
dead_lines = set(lines).difference(executable_lines.get(filename, ()))
for lineno in dead_lines:
del lines[lineno]
return code_lines


class CythonModuleTracer(FileTracer):
"""
Expand Down

0 comments on commit b9eaba7

Please sign in to comment.