From b378002a367db96295693d472cc73e06967ed5a4 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 23:11:58 +0000 Subject: [PATCH] Add CLI, utils, and fixers modules --- src/fixers/__init__.py | 212 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 src/fixers/__init__.py diff --git a/src/fixers/__init__.py b/src/fixers/__init__.py new file mode 100644 index 0000000..0af8a5f --- /dev/null +++ b/src/fixers/__init__.py @@ -0,0 +1,212 @@ +"""Fixer base class and implementations.""" + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional +import tree_sitter + +from src.analyzers.base import Finding + + +class Fixer(ABC): + """Abstract base class for code fixers.""" + + @abstractmethod + def can_fix(self, finding: Finding) -> bool: + """Check if this fixer can handle the finding.""" + pass + + @abstractmethod + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + """Apply fix to source code.""" + pass + + @abstractmethod + def rule_id(self) -> str: + """Return the rule ID this fixer handles.""" + pass + + +class SQLInjectionFixer(Fixer): + """Fixer for SQL injection vulnerabilities.""" + + def rule_id(self) -> str: + return "security.sql_injection" + + def can_fix(self, finding: Finding) -> bool: + return finding.rule_id == self.rule_id() + + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + lines = source_code.split("\n") + line_idx = finding.line_number - 1 + + if 0 <= line_idx < len(lines): + original_line = lines[line_idx] + fixed_line = self._fix_line(original_line) + lines[line_idx] = fixed_line + + return "\n".join(lines) + + def _fix_line(self, line: str) -> str: + if "execute(" in line: + return line.replace("%s", "?").replace("f\"", "\"").replace("'", "\"") + return line + + +class EvalUsageFixer(Fixer): + """Fixer for eval/exec usage.""" + + def rule_id(self) -> str: + return "security.eval_usage" + + def can_fix(self, finding: Finding) -> bool: + return finding.rule_id == self.rule_id() + + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + lines = source_code.split("\n") + line_idx = finding.line_number - 1 + + if 0 <= line_idx < len(lines): + original_line = lines[line_idx] + fixed_line = self._fix_line(original_line) + lines[line_idx] = fixed_line + + return "\n".join(lines) + + def _fix_line(self, line: str) -> str: + if "eval(" in line: + return line.replace("eval(", "# eval( # Warning: eval is dangerous - ") + if "exec(" in line: + return line.replace("exec(", "# exec( # Warning: exec is dangerous - ") + return line + + +class ExceptionSwallowFixer(Fixer): + """Fixer for exception swallowing.""" + + def rule_id(self) -> str: + return "antipattern.exception_swallow" + + def can_fix(self, finding: Finding) -> bool: + return finding.rule_id == self.rule_id() + + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + lines = source_code.split("\n") + line_idx = finding.line_number - 1 + + if 0 <= line_idx < len(lines): + for i, line in enumerate(lines): + if "except:" in line or "except ()" in line: + lines[i] = line.replace("except:", "except Exception as e:").replace("except ()", "except Exception as e:") + elif line.strip() == "pass": + lines[i] = line.replace("pass", "logger.error(f'Exception occurred: {e}')") + + return "\n".join(lines) + + def _fix_line(self, line: str) -> str: + if "except:" in line or "except ():" in line: + return line.replace("except:", "except Exception as e:").replace("except ():", "except Exception as e:") + if "pass" in line: + return line.replace("pass", "logger.error(f'Exception occurred: {e}')") + return line + + +class MagicNumberFixer(Fixer): + """Fixer for magic numbers.""" + + def rule_id(self) -> str: + return "antipattern.magic_number" + + def can_fix(self, finding: Finding) -> bool: + return finding.rule_id == self.rule_id() + + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + lines = source_code.split("\n") + line_idx = finding.line_number - 1 + + if 0 <= line_idx < len(lines): + original_line = lines[line_idx] + fixed_line = self._fix_line(original_line) + lines[line_idx] = fixed_line + + return "\n".join(lines) + + def _fix_line(self, line: str) -> str: + import re + magic_pattern = r"(\d{2,})" + matches = list(re.finditer(magic_pattern, line)) + if matches: + last_match = matches[-1] + num = last_match.group(1) + line = line[:last_match.start()] + f"MAGIC_NUMBER_{num}" + line[last_match.end():] + return line + + +class RedundantOperationFixer(Fixer): + """Fixer for redundant operations.""" + + def rule_id(self) -> str: + return "performance.redundant_operation" + + def can_fix(self, finding: Finding) -> bool: + return finding.rule_id == self.rule_id() + + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + lines = source_code.split("\n") + line_idx = finding.line_number - 1 + + if 0 <= line_idx < len(lines): + original_line = lines[line_idx] + fixed_line = self._fix_line(original_line) + lines[line_idx] = fixed_line + + return "\n".join(lines) + + def _fix_line(self, line: str) -> str: + if "list(list(" in line: + return line.replace("list(list(", "list(") + if "str(str(" in line: + return line.replace("str(str(", "str(") + if "dict(dict(" in line: + return line.replace("dict(dict(", "dict(") + return line + + +class FixerRegistry: + """Registry for code fixers.""" + + def __init__(self): + self._fixers: dict[str, Fixer] = { + "security.sql_injection": SQLInjectionFixer(), + "security.eval_usage": EvalUsageFixer(), + "antipattern.exception_swallow": ExceptionSwallowFixer(), + "antipattern.magic_number": MagicNumberFixer(), + "performance.redundant_operation": RedundantOperationFixer(), + } + + def get_fixer(self, rule_id: str) -> Optional[Fixer]: + return self._fixers.get(rule_id) + + def can_fix(self, finding: Finding) -> bool: + fixer = self._fixers.get(finding.rule_id) + return fixer is not None and fixer.can_fix(finding) + + def fix( + self, source_code: str, finding: Finding, tree: tree_sitter.Tree + ) -> str: + fixer = self._fixers.get(finding.rule_id) + if fixer and fixer.can_fix(finding): + return fixer.fix(source_code, finding, tree) + return source_code