| #!/usr/bin/env python |
| # Copyright 2025 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import sys |
| import re |
| import os |
| |
| import range_merger |
| |
| def apply_fixes(file_path, fixes): |
| """Applies the collected fixes to the given file. |
| Fixes are (start_line, start_col, end_line, end_col) 0-indexed tuples. |
| This function modifies the file in place.""" |
| try: |
| # Read file content |
| with open(file_path, 'r') as f: |
| lines = f.readlines() |
| except IOError as e: |
| sys.stderr.write(f"Error: Could not read file '{file_path}': {e}\n") |
| return |
| |
| # Remove trailing newlines from lines to simplify string manipulation. |
| # Newlines will be added back when writing the modified content. |
| lines = [line.rstrip('\n') for line in lines] |
| |
| # Combine adjacent and overlapping fixes |
| fixes = range_merger.merge_ranges(fixes) |
| |
| # Sort fixes in reverse order (from end of file to beginning). |
| # This is crucial to prevent earlier insertions from invalidating |
| # the line and column indices of later insertions. |
| fixes.sort(key=lambda x: (x[0], x[1]), reverse=True) |
| |
| for fix in fixes: |
| start_line, start_col, end_line, end_col = fix |
| |
| # Define the macros to be inserted |
| prefix = "UNSAFE_TODO(" |
| suffix = ")" |
| |
| # Ensure the start_line is within the bounds of the file content |
| if not (0 <= start_line < len(lines)): |
| sys.stderr.write( |
| f"Warning: Fix for '{file_path}' has out-of-bounds start line " |
| f"({start_line + 1}). Skipping fix {fix}.\n") |
| continue |
| |
| # Ensure start_col is within the bounds of the specific line |
| # Use <= because insertion can be at the end of the line |
| if not (0 <= start_col <= len(lines[start_line])): |
| sys.stderr.write( |
| f"Warning: Fix for '{file_path}' on line {start_line + 1} has " |
| f"out-of-bounds start column ({start_col + 1}). Skipping fix " |
| f"{fix}.\n") |
| continue |
| |
| # Ensure end_line is within bounds |
| if not (0 <= end_line < len(lines)): |
| sys.stderr.write( |
| f"Warning: Fix for '{file_path}' has out-of-bounds end line " |
| f"({end_line + 1}). Skipping fix {fix}.\n") |
| continue |
| |
| # Ensure end_col_adjusted is within the bounds of the specific line for. |
| # Use <= because insertion can be at the end of the line. |
| if not (0 <= end_col <= len(lines[end_line])): |
| sys.stderr.write( |
| f"Warning: Fix for '{file_path}' on line {end_line + 1} has " |
| f"out-of-bounds end column ({end_col + 1}). Skipping " |
| f"suffix for fix {fix}.\n") |
| continue |
| |
| # Insert the suffix after the character at end_col_adjusted |
| lines[end_line] = (lines[end_line][:end_col] + suffix + |
| lines[end_line][end_col:]) |
| |
| # Insert the prefix before the character at start_col |
| lines[start_line] = (lines[start_line][:start_col] + prefix + |
| lines[start_line][start_col:]) |
| |
| try: |
| # Write the modified content back to the file |
| with open(file_path, 'w') as f: |
| for line in lines: |
| f.write(line + '\n') # Re-add newline |
| sys.stderr.write(f"Successfully applied fixes to '{file_path}'\n") |
| except IOError as e: |
| sys.stderr.write(f"Error: Could not write to file '{file_path}': {e}\n") |
| |
| |
| def main(): |
| all_corrections = {} |
| |
| # Matches the error start line: file:line:col:{range}: error: ...unsafe... |
| error_start_re = re.compile( |
| rb"^../../(.*?):(\d+):(\d+):\{(\d+):(\d+)-(\d+):(\d+)\}: " |
| rb"(warning|error): .*unsafe.*") |
| |
| # Read compiler output line by line from standard input |
| for line in sys.stdin.buffer.read().splitlines(): |
| match = error_start_re.match(line) |
| if match: |
| # Canonicalize the file path to handle relative paths consistently |
| abs_file_path = os.path.abspath(match.group(1).decode('ascii')) |
| |
| # Convert to zero-based indices |
| source_range = (int(match.group(4)) - 1, int(match.group(5)) - 1, |
| int(match.group(6)) - 1, int(match.group(7)) - 1) |
| |
| entry = all_corrections.setdefault(abs_file_path, []) |
| entry.append(source_range) |
| |
| # After parsing all input from stdin, apply the collected fixes to the files |
| for file_path, fixes_list in all_corrections.items(): |
| apply_fixes(file_path, fixes_list) |
| |
| |
| if __name__ == "__main__": |
| main() |