# Semantic Bug Detector

> Detect semantic bugs (logic errors, incorrect assumptions, race conditions) beyond syntactic checks. Use when reviewing code for logic errors, identifying.

- Skill: `yigityildiz0/semantic-bug-detector` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add yigityildiz0/semantic-bug-detector`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yigityildiz0/semantic-bug-detector/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yigityildiz0 (https://skillmd.com/u/yigityildiz0)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yigityildiz0/semantic-bug-detector

---


# Semantic Bug Detector

Detect semantic bugs that pass compilation and linting but produce incorrect behavior at runtime. This skill covers logic flow analysis, type confusion detection, off-by-one error detection, null safety analysis, race condition identification, and invariant violation detection.

## When to Use This Skill

Use this skill when you need to:

- Review code for logic errors that compilers and linters cannot catch
- Identify off-by-one errors in loops, array accesses, and boundary conditions
- Detect null/undefined safety issues before they cause runtime crashes
- Find type confusion bugs where values are silently coerced or misinterpreted
- Identify race conditions and concurrency bugs in multi-threaded or async code
- Verify that code invariants (preconditions, postconditions, loop invariants) are maintained
- Audit code for incorrect assumptions about data formats, ranges, or ordering

**Trigger phrases**: "check for logic errors", "find semantic bugs", "detect off-by-one", "null safety review", "race condition detection", "find logic flaws", "check invariants", "semantic analysis", "detect concurrency bugs"

## What This Skill Does

### Methodology Overview

Semantic bug detection operates at a higher level than syntax checking. While compilers verify that code is well-formed and linters check style, semantic analysis verifies that code does what the developer intended. This skill applies six complementary detection techniques:

1. **Logic Flow Analysis** -- Trace the logical paths through code to find unreachable branches, inverted conditions, missing cases, and incorrect boolean logic.
2. **Type Confusion Detection** -- Identify cases where values of one type are silently treated as another (string-to-number coercion, lossy casts, enum misuse).
3. **Off-by-One Detection** -- Examine loop bounds, array indices, range calculations, and boundary conditions for fencepost errors.
4. **Null Safety Analysis** -- Find code paths where null or undefined values can reach operations that require non-null values.
5. **Race Condition Identification** -- Detect shared mutable state accessed without synchronization, time-of-check-to-time-of-use (TOCTOU) patterns, and async ordering issues.
6. **Invariant Violation Detection** -- Verify that preconditions, postconditions, and loop invariants hold across all code paths.

### Bug Category Reference

| Category | Severity | Detection Difficulty | Common Languages |
|----------|----------|---------------------|-----------------|
| Off-by-one | Medium | Medium | All |
| Null dereference | High | Low-Medium | Java, C#, JavaScript, Python |
| Type confusion | Medium-High | High | JavaScript, Python |
| Race condition | Critical | Very High | All (concurrent code) |
| Logic inversion | Medium | Medium | All |
| Invariant violation | High | High | All |
| Integer overflow | High | Medium | C, C++, Java |
| Incorrect comparison | Medium | Low | JavaScript, Python |

## Instructions

### Step 1: Logic Flow Analysis

Trace the logical structure of code to find errors in conditions, branches, and control flow.

**Common logic flow bugs:**

- Inverted conditions (`if (x > 0)` when `if (x < 0)` was intended)
- Missing `else` branches or missing `default` cases in switch statements
- Short-circuit evaluation side effects
- Dead code after unconditional returns
- Incorrect operator precedence in compound conditions

**Python: Logic flow analyzer**

```python
import ast
from dataclasses import dataclass

@dataclass
class LogicFlowIssue:
    file: str
    line: int
    category: str
    description: str
    severity: str

