blob: ff117652e838f0f5d839a204b5042f4f54db5c6b [file] [log] [blame]
#!/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()