acc-check-file-io

📁 dykyi-roman/awesome-claude-code 📅 2 days ago
1
总安装量
1
周安装量
#47457
全站排名
安装命令
npx skills add https://github.com/dykyi-roman/awesome-claude-code --skill acc-check-file-io

Agent 安装分布

opencode 1
claude-code 1

Skill 文档

File I/O Patterns Audit

Analyze PHP code for suboptimal or dangerous file I/O patterns.

Detection Patterns

1. Full File Read Into Memory

// CRITICAL: Entire file loaded into memory
$content = file_get_contents('/path/to/large-file.csv'); // 500MB file → OOM!
$lines = file('/path/to/large-file.csv'); // Loads all lines into array!

// CRITICAL: Reading large file to process line by line
$content = file_get_contents($path);
$lines = explode("\n", $content); // Double memory: content + lines array
foreach ($lines as $line) {
    $this->process($line);
}

// CORRECT: Stream processing
$handle = fopen($path, 'rb');
while (($line = fgets($handle)) !== false) {
    $this->process(trim($line));
}
fclose($handle);

// CORRECT: Generator for large files
function readLines(string $path): \Generator
{
    $handle = fopen($path, 'rb');
    try {
        while (($line = fgets($handle)) !== false) {
            yield trim($line);
        }
    } finally {
        fclose($handle);
    }
}

2. Missing File Locks

// CRITICAL: Concurrent writes without locking
class FileLogger
{
    public function log(string $message): void
    {
        $handle = fopen($this->path, 'ab');
        fwrite($handle, $message . "\n"); // Race condition with concurrent writers!
        fclose($handle);
    }
}

// CRITICAL: Read-modify-write without lock
$counter = (int) file_get_contents('counter.txt');
$counter++;
file_put_contents('counter.txt', (string) $counter);
// Concurrent requests: both read 5, both write 6 instead of 7

// CORRECT: File locking
$handle = fopen($this->path, 'cb+');
if (flock($handle, LOCK_EX)) {
    $counter = (int) stream_get_contents($handle);
    $counter++;
    ftruncate($handle, 0);
    rewind($handle);
    fwrite($handle, (string) $counter);
    flock($handle, LOCK_UN);
}
fclose($handle);

3. Temp File Not Cleaned Up

// CRITICAL: Temp file created but never deleted
class PdfGenerator
{
    public function generate(array $data): string
    {
        $tmpFile = tempnam(sys_get_temp_dir(), 'pdf_');
        file_put_contents($tmpFile, $this->render($data));
        $pdf = $this->converter->convert($tmpFile);
        // $tmpFile never deleted! Disk fills up over time
        return $pdf;
    }
}

// CORRECT: Always clean up in finally
class PdfGenerator
{
    public function generate(array $data): string
    {
        $tmpFile = tempnam(sys_get_temp_dir(), 'pdf_');
        try {
            file_put_contents($tmpFile, $this->render($data));
            return $this->converter->convert($tmpFile);
        } finally {
            if (file_exists($tmpFile)) {
                unlink($tmpFile);
            }
        }
    }
}

4. Missing File Handle Cleanup

// CRITICAL: File handle not closed on exception
class CsvProcessor
{
    public function process(string $path): array
    {
        $handle = fopen($path, 'rb');
        $results = [];
        while (($row = fgetcsv($handle)) !== false) {
            $results[] = $this->transform($row); // If this throws, handle leaks!
        }
        fclose($handle);
        return $results;
    }
}

// CORRECT: try/finally pattern
class CsvProcessor
{
    public function process(string $path): array
    {
        $handle = fopen($path, 'rb');
        try {
            $results = [];
            while (($row = fgetcsv($handle)) !== false) {
                $results[] = $this->transform($row);
            }
            return $results;
        } finally {
            fclose($handle);
        }
    }
}

5. SplFileObject Not Used for CSV

// ANTIPATTERN: Manual CSV parsing
$handle = fopen($path, 'rb');
$header = fgetcsv($handle);
while ($row = fgetcsv($handle)) {
    $data = array_combine($header, $row);
}
fclose($handle);

// CORRECT: SplFileObject with generator
function readCsv(string $path): \Generator
{
    $file = new \SplFileObject($path, 'rb');
    $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
    $header = $file->fgetcsv();
    foreach ($file as $row) {
        if ($row === [null]) continue;
        yield array_combine($header, $row);
    }
}

6. Unsafe File Path Construction

// CRITICAL: Path traversal + race condition
$path = '/uploads/' . $request->get('filename');
if (file_exists($path)) {        // Check
    $content = file_get_contents($path); // Use — TOCTOU!
}

// CORRECT: Validate and use realpath
$basePath = realpath('/uploads');
$fullPath = realpath('/uploads/' . basename($request->get('filename')));
if ($fullPath === false || !str_starts_with($fullPath, $basePath)) {
    throw new SecurityException('Invalid file path');
}
$content = file_get_contents($fullPath);

7. Writing Large Output Without Streaming

// CRITICAL: Building entire response in memory
class ExportController
{
    public function export(): Response
    {
        $data = $this->repository->findAll(); // 100K rows
        $csv = '';
        foreach ($data as $row) {
            $csv .= implode(',', $row) . "\n"; // String grows to 500MB
        }
        return new Response($csv);
    }
}

// CORRECT: Streaming response
class ExportController
{
    public function export(): StreamedResponse
    {
        return new StreamedResponse(function () {
            $handle = fopen('php://output', 'wb');
            foreach ($this->repository->findAllIterable() as $row) {
                fputcsv($handle, $row);
                flush();
            }
            fclose($handle);
        }, 200, ['Content-Type' => 'text/csv']);
    }
}

Grep Patterns

# Full file reads
Grep: "file_get_contents\(|file\(" --glob "**/*.php"

# Missing file locks
Grep: "fwrite\(|file_put_contents\(" --glob "**/*.php"
Grep: "flock\(" --glob "**/*.php"

# Temp files
Grep: "tempnam\(|sys_get_temp_dir\(|tmpfile\(" --glob "**/*.php"
Grep: "unlink\(.*tmp" --glob "**/*.php"

# File handles without finally
Grep: "fopen\(" --glob "**/*.php"
Grep: "finally.*fclose" --glob "**/*.php"

# CSV processing
Grep: "fgetcsv\(|SplFileObject" --glob "**/*.php"

# String concatenation for output
Grep: "\\\$.*\.=.*\\\\n|\\\$.*\.= implode" --glob "**/*.php"

Severity Classification

Pattern Severity
Full file read of unbounded size 🔴 Critical
File handle leak on exception 🔴 Critical
Write without lock (shared file) 🟠 Major
Temp file not cleaned up 🟠 Major
Building large string in memory 🟠 Major
Missing SplFileObject for CSV 🟡 Minor

Output Format

### File I/O: [Description]

**Severity:** 🔴/🟠/🟡
**Location:** `file.php:line`
**Impact:** [Memory/Performance/Data integrity]

**Issue:**
[Description of the file I/O problem]

**Code:**
```php
// Problematic pattern

Fix:

// Stream-based / locked / cleaned-up pattern

Expected Improvement: Memory: 500MB → 4KB (streaming)