class LogicFlowAnalyzer(ast.NodeVisitor):
    """Detect logic flow issues in Python code."""

    def __init__(self, filename: str):
        self.filename = filename
        self.issues: list[LogicFlowIssue] = []

    def visit_If(self, node: ast.If):
        # Check for tautologies and contradictions
        if isinstance(node.test, ast.Compare):
            self._check_tautological_comparison(node)

        # Check for missing else on exhaustive conditions
        if not node.orelse:
            self._check_missing_else(node)

        # Check for dead code after return in if body
        self._check_dead_code_after_return(node)

        self.generic_visit(node)

    def _check_tautological_comparison(self, node: ast.If):
        """Detect always-true or always-false comparisons."""
        test = node.test
        if isinstance(test, ast.Compare) and len(test.ops) == 1:
            left = test.left
            right = test.comparators[0]
            # Check x == x (always true)
            if (isinstance(left, ast.Name) and isinstance(right, ast.Name)
                    and left.id == right.id):
                self.issues.append(LogicFlowIssue(
                    file=self.filename,
                    line=node.lineno,
                    category="tautological_comparison",
                    description=f"Comparison '{left.id} == {right.id}' is always True",
                    severity="medium",
                ))

    def _check_missing_else(self, node: ast.If):
        """Flag if statements that may need an else clause."""
        # Heuristic: if the if body contains a return, an else might be needed
        for child in ast.walk(node):
            if isinstance(child, ast.Return):
                self.issues.append(LogicFlowIssue(
                    file=self.filename,
                    line=node.lineno,
                    category="missing_else",
                    description="If statement with return but no else clause; "
                                "consider whether the else case is handled",
                    severity="info",
                ))
                break

    def _check_dead_code_after_return(self, node: ast.If):
        """Detect code that appears after an unconditional return."""
        body = node.body
        for i, stmt in enumerate(body):
            if isinstance(stmt, ast.Return) and i < len(body) - 1:
                self.issues.append(LogicFlowIssue(
                    file=self.filename,
                    line=body[i + 1].lineno,
                    category="dead_code",
                    description="Code after return statement is unreachable",
                    severity="high",
                ))

    def visit_BoolOp(self, node: ast.BoolOp):
        """Check for suspicious boolean operations."""
        # Check for duplicate operands: x or x, x and x
        names = []
        for value in node.values:
            if isinstance(value, ast.Name):
                names.append(value.id)
        if len(names) != len(set(names)):
            self.issues.append(LogicFlowIssue(
                file=self.filename,
                line=node.lineno,
                category="duplicate_boolean_operand",
                description="Boolean operation contains duplicate operands",
                severity="medium",
            ))
        self.generic_visit(node)


def analyze_logic_flow(source_code: str, filename: str = "<input>") -> list[LogicFlowIssue]:
    """Analyze Python source code for logic flow issues."""
    tree = ast.parse(source_code)
    analyzer = LogicFlowAnalyzer(filename)
    analyzer.visit(tree)
    return analyzer.issues
```

**JavaScript: Logic flow analysis patterns**

```javascript
/**
 * Common logic flow bug patterns to check during code review.
 * These are patterns that eslint and TypeScript may not catch.
 */

// BUG PATTERN 1: Incorrect equality comparison
// JavaScript's == performs type coercion; use === instead
function checkEqualityBugs(code) {
  const issues = [];
  const lines = code.split("\n");

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];

    // Detect == null (should usually be === null || === undefined)
    if (/[^=!]=\s*null\b/.test(line) && !/===\s*null/.test(line)) {
      // Note: == null intentionally catches both null and undefined
      // This is a common pattern, but should be deliberate
      issues.push({
        line: i + 1,
        category: "loose_equality",
        description: "Using == null; verify this is intentional (catches both null and undefined)",
        severity: "info",
      });
    }

    // Detect = in conditions (assignment instead of comparison)
    if (/if\s*\([^=]*[^=!<>]=[^=]/.test(line)) {
      issues.push({
        line: i + 1,
        category: "assignment_in_condition",
        description: "Possible assignment (=) in condition instead of comparison (=== or ==)",
        severity: "high",
      });
    }
  }

  return issues;
}

// BUG PATTERN 2: Unreachable code after return/throw
function checkUnreachableCode(code) {
  const issues = [];
  const lines = code.split("\n");

  for (let i = 0; i < lines.length - 1; i++) {
    const line = lines[i].trim();
    const nextLine = lines[i + 1].trim();

    if (
      (line.startsWith("return ") || line.startsWith("throw ")) &&
      nextLine &&
      !nextLine.startsWith("}") &&
      !nextLine.startsWith("//") &&
      !nextLine.startsWith("case ") &&
      !nextLine.startsWith("default:")
    ) {
      issues.push({
        line: i + 2,
        category: "unreachable_code",
        description: "Code after return/throw is unreachable",
        severity: "high",
      });
    }
  }

  return issues;
}

