thread-safe-check 0.4.0
Multi-language CLI tool for detecting concurrency and thread safety issues
Requires Python: >=3.10
Keywords: concurrency, linting, static-analysis, thread-safety

# Thread Safety Analyzer

A multi-language static analysis tool for detecting concurrency and thread safety issues. Use it locally as a linter, in CI pipelines, or to generate context for LLM-assisted PR reviews.

## Installation

```bash
pip install thread-safety
```

Or install from source:

```bash
git clone https://github.com/steph-dove/thread-safe-check.git
cd thread-safe-check
pip install -e .
```

## Use Cases

### Local Development (Linter)

Run as a linter during development to catch concurrency issues early:

```bash
# Analyze your project
thread-safety analyze ./src

# Check only files you've changed
thread-safety analyze . --git-diff

# Focus on critical issues
thread-safety analyze ./src --min-severity high
```

### CI/CD Integration

Add to your CI pipeline to catch thread safety issues before merge.

**GitHub Actions:**
```yaml
- name: Thread Safety Check
  run: |
    pip install thread-safety
    thread-safety analyze ./src --output json -f thread-safety-report.json

- name: Upload Report
  uses: actions/upload-artifact@v3
  with:
    name: thread-safety-report
    path: thread-safety-report.json
```

**GitLab CI:**
```yaml
thread-safety:
  script:
    - pip install thread-safety
    - thread-safety analyze ./src --min-severity medium
  allow_failure: true  # Start as warning-only
```

**Pre-commit Hook:**
```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: thread-safety
        name: Thread Safety Check
        entry: thread-safety analyze
        language: system
        files: \.(py|js|ts|go|rs)$
        pass_filenames: false
```

### LLM PR Review Context

Generate rich context for AI-assisted code review:

```bash
# Generate LLM-optimized output
thread-safety analyze ./src --output llm

# Analyze only changed files for PR review
thread-safety analyze . --git-diff --output llm
```

The LLM output format includes:
- Code snippets with context
- Detailed explanations of why each issue matters
- Suggested fixes with before/after examples
- Severity and category information

## Quick Start

```bash
# Analyze a directory
thread-safety analyze ./src

# Output formats
thread-safety analyze ./src --output text      # Human-readable (default)
thread-safety analyze ./src --output json      # For CI/tooling
thread-safety analyze ./src --output markdown  # For documentation
thread-safety analyze ./src --output llm       # For AI review

# Filter by severity (info, low, medium, high, critical)
thread-safety analyze ./src --min-severity high

# Run specific rules only
thread-safety analyze ./src --rule TS001 --rule TS010

# Analyze only git-changed files
thread-safety analyze . --git-diff

# Save report to file
thread-safety analyze ./src --output json -f report.json

# List all available rules
thread-safety rules

# Generate config file
thread-safety init
```

## Detection Rules

### Shared Mutable State
- **TS001**: Global variable mutation in threaded context
- **TS002**: Class attribute mutation across threads
- **TS003**: Closure capturing mutable variable
- **TS004**: Module-level mutable default argument

### Race Conditions
- **TS010**: Unprotected shared variable access
- **TS011**: Check-then-act (TOCTOU)
- **TS012**: Compound operation without lock
- **TS013**: Collection modification during iteration

### Lock Issues
- **TS020**: Potential deadlock (inconsistent lock ordering)
- **TS021**: Lock acquired but never released
- **TS022**: Double lock acquisition (non-reentrant)
- **TS023**: Lock held during blocking I/O

### Async Patterns
- **TS030**: Blocking call in async function
- **TS031**: Missing await on coroutine
- **TS032**: Using threading.Lock in async code
- **TS033**: Shared mutable state in coroutine
- **TS034**: Sync primitive in async context

## Supported Languages

| Language | Status | Rules | Notes |
|----------|--------|-------|-------|
| Python | ✅ Stable | 17 | Full coverage |
| JavaScript/TypeScript | ✅ Stable | 8 | Async/Promise patterns |
| Go | ✅ Stable | 5 | Goroutines, sync.Mutex |
| Rust | ✅ Stable | 8 | Async, std::sync |
| Java | Planned | - | - |

### Language Support Matrix

| Rule | Python | JS/TS | Go | Rust |
|------|--------|-------|-----|------|
| TS001 Global Mutation | ✅ | - | - | - |
| TS002 Class Attribute | ✅ | - | - | - |
| TS003 Closure Capture | ✅ | ✅ | ✅ | ✅ |
| TS004 Mutable Default | ✅ | - | - | - |
| TS010 Unprotected Access | ✅ | - | - | - |
| TS011 Check-Then-Act | ✅ | ✅ | ✅ | ✅ |
| TS012 Compound Operation | ✅ | - | - | - |
| TS013 Collection Iteration | ✅ | ✅ | ✅ | ✅ |
| TS020 Deadlock | ✅ | - | ✅ | ✅ |
| TS021 Lock Not Released | ✅ | - | - | - |
| TS022 Double Lock | ✅ | - | - | - |
| TS023 Lock Blocking I/O | ✅ | - | ✅ | ✅ |
| TS030 Blocking in Async | ✅ | ✅ | - | ✅ |
| TS031 Missing Await | ✅ | ✅ | - | ✅ |
| TS032 Threading Lock in Async | ✅ | - | - | - |
| TS033 Shared State in Coroutine | ✅ | ✅ | - | ✅ |
| TS034 Sync Primitive in Async | ✅ | - | - | - |

## Configuration

Create a `thread-safety.toml` file in your project root:

```toml
[thread-safety]
include = ["src", "lib"]
exclude = ["**/node_modules/**", "**/.venv/**", "**/test/**"]
min_severity = "medium"

[thread-safety.rules]
# Enable only specific rules
enable = ["TS001", "TS010", "TS020"]

# Or disable specific rules
disable = ["TS004"]
```

## Output Formats

### Text (default)
Human-readable output for terminal with colors and code snippets.

### JSON
Structured output for CI/CD integration and tooling:
```bash
thread-safety analyze ./src --output json -f report.json
```

### Markdown
Documentation-friendly format for reports:
```bash
thread-safety analyze ./src --output markdown -f report.md
```

### LLM
Optimized for AI-assisted code review with rich context:
```bash
thread-safety analyze ./src --output llm
```

## Example Output

```
Found 2 issue(s):

■ TS030  src/api/handler.py:45:4
  Blocking call 'requests.get' in async function 'fetch_data'

       43 | async def fetch_data(url: str):
       44 |     # This blocks the event loop!
    >  45 |     response = requests.get(url)
       46 |     return response.json()

  Suggestion: Replace with async alternative: aiohttp or httpx

■ TS013  src/cache.py:23:8
  Collection 'items' modified with 'pop' during iteration

       21 |     for key in cache.items:
       22 |         if is_expired(key):
    >  23 |             cache.items.pop(key)
       24 |

  Suggestion: Collect keys to remove first, then remove outside the loop
```

## Development

```bash
# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run linting
ruff check src tests

# Type checking
mypy src
```

## License

MIT