// BUG PATTERN 3: Missing break in switch
function checkSwitchFallthrough(code) {
  const issues = [];
  const caseRegex = /case\s+.+:/g;
  const breakRegex = /\bbreak\b|\breturn\b|\bthrow\b/;
  const lines = code.split("\n");

  let inCase = false;
  let caseStartLine = 0;
  let hasTerminator = false;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();

    if (/^case\s/.test(line) || line === "default:") {
      if (inCase && !hasTerminator) {
        issues.push({
          line: caseStartLine,
          category: "switch_fallthrough",
          description: "Case block without break, return, or throw (possible fallthrough bug)",
          severity: "medium",
        });
      }
      inCase = true;
      caseStartLine = i + 1;
      hasTerminator = false;
    }

    if (breakRegex.test(line)) {
      hasTerminator = true;
    }
  }

  return issues;
}
```

**Java: Logic flow analysis**

```java
import java.util.*;
import java.util.regex.*;

public class LogicFlowAnalyzer {
    public record Issue(int line, String category,
                        String description, String severity) {}

    public static List<Issue> analyzeJavaSource(String sourceCode) {
        List<Issue> issues = new ArrayList<>();
        String[] lines = sourceCode.split("\n");

        for (int i = 0; i < lines.length; i++) {
            String line = lines[i].trim();
            int lineNum = i + 1;

            // Check for assignment in condition
            if (Pattern.matches(".*if\\s*\\([^=]*[^=!<>]=[^=].*", line)) {
                issues.add(new Issue(lineNum, "assignment_in_condition",
                    "Possible assignment in condition instead of comparison",
                    "high"));
            }

            // Check for unreachable code after return
            if ((line.startsWith("return ") || line.startsWith("throw "))
                    && i + 1 < lines.length) {
                String nextLine = lines[i + 1].trim();
                if (!nextLine.isEmpty() && !nextLine.startsWith("}")
                        && !nextLine.startsWith("case ")
                        && !nextLine.startsWith("//")) {
                    issues.add(new Issue(lineNum + 1, "unreachable_code",
                        "Code after return/throw is unreachable", "high"));
                }
            }

            // Check for String comparison with ==
            if (Pattern.matches(".*\\w+\\s*==\\s*\".*\".*", line)
                    || Pattern.matches(".*\".*\"\\s*==\\s*\\w+.*", line)) {
                issues.add(new Issue(lineNum, "string_reference_comparison",
                    "Comparing String with == instead of .equals()",
                    "high"));
            }

            // Check for empty catch blocks
            if (line.startsWith("catch") && i + 1 < lines.length
                    && lines[i + 1].trim().equals("}")) {
                issues.add(new Issue(lineNum, "empty_catch",
                    "Empty catch block silently swallows exception",
                    "high"));
            }
        }

        return issues;
    }
}
```

### Step 2: Off-by-One Detection

Off-by-one errors are among the most common semantic bugs. They occur at boundaries: loop limits, array indices, range ends, and string positions.

**Common off-by-one patterns:**

| Pattern | Bug | Fix |
|---------|-----|-----|
| `for i in range(len(arr))` accessing `arr[i+1]` | Index out of bounds on last iteration | `range(len(arr) - 1)` |
| `for (int i = 0; i <= arr.length; i++)` | Index out of bounds | `i < arr.length` |
| `substring(0, str.length() - 1)` | Drops last character unintentionally | Verify intent; use `str.length()` if full string needed |
| `arr[arr.length]` | Index out of bounds | `arr[arr.length - 1]` |
| Inclusive vs exclusive range confusion | Wrong number of iterations or elements | Document whether ranges are inclusive or exclusive |

**Python: Off-by-one detector**

```python
import ast

class OffByOneDetector(ast.NodeVisitor):
    """Detect potential off-by-one errors in Python code."""

    def __init__(self, filename: str):
        self.filename = filename
        self.issues: list[dict] = []

    def visit_For(self, node: ast.For):
        """Check for loops for off-by-one risks."""
        # Check range() calls
        if isinstance(node.iter, ast.Call):
            func = node.iter
            if isinstance(func.func, ast.Name) and func.func.id == "range":
                self._check_range_obo(node, func)
        self.generic_visit(node)

    def _check_range_obo(self, for_node: ast.For, range_call: ast.Call):
        """Check range() for off-by-one patterns."""
        args = range_call.args

        if len(args) >= 2:
            # range(start, stop) -- check if stop is a len() call
            stop = args[1]
            if isinstance(stop, ast.Call) and isinstance(stop.func, ast.Name):
                if stop.func.id == "len":
                    # range(x, len(arr)) is correct for 0-based indexing
                    pass

            # Check for range(1, len(arr)) when 0-based access expected
            start = args[0]
            if isinstance(start, ast.Constant) and start.value == 1:
                self.issues.append({
                    "line": for_node.lineno,
                    "category": "range_start_one",
                    "description": "range() starts at 1; verify this is intentional "
                                   "(Python uses 0-based indexing)",
                    "severity": "medium",
                })

    def visit_Subscript(self, node: ast.Subscript):
        """Check array subscript access for off-by-one risks."""
        # Check for arr[len(arr)] (should be arr[len(arr) - 1])
        if isinstance(node.slice, ast.Call):
            if (isinstance(node.slice.func, ast.Name)
                    and node.slice.func.id == "len"):
                self.issues.append({
                    "line": node.lineno,
                    "category": "index_equals_length",
                    "description": "Accessing arr[len(arr)] will raise IndexError; "
                                   "last element is arr[len(arr) - 1]",
                    "severity": "high",
                })
        self.generic_visit(node)


def detect_off_by_one(source_code: str, filename: str = "<input>") -> list[dict]:
    """Detect off-by-one errors in Python source code."""
    tree = ast.parse(source_code)
    detector = OffByOneDetector(filename)
    detector.visit(tree)
    return detector.issues
```

**JavaScript: Off-by-one detector**

```javascript
function detectOffByOne(code) {
  const issues = [];
  const lines = code.split("\n");

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const lineNum = i + 1;

    // Pattern: for (... i <= arr.length ...) -- should be < not <=
    const forLeMatch = line.match(
      /for\s*\(.+\b(\w+)\s*<=\s*(\w+)\.length\b/
    );
    if (forLeMatch) {
      issues.push({
        line: lineNum,
        category: "loop_bound_inclusive",
        description: `Loop uses '<= ${forLeMatch[2]}.length' which will access ` +
          `index ${forLeMatch[2]}.length (out of bounds). Use '< ${forLeMatch[2]}.length'`,
        severity: "high",
      });
    }

    // Pattern: arr[arr.length] -- should be arr[arr.length - 1]
    const lengthAccessMatch = line.match(/(\w+)\[(\w+)\.length\]/);
    if (lengthAccessMatch && lengthAccessMatch[1] === lengthAccessMatch[2]) {
      issues.push({
        line: lineNum,
        category: "index_equals_length",
        description: `Accessing ${lengthAccessMatch[1]}[${lengthAccessMatch[1]}.length] ` +
          "is out of bounds. Use .length - 1 for the last element",
        severity: "high",
      });
    }

    // Pattern: slice(0, -1) when the intent may be to include the last element
    if (/\.slice\(\s*0\s*,\s*-1\s*\)/.test(line)) {
      issues.push({
        line: lineNum,
        category: "slice_excludes_last",
        description: "slice(0, -1) excludes the last element. " +
          "Verify this is intentional",
        severity: "info",
      });
    }

    // Pattern: substring(0, str.length - 1) dropping last character
    if (/\.substring\(\s*0\s*,\s*\w+\.length\s*-\s*1\s*\)/.test(line)) {
      issues.push({
        line: lineNum,
        category: "substring_drops_last",
        description: "substring(0, str.length - 1) drops the last character. " +
          "Verify this is intentional",
        severity: "info",
      });
    }
  }

  return issues;
}
```

### Step 3: Null Safety Analysis

Detect code paths where null, None, undefined, or similar sentinel values can reach operations that do not handle them.

**Python: Null safety analyzer**

```python
import ast

class NullSafetyAnalyzer(ast.NodeVisitor):
    """Detect potential None-related bugs in Python code."""

    def __init__(self, filename: str):
        self.filename = filename
        self.issues: list[dict] = []
        self.nullable_vars: set[str] = set()

    def visit_FunctionDef(self, node: ast.FunctionDef):
        """Track function parameters and their None potential."""
        # Check for parameters with None defaults
        defaults = node.args.defaults
        args = node.args.args
        num_defaults = len(defaults)
        num_args = len(args)

        for i, default in enumerate(defaults):
            if isinstance(default, ast.Constant) and default.value is None:
                arg_index = num_args - num_defaults + i
                arg_name = args[arg_index].arg
                self.nullable_vars.add(arg_name)

        self.generic_visit(node)

    def visit_Assign(self, node: ast.Assign):
        """Track assignments that may introduce None."""
        # Check for x = dict.get(key) (returns None if key missing)
        if isinstance(node.value, ast.Call):
            func = node.value.func
            if isinstance(func, ast.Attribute) and func.attr == "get":
                # Only one default arg means it defaults to None
                if len(node.value.args) <= 1 and not node.value.keywords:
                    for target in node.targets:
                        if isinstance(target, ast.Name):
                            self.nullable_vars.add(target.id)

        # Check for x = None
        if isinstance(node.value, ast.Constant) and node.value.value is None:
            for target in node.targets:
                if isinstance(target, ast.Name):
                    self.nullable_vars.add(target.id)

        self.generic_visit(node)

    def visit_Attribute(self, node: ast.Attribute):
        """Check attribute access on potentially None variables."""
        if isinstance(node.value, ast.Name) and node.value.id in self.nullable_vars:
            self.issues.append({
                "line": node.lineno,
                "category": "null_dereference",
                "description": f"Attribute access '.{node.attr}' on "
                               f"'{node.value.id}' which may be None",
                "severity": "high",
            })
        self.generic_visit(node)

    def visit_Call(self, node: ast.Call):
        """Check method calls on potentially None variables."""
        if isinstance(node.func, ast.Attribute):
            obj = node.func.value
            if isinstance(obj, ast.Name) and obj.id in self.nullable_vars:
                self.issues.append({
                    "line": node.lineno,
                    "category": "null_method_call",
                    "description": f"Method call '.{node.func.attr}()' on "
                                   f"'{obj.id}' which may be None",
                    "severity": "high",
                })
        self.generic_visit(node)


def analyze_null_safety(source_code: str, filename: str = "<input>") -> list[dict]:
    """Analyze Python code for null safety issues."""
    tree = ast.parse(source_code)
    analyzer = NullSafetyAnalyzer(filename)
    analyzer.visit(tree)
    return analyzer.issues
```

**JavaScript: Null safety patterns**

```javascript
function analyzeNullSafety(code) {
  const issues = [];
  const lines = code.split("\n");
  const nullableVars = new Set();

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    const lineNum = i + 1;

    // Track variables that may be null/undefined
    const nullAssign = line.match(
      /(?:const|let|var)\s+(\w+)\s*=\s*(?:null|undefined)\s*;/
    );
    if (nullAssign) {
      nullableVars.add(nullAssign[1]);
    }

    // Track .find() results (returns undefined if not found)
    const findResult = line.match(
      /(?:const|let|var)\s+(\w+)\s*=\s*\w+\.find\(/
    );
    if (findResult) {
      nullableVars.add(findResult[1]);
    }

    // Track Map.get() results
    const mapGet = line.match(
      /(?:const|let|var)\s+(\w+)\s*=\s*\w+\.get\(/
    );
    if (mapGet) {
      nullableVars.add(mapGet[1]);
    }

    // Check for property access on nullable variables
    for (const varName of nullableVars) {
      const accessPattern = new RegExp(
        `\\b${varName}\\.(\\w+)`, "g"
      );
      const accessMatch = accessPattern.exec(line);
      if (accessMatch) {
        // Check if there is a null guard before this access
        const beforeAccess = lines.slice(Math.max(0, i - 3), i + 1).join("\n");
        const hasGuard =
          beforeAccess.includes(`if (${varName}`) ||
          beforeAccess.includes(`if (!${varName}`) ||
          beforeAccess.includes(`${varName}?.`) ||
          beforeAccess.includes(`${varName} &&`);

        if (!hasGuard) {
          issues.push({
            line: lineNum,
            category: "null_dereference",
            description: `Property access '.${accessMatch[1]}' on '${varName}' ` +
              "which may be null/undefined. Use optional chaining (?.) or add a guard",
            severity: "high",
          });
        }
      }
    }
  }

  return issues;
}
```

**Java: Null safety analyzer**

```java
import java.util.*;
import java.util.regex.*;

public class NullSafetyAnalyzer {
    public record NullIssue(int line, String category,
                             String description, String severity) {}

    private final Set<String> nullableVars = new HashSet<>();

    public List<NullIssue> analyze(String sourceCode) {
        List<NullIssue> issues = new ArrayList<>();
        String[] lines = sourceCode.split("\n");

        for (int i = 0; i < lines.length; i++) {
            String line = lines[i].trim();
            int lineNum = i + 1;

            // Track null assignments
            Matcher nullAssign = Pattern.compile(
                "(\\w+)\\s+(\\w+)\\s*=\\s*null\\s*;"
            ).matcher(line);
            if (nullAssign.find()) {
                nullableVars.add(nullAssign.group(2));
            }

            // Track Map.get() results (can return null)
            Matcher mapGet = Pattern.compile(
                "(\\w+)\\s+(\\w+)\\s*=\\s*\\w+\\.get\\("
            ).matcher(line);
            if (mapGet.find()) {
                nullableVars.add(mapGet.group(2));
            }

            // Track methods that may return null
            Matcher findFirst = Pattern.compile(
                "(\\w+)\\s+(\\w+)\\s*=.*\\.findFirst\\(\\)\\.orElse\\(null\\)"
            ).matcher(line);
            if (findFirst.find()) {
                nullableVars.add(findFirst.group(2));
            }

            // Check for method calls on nullable variables without null check
            for (String varName : nullableVars) {
                if (line.contains(varName + ".") && !line.contains("null")) {
                    // Look for a null check in surrounding lines
                    boolean hasGuard = false;
                    for (int j = Math.max(0, i - 3); j <= i; j++) {
                        String surrounding = lines[j].trim();
                        if (surrounding.contains("if (" + varName + " != null")
                                || surrounding.contains("if (" + varName + " == null")
                                || surrounding.contains("Objects.requireNonNull(" + varName)) {
                            hasGuard = true;
                            break;
                        }
                    }
                    if (!hasGuard) {
                        issues.add(new NullIssue(lineNum, "null_dereference",
                            "Method call on '" + varName + "' which may be null. " +
                            "Add a null check or use Optional",
                            "high"));
                    }
                }
            }
        }

        return issues;
    }
}
```

### Step 4: Race Condition Identification

Detect concurrency bugs including unsynchronized shared state, TOCTOU patterns, and async ordering issues.

**Python: Race condition detector**

```python
import ast
from dataclasses import dataclass

@dataclass
class RaceConditionIssue:
    line: int
    category: str
    description: str
    severity: str

class RaceConditionDetector(ast.NodeVisitor):
    """Detect potential race conditions in Python code."""

    def __init__(self, filename: str):
        self.filename = filename
        self.issues: list[RaceConditionIssue] = []
        self.shared_state_writes: dict[str, list[int]] = {}
        self.in_async = False

    def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
        old_async = self.in_async
        self.in_async = True
        self.generic_visit(node)
        self.in_async = old_async

    def visit_Assign(self, node: ast.Assign):
        """Track writes to shared state."""
        for target in node.targets:
            if isinstance(target, ast.Attribute):
                # Writing to self.x in an async context is a potential race
                if (isinstance(target.value, ast.Name)
                        and target.value.id == "self" and self.in_async):
                    attr_name = f"self.{target.attr}"
                    if attr_name not in self.shared_state_writes:
                        self.shared_state_writes[attr_name] = []
                    self.shared_state_writes[attr_name].append(node.lineno)

                    if len(self.shared_state_writes[attr_name]) > 1:
                        self.issues.append(RaceConditionIssue(
                            line=node.lineno,
                            category="unsynchronized_shared_write",
                            description=f"Multiple async writes to '{attr_name}' "
                                        "without synchronization",
                            severity="high",
                        ))
        self.generic_visit(node)

    def visit_If(self, node: ast.If):
        """Detect TOCTOU patterns (check-then-act without lock)."""
        # Check for patterns like: if os.path.exists(f): ... open(f) ...
        if self._is_file_existence_check(node.test):
            for child in ast.walk(node):
                if self._is_file_operation(child):
                    self.issues.append(RaceConditionIssue(
                        line=node.lineno,
                        category="toctou",
                        description="Time-of-check-to-time-of-use: file existence "
                                    "check followed by file operation is vulnerable "
                                    "to race conditions",
                        severity="high",
                    ))
                    break
        self.generic_visit(node)

    def _is_file_existence_check(self, node: ast.expr) -> bool:
        if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
            return node.func.attr in ("exists", "isfile", "isdir")
        return False

    def _is_file_operation(self, node: ast.AST) -> bool:
        if isinstance(node, ast.Call):
            if isinstance(node.func, ast.Name):
                return node.func.id in ("open", "remove", "unlink")
        return False
```

**JavaScript: Async race condition patterns**

```javascript
function detectAsyncRaceConditions(code) {
  const issues = [];
  const lines = code.split("\n");

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    const lineNum = i + 1;

    // Pattern: Forgetting to await an async function
    if (/(?:const|let|var)\s+\w+\s*=\s*\w+\.\w+\(/.test(line) && !line.includes("await")) {
      // Check if the function is likely async by looking for async in context
      const context = lines.slice(Math.max(0, i - 10), i).join("\n");
      if (context.includes("async ") && !line.includes("then(")) {
        issues.push({
          line: lineNum,
          category: "missing_await",
          description: "Assignment from function call without await inside async function. " +
            "If the function is async, the result will be a Promise, not the resolved value",
          severity: "high",
        });
      }
    }

    // Pattern: forEach with async callback (does not await)
    if (/\.forEach\(\s*async/.test(line)) {
      issues.push({
        line: lineNum,
        category: "async_foreach",
        description: "Array.forEach() does not await async callbacks. " +
          "Use for...of or Promise.all(arr.map(async ...)) instead",
        severity: "high",
      });
    }

    // Pattern: Multiple state updates without batching
    if (/setState|dispatch|commit/.test(line)) {
      const nextLine = i + 1 < lines.length ? lines[i + 1].trim() : "";
      if (/setState|dispatch|commit/.test(nextLine)) {
        issues.push({
          line: lineNum,
          category: "unbatched_state_updates",
          description: "Consecutive state updates may cause race conditions " +
            "or unnecessary re-renders. Consider batching",
          severity: "medium",
        });
      }
    }

    // Pattern: Shared mutable variable in closure
    if (/let\s+(\w+)\s*=/.test(line)) {
      const varName = line.match(/let\s+(\w+)/)[1];
      const remainingCode = lines.slice(i + 1).join("\n");
      const inCallback = new RegExp(
        `(?:setTimeout|setInterval|addEventListener|on\\w+)\\s*\\([^)]*${varName}`
      ).test(remainingCode);
      if (inCallback) {
        issues.push({
          line: lineNum,
          category: "shared_mutable_closure",
          description: `Mutable variable '${varName}' is captured by an async callback. ` +
            "The value may change between when the callback is created and when it executes",
          severity: "medium",
        });
      }
    }
  }

  return issues;
}
```

**Java: Race condition detector**

```java
import java.util.*;
import java.util.regex.*;

public class RaceConditionDetector {
    public record RaceIssue(int line, String category,
                             String description, String severity) {}

    public static List<RaceIssue> detect(String sourceCode) {
        List<RaceIssue> issues = new ArrayList<>();
        String[] lines = sourceCode.split("\n");
        boolean inSynchronized = false;
        Set<String> sharedFields = new HashSet<>();

        // First pass: identify shared mutable fields
        for (String line : lines) {
            String trimmed = line.trim();
            // Non-final, non-volatile fields are potentially unsafe
            if (trimmed.matches(".*(?:private|protected|public)\\s+(?!final|static final)\\w+\\s+\\w+\\s*[;=].*")
                    && !trimmed.contains("volatile")) {
                Matcher m = Pattern.compile("(\\w+)\\s*[;=]").matcher(trimmed);
                if (m.find()) {
                    sharedFields.add(m.group(1));
                }
            }
        }

        // Second pass: detect unsafe access patterns
        for (int i = 0; i < lines.length; i++) {
            String line = lines[i].trim();
            int lineNum = i + 1;

            if (line.contains("synchronized")) {
                inSynchronized = true;
            }
            if (line.equals("}") && inSynchronized) {
                inSynchronized = false;
            }

            // Check for check-then-act outside synchronized
            if (!inSynchronized && line.startsWith("if (") && line.contains("!= null")) {
                Matcher m = Pattern.compile("if\\s*\\((\\w+)\\s*!=\\s*null\\)").matcher(line);
                if (m.find()) {
                    String varName = m.group(1);
                    if (sharedFields.contains(varName)) {
                        issues.add(new RaceIssue(lineNum, "check_then_act",
                            "Check-then-act on shared field '" + varName +
                            "' outside synchronized block is vulnerable to race conditions",
                            "critical"));
                    }
                }
            }

            // Check for compound operations on shared state without synchronization
            if (!inSynchronized) {
                for (String field : sharedFields) {
                    if (line.contains(field + "++") || line.contains(field + "--")
                            || line.contains(field + " += ") || line.contains(field + " -= ")) {
                        issues.add(new RaceIssue(lineNum, "compound_operation",
                            "Compound operation on shared field '" + field +
                            "' is not atomic. Use AtomicInteger or synchronize",
                            "critical"));
                    }
                }
            }

            // Check for double-checked locking without volatile
            if (line.contains("if (") && line.contains("== null")) {
                Matcher m = Pattern.compile("if\\s*\\((\\w+)\\s*==\\s*null\\)").matcher(line);
                if (m.find()) {
                    String varName = m.group(1);
                    if (sharedFields.contains(varName)) {
                        // Look for synchronized block after this check
                        boolean hasSyncBlock = false;
                        for (int j = i + 1; j < Math.min(i + 5, lines.length); j++) {
                            if (lines[j].trim().contains("synchronized")) {
                                hasSyncBlock = true;
                                break;
                            }
                        }
                        if (hasSyncBlock) {
                            // This looks like double-checked locking
                            issues.add(new RaceIssue(lineNum, "double_checked_locking",
                                "Double-checked locking on '" + varName +
                                "' requires the field to be volatile",
                                "critical"));
                        }
                    }
                }
            }
        }

        return issues;
    }
}
```

### Step 5: Invariant Violation Detection

Verify that preconditions, postconditions, and loop invariants hold throughout the code.

**Python: Invariant checker**

```python
from functools import wraps
from typing import Callable

def precondition(*conditions: Callable):
    """Decorator that checks preconditions before function execution."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for condition in conditions:
                result = condition(*args, **kwargs)
                if not result:
                    raise AssertionError(
                        f"Precondition violated for {func.__name__}: "
                        f"{condition.__doc__ or condition.__name__}"
                    )
            return func(*args, **kwargs)
        return wrapper
    return decorator


def postcondition(*conditions: Callable):
    """Decorator that checks postconditions after function execution."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            for condition in conditions:
                check = condition(result, *args, **kwargs)
                if not check:
                    raise AssertionError(
                        f"Postcondition violated for {func.__name__}: "
                        f"{condition.__doc__ or condition.__name__}"
                    )
            return result
        return wrapper
    return decorator


# Example usage
def non_negative_amount(amount, **kwargs):
    """Amount must be non-negative"""
    return amount >= 0

def balance_non_negative(result, amount, **kwargs):
    """Balance must remain non-negative after withdrawal"""
    return result >= 0

@precondition(non_negative_amount)
@postcondition(balance_non_negative)
def withdraw(amount, balance=0):
    return balance - amount


class InvariantChecker:
    """Runtime invariant checking for detecting semantic bugs."""

    def __init__(self):
        self.violations: list[dict] = []

    def check_loop_invariant(
        self,
        invariant_fn: Callable,
        description: str,
        context: dict,
    ) -> bool:
        """Check a loop invariant and record violations."""
        result = invariant_fn(context)
        if not result:
            self.violations.append({
                "type": "loop_invariant",
                "description": description,
                "context": context,
            })
        return result

    def check_data_invariant(
        self,
        data: any,
        invariant_fn: Callable,
        description: str,
    ) -> bool:
        """Check a data structure invariant."""
        result = invariant_fn(data)
        if not result:
            self.violations.append({
                "type": "data_invariant",
                "description": description,
                "data_snapshot": repr(data)[:200],
            })
        return result


# Example: checking a sorted list invariant
def is_sorted(lst):
    return all(lst[i] <= lst[i + 1] for i in range(len(lst) - 1))

checker = InvariantChecker()

def insert_sorted(lst, value):
    """Insert a value while maintaining sorted order."""
    # Precondition: list must be sorted
    checker.check_data_invariant(lst, is_sorted, "Input list must be sorted")

    # Find insertion point
    i = 0
    while i < len(lst) and lst[i] < value:
        i += 1
    lst.insert(i, value)

    # Postcondition: list must still be sorted
    checker.check_data_invariant(lst, is_sorted, "Output list must remain sorted")
    return lst
```

**JavaScript: Invariant checker**

```javascript
function precondition(conditionFn, description) {
  return function (target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args) {
      if (!conditionFn.apply(this, args)) {
        throw new Error(


…(truncated